@boyernick/standard-ui-react 0.1.1-canary.9 → 0.2.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 (82) hide show
  1. package/package.json +5 -3
  2. package/src/accordion.tsx +5 -2
  3. package/src/alert-dialog.tsx +1 -1
  4. package/src/attachment.tsx +5 -2
  5. package/src/autocomplete.tsx +4 -3
  6. package/src/block-editor.tsx +1747 -0
  7. package/src/brand.tsx +2 -2
  8. package/src/breadcrumb.tsx +14 -7
  9. package/src/button.tsx +22 -11
  10. package/src/calendar.tsx +6 -3
  11. package/src/carousel.tsx +220 -53
  12. package/src/checkbox.tsx +3 -2
  13. package/src/code-block.tsx +66 -5
  14. package/src/collapsible.tsx +4 -2
  15. package/src/combobox.tsx +4 -3
  16. package/src/command.tsx +27 -12
  17. package/src/dialog.tsx +33 -10
  18. package/src/empty.tsx +4 -4
  19. package/src/field.tsx +4 -3
  20. package/src/filter-group.tsx +143 -0
  21. package/src/icons.tsx +28 -1
  22. package/src/illustrations.tsx +219 -141
  23. package/src/index.ts +238 -18
  24. package/src/input.tsx +8 -5
  25. package/src/kbd.tsx +10 -4
  26. package/src/lib/focus.ts +41 -0
  27. package/src/lib/popup.ts +1 -1
  28. package/src/lifeline/company-icon.tsx +71 -0
  29. package/src/lifeline/index.ts +42 -0
  30. package/src/lifeline/lifeline-data.ts +97 -0
  31. package/src/lifeline/lifeline-desktop.tsx +204 -0
  32. package/src/lifeline/lifeline-event.tsx +108 -0
  33. package/src/lifeline/lifeline-fireworks.tsx +302 -0
  34. package/src/lifeline/lifeline-hover-image.tsx +269 -0
  35. package/src/lifeline/lifeline-icons.tsx +39 -0
  36. package/src/lifeline/lifeline-intro-timing.ts +105 -0
  37. package/src/lifeline/lifeline-labels.tsx +24 -0
  38. package/src/lifeline/lifeline-layout.ts +1 -0
  39. package/src/lifeline/lifeline-legend.tsx +31 -0
  40. package/src/lifeline/lifeline-lightbox.tsx +309 -0
  41. package/src/lifeline/lifeline-marker.tsx +183 -0
  42. package/src/lifeline/lifeline-people.tsx +99 -0
  43. package/src/lifeline/lifeline-photos.tsx +366 -0
  44. package/src/lifeline/lifeline-shell.tsx +135 -0
  45. package/src/lifeline/lifeline-utils.ts +83 -0
  46. package/src/lifeline/lifeline-vertical.tsx +482 -0
  47. package/src/lifeline/lifeline.tsx +69 -0
  48. package/src/lifeline/types.ts +112 -0
  49. package/src/lifeline/use-lifeline-intro.ts +130 -0
  50. package/src/lifeline/use-lifeline-scroll.ts +1011 -0
  51. package/src/lifeline/use-lifeline-vertical-scroll.ts +271 -0
  52. package/src/menubar.tsx +9 -2
  53. package/src/minimap.tsx +296 -0
  54. package/src/modal.tsx +608 -0
  55. package/src/navigation-menu.tsx +338 -67
  56. package/src/number-field.tsx +336 -61
  57. package/src/otp-field.tsx +4 -3
  58. package/src/pagination.tsx +262 -42
  59. package/src/password-protection.tsx +345 -0
  60. package/src/popover.tsx +1 -1
  61. package/src/progress.tsx +5 -0
  62. package/src/questionnaire.tsx +284 -0
  63. package/src/radio.tsx +4 -2
  64. package/src/scroll-area.tsx +4 -1
  65. package/src/select.tsx +5 -2
  66. package/src/sidebar.tsx +113 -28
  67. package/src/slider.tsx +66 -6
  68. package/src/sounds.tsx +6 -10
  69. package/src/spinner.tsx +105 -32
  70. package/src/switch.tsx +4 -1
  71. package/src/table.tsx +82 -15
  72. package/src/tabs.tsx +189 -38
  73. package/src/text-animate.tsx +71 -28
  74. package/src/textarea.tsx +6 -1
  75. package/src/timeline.tsx +378 -0
  76. package/src/toast.tsx +214 -35
  77. package/src/toggle.tsx +4 -2
  78. package/src/toolbar.tsx +12 -7
  79. package/src/tooltip.tsx +43 -3
  80. package/src/video-player.tsx +396 -127
  81. package/src/image-modal.tsx +0 -110
  82. package/src/markdown-editor.tsx +0 -302
