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,44 @@
|
|
|
1
|
+
import { useTheme } from "next-themes"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
|
|
4
|
+
interface ThemedLogoProps {
|
|
5
|
+
width?: number
|
|
6
|
+
height?: number
|
|
7
|
+
className?: string
|
|
8
|
+
alt?: string
|
|
9
|
+
inverted?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ThemedLogo({
|
|
13
|
+
width = 24,
|
|
14
|
+
height = 24,
|
|
15
|
+
className = "",
|
|
16
|
+
alt = "Strayl Logo",
|
|
17
|
+
inverted = false,
|
|
18
|
+
}: ThemedLogoProps) {
|
|
19
|
+
const { resolvedTheme } = useTheme()
|
|
20
|
+
const [mounted, setMounted] = useState(false)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setMounted(true)
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
if (!mounted) {
|
|
27
|
+
return <div style={{ width, height }} className={className} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isDark = resolvedTheme === "dark"
|
|
31
|
+
const logoSrc = inverted
|
|
32
|
+
? (isDark ? "/logo-dark.webp" : "/logo-light.webp")
|
|
33
|
+
: (isDark ? "/logo-light.webp" : "/logo-dark.webp")
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<img
|
|
37
|
+
src={logoSrc}
|
|
38
|
+
alt={alt}
|
|
39
|
+
width={width}
|
|
40
|
+
height={height}
|
|
41
|
+
className={className}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
4
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
function Accordion(props: AccordionPrimitive.Root.Props) {
|
|
9
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
13
|
+
return (
|
|
14
|
+
<AccordionPrimitive.Item
|
|
15
|
+
className={cn("border-b last:border-b-0", className)}
|
|
16
|
+
data-slot="accordion-item"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function AccordionTrigger({
|
|
23
|
+
className,
|
|
24
|
+
children,
|
|
25
|
+
...props
|
|
26
|
+
}: AccordionPrimitive.Trigger.Props) {
|
|
27
|
+
return (
|
|
28
|
+
<AccordionPrimitive.Header className="flex">
|
|
29
|
+
<AccordionPrimitive.Trigger
|
|
30
|
+
className={cn(
|
|
31
|
+
"flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left font-medium text-sm outline-none transition-all focus-visible:ring-[3px] focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
data-slot="accordion-trigger"
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
<ChevronDownIcon
|
|
39
|
+
className="pointer-events-none size-4 shrink-0 translate-y-0.5 opacity-80 transition-transform duration-200 ease-in-out"
|
|
40
|
+
data-slot="accordion-indicator"
|
|
41
|
+
/>
|
|
42
|
+
</AccordionPrimitive.Trigger>
|
|
43
|
+
</AccordionPrimitive.Header>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function AccordionPanel({
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
...props
|
|
51
|
+
}: AccordionPrimitive.Panel.Props) {
|
|
52
|
+
return (
|
|
53
|
+
<AccordionPrimitive.Panel
|
|
54
|
+
className="h-(--accordion-panel-height) overflow-hidden text-muted-foreground text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0"
|
|
55
|
+
data-slot="accordion-panel"
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
59
|
+
</AccordionPrimitive.Panel>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
Accordion,
|
|
65
|
+
AccordionItem,
|
|
66
|
+
AccordionTrigger,
|
|
67
|
+
AccordionPanel,
|
|
68
|
+
AccordionPanel as AccordionContent,
|
|
69
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const AlertDialogCreateHandle = AlertDialogPrimitive.createHandle;
|
|
8
|
+
|
|
9
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
12
|
+
|
|
13
|
+
function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) {
|
|
14
|
+
return (
|
|
15
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function AlertDialogBackdrop({
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}: AlertDialogPrimitive.Backdrop.Props) {
|
|
23
|
+
return (
|
|
24
|
+
<AlertDialogPrimitive.Backdrop
|
|
25
|
+
className={cn(
|
|
26
|
+
"fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 ease-out data-ending-style:opacity-0 data-starting-style:opacity-0",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
data-slot="alert-dialog-backdrop"
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AlertDialogViewport({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: AlertDialogPrimitive.Viewport.Props) {
|
|
39
|
+
return (
|
|
40
|
+
<AlertDialogPrimitive.Viewport
|
|
41
|
+
className={cn(
|
|
42
|
+
"fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
data-slot="alert-dialog-viewport"
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function AlertDialogPopup({
|
|
52
|
+
className,
|
|
53
|
+
bottomStickOnMobile = true,
|
|
54
|
+
...props
|
|
55
|
+
}: AlertDialogPrimitive.Popup.Props & {
|
|
56
|
+
bottomStickOnMobile?: boolean;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<AlertDialogPortal>
|
|
60
|
+
<AlertDialogBackdrop />
|
|
61
|
+
<AlertDialogViewport
|
|
62
|
+
className={cn(
|
|
63
|
+
bottomStickOnMobile &&
|
|
64
|
+
"max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12",
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<AlertDialogPrimitive.Popup
|
|
68
|
+
className={cn(
|
|
69
|
+
"-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top 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%)]",
|
|
70
|
+
bottomStickOnMobile &&
|
|
71
|
+
"max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
data-slot="alert-dialog-popup"
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
</AlertDialogViewport>
|
|
78
|
+
</AlertDialogPortal>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function AlertDialogHeader({
|
|
83
|
+
className,
|
|
84
|
+
...props
|
|
85
|
+
}: React.ComponentProps<"div">) {
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
data-slot="alert-dialog-header"
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function AlertDialogFooter({
|
|
99
|
+
className,
|
|
100
|
+
variant = "default",
|
|
101
|
+
...props
|
|
102
|
+
}: React.ComponentProps<"div"> & {
|
|
103
|
+
variant?: "default" | "bare";
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
className={cn(
|
|
108
|
+
"flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
|
|
109
|
+
variant === "default" && "border-t bg-muted/72 py-4",
|
|
110
|
+
variant === "bare" && "pb-6",
|
|
111
|
+
className,
|
|
112
|
+
)}
|
|
113
|
+
data-slot="alert-dialog-footer"
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AlertDialogTitle({
|
|
120
|
+
className,
|
|
121
|
+
...props
|
|
122
|
+
}: AlertDialogPrimitive.Title.Props) {
|
|
123
|
+
return (
|
|
124
|
+
<AlertDialogPrimitive.Title
|
|
125
|
+
className={cn(
|
|
126
|
+
"font-heading font-semibold text-xl leading-none",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
data-slot="alert-dialog-title"
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function AlertDialogDescription({
|
|
136
|
+
className,
|
|
137
|
+
...props
|
|
138
|
+
}: AlertDialogPrimitive.Description.Props) {
|
|
139
|
+
return (
|
|
140
|
+
<AlertDialogPrimitive.Description
|
|
141
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
142
|
+
data-slot="alert-dialog-description"
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function AlertDialogClose(props: AlertDialogPrimitive.Close.Props) {
|
|
149
|
+
return (
|
|
150
|
+
<AlertDialogPrimitive.Close data-slot="alert-dialog-close" {...props} />
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
AlertDialogCreateHandle,
|
|
156
|
+
AlertDialog,
|
|
157
|
+
AlertDialogPortal,
|
|
158
|
+
AlertDialogBackdrop,
|
|
159
|
+
AlertDialogBackdrop as AlertDialogOverlay,
|
|
160
|
+
AlertDialogTrigger,
|
|
161
|
+
AlertDialogPopup,
|
|
162
|
+
AlertDialogPopup as AlertDialogContent,
|
|
163
|
+
AlertDialogHeader,
|
|
164
|
+
AlertDialogFooter,
|
|
165
|
+
AlertDialogTitle,
|
|
166
|
+
AlertDialogDescription,
|
|
167
|
+
AlertDialogClose,
|
|
168
|
+
AlertDialogViewport,
|
|
169
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"relative grid w-full items-start gap-x-2 gap-y-0.5 rounded-xl border px-3.5 py-3 text-card-foreground text-sm has-[>svg]:has-data-[slot=alert-action]:grid-cols-[calc(var(--spacing)*4)_1fr_auto] has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-[>svg]:gap-x-2 [&>svg]:h-lh [&>svg]:w-4",
|
|
8
|
+
{
|
|
9
|
+
defaultVariants: {
|
|
10
|
+
variant: "default",
|
|
11
|
+
},
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default:
|
|
15
|
+
"bg-transparent dark:bg-input/32 [&>svg]:text-muted-foreground",
|
|
16
|
+
error:
|
|
17
|
+
"border-destructive/32 bg-destructive/4 [&>svg]:text-destructive",
|
|
18
|
+
info: "border-info/32 bg-info/4 [&>svg]:text-info",
|
|
19
|
+
success: "border-success/32 bg-success/4 [&>svg]:text-success",
|
|
20
|
+
warning: "border-warning/32 bg-warning/4 [&>svg]:text-warning",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function Alert({
|
|
27
|
+
className,
|
|
28
|
+
variant,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={cn(alertVariants({ variant }), className)}
|
|
34
|
+
data-slot="alert"
|
|
35
|
+
role="alert"
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn("font-medium [svg~&]:col-start-2", className)}
|
|
45
|
+
data-slot="alert-title"
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function AlertDescription({
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<"div">) {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className={cn(
|
|
58
|
+
"flex flex-col gap-2.5 text-muted-foreground [svg~&]:col-start-2",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
data-slot="alert-description"
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
"flex gap-1 max-sm:col-start-2 max-sm:mt-2 sm:row-start-1 sm:row-end-3 sm:self-center sm:[[data-slot=alert-description]~&]:col-start-2 sm:[[data-slot=alert-title]~&]:col-start-2 sm:[svg~&]:col-start-2 sm:[svg~[data-slot=alert-description]~&]:col-start-3 sm:[svg~[data-slot=alert-title]~&]:col-start-3",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
data-slot="alert-action"
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";
|
|
4
|
+
import { ChevronsUpDownIcon, XIcon } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
9
|
+
|
|
10
|
+
const Autocomplete = AutocompletePrimitive.Root;
|
|
11
|
+
|
|
12
|
+
function AutocompleteInput({
|
|
13
|
+
className,
|
|
14
|
+
showTrigger = false,
|
|
15
|
+
showClear = false,
|
|
16
|
+
startAddon,
|
|
17
|
+
size,
|
|
18
|
+
...props
|
|
19
|
+
}: Omit<AutocompletePrimitive.Input.Props, "size"> & {
|
|
20
|
+
showTrigger?: boolean;
|
|
21
|
+
showClear?: boolean;
|
|
22
|
+
startAddon?: React.ReactNode;
|
|
23
|
+
size?: "sm" | "default" | "lg" | number;
|
|
24
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
25
|
+
}) {
|
|
26
|
+
const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64">
|
|
30
|
+
{startAddon && (
|
|
31
|
+
<div
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
|
|
34
|
+
data-slot="autocomplete-start-addon"
|
|
35
|
+
>
|
|
36
|
+
{startAddon}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
<AutocompletePrimitive.Input
|
|
40
|
+
className={cn(
|
|
41
|
+
startAddon &&
|
|
42
|
+
"data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]",
|
|
43
|
+
sizeValue === "sm"
|
|
44
|
+
? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5"
|
|
45
|
+
: "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
data-slot="autocomplete-input"
|
|
49
|
+
render={<Input nativeInput size={sizeValue} />}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
{showTrigger && (
|
|
53
|
+
<AutocompleteTrigger
|
|
54
|
+
className={cn(
|
|
55
|
+
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
56
|
+
sizeValue === "sm" ? "end-0" : "end-0.5",
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<ChevronsUpDownIcon />
|
|
60
|
+
</AutocompleteTrigger>
|
|
61
|
+
)}
|
|
62
|
+
{showClear && (
|
|
63
|
+
<AutocompleteClear
|
|
64
|
+
className={cn(
|
|
65
|
+
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
66
|
+
sizeValue === "sm" ? "end-0" : "end-0.5",
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<XIcon />
|
|
70
|
+
</AutocompleteClear>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function AutocompletePopup({
|
|
77
|
+
className,
|
|
78
|
+
children,
|
|
79
|
+
sideOffset = 4,
|
|
80
|
+
...props
|
|
81
|
+
}: AutocompletePrimitive.Popup.Props & {
|
|
82
|
+
sideOffset?: number;
|
|
83
|
+
}) {
|
|
84
|
+
return (
|
|
85
|
+
<AutocompletePrimitive.Portal>
|
|
86
|
+
<AutocompletePrimitive.Positioner
|
|
87
|
+
className="z-50 select-none"
|
|
88
|
+
data-slot="autocomplete-positioner"
|
|
89
|
+
sideOffset={sideOffset}
|
|
90
|
+
>
|
|
91
|
+
<span
|
|
92
|
+
className={cn(
|
|
93
|
+
"relative flex max-h-full origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] 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%)]",
|
|
94
|
+
className,
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
<AutocompletePrimitive.Popup
|
|
98
|
+
className="flex max-h-[min(var(--available-height),23rem)] w-(--anchor-width) max-w-(--available-width) flex-col text-foreground"
|
|
99
|
+
data-slot="autocomplete-popup"
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</AutocompletePrimitive.Popup>
|
|
104
|
+
</span>
|
|
105
|
+
</AutocompletePrimitive.Positioner>
|
|
106
|
+
</AutocompletePrimitive.Portal>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function AutocompleteItem({
|
|
111
|
+
className,
|
|
112
|
+
children,
|
|
113
|
+
...props
|
|
114
|
+
}: AutocompletePrimitive.Item.Props) {
|
|
115
|
+
return (
|
|
116
|
+
<AutocompletePrimitive.Item
|
|
117
|
+
className={cn(
|
|
118
|
+
"flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
data-slot="autocomplete-item"
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
{children}
|
|
125
|
+
</AutocompletePrimitive.Item>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function AutocompleteSeparator({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: AutocompletePrimitive.Separator.Props) {
|
|
133
|
+
return (
|
|
134
|
+
<AutocompletePrimitive.Separator
|
|
135
|
+
className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
|
|
136
|
+
data-slot="autocomplete-separator"
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function AutocompleteGroup({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: AutocompletePrimitive.Group.Props) {
|
|
146
|
+
return (
|
|
147
|
+
<AutocompletePrimitive.Group
|
|
148
|
+
className={cn("[[role=group]+&]:mt-1.5", className)}
|
|
149
|
+
data-slot="autocomplete-group"
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function AutocompleteGroupLabel({
|
|
156
|
+
className,
|
|
157
|
+
...props
|
|
158
|
+
}: AutocompletePrimitive.GroupLabel.Props) {
|
|
159
|
+
return (
|
|
160
|
+
<AutocompletePrimitive.GroupLabel
|
|
161
|
+
className={cn(
|
|
162
|
+
"px-2 py-1.5 font-medium text-muted-foreground text-xs",
|
|
163
|
+
className,
|
|
164
|
+
)}
|
|
165
|
+
data-slot="autocomplete-group-label"
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function AutocompleteEmpty({
|
|
172
|
+
className,
|
|
173
|
+
...props
|
|
174
|
+
}: AutocompletePrimitive.Empty.Props) {
|
|
175
|
+
return (
|
|
176
|
+
<AutocompletePrimitive.Empty
|
|
177
|
+
className={cn(
|
|
178
|
+
"not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
|
|
179
|
+
className,
|
|
180
|
+
)}
|
|
181
|
+
data-slot="autocomplete-empty"
|
|
182
|
+
{...props}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function AutocompleteRow({
|
|
188
|
+
className,
|
|
189
|
+
...props
|
|
190
|
+
}: AutocompletePrimitive.Row.Props) {
|
|
191
|
+
return (
|
|
192
|
+
<AutocompletePrimitive.Row
|
|
193
|
+
className={className}
|
|
194
|
+
data-slot="autocomplete-row"
|
|
195
|
+
{...props}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {
|
|
201
|
+
return (
|
|
202
|
+
<AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function AutocompleteList({
|
|
207
|
+
className,
|
|
208
|
+
...props
|
|
209
|
+
}: AutocompletePrimitive.List.Props) {
|
|
210
|
+
return (
|
|
211
|
+
<ScrollArea scrollbarGutter scrollFade>
|
|
212
|
+
<AutocompletePrimitive.List
|
|
213
|
+
className={cn(
|
|
214
|
+
"not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3",
|
|
215
|
+
className,
|
|
216
|
+
)}
|
|
217
|
+
data-slot="autocomplete-list"
|
|
218
|
+
{...props}
|
|
219
|
+
/>
|
|
220
|
+
</ScrollArea>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function AutocompleteClear({
|
|
225
|
+
className,
|
|
226
|
+
...props
|
|
227
|
+
}: AutocompletePrimitive.Clear.Props) {
|
|
228
|
+
return (
|
|
229
|
+
<AutocompletePrimitive.Clear
|
|
230
|
+
className={cn(
|
|
231
|
+
"-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
232
|
+
className,
|
|
233
|
+
)}
|
|
234
|
+
data-slot="autocomplete-clear"
|
|
235
|
+
{...props}
|
|
236
|
+
>
|
|
237
|
+
<XIcon />
|
|
238
|
+
</AutocompletePrimitive.Clear>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function AutocompleteStatus({
|
|
243
|
+
className,
|
|
244
|
+
...props
|
|
245
|
+
}: AutocompletePrimitive.Status.Props) {
|
|
246
|
+
return (
|
|
247
|
+
<AutocompletePrimitive.Status
|
|
248
|
+
className={cn(
|
|
249
|
+
"px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
|
|
250
|
+
className,
|
|
251
|
+
)}
|
|
252
|
+
data-slot="autocomplete-status"
|
|
253
|
+
{...props}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function AutocompleteCollection({
|
|
259
|
+
...props
|
|
260
|
+
}: AutocompletePrimitive.Collection.Props) {
|
|
261
|
+
return (
|
|
262
|
+
<AutocompletePrimitive.Collection
|
|
263
|
+
data-slot="autocomplete-collection"
|
|
264
|
+
{...props}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function AutocompleteTrigger({
|
|
270
|
+
className,
|
|
271
|
+
...props
|
|
272
|
+
}: AutocompletePrimitive.Trigger.Props) {
|
|
273
|
+
return (
|
|
274
|
+
<AutocompletePrimitive.Trigger
|
|
275
|
+
className={className}
|
|
276
|
+
data-slot="autocomplete-trigger"
|
|
277
|
+
{...props}
|
|
278
|
+
/>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const useAutocompleteFilter = AutocompletePrimitive.useFilter;
|
|
283
|
+
|
|
284
|
+
export {
|
|
285
|
+
Autocomplete,
|
|
286
|
+
AutocompleteInput,
|
|
287
|
+
AutocompleteTrigger,
|
|
288
|
+
AutocompletePopup,
|
|
289
|
+
AutocompleteItem,
|
|
290
|
+
AutocompleteSeparator,
|
|
291
|
+
AutocompleteGroup,
|
|
292
|
+
AutocompleteGroupLabel,
|
|
293
|
+
AutocompleteEmpty,
|
|
294
|
+
AutocompleteValue,
|
|
295
|
+
AutocompleteList,
|
|
296
|
+
AutocompleteClear,
|
|
297
|
+
AutocompleteStatus,
|
|
298
|
+
AutocompleteRow,
|
|
299
|
+
AutocompleteCollection,
|
|
300
|
+
useAutocompleteFilter,
|
|
301
|
+
};
|