question-bank-framework 0.7.1 → 0.9.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 (33) hide show
  1. package/README.md +22 -7
  2. package/framework/components/app-shell.tsx +40 -26
  3. package/framework/components/exam-timer.tsx +134 -0
  4. package/framework/components/filter-builder.tsx +65 -63
  5. package/framework/components/markdown-content.tsx +15 -15
  6. package/framework/components/question-bank.tsx +150 -227
  7. package/framework/components/question-response.tsx +41 -31
  8. package/framework/components/quiz-list.tsx +42 -37
  9. package/framework/components/quiz-page.tsx +63 -62
  10. package/framework/components/quiz-question-editor.tsx +11 -11
  11. package/framework/components/ui/badge.tsx +29 -23
  12. package/framework/components/ui/button.tsx +24 -28
  13. package/framework/components/ui/card.tsx +104 -0
  14. package/framework/components/ui/checkbox.tsx +9 -10
  15. package/framework/components/ui/dropdown-menu.tsx +139 -123
  16. package/framework/components/ui/field.tsx +238 -0
  17. package/framework/components/ui/input.tsx +6 -5
  18. package/framework/components/ui/kbd.tsx +28 -0
  19. package/framework/components/ui/label.tsx +19 -0
  20. package/framework/components/ui/popover.tsx +35 -32
  21. package/framework/components/ui/select.tsx +80 -65
  22. package/framework/components/ui/separator.tsx +24 -0
  23. package/framework/components/ui/sheet.tsx +36 -39
  24. package/framework/components/ui/table.tsx +4 -4
  25. package/framework/components/ui/toast.tsx +234 -0
  26. package/framework/components/ui/tooltip.tsx +65 -0
  27. package/framework/index.ts +4 -0
  28. package/framework/lib/exam-timer.ts +115 -0
  29. package/framework/styles/theme.css +66 -28
  30. package/package.json +4 -1
  31. package/template/src/routes/__root.tsx +1 -1
  32. package/template/src/routes/styles.css +0 -4
  33. package/template/src/routes/timer.tsx +4 -0
@@ -1,60 +1,78 @@
1
- "use client"
1
+ // shadcn/ui dropdown-menu (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/dropdown-menu.tsx`.
2
2
 
3
3
  import * as React from "react"
4
- import { cn } from '../../lib/utils.ts'
5
- import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
- import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
4
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
5
+ import { CheckIcon, ChevronRightIcon } from "lucide-react"
6
+ import { cn } from "../../lib/utils.ts"
7
7
 