@@ -0,0 +1,284 @@
1
+ "use client"
2
+
3
+ import { Questionnaire as BaseQuestionnaire } from "@shadcn/react/questionnaire"
4
+ import type { VariantProps } from "class-variance-authority"
5
+ import type { ComponentProps } from "react"
6
+ import { buttonVariants } from "./button"
7
+ import { inputVariants } from "./input"
8
+ import { cn } from "./lib/cn"
9
+ import { focusRing, focusRingBorder } from "./lib/focus"
10
+
11
+ type StyledProps<T> = Omit<T, "className"> & { className?: string }
12
+
13
+ export type QuestionnaireProps = StyledProps<
14
+ ComponentProps<typeof BaseQuestionnaire.Root>
15
+ >
16
+ export type QuestionnaireProgressProps = StyledProps<
17
+ ComponentProps<typeof BaseQuestionnaire.Progress>
18
+ >
19
+ export type QuestionnaireItemProps = StyledProps<
20
+ ComponentProps<typeof BaseQuestionnaire.Item>
21
+ >
22
+ export type QuestionnaireTitleProps = StyledProps<
23
+ ComponentProps<typeof BaseQuestionnaire.Title>
24
+ >
25
+ export type QuestionnaireDescriptionProps = StyledProps<
26
+ ComponentProps<typeof BaseQuestionnaire.Description>
27
+ >
28
+ export type QuestionnaireChoicesProps = StyledProps<
29
+ ComponentProps<typeof BaseQuestionnaire.Choices>
30
+ >
31
+ export type QuestionnaireChoiceInputProps = StyledProps<
32
+ ComponentProps<typeof BaseQuestionnaire.ChoiceInput>
33
+ >
34
+ export type QuestionnaireChoiceLabelProps = StyledProps<
35
+ ComponentProps<typeof BaseQuestionnaire.ChoiceLabel>
36
+ >
37
+ export type QuestionnaireChoiceShortcutProps = StyledProps<
38
+ ComponentProps<typeof BaseQuestionnaire.ChoiceShortcut>
39
+ >
40
+ export type QuestionnaireChoiceProps = StyledProps<
41
+ ComponentProps<typeof BaseQuestionnaire.Choice>
42
+ > & {
43
+ inputProps?: QuestionnaireChoiceInputProps
44
+ labelProps?: QuestionnaireChoiceLabelProps
45
+ shortcutProps?: QuestionnaireChoiceShortcutProps
46
+ }
47
+ export type QuestionnaireInputProps = StyledProps<
48
+ Omit<ComponentProps<typeof BaseQuestionnaire.Input>, "size">
49
+ > &
50
+ VariantProps<typeof inputVariants>
51
+ export type QuestionnaireErrorProps = StyledProps<
52
+ ComponentProps<typeof BaseQuestionnaire.Error>
53
+ >
54
+ export type QuestionnaireActionsProps = ComponentProps<"div">
55
+
56
+ type QuestionnaireNavigationProps<T> = StyledProps<
57
+ Omit<T, "size" | "variant">
58
+ > &
59
+ VariantProps<typeof buttonVariants>
60
+
61
+ export type QuestionnairePreviousProps = QuestionnaireNavigationProps<
62
+ ComponentProps<typeof BaseQuestionnaire.Previous>
63
+ >
64
+ export type QuestionnaireSkipProps = QuestionnaireNavigationProps<
65
+ ComponentProps<typeof BaseQuestionnaire.Skip>
66
+ >
67
+ export type QuestionnaireNextProps = QuestionnaireNavigationProps<
68
+ ComponentProps<typeof BaseQuestionnaire.Next>
69
+ >
70
+ export type QuestionnaireSubmitProps = QuestionnaireNavigationProps<
71
+ ComponentProps<typeof BaseQuestionnaire.Submit>
72
+ >
73
+
74
+ export const Questionnaire = ({ className, ...props }: QuestionnaireProps) => (
75
+ <BaseQuestionnaire.Root
76
+ className={cn("flex w-full flex-col gap-6", className)}
77
+ {...props}
78
+ />
79
+ )
80
+
81
+ export const QuestionnaireProgress = ({
82
+ className,
83
+ ...props
84
+ }: QuestionnaireProgressProps) => (
85
+ <BaseQuestionnaire.Progress
86
+ className={cn("text-sm text-fg-secondary tabular-nums", className)}
87
+ {...props}
88
+ />
89
+ )
90
+
91
+ export const QuestionnaireItem = ({
92
+ className,
93
+ ...props
94
+ }: QuestionnaireItemProps) => (
95
+ <BaseQuestionnaire.Item
96
+ className={cn(
97
+ "m-0 min-w-0 border-0 p-0",
98
+ focusRingBorder,
99
+ focusRing,
100
+ className,
101
+ )}
102
+ {...props}
103
+ />
104
+ )
105
+
106
+ export const QuestionnaireTitle = ({
107
+ className,
108
+ ...props
109
+ }: QuestionnaireTitleProps) => (
110
+ <BaseQuestionnaire.Title
111
+ className={cn("heading-sm text-fg-primary", className)}
112
+ {...props}
113
+ />
114
+ )
115
+
116
+ export const QuestionnaireDescription = ({
117
+ className,
118
+ ...props
119
+ }: QuestionnaireDescriptionProps) => (
120
+ <BaseQuestionnaire.Description
121
+ className={cn("text-sm mt-1 text-fg-secondary", className)}
122
+ {...props}
123
+ />
124
+ )
125
+
126
+ export const QuestionnaireChoices = ({
127
+ className,
128
+ ...props
129
+ }: QuestionnaireChoicesProps) => (
130
+ <BaseQuestionnaire.Choices
131
+ className={cn("mt-5 grid gap-2", className)}
132
+ {...props}
133
+ />
134
+ )
135
+
136
+ export const QuestionnaireChoiceInput = ({
137
+ className,
138
+ ...props
139
+ }: QuestionnaireChoiceInputProps) => (
140
+ <BaseQuestionnaire.ChoiceInput
141
+ className={cn(
142
+ "mt-0.5 size-4 shrink-0 cursor-pointer accent-brand-primary disabled:cursor-not-allowed",
143
+ focusRingBorder,
144
+ focusRing,
145
+ className,
146
+ )}
147
+ {...props}
148
+ />
149
+ )
150
+
151
+ export const QuestionnaireChoiceLabel = ({
152
+ className,
153
+ ...props
154
+ }: QuestionnaireChoiceLabelProps) => (
155
+ <BaseQuestionnaire.ChoiceLabel
156
+ className={cn("text-sm min-w-0 text-fg-primary", className)}
157
+ {...props}
158
+ />
159
+ )
160
+
161
+ export const QuestionnaireChoiceShortcut = ({
162
+ className,
163
+ ...props
164
+ }: QuestionnaireChoiceShortcutProps) => (
165
+ <BaseQuestionnaire.ChoiceShortcut
166
+ className={cn(
167
+ "text-xs flex min-w-5 items-center justify-center rounded border border-border-secondary bg-background-secondary px-1 py-0.5 text-fg-tertiary",
168
+ className,
169
+ )}
170
+ {...props}
171
+ />
172
+ )
173
+
174
+ export const QuestionnaireChoice = ({
175
+ className,
176
+ children,
177
+ inputProps,
178
+ labelProps,
179
+ shortcutProps,
180
+ ...props
181
+ }: QuestionnaireChoiceProps) => (
182
+ <BaseQuestionnaire.Choice
183
+ className={cn(
184
+ "group grid cursor-pointer grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 rounded-lg border border-border-secondary bg-surface px-3.5 py-3 text-fg-primary outline-none transition-[background-color,border-color,box-shadow] duration-[var(--duration-sm)] ease-enter hover:bg-background-secondary has-[:focus-visible]:border-ring has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-offset-1 has-[:focus-visible]:ring-offset-background-primary has-[:focus-visible]:ring-ring/20 data-checked:border-brand-primary data-checked:bg-background-secondary data-disabled:cursor-not-allowed data-disabled:opacity-50",
185
+ className,
186
+ )}
187
+ {...props}
188
+ >
189
+ <QuestionnaireChoiceInput {...inputProps} />
190
+ <QuestionnaireChoiceLabel {...labelProps}>
191
+ {children}
192
+ </QuestionnaireChoiceLabel>
193
+ <QuestionnaireChoiceShortcut {...shortcutProps} />
194
+ </BaseQuestionnaire.Choice>
195
+ )
196
+
197
+ export const QuestionnaireInput = ({
198
+ className,
199
+ variant,
200
+ size,
201
+ invalid,
202
+ ...props
203
+ }: QuestionnaireInputProps) => (
204
+ <BaseQuestionnaire.Input
205
+ className={cn(inputVariants({ variant, size, invalid }), "mt-5", className)}
206
+ {...props}
207
+ />
208
+ )
209
+
210
+ export const QuestionnaireError = ({
211
+ className,
212
+ ...props
213
+ }: QuestionnaireErrorProps) => (
214
+ <BaseQuestionnaire.Error
215
+ className={cn("text-sm mt-2 text-destructive", className)}
216
+ {...props}
217
+ />
218
+ )
219
+
220
+ export const QuestionnaireActions = ({
221
+ className,
222
+ ...props
223
+ }: QuestionnaireActionsProps) => (
224
+ <div
225
+ className={cn("flex min-h-9 items-center justify-end gap-2", className)}
226
+ {...props}
227
+ />
228
+ )
229
+
230
+ export const QuestionnairePrevious = ({
231
+ className,
232
+ variant = "outline",
233
+ size,
234
+ ...props
235
+ }: QuestionnairePreviousProps) => (
236
+ <BaseQuestionnaire.Previous
237
+ className={cn(buttonVariants({ variant, size }), "mr-auto", className)}
238
+ {...props}
239
+ />
240
+ )
241
+
242
+ export const QuestionnaireSkip = ({
243
+ className,
244
+ variant = "ghost",
245
+ size,
246
+ ...props
247
+ }: QuestionnaireSkipProps) => (
248
+ <BaseQuestionnaire.Skip
249
+ className={cn(buttonVariants({ variant, size }), className)}
250
+ {...props}
251
+ />
252
+ )
253
+
254
+ export const QuestionnaireNext = ({
255
+ className,
256
+ variant = "primary",
257
+ size,
258
+ ...props
259
+ }: QuestionnaireNextProps) => (
260
+ <BaseQuestionnaire.Next
261
+ className={cn(buttonVariants({ variant, size }), className)}
262
+ {...props}
263
+ />
264
+ )
265
+
266
+ export const QuestionnaireSubmit = ({
267
+ className,
268
+ variant = "primary",
269
+ size,
270
+ ...props
271
+ }: QuestionnaireSubmitProps) => (
272
+ <BaseQuestionnaire.Submit
273
+ className={cn(buttonVariants({ variant, size }), className)}
274
+ {...props}
275
+ />
276
+ )
277
+
278
+ export type {
279
+ QuestionnaireChoiceDefinition,
280
+ QuestionnaireInputType,
281
+ QuestionnaireItemDefinition,
282
+ QuestionnaireItemStatus,
283
+ QuestionnaireShortcutMode,
284
+ } from "@shadcn/react/questionnaire"
package/src/radio.tsx CHANGED
@@ -4,6 +4,7 @@ import { Radio as BaseRadio } from "@base-ui/react/radio"
4
4
  import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group"
