@cntyclub/ui-react 0.1.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 (124) hide show
  1. package/dist/chunk-HDGMSYQS.js +26461 -0
  2. package/dist/chunk-HDGMSYQS.js.map +1 -0
  3. package/dist/chunk-PR4QN5HX.js +39 -0
  4. package/dist/chunk-PR4QN5HX.js.map +1 -0
  5. package/dist/form.d.ts +175 -0
  6. package/dist/form.js +5207 -0
  7. package/dist/form.js.map +1 -0
  8. package/dist/index.d.ts +1462 -0
  9. package/dist/index.js +81862 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/input-CZvh825j.d.ts +24 -0
  12. package/dist/qr-code-styling-3Y6LZH6V.js +1123 -0
  13. package/dist/qr-code-styling-3Y6LZH6V.js.map +1 -0
  14. package/package.json +79 -0
  15. package/src/components/form/checkbox-group-field.tsx +101 -0
  16. package/src/components/form/date-field.tsx +79 -0
  17. package/src/components/form/date-range-field.tsx +106 -0
  18. package/src/components/form/form-context.ts +10 -0
  19. package/src/components/form/form.tsx +54 -0
  20. package/src/components/form/number-field.tsx +69 -0
  21. package/src/components/form/select-field.tsx +76 -0
  22. package/src/components/form/submit-button.tsx +28 -0
  23. package/src/components/form/text-field.tsx +107 -0
  24. package/src/components/layout/dashboard-header.tsx +54 -0
  25. package/src/components/layout/dashboard-panel.tsx +34 -0
  26. package/src/components/theme-provider.tsx +403 -0
  27. package/src/components/ui/accordion.tsx +69 -0
  28. package/src/components/ui/alert-dialog.tsx +169 -0
  29. package/src/components/ui/alert.tsx +80 -0
  30. package/src/components/ui/animated-theme-toggler.tsx +265 -0
  31. package/src/components/ui/app-store-buttons.tsx +182 -0
  32. package/src/components/ui/aspect-ratio.tsx +23 -0
  33. package/src/components/ui/autocomplete.tsx +296 -0
  34. package/src/components/ui/avatar-group.tsx +95 -0
  35. package/src/components/ui/avatar.tsx +285 -0
  36. package/src/components/ui/badge-group.tsx +160 -0
  37. package/src/components/ui/badge.tsx +172 -0
  38. package/src/components/ui/breadcrumb.tsx +112 -0
  39. package/src/components/ui/button.tsx +77 -0
  40. package/src/components/ui/calendar.tsx +137 -0
  41. package/src/components/ui/card.tsx +244 -0
  42. package/src/components/ui/carousel.tsx +258 -0
  43. package/src/components/ui/chart.tsx +379 -0
  44. package/src/components/ui/checkbox-group.tsx +16 -0
  45. package/src/components/ui/checkbox.tsx +82 -0
  46. package/src/components/ui/collapsible.tsx +45 -0
  47. package/src/components/ui/combobox.tsx +411 -0
  48. package/src/components/ui/command.tsx +264 -0
  49. package/src/components/ui/context-menu.tsx +271 -0
  50. package/src/components/ui/credit-card.tsx +214 -0
  51. package/src/components/ui/dialog.tsx +196 -0
  52. package/src/components/ui/drawer.tsx +135 -0
  53. package/src/components/ui/empty.tsx +127 -0
  54. package/src/components/ui/featured-icon.tsx +149 -0
  55. package/src/components/ui/field.tsx +88 -0
  56. package/src/components/ui/fieldset.tsx +29 -0
  57. package/src/components/ui/form.tsx +17 -0
  58. package/src/components/ui/frame.tsx +82 -0
  59. package/src/components/ui/generic-empty.tsx +142 -0
  60. package/src/components/ui/group.tsx +97 -0
  61. package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
  62. package/src/components/ui/input-group.tsx +102 -0
  63. package/src/components/ui/input-otp.tsx +96 -0
  64. package/src/components/ui/input.tsx +66 -0
  65. package/src/components/ui/item.tsx +198 -0
  66. package/src/components/ui/kbd.tsx +30 -0
  67. package/src/components/ui/label.tsx +28 -0
  68. package/src/components/ui/menu.tsx +312 -0
  69. package/src/components/ui/menubar.tsx +93 -0
  70. package/src/components/ui/meter.tsx +67 -0
  71. package/src/components/ui/multi-select.tsx +308 -0
  72. package/src/components/ui/navigation-menu.tsx +143 -0
  73. package/src/components/ui/number-field.tsx +160 -0
  74. package/src/components/ui/pagination-controls.tsx +74 -0
  75. package/src/components/ui/pagination.tsx +149 -0
  76. package/src/components/ui/popover.tsx +119 -0
  77. package/src/components/ui/preview-card.tsx +55 -0
  78. package/src/components/ui/progress.tsx +289 -0
  79. package/src/components/ui/qr-code.tsx +150 -0
  80. package/src/components/ui/radio-group.tsx +103 -0
  81. package/src/components/ui/resizable.tsx +56 -0
  82. package/src/components/ui/scroll-area.tsx +90 -0
  83. package/src/components/ui/scroller.tsx +38 -0
  84. package/src/components/ui/section-header.tsx +118 -0
  85. package/src/components/ui/select.tsx +181 -0
  86. package/src/components/ui/separator.tsx +23 -0
  87. package/src/components/ui/sheet.tsx +224 -0
  88. package/src/components/ui/sidebar.tsx +744 -0
  89. package/src/components/ui/skeleton.tsx +16 -0
  90. package/src/components/ui/slider.tsx +108 -0
  91. package/src/components/ui/smooth-scroll.tsx +143 -0
  92. package/src/components/ui/social-button.tsx +247 -0
  93. package/src/components/ui/spinner-on-demand.tsx +32 -0
  94. package/src/components/ui/spinner.tsx +18 -0
  95. package/src/components/ui/stat.tsx +187 -0
  96. package/src/components/ui/stepper.tsx +167 -0
  97. package/src/components/ui/switch.tsx +56 -0
  98. package/src/components/ui/table.tsx +126 -0
  99. package/src/components/ui/tabs.tsx +90 -0
  100. package/src/components/ui/tag.tsx +229 -0
  101. package/src/components/ui/target-countdown.tsx +46 -0
  102. package/src/components/ui/text-editor.tsx +313 -0
  103. package/src/components/ui/textarea.tsx +51 -0
  104. package/src/components/ui/timeline.tsx +116 -0
  105. package/src/components/ui/toast.tsx +268 -0
  106. package/src/components/ui/toggle-group.tsx +101 -0
  107. package/src/components/ui/toggle.tsx +45 -0
  108. package/src/components/ui/toolbar.tsx +89 -0
  109. package/src/components/ui/tooltip.tsx +102 -0
  110. package/src/components/ui/vertical-scroll-fader.tsx +250 -0
  111. package/src/components/ui/video-player.tsx +275 -0
  112. package/src/components/upload/avatar-upload-base.tsx +131 -0
  113. package/src/components/upload/image-upload-base.tsx +112 -0
  114. package/src/form.ts +17 -0
  115. package/src/index.ts +125 -0
  116. package/src/lib/hooks/use-callback-ref.ts +15 -0
  117. package/src/lib/hooks/use-first-render.ts +11 -0
  118. package/src/lib/hooks/use-hover.ts +53 -0
  119. package/src/lib/hooks/use-is-tab-active.ts +17 -0
  120. package/src/lib/hooks/use-media-query.ts +164 -0
  121. package/src/lib/utils/css.ts +6 -0
  122. package/src/styles.css +300 -0
  123. package/src/types/helpers.ts +24 -0
  124. package/src/types/react.d.ts +7 -0
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
4
+ import {
5
+ Menubar as MenubarPrimitive,
6
+ type MenubarProps,
7
+ } from "@base-ui/react/menubar";
8
+ import type * as React from "react";
9
+
10
+ import { cn } from "../../lib/utils/css";
11
+ import {
12
+ Menu,
13
+ MenuCheckboxItem,
14
+ MenuGroup,
15
+ MenuGroupLabel,
16
+ MenuItem,
17
+ MenuPopup,
18
+ MenuPortal,
19
+ MenuRadioGroup,
20
+ MenuRadioItem,
21
+ MenuSeparator,
22
+ MenuShortcut,
23
+ MenuSub,
24
+ MenuSubPopup,
25
+ MenuSubTrigger,
26
+ } from "./menu";
27
+
28
+ function Menubar({ className, ...props }: MenubarProps) {
29
+ return (
30
+ <MenubarPrimitive
31
+ className={cn(
32
+ "flex h-9 w-fit items-center gap-1 rounded-lg border bg-background p-1 shadow-xs/5 sm:h-8 dark:bg-input/32",
33
+ className,
34
+ )}
35
+ data-slot="menubar"
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function MenubarMenu(props: MenuPrimitive.Root.Props) {
42
+ return <Menu data-slot="menubar-menu" {...props} />;
43
+ }
44
+
45
+ function MenubarTrigger({
46
+ className,
47
+ ...props
48
+ }: MenuPrimitive.Trigger.Props) {
49
+ return (
50
+ <MenuPrimitive.Trigger
51
+ className={cn(
52
+ "flex select-none items-center rounded-md px-2 py-1 font-medium text-base outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-popup-open:bg-accent data-popup-open:text-accent-foreground sm:text-sm",
53
+ className,
54
+ )}
55
+ data-slot="menubar-trigger"
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ function MenubarContent({
62
+ align = "start",
63
+ sideOffset = 8,
64
+ ...props
65
+ }: React.ComponentProps<typeof MenuPopup>) {
66
+ return (
67
+ <MenuPopup
68
+ align={align}
69
+ data-slot="menubar-content"
70
+ sideOffset={sideOffset}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ export {
77
+ Menubar,
78
+ MenubarMenu,
79
+ MenubarTrigger,
80
+ MenubarContent,
81
+ MenuPortal as MenubarPortal,
82
+ MenuGroup as MenubarGroup,
83
+ MenuItem as MenubarItem,
84
+ MenuCheckboxItem as MenubarCheckboxItem,
85
+ MenuRadioGroup as MenubarRadioGroup,
86
+ MenuRadioItem as MenubarRadioItem,
87
+ MenuGroupLabel as MenubarLabel,
88
+ MenuSeparator as MenubarSeparator,
89
+ MenuShortcut as MenubarShortcut,
90
+ MenuSub as MenubarSub,
91
+ MenuSubTrigger as MenubarSubTrigger,
92
+ MenuSubPopup as MenubarSubContent,
93
+ };
@@ -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/css";
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 };
@@ -0,0 +1,308 @@
1
+ "use client";
2
+
3
+ import { Select as SelectPrimitive } from "@base-ui/react/select";
4
+ import { ChevronsUpDownIcon, SearchIcon } from "lucide-react";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../../lib/utils/css";
8
+ import { Button } from "./button";
9
+
10
+ interface MultiSelectItem {
11
+ label: string;
12
+ value: string;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ interface MultiSelectProps {
17
+ items: MultiSelectItem[];
18
+ /** Controlled selected values. */
19
+ value?: string[];
20
+ /** Uncontrolled initial selected values. */
21
+ defaultValue?: string[];
22
+ onValueChange?: (value: string[]) => void;
23
+ size?: "sm" | "default" | "lg";
24
+ placeholder?: React.ReactNode;
25
+ /** Extra context rendered next to the selected count, e.g. "of 12 users". */
26
+ supportingText?: React.ReactNode;
27
+ /** Formats the trigger text from the selected count. Defaults to `${count} selected`. */
28
+ selectedCountFormatter?: (count: number) => React.ReactNode;
29
+ showSearch?: boolean;
30
+ searchPlaceholder?: string;
31
+ showFooter?: boolean;
32
+ selectAllLabel?: React.ReactNode;
33
+ resetLabel?: React.ReactNode;
34
+ emptyStateTitle?: React.ReactNode;
35
+ emptyStateDescription?: React.ReactNode;
36
+ disabled?: boolean;
37
+ name?: string;
38
+ required?: boolean;
39
+ id?: string;
40
+ /** Trigger class name. */
41
+ className?: string;
42
+ popupClassName?: string;
43
+ sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
44
+ }
45
+
46
+ function MultiSelect({
47
+ items,
48
+ value: valueProp,
49
+ defaultValue,
50
+ onValueChange,
51
+ size = "default",
52
+ placeholder = "Select options",
53
+ supportingText,
54
+ selectedCountFormatter = (count) => `${count} selected`,
55
+ showSearch = true,
56
+ searchPlaceholder = "Search",
57
+ showFooter = true,
58
+ selectAllLabel = "Select all",
59
+ resetLabel = "Reset",
60
+ emptyStateTitle = "No results found",
61
+ emptyStateDescription,
62
+ disabled,
63
+ name,
64
+ required,
65
+ id,
66
+ className,
67
+ popupClassName,
68
+ sideOffset = 4,
69
+ }: MultiSelectProps) {
70
+ const [uncontrolledValue, setUncontrolledValue] = React.useState<string[]>(
71
+ defaultValue ?? [],
72
+ );
73
+ const [search, setSearch] = React.useState("");
74
+
75
+ const isControlled = valueProp !== undefined;
76
+ const value = isControlled ? valueProp : uncontrolledValue;
77
+
78
+ const setValue = (next: string[]) => {
79
+ if (!isControlled) {
80
+ setUncontrolledValue(next);
81
+ }
82
+ onValueChange?.(next);
83
+ };
84
+
85
+ const query = search.trim().toLowerCase();
86
+ const filteredItems = query
87
+ ? items.filter((item) => item.label.toLowerCase().includes(query))
88
+ : items;
89
+
90
+ const count = value.length;
91
+
92
+ return (
93
+ <SelectPrimitive.Root
94
+ disabled={disabled}
95
+ multiple
96
+ name={name}
97
+ onOpenChange={(open) => {
98
+ if (!open) {
99
+ setSearch("");
100
+ }
101
+ }}
102
+ onValueChange={(next) => setValue(next as string[])}
103
+ required={required}
104
+ value={value}
105
+ >
106
+ <SelectPrimitive.Trigger
107
+ className={cn(
108
+ "relative inline-flex min-h-9 w-full min-w-36 select-none items-center justify-center gap-2 rounded-lg border border-input bg-background not-dark:bg-clip-padding px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_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 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
109
+ size === "sm" &&
110
+ "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7",
111
+ size === "lg" && "min-h-10 sm:min-h-9",
112
+ className,
113
+ )}
114
+ data-slot="multi-select-trigger"
115
+ id={id}
116
+ >
117
+ <span className="flex flex-1 items-baseline gap-1.5 truncate">
118
+ {count > 0 ? (
119
+ <>
120
+ <span className="truncate">{selectedCountFormatter(count)}</span>
121
+ {supportingText ? (
122
+ <span className="truncate font-normal text-muted-foreground">
123
+ {supportingText}
124
+ </span>
125
+ ) : null}
126
+ </>
127
+ ) : (
128
+ <span className="truncate text-muted-foreground">
129
+ {placeholder}
130
+ </span>
131
+ )}
132
+ </span>
133
+ <SelectPrimitive.Icon data-slot="multi-select-icon">
134
+ <ChevronsUpDownIcon className="-me-1 size-4.5 opacity-80 sm:size-4" />
135
+ </SelectPrimitive.Icon>
136
+ </SelectPrimitive.Trigger>
137
+ <SelectPrimitive.Portal>
138
+ <SelectPrimitive.Positioner
139
+ alignItemWithTrigger={false}
140
+ className="z-50 select-none"
141
+ data-slot="multi-select-positioner"
142
+ sideOffset={sideOffset}
143
+ >
144
+ <SelectPrimitive.Popup
145
+ className={cn(
146
+ "relative flex max-h-[min(var(--available-height),20rem)] w-(--anchor-width) min-w-48 max-w-(--available-width) origin-(--transform-origin) flex-col overflow-hidden rounded-lg border bg-popover not-dark:bg-clip-padding text-foreground shadow-lg/5 transition-[transform,scale,opacity] duration-150 ease-out before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
147
+ popupClassName,
148
+ )}
149
+ data-slot="multi-select-popup"
150
+ >
151
+ {showSearch ? (
152
+ <MultiSelectSearch
153
+ onChange={setSearch}
154
+ placeholder={searchPlaceholder}
155
+ value={search}
156
+ />
157
+ ) : null}
158
+ <SelectPrimitive.List
159
+ className="min-h-0 flex-1 overflow-y-auto p-1"
160
+ data-slot="multi-select-list"
161
+ >
162
+ {filteredItems.map((item) => (
163
+ <MultiSelectOption
164
+ disabled={item.disabled}
165
+ key={item.value}
166
+ label={item.label}
167
+ value={item.value}
168
+ />
169
+ ))}
170
+ {filteredItems.length === 0 ? (
171
+ <div
172
+ className="flex flex-col items-center justify-center gap-0.5 px-4 py-6 text-center"
173
+ data-slot="multi-select-empty"
174
+ >
175
+ <p className="font-medium text-foreground text-sm">
176
+ {emptyStateTitle}
177
+ </p>
178
+ {emptyStateDescription ? (
179
+ <p className="text-muted-foreground text-sm">
180
+ {emptyStateDescription}
181
+ </p>
182
+ ) : null}
183
+ </div>
184
+ ) : null}
185
+ </SelectPrimitive.List>
186
+ {showFooter ? (
187
+ <div
188
+ className="flex items-center justify-between gap-1 border-t p-1"
189
+ data-slot="multi-select-footer"
190
+ >
191
+ <Button
192
+ onClick={() =>
193
+ setValue(
194
+ items
195
+ .filter((item) => !item.disabled)
196
+ .map((item) => item.value),
197
+ )
198
+ }
199
+ size="xs"
200
+ variant="ghost"
201
+ >
202
+ {selectAllLabel}
203
+ </Button>
204
+ <Button
205
+ className="text-muted-foreground"
206
+ onClick={() => setValue([])}
207
+ size="xs"
208
+ variant="ghost"
209
+ >
210
+ {resetLabel}
211
+ </Button>
212
+ </div>
213
+ ) : null}
214
+ </SelectPrimitive.Popup>
215
+ </SelectPrimitive.Positioner>
216
+ </SelectPrimitive.Portal>
217
+ </SelectPrimitive.Root>
218
+ );
219
+ }
220
+
221
+ function MultiSelectSearch({
222
+ value,
223
+ onChange,
224
+ placeholder,
225
+ }: {
226
+ value: string;
227
+ onChange: (value: string) => void;
228
+ placeholder?: string;
229
+ }) {
230
+ const inputRef = React.useRef<HTMLInputElement>(null);
231
+
232
+ // Focus after Base UI's own initial focus management has run.
233
+ React.useEffect(() => {
234
+ const frame = requestAnimationFrame(() => inputRef.current?.focus());
235
+ return () => cancelAnimationFrame(frame);
236
+ }, []);
237
+
238
+ return (
239
+ <div
240
+ className="flex shrink-0 items-center gap-2 border-b px-2.5"
241
+ data-slot="multi-select-search"
242
+ >
243
+ <SearchIcon className="size-4.5 shrink-0 opacity-64 sm:size-4" />
244
+ <input
245
+ className="h-9 min-w-0 flex-1 bg-transparent text-base text-foreground outline-none placeholder:text-muted-foreground sm:h-8 sm:text-sm"
246
+ onChange={(event) => onChange(event.target.value)}
247
+ onKeyDown={(event) => {
248
+ // Let list navigation keys bubble to Base UI; keep everything else
249
+ // (printable characters, Space, Backspace…) from triggering the
250
+ // select's typeahead while typing in the search box.
251
+ if (
252
+ event.key !== "ArrowDown" &&
253
+ event.key !== "ArrowUp" &&
254
+ event.key !== "Enter" &&
255
+ event.key !== "Escape"
256
+ ) {
257
+ event.stopPropagation();
258
+ }
259
+ }}
260
+ placeholder={placeholder}
261
+ ref={inputRef}
262
+ value={value}
263
+ />
264
+ </div>
265
+ );
266
+ }
267
+
268
+ function MultiSelectOption({
269
+ label,
270
+ value,
271
+ disabled,
272
+ }: {
273
+ label: string;
274
+ value: string;
275
+ disabled?: boolean;
276
+ }) {
277
+ return (
278
+ <SelectPrimitive.Item
279
+ className="grid min-h-8 cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 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 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
280
+ data-slot="multi-select-item"
281
+ disabled={disabled}
282
+ value={value}
283
+ >
284
+ <SelectPrimitive.ItemIndicator className="col-start-1">
285
+ {/** biome-ignore lint/a11y/noSvgWithoutTitle: Imported from library */}
286
+ <svg
287
+ fill="none"
288
+ height="24"
289
+ stroke="currentColor"
290
+ strokeLinecap="round"
291
+ strokeLinejoin="round"
292
+ strokeWidth="2"
293
+ viewBox="0 0 24 24"
294
+ width="24"
295
+ xmlns="http://www.w3.org/1500/svg"
296
+ >
297
+ <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
298
+ </svg>
299
+ </SelectPrimitive.ItemIndicator>
300
+ <SelectPrimitive.ItemText className="col-start-2 min-w-0 truncate">
301
+ {label}
302
+ </SelectPrimitive.ItemText>
303
+ </SelectPrimitive.Item>
304
+ );
305
+ }
306
+
307
+ export { MultiSelect };
308
+ export type { MultiSelectItem, MultiSelectProps };
@@ -0,0 +1,143 @@
1
+ "use client";
2
+
3
+ import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu";
4
+ import { cva } from "class-variance-authority";
5
+ import { ChevronDownIcon } from "lucide-react";
6
+ import type * as React from "react";
7
+
8
+ import { cn } from "../../lib/utils/css";
9
+
10
+ function NavigationMenu({
11
+ className,
12
+ children,
13
+ ...props
14
+ }: NavigationMenuPrimitive.Root.Props) {
15
+ return (
16
+ <NavigationMenuPrimitive.Root
17
+ className={cn(
18
+ "relative flex max-w-max flex-1 items-center justify-center",
19
+ className,
20
+ )}
21
+ data-slot="navigation-menu"
22
+ {...props}
23
+ >
24
+ {children}
25
+ <NavigationMenuPrimitive.Portal>
26
+ <NavigationMenuPrimitive.Positioner
27
+ className="z-50 box-border h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] before:absolute before:content-['']"
28
+ collisionAvoidance={{ side: "none" }}
29
+ collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
30
+ data-slot="navigation-menu-positioner"
31
+ sideOffset={8}
32
+ >
33
+ <NavigationMenuPrimitive.Popup
34
+ className="relative h-(--popup-height) w-full min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 outline-none transition-[opacity,transform,width,height,scale,translate] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 sm:w-(--popup-width) dark:before:shadow-[0_-1px_--theme(--color-white/6%)]"
35
+ data-slot="navigation-menu-popup"
36
+ >
37
+ <NavigationMenuPrimitive.Viewport
38
+ className="relative h-full w-full overflow-hidden"
39
+ data-slot="navigation-menu-viewport"
40
+ />
41
+ </NavigationMenuPrimitive.Popup>
42
+ </NavigationMenuPrimitive.Positioner>
43
+ </NavigationMenuPrimitive.Portal>
44
+ </NavigationMenuPrimitive.Root>
45
+ );
46
+ }
47
+
48
+ function NavigationMenuList({
49
+ className,
50
+ ...props
51
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
52
+ return (
53
+ <NavigationMenuPrimitive.List
54
+ className={cn(
55
+ "group flex flex-1 list-none items-center justify-center gap-1",
56
+ className,
57
+ )}
58
+ data-slot="navigation-menu-list"
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function NavigationMenuItem({
65
+ className,
66
+ ...props
67
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
68
+ return (
69
+ <NavigationMenuPrimitive.Item
70
+ className={cn("relative", className)}
71
+ data-slot="navigation-menu-item"
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ const navigationMenuTriggerStyle = cva(
78
+ "group inline-flex h-9 w-max cursor-pointer select-none items-center justify-center gap-1 rounded-lg bg-transparent px-3 py-2 font-medium text-base outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-popup-open:bg-accent/50 data-popup-open:text-accent-foreground disabled:pointer-events-none disabled:opacity-64 sm:h-8 sm:text-sm",
79
+ );
80
+
81
+ function NavigationMenuTrigger({
82
+ className,
83
+ children,
84
+ ...props
85
+ }: NavigationMenuPrimitive.Trigger.Props) {
86
+ return (
87
+ <NavigationMenuPrimitive.Trigger
88
+ className={cn(navigationMenuTriggerStyle(), className)}
89
+ data-slot="navigation-menu-trigger"
90
+ {...props}
91
+ >
92
+ {children}
93
+ <NavigationMenuPrimitive.Icon
94
+ className="relative top-px transition-transform duration-300 ease-in-out group-data-popup-open:rotate-180"
95
+ data-slot="navigation-menu-icon"
96
+ >
97
+ <ChevronDownIcon className="size-3.5 opacity-80" />
98
+ </NavigationMenuPrimitive.Icon>
99
+ </NavigationMenuPrimitive.Trigger>
100
+ );
101
+ }
102
+
103
+ function NavigationMenuContent({
104
+ className,
105
+ ...props
106
+ }: NavigationMenuPrimitive.Content.Props) {
107
+ return (
108
+ <NavigationMenuPrimitive.Content
109
+ className={cn(
110
+ "h-full w-max p-2 transition-[opacity,translate,transform] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] data-ending-style:opacity-0 data-starting-style:opacity-0 data-[activation-direction=left]:data-ending-style:translate-x-[50%] data-[activation-direction=left]:data-starting-style:translate-x-[-50%] data-[activation-direction=right]:data-ending-style:translate-x-[-50%] data-[activation-direction=right]:data-starting-style:translate-x-[50%]",
111
+ className,
112
+ )}
113
+ data-slot="navigation-menu-content"
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ function NavigationMenuLink({
120
+ className,
121
+ ...props
122
+ }: NavigationMenuPrimitive.Link.Props) {
123
+ return (
124
+ <NavigationMenuPrimitive.Link
125
+ className={cn(
126
+ "flex cursor-pointer flex-col gap-1 rounded-md p-2 text-base no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background sm:text-sm [&_svg:not([class*='size-'])]:size-4",
127
+ className,
128
+ )}
129
+ data-slot="navigation-menu-link"
130
+ {...props}
131
+ />
132
+ );
133
+ }
134
+
135
+ export {
136
+ NavigationMenu,
137
+ NavigationMenuList,
138
+ NavigationMenuItem,
139
+ NavigationMenuTrigger,
140
+ NavigationMenuContent,
141
+ NavigationMenuLink,
142
+ navigationMenuTriggerStyle,
143
+ };