question-bank-framework 0.8.0 → 0.10.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 (32) hide show
  1. package/README.md +14 -8
  2. package/framework/components/app-shell.tsx +11 -7
  3. package/framework/components/exam-timer.tsx +64 -33
  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 +2 -2
  26. package/framework/components/ui/tooltip.tsx +65 -0
  27. package/framework/index.ts +1 -1
  28. package/framework/lib/exam-timer.ts +29 -10
  29. package/framework/styles/theme.css +66 -28
  30. package/package.json +3 -1
  31. package/template/src/routes/__root.tsx +1 -1
  32. package/template/src/routes/styles.css +0 -4
@@ -1,62 +1,66 @@
1
- "use client"
1
+ // shadcn/ui popover (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/popover.tsx`.
2
2
 
3
3
  import * as React from "react"
4
- import { cn } from '../../lib/utils.ts'
5
- import { Popover as PopoverPrimitive } from "radix-ui"
4
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
5
+ import { cn } from "../../lib/utils.ts"
6
6
 
7
- function Popover({
8
- ...props
9
- }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
7
+ function Popover({ ...props }: PopoverPrimitive.Root.Props) {
10
8
  return <PopoverPrimitive.Root data-slot="popover" {...props} />
11
9
  }
12
10
 
13
- function PopoverTrigger({
14
- ...props
15
- }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
11
+ function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
16
12
  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
17
13
  }
18
14
 
19
15
  function PopoverContent({
20
16
  className,
21
17
  align = "center",
18
+ alignOffset = 0,
19
+ side = "bottom",
22
20
  sideOffset = 4,
21
+ collisionPadding,
23
22
  ...props
24
- }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
23
+ }: PopoverPrimitive.Popup.Props &
24
+ Pick<
25
+ PopoverPrimitive.Positioner.Props,
26
+ "align" | "alignOffset" | "side" | "sideOffset" | "collisionPadding"
27
+ >) {
25
28
  return (
26
29
  <PopoverPrimitive.Portal>
27
- <PopoverPrimitive.Content
28
- data-slot="popover-content"
30
+ <PopoverPrimitive.Positioner
31
+ collisionPadding={collisionPadding}
29
32
  align={align}
33
+ alignOffset={alignOffset}
34
+ side={side}
30
35
  sideOffset={sideOffset}
31
- className={cn(
32
- "z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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",
33
- className
34
- )}
35
- {...props}
36
- />
36
+ className="isolate z-50"
37
+ >
38
+ <PopoverPrimitive.Popup
39
+ data-slot="popover-content"
40
+ className={cn(
41
+ "z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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:fade-out-0 data-closed:zoom-out-95",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ </PopoverPrimitive.Positioner>
37
47
  </PopoverPrimitive.Portal>
38
48
  )
39
49
  }
40
50
 
41
- function PopoverAnchor({
42
- ...props
43
- }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
44
- return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
45
- }
46
-
47
51
  function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
48
52
  return (
49
53
  <div
50
54
  data-slot="popover-header"
51
- className={cn("flex flex-col gap-1 text-sm", className)}
55
+ className={cn("flex flex-col gap-0.5 text-sm", className)}
52
56
  {...props}
53
57
  />
54
58
  )
55
59
  }
56
60
 
57
- function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
61
+ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
58
62
  return (
59
- <div
63
+ <PopoverPrimitive.Title
60
64
  data-slot="popover-title"
61
65
  className={cn("font-medium", className)}
62
66
  {...props}
@@ -67,9 +71,9 @@ function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
67
71
  function PopoverDescription({
68
72
  className,
69
73
  ...props
70
- }: React.ComponentProps<"p">) {
74
+ }: PopoverPrimitive.Description.Props) {
71
75
  return (
72
- <p
76
+ <PopoverPrimitive.Description
73
77
  data-slot="popover-description"
74
78
  className={cn("text-muted-foreground", className)}
75
79
  {...props}
@@ -79,10 +83,9 @@ function PopoverDescription({
79
83
 
80
84
  export {
81
85
  Popover,
82
- PopoverTrigger,
83
86
  PopoverContent,
84
- PopoverAnchor,
87
+ PopoverDescription,
85
88
  PopoverHeader,
86
89
  PopoverTitle,
87
- PopoverDescription,
90
+ PopoverTrigger,
88
91
  }
@@ -1,24 +1,31 @@
1
+ // shadcn/ui select (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/select.tsx`.
2
+
1
3
  import * as React from "react"
2
- import { cn } from '../../lib/utils.ts'
4
+ import { Select as SelectPrimitive } from "@base-ui/react/select"
3
5
  import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
4
- import { Select as SelectPrimitive } from "radix-ui"
6
+ import { cn } from "../../lib/utils.ts"
5
7
 
6
- function Select({
7
- ...props
8
- }: React.ComponentProps<typeof SelectPrimitive.Root>) {
9
- return <SelectPrimitive.Root data-slot="select" {...props} />
10
- }
11
8
 
12
- function SelectGroup({
13
- ...props
14
- }: React.ComponentProps<typeof SelectPrimitive.Group>) {
15
- return <SelectPrimitive.Group data-slot="select-group" {...props} />
9
+ const Select = SelectPrimitive.Root
10
+
11
+ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
12
+ return (
13
+ <SelectPrimitive.Group
14
+ data-slot="select-group"
15
+ className={cn("scroll-my-1 p-1", className)}
16
+ {...props}
17
+ />
18
+ )
16
19
  }
17
20
 
18
- function SelectValue({
19
- ...props
20
- }: React.ComponentProps<typeof SelectPrimitive.Value>) {
21
- return <SelectPrimitive.Value data-slot="select-value" {...props} />
21
+ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
22
+ return (
23
+ <SelectPrimitive.Value
24
+ data-slot="select-value"
25
+ className={cn("flex flex-1 text-left", className)}
26
+ {...props}
27
+ />
28
+ )
22
29
  }
23
30
 
24
31
  function SelectTrigger({
@@ -26,7 +33,7 @@ function SelectTrigger({
26
33
  size = "default",
27
34
  children,
28
35
  ...props
29
- }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
36
+ }: SelectPrimitive.Trigger.Props & {
30
37
  size?: "sm" | "default"
31
38
  }) {
32
39
  return (
@@ -34,15 +41,17 @@ function SelectTrigger({
34
41
  data-slot="select-trigger"
35
42
  data-size={size}
36
43
  className={cn(
37
- "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",
44
+ "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
38
45
  className
39
46
  )}
40
47
  {...props}
41
48
  >
42
49
  {children}
43
- <SelectPrimitive.Icon asChild>
44
- <ChevronDownIcon className="size-4 opacity-50" />
45
- </SelectPrimitive.Icon>
50
+ <SelectPrimitive.Icon
51
+ render={
52
+ <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
53
+ }
54
+ />
46
55
  </SelectPrimitive.Trigger>
47
56
  )
48
57
  }
@@ -50,36 +59,41 @@ function SelectTrigger({
50
59
  function SelectContent({
51
60
  className,
52
61
  children,
53
- position = "item-aligned",
62
+ side = "bottom",
63
+ sideOffset = 4,
54
64
  align = "center",
65
+ alignOffset = 0,
66
+ alignItemWithTrigger = true,
55
67
  ...props
56
- }: React.ComponentProps<typeof SelectPrimitive.Content>) {
68
+ }: SelectPrimitive.Popup.Props &
69
+ Pick<
70
+ SelectPrimitive.Positioner.Props,
71
+ "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
72
+ >) {
57
73
  return (
58
74
  <SelectPrimitive.Portal>
59
- <SelectPrimitive.Content
60
- data-slot="select-content"
61
- className={cn(
62
- "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",
63
- position === "popper" &&
64
- "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
65
- className
66
- )}
67
- position={position}
75
+ <SelectPrimitive.Positioner
76
+ side={side}
77
+ sideOffset={sideOffset}
68
78
  align={align}
69
- {...props}
79
+ alignOffset={alignOffset}
80
+ alignItemWithTrigger={alignItemWithTrigger}
81
+ className="isolate z-50"
70
82
  >
71
- <SelectScrollUpButton />
72
- <SelectPrimitive.Viewport
83
+ <SelectPrimitive.Popup
84
+ data-slot="select-content"
85
+ data-align-trigger={alignItemWithTrigger}
73
86
  className={cn(
74
- "p-1",
75
- position === "popper" &&
76
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
87
+ "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-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:fade-out-0 data-closed:zoom-out-95",
88
+ className
77
89
  )}
90
+ {...props}
78
91
  >
79
- {children}
80
- </SelectPrimitive.Viewport>
81
- <SelectScrollDownButton />
82
- </SelectPrimitive.Content>
92
+ <SelectScrollUpButton />
93
+ <SelectPrimitive.List>{children}</SelectPrimitive.List>
94
+ <SelectScrollDownButton />
95
+ </SelectPrimitive.Popup>
96
+ </SelectPrimitive.Positioner>
83
97
  </SelectPrimitive.Portal>
84
98
  )
85
99
  }
@@ -87,11 +101,11 @@ function SelectContent({
87
101
  function SelectLabel({
88
102
  className,
89
103
  ...props
90
- }: React.ComponentProps<typeof SelectPrimitive.Label>) {
104
+ }: SelectPrimitive.GroupLabel.Props) {
91
105
  return (
92
- <SelectPrimitive.Label
106
+ <SelectPrimitive.GroupLabel
93
107
  data-slot="select-label"
94
- className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
108
+ className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
95
109
  {...props}
96
110
  />
97
111
  )
@@ -101,25 +115,26 @@ function SelectItem({
101
115
  className,
102
116
  children,
103
117
  ...props
104
- }: React.ComponentProps<typeof SelectPrimitive.Item>) {
118
+ }: SelectPrimitive.Item.Props) {
105
119
  return (
106
120
  <SelectPrimitive.Item
107
121
  data-slot="select-item"
108
122
  className={cn(
109
- "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",
123
+ "relative flex w-full 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 not-data-[variant=destructive]: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 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
110
124
  className
111
125
  )}
112
126
  {...props}
113
127
  >
114
- <span
115
- data-slot="select-item-indicator"
116
- className="absolute right-2 flex size-3.5 items-center justify-center"
128
+ <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
129
+ {children}
130
+ </SelectPrimitive.ItemText>
131
+ <SelectPrimitive.ItemIndicator
132
+ render={
133
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
134
+ }
117
135
  >
118
- <SelectPrimitive.ItemIndicator>
119
- <CheckIcon className="size-4" />
120
- </SelectPrimitive.ItemIndicator>
121
- </span>
122
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
136
+ <CheckIcon className="pointer-events-none" />
137
+ </SelectPrimitive.ItemIndicator>
123
138
  </SelectPrimitive.Item>
124
139
  )
125
140
  }
@@ -127,7 +142,7 @@ function SelectItem({
127
142
  function SelectSeparator({
128
143
  className,
129
144
  ...props
130
- }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
145
+ }: SelectPrimitive.Separator.Props) {
131
146
  return (
132
147
  <SelectPrimitive.Separator
133
148
  data-slot="select-separator"
@@ -140,36 +155,36 @@ function SelectSeparator({
140
155
  function SelectScrollUpButton({
141
156
  className,
142
157
  ...props
143
- }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
158
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
144
159
  return (
145
- <SelectPrimitive.ScrollUpButton
160
+ <SelectPrimitive.ScrollUpArrow
146
161
  data-slot="select-scroll-up-button"
147
162
  className={cn(
148
- "flex cursor-default items-center justify-center py-1",
163
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
149
164
  className
150
165
  )}
151
166
  {...props}
152
167
  >
153
- <ChevronUpIcon className="size-4" />
154
- </SelectPrimitive.ScrollUpButton>
168
+ <ChevronUpIcon />
169
+ </SelectPrimitive.ScrollUpArrow>
155
170
  )
156
171
  }
157
172
 
158
173
  function SelectScrollDownButton({
159
174
  className,
160
175
  ...props
161
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
176
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
162
177
  return (
163
- <SelectPrimitive.ScrollDownButton
178
+ <SelectPrimitive.ScrollDownArrow
164
179
  data-slot="select-scroll-down-button"
165
180
  className={cn(
166
- "flex cursor-default items-center justify-center py-1",
181
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
167
182
  className
168
183
  )}
169
184
  {...props}
170
185
  >
171
- <ChevronDownIcon className="size-4" />
172
- </SelectPrimitive.ScrollDownButton>
186
+ <ChevronDownIcon />
187
+ </SelectPrimitive.ScrollDownArrow>
173
188
  )
174
189
  }
175
190
 
@@ -0,0 +1,24 @@
1
+ // shadcn/ui separator (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/separator.tsx`.
2
+
3
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
4
+ import { cn } from "../../lib/utils.ts"
5
+
6
+ function Separator({
7
+ className,
8
+ orientation = "horizontal",
9
+ ...props
10
+ }: SeparatorPrimitive.Props) {
11
+ return (
12
+ <SeparatorPrimitive
13
+ data-slot="separator"
14
+ orientation={orientation}
15
+ className={cn(
16
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Separator }
@@ -1,39 +1,34 @@
1
+ // shadcn/ui sheet (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/sheet.tsx`.
2
+
1
3
  import * as React from "react"
2
- import { cn } from '../../lib/utils.ts'
4
+ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
3
5
  import { XIcon } from "lucide-react"
4
- import { Dialog as SheetPrimitive } from "radix-ui"
6
+ import { cn } from "../../lib/utils.ts"
7
+
8
+ import { Button } from "./button"
5
9
 
6
- function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
10
+ function Sheet({ ...props }: SheetPrimitive.Root.Props) {
7
11
  return <SheetPrimitive.Root data-slot="sheet" {...props} />
8
12
  }
9
13
 
10
- function SheetTrigger({
11
- ...props
12
- }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
14
+ function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
13
15
  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
14
16
  }
15
17
 
16
- function SheetClose({
17
- ...props
18
- }: React.ComponentProps<typeof SheetPrimitive.Close>) {
18
+ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
19
19
  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
20
20
  }
21
21
 
22
- function SheetPortal({
23
- ...props
24
- }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
22
+ function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
25
23
  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
26
24
  }
27
25
 
28
- function SheetOverlay({
29
- className,
30
- ...props
31
- }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
26
+ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
32
27
  return (
33
- <SheetPrimitive.Overlay
28
+ <SheetPrimitive.Backdrop
34
29
  data-slot="sheet-overlay"
35
30
  className={cn(
36
- "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
31
+ "fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
37
32
  className
38
33
  )}
39
34
  {...props}
@@ -47,37 +42,39 @@ function SheetContent({
47
42
  side = "right",
48
43
  showCloseButton = true,
49
44
  ...props
50
- }: React.ComponentProps<typeof SheetPrimitive.Content> & {
45
+ }: SheetPrimitive.Popup.Props & {
51
46
  side?: "top" | "right" | "bottom" | "left"
52
47
  showCloseButton?: boolean
53
48
  }) {
54
49
  return (
55
50
  <SheetPortal>
56
51
  <SheetOverlay />
57
- <SheetPrimitive.Content
52
+ <SheetPrimitive.Popup
58
53
  data-slot="sheet-content"
54
+ data-side={side}
59
55
  className={cn(
60
- "fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
61
- side === "right" &&
62
- "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
63
- side === "left" &&
64
- "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
65
- side === "top" &&
66
- "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
67
- side === "bottom" &&
68
- "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
56
+ "fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
69
57
  className
70
58
  )}
71
59
  {...props}
72
60
  >
73
61
  {children}
74
62
  {showCloseButton && (
75
- <SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
76
- <XIcon className="size-4" />
63
+ <SheetPrimitive.Close
64
+ data-slot="sheet-close"
65
+ render={
66
+ <Button
67
+ variant="ghost"
68
+ className="absolute top-3 right-3"
69
+ size="icon-sm"
70
+ />
71
+ }
72
+ >
73
+ <XIcon />
77
74
  <span className="sr-only">Close</span>
78
75
  </SheetPrimitive.Close>
79
76
  )}
80
- </SheetPrimitive.Content>
77
+ </SheetPrimitive.Popup>
81
78
  </SheetPortal>
82
79
  )
83
80
  }
@@ -86,7 +83,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
86
83
  return (
87
84
  <div
88
85
  data-slot="sheet-header"
89
- className={cn("flex flex-col gap-1.5 p-4", className)}
86
+ className={cn("flex flex-col gap-0.5 p-4", className)}
90
87
  {...props}
91
88
  />
92
89
  )
@@ -102,14 +99,14 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
102
99
  )
103
100
  }
104
101
 
105
- function SheetTitle({
106
- className,
107
- ...props
108
- }: React.ComponentProps<typeof SheetPrimitive.Title>) {
102
+ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
109
103
  return (
110
104
  <SheetPrimitive.Title
111
105
  data-slot="sheet-title"
112
- className={cn("font-semibold text-foreground", className)}
106
+ className={cn(
107
+ "text-base font-medium text-foreground",
108
+ className
109
+ )}
113
110
  {...props}
114
111
  />
115
112
  )
@@ -118,7 +115,7 @@ function SheetTitle({
118
115
  function SheetDescription({
119
116
  className,
120
117
  ...props
121
- }: React.ComponentProps<typeof SheetPrimitive.Description>) {
118
+ }: SheetPrimitive.Description.Props) {
122
119
  return (
123
120
  <SheetPrimitive.Description
124
121
  data-slot="sheet-description"
@@ -1,7 +1,7 @@
1
- "use client"
1
+ // shadcn/ui table (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/table.tsx`.
2
2
 
3
3
  import * as React from "react"
4
- import { cn } from '../../lib/utils.ts'
4
+ import { cn } from "../../lib/utils.ts"
5
5
 
6
6
  type TableProps = React.ComponentProps<"table"> & {
7
7
  containerRef?: React.Ref<HTMLDivElement>
@@ -75,7 +75,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
75
75
  <th
76
76
  data-slot="table-head"
77
77
  className={cn(
78
- "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
78
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
79
79
  className
80
80
  )}
81
81
  {...props}
@@ -88,7 +88,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
88
88
  <td
89
89
  data-slot="table-cell"
90
90
  className={cn(
91
- "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
91
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
92
92
  className
93
93
  )}
94
94
  {...props}
@@ -1,4 +1,4 @@
1
- // shadcn/ui Toast (Base UI, base-nova style). Framework-owned: eject with `qb reveal framework/components/ui/toast.tsx`.
1
+ // shadcn/ui toast (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/toast.tsx`.
2
2
 
3
3
  import * as React from "react"
4
4
  import { Toast as ToastPrimitive } from "@base-ui/react/toast"
@@ -35,7 +35,7 @@ function Toast({ className, ...props }: ToastPrimitive.Root.Props) {
35
35
  <ToastPrimitive.Root
36
36
  data-slot="toast"
37
37
  className={cn(
38
- "group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom rounded-2xl border border-gray-200 bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
38
+ "group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom rounded-2xl border bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
39
39
  "[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
40
40
  "h-(--height) [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]",
41
41
  "after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
@@ -0,0 +1,65 @@
1
+ // shadcn/ui tooltip (Base UI, base-nova). Framework-owned: eject with `qb reveal framework/components/ui/tooltip.tsx`.
2
+
3
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
4
+ import { cn } from "../../lib/utils.ts"
5
+
6
+ function TooltipProvider({
7
+ delay = 0,
8
+ ...props
9
+ }: TooltipPrimitive.Provider.Props) {
10
+ return (
11
+ <TooltipPrimitive.Provider
12
+ data-slot="tooltip-provider"
13
+ delay={delay}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
20
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
21
+ }
22
+
23
+ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
24
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
25
+ }
26
+
27
+ function TooltipContent({
28
+ className,
29
+ side = "top",
30
+ sideOffset = 4,
31
+ align = "center",
32
+ alignOffset = 0,
33
+ children,
34
+ ...props
35
+ }: TooltipPrimitive.Popup.Props &
36
+ Pick<
37
+ TooltipPrimitive.Positioner.Props,
38
+ "align" | "alignOffset" | "side" | "sideOffset"
39
+ >) {
40
+ return (
41
+ <TooltipPrimitive.Portal>
42
+ <TooltipPrimitive.Positioner
43
+ align={align}
44
+ alignOffset={alignOffset}
45
+ side={side}
46
+ sideOffset={sideOffset}
47
+ className="isolate z-50"
48
+ >
49
+ <TooltipPrimitive.Popup
50
+ data-slot="tooltip-content"
51
+ className={cn(
52
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 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-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 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",
53
+ className
54
+ )}
55
+ {...props}
56
+ >
57
+ {children}
58
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
59
+ </TooltipPrimitive.Popup>
60
+ </TooltipPrimitive.Positioner>
61
+ </TooltipPrimitive.Portal>
62
+ )
63
+ }
64
+
65
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -33,7 +33,7 @@ export {
33
33
  syncQuiz,
34
34
  } from './lib/quiz-storage.ts';
35
35
  export { getQuestionStatuses, setQuestionStatus } from './lib/question-status-storage.ts';
36
- export { examState, setExamTimer, useExamTimer } from './lib/exam-timer.ts';
36
+ export { examState, pauseExamTimer, resumeExamTimer, setExamTimer, useExamTimer } from './lib/exam-timer.ts';
37
37
  export type { ExamState, ExamTimer } from './lib/exam-timer.ts';
38
38
  export { Toaster, toast } from './components/ui/toast.tsx';
39
39
  export type { StorageTarget } from './lib/sync-client.ts';