5
5
  import type { ComponentProps } from "react"
6
6
  import { cn } from "./lib/cn"
7
+ import { focusRing } from "./lib/focus"
7
8
  import { motion } from "./lib/motion"
8
9
 
9
10
  export type RadioGroupProps = ComponentProps<typeof BaseRadioGroup>
@@ -20,9 +21,10 @@ export const RadioGroup = ({ className, ...props }: RadioGroupProps) => (
20
21
  export const Radio = ({ className, ...props }: RadioProps) => (
21
22
  <BaseRadio.Root
22
23
  className={cn(
23
- "flex size-4 shrink-0 items-center justify-center rounded-full border border-border-secondary bg-surface outline-none",
24
+ "flex size-4 shrink-0 items-center justify-center rounded-full border border-border-secondary bg-surface",
24
25
  motion.colors,
25
- "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-checked:border-brand-primary data-checked:bg-brand-primary data-disabled:cursor-not-allowed data-disabled:opacity-50",
26
+ focusRing,
27
+ "data-checked:border-brand-primary data-checked:bg-brand-primary data-disabled:cursor-not-allowed data-disabled:opacity-50",
26
28
  className,
27
29
  )}
28
30
  {...props}
@@ -3,6 +3,7 @@
3
3
  import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area"
4
4
  import type { ComponentProps } from "react"
5
5
  import { cn } from "./lib/cn"
6
+ import { focusRing, focusRingBorder } from "./lib/focus"
6
7
 
7
8
  export type ScrollAreaProps = ComponentProps<typeof BaseScrollArea.Root>
8
9
  export type ScrollAreaViewportProps = ComponentProps<
@@ -29,7 +30,9 @@ export const ScrollAreaViewport = ({
29
30
  }: ScrollAreaViewportProps) => (
30
31
  <BaseScrollArea.Viewport
31
32
  className={cn(
32
- "size-full rounded-[inherit] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
33
+ "size-full rounded-[inherit]",
34
+ focusRingBorder,
35
+ focusRing,
33
36
  className,
34
37
  )}
35
38
  {...props}
package/src/select.tsx CHANGED
@@ -4,6 +4,7 @@ import { Select as BaseSelect } from "@base-ui/react/select"
4
4
  import type { ComponentProps } from "react"
5
5
  import { IconChevronDownSmall } from "./icons"
6
6
  import { cn } from "./lib/cn"
7
+ import { focusRing, focusRingInvalid } from "./lib/focus"
7
8
  import { motion } from "./lib/motion"
8
9
  import { popupInset, popupItem, popupLabel, popupSurface } from "./lib/popup"
9
10
 
@@ -32,9 +33,11 @@ export const SelectTrigger = ({
32
33
  }: SelectTriggerProps) => (
33
34
  <BaseSelect.Trigger
34
35
  className={cn(
35
- "flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top outline-none",
36
+ "flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top",
36
37
  motion.all,
37
- "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/20 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-placeholder:text-fg-quaternary data-popup-open:border-border-secondary",
38
+ focusRing,
39
+ focusRingInvalid,
40
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-placeholder:text-fg-quaternary data-popup-open:border-border-secondary",
38
41
  className,
39
42
  )}
40
43
  {...props}
package/src/sidebar.tsx CHANGED
@@ -1,56 +1,119 @@
1
- import type { ComponentProps } from "react";
2
- import { cn } from "./lib/cn";
3
- import { motion } from "./lib/motion";
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import type { ComponentProps } from "react"
3
+ import { cn } from "./lib/cn"
4
+ import { focusRing } from "./lib/focus"
5
+ import { motion } from "./lib/motion"
4
6
 
5
- export type SidebarProps = ComponentProps<"aside">;
6
- export type SidebarHeaderProps = ComponentProps<"div">;
7
- export type SidebarContentProps = ComponentProps<"div">;
8
- export type SidebarFooterProps = ComponentProps<"div">;
9
- export type SidebarNavProps = ComponentProps<"nav">;
10
- export type SidebarNavItemProps = ComponentProps<"button"> & {
11
- active?: boolean;
12
- };
13
- export type SidebarGroupProps = ComponentProps<"div">;
14
- export type SidebarGroupLabelProps = ComponentProps<"h3">;
7
+ const sidebarVariants = cva(
8
+ "flex h-full shrink-0 flex-col overflow-hidden text-fg-primary",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "border-r border-border-primary bg-surface",
13
+ inset: "rounded-lg bg-background-secondary",
14
+ floating:
15
+ "rounded-xl border border-border-primary bg-surface-raised shadow-sm",
16
+ },
17
+ size: {
18
+ sm: "w-52",
19
+ md: "w-60",
20
+ lg: "w-72",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ size: "md",
26
+ },
27
+ },
28
+ )
15
29
 
16
- export const Sidebar = ({ className, ...props }: SidebarProps) => (
30
+ const sidebarNavItemVariants = cva(
31
+ cn(
32
+ "group/nav-item relative flex w-full cursor-pointer items-center gap-2 border border-transparent text-left text-fg-secondary disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
33
+ focusRing,
34
+ ),
35
+ {
36
+ variants: {
37
+ variant: {
38
+ default:
39
+ "rounded-md hover:bg-background-tertiary hover:text-fg-primary data-[active]:bg-background-tertiary data-[active]:font-medium data-[active]:text-fg-primary",
40
+ pill: "rounded-full hover:bg-background-tertiary hover:text-fg-primary data-[active]:border-brand-primary-border data-[active]:bg-brand-primary data-[active]:font-medium data-[active]:text-brand-foreground",
41
+ indicator:
42
+ "rounded-md before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-transparent before:content-[''] hover:bg-background-tertiary hover:text-fg-primary data-[active]:bg-background-tertiary data-[active]:font-medium data-[active]:text-fg-primary data-[active]:before:bg-brand-primary",
43
+ },
44
+ size: {
45
+ sm: "h-8 px-2.5 text-xs",
46
+ md: "h-9 px-3 text-sm",
47
+ lg: "h-10 px-3.5 text-sm",
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ variant: "default",
52
+ size: "md",
53
+ },
54
+ },
55
+ )
56
+
57
+ export type SidebarProps = Omit<ComponentProps<"aside">, "size"> &
58
+ VariantProps<typeof sidebarVariants>
59
+ export type SidebarHeaderProps = ComponentProps<"div">
60
+ export type SidebarContentProps = ComponentProps<"div">
61
+ export type SidebarFooterProps = ComponentProps<"div">
62
+ export type SidebarNavProps = ComponentProps<"nav">
63
+ export type SidebarNavItemProps = Omit<ComponentProps<"button">, "size"> &
64
+ VariantProps<typeof sidebarNavItemVariants> & {
65
+ active?: boolean
66
+ }
67
+ export type SidebarNavItemLabelProps = ComponentProps<"span">
68
+ export type SidebarNavBadgeProps = ComponentProps<"span">
69
+ export type SidebarGroupProps = ComponentProps<"div">
70
+ export type SidebarGroupLabelProps = ComponentProps<"h3">
71
+
72
+ export const Sidebar = ({
73
+ className,
74
+ variant,
75
+ size,
76
+ ...props
77
+ }: SidebarProps) => (
17
78
  <aside
18
- className={cn(
19
- "flex h-full w-60 shrink-0 flex-col border-r border-border-primary bg-surface",
20
- className,
21
- )}
79
+ className={cn(sidebarVariants({ variant, size }), className)}
22
80
  {...props}
23
81
  />
24
- );
82
+ )
25
83
 
26
84
  export const SidebarHeader = ({ className, ...props }: SidebarHeaderProps) => (
27
85
  <div
28
86
  className={cn("border-b border-border-primary p-4", className)}
29
87
  {...props}
30
88
  />
31
- );
89
+ )
32
90
 
33
91
  export const SidebarContent = ({
34
92
  className,
35
93
  ...props
36
94
  }: SidebarContentProps) => (
37
- <div className={cn("flex-1 overflow-y-auto p-3", className)} {...props} />
38
- );
95
+ <div
96
+ className={cn("min-h-0 flex-1 overflow-y-auto p-3", className)}
97
+ {...props}
98
+ />
99
+ )
39
100
 
40
101
  export const SidebarFooter = ({ className, ...props }: SidebarFooterProps) => (
41
102
  <div
42
103
  className={cn("border-t border-border-primary p-3", className)}
43
104
  {...props}
44
105
  />
45
- );
106
+ )
46
107
 
