create-strayl-web-app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/template/README.md +290 -0
- package/template/components.json +24 -0
- package/template/package.json +56 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/google-logo.svg +6 -0
- package/template/public/logo-dark.ico +0 -0
- package/template/public/logo-dark.webp +0 -0
- package/template/public/logo-light.ico +0 -0
- package/template/public/logo-light.webp +0 -0
- package/template/public/manifest.json +16 -0
- package/template/public/robots.txt +3 -0
- package/template/src/components/Header.tsx +76 -0
- package/template/src/components/language-switcher.tsx +38 -0
- package/template/src/components/theme-provider.tsx +14 -0
- package/template/src/components/themed-logo.tsx +44 -0
- package/template/src/components/ui/accordion.tsx +69 -0
- package/template/src/components/ui/alert-dialog.tsx +169 -0
- package/template/src/components/ui/alert.tsx +80 -0
- package/template/src/components/ui/autocomplete.tsx +301 -0
- package/template/src/components/ui/avatar.tsx +46 -0
- package/template/src/components/ui/badge.tsx +60 -0
- package/template/src/components/ui/breadcrumb.tsx +112 -0
- package/template/src/components/ui/button.tsx +73 -0
- package/template/src/components/ui/card.tsx +244 -0
- package/template/src/components/ui/checkbox-group.tsx +16 -0
- package/template/src/components/ui/checkbox.tsx +60 -0
- package/template/src/components/ui/collapsible.tsx +45 -0
- package/template/src/components/ui/combobox.tsx +415 -0
- package/template/src/components/ui/command.tsx +264 -0
- package/template/src/components/ui/dialog.tsx +196 -0
- package/template/src/components/ui/empty.tsx +127 -0
- package/template/src/components/ui/field.tsx +74 -0
- package/template/src/components/ui/fieldset.tsx +29 -0
- package/template/src/components/ui/form.tsx +17 -0
- package/template/src/components/ui/frame.tsx +82 -0
- package/template/src/components/ui/group.tsx +97 -0
- package/template/src/components/ui/input-group.tsx +101 -0
- package/template/src/components/ui/input.tsx +66 -0
- package/template/src/components/ui/kbd.tsx +28 -0
- package/template/src/components/ui/label.tsx +28 -0
- package/template/src/components/ui/menu.tsx +310 -0
- package/template/src/components/ui/meter.tsx +67 -0
- package/template/src/components/ui/number-field.tsx +160 -0
- package/template/src/components/ui/pagination.tsx +136 -0
- package/template/src/components/ui/popover.tsx +104 -0
- package/template/src/components/ui/preview-card.tsx +55 -0
- package/template/src/components/ui/progress.tsx +81 -0
- package/template/src/components/ui/radio-group.tsx +36 -0
- package/template/src/components/ui/scroll-area.tsx +64 -0
- package/template/src/components/ui/select.tsx +180 -0
- package/template/src/components/ui/separator.tsx +23 -0
- package/template/src/components/ui/sheet.tsx +203 -0
- package/template/src/components/ui/sidebar.tsx +743 -0
- package/template/src/components/ui/skeleton.tsx +16 -0
- package/template/src/components/ui/slider.tsx +74 -0
- package/template/src/components/ui/spinner.tsx +18 -0
- package/template/src/components/ui/switch.tsx +27 -0
- package/template/src/components/ui/table.tsx +126 -0
- package/template/src/components/ui/tabs.tsx +87 -0
- package/template/src/components/ui/textarea.tsx +51 -0
- package/template/src/components/ui/toast.tsx +269 -0
- package/template/src/components/ui/toggle-group.tsx +102 -0
- package/template/src/components/ui/toggle.tsx +45 -0
- package/template/src/components/ui/toolbar.tsx +83 -0
- package/template/src/components/ui/tooltip.tsx +65 -0
- package/template/src/hooks/use-mobile.ts +21 -0
- package/template/src/lib/i18n.ts +70 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/routeTree.gen.ts +68 -0
- package/template/src/router.tsx +17 -0
- package/template/src/routes/__root.tsx +62 -0
- package/template/src/routes/index.tsx +71 -0
- package/template/src/styles.css +121 -0
- package/template/tsconfig.json +28 -0
- package/template/vite.config.ts +30 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className={cn(
|
|
7
|
+
"animate-skeleton rounded-sm [--skeleton-highlight:--alpha(var(--color-white)/64%)] [background:linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)_var(--color-muted)_0_0/200%_100%_fixed] dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]",
|
|
8
|
+
className,
|
|
9
|
+
)}
|
|
10
|
+
data-slot="skeleton"
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { Skeleton };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Slider as SliderPrimitive } from "@base-ui/react/slider";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
function Slider({
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
defaultValue,
|
|
12
|
+
value,
|
|
13
|
+
min = 0,
|
|
14
|
+
max = 100,
|
|
15
|
+
...props
|
|
16
|
+
}: SliderPrimitive.Root.Props) {
|
|
17
|
+
const _values = React.useMemo(() => {
|
|
18
|
+
if (value !== undefined) {
|
|
19
|
+
return Array.isArray(value) ? value : [value];
|
|
20
|
+
}
|
|
21
|
+
if (defaultValue !== undefined) {
|
|
22
|
+
return Array.isArray(defaultValue) ? defaultValue : [defaultValue];
|
|
23
|
+
}
|
|
24
|
+
return [min];
|
|
25
|
+
}, [value, defaultValue, min]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<SliderPrimitive.Root
|
|
29
|
+
className={cn("data-[orientation=horizontal]:w-full", className)}
|
|
30
|
+
defaultValue={defaultValue}
|
|
31
|
+
max={max}
|
|
32
|
+
min={min}
|
|
33
|
+
thumbAlignment="edge"
|
|
34
|
+
value={value}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
<SliderPrimitive.Control
|
|
39
|
+
className="flex touch-none select-none data-disabled:pointer-events-none data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:min-w-44 data-[orientation=vertical]:flex-col data-disabled:opacity-64"
|
|
40
|
+
data-slot="slider-control"
|
|
41
|
+
>
|
|
42
|
+
<SliderPrimitive.Track
|
|
43
|
+
className="relative grow select-none before:absolute before:rounded-full before:bg-input data-[orientation=horizontal]:h-1 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1 data-[orientation=horizontal]:before:inset-x-0.5 data-[orientation=vertical]:before:inset-x-0 data-[orientation=horizontal]:before:inset-y-0 data-[orientation=vertical]:before:inset-y-0.5"
|
|
44
|
+
data-slot="slider-track"
|
|
45
|
+
>
|
|
46
|
+
<SliderPrimitive.Indicator
|
|
47
|
+
className="select-none rounded-full bg-primary data-[orientation=horizontal]:ms-0.5 data-[orientation=vertical]:mb-0.5"
|
|
48
|
+
data-slot="slider-indicator"
|
|
49
|
+
/>
|
|
50
|
+
{Array.from({ length: _values.length }, (_, index) => (
|
|
51
|
+
<SliderPrimitive.Thumb
|
|
52
|
+
className="block size-5 shrink-0 select-none rounded-full border border-input bg-white not-dark:bg-clip-padding shadow-xs/5 outline-none transition-[box-shadow,scale] before:absolute before:inset-0 before:rounded-full before:shadow-[0_1px_--theme(--color-black/6%)] has-focus-visible:ring-[3px] has-focus-visible:ring-ring/24 data-dragging:scale-120 sm:size-4 dark:border-background dark:has-focus-visible:ring-ring/48 [:has(*:focus-visible),[data-dragging]]:shadow-none"
|
|
53
|
+
data-slot="slider-thumb"
|
|
54
|
+
index={index}
|
|
55
|
+
key={String(index)}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</SliderPrimitive.Track>
|
|
59
|
+
</SliderPrimitive.Control>
|
|
60
|
+
</SliderPrimitive.Root>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function SliderValue({ className, ...props }: SliderPrimitive.Value.Props) {
|
|
65
|
+
return (
|
|
66
|
+
<SliderPrimitive.Value
|
|
67
|
+
className={cn("flex justify-end text-sm", className)}
|
|
68
|
+
data-slot="slider-value"
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { Slider, SliderValue };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
function Spinner({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<typeof Loader2Icon>) {
|
|
8
|
+
return (
|
|
9
|
+
<Loader2Icon
|
|
10
|
+
aria-label="Loading"
|
|
11
|
+
className={cn("animate-spin", className)}
|
|
12
|
+
role="status"
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Spinner };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Switch({ className, ...props }: SwitchPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<SwitchPrimitive.Root
|
|
10
|
+
className={cn(
|
|
11
|
+
"inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(5)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(4)]",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
data-slot="switch"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<SwitchPrimitive.Thumb
|
|
18
|
+
className={cn(
|
|
19
|
+
"pointer-events-none block aspect-square h-full origin-left in-[[role=switch]:active,[data-slot=label]:active]:not-data-disabled:scale-x-110 in-[[role=switch]:active,[data-slot=label]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.1)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s] data-checked:origin-[var(--thumb-size)_50%] data-checked:translate-x-[calc(var(--thumb-size)-4px)]",
|
|
20
|
+
)}
|
|
21
|
+
data-slot="switch-thumb"
|
|
22
|
+
/>
|
|
23
|
+
</SwitchPrimitive.Root>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Switch };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
className="relative w-full overflow-x-auto"
|
|
9
|
+
data-slot="table-container"
|
|
10
|
+
>
|
|
11
|
+
<table
|
|
12
|
+
className={cn(
|
|
13
|
+
"w-full caption-bottom in-data-[slot=frame]:border-separate in-data-[slot=frame]:border-spacing-0 text-sm",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
data-slot="table"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
24
|
+
return (
|
|
25
|
+
<thead
|
|
26
|
+
className={cn(
|
|
27
|
+
"[&_tr]:border-b in-data-[slot=frame]:**:[th]:h-9 in-data-[slot=frame]:*:[tr]:border-none in-data-[slot=frame]:*:[tr]:hover:bg-transparent",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
data-slot="table-header"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
37
|
+
return (
|
|
38
|
+
<tbody
|
|
39
|
+
className={cn(
|
|
40
|
+
"relative in-data-[slot=frame]:rounded-xl in-data-[slot=frame]:shadow-xs/5 before:pointer-events-none before:absolute before:inset-px not-in-data-[slot=frame]:before:hidden before:rounded-[calc(var(--radius-xl)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] dark:before:shadow-[0_-1px_--theme(--color-white/8%)] [&_tr:last-child]:border-0 in-data-[slot=frame]:*:[tr]:border-0 in-data-[slot=frame]:*:[tr]:*:[td]:border-b in-data-[slot=frame]:*:[tr]:*:[td]:bg-background in-data-[slot=frame]:*:[tr]:*:[td]:bg-clip-padding in-data-[slot=frame]:*:[tr]:first:*:[td]:first:rounded-ss-xl in-data-[slot=frame]:*:[tr]:*:[td]:first:border-s in-data-[slot=frame]:*:[tr]:first:*:[td]:border-t in-data-[slot=frame]:*:[tr]:last:*:[td]:last:rounded-ee-xl in-data-[slot=frame]:*:[tr]:*:[td]:last:border-e in-data-[slot=frame]:*:[tr]:first:*:[td]:last:rounded-se-xl in-data-[slot=frame]:*:[tr]:last:*:[td]:first:rounded-es-xl in-data-[slot=frame]:*:[tr]:hover:*:[td]:bg-transparent in-data-[slot=frame]:*:[tr]:data-[state=selected]:*:[td]:bg-muted/72",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
data-slot="table-body"
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
50
|
+
return (
|
|
51
|
+
<tfoot
|
|
52
|
+
className={cn(
|
|
53
|
+
"border-t in-data-[slot=frame]:border-none bg-muted/72 in-data-[slot=frame]:bg-transparent font-medium [&>tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
data-slot="table-footer"
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
63
|
+
return (
|
|
64
|
+
<tr
|
|
65
|
+
className={cn(
|
|
66
|
+
"border-b transition-colors hover:bg-muted/72 in-data-[slot=frame]:hover:bg-transparent data-[state=selected]:bg-muted/72 in-data-[slot=frame]:data-[state=selected]:bg-transparent",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
data-slot="table-row"
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
76
|
+
return (
|
|
77
|
+
<th
|
|
78
|
+
className={cn(
|
|
79
|
+
"h-10 whitespace-nowrap px-2.5 text-left align-middle font-medium text-muted-foreground leading-none has-[[role=checkbox]]:w-px has-[[role=checkbox]]:pe-0",
|
|
80
|
+
className,
|
|
81
|
+
)}
|
|
82
|
+
data-slot="table-head"
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
89
|
+
return (
|
|
90
|
+
<td
|
|
91
|
+
className={cn(
|
|
92
|
+
"whitespace-nowrap p-2.5 align-middle leading-none in-data-[slot=frame]:first:p-[calc(--spacing(2.5)-1px)] in-data-[slot=frame]:last:p-[calc(--spacing(2.5)-1px)] has-[[role=checkbox]]:pe-0",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
data-slot="table-cell"
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function TableCaption({
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentProps<"caption">) {
|
|
105
|
+
return (
|
|
106
|
+
<caption
|
|
107
|
+
className={cn(
|
|
108
|
+
"in-data-[slot=frame]:my-4 mt-4 text-muted-foreground text-sm",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
data-slot="table-caption"
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
Table,
|
|
119
|
+
TableHeader,
|
|
120
|
+
TableBody,
|
|
121
|
+
TableFooter,
|
|
122
|
+
TableHead,
|
|
123
|
+
TableRow,
|
|
124
|
+
TableCell,
|
|
125
|
+
TableCaption,
|
|
126
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
type TabsVariant = "default" | "underline";
|
|
8
|
+
|
|
9
|
+
function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<TabsPrimitive.Root
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex flex-col gap-2 data-[orientation=vertical]:flex-row",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TabsList({
|
|
23
|
+
variant = "default",
|
|
24
|
+
className,
|
|
25
|
+
children,
|
|
26
|
+
...props
|
|
27
|
+
}: TabsPrimitive.List.Props & {
|
|
28
|
+
variant?: TabsVariant;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<TabsPrimitive.List
|
|
32
|
+
className={cn(
|
|
33
|
+
"relative z-0 flex w-fit items-center justify-center gap-x-0.5 text-muted-foreground",
|
|
34
|
+
"data-[orientation=vertical]:flex-col",
|
|
35
|
+
variant === "default"
|
|
36
|
+
? "rounded-lg bg-muted p-0.5 text-muted-foreground/72"
|
|
37
|
+
: "data-[orientation=vertical]:px-1 data-[orientation=horizontal]:py-1 *:data-[slot=tabs-tab]:hover:bg-accent",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-slot="tabs-list"
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<TabsPrimitive.Indicator
|
|
45
|
+
className={cn(
|
|
46
|
+
"-translate-y-(--active-tab-bottom) absolute bottom-0 left-0 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) transition-[width,translate] duration-200 ease-in-out",
|
|
47
|
+
variant === "underline"
|
|
48
|
+
? "data-[orientation=vertical]:-translate-x-px z-10 bg-primary data-[orientation=horizontal]:h-0.5 data-[orientation=vertical]:w-0.5 data-[orientation=horizontal]:translate-y-px"
|
|
49
|
+
: "-z-1 rounded-md bg-background shadow-sm/5 dark:bg-input",
|
|
50
|
+
)}
|
|
51
|
+
data-slot="tab-indicator"
|
|
52
|
+
/>
|
|
53
|
+
</TabsPrimitive.List>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function TabsTab({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
58
|
+
return (
|
|
59
|
+
<TabsPrimitive.Tab
|
|
60
|
+
className={cn(
|
|
61
|
+
"[&_svg]:-mx-0.5 relative flex h-9 shrink-0 grow cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-[calc(--spacing(2.5)-1px)] font-medium text-base outline-none transition-[color,background-color,box-shadow] hover:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring data-disabled:pointer-events-none data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start data-active:text-foreground data-disabled:opacity-64 sm:h-8 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
data-slot="tabs-tab"
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function TabsPanel({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
71
|
+
return (
|
|
72
|
+
<TabsPrimitive.Panel
|
|
73
|
+
className={cn("flex-1 outline-none", className)}
|
|
74
|
+
data-slot="tabs-content"
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
Tabs,
|
|
82
|
+
TabsList,
|
|
83
|
+
TabsTab,
|
|
84
|
+
TabsTab as TabsTrigger,
|
|
85
|
+
TabsPanel,
|
|
86
|
+
TabsPanel as TabsContent,
|
|
87
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Field as FieldPrimitive } from "@base-ui/react/field";
|
|
4
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
type TextareaProps = React.ComponentProps<"textarea"> & {
|
|
10
|
+
size?: "sm" | "default" | "lg" | number;
|
|
11
|
+
unstyled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function Textarea({
|
|
15
|
+
className,
|
|
16
|
+
size = "default",
|
|
17
|
+
unstyled = false,
|
|
18
|
+
...props
|
|
19
|
+
}: TextareaProps) {
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
className={
|
|
23
|
+
cn(
|
|
24
|
+
!unstyled &&
|
|
25
|
+
"relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/6%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
26
|
+
className,
|
|
27
|
+
) || undefined
|
|
28
|
+
}
|
|
29
|
+
data-size={size}
|
|
30
|
+
data-slot="textarea-control"
|
|
31
|
+
>
|
|
32
|
+
<FieldPrimitive.Control
|
|
33
|
+
render={(defaultProps) => (
|
|
34
|
+
<textarea
|
|
35
|
+
className={cn(
|
|
36
|
+
"field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5",
|
|
37
|
+
size === "sm" &&
|
|
38
|
+
"min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",
|
|
39
|
+
size === "lg" &&
|
|
40
|
+
"min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5",
|
|
41
|
+
)}
|
|
42
|
+
data-slot="textarea"
|
|
43
|
+
{...mergeProps(defaultProps, props)}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
</span>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Textarea, type TextareaProps };
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toast } from "@base-ui/react/toast";
|
|
4
|
+
import {
|
|
5
|
+
CircleAlertIcon,
|
|
6
|
+
CircleCheckIcon,
|
|
7
|
+
InfoIcon,
|
|
8
|
+
LoaderCircleIcon,
|
|
9
|
+
TriangleAlertIcon,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import { buttonVariants } from "@/components/ui/button";
|
|
14
|
+
|
|
15
|
+
const toastManager = Toast.createToastManager();
|
|
16
|
+
const anchoredToastManager = Toast.createToastManager();
|
|
17
|
+
|
|
18
|
+
const TOAST_ICONS = {
|
|
19
|
+
error: CircleAlertIcon,
|
|
20
|
+
info: InfoIcon,
|
|
21
|
+
loading: LoaderCircleIcon,
|
|
22
|
+
success: CircleCheckIcon,
|
|
23
|
+
warning: TriangleAlertIcon,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
type ToastPosition =
|
|
27
|
+
| "top-left"
|
|
28
|
+
| "top-center"
|
|
29
|
+
| "top-right"
|
|
30
|
+
| "bottom-left"
|
|
31
|
+
| "bottom-center"
|
|
32
|
+
| "bottom-right";
|
|
33
|
+
|
|
34
|
+
interface ToastProviderProps extends Toast.Provider.Props {
|
|
35
|
+
position?: ToastPosition;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ToastProvider({
|
|
39
|
+
children,
|
|
40
|
+
position = "bottom-right",
|
|
41
|
+
...props
|
|
42
|
+
}: ToastProviderProps) {
|
|
43
|
+
return (
|
|
44
|
+
<Toast.Provider toastManager={toastManager} {...props}>
|
|
45
|
+
{children}
|
|
46
|
+
<Toasts position={position} />
|
|
47
|
+
</Toast.Provider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Toasts({ position = "bottom-right" }: { position: ToastPosition }) {
|
|
52
|
+
const { toasts } = Toast.useToastManager();
|
|
53
|
+
const isTop = position.startsWith("top");
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Toast.Portal data-slot="toast-portal">
|
|
57
|
+
<Toast.Viewport
|
|
58
|
+
className={cn(
|
|
59
|
+
"fixed z-50 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-90 [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]",
|
|
60
|
+
// Vertical positioning
|
|
61
|
+
"data-[position*=top]:top-(--toast-inset)",
|
|
62
|
+
"data-[position*=bottom]:bottom-(--toast-inset)",
|
|
63
|
+
// Horizontal positioning
|
|
64
|
+
"data-[position*=left]:left-(--toast-inset)",
|
|
65
|
+
"data-[position*=right]:right-(--toast-inset)",
|
|
66
|
+
"data-[position*=center]:-translate-x-1/2 data-[position*=center]:left-1/2",
|
|
67
|
+
)}
|
|
68
|
+
data-position={position}
|
|
69
|
+
data-slot="toast-viewport"
|
|
70
|
+
>
|
|
71
|
+
{toasts.map((toast) => {
|
|
72
|
+
const Icon = toast.type
|
|
73
|
+
? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
|
|
74
|
+
: null;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Toast.Root
|
|
78
|
+
className={cn(
|
|
79
|
+
"absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
80
|
+
// Base positioning using data-position
|
|
81
|
+
"data-[position*=right]:right-0 data-[position*=right]:left-auto",
|
|
82
|
+
"data-[position*=left]:right-auto data-[position*=left]:left-0",
|
|
83
|
+
"data-[position*=center]:right-0 data-[position*=center]:left-0",
|
|
84
|
+
"data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top",
|
|
85
|
+
"data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom",
|
|
86
|
+
// Gap fill for hover
|
|
87
|
+
"after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full",
|
|
88
|
+
"data-[position*=top]:after:top-full",
|
|
89
|
+
"data-[position*=bottom]:after:bottom-full",
|
|
90
|
+
// Define some variables
|
|
91
|
+
"[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]",
|
|
92
|
+
// Define offset-y variable
|
|
93
|
+
"data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]",
|
|
94
|
+
"data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]",
|
|
95
|
+
// Default state transform
|
|
96
|
+
"data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
|
|
97
|
+
"data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
|
|
98
|
+
// Limited state
|
|
99
|
+
"data-limited:opacity-0",
|
|
100
|
+
// Expanded state
|
|
101
|
+
"data-expanded:h-(--toast-height)",
|
|
102
|
+
"data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]",
|
|
103
|
+
// Starting and ending animations
|
|
104
|
+
"data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]",
|
|
105
|
+
"data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]",
|
|
106
|
+
"data-ending-style:opacity-0",
|
|
107
|
+
// Ending animations (direction-aware)
|
|
108
|
+
"data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]",
|
|
109
|
+
"data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
|
|
110
|
+
"data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
|
|
111
|
+
"data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
|
|
112
|
+
"data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
|
|
113
|
+
// Ending animations (expanded)
|
|
114
|
+
"data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
|
|
115
|
+
"data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
|
|
116
|
+
"data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
|
|
117
|
+
"data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
|
|
118
|
+
)}
|
|
119
|
+
data-position={position}
|
|
120
|
+
key={toast.id}
|
|
121
|
+
swipeDirection={
|
|
122
|
+
position.includes("center")
|
|
123
|
+
? [isTop ? "up" : "down"]
|
|
124
|
+
: position.includes("left")
|
|
125
|
+
? ["left", isTop ? "up" : "down"]
|
|
126
|
+
: ["right", isTop ? "up" : "down"]
|
|
127
|
+
}
|
|
128
|
+
toast={toast}
|
|
129
|
+
>
|
|
130
|
+
<Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-behind:not-data-expanded:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100">
|
|
131
|
+
<div className="flex gap-2">
|
|
132
|
+
{Icon && (
|
|
133
|
+
<div
|
|
134
|
+
className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
135
|
+
data-slot="toast-icon"
|
|
136
|
+
>
|
|
137
|
+
<Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
<div className="flex flex-col gap-0.5">
|
|
142
|
+
<Toast.Title
|
|
143
|
+
className="font-medium"
|
|
144
|
+
data-slot="toast-title"
|
|
145
|
+
/>
|
|
146
|
+
<Toast.Description
|
|
147
|
+
className="text-muted-foreground"
|
|
148
|
+
data-slot="toast-description"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
{toast.actionProps && (
|
|
153
|
+
<Toast.Action
|
|
154
|
+
className={buttonVariants({ size: "xs" })}
|
|
155
|
+
data-slot="toast-action"
|
|
156
|
+
>
|
|
157
|
+
{toast.actionProps.children}
|
|
158
|
+
</Toast.Action>
|
|
159
|
+
)}
|
|
160
|
+
</Toast.Content>
|
|
161
|
+
</Toast.Root>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</Toast.Viewport>
|
|
165
|
+
</Toast.Portal>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function AnchoredToastProvider({ children, ...props }: Toast.Provider.Props) {
|
|
170
|
+
return (
|
|
171
|
+
<Toast.Provider toastManager={anchoredToastManager} {...props}>
|
|
172
|
+
{children}
|
|
173
|
+
<AnchoredToasts />
|
|
174
|
+
</Toast.Provider>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function AnchoredToasts() {
|
|
179
|
+
const { toasts } = Toast.useToastManager();
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Toast.Portal data-slot="toast-portal-anchored">
|
|
183
|
+
<Toast.Viewport
|
|
184
|
+
className="outline-none"
|
|
185
|
+
data-slot="toast-viewport-anchored"
|
|
186
|
+
>
|
|
187
|
+
{toasts.map((toast) => {
|
|
188
|
+
const Icon = toast.type
|
|
189
|
+
? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
|
|
190
|
+
: null;
|
|
191
|
+
const tooltipStyle =
|
|
192
|
+
(toast.data as { tooltipStyle?: boolean })?.tooltipStyle ?? false;
|
|
193
|
+
const positionerProps = toast.positionerProps;
|
|
194
|
+
|
|
195
|
+
if (!positionerProps?.anchor) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Toast.Positioner
|
|
201
|
+
className="z-50 max-w-[min(--spacing(64),var(--available-width))]"
|
|
202
|
+
data-slot="toast-positioner"
|
|
203
|
+
key={toast.id}
|
|
204
|
+
sideOffset={positionerProps.sideOffset ?? 4}
|
|
205
|
+
toast={toast}
|
|
206
|
+
>
|
|
207
|
+
<Toast.Root
|
|
208
|
+
className={cn(
|
|
209
|
+
"relative text-balance border bg-popover not-dark:bg-clip-padding text-popover-foreground text-xs transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
210
|
+
tooltipStyle
|
|
211
|
+
? "rounded-md shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]"
|
|
212
|
+
: "rounded-lg shadow-lg/5 before:rounded-[calc(var(--radius-lg)-1px)]",
|
|
213
|
+
)}
|
|
214
|
+
data-slot="toast-popup"
|
|
215
|
+
toast={toast}
|
|
216
|
+
>
|
|
217
|
+
{tooltipStyle ? (
|
|
218
|
+
<Toast.Content className="pointer-events-auto px-2 py-1">
|
|
219
|
+
<Toast.Title data-slot="toast-title" />
|
|
220
|
+
</Toast.Content>
|
|
221
|
+
) : (
|
|
222
|
+
<Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm">
|
|
223
|
+
<div className="flex gap-2">
|
|
224
|
+
{Icon && (
|
|
225
|
+
<div
|
|
226
|
+
className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
227
|
+
data-slot="toast-icon"
|
|
228
|
+
>
|
|
229
|
+
<Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
<div className="flex flex-col gap-0.5">
|
|
234
|
+
<Toast.Title
|
|
235
|
+
className="font-medium"
|
|
236
|
+
data-slot="toast-title"
|
|
237
|
+
/>
|
|
238
|
+
<Toast.Description
|
|
239
|
+
className="text-muted-foreground"
|
|
240
|
+
data-slot="toast-description"
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
{toast.actionProps && (
|
|
245
|
+
<Toast.Action
|
|
246
|
+
className={buttonVariants({ size: "xs" })}
|
|
247
|
+
data-slot="toast-action"
|
|
248
|
+
>
|
|
249
|
+
{toast.actionProps.children}
|
|
250
|
+
</Toast.Action>
|
|
251
|
+
)}
|
|
252
|
+
</Toast.Content>
|
|
253
|
+
)}
|
|
254
|
+
</Toast.Root>
|
|
255
|
+
</Toast.Positioner>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</Toast.Viewport>
|
|
259
|
+
</Toast.Portal>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export {
|
|
264
|
+
ToastProvider,
|
|
265
|
+
type ToastPosition,
|
|
266
|
+
toastManager,
|
|
267
|
+
AnchoredToastProvider,
|
|
268
|
+
anchoredToastManager,
|
|
269
|
+
};
|