8
- function DropdownMenu({
9
- ...props
10
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
11
- return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
8
+
9
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
10
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
12
11
  }
13
12
 
14
- function DropdownMenuPortal({
15
- ...props
16
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
17
- return (
18
- <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
19
- )
13
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
14
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
15
  }
21
16
 
22
- function DropdownMenuTrigger({
23
- ...props
24
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
25
- return (
26
- <DropdownMenuPrimitive.Trigger
27
- data-slot="dropdown-menu-trigger"
28
- {...props}
29
- />
30
- )
17
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
18
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
31
19
  }
32
20
 
33
21
  function DropdownMenuContent({
34
- className,
22
+ align = "start",
23
+ alignOffset = 0,
24
+ side = "bottom",
35
25
  sideOffset = 4,
26
+ className,
36
27
  ...props
37
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
28
+ }: MenuPrimitive.Popup.Props &
29
+ Pick<
30
+ MenuPrimitive.Positioner.Props,
31
+ "align" | "alignOffset" | "side" | "sideOffset"
32
+ >) {
38
33
  return (
39
- <DropdownMenuPrimitive.Portal>
40
- <DropdownMenuPrimitive.Content
41
- data-slot="dropdown-menu-content"
34
+ <MenuPrimitive.Portal>
35
+ <MenuPrimitive.Positioner
36
+ className="isolate z-50 outline-none"
37
+ align={align}
38
+ alignOffset={alignOffset}
39
+ side={side}
42
40
  sideOffset={sideOffset}
43
- className={cn(
44
- "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",
45
- className
46
- )}
47
- {...props}
48
- />
49
- </DropdownMenuPrimitive.Portal>
41
+ >
42
+ <MenuPrimitive.Popup
43
+ data-slot="dropdown-menu-content"
44
+ className={cn(
45
+ "z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </MenuPrimitive.Positioner>
51
+ </MenuPrimitive.Portal>
50
52
  )
51
53
  }
52
54
 
53
- function DropdownMenuGroup({
55
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
56
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
57
+ }
58
+
59
+ function DropdownMenuLabel({
60
+ className,
61
+ inset,
54
62
  ...props
55
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
63
+ }: MenuPrimitive.GroupLabel.Props & {
64
+ inset?: boolean
65
+ }) {
56
66
  return (
57
- <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
67
+ <MenuPrimitive.GroupLabel
68
+ data-slot="dropdown-menu-label"
69
+ data-inset={inset}
70
+ className={cn(
71
+ "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
72
+ className
73
+ )}
74
+ {...props}
75
+ />
58
76
  )
59
77
  }
60
78
 
@@ -63,17 +81,17 @@ function DropdownMenuItem({
63
81
  inset,
64
82
  variant = "default",
65
83
  ...props
66
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
84
+ }: MenuPrimitive.Item.Props & {
67
85
  inset?: boolean
68
86
  variant?: "default" | "destructive"
69
87
  }) {
70
88
  return (
71
- <DropdownMenuPrimitive.Item
89
+ <MenuPrimitive.Item
72
90
  data-slot="dropdown-menu-item"
73
91
  data-inset={inset}
74
92
  data-variant={variant}
75
93
  className={cn(
76
- "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!",
94
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 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 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
77
95
  className
78
96
  )}
79
97
  {...props}
@@ -81,93 +99,137 @@ function DropdownMenuItem({
81
99
  )
82
100
  }
83
101
 
84
- function DropdownMenuCheckboxItem({
102
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
103
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
104
+ }
105
+
106
+ function DropdownMenuSubTrigger({
85
107
  className,
108
+ inset,
86
109
  children,
87
- checked,
88
110
  ...props
89
- }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
111
+ }: MenuPrimitive.SubmenuTrigger.Props & {
112
+ inset?: boolean
113
+ }) {
90
114
  return (
91
- <DropdownMenuPrimitive.CheckboxItem
92
- data-slot="dropdown-menu-checkbox-item"
115
+ <MenuPrimitive.SubmenuTrigger
116
+ data-slot="dropdown-menu-sub-trigger"
117
+ data-inset={inset}
93
118
  className={cn(
94
- "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",
119
+ "flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
95
120
  className
96
121
  )}
97
- checked={checked}
98
122
  {...props}
99
123
  >
100
- <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
101
- <DropdownMenuPrimitive.ItemIndicator>
102
- <CheckIcon className="size-4" />
103
- </DropdownMenuPrimitive.ItemIndicator>
104
- </span>
105
124
  {children}
106
- </DropdownMenuPrimitive.CheckboxItem>
125
+ <ChevronRightIcon className="ml-auto" />
126
+ </MenuPrimitive.SubmenuTrigger>
107
127
  )
108
128
  }
109
129
 
110
- function DropdownMenuRadioGroup({
130
+ function DropdownMenuSubContent({
131
+ align = "start",
132
+ alignOffset = -3,
133
+ side = "right",
134
+ sideOffset = 0,
135
+ className,
111
136
  ...props
112
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
137
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
113
138
  return (
114
- <DropdownMenuPrimitive.RadioGroup
115
- data-slot="dropdown-menu-radio-group"
139
+ <DropdownMenuContent
140
+ data-slot="dropdown-menu-sub-content"
141
+ className={cn(
142
+ "w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
143
+ className
144
+ )}
145
+ align={align}
146
+ alignOffset={alignOffset}
147
+ side={side}
148
+ sideOffset={sideOffset}
116
149
  {...props}
117
150
  />
118
151
  )
119
152
  }
120
153
 
121
- function DropdownMenuRadioItem({
154
+ function DropdownMenuCheckboxItem({
122
155
  className,
123
156
  children,
157
+ checked,
158
+ inset,
124
159
  ...props
125
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
160
+ }: MenuPrimitive.CheckboxItem.Props & {
161
+ inset?: boolean
162
+ }) {
126
163
  return (
127
- <DropdownMenuPrimitive.RadioItem
128
- data-slot="dropdown-menu-radio-item"
164
+ <MenuPrimitive.CheckboxItem
165
+ data-slot="dropdown-menu-checkbox-item"
166
+ data-inset={inset}
129
167
  className={cn(
130
- "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",
168
+ "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
131
169
  className
132
170
  )}
171
+ checked={checked}
133
172
  {...props}
134
173
  >
135
- <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
136
- <DropdownMenuPrimitive.ItemIndicator>
137
- <CircleIcon className="size-2 fill-current" />
138
- </DropdownMenuPrimitive.ItemIndicator>
174
+ <span
175
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
176
+ data-slot="dropdown-menu-checkbox-item-indicator"
177
+ >
178
+ <MenuPrimitive.CheckboxItemIndicator>
179
+ <CheckIcon />
180
+ </MenuPrimitive.CheckboxItemIndicator>
139
181
  </span>
140
182
  {children}
141
- </DropdownMenuPrimitive.RadioItem>
183
+ </MenuPrimitive.CheckboxItem>
142
184
  )
143
185
  }
144
186
 
145
- function DropdownMenuLabel({
187
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
188
+ return (
189
+ <MenuPrimitive.RadioGroup
190
+ data-slot="dropdown-menu-radio-group"
191
+ {...props}
192
+ />
193
+ )
194
+ }
195
+
196
+ function DropdownMenuRadioItem({
146
197
  className,
198
+ children,
147
199
  inset,
148
200
  ...props
149
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
201
+ }: MenuPrimitive.RadioItem.Props & {
150
202
  inset?: boolean
151
203
  }) {
152
204
  return (
153
- <DropdownMenuPrimitive.Label
154
- data-slot="dropdown-menu-label"
205
+ <MenuPrimitive.RadioItem
206
+ data-slot="dropdown-menu-radio-item"
155
207
  data-inset={inset}
156
208
  className={cn(
157
- "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
209
+ "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
158
210
  className
159
211
  )}
160
212
  {...props}
161
- />
213
+ >
214
+ <span
215
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
216
+ data-slot="dropdown-menu-radio-item-indicator"
217
+ >
218
+ <MenuPrimitive.RadioItemIndicator>
219
+ <CheckIcon />
220
+ </MenuPrimitive.RadioItemIndicator>
221
+ </span>
222
+ {children}
223
+ </MenuPrimitive.RadioItem>
162
224
  )
163
225
  }
164
226
 
165
227
  function DropdownMenuSeparator({
166
228
  className,
167
229
  ...props
168
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
230
+ }: MenuPrimitive.Separator.Props) {
169
231
  return (
170
- <DropdownMenuPrimitive.Separator
232
+ <MenuPrimitive.Separator
171
233
  data-slot="dropdown-menu-separator"
172
234
  className={cn("-mx-1 my-1 h-px bg-border", className)}
173
235
  {...props}
@@ -183,53 +245,7 @@ function DropdownMenuShortcut({
183
245
  <span
184
246
  data-slot="dropdown-menu-shortcut"
185
247
  className={cn(
186
- "ml-auto text-xs tracking-widest text-muted-foreground",
187
- className
188
- )}
189
- {...props}
190
- />
191
- )
192
- }
193
-
194
- function DropdownMenuSub({
195
- ...props
196
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
197
- return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
198
- }
199
-
200
- function DropdownMenuSubTrigger({
201
- className,
202
- inset,
203
- children,
204
- ...props
205
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
206
- inset?: boolean
207
- }) {
208
- return (
209
- <DropdownMenuPrimitive.SubTrigger
210
- data-slot="dropdown-menu-sub-trigger"
211
- data-inset={inset}
212
- className={cn(
213
- "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",
214
- className
215
- )}
216
- {...props}
217
- >
218
- {children}
219
- <ChevronRightIcon className="ml-auto size-4" />
220
- </DropdownMenuPrimitive.SubTrigger>
221
- )
222
- }
223
-
224
- function DropdownMenuSubContent({
225
- className,
226
- ...props
227
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
228
- return (
229
- <DropdownMenuPrimitive.SubContent
230
- data-slot="dropdown-menu-sub-content"
231
- className={cn(
232
- "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",
248
+ "ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
233
249
  className
234
250
  )}
235
251
  {...props}
@@ -0,0 +1,238 @@
1
+ // shadcn/ui field (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/field.tsx`.
2
+
3
+ import { useMemo } from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import { cn } from "../../lib/utils.ts"
6
+
7
+ import { Label } from "./label"
8
+ import { Separator } from "./separator"
9
+
10
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className={cn(
15
+ "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function FieldLegend({
24
+ className,
25
+ variant = "legend",
26
+ ...props
27
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
28
+ return (
29
+ <legend
30
+ data-slot="field-legend"
31
+ data-variant={variant}
32
+ className={cn(
33
+ "mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ const fieldVariants = cva(
55
+ "group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
56
+ {
57
+ variants: {
58
+ orientation: {
59
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
60
+ horizontal:
61
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ responsive:
63
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ orientation: "vertical",
68
+ },
69
+ }
70
+ )
71
+
72
+ function Field({
73
+ className,
74
+ orientation = "vertical",
75
+ ...props
76
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
77
+ return (
78
+ <div
79
+ role="group"
80
+ data-slot="field"
81
+ data-orientation={orientation}
82
+ className={cn(fieldVariants({ orientation }), className)}
83
+ {...props}
84
+ />
85
+ )
86
+ }
87
+
88
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="field-content"
92
+ className={cn(
93
+ "group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
94
+ className
95
+ )}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function FieldLabel({
102
+ className,
103
+ ...props
104
+ }: React.ComponentProps<typeof Label>) {
105
+ return (
106
+ <Label
107
+ data-slot="field-label"
108
+ className={cn(
109
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
110
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
119
+ return (
120
+ <div
121
+ data-slot="field-label"
122
+ className={cn(
123
+ "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
132
+ return (
133
+ <p
134
+ data-slot="field-description"
135
+ className={cn(
136
+ "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
137
+ "last:mt-0 nth-last-2:-mt-1",
138
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function FieldSeparator({
147
+ children,
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<"div"> & {
151
+ children?: React.ReactNode
152
+ }) {
153
+ return (
154
+ <div
155
+ data-slot="field-separator"
156
+ data-content={!!children}
157
+ className={cn(
158
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <Separator className="absolute inset-0 top-1/2" />
164
+ {children && (
165
+ <span
166
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
167
+ data-slot="field-separator-content"
168
+ >
169
+ {children}
170
+ </span>
171
+ )}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ function FieldError({
177
+ className,
178
+ children,
179
+ errors,
180
+ ...props
181
+ }: React.ComponentProps<"div"> & {
182
+ errors?: Array<{ message?: string } | undefined>
183
+ }) {
184
+ const content = useMemo(() => {
185
+ if (children) {
186
+ return children
187
+ }
188
+
189
+ if (!errors?.length) {
190
+ return null
191
+ }
192
+
193
+ const uniqueErrors = [
194
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
195
+ ]
196
+
197
+ if (uniqueErrors?.length == 1) {
198
+ return uniqueErrors[0]?.message
199
+ }
200
+
201
+ return (
202
+ <ul className="ml-4 flex list-disc flex-col gap-1">
203
+ {uniqueErrors.map(
204
+ (error, index) =>
205
+ error?.message && <li key={index}>{error.message}</li>
206
+ )}
207
+ </ul>
208
+ )
209
+ }, [children, errors])
210
+
211
+ if (!content) {
212
+ return null
213
+ }
214
+
215
+ return (
216
+ <div
217
+ role="alert"
218
+ data-slot="field-error"
219
+ className={cn("text-sm font-normal text-destructive", className)}
220
+ {...props}
221
+ >
222
+ {content}
223
+ </div>
224
+ )
225
+ }
226
+
227
+ export {
228
+ Field,
229
+ FieldLabel,
230
+ FieldDescription,
231
+ FieldError,
232
+ FieldGroup,
233
+ FieldLegend,
234
+ FieldSeparator,
235
+ FieldSet,
236
+ FieldContent,
237
+ FieldTitle,
238
+ }
@@ -1,15 +1,16 @@
1
+ // shadcn/ui input (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/input.tsx`.
2
+
1
3
  import * as React from "react"
2
- import { cn } from '../../lib/utils.ts'
4
+ import { Input as InputPrimitive } from "@base-ui/react/input"
5
+ import { cn } from "../../lib/utils.ts"
3
6
 
4
7
  function Input({ className, type, ...props }: React.ComponentProps<"input">) {
5
8
  return (
6
- <input
9
+ <InputPrimitive
7
10
  type={type}
8
11
  data-slot="input"
9
12
  className={cn(
10
- "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
11
- "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
12
- "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
13
+ "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
13
14
  className
14
15
  )}
15
16
  {...props}
@@ -0,0 +1,28 @@
1
+ // shadcn/ui kbd (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/kbd.tsx`.
2
+
3
+ import { cn } from "../../lib/utils.ts"
4
+
5
+ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
6
+ return (
7
+ <kbd
8
+ data-slot="kbd"
9
+ className={cn(
10
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <kbd
21
+ data-slot="kbd-group"
22
+ className={cn("inline-flex items-center gap-1", className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export { Kbd, KbdGroup }
@@ -0,0 +1,19 @@
1
+ // shadcn/ui label (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/label.tsx`.
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils.ts"
5
+
6
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
7
+ return (
8
+ <label
9
+ data-slot="label"
10
+ className={cn(
11
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Label }