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.
Files changed (78) hide show
  1. package/dist/index.js +59 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +39 -0
  4. package/template/README.md +290 -0
  5. package/template/components.json +24 -0
  6. package/template/package.json +56 -0
  7. package/template/public/favicon.ico +0 -0
  8. package/template/public/google-logo.svg +6 -0
  9. package/template/public/logo-dark.ico +0 -0
  10. package/template/public/logo-dark.webp +0 -0
  11. package/template/public/logo-light.ico +0 -0
  12. package/template/public/logo-light.webp +0 -0
  13. package/template/public/manifest.json +16 -0
  14. package/template/public/robots.txt +3 -0
  15. package/template/src/components/Header.tsx +76 -0
  16. package/template/src/components/language-switcher.tsx +38 -0
  17. package/template/src/components/theme-provider.tsx +14 -0
  18. package/template/src/components/themed-logo.tsx +44 -0
  19. package/template/src/components/ui/accordion.tsx +69 -0
  20. package/template/src/components/ui/alert-dialog.tsx +169 -0
  21. package/template/src/components/ui/alert.tsx +80 -0
  22. package/template/src/components/ui/autocomplete.tsx +301 -0
  23. package/template/src/components/ui/avatar.tsx +46 -0
  24. package/template/src/components/ui/badge.tsx +60 -0
  25. package/template/src/components/ui/breadcrumb.tsx +112 -0
  26. package/template/src/components/ui/button.tsx +73 -0
  27. package/template/src/components/ui/card.tsx +244 -0
  28. package/template/src/components/ui/checkbox-group.tsx +16 -0
  29. package/template/src/components/ui/checkbox.tsx +60 -0
  30. package/template/src/components/ui/collapsible.tsx +45 -0
  31. package/template/src/components/ui/combobox.tsx +415 -0
  32. package/template/src/components/ui/command.tsx +264 -0
  33. package/template/src/components/ui/dialog.tsx +196 -0
  34. package/template/src/components/ui/empty.tsx +127 -0
  35. package/template/src/components/ui/field.tsx +74 -0
  36. package/template/src/components/ui/fieldset.tsx +29 -0
  37. package/template/src/components/ui/form.tsx +17 -0
  38. package/template/src/components/ui/frame.tsx +82 -0
  39. package/template/src/components/ui/group.tsx +97 -0
  40. package/template/src/components/ui/input-group.tsx +101 -0
  41. package/template/src/components/ui/input.tsx +66 -0
  42. package/template/src/components/ui/kbd.tsx +28 -0
  43. package/template/src/components/ui/label.tsx +28 -0
  44. package/template/src/components/ui/menu.tsx +310 -0
  45. package/template/src/components/ui/meter.tsx +67 -0
  46. package/template/src/components/ui/number-field.tsx +160 -0
  47. package/template/src/components/ui/pagination.tsx +136 -0
  48. package/template/src/components/ui/popover.tsx +104 -0
  49. package/template/src/components/ui/preview-card.tsx +55 -0
  50. package/template/src/components/ui/progress.tsx +81 -0
  51. package/template/src/components/ui/radio-group.tsx +36 -0
  52. package/template/src/components/ui/scroll-area.tsx +64 -0
  53. package/template/src/components/ui/select.tsx +180 -0
  54. package/template/src/components/ui/separator.tsx +23 -0
  55. package/template/src/components/ui/sheet.tsx +203 -0
  56. package/template/src/components/ui/sidebar.tsx +743 -0
  57. package/template/src/components/ui/skeleton.tsx +16 -0
  58. package/template/src/components/ui/slider.tsx +74 -0
  59. package/template/src/components/ui/spinner.tsx +18 -0
  60. package/template/src/components/ui/switch.tsx +27 -0
  61. package/template/src/components/ui/table.tsx +126 -0
  62. package/template/src/components/ui/tabs.tsx +87 -0
  63. package/template/src/components/ui/textarea.tsx +51 -0
  64. package/template/src/components/ui/toast.tsx +269 -0
  65. package/template/src/components/ui/toggle-group.tsx +102 -0
  66. package/template/src/components/ui/toggle.tsx +45 -0
  67. package/template/src/components/ui/toolbar.tsx +83 -0
  68. package/template/src/components/ui/tooltip.tsx +65 -0
  69. package/template/src/hooks/use-mobile.ts +21 -0
  70. package/template/src/lib/i18n.ts +70 -0
  71. package/template/src/lib/utils.ts +6 -0
  72. package/template/src/routeTree.gen.ts +68 -0
  73. package/template/src/router.tsx +17 -0
  74. package/template/src/routes/__root.tsx +62 -0
  75. package/template/src/routes/index.tsx +71 -0
  76. package/template/src/styles.css +121 -0
  77. package/template/tsconfig.json +28 -0
  78. 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 };