banhaten 0.2.0 → 0.3.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/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/docs/design-system/component-implementation-ownership-audit.md +244 -0
- package/docs/design-system/component-quality-audit-2026-08-20.md +223 -0
- package/docs/design-system/component-showcase-consistency-report.md +2 -0
- package/docs/design-system/components/README.md +1 -0
- package/docs/design-system/components/activity-feed.md +2 -0
- package/docs/design-system/components/activity-heatmap.md +73 -0
- package/docs/design-system/components/alert-dialog.md +1 -1
- package/docs/design-system/components/alert.md +2 -2
- package/docs/design-system/components/avatar.md +1 -1
- package/docs/design-system/components/badge.md +1 -1
- package/docs/design-system/components/banner.md +5 -1
- package/docs/design-system/components/breadcrumbs.md +3 -0
- package/docs/design-system/components/button-group.md +1 -0
- package/docs/design-system/components/button.md +1 -1
- package/docs/design-system/components/card.md +11 -3
- package/docs/design-system/components/catalog-components.md +3 -0
- package/docs/design-system/components/date-picker.md +1 -1
- package/docs/design-system/components/file-upload.md +4 -0
- package/docs/design-system/components/modal.md +1 -1
- package/docs/design-system/components/navigation-menu.md +1 -1
- package/docs/design-system/components/onboarding-step-list-item.md +1 -0
- package/docs/design-system/components/page-header.md +3 -1
- package/docs/design-system/components/pagination.md +1 -0
- package/docs/design-system/components/resizable.md +1 -1
- package/docs/design-system/components/scroll-area.md +7 -3
- package/docs/design-system/components/sidebar.md +3 -1
- package/docs/design-system/components/slideout.md +4 -1
- package/docs/design-system/components/steps.md +7 -2
- package/docs/design-system/components/table.md +25 -7
- package/docs/design-system/components/tag.md +1 -1
- package/docs/design-system/components/timeline.md +4 -1
- package/docs/design-system/components/toggle.md +1 -1
- package/docs/design-system/components/toolbar.md +1 -1
- package/docs/design-system/components/tooltip.md +1 -1
- package/docs/design-system/density-and-elevation.md +18 -1
- package/docs/design-system/gap-promotion-report.md +1 -67
- package/docs/design-system/launch-comparison-shadcn.md +10 -10
- package/package.json +3 -2
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +15 -11
- package/registry/components/avatar.tsx +7 -3
- package/registry/components/badge.tsx +17 -17
- package/registry/components/button-group.tsx +3 -1
- package/registry/components/button.tsx +2 -2
- package/registry/components/card.tsx +19 -3
- package/registry/components/carousel.tsx +14 -0
- package/registry/components/command.tsx +37 -3
- package/registry/components/date-picker.tsx +67 -31
- package/registry/components/expanded/ActivityFeed.tsx +3 -7
- package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
- package/registry/components/expanded/Banner.tsx +0 -1
- package/registry/components/expanded/Breadcrumbs.tsx +3 -0
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +3 -4
- package/registry/components/expanded/CatalogDivider.tsx +3 -2
- package/registry/components/expanded/FileUpload.tsx +7 -5
- package/registry/components/expanded/OnboardingStepListItem.tsx +0 -2
- package/registry/components/expanded/PageHeader.tsx +35 -44
- package/registry/components/expanded/Slideout.tsx +18 -13
- package/registry/components/expanded/Steps.tsx +147 -9
- package/registry/components/expanded/Table.tsx +50 -18
- package/registry/components/expanded/Timeline.tsx +11 -24
- package/registry/components/expanded/activityFeed.css +8 -8
- package/registry/components/expanded/activityHeatmap.css +66 -0
- package/registry/components/expanded/banner.css +21 -11
- package/registry/components/expanded/breadcrumbs.css +1 -1
- package/registry/components/expanded/fileUpload.css +1 -1
- package/registry/components/expanded/onboardingStepListItem.css +2 -2
- package/registry/components/expanded/pageHeader.css +14 -11
- package/registry/components/expanded/slideout.css +3 -7
- package/registry/components/expanded/steps.css +75 -12
- package/registry/components/expanded/table.css +58 -67
- package/registry/components/expanded/timeline.css +17 -16
- package/registry/components/field.tsx +61 -8
- package/registry/components/menu.tsx +56 -6
- package/registry/components/modal.tsx +1 -1
- package/registry/components/navigation-menu.tsx +1 -1
- package/registry/components/pagination.css +36 -0
- package/registry/components/pagination.tsx +8 -6
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/resizable.tsx +36 -2
- package/registry/components/scroll-area.tsx +30 -6
- package/registry/components/select.tsx +34 -11
- package/registry/components/sidebar.tsx +11 -5
- package/registry/components/social-button.tsx +12 -5
- package/registry/components/table-elements.tsx +128 -0
- package/registry/components/tabs.tsx +5 -10
- package/registry/components/tag.tsx +1 -1
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +5 -5
- package/registry/components/tooltip.tsx +1 -1
- package/registry/examples/avatar-demo.tsx +7 -4
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
- package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
- package/registry/examples/pagination-demo.tsx +5 -5
- package/registry/examples/sidebar-demo.tsx +4 -2
- package/registry/index.json +392 -33
- package/registry/styles/globals.css +68 -42
- package/registry.json +40 -3
- package/tokens/banhaten.tokens.json +165 -55
|
@@ -10,10 +10,14 @@ type FieldOrientation = "vertical" | "horizontal" | "responsive"
|
|
|
10
10
|
|
|
11
11
|
type FieldContextValue = {
|
|
12
12
|
controlId: string
|
|
13
|
-
|
|
13
|
+
defaultDescriptionId: string
|
|
14
|
+
defaultErrorId: string
|
|
15
|
+
descriptionIds: readonly string[]
|
|
14
16
|
disabled?: boolean
|
|
15
|
-
|
|
17
|
+
errorIds: readonly string[]
|
|
16
18
|
invalid?: boolean
|
|
19
|
+
registerDescription: (id: string) => () => void
|
|
20
|
+
registerError: (id: string) => () => void
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
type FieldProps = React.ComponentProps<"div"> & {
|
|
@@ -48,15 +52,38 @@ function Field({
|
|
|
48
52
|
}: FieldProps) {
|
|
49
53
|
const generatedId = React.useId()
|
|
50
54
|
const resolvedControlId = controlId ?? `${generatedId}-control`
|
|
55
|
+
const [descriptionIds, setDescriptionIds] = React.useState<string[]>([])
|
|
56
|
+
const [errorIds, setErrorIds] = React.useState<string[]>([])
|
|
57
|
+
const registerDescription = React.useCallback(
|
|
58
|
+
(id: string) => registerFieldReference(setDescriptionIds, id),
|
|
59
|
+
[]
|
|
60
|
+
)
|
|
61
|
+
const registerError = React.useCallback(
|
|
62
|
+
(id: string) => registerFieldReference(setErrorIds, id),
|
|
63
|
+
[]
|
|
64
|
+
)
|
|
51
65
|
const context = React.useMemo<FieldContextValue>(
|
|
52
66
|
() => ({
|
|
53
67
|
controlId: resolvedControlId,
|
|
54
|
-
|
|
68
|
+
defaultDescriptionId: `${generatedId}-description`,
|
|
69
|
+
defaultErrorId: `${generatedId}-error`,
|
|
70
|
+
descriptionIds,
|
|
55
71
|
disabled,
|
|
56
|
-
|
|
72
|
+
errorIds,
|
|
57
73
|
invalid,
|
|
74
|
+
registerDescription,
|
|
75
|
+
registerError,
|
|
58
76
|
}),
|
|
59
|
-
[
|
|
77
|
+
[
|
|
78
|
+
descriptionIds,
|
|
79
|
+
disabled,
|
|
80
|
+
errorIds,
|
|
81
|
+
generatedId,
|
|
82
|
+
invalid,
|
|
83
|
+
registerDescription,
|
|
84
|
+
registerError,
|
|
85
|
+
resolvedControlId,
|
|
86
|
+
]
|
|
60
87
|
)
|
|
61
88
|
|
|
62
89
|
return (
|
|
@@ -86,7 +113,10 @@ function FieldControl({ className, ...props }: FieldControlProps) {
|
|
|
86
113
|
const context = useFieldContext("FieldControl")
|
|
87
114
|
const describedBy =
|
|
88
115
|
props["aria-describedby"] ??
|
|
89
|
-
[
|
|
116
|
+
[
|
|
117
|
+
...context.descriptionIds,
|
|
118
|
+
...(context.invalid ? context.errorIds : []),
|
|
119
|
+
]
|
|
90
120
|
.filter(Boolean)
|
|
91
121
|
.join(" ")
|
|
92
122
|
|
|
@@ -117,12 +147,18 @@ function FieldLabel({ htmlFor, ...props }: FieldLabelProps) {
|
|
|
117
147
|
|
|
118
148
|
function FieldDescription({ className, id, ...props }: FieldDescriptionProps) {
|
|
119
149
|
const context = useFieldContext("FieldDescription")
|
|
150
|
+
const resolvedId = id ?? context.defaultDescriptionId
|
|
151
|
+
|
|
152
|
+
React.useEffect(
|
|
153
|
+
() => context.registerDescription(resolvedId),
|
|
154
|
+
[context.registerDescription, resolvedId]
|
|
155
|
+
)
|
|
120
156
|
|
|
121
157
|
return (
|
|
122
158
|
<p
|
|
123
159
|
data-slot="field-description"
|
|
124
160
|
dir="auto"
|
|
125
|
-
id={
|
|
161
|
+
id={resolvedId}
|
|
126
162
|
className={cn(
|
|
127
163
|
"m-0 text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]",
|
|
128
164
|
className
|
|
@@ -134,12 +170,18 @@ function FieldDescription({ className, id, ...props }: FieldDescriptionProps) {
|
|
|
134
170
|
|
|
135
171
|
function FieldError({ className, id, role = "alert", ...props }: FieldErrorProps) {
|
|
136
172
|
const context = useFieldContext("FieldError")
|
|
173
|
+
const resolvedId = id ?? context.defaultErrorId
|
|
174
|
+
|
|
175
|
+
React.useEffect(
|
|
176
|
+
() => context.registerError(resolvedId),
|
|
177
|
+
[context.registerError, resolvedId]
|
|
178
|
+
)
|
|
137
179
|
|
|
138
180
|
return (
|
|
139
181
|
<p
|
|
140
182
|
data-slot="field-error"
|
|
141
183
|
dir="auto"
|
|
142
|
-
id={
|
|
184
|
+
id={resolvedId}
|
|
143
185
|
role={role}
|
|
144
186
|
className={cn(
|
|
145
187
|
"m-0 text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-danger-default)]",
|
|
@@ -150,6 +192,17 @@ function FieldError({ className, id, role = "alert", ...props }: FieldErrorProps
|
|
|
150
192
|
)
|
|
151
193
|
}
|
|
152
194
|
|
|
195
|
+
function registerFieldReference(
|
|
196
|
+
setIds: React.Dispatch<React.SetStateAction<string[]>>,
|
|
197
|
+
id: string
|
|
198
|
+
) {
|
|
199
|
+
setIds((current) => (current.includes(id) ? current : [...current, id]))
|
|
200
|
+
|
|
201
|
+
return () => {
|
|
202
|
+
setIds((current) => current.filter((candidate) => candidate !== id))
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
153
206
|
function FieldGroup({ className, ...props }: FieldGroupProps) {
|
|
154
207
|
return (
|
|
155
208
|
<div
|
|
@@ -283,11 +283,12 @@ type MenuSubTriggerProps = React.ComponentProps<"div"> &
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuItem({
|
|
286
|
+
"aria-checked": ariaChecked,
|
|
286
287
|
className,
|
|
287
288
|
kind = "default",
|
|
288
289
|
disabled = false,
|
|
289
290
|
asChild = false,
|
|
290
|
-
role
|
|
291
|
+
role,
|
|
291
292
|
tabIndex,
|
|
292
293
|
children,
|
|
293
294
|
onSelect,
|
|
@@ -298,15 +299,23 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
298
299
|
const isStaticMenu = React.useContext(MenuStaticContext)
|
|
299
300
|
const selectedKind: MenuItemKind = kind ?? "default"
|
|
300
301
|
const isDisabled = disabled
|
|
302
|
+
const switchState = findMenuItemSwitchState(children)
|
|
303
|
+
const resolvedRole = role ??
|
|
304
|
+
(switchState === undefined ? "menuitem" : "menuitemcheckbox")
|
|
305
|
+
const resolvedChecked =
|
|
306
|
+
resolvedRole === "menuitemcheckbox"
|
|
307
|
+
? (ariaChecked ?? switchState ?? false)
|
|
308
|
+
: undefined
|
|
301
309
|
|
|
302
310
|
const item = (
|
|
303
311
|
<Comp
|
|
312
|
+
aria-checked={resolvedChecked}
|
|
304
313
|
aria-disabled={isDisabled ? true : undefined}
|
|
305
314
|
data-disabled={isDisabled ? "true" : "false"}
|
|
306
315
|
data-kind={selectedKind}
|
|
307
316
|
data-slot="menu-item"
|
|
308
317
|
ref={ref}
|
|
309
|
-
role={
|
|
318
|
+
role={resolvedRole}
|
|
310
319
|
tabIndex={isDisabled ? -1 : (tabIndex ?? 0)}
|
|
311
320
|
className={cn(menuItemVariants({ kind: selectedKind, className }))}
|
|
312
321
|
{...props}
|
|
@@ -319,6 +328,20 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
319
328
|
|
|
320
329
|
if (isStaticMenu) return item
|
|
321
330
|
|
|
331
|
+
if (switchState !== undefined) {
|
|
332
|
+
return (
|
|
333
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
334
|
+
asChild
|
|
335
|
+
checked={switchState}
|
|
336
|
+
disabled={isDisabled}
|
|
337
|
+
onSelect={onSelect}
|
|
338
|
+
textValue={textValue ?? getMenuItemTextValue(children)}
|
|
339
|
+
>
|
|
340
|
+
{item}
|
|
341
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
322
345
|
return (
|
|
323
346
|
<DropdownMenuPrimitive.Item
|
|
324
347
|
asChild
|
|
@@ -333,6 +356,27 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
333
356
|
|
|
334
357
|
MenuItem.displayName = "MenuItem"
|
|
335
358
|
|
|
359
|
+
function findMenuItemSwitchState(children: React.ReactNode): boolean | undefined {
|
|
360
|
+
let state: boolean | undefined
|
|
361
|
+
|
|
362
|
+
React.Children.forEach(children, (child) => {
|
|
363
|
+
if (state !== undefined || !React.isValidElement(child)) return
|
|
364
|
+
|
|
365
|
+
const element = child as React.ReactElement<{
|
|
366
|
+
active?: boolean
|
|
367
|
+
children?: React.ReactNode
|
|
368
|
+
}>
|
|
369
|
+
if (element.type === MenuItemSwitch) {
|
|
370
|
+
state = Boolean(element.props.active)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
state = findMenuItemSwitchState(element.props.children)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
return state
|
|
378
|
+
}
|
|
379
|
+
|
|
336
380
|
const MenuSubTrigger = React.forwardRef<HTMLDivElement, MenuSubTriggerProps>(function MenuSubTrigger({
|
|
337
381
|
className,
|
|
338
382
|
kind = "default",
|
|
@@ -576,7 +620,10 @@ function MenuItemBadge({
|
|
|
576
620
|
)
|
|
577
621
|
}
|
|
578
622
|
|
|
579
|
-
type MenuItemSwitchProps = Omit<
|
|
623
|
+
type MenuItemSwitchProps = Omit<
|
|
624
|
+
React.ComponentProps<"span">,
|
|
625
|
+
"aria-checked" | "aria-label" | "aria-labelledby" | "role"
|
|
626
|
+
> & {
|
|
580
627
|
active?: boolean
|
|
581
628
|
disabled?: boolean
|
|
582
629
|
}
|
|
@@ -589,12 +636,10 @@ function MenuItemSwitch({
|
|
|
589
636
|
}: MenuItemSwitchProps) {
|
|
590
637
|
return (
|
|
591
638
|
<span
|
|
592
|
-
aria-
|
|
593
|
-
aria-disabled={disabled || undefined}
|
|
639
|
+
aria-hidden="true"
|
|
594
640
|
data-active={active ? "true" : "false"}
|
|
595
641
|
data-disabled={disabled ? "true" : "false"}
|
|
596
642
|
data-slot="menu-item-switch"
|
|
597
|
-
role="switch"
|
|
598
643
|
className={cn(
|
|
599
644
|
"flex h-[var(--bh-menu-switch-height)] w-[var(--bh-menu-switch-width)] shrink-0 items-center rounded-[var(--bh-radius-full)] p-[var(--bh-menu-switch-padding)] group-data-[disabled=true]/menu-item:bg-[var(--bh-interactive-switch-disabled)]",
|
|
600
645
|
active
|
|
@@ -677,6 +722,8 @@ function MenuProgress({
|
|
|
677
722
|
...props
|
|
678
723
|
}: MenuProgressProps) {
|
|
679
724
|
const clampedValue = Math.min(100, Math.max(0, value))
|
|
725
|
+
const labelId = React.useId()
|
|
726
|
+
const hasLabel = hasRenderableContent(label)
|
|
680
727
|
|
|
681
728
|
return (
|
|
682
729
|
<div
|
|
@@ -688,6 +735,8 @@ function MenuProgress({
|
|
|
688
735
|
{...props}
|
|
689
736
|
>
|
|
690
737
|
<div
|
|
738
|
+
aria-label={hasLabel ? undefined : "Progress"}
|
|
739
|
+
aria-labelledby={hasLabel ? labelId : undefined}
|
|
691
740
|
aria-valuemax={100}
|
|
692
741
|
aria-valuemin={0}
|
|
693
742
|
aria-valuenow={clampedValue}
|
|
@@ -717,6 +766,7 @@ function MenuProgress({
|
|
|
717
766
|
>
|
|
718
767
|
{hasRenderableContent(label) ? (
|
|
719
768
|
<span
|
|
769
|
+
id={labelId}
|
|
720
770
|
data-slot="menu-progress-label-text"
|
|
721
771
|
dir="auto"
|
|
722
772
|
className="truncate text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)] leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] text-[var(--bh-content-default)]"
|
|
@@ -39,7 +39,7 @@ type ConfirmModalMessages = {
|
|
|
39
39
|
|
|
40
40
|
const modalSurfaceBaseClassName = [
|
|
41
41
|
"flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-modal-width)] max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] flex-col overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
|
|
42
|
-
"[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-
|
|
42
|
+
"[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-lg)]",
|
|
43
43
|
]
|
|
44
44
|
|
|
45
45
|
const modalSizeVariants = {
|
|
@@ -145,7 +145,7 @@ const NavigationMenuIndicator = React.forwardRef<
|
|
|
145
145
|
className={cn("top-full z-[var(--bh-z-popover)] flex h-[var(--bh-space-lg-10)] items-end justify-center overflow-hidden", className)}
|
|
146
146
|
{...props}
|
|
147
147
|
>
|
|
148
|
-
<div className="relative top-[var(--bh-space-xs-4)] size-[var(--bh-space-lg-10)] rotate-45 rounded-tl-[var(--bh-radius-sm-4)] bg-[var(--bh-bg-raised)] shadow-[
|
|
148
|
+
<div className="relative top-[var(--bh-space-xs-4)] size-[var(--bh-space-lg-10)] rotate-45 rounded-tl-[var(--bh-radius-sm-4)] bg-[var(--bh-bg-raised)] shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-border-subtle)]" />
|
|
149
149
|
</NavigationMenuPrimitive.Indicator>
|
|
150
150
|
)
|
|
151
151
|
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.ds-pagination {
|
|
2
|
+
container-type: inline-size;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ds-pagination__controls {
|
|
6
|
+
overflow-x: auto;
|
|
7
|
+
overscroll-behavior-inline: contain;
|
|
8
|
+
scrollbar-width: thin;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Approved exception: container-query breakpoints stay literal because CSS vars are not reliable in query conditions. */
|
|
12
|
+
@container (max-width: 28rem) {
|
|
13
|
+
.ds-pagination [data-slot="pagination-caption"] {
|
|
14
|
+
width: 100%;
|
|
15
|
+
flex: 1 0 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ds-pagination[data-type="numeric"] .ds-pagination__controls {
|
|
19
|
+
width: 100%;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ds-pagination[data-type="numeric"] .ds-pagination__pages > :nth-child(n + 4):not(:last-child) {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ds-pagination[data-type="simple"] .ds-pagination__controls {
|
|
28
|
+
margin-inline-start: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.ds-pagination[data-type="summary"] [data-slot="pagination-summary"] {
|
|
32
|
+
order: -1;
|
|
33
|
+
width: 100%;
|
|
34
|
+
text-align: center;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -5,6 +5,8 @@ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
6
6
|
import { useResolvedDirection } from "@/hooks/use-direction"
|
|
7
7
|
|
|
8
|
+
import "./pagination.css"
|
|
9
|
+
|
|
8
10
|
type PaginationType = "numeric" | "simple" | "summary"
|
|
9
11
|
type PaginationVariant = "soft" | "ghost"
|
|
10
12
|
type PaginationPage =
|
|
@@ -65,7 +67,7 @@ const arabicPaginationMessages: PaginationMessages = {
|
|
|
65
67
|
|
|
66
68
|
const paginationVariants = cva(
|
|
67
69
|
[
|
|
68
|
-
"flex max-w-full items-center",
|
|
70
|
+
"ds-pagination flex w-full max-w-full min-w-0 flex-wrap items-center",
|
|
69
71
|
"text-[length:var(--bh-pagination-font-size)]",
|
|
70
72
|
"leading-[var(--bh-pagination-line-height)]",
|
|
71
73
|
"tracking-[var(--bh-pagination-letter-spacing)]",
|
|
@@ -74,11 +76,11 @@ const paginationVariants = cva(
|
|
|
74
76
|
variants: {
|
|
75
77
|
type: {
|
|
76
78
|
numeric:
|
|
77
|
-
"w-[var(--bh-pagination-numeric-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
79
|
+
"max-w-[var(--bh-pagination-numeric-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
78
80
|
simple:
|
|
79
|
-
"w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
81
|
+
"max-w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
80
82
|
summary:
|
|
81
|
-
"w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
83
|
+
"max-w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
|
|
82
84
|
},
|
|
83
85
|
showCaption: {
|
|
84
86
|
true: "",
|
|
@@ -321,7 +323,7 @@ const Pagination = React.forwardRef<HTMLElement, PaginationProps>(function Pagin
|
|
|
321
323
|
/>
|
|
322
324
|
<div
|
|
323
325
|
data-slot="pagination-pages"
|
|
324
|
-
className="flex shrink-0 items-center gap-[var(--bh-pagination-page-gap)]"
|
|
326
|
+
className="ds-pagination__pages flex min-w-0 shrink-0 items-center gap-[var(--bh-pagination-page-gap)]"
|
|
325
327
|
>
|
|
326
328
|
{pages.map((item, index) => (
|
|
327
329
|
<PaginationPageItem
|
|
@@ -381,7 +383,7 @@ function PaginationControls({
|
|
|
381
383
|
<div
|
|
382
384
|
data-slot="pagination-controls"
|
|
383
385
|
className={cn(
|
|
384
|
-
"flex shrink-0 items-center",
|
|
386
|
+
"ds-pagination__controls flex max-w-full min-w-0 shrink-0 items-center",
|
|
385
387
|
type === "numeric"
|
|
386
388
|
? "gap-[var(--bh-pagination-control-gap)]"
|
|
387
389
|
: "gap-[var(--bh-pagination-action-gap)]"
|
|
@@ -12,7 +12,7 @@ type ProgressSliderSize = "default" | "lg"
|
|
|
12
12
|
const progressSliderVariants = cva(
|
|
13
13
|
[
|
|
14
14
|
"group/progress-slider relative flex touch-none select-none items-center",
|
|
15
|
-
"w-[var(--bh-progress-slider-width-current)]
|
|
15
|
+
"w-full max-w-[var(--bh-progress-slider-width-current)] min-w-0",
|
|
16
16
|
"data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--bh-opacity-60)]",
|
|
17
17
|
"[--bh-progress-slider-width-current:var(--bh-progress-slider-width-md)]",
|
|
18
18
|
"[--bh-progress-slider-height-current:var(--bh-progress-slider-height-md)]",
|
|
@@ -17,7 +17,9 @@ import { cn } from "@/lib/utils"
|
|
|
17
17
|
type ResizablePanelGroupProps = Omit<GroupProps, "orientation"> & {
|
|
18
18
|
direction?: Orientation
|
|
19
19
|
}
|
|
20
|
-
type ResizablePanelProps = PanelProps
|
|
20
|
+
type ResizablePanelProps = PanelProps & {
|
|
21
|
+
contentTabIndex?: number
|
|
22
|
+
}
|
|
21
23
|
type ResizableHandleProps = SeparatorProps & {
|
|
22
24
|
withHandle?: boolean
|
|
23
25
|
}
|
|
@@ -41,7 +43,39 @@ function ResizablePanelGroup({
|
|
|
41
43
|
)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
function ResizablePanel({
|
|
47
|
+
className,
|
|
48
|
+
contentTabIndex = 0,
|
|
49
|
+
elementRef,
|
|
50
|
+
...props
|
|
51
|
+
}: ResizablePanelProps) {
|
|
52
|
+
const setElementRef = React.useCallback(
|
|
53
|
+
(node: HTMLDivElement | null) => {
|
|
54
|
+
const content = node?.firstElementChild
|
|
55
|
+
if (content instanceof HTMLElement) {
|
|
56
|
+
content.tabIndex = contentTabIndex
|
|
57
|
+
content.setAttribute("data-slot", "resizable-panel-content")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof elementRef === "function") elementRef(node)
|
|
61
|
+
else if (elementRef) {
|
|
62
|
+
;(elementRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[contentTabIndex, elementRef]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Panel
|
|
70
|
+
className={cn(
|
|
71
|
+
"outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]",
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
elementRef={setElementRef}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
45
79
|
|
|
46
80
|
function ResizableHandle({
|
|
47
81
|
className,
|
|
@@ -5,9 +5,15 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
7
|
|
|
8
|
+
type ScrollAreaOrientation = "vertical" | "horizontal" | "both"
|
|
8
9
|
type ScrollAreaProps = React.ComponentPropsWithoutRef<
|
|
9
10
|
typeof ScrollAreaPrimitive.Root
|
|
10
|
-
>
|
|
11
|
+
> & {
|
|
12
|
+
orientation?: ScrollAreaOrientation
|
|
13
|
+
viewportProps?: React.ComponentPropsWithoutRef<
|
|
14
|
+
typeof ScrollAreaPrimitive.Viewport
|
|
15
|
+
>
|
|
16
|
+
}
|
|
11
17
|
type ScrollBarProps = React.ComponentPropsWithoutRef<
|
|
12
18
|
typeof ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
13
19
|
>
|
|
@@ -15,9 +21,24 @@ type ScrollBarProps = React.ComponentPropsWithoutRef<
|
|
|
15
21
|
const ScrollArea = React.forwardRef<
|
|
16
22
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
17
23
|
ScrollAreaProps
|
|
18
|
-
>(function ScrollArea(
|
|
24
|
+
>(function ScrollArea(
|
|
25
|
+
{
|
|
26
|
+
children,
|
|
27
|
+
className,
|
|
28
|
+
orientation = "vertical",
|
|
29
|
+
viewportProps,
|
|
30
|
+
...props
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) {
|
|
34
|
+
const hasVerticalScrollbar =
|
|
35
|
+
orientation === "vertical" || orientation === "both"
|
|
36
|
+
const hasHorizontalScrollbar =
|
|
37
|
+
orientation === "horizontal" || orientation === "both"
|
|
38
|
+
|
|
19
39
|
return (
|
|
20
40
|
<ScrollAreaPrimitive.Root
|
|
41
|
+
data-orientation={orientation}
|
|
21
42
|
data-slot="scroll-area"
|
|
22
43
|
ref={ref}
|
|
23
44
|
className={cn("relative overflow-hidden", className)}
|
|
@@ -25,12 +46,15 @@ const ScrollArea = React.forwardRef<
|
|
|
25
46
|
>
|
|
26
47
|
<ScrollAreaPrimitive.Viewport
|
|
27
48
|
data-slot="scroll-area-viewport"
|
|
28
|
-
|
|
49
|
+
{...viewportProps}
|
|
50
|
+
tabIndex={viewportProps?.tabIndex ?? 0}
|
|
51
|
+
className={cn("size-full rounded-[inherit] outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]", viewportProps?.className)}
|
|
29
52
|
>
|
|
30
53
|
{children}
|
|
31
54
|
</ScrollAreaPrimitive.Viewport>
|
|
32
|
-
<ScrollBar />
|
|
33
|
-
<
|
|
55
|
+
{hasVerticalScrollbar ? <ScrollBar orientation="vertical" /> : null}
|
|
56
|
+
{hasHorizontalScrollbar ? <ScrollBar orientation="horizontal" /> : null}
|
|
57
|
+
{orientation === "both" ? <ScrollAreaPrimitive.Corner /> : null}
|
|
34
58
|
</ScrollAreaPrimitive.Root>
|
|
35
59
|
)
|
|
36
60
|
})
|
|
@@ -64,4 +88,4 @@ const ScrollBar = React.forwardRef<
|
|
|
64
88
|
})
|
|
65
89
|
|
|
66
90
|
export { ScrollArea, ScrollBar }
|
|
67
|
-
export type { ScrollAreaProps, ScrollBarProps }
|
|
91
|
+
export type { ScrollAreaOrientation, ScrollAreaProps, ScrollBarProps }
|
|
@@ -224,6 +224,9 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
|
|
|
224
224
|
const shouldRenderMenu = React.Children.count(children) > 0
|
|
225
225
|
const selectId = triggerId || generatedId
|
|
226
226
|
const helperId = `${selectId}-helper`
|
|
227
|
+
const triggerAccessibleLabel = shouldRenderLabel
|
|
228
|
+
? undefined
|
|
229
|
+
: getAccessibleText(selectedValue) ?? getAccessibleText(selectedPlaceholder)
|
|
227
230
|
const rootOpenProps =
|
|
228
231
|
open === undefined ? { onOpenChange } : { open, onOpenChange }
|
|
229
232
|
|
|
@@ -261,8 +264,8 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
|
|
|
261
264
|
<div data-slot="select-control" className="relative w-full">
|
|
262
265
|
<SelectPrimitive.Trigger asChild>
|
|
263
266
|
<SelectTrigger
|
|
267
|
+
aria-label={triggerAccessibleLabel}
|
|
264
268
|
aria-describedby={shouldRenderHelperText ? helperId : undefined}
|
|
265
|
-
aria-expanded={open || undefined}
|
|
266
269
|
aria-haspopup="listbox"
|
|
267
270
|
aria-invalid={isError || undefined}
|
|
268
271
|
expandIcon={expandIcon}
|
|
@@ -436,6 +439,12 @@ function hasRenderableContent(content: React.ReactNode) {
|
|
|
436
439
|
)
|
|
437
440
|
}
|
|
438
441
|
|
|
442
|
+
function getAccessibleText(content: React.ReactNode) {
|
|
443
|
+
return typeof content === "string" || typeof content === "number"
|
|
444
|
+
? String(content)
|
|
445
|
+
: undefined
|
|
446
|
+
}
|
|
447
|
+
|
|
439
448
|
type SelectHelperTextProps = React.ComponentProps<"p"> & {
|
|
440
449
|
hasIcon?: boolean
|
|
441
450
|
type?: "default" | "error"
|
|
@@ -592,7 +601,14 @@ SelectTrigger.displayName = "SelectTrigger"
|
|
|
592
601
|
type SelectMenuProps = React.ComponentProps<"div">
|
|
593
602
|
|
|
594
603
|
const SelectMenu = React.forwardRef<HTMLDivElement, SelectMenuProps>(function SelectMenu(
|
|
595
|
-
{
|
|
604
|
+
{
|
|
605
|
+
"aria-label": ariaLabel = "Options",
|
|
606
|
+
children,
|
|
607
|
+
className,
|
|
608
|
+
onKeyDown,
|
|
609
|
+
role,
|
|
610
|
+
...props
|
|
611
|
+
},
|
|
596
612
|
ref
|
|
597
613
|
) {
|
|
598
614
|
const handleKeyDown = React.useCallback(
|
|
@@ -638,6 +654,7 @@ const SelectMenu = React.forwardRef<HTMLDivElement, SelectMenuProps>(function Se
|
|
|
638
654
|
|
|
639
655
|
return (
|
|
640
656
|
<div
|
|
657
|
+
aria-label={ariaLabel}
|
|
641
658
|
data-slot="select-menu"
|
|
642
659
|
onKeyDown={handleKeyDown}
|
|
643
660
|
ref={ref}
|
|
@@ -645,7 +662,11 @@ const SelectMenu = React.forwardRef<HTMLDivElement, SelectMenuProps>(function Se
|
|
|
645
662
|
className={cn(selectMenu(), className)}
|
|
646
663
|
{...props}
|
|
647
664
|
>
|
|
648
|
-
<div
|
|
665
|
+
<div
|
|
666
|
+
data-slot="select-menu-list"
|
|
667
|
+
role="presentation"
|
|
668
|
+
className={selectMenuList()}
|
|
669
|
+
>
|
|
649
670
|
{children}
|
|
650
671
|
</div>
|
|
651
672
|
</div>
|
|
@@ -689,6 +710,8 @@ type SelectMenuItemProps = SelectMenuItemBaseProps &
|
|
|
689
710
|
|
|
690
711
|
const SelectMenuItem = React.forwardRef<HTMLButtonElement, SelectMenuItemProps>(function SelectMenuItem({
|
|
691
712
|
addonText,
|
|
713
|
+
"aria-checked": ariaChecked,
|
|
714
|
+
"aria-selected": ariaSelected,
|
|
692
715
|
children,
|
|
693
716
|
className,
|
|
694
717
|
disabled,
|
|
@@ -706,8 +729,12 @@ const SelectMenuItem = React.forwardRef<HTMLButtonElement, SelectMenuItemProps>(
|
|
|
706
729
|
const generatedValue = React.useId()
|
|
707
730
|
const isDisabled = disabled
|
|
708
731
|
const selectedWithEndCheck = selected && selectionType === "default"
|
|
709
|
-
const resolvedRole =
|
|
710
|
-
|
|
732
|
+
const resolvedRole = role || "option"
|
|
733
|
+
const usesCheckedState =
|
|
734
|
+
resolvedRole === "checkbox" ||
|
|
735
|
+
resolvedRole === "menuitemcheckbox" ||
|
|
736
|
+
resolvedRole === "radio" ||
|
|
737
|
+
resolvedRole === "menuitemradio"
|
|
711
738
|
const selectedMedia =
|
|
712
739
|
media === false ? null : media ?? getDefaultSelectItemMedia(itemType)
|
|
713
740
|
const content = label ?? children
|
|
@@ -723,13 +750,9 @@ const SelectMenuItem = React.forwardRef<HTMLButtonElement, SelectMenuItemProps>(
|
|
|
723
750
|
|
|
724
751
|
const item = (
|
|
725
752
|
<button
|
|
726
|
-
aria-checked={
|
|
727
|
-
selectionType === "checkbox" ? Boolean(selected) : undefined
|
|
728
|
-
}
|
|
753
|
+
aria-checked={usesCheckedState ? (ariaChecked ?? Boolean(selected)) : undefined}
|
|
729
754
|
aria-disabled={isDisabled || undefined}
|
|
730
|
-
aria-selected={
|
|
731
|
-
selectionType === "default" ? Boolean(selected) : undefined
|
|
732
|
-
}
|
|
755
|
+
aria-selected={resolvedRole === "option" ? (ariaSelected ?? Boolean(selected)) : undefined}
|
|
733
756
|
data-disabled={isDisabled ? "true" : undefined}
|
|
734
757
|
data-item-type={itemType}
|
|
735
758
|
data-selected={selected ? "true" : "false"}
|
|
@@ -34,7 +34,9 @@ type SidebarMenuButtonProps = React.ComponentProps<"button"> & {
|
|
|
34
34
|
type SidebarToggleProps = React.ComponentProps<"button">
|
|
35
35
|
type SidebarTriggerProps = SidebarToggleProps
|
|
36
36
|
type SidebarRailProps = React.ComponentProps<"button">
|
|
37
|
-
type SidebarInsetProps = React.ComponentProps<"
|
|
37
|
+
type SidebarInsetProps = React.ComponentProps<"div"> & {
|
|
38
|
+
asChild?: boolean
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
const SidebarContext = React.createContext<SidebarContextValue | null>(null)
|
|
40
42
|
|
|
@@ -91,7 +93,9 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
|
91
93
|
{ className, side = "start", ...props },
|
|
92
94
|
ref
|
|
93
95
|
) {
|
|
94
|
-
const
|
|
96
|
+
const context = React.useContext(SidebarContext)
|
|
97
|
+
const collapsed = context?.collapsed ?? false
|
|
98
|
+
const dir = context?.dir
|
|
95
99
|
|
|
96
100
|
return (
|
|
97
101
|
<aside
|
|
@@ -299,10 +303,12 @@ const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
|
299
303
|
}
|
|
300
304
|
)
|
|
301
305
|
|
|
302
|
-
const SidebarInset = React.forwardRef<
|
|
303
|
-
function SidebarInset({ className, ...props }, ref) {
|
|
306
|
+
const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
|
|
307
|
+
function SidebarInset({ asChild = false, className, ...props }, ref) {
|
|
308
|
+
const Comp = asChild ? Slot : "div"
|
|
309
|
+
|
|
304
310
|
return (
|
|
305
|
-
<
|
|
311
|
+
<Comp
|
|
306
312
|
data-slot="sidebar-inset"
|
|
307
313
|
ref={ref}
|
|
308
314
|
className={cn("min-w-0 flex-1", className)}
|