question-bank-framework 0.8.0 → 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.
- package/README.md +11 -7
- package/framework/components/app-shell.tsx +11 -7
- package/framework/components/exam-timer.tsx +36 -27
- package/framework/components/filter-builder.tsx +65 -63
- package/framework/components/markdown-content.tsx +15 -15
- package/framework/components/question-bank.tsx +150 -227
- package/framework/components/question-response.tsx +41 -31
- package/framework/components/quiz-list.tsx +42 -37
- package/framework/components/quiz-page.tsx +63 -62
- package/framework/components/quiz-question-editor.tsx +11 -11
- package/framework/components/ui/badge.tsx +29 -23
- package/framework/components/ui/button.tsx +24 -28
- package/framework/components/ui/card.tsx +104 -0
- package/framework/components/ui/checkbox.tsx +9 -10
- package/framework/components/ui/dropdown-menu.tsx +139 -123
- package/framework/components/ui/field.tsx +238 -0
- package/framework/components/ui/input.tsx +6 -5
- package/framework/components/ui/kbd.tsx +28 -0
- package/framework/components/ui/label.tsx +19 -0
- package/framework/components/ui/popover.tsx +35 -32
- package/framework/components/ui/select.tsx +80 -65
- package/framework/components/ui/separator.tsx +24 -0
- package/framework/components/ui/sheet.tsx +36 -39
- package/framework/components/ui/table.tsx +4 -4
- package/framework/components/ui/toast.tsx +2 -2
- package/framework/components/ui/tooltip.tsx +65 -0
- package/framework/styles/theme.css +66 -28
- package/package.json +3 -1
- package/template/src/routes/__root.tsx +1 -1
- package/template/src/routes/styles.css +0 -4
|
@@ -1,60 +1,78 @@
|
|
|
1
|
-
|
|
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 {
|
|
5
|
-
import { CheckIcon, ChevronRightIcon
|
|
6
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
22
|
+
align = "start",
|
|
23
|
+
alignOffset = 0,
|
|
24
|
+
side = "bottom",
|
|
35
25
|
sideOffset = 4,
|
|
26
|
+
className,
|
|
36
27
|
...props
|
|
37
|
-
}:
|
|
28
|
+
}: MenuPrimitive.Popup.Props &
|
|
29
|
+
Pick<
|
|
30
|
+
MenuPrimitive.Positioner.Props,
|
|
31
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
32
|
+
>) {
|
|
38
33
|
return (
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
}:
|
|
63
|
+
}: MenuPrimitive.GroupLabel.Props & {
|
|
64
|
+
inset?: boolean
|
|
65
|
+
}) {
|
|
56
66
|
return (
|
|
57
|
-
<
|
|
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
|
-
}:
|
|
84
|
+
}: MenuPrimitive.Item.Props & {
|
|
67
85
|
inset?: boolean
|
|
68
86
|
variant?: "default" | "destructive"
|
|
69
87
|
}) {
|
|
70
88
|
return (
|
|
71
|
-
<
|
|
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-
|
|
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
|
|
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
|
-
}:
|
|
111
|
+
}: MenuPrimitive.SubmenuTrigger.Props & {
|
|
112
|
+
inset?: boolean
|
|
113
|
+
}) {
|
|
90
114
|
return (
|
|
91
|
-
<
|
|
92
|
-
data-slot="dropdown-menu-
|
|
115
|
+
<MenuPrimitive.SubmenuTrigger
|
|
116
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
117
|
+
data-inset={inset}
|
|
93
118
|
className={cn(
|
|
94
|
-
"
|
|
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
|
-
|
|
125
|
+
<ChevronRightIcon className="ml-auto" />
|
|
126
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
107
127
|
)
|
|
108
128
|
}
|
|
109
129
|
|
|
110
|
-
function
|
|
130
|
+
function DropdownMenuSubContent({
|
|
131
|
+
align = "start",
|
|
132
|
+
alignOffset = -3,
|
|
133
|
+
side = "right",
|
|
134
|
+
sideOffset = 0,
|
|
135
|
+
className,
|
|
111
136
|
...props
|
|
112
|
-
}: React.ComponentProps<typeof
|
|
137
|
+
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
|
113
138
|
return (
|
|
114
|
-
<
|
|
115
|
-
data-slot="dropdown-menu-
|
|
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
|
|
154
|
+
function DropdownMenuCheckboxItem({
|
|
122
155
|
className,
|
|
123
156
|
children,
|
|
157
|
+
checked,
|
|
158
|
+
inset,
|
|
124
159
|
...props
|
|
125
|
-
}:
|
|
160
|
+
}: MenuPrimitive.CheckboxItem.Props & {
|
|
161
|
+
inset?: boolean
|
|
162
|
+
}) {
|
|
126
163
|
return (
|
|
127
|
-
<
|
|
128
|
-
data-slot="dropdown-menu-
|
|
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-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
</
|
|
183
|
+
</MenuPrimitive.CheckboxItem>
|
|
142
184
|
)
|
|
143
185
|
}
|
|
144
186
|
|
|
145
|
-
function
|
|
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
|
-
}:
|
|
201
|
+
}: MenuPrimitive.RadioItem.Props & {
|
|
150
202
|
inset?: boolean
|
|
151
203
|
}) {
|
|
152
204
|
return (
|
|
153
|
-
<
|
|
154
|
-
data-slot="dropdown-menu-
|
|
205
|
+
<MenuPrimitive.RadioItem
|
|
206
|
+
data-slot="dropdown-menu-radio-item"
|
|
155
207
|
data-inset={inset}
|
|
156
208
|
className={cn(
|
|
157
|
-
"
|
|
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
|
-
}:
|
|
230
|
+
}: MenuPrimitive.Separator.Props) {
|
|
169
231
|
return (
|
|
170
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
9
|
+
<InputPrimitive
|
|
7
10
|
type={type}
|
|
8
11
|
data-slot="input"
|
|
9
12
|
className={cn(
|
|
10
|
-
"h-
|
|
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 }
|