Skip to content
This repository has been archived by the owner on Aug 10, 2023. It is now read-only.

Update Tailwind UI components #7

Draft
wants to merge 2 commits into
base: github-config
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Leaflet } from "./components/Leaflet";
import SideCard from "./components/SideCard";
import { Toaster } from "./components/ui/Toast";
import Footer from "./layout/Footer";
import Header from "./layout/Header";

Expand All @@ -17,6 +18,7 @@ export function App() {
</div>
</div>
</div>
<Toaster />
<Footer />
</div>
);
Expand Down
26 changes: 9 additions & 17 deletions src/components/ShapeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,16 @@ import { Input } from "./ui/Input";
import { Label } from "./ui/Label";
import { DialogFooter } from "./ui/Dialog";
import { Button } from "./ui/Button";
import { showToast } from "./ui/Toast";

export function ShapeForm() {
const drawnItems = DrawnItemsStore[0]();
const [shapes, setShapes] = ShapeStore;
const [name, setName] = createSignal("");
const [cords, setCords] = createSignal("");
const [error, setError] = createSignal("");

const onSubmit = (e: SubmitEvent) => {
e.preventDefault();
setError("");

if (!name()) {
setError("Please specify a valid name");
return;
}

try {
const polygon = new L.Polygon(JSON.parse(cords()));
Expand All @@ -33,29 +27,27 @@ export function ShapeForm() {
setCords("");
} catch (err) {
console.error(err);
setError("Could not parse JSON");
showToast({
title: "Error",
description: "Could not parse Input. Please check your input and try again.",
variant: "destructive",
});
return;
}
};

return (
<form class="space-y-4" onSubmit={onSubmit}>
{error() && (
<>
<span style="color: red;">{error()}</span>
<br />
</>
)}

<div>
<Label for="name">Name</Label>
<Input type="text" id="name" name="name" value={name()} onChange={(e) => setName(e.currentTarget.value)} />
<Input type="text" id="name" name="name" value={name()} onChange={(e) => setName(e.currentTarget.value)} required />
</div>

<div>
<Label for="cords">Cords</Label>
<Textarea id="cords" name="cords" value={cords()} onChange={(e) => setCords(e.currentTarget.value)} />
<Textarea id="cords" name="cords" value={cords()} onChange={(e) => setCords(e.currentTarget.value)} required />
</div>

<DialogFooter>
<Button type="submit">Import</Button>
</DialogFooter>
Expand Down
56 changes: 36 additions & 20 deletions src/components/ShapeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,46 @@ import { For } from "solid-js";
import { ShapeStore } from "../store";
import { Button } from "./ui/Button";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "./ui/Table";
import { IconCopy } from "@tabler/icons-solidjs";
import { Toaster, showToast } from "./ui/Toast";

export function ShapeList() {
const [shapes] = ShapeStore;

return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Element</TableHead>
<TableHead>Edit</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<For each={shapes()}>
{({ name }) => (
<TableRow>
<TableCell class="font-medium">{name}</TableCell>
<TableCell class="font-medium">
<Button />
</TableCell>
</TableRow>
)}
</For>
</TableBody>
</Table>
<>
<Table>
<TableHeader>
<TableRow>
<TableHead>Element</TableHead>
<TableHead>Edit</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<For each={shapes()}>
{({ name, layer }, i) => (
<TableRow class="group">
<TableCell>{name}</TableCell>
<TableCell class="flex justify-end opacity-0 transition duration-300 group-hover:opacity-100">
<Button
onClick={() => {
navigator.clipboard.writeText(
JSON.stringify(layer.getLatLngs()[0].map((e: any) => [e.lng, e.lat])),
);
showToast({
title: "Copied to clipboard",
description: `The coordinates (id: ${i()}) have been copied to your clipboard.`,
});
}}
>
<IconCopy />
</Button>
</TableCell>
</TableRow>
)}
</For>
</TableBody>
</Table>
</>
);
}
18 changes: 12 additions & 6 deletions src/components/SideCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, buttonVariants } from "./ui/Button";
import { Card, CardContent, CardHeader } from "./ui/Card";
import { IconSquareRoundedPlus, IconFileExport } from "@tabler/icons-solidjs";
import { IconPlus, IconDownload } from "@tabler/icons-solidjs";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/Dialog";
import { ShapeList } from "./ShapeList";
import { ShapeForm } from "./ShapeForm";
Expand All @@ -9,10 +9,10 @@ function SideCard() {
return (
<Card class="overflow-auto">
<CardHeader class="grid grid-cols-2 gap-4 space-y-0">
<div class="flex flex-col justify-start">
<div class="flex justify-start">
<Dialog>
<DialogTrigger class={buttonVariants({ variant: "ghost", size: "icon-sm" })}>
<IconSquareRoundedPlus size={20} aria-label="GitHub Icon" />
<IconPlus size={20} aria-label="Import Shape" />
</DialogTrigger>
<DialogContent>
<DialogHeader>
Expand All @@ -23,9 +23,15 @@ function SideCard() {
</DialogContent>
</Dialog>
</div>
<div class="flex flex-col justify-end">
<Button variant="ghost" size="icon-sm">
<IconFileExport size={20} aria-label="GitHub Icon" />
<div class="flex justify-end">
<Button
variant="ghost"
size="icon-sm"
onClick={() => {
alert("Not implemented yet.");
}}
>
<IconDownload size={20} aria-label="Export All" />
</Button>
</div>
</CardHeader>
Expand Down
89 changes: 89 additions & 0 deletions src/components/ui/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Component, JSX } from "solid-js";
import { splitProps } from "solid-js";

import { Toast as ToastPrimitive, toaster } from "@kobalte/core";
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import { Portal } from "solid-js/web";
import { IconX } from "@tabler/icons-solidjs";

import { cn } from "@/lib/utils";

const Toaster: Component<ToastPrimitive.ToastListProps> = (props) => {
const [, rest] = splitProps(props, ["class"]);
return (
<Portal>
<ToastPrimitive.Region>
<ToastPrimitive.List
class={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
props.class,
)}
{...rest}
/>
</ToastPrimitive.Region>
</Portal>
);
};

const toastVariants = cva(
"data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--kb-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--kb-toast-swipe-end-x)] data-[opened]:animate-in data-[closed]:animate-out data-[swipe=end]:animate-out data-[closed]:fade-out-80 data-[opened]:slide-in-from-top-full data-[opened]:sm:slide-in-from-bottom-full data-[closed]:slide-out-to-right-full",
{
variants: {
variant: {
default: "bg-background border",
destructive: "group destructive border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
type ToastVariant = NonNullable<VariantProps<typeof toastVariants>["variant"]>;

export interface ToastProps extends ToastPrimitive.ToastRootProps, VariantProps<typeof toastVariants> {}

const Toast: Component<ToastProps> = (props) => {
const [, rest] = splitProps(props, ["class", "variant"]);
return <ToastPrimitive.Root class={cn(toastVariants({ variant: props.variant }), props.class)} {...rest} />;
};

const ToastClose: Component<ToastPrimitive.ToastCloseButtonProps> = (props) => {
const [, rest] = splitProps(props, ["class"]);
return (
<ToastPrimitive.CloseButton
class={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
props.class,
)}
{...rest}
>
<IconX class="h-4 w-4" />
</ToastPrimitive.CloseButton>
);
};

const ToastTitle: Component<ToastPrimitive.ToastTitleProps> = (props) => {
const [, rest] = splitProps(props, ["class"]);
return <ToastPrimitive.Title class={cn("text-sm font-semibold", props.class)} {...rest} />;
};

const ToastDescription: Component<ToastPrimitive.ToastDescriptionProps> = (props) => {
const [, rest] = splitProps(props, ["class"]);
return <ToastPrimitive.Description class={cn("text-sm opacity-90", props.class)} {...rest} />;
};

function showToast(props: { title?: JSX.Element; description?: JSX.Element; variant?: ToastVariant }) {
toaster.show((data) => (
<Toast toastId={data.toastId} variant={props.variant}>
<div class="grid gap-1">
{props.title && <ToastTitle>{props.title}</ToastTitle>}
{props.description && <ToastDescription>{props.description}</ToastDescription>}
</div>
<ToastClose />
</Toast>
));
}

export { Toaster, Toast, ToastClose, ToastTitle, ToastDescription, showToast };