@cosmicdrift/kumiko-renderer-web 1.0.0 → 2.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 (79) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/action-menu.test.tsx +75 -0
  3. package/src/__tests__/breadcrumb.test.tsx +50 -0
  4. package/src/__tests__/create-app.test.tsx +171 -1
  5. package/src/__tests__/dashboard-screen.test.tsx +393 -0
  6. package/src/__tests__/default-app-shell.test.tsx +47 -1
  7. package/src/__tests__/form-action-bar.test.tsx +9 -0
  8. package/src/__tests__/kumiko-screen.test.tsx +155 -1
  9. package/src/__tests__/lightbox.test.tsx +46 -0
  10. package/src/__tests__/nav-tree.test.tsx +122 -0
  11. package/src/__tests__/primitives.test.tsx +32 -1
  12. package/src/__tests__/profile-menu.test.tsx +29 -0
  13. package/src/__tests__/projection-detail.test.tsx +101 -0
  14. package/src/__tests__/projection-list-actions.test.tsx +131 -0
  15. package/src/__tests__/render-list.test.tsx +11 -3
  16. package/src/__tests__/test-utils.tsx +1 -0
  17. package/src/__tests__/toast.test.tsx +9 -9
  18. package/src/__tests__/use-mutation.test.tsx +86 -0
  19. package/src/__tests__/workspace-shell.test.tsx +102 -47
  20. package/src/__tests__/xss-safety.test.tsx +62 -0
  21. package/src/app/__tests__/first-open-screen-qn.test.ts +77 -0
  22. package/src/app/browser-locale.ts +1 -1
  23. package/src/app/create-app.tsx +96 -29
  24. package/src/app/dashboard-body.tsx +454 -0
  25. package/src/index.ts +75 -1
  26. package/src/layout/__tests__/nav-registry.test.ts +105 -0
  27. package/src/layout/__tests__/shell-breadcrumb.test.ts +82 -0
  28. package/src/layout/default-app-shell.tsx +9 -71
  29. package/src/layout/nav-tree.tsx +167 -37
  30. package/src/layout/shell-breadcrumb.ts +46 -0
  31. package/src/layout/shell-header.tsx +114 -0
  32. package/src/layout/workspace-shell.tsx +45 -18
  33. package/src/primitives/__tests__/data-table-logic.test.ts +27 -0
  34. package/src/primitives/__tests__/input-testid.test.tsx +45 -0
  35. package/src/primitives/__tests__/money-input.test.tsx +13 -1
  36. package/src/primitives/dialog.tsx +47 -76
  37. package/src/primitives/index.tsx +215 -37
  38. package/src/primitives/layout.tsx +39 -0
  39. package/src/primitives/lightbox.tsx +39 -0
  40. package/src/primitives/modal-shell.tsx +62 -0
  41. package/src/primitives/money-input.tsx +20 -10
  42. package/src/primitives/toast.tsx +14 -9
  43. package/src/sse/live-events.ts +6 -1
  44. package/src/styles.css +25 -0
  45. package/src/tokens.ts +4 -0
  46. package/src/ui/__tests__/avatar.test.tsx +66 -0
  47. package/src/ui/__tests__/sheet.test.tsx +102 -0
  48. package/src/version/__tests__/update-checker.test.tsx +115 -0
  49. package/src/widgets/__tests__/ai-text-field.test.tsx +267 -0
  50. package/src/widgets/__tests__/drawer.test.tsx +35 -0
  51. package/src/widgets/__tests__/form-kit.test.tsx +368 -0
  52. package/src/widgets/__tests__/infinity-list.test.tsx +132 -0
  53. package/src/widgets/__tests__/query-table.test.tsx +118 -0
  54. package/src/widgets/__tests__/result-panel.test.tsx +117 -0
  55. package/src/widgets/__tests__/widgets.test.tsx +243 -0
  56. package/src/widgets/ai-text-field.tsx +370 -0
  57. package/src/widgets/charts.tsx +228 -0
  58. package/src/widgets/collapsible-section.tsx +42 -0
  59. package/src/widgets/detail-list.tsx +43 -0
  60. package/src/widgets/drawer.tsx +49 -0
  61. package/src/widgets/feed-list.tsx +35 -0
  62. package/src/widgets/form-fields.tsx +326 -0
  63. package/src/widgets/index.ts +59 -0
  64. package/src/widgets/infinity-list.tsx +112 -0
  65. package/src/widgets/mode-switch.tsx +41 -0
  66. package/src/widgets/progress-bar.tsx +27 -0
  67. package/src/widgets/progress-list.tsx +38 -0
  68. package/src/widgets/query-table.tsx +103 -0
  69. package/src/widgets/result-panel.tsx +251 -0
  70. package/src/widgets/section-card.tsx +30 -0
  71. package/src/widgets/stat.tsx +181 -0
  72. package/src/widgets/states.tsx +89 -0
  73. package/src/widgets/status-badge.tsx +51 -0
  74. package/src/widgets/use-draft.ts +40 -0
  75. package/src/ui/card.tsx +0 -93
  76. package/src/ui/collapsible.tsx +0 -34
  77. package/src/ui/dropdown-menu.tsx +0 -258
  78. package/src/ui/select.tsx +0 -191
  79. /package/src/__tests__/{download.test.ts → download.test.tsx} +0 -0