47
108
  export const SidebarNav = ({ className, ...props }: SidebarNavProps) => (
48
109
  <nav className={cn("flex flex-col gap-1", className)} {...props} />
49
- );
110
+ )
50
111
 
51
112
  export const SidebarNavItem = ({
52
113
  className,
53
114
  active = false,
115
+ variant,
116
+ size,
54
117
  type = "button",
55
118
  ...props
56
119
  }: SidebarNavItemProps) => (
@@ -59,17 +122,37 @@ export const SidebarNavItem = ({
59
122
  data-active={active || undefined}
60
123
  aria-current={active ? "page" : undefined}
61
124
  className={cn(
62
- "flex h-9 w-full cursor-pointer items-center gap-2 rounded-md border border-transparent px-3 text-left text-sm text-fg-secondary outline-none hover:bg-background-tertiary hover:text-fg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-[active]:bg-background-tertiary data-[active]:font-medium data-[active]:text-fg-primary [&_svg]:size-4 [&_svg]:shrink-0",
125
+ sidebarNavItemVariants({ variant, size }),
63
126
  motion.colors,
64
127
  className,
65
128
  )}
66
129
  {...props}
67
130
  />
68
- );
131
+ )
132
+
133
+ export const SidebarNavItemLabel = ({
134
+ className,
135
+ ...props
136
+ }: SidebarNavItemLabelProps) => (
137
+ <span className={cn("min-w-0 flex-1 truncate", className)} {...props} />
138
+ )
139
+
140
+ export const SidebarNavBadge = ({
141
+ className,
142
+ ...props
143
+ }: SidebarNavBadgeProps) => (
144
+ <span
145
+ className={cn(
146
+ "text-2xs-strong ml-auto inline-flex min-w-5 shrink-0 items-center justify-center rounded-full border border-border-primary bg-surface px-1.5 py-0.5 text-fg-secondary",
147
+ className,
148
+ )}
149
+ {...props}
150
+ />
151
+ )
69
152
 
70
153
  export const SidebarGroup = ({ className, ...props }: SidebarGroupProps) => (
71
154
  <div className={cn("flex flex-col gap-1 py-2", className)} {...props} />
72
- );
155
+ )
73
156
 
