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,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { Input, type InputProps } from "@/components/ui/input";
|
|
8
|
+
import { Textarea, type TextareaProps } from "@/components/ui/textarea";
|
|
9
|
+
|
|
10
|
+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className={cn(
|
|
14
|
+
"relative inline-flex w-full min-w-0 items-center 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)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/6%)] has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)] has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
data-slot="input-group"
|
|
18
|
+
role="group"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const inputGroupAddonVariants = cva(
|
|
25
|
+
"[&_svg]:-mx-0.5 flex h-auto cursor-text select-none items-center justify-center gap-2 leading-none [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 not-has-[button]:**:[svg:not([class*='opacity-'])]:opacity-80",
|
|
26
|
+
{
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
align: "inline-start",
|
|
29
|
+
},
|
|
30
|
+
variants: {
|
|
31
|
+
align: {
|
|
32
|
+
"block-end":
|
|
33
|
+
"order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
34
|
+
"block-start":
|
|
35
|
+
"order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
36
|
+
"inline-end":
|
|
37
|
+
"has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 order-last pe-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]",
|
|
38
|
+
"inline-start":
|
|
39
|
+
"has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 order-first ps-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function InputGroupAddon({
|
|
46
|
+
className,
|
|
47
|
+
align = "inline-start",
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
53
|
+
data-align={align}
|
|
54
|
+
data-slot="input-group-addon"
|
|
55
|
+
onMouseDown={(e) => {
|
|
56
|
+
const target = e.target as HTMLElement;
|
|
57
|
+
const isInteractive = target.closest(
|
|
58
|
+
"button, a, input, select, textarea, [role='button'], [role='combobox'], [role='listbox'], [data-slot='select-trigger']",
|
|
59
|
+
);
|
|
60
|
+
if (isInteractive) return;
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
const parent = e.currentTarget.parentElement;
|
|
63
|
+
const input = parent?.querySelector<
|
|
64
|
+
HTMLInputElement | HTMLTextAreaElement
|
|
65
|
+
>("input, textarea");
|
|
66
|
+
if (input && !parent?.querySelector("input:focus, textarea:focus")) {
|
|
67
|
+
input.focus();
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
76
|
+
return (
|
|
77
|
+
<span
|
|
78
|
+
className={cn(
|
|
79
|
+
"[&_svg]:-mx-0.5 line-clamp-1 flex items-center gap-2 text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
80
|
+
className,
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function InputGroupInput({ className, ...props }: InputProps) {
|
|
88
|
+
return <Input className={className} unstyled {...props} />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function InputGroupTextarea({ className, ...props }: TextareaProps) {
|
|
92
|
+
return <Textarea className={className} unstyled {...props} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
InputGroup,
|
|
97
|
+
InputGroupAddon,
|
|
98
|
+
InputGroupText,
|
|
99
|
+
InputGroupInput,
|
|
100
|
+
InputGroupTextarea,
|
|
101
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
type InputProps = Omit<
|
|
9
|
+
InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,
|
|
10
|
+
"size"
|
|
11
|
+
> & {
|
|
12
|
+
size?: "sm" | "default" | "lg" | number;
|
|
13
|
+
unstyled?: boolean;
|
|
14
|
+
nativeInput?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function Input({
|
|
18
|
+
className,
|
|
19
|
+
size = "default",
|
|
20
|
+
unstyled = false,
|
|
21
|
+
nativeInput = false,
|
|
22
|
+
...props
|
|
23
|
+
}: InputProps) {
|
|
24
|
+
const inputClassName = cn(
|
|
25
|
+
"h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-muted-foreground/72 sm:h-7.5 sm:leading-7.5",
|
|
26
|
+
size === "sm" &&
|
|
27
|
+
"h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5",
|
|
28
|
+
size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5",
|
|
29
|
+
props.type === "search" &&
|
|
30
|
+
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
|
|
31
|
+
props.type === "file" &&
|
|
32
|
+
"text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<span
|
|
37
|
+
className={
|
|
38
|
+
cn(
|
|
39
|
+
!unstyled &&
|
|
40
|
+
"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)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/6%)] 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] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
41
|
+
className,
|
|
42
|
+
) || undefined
|
|
43
|
+
}
|
|
44
|
+
data-size={size}
|
|
45
|
+
data-slot="input-control"
|
|
46
|
+
>
|
|
47
|
+
{nativeInput ? (
|
|
48
|
+
<input
|
|
49
|
+
className={inputClassName}
|
|
50
|
+
data-slot="input"
|
|
51
|
+
size={typeof size === "number" ? size : undefined}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
) : (
|
|
55
|
+
<InputPrimitive
|
|
56
|
+
className={inputClassName}
|
|
57
|
+
data-slot="input"
|
|
58
|
+
size={typeof size === "number" ? size : undefined}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
</span>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Input, type InputProps };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
6
|
+
return (
|
|
7
|
+
<kbd
|
|
8
|
+
className={cn(
|
|
9
|
+
"pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded bg-muted px-1 font-medium font-sans text-muted-foreground text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
data-slot="kbd"
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
19
|
+
return (
|
|
20
|
+
<kbd
|
|
21
|
+
className={cn("inline-flex items-center gap-1", className)}
|
|
22
|
+
data-slot="kbd-group"
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
function Label({
|
|
9
|
+
className,
|
|
10
|
+
render,
|
|
11
|
+
...props
|
|
12
|
+
}: useRender.ComponentProps<"label">) {
|
|
13
|
+
const defaultProps = {
|
|
14
|
+
className: cn(
|
|
15
|
+
"inline-flex items-center gap-2 text-base/4.5 sm:text-sm/4 font-medium text-foreground",
|
|
16
|
+
className,
|
|
17
|
+
),
|
|
18
|
+
"data-slot": "label",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return useRender({
|
|
22
|
+
defaultTagName: "label",
|
|
23
|
+
props: mergeProps<"label">(defaultProps, props),
|
|
24
|
+
render,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Label };
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
4
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const MenuCreateHandle = MenuPrimitive.createHandle;
|
|
9
|
+
|
|
10
|
+
const Menu = MenuPrimitive.Root;
|
|
11
|
+
|
|
12
|
+
const MenuPortal = MenuPrimitive.Portal;
|
|
13
|
+
|
|
14
|
+
function MenuTrigger(props: MenuPrimitive.Trigger.Props) {
|
|
15
|
+
return <MenuPrimitive.Trigger data-slot="menu-trigger" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function MenuPopup({
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
sideOffset = 4,
|
|
22
|
+
align = "center",
|
|
23
|
+
alignOffset,
|
|
24
|
+
side = "bottom",
|
|
25
|
+
...props
|
|
26
|
+
}: MenuPrimitive.Popup.Props & {
|
|
27
|
+
align?: MenuPrimitive.Positioner.Props["align"];
|
|
28
|
+
sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
|
|
29
|
+
alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
|
|
30
|
+
side?: MenuPrimitive.Positioner.Props["side"];
|
|
31
|
+
}) {
|
|
32
|
+
return (
|
|
33
|
+
<MenuPrimitive.Portal>
|
|
34
|
+
<MenuPrimitive.Positioner
|
|
35
|
+
align={align}
|
|
36
|
+
alignOffset={alignOffset}
|
|
37
|
+
className="z-50"
|
|
38
|
+
data-slot="menu-positioner"
|
|
39
|
+
side={side}
|
|
40
|
+
sideOffset={sideOffset}
|
|
41
|
+
>
|
|
42
|
+
<MenuPrimitive.Popup
|
|
43
|
+
className={cn(
|
|
44
|
+
"relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] focus:outline-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
data-slot="menu-popup"
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<div className="max-h-(--available-height) w-full overflow-y-auto p-1">
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
</MenuPrimitive.Popup>
|
|
54
|
+
</MenuPrimitive.Positioner>
|
|
55
|
+
</MenuPrimitive.Portal>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function MenuGroup(props: MenuPrimitive.Group.Props) {
|
|
60
|
+
return <MenuPrimitive.Group data-slot="menu-group" {...props} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function MenuItem({
|
|
64
|
+
className,
|
|
65
|
+
inset,
|
|
66
|
+
variant = "default",
|
|
67
|
+
...props
|
|
68
|
+
}: MenuPrimitive.Item.Props & {
|
|
69
|
+
inset?: boolean;
|
|
70
|
+
variant?: "default" | "destructive";
|
|
71
|
+
}) {
|
|
72
|
+
return (
|
|
73
|
+
<MenuPrimitive.Item
|
|
74
|
+
className={cn(
|
|
75
|
+
"[&>svg]:-mx-0.5 flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4.5 sm:[&>svg:not([class*='size-'])]:size-4 [&>svg]:pointer-events-none [&>svg]:shrink-0",
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
data-inset={inset}
|
|
79
|
+
data-slot="menu-item"
|
|
80
|
+
data-variant={variant}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function MenuCheckboxItem({
|
|
87
|
+
className,
|
|
88
|
+
children,
|
|
89
|
+
checked,
|
|
90
|
+
variant = "default",
|
|
91
|
+
...props
|
|
92
|
+
}: MenuPrimitive.CheckboxItem.Props & {
|
|
93
|
+
variant?: "default" | "switch";
|
|
94
|
+
}) {
|
|
95
|
+
return (
|
|
96
|
+
<MenuPrimitive.CheckboxItem
|
|
97
|
+
checked={checked}
|
|
98
|
+
className={cn(
|
|
99
|
+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 ps-2 text-base text-foreground 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 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
100
|
+
variant === "switch"
|
|
101
|
+
? "grid-cols-[1fr_auto] gap-4 pe-1.5"
|
|
102
|
+
: "grid-cols-[1rem_1fr] pe-4",
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
data-slot="menu-checkbox-item"
|
|
106
|
+
{...props}
|
|
107
|
+
>
|
|
108
|
+
{variant === "switch" ? (
|
|
109
|
+
<>
|
|
110
|
+
<span className="col-start-1">{children}</span>
|
|
111
|
+
<MenuPrimitive.CheckboxItemIndicator
|
|
112
|
+
className="inset-shadow-[0_1px_--theme(--color-black/6%)] 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(4)] 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(3)]"
|
|
113
|
+
keepMounted
|
|
114
|
+
>
|
|
115
|
+
<span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />
|
|
116
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
117
|
+
</>
|
|
118
|
+
) : (
|
|
119
|
+
<>
|
|
120
|
+
<MenuPrimitive.CheckboxItemIndicator className="col-start-1">
|
|
121
|
+
<svg
|
|
122
|
+
fill="none"
|
|
123
|
+
height="24"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
strokeLinecap="round"
|
|
126
|
+
strokeLinejoin="round"
|
|
127
|
+
strokeWidth="2"
|
|
128
|
+
viewBox="0 0 24 24"
|
|
129
|
+
width="24"
|
|
130
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
131
|
+
>
|
|
132
|
+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
|
|
133
|
+
</svg>
|
|
134
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
135
|
+
<span className="col-start-2">{children}</span>
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
</MenuPrimitive.CheckboxItem>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
|
|
143
|
+
return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function MenuRadioItem({
|
|
147
|
+
className,
|
|
148
|
+
children,
|
|
149
|
+
...props
|
|
150
|
+
}: MenuPrimitive.RadioItem.Props) {
|
|
151
|
+
return (
|
|
152
|
+
<MenuPrimitive.RadioItem
|
|
153
|
+
className={cn(
|
|
154
|
+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground 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 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
155
|
+
className,
|
|
156
|
+
)}
|
|
157
|
+
data-slot="menu-radio-item"
|
|
158
|
+
{...props}
|
|
159
|
+
>
|
|
160
|
+
<MenuPrimitive.RadioItemIndicator className="col-start-1">
|
|
161
|
+
<svg
|
|
162
|
+
fill="none"
|
|
163
|
+
height="24"
|
|
164
|
+
stroke="currentColor"
|
|
165
|
+
strokeLinecap="round"
|
|
166
|
+
strokeLinejoin="round"
|
|
167
|
+
strokeWidth="2"
|
|
168
|
+
viewBox="0 0 24 24"
|
|
169
|
+
width="24"
|
|
170
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
171
|
+
>
|
|
172
|
+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
|
|
173
|
+
</svg>
|
|
174
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
175
|
+
<span className="col-start-2">{children}</span>
|
|
176
|
+
</MenuPrimitive.RadioItem>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function MenuGroupLabel({
|
|
181
|
+
className,
|
|
182
|
+
inset,
|
|
183
|
+
...props
|
|
184
|
+
}: MenuPrimitive.GroupLabel.Props & {
|
|
185
|
+
inset?: boolean;
|
|
186
|
+
}) {
|
|
187
|
+
return (
|
|
188
|
+
<MenuPrimitive.GroupLabel
|
|
189
|
+
className={cn(
|
|
190
|
+
"px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",
|
|
191
|
+
className,
|
|
192
|
+
)}
|
|
193
|
+
data-inset={inset}
|
|
194
|
+
data-slot="menu-label"
|
|
195
|
+
{...props}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
|
|
201
|
+
return (
|
|
202
|
+
<MenuPrimitive.Separator
|
|
203
|
+
className={cn("mx-2 my-1 h-px bg-border", className)}
|
|
204
|
+
data-slot="menu-separator"
|
|
205
|
+
{...props}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function MenuShortcut({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
211
|
+
return (
|
|
212
|
+
<kbd
|
|
213
|
+
className={cn(
|
|
214
|
+
"ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest",
|
|
215
|
+
className,
|
|
216
|
+
)}
|
|
217
|
+
data-slot="menu-shortcut"
|
|
218
|
+
{...props}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function MenuSub(props: MenuPrimitive.SubmenuRoot.Props) {
|
|
224
|
+
return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function MenuSubTrigger({
|
|
228
|
+
className,
|
|
229
|
+
inset,
|
|
230
|
+
children,
|
|
231
|
+
...props
|
|
232
|
+
}: MenuPrimitive.SubmenuTrigger.Props & {
|
|
233
|
+
inset?: boolean;
|
|
234
|
+
}) {
|
|
235
|
+
return (
|
|
236
|
+
<MenuPrimitive.SubmenuTrigger
|
|
237
|
+
className={cn(
|
|
238
|
+
"flex min-h-8 items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
239
|
+
className,
|
|
240
|
+
)}
|
|
241
|
+
data-inset={inset}
|
|
242
|
+
data-slot="menu-sub-trigger"
|
|
243
|
+
{...props}
|
|
244
|
+
>
|
|
245
|
+
{children}
|
|
246
|
+
<ChevronRightIcon className="-me-0.5 ms-auto opacity-80" />
|
|
247
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function MenuSubPopup({
|
|
252
|
+
className,
|
|
253
|
+
sideOffset = 0,
|
|
254
|
+
alignOffset,
|
|
255
|
+
align = "start",
|
|
256
|
+
...props
|
|
257
|
+
}: MenuPrimitive.Popup.Props & {
|
|
258
|
+
align?: MenuPrimitive.Positioner.Props["align"];
|
|
259
|
+
sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
|
|
260
|
+
alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
|
|
261
|
+
}) {
|
|
262
|
+
const defaultAlignOffset = align !== "center" ? -5 : undefined;
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<MenuPopup
|
|
266
|
+
align={align}
|
|
267
|
+
alignOffset={alignOffset ?? defaultAlignOffset}
|
|
268
|
+
className={className}
|
|
269
|
+
data-slot="menu-sub-content"
|
|
270
|
+
side="inline-end"
|
|
271
|
+
sideOffset={sideOffset}
|
|
272
|
+
{...props}
|
|
273
|
+
/>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export {
|
|
278
|
+
MenuCreateHandle,
|
|
279
|
+
MenuCreateHandle as DropdownMenuCreateHandle,
|
|
280
|
+
Menu,
|
|
281
|
+
Menu as DropdownMenu,
|
|
282
|
+
MenuPortal,
|
|
283
|
+
MenuPortal as DropdownMenuPortal,
|
|
284
|
+
MenuTrigger,
|
|
285
|
+
MenuTrigger as DropdownMenuTrigger,
|
|
286
|
+
MenuPopup,
|
|
287
|
+
MenuPopup as DropdownMenuContent,
|
|
288
|
+
MenuGroup,
|
|
289
|
+
MenuGroup as DropdownMenuGroup,
|
|
290
|
+
MenuItem,
|
|
291
|
+
MenuItem as DropdownMenuItem,
|
|
292
|
+
MenuCheckboxItem,
|
|
293
|
+
MenuCheckboxItem as DropdownMenuCheckboxItem,
|
|
294
|
+
MenuRadioGroup,
|
|
295
|
+
MenuRadioGroup as DropdownMenuRadioGroup,
|
|
296
|
+
MenuRadioItem,
|
|
297
|
+
MenuRadioItem as DropdownMenuRadioItem,
|
|
298
|
+
MenuGroupLabel,
|
|
299
|
+
MenuGroupLabel as DropdownMenuLabel,
|
|
300
|
+
MenuSeparator,
|
|
301
|
+
MenuSeparator as DropdownMenuSeparator,
|
|
302
|
+
MenuShortcut,
|
|
303
|
+
MenuShortcut as DropdownMenuShortcut,
|
|
304
|
+
MenuSub,
|
|
305
|
+
MenuSub as DropdownMenuSub,
|
|
306
|
+
MenuSubTrigger,
|
|
307
|
+
MenuSubTrigger as DropdownMenuSubTrigger,
|
|
308
|
+
MenuSubPopup,
|
|
309
|
+
MenuSubPopup as DropdownMenuSubContent,
|
|
310
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Meter as MeterPrimitive } from "@base-ui/react/meter";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Meter({ className, children, ...props }: MeterPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<MeterPrimitive.Root
|
|
10
|
+
className={cn("flex w-full flex-col gap-2", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
{children ? (
|
|
14
|
+
children
|
|
15
|
+
) : (
|
|
16
|
+
<MeterTrack>
|
|
17
|
+
<MeterIndicator />
|
|
18
|
+
</MeterTrack>
|
|
19
|
+
)}
|
|
20
|
+
</MeterPrimitive.Root>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function MeterLabel({ className, ...props }: MeterPrimitive.Label.Props) {
|
|
25
|
+
return (
|
|
26
|
+
<MeterPrimitive.Label
|
|
27
|
+
className={cn("font-medium text-foreground text-sm", className)}
|
|
28
|
+
data-slot="meter-label"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function MeterTrack({ className, ...props }: MeterPrimitive.Track.Props) {
|
|
35
|
+
return (
|
|
36
|
+
<MeterPrimitive.Track
|
|
37
|
+
className={cn("block h-2 w-full overflow-hidden bg-input", className)}
|
|
38
|
+
data-slot="meter-track"
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function MeterIndicator({
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: MeterPrimitive.Indicator.Props) {
|
|
48
|
+
return (
|
|
49
|
+
<MeterPrimitive.Indicator
|
|
50
|
+
className={cn("bg-primary transition-all duration-500", className)}
|
|
51
|
+
data-slot="meter-indicator"
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function MeterValue({ className, ...props }: MeterPrimitive.Value.Props) {
|
|
58
|
+
return (
|
|
59
|
+
<MeterPrimitive.Value
|
|
60
|
+
className={cn("text-foreground text-sm tabular-nums", className)}
|
|
61
|
+
data-slot="meter-value"
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { Meter, MeterLabel, MeterTrack, MeterIndicator, MeterValue };
|