@@ -0,0 +1,51 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ // Semantische Status-Tones — eine Farb-Familie für Component-Status,
5
+ // Incident-Status, Severity, Job-Zustände. Farben kommen aus den
6
+ // --color-status-* Theme-Tokens (styles.css), Apps überschreiben zentral.
7
+ export type StatusTone = "ok" | "warn" | "bad" | "critical" | "muted";
8
+
9
+ export const STATUS_TONE_TEXT: Record<StatusTone, string> = {
10
+ ok: "text-status-ok",
11
+ warn: "text-status-warn",
12
+ bad: "text-status-bad",
13
+ critical: "text-status-critical",
14
+ muted: "text-muted-foreground",
15
+ };
16
+
17
+ const TONE_PILL: Record<StatusTone, string> = {
18
+ ok: "bg-status-ok/10 text-status-ok",
19
+ warn: "bg-status-warn/10 text-status-warn",
20
+ bad: "bg-status-bad/10 text-status-bad",
21
+ critical: "bg-status-critical/15 text-status-critical",
22
+ muted: "bg-muted text-muted-foreground",
23
+ };
24
+
25
+ /** Pill-Badge für Status-Werte. Caller mappt Domain-Werte → Tone
26
+ * (z.B. operational→ok, investigating→warn) und liefert das
27
+ * translated Label als children. */
28
+ export function StatusBadge({
29
+ tone,
30
+ children,
31
+ className,
32
+ testId,
33
+ }: {
34
+ readonly tone: StatusTone;
35
+ readonly children: ReactNode;
36
+ readonly className?: string;
37
+ readonly testId?: string;
38
+ }): ReactNode {
39
+ return (
40
+ <span
41
+ data-testid={testId}
42
+ className={cn(
43
+ "inline-block rounded-xl px-2.5 py-1 text-xs font-semibold",
44
+ TONE_PILL[tone],
45
+ className,
46
+ )}
47
+ >
48
+ {children}
49
+ </span>
50
+ );
51
+ }
@@ -0,0 +1,40 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ /** Formular-State für Rechner-Screens: ein Draft-Objekt + `patch` für
4
+ * Teil-Updates + `field(name)` das die `{ id, name, value, onChange }`-Props
5
+ * fertig für die Feld-Widgets (NumberField/MoneyField/PercentField) liefert.
6
+ * Ersetzt das pro Screen wiederholte Draft-Interface + patch-Helper.
7
+ * `idPrefix` verhindert doppelte DOM-`id`s, wenn mehrere `useDraft`-Formulare
8
+ * mit gleichnamigen Feldern auf derselben Seite gemountet werden — `name`
9
+ * bleibt der reine Key fürs Form-Payload. */
10
+ export function useDraft<T extends object>(
11
+ defaults: T,
12
+ options?: { readonly idPrefix?: string },
13
+ ): {
14
+ readonly draft: T;
15
+ readonly patch: (changes: Partial<T>) => void;
16
+ readonly reset: () => void;
17
+ readonly field: <K extends keyof T>(
18
+ name: K,
19
+ ) => {
20
+ readonly id: string;
21
+ readonly name: string;
22
+ readonly value: T[K];
23
+ readonly onChange: (v: T[K]) => void;
24
+ };
25
+ } {
26
+ const idPrefix = options?.idPrefix;
27
+ const [draft, setDraft] = useState<T>(defaults);
28
+ const patch = useCallback((changes: Partial<T>) => setDraft((d) => ({ ...d, ...changes })), []);
29
+ const reset = useCallback(() => setDraft(defaults), [defaults]);
30
+ const field = useCallback(
31
+ <K extends keyof T>(name: K) => ({
32
+ id: idPrefix !== undefined ? `${idPrefix}-${String(name)}` : String(name),
33
+ name: String(name),
34
+ value: draft[name],
35
+ onChange: (v: T[K]) => setDraft((d) => ({ ...d, [name]: v })),
36
+ }),
37
+ [draft, idPrefix],
38
+ );
39
+ return { draft, patch, reset, field };
40
+ }
package/src/ui/card.tsx DELETED
@@ -1,93 +0,0 @@
1
- // @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
2
- import * as React from "react"
3
-
4
- import { cn } from "../lib/cn"
5
-
6
- function Card({ className, ...props }: React.ComponentProps<"div">) {
7
- return (
8
- <div
9
- data-slot="card"
10
- className={cn(
11
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
12
- className
13
- )}
14
- {...props}
15
- />
16
- )
17
- }
18
-
19
- function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
20
- return (
21
- <div
22
- data-slot="card-header"
23
- className={cn(
24
- "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
25
- className
26
- )}
27
- {...props}
28
- />
29
- )
30
- }
31
-
32
- function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
33
- return (
34
- <div
35
- data-slot="card-title"
36
- className={cn("leading-none font-semibold", className)}
37
- {...props}
38
- />
39
- )
40
- }
41
-
42
- function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
43
- return (
44
- <div
45
- data-slot="card-description"
46
- className={cn("text-sm text-muted-foreground", className)}
47
- {...props}
48
- />
49
- )
50
- }
51
-
52
- function CardAction({ className, ...props }: React.ComponentProps<"div">) {
53
- return (
54
- <div
55
- data-slot="card-action"
56
- className={cn(
57
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
58
- className
59
- )}
60
- {...props}
61
- />
62
- )
63
- }
64
-
65
- function CardContent({ className, ...props }: React.ComponentProps<"div">) {
66
- return (
67
- <div
68
- data-slot="card-content"
69
- className={cn("px-6", className)}
70
- {...props}
71
- />
72
- )
73
- }
74
-
75
- function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
76
- return (
77
- <div
78
- data-slot="card-footer"
79
- className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
80
- {...props}
81
- />
82
- )
83
- }
84
-
85
- export {
86
- Card,
87
- CardHeader,
88
- CardFooter,
89
- CardTitle,
90
- CardAction,
91
- CardDescription,
92
- CardContent,
93
- }
@@ -1,34 +0,0 @@
1
- // @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
2
- "use client"
3
-
4
- import { Collapsible as CollapsiblePrimitive } from "radix-ui"
5
-
6
- function Collapsible({
7
- ...props
8
- }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
9
- return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
10
- }
11
-
12
- function CollapsibleTrigger({
13
- ...props
14
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
15
- return (
16
- <CollapsiblePrimitive.CollapsibleTrigger
17
- data-slot="collapsible-trigger"
18
- {...props}
19
- />
20
- )
21
- }
22
-
23
- function CollapsibleContent({
24
- ...props
25
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
26
- return (
27
- <CollapsiblePrimitive.CollapsibleContent
28
- data-slot="collapsible-content"
29
- {...props}
30
- />
31
- )
32
- }
33
-
34
- export { Collapsible, CollapsibleTrigger, CollapsibleContent }
@@ -1,258 +0,0 @@
1
- // @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
2
- "use client"
3
-
4
- import * as React from "react"
5
- import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
- import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
7
-
8
- import { cn } from "../lib/cn"
9
-
10
- function DropdownMenu({
11
- ...props
12
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
13
- return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
14
- }
15
-
16
- function DropdownMenuPortal({
17
- ...props
18
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
19
- return (
20
- <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
21
- )
22
- }
23
-
24
- function DropdownMenuTrigger({
25
- ...props
26
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
27
- return (
28
- <DropdownMenuPrimitive.Trigger
29
- data-slot="dropdown-menu-trigger"
30
- {...props}
31
- />
32
- )
33
- }
34
-
35
- function DropdownMenuContent({
36
- className,
37
- sideOffset = 4,
38
- ...props
39
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
40
- return (
41
- <DropdownMenuPrimitive.Portal>
42
- <DropdownMenuPrimitive.Content
43
- data-slot="dropdown-menu-content"
44
- sideOffset={sideOffset}
45
- className={cn(
46
- "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
47
- className
48
- )}
49
- {...props}
50
- />
51
- </DropdownMenuPrimitive.Portal>
52
- )
53
- }
54
-
55
- function DropdownMenuGroup({
56
- ...props
57
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
58
- return (
59
- <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
60
- )
61
- }
62
-
63
- function DropdownMenuItem({
64
- className,
65
- inset,
66
- variant = "default",
67
- ...props
68
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
69
- inset?: boolean
70
- variant?: "default" | "destructive"
71
- }) {
72
- return (
73
- <DropdownMenuPrimitive.Item
74
- data-slot="dropdown-menu-item"
75
- data-inset={inset}
76
- data-variant={variant}
77
- className={cn(
78
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
79
- className
80
- )}
81
- {...props}
82
- />
83
- )
84
- }
85
-
86
- function DropdownMenuCheckboxItem({
87
- className,
88
- children,
89
- checked,
90
- ...props
91
- }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
92
- return (
93
- <DropdownMenuPrimitive.CheckboxItem
94
- data-slot="dropdown-menu-checkbox-item"
95
- className={cn(
96
- "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
97
- className
98
- )}
99
- checked={checked}
100
- {...props}
101
- >
102
- <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
103
- <DropdownMenuPrimitive.ItemIndicator>
104
- <CheckIcon className="size-4" />
105
- </DropdownMenuPrimitive.ItemIndicator>
106
- </span>
107
- {children}
108
- </DropdownMenuPrimitive.CheckboxItem>
109
- )
110
- }
111
-
112
- function DropdownMenuRadioGroup({
113
- ...props
114
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
115
- return (
116
- <DropdownMenuPrimitive.RadioGroup
117
- data-slot="dropdown-menu-radio-group"
118
- {...props}
119
- />
120
- )
121
- }
122
-
123
- function DropdownMenuRadioItem({
124
- className,
125
- children,
126
- ...props
127
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
128
- return (
129
- <DropdownMenuPrimitive.RadioItem
130
- data-slot="dropdown-menu-radio-item"
131
- className={cn(
132
- "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
133
- className
134
- )}
135
- {...props}
136
- >
137
- <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
138
- <DropdownMenuPrimitive.ItemIndicator>
139
- <CircleIcon className="size-2 fill-current" />
140
- </DropdownMenuPrimitive.ItemIndicator>
141
- </span>
142
- {children}
143
- </DropdownMenuPrimitive.RadioItem>
144
- )
145
- }
146
-
147
- function DropdownMenuLabel({
148
- className,
149
- inset,
150
- ...props
151
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
152
- inset?: boolean
153
- }) {
154
- return (
155
- <DropdownMenuPrimitive.Label
156
- data-slot="dropdown-menu-label"
157
- data-inset={inset}
158
- className={cn(
159
- "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
160
- className
161
- )}
162
- {...props}
163
- />
164
- )
165
- }
166
-
167
- function DropdownMenuSeparator({
168
- className,
169
- ...props
170
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
171
- return (
172
- <DropdownMenuPrimitive.Separator
173
- data-slot="dropdown-menu-separator"
174
- className={cn("-mx-1 my-1 h-px bg-border", className)}
175
- {...props}
176
- />
177
- )
178
- }
179
-
180
- function DropdownMenuShortcut({
181
- className,
182
- ...props
183
- }: React.ComponentProps<"span">) {
184
- return (
185
- <span
186
- data-slot="dropdown-menu-shortcut"
187
- className={cn(
188
- "ml-auto text-xs tracking-widest text-muted-foreground",
189
- className
190
- )}
191
- {...props}
192
- />
193
- )
194
- }
195
-
196
- function DropdownMenuSub({
197
- ...props
198
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
199
- return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
200
- }
201
-
202
- function DropdownMenuSubTrigger({
203
- className,
204
- inset,
205
- children,
206
- ...props
207
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
208
- inset?: boolean
209
- }) {
210
- return (
211
- <DropdownMenuPrimitive.SubTrigger
212
- data-slot="dropdown-menu-sub-trigger"
213
- data-inset={inset}
214
- className={cn(
215
- "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
216
- className
217
- )}
218
- {...props}
219
- >
220
- {children}
221
- <ChevronRightIcon className="ml-auto size-4" />
222
- </DropdownMenuPrimitive.SubTrigger>
223
- )
224
- }
225
-
226
- function DropdownMenuSubContent({
227
- className,
228
- ...props
229
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
230
- return (
231
- <DropdownMenuPrimitive.SubContent
232
- data-slot="dropdown-menu-sub-content"
233
- className={cn(
234
- "z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
235
- className
236
- )}
237
- {...props}
238
- />
239
- )
240
- }
241
-
242
- export {
243
- DropdownMenu,
244
- DropdownMenuPortal,
245
- DropdownMenuTrigger,
246
- DropdownMenuContent,
247
- DropdownMenuGroup,
248
- DropdownMenuLabel,
249
- DropdownMenuItem,
250
- DropdownMenuCheckboxItem,
251
- DropdownMenuRadioGroup,
252
- DropdownMenuRadioItem,
253
- DropdownMenuSeparator,
254
- DropdownMenuShortcut,
255
- DropdownMenuSub,
256
- DropdownMenuSubTrigger,
257
- DropdownMenuSubContent,
258
- }
package/src/ui/select.tsx DELETED
@@ -1,191 +0,0 @@
1
- // @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
2
- "use client"
3
-
4
- import * as React from "react"
5
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
- import { Select as SelectPrimitive } from "radix-ui"
7
-
8
- import { cn } from "../lib/cn"
9
-
10
- function Select({
11
- ...props
12
- }: React.ComponentProps<typeof SelectPrimitive.Root>) {
13
- return <SelectPrimitive.Root data-slot="select" {...props} />
14
- }
15
-
16
- function SelectGroup({
17
- ...props
18
- }: React.ComponentProps<typeof SelectPrimitive.Group>) {
19
- return <SelectPrimitive.Group data-slot="select-group" {...props} />
20
- }
21
-
22
- function SelectValue({
23
- ...props
24
- }: React.ComponentProps<typeof SelectPrimitive.Value>) {
25
- return <SelectPrimitive.Value data-slot="select-value" {...props} />
26
- }
27
-
28
- function SelectTrigger({
29
- className,
30
- size = "default",
31
- children,
32
- ...props
33
- }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
34
- size?: "sm" | "default"
35
- }) {
36
- return (
37
- <SelectPrimitive.Trigger
38
- data-slot="select-trigger"
39
- data-size={size}
40
- className={cn(
41
- "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
42
- className
43
- )}
44
- {...props}
45
- >
46
- {children}
47
- <SelectPrimitive.Icon asChild>
48
- <ChevronDownIcon className="size-4 opacity-50" />
49
- </SelectPrimitive.Icon>
50
- </SelectPrimitive.Trigger>
51
- )
52
- }
53
-
54
- function SelectContent({
55
- className,
56
- children,
57
- position = "item-aligned",
58
- align = "center",
59
- ...props
60
- }: React.ComponentProps<typeof SelectPrimitive.Content>) {
61
- return (
62
- <SelectPrimitive.Portal>
63
- <SelectPrimitive.Content
64
- data-slot="select-content"
65
- className={cn(
66
- "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
67
- position === "popper" &&
68
- "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
69
- className
70
- )}
71
- position={position}
72
- align={align}
73
- {...props}
74
- >
75
- <SelectScrollUpButton />
76
- <SelectPrimitive.Viewport
77
- className={cn(
78
- "p-1",
79
- position === "popper" &&
80
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
81
- )}
82
- >
83
- {children}
84
- </SelectPrimitive.Viewport>
85
- <SelectScrollDownButton />
86
- </SelectPrimitive.Content>
87
- </SelectPrimitive.Portal>
88
- )
89
- }
90
-
91
- function SelectLabel({
92
- className,
93
- ...props
94
- }: React.ComponentProps<typeof SelectPrimitive.Label>) {
95
- return (
96
- <SelectPrimitive.Label
97
- data-slot="select-label"
98
- className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
99
- {...props}
100
- />
101
- )
102
- }
103
-
104
- function SelectItem({
105
- className,
106
- children,
107
- ...props
108
- }: React.ComponentProps<typeof SelectPrimitive.Item>) {
109
- return (
110
- <SelectPrimitive.Item
111
- data-slot="select-item"
112
- className={cn(
113
- "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
114
- className
115
- )}
116
- {...props}
117
- >
118
- <span
119
- data-slot="select-item-indicator"
120
- className="absolute right-2 flex size-3.5 items-center justify-center"
121
- >
122
- <SelectPrimitive.ItemIndicator>
123
- <CheckIcon className="size-4" />
124
- </SelectPrimitive.ItemIndicator>
125
- </span>
126
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
127
- </SelectPrimitive.Item>
128
- )
129
- }
130
-
131
- function SelectSeparator({
132
- className,
133
- ...props
134
- }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
135
- return (
136
- <SelectPrimitive.Separator
137
- data-slot="select-separator"
138
- className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
139
- {...props}
140
- />
141
- )
142
- }
143
-
144
- function SelectScrollUpButton({
145
- className,
146
- ...props
147
- }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
148
- return (
149
- <SelectPrimitive.ScrollUpButton
150
- data-slot="select-scroll-up-button"
151
- className={cn(
152
- "flex cursor-default items-center justify-center py-1",
153
- className
154
- )}
155
- {...props}
156
- >
157
- <ChevronUpIcon className="size-4" />
158
- </SelectPrimitive.ScrollUpButton>
159
- )
160
- }
161
-
162
- function SelectScrollDownButton({
163
- className,
164
- ...props
165
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
166
- return (
167
- <SelectPrimitive.ScrollDownButton
168
- data-slot="select-scroll-down-button"
169
- className={cn(
170
- "flex cursor-default items-center justify-center py-1",
171
- className
172
- )}
173
- {...props}
174
- >
175
- <ChevronDownIcon className="size-4" />
176
- </SelectPrimitive.ScrollDownButton>
177
- )
178
- }
179
-
180
- export {
181
- Select,
182
- SelectContent,
183
- SelectGroup,
184
- SelectItem,
185
- SelectLabel,
186
- SelectScrollDownButton,
187
- SelectScrollUpButton,
188
- SelectSeparator,
189
- SelectTrigger,
190
- SelectValue,
191
- }