74
157
  export const SidebarGroupLabel = ({
75
158
  className,
@@ -79,4 +162,6 @@ export const SidebarGroupLabel = ({
79
162
  className={cn("px-3 py-1.5 text-xs-strong text-fg-tertiary", className)}
80
163
  {...props}
81
164
  />
82
- );
165
+ )
166
+
167
+ export { sidebarNavItemVariants, sidebarVariants }
package/src/slider.tsx CHANGED
@@ -1,21 +1,52 @@
1
1
  "use client"
2
2
 
3
3
  import { Slider as BaseSlider } from "@base-ui/react/slider"
4
+ import { cva, type VariantProps } from "class-variance-authority"
4
5
  import type { ComponentProps } from "react"
5
6
  import { cn } from "./lib/cn"
7
+ import { focusRing } from "./lib/focus"
6
8
 
7
- export type SliderProps = ComponentProps<typeof BaseSlider.Root>
9
+ const sliderVariants = cva(
10
+ "group/slider relative flex w-full data-disabled:opacity-50 data-[orientation=vertical]:h-48 data-[orientation=vertical]:w-fit",
11
+ {
12
+ variants: {
13
+ size: {
14
+ sm: "[--slider-thumb-size:0.875rem] [--slider-track-size:0.25rem]",
15
+ md: "[--slider-thumb-size:1rem] [--slider-track-size:0.375rem]",
16
+ lg: "[--slider-thumb-size:1.25rem] [--slider-track-size:0.5rem]",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ size: "md",
21
+ },
22
+ },
23
+ )
24
+
25
+ export type SliderProps = Omit<
26
+ ComponentProps<typeof BaseSlider.Root>,
27
+ "size"
28
+ > &
29
+ VariantProps<typeof sliderVariants>
8
30
  export type SliderControlProps = ComponentProps<typeof BaseSlider.Control>
9
31
  export type SliderTrackProps = ComponentProps<typeof BaseSlider.Track>
10
32
  export type SliderIndicatorProps = ComponentProps<typeof BaseSlider.Indicator>
11
33
  export type SliderThumbProps = ComponentProps<typeof BaseSlider.Thumb>
34
+ export type SliderTicksProps = ComponentProps<"div"> & {
35
+ /** Number of evenly spaced marks, including both ends. */
36
+ count?: number
37
+ }
12
38
 
13
- export const Slider = (props: SliderProps) => <BaseSlider.Root {...props} />
39
+ export const Slider = ({ className, size, ...props }: SliderProps) => (
40
+ <BaseSlider.Root
41
+ className={cn(sliderVariants({ size }), className)}
42
+ {...props}
43
+ />
44
+ )
14
45
 
15
46
  export const SliderControl = ({ className, ...props }: SliderControlProps) => (
16
47
  <BaseSlider.Control
17
48
  className={cn(
18
- "flex w-full touch-none items-center py-3 select-none",
49
+ "flex w-full touch-none items-center py-3 select-none data-disabled:cursor-not-allowed data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:px-3 data-[orientation=vertical]:py-0",
19
50
  className,
20
51
  )}
21
52
  {...props}
@@ -25,7 +56,7 @@ export const SliderControl = ({ className, ...props }: SliderControlProps) => (
25
56
  export const SliderTrack = ({ className, ...props }: SliderTrackProps) => (
26
57
  <BaseSlider.Track
27
58
  className={cn(
28
- "relative h-1.5 w-full rounded-full bg-background-quaternary",
59
+ "relative h-[var(--slider-track-size)] w-full rounded-full bg-background-quaternary transition-colors duration-[var(--duration-sm)] group-hover/slider:bg-background-active data-[orientation=vertical]:h-full data-[orientation=vertical]:w-[var(--slider-track-size)]",
29
60
  className,
30
61
  )}
31
62
  {...props}
@@ -38,7 +69,7 @@ export const SliderIndicator = ({
38
69
  }: SliderIndicatorProps) => (
39
70
  <BaseSlider.Indicator
40
71
  className={cn(
41
- "absolute h-full rounded-full bg-brand-primary transition-[width] duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none",
72
+ "absolute h-full rounded-full bg-brand-primary data-[orientation=vertical]:h-auto data-[orientation=vertical]:w-full",
42
73
  className,
43
74
  )}
44
75
  {...props}
@@ -48,9 +79,38 @@ export const SliderIndicator = ({
48
79
  export const SliderThumb = ({ className, ...props }: SliderThumbProps) => (
49
80
  <BaseSlider.Thumb
50
81
  className={cn(
51
- "size-4 cursor-pointer rounded-full border border-brand-primary-border bg-surface shadow-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-disabled:cursor-not-allowed data-disabled:opacity-50",
82
+ "flex size-[var(--slider-thumb-size)] cursor-grab items-center justify-center rounded-full border border-brand-primary-border bg-surface shadow-sm transition-[scale,box-shadow,background-color,border-color] duration-[var(--duration-sm)] ease-enter after:size-1 after:rounded-full after:bg-brand-primary after:content-[''] hover:scale-110 hover:bg-background-secondary active:scale-105 data-dragging:cursor-grabbing data-dragging:scale-110 data-disabled:cursor-not-allowed data-disabled:hover:scale-100",
83
+ focusRing,
52
84
  className,
53
85
  )}
54
86
  {...props}
55
87
  />
56
88
  )
89
+
90
+ export const SliderTicks = ({
91
+ count = 5,
92
+ className,
93
+ ...props
94
+ }: SliderTicksProps) => {
95
+ const tickCount = Number.isFinite(count) ? Math.max(2, Math.floor(count)) : 2
96
+
97
+ return (
98
+ <div
99
+ aria-hidden
100
+ className={cn(
101
+ "pointer-events-none absolute inset-0 flex items-center justify-between group-data-[orientation=vertical]/slider:flex-col",
102
+ className,
103
+ )}
104
+ {...props}
105
+ >
106
+ {Array.from({ length: tickCount }, (_, index) => (
107
+ <span
108
+ key={index}
109
+ className="h-2 w-px rounded-full bg-surface/70 group-data-[orientation=vertical]/slider:h-px group-data-[orientation=vertical]/slider:w-2"
110
+ />
111
+ ))}
112
+ </div>
113
+ )
114
+ }
115
+
116
+ export { sliderVariants }