banhaten 0.2.0 → 0.4.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 +55 -0
- package/README.md +5 -5
- package/docs/design-system/component-implementation-ownership-audit.md +245 -0
- package/docs/design-system/component-quality-audit-2026-08-20.md +225 -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 +3 -3
- package/docs/design-system/components/attribute.md +1 -1
- package/docs/design-system/components/avatar.md +2 -2
- package/docs/design-system/components/badge.md +1 -1
- package/docs/design-system/components/banner.md +6 -2
- package/docs/design-system/components/breadcrumbs.md +5 -2
- package/docs/design-system/components/button-group.md +3 -2
- package/docs/design-system/components/button.md +2 -2
- 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 +3 -1
- package/docs/design-system/components/file-upload.md +5 -1
- package/docs/design-system/components/input.md +8 -6
- package/docs/design-system/components/item.md +3 -1
- package/docs/design-system/components/kbd.md +11 -8
- package/docs/design-system/components/menu.md +2 -2
- 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 +3 -2
- package/docs/design-system/components/page-header.md +3 -1
- package/docs/design-system/components/pagination.md +3 -2
- package/docs/design-system/components/radio-card.md +1 -1
- package/docs/design-system/components/resizable.md +1 -1
- package/docs/design-system/components/scroll-area.md +7 -3
- package/docs/design-system/components/select.md +1 -1
- package/docs/design-system/components/sidebar.md +60 -18
- 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 +3 -3
- 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 +23 -2
- package/docs/design-system/gap-promotion-report.md +1 -67
- package/docs/design-system/icons.md +6 -1
- package/docs/design-system/installation.md +1 -1
- package/docs/design-system/launch-comparison-shadcn.md +10 -10
- package/docs/design-system/token-governance.md +7 -8
- package/package.json +3 -2
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +17 -13
- package/registry/components/attribute.tsx +8 -2
- package/registry/components/avatar.tsx +28 -20
- package/registry/components/badge.tsx +17 -17
- package/registry/components/button-group.tsx +4 -2
- package/registry/components/button.tsx +3 -3
- 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 +83 -35
- 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 +6 -7
- 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 +30 -15
- package/registry/components/expanded/breadcrumbs.css +5 -5
- package/registry/components/expanded/fileUpload.css +4 -4
- package/registry/components/expanded/onboardingStepListItem.css +16 -7
- 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 +63 -69
- package/registry/components/expanded/timeline.css +17 -16
- package/registry/components/field.tsx +61 -8
- package/registry/components/input.tsx +12 -5
- package/registry/components/item.tsx +1 -1
- package/registry/components/kbd.tsx +109 -15
- package/registry/components/menu.tsx +57 -7
- 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 +20 -13
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/radio-card.tsx +1 -2
- package/registry/components/resizable.tsx +36 -2
- package/registry/components/scroll-area.tsx +30 -6
- package/registry/components/select.tsx +35 -12
- package/registry/components/sidebar.tsx +940 -62
- 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 +2 -2
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +6 -6
- package/registry/components/tooltip.tsx +1 -1
- package/registry/examples/attribute-demo.tsx +1 -1
- package/registry/examples/avatar-demo.tsx +7 -4
- package/registry/examples/command-palette-demo.tsx +0 -1
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
- package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
- package/registry/examples/kbd-demo.tsx +2 -1
- package/registry/examples/pagination-demo.tsx +5 -5
- package/registry/examples/sidebar-demo.tsx +59 -13
- package/registry/guidance/USAGE.md +1 -1
- package/registry/index.json +621 -60
- package/registry/styles/globals.css +211 -151
- package/registry.json +44 -6
- package/tokens/banhaten.tokens.json +325 -95
|
@@ -58,7 +58,7 @@ const cardVariants = cva(
|
|
|
58
58
|
orientation: "horizontal",
|
|
59
59
|
breakpoint: "desktop",
|
|
60
60
|
class:
|
|
61
|
-
"min-h-[var(--bh-card-horizontal-min-height)] w-[min(100%,var(--bh-card-horizontal-width))]",
|
|
61
|
+
"min-h-[var(--bh-card-horizontal-min-height)] w-[min(100%,var(--bh-card-horizontal-width))] flex-wrap",
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
orientation: "horizontal",
|
|
@@ -109,6 +109,20 @@ type CardActionsProps = React.ComponentProps<"div"> & {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
type CardFooterProps = React.ComponentProps<"div">
|
|
112
|
+
type CardSurfaceProps = React.ComponentPropsWithoutRef<"article">
|
|
113
|
+
|
|
114
|
+
const CardSurface = React.forwardRef<HTMLElement, CardSurfaceProps>(
|
|
115
|
+
function CardSurface({ className, ...props }, ref) {
|
|
116
|
+
return (
|
|
117
|
+
<article
|
|
118
|
+
data-slot="card-surface"
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(cardVariants(), className)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
)
|
|
112
126
|
|
|
113
127
|
const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
|
|
114
128
|
actions,
|
|
@@ -362,7 +376,7 @@ function CardBody({
|
|
|
362
376
|
className={cn(
|
|
363
377
|
"flex min-w-0 flex-col",
|
|
364
378
|
horizontal
|
|
365
|
-
? "flex-
|
|
379
|
+
? "min-w-[min(100%,var(--bh-card-vertical-width))] flex-[1_1_var(--bh-card-vertical-width)] gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-horizontal-padding)]"
|
|
366
380
|
: "w-full gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-content-padding)]"
|
|
367
381
|
)}
|
|
368
382
|
>
|
|
@@ -566,7 +580,7 @@ function getCardMediaClassName(
|
|
|
566
580
|
breakpoint: CardBreakpoint
|
|
567
581
|
) {
|
|
568
582
|
if (orientation === "horizontal" && breakpoint === "desktop") {
|
|
569
|
-
return "h-
|
|
583
|
+
return "min-h-[var(--bh-card-media-height)] w-[min(100%,var(--bh-card-horizontal-media-width))] shrink flex-[1_1_var(--bh-card-horizontal-media-width)]"
|
|
570
584
|
}
|
|
571
585
|
|
|
572
586
|
return "h-[var(--bh-card-media-height)] w-full"
|
|
@@ -611,6 +625,7 @@ export {
|
|
|
611
625
|
CardActions,
|
|
612
626
|
CardFooter,
|
|
613
627
|
CardMedia,
|
|
628
|
+
CardSurface,
|
|
614
629
|
cardVariants,
|
|
615
630
|
type CardActionsProps,
|
|
616
631
|
type CardBreakpoint,
|
|
@@ -618,5 +633,6 @@ export {
|
|
|
618
633
|
type CardMediaProps,
|
|
619
634
|
type CardOrientation,
|
|
620
635
|
type CardProps,
|
|
636
|
+
type CardSurfaceProps,
|
|
621
637
|
type CardType,
|
|
622
638
|
}
|
|
@@ -17,16 +17,22 @@ type CarouselPlugin = UseCarouselParameters[1]
|
|
|
17
17
|
type CarouselOrientation = "horizontal" | "vertical"
|
|
18
18
|
|
|
19
19
|
type CarouselMessages = {
|
|
20
|
+
carouselLabel: string
|
|
21
|
+
carouselRoleDescription: string
|
|
20
22
|
nextSlide: string
|
|
21
23
|
previousSlide: string
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
const defaultCarouselMessages: CarouselMessages = {
|
|
27
|
+
carouselLabel: "Carousel",
|
|
28
|
+
carouselRoleDescription: "carousel",
|
|
25
29
|
nextSlide: "Next slide",
|
|
26
30
|
previousSlide: "Previous slide",
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const arabicCarouselMessages: CarouselMessages = {
|
|
34
|
+
carouselLabel: "\u0639\u0627\u0631\u0636 \u0627\u0644\u0634\u0631\u0627\u0626\u062d",
|
|
35
|
+
carouselRoleDescription: "\u0639\u0627\u0631\u0636 \u0634\u0631\u0627\u0626\u062d",
|
|
30
36
|
nextSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u062a\u0627\u0644\u064a\u0629",
|
|
31
37
|
previousSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u0633\u0627\u0628\u0642\u0629",
|
|
32
38
|
}
|
|
@@ -58,6 +64,9 @@ const CarouselContext = React.createContext<CarouselContextValue | null>(null)
|
|
|
58
64
|
const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
59
65
|
function Carousel(
|
|
60
66
|
{
|
|
67
|
+
"aria-label": ariaLabel,
|
|
68
|
+
"aria-labelledby": ariaLabelledBy,
|
|
69
|
+
"aria-roledescription": ariaRoleDescription,
|
|
61
70
|
children,
|
|
62
71
|
className,
|
|
63
72
|
dir,
|
|
@@ -138,6 +147,11 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
|
138
147
|
dir={direction}
|
|
139
148
|
ref={setRootRef}
|
|
140
149
|
role="region"
|
|
150
|
+
aria-label={ariaLabelledBy ? undefined : (ariaLabel ?? messages.carouselLabel)}
|
|
151
|
+
aria-labelledby={ariaLabelledBy}
|
|
152
|
+
aria-roledescription={
|
|
153
|
+
ariaRoleDescription ?? messages.carouselRoleDescription
|
|
154
|
+
}
|
|
141
155
|
className={cn("relative", className)}
|
|
142
156
|
{...props}
|
|
143
157
|
>
|
|
@@ -63,10 +63,27 @@ const CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(
|
|
|
63
63
|
|
|
64
64
|
const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
|
|
65
65
|
function CommandList({ className, ...props }, ref) {
|
|
66
|
+
const listRef = React.useRef<HTMLDivElement | null>(null)
|
|
67
|
+
React.useLayoutEffect(() => {
|
|
68
|
+
listRef.current
|
|
69
|
+
?.querySelector<HTMLElement>("[cmdk-list-sizer]")
|
|
70
|
+
?.setAttribute("role", "presentation")
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const setListRef = React.useCallback(
|
|
74
|
+
(node: HTMLDivElement | null) => {
|
|
75
|
+
listRef.current = node
|
|
76
|
+
|
|
77
|
+
if (typeof ref === "function") ref(node)
|
|
78
|
+
else if (ref) ref.current = node
|
|
79
|
+
},
|
|
80
|
+
[ref]
|
|
81
|
+
)
|
|
82
|
+
|
|
66
83
|
return (
|
|
67
84
|
<CommandPrimitive.List
|
|
68
85
|
data-slot="command-list"
|
|
69
|
-
ref={
|
|
86
|
+
ref={setListRef}
|
|
70
87
|
className={cn(
|
|
71
88
|
"max-h-[calc(var(--bh-space-19xl-384)+var(--bh-space-8xl-48))] overflow-y-auto overflow-x-hidden p-[var(--bh-space-md-8)]",
|
|
72
89
|
className
|
|
@@ -79,10 +96,25 @@ const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
|
|
|
79
96
|
|
|
80
97
|
const CommandEmpty = React.forwardRef<HTMLDivElement, CommandEmptyProps>(
|
|
81
98
|
function CommandEmpty({ className, ...props }, ref) {
|
|
99
|
+
const emptyRef = React.useRef<HTMLDivElement | null>(null)
|
|
100
|
+
React.useLayoutEffect(() => {
|
|
101
|
+
emptyRef.current?.setAttribute("role", "option")
|
|
102
|
+
emptyRef.current?.setAttribute("aria-disabled", "true")
|
|
103
|
+
emptyRef.current?.setAttribute("aria-selected", "false")
|
|
104
|
+
})
|
|
105
|
+
const setEmptyRef = React.useCallback(
|
|
106
|
+
(node: HTMLDivElement | null) => {
|
|
107
|
+
emptyRef.current = node
|
|
108
|
+
if (typeof ref === "function") ref(node)
|
|
109
|
+
else if (ref) ref.current = node
|
|
110
|
+
},
|
|
111
|
+
[ref]
|
|
112
|
+
)
|
|
113
|
+
|
|
82
114
|
return (
|
|
83
115
|
<CommandPrimitive.Empty
|
|
84
116
|
data-slot="command-empty"
|
|
85
|
-
ref={
|
|
117
|
+
ref={setEmptyRef}
|
|
86
118
|
className={cn(
|
|
87
119
|
"py-[var(--bh-space-8xl-48)] text-center text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-text-body-sm-regular-font-weight)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--bh-content-subtle)]",
|
|
88
120
|
className
|
|
@@ -130,9 +162,11 @@ const CommandSeparator = React.forwardRef<
|
|
|
130
162
|
CommandSeparatorProps
|
|
131
163
|
>(function CommandSeparator({ className, ...props }, ref) {
|
|
132
164
|
return (
|
|
133
|
-
<
|
|
165
|
+
<div
|
|
134
166
|
data-slot="command-separator"
|
|
135
167
|
ref={ref}
|
|
168
|
+
role="presentation"
|
|
169
|
+
aria-hidden="true"
|
|
136
170
|
className={cn("-mx-[var(--bh-space-md-8)] my-[var(--bh-space-md-8)] h-[var(--bh-border-width-default)] bg-[var(--bh-border-subtle)]", className)}
|
|
137
171
|
{...props}
|
|
138
172
|
/>
|
|
@@ -159,7 +159,15 @@ type DateRangePickerProps = Omit<
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const defaultTimeValue = getCurrentTimeValue()
|
|
162
|
-
const defaultDayLabels = [
|
|
162
|
+
const defaultDayLabels = [
|
|
163
|
+
{ full: "Sunday", short: "S" },
|
|
164
|
+
{ full: "Monday", short: "M" },
|
|
165
|
+
{ full: "Tuesday", short: "T" },
|
|
166
|
+
{ full: "Wednesday", short: "W" },
|
|
167
|
+
{ full: "Thursday", short: "T" },
|
|
168
|
+
{ full: "Friday", short: "F" },
|
|
169
|
+
{ full: "Saturday", short: "S" },
|
|
170
|
+
] as const
|
|
163
171
|
|
|
164
172
|
const calendarSurface = cva(
|
|
165
173
|
[
|
|
@@ -216,7 +224,7 @@ const monthButton = cva(
|
|
|
216
224
|
"flex size-[var(--bh-date-nav-button-size)] shrink-0 items-center justify-center rounded-[var(--bh-control-default)]",
|
|
217
225
|
"bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] outline-none transition-[background-color,box-shadow,color]",
|
|
218
226
|
"hover:bg-[var(--bh-interactive-soft-hover)] focus-visible:shadow-[var(--shadow-button-focus)] disabled:opacity-[var(--bh-opacity-0)]",
|
|
219
|
-
"[&
|
|
227
|
+
"[&_[data-slot=date-picker-nav-icon]>svg]:size-[var(--bh-date-nav-icon-size)]",
|
|
220
228
|
]
|
|
221
229
|
)
|
|
222
230
|
|
|
@@ -262,7 +270,7 @@ const dayCellButton = cva(
|
|
|
262
270
|
event:
|
|
263
271
|
"rounded-[var(--bh-control-default)] text-[var(--bh-content-brand-default)] after:absolute after:bottom-[var(--bh-date-event-dot-offset-bottom)] after:left-1/2 after:size-[var(--bh-date-event-dot-size)] after:-translate-x-1/2 after:rounded-[var(--bh-radius-full)] after:bg-[var(--bh-content-brand-default)] rtl:after:translate-x-1/2",
|
|
264
272
|
selected:
|
|
265
|
-
"rounded-[var(--bh-control-default)] bg-[var(--bh-bg-brand-
|
|
273
|
+
"rounded-[var(--bh-control-default)] bg-[var(--bh-bg-brand-bold)] text-[var(--bh-content-on-brand)] hover:bg-[var(--bh-bg-brand-bold)] hover:shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-border-focus)]",
|
|
266
274
|
range:
|
|
267
275
|
"rounded-none bg-[var(--bh-bg-brand-subtle)] text-[var(--bh-content-brand-strong)] hover:bg-[var(--bh-bg-brand-subtle)]",
|
|
268
276
|
},
|
|
@@ -756,7 +764,7 @@ function DatePickerTrigger({
|
|
|
756
764
|
<span
|
|
757
765
|
aria-hidden="true"
|
|
758
766
|
data-slot="date-picker-trigger-icon"
|
|
759
|
-
className="flex size-[var(--bh-date-trigger-icon-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] group-disabled/date-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-date-trigger-icon-size)]"
|
|
767
|
+
className="flex size-[var(--bh-date-trigger-icon-slot-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] group-disabled/date-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-date-trigger-icon-size)]"
|
|
760
768
|
>
|
|
761
769
|
{icon ?? <CalendarDaysIcon strokeWidth="var(--bh-icon-stroke-180)" />}
|
|
762
770
|
</span>
|
|
@@ -816,6 +824,7 @@ function CalendarMonth({
|
|
|
816
824
|
}) {
|
|
817
825
|
const cells = React.useMemo(() => getCalendarCells(displayMonth), [displayMonth])
|
|
818
826
|
const gridRef = React.useRef<HTMLDivElement>(null)
|
|
827
|
+
const titleId = React.useId()
|
|
819
828
|
const pendingFocusDateRef = React.useRef<Date | undefined>(undefined)
|
|
820
829
|
const monthTitle = title ?? formatMonthTitle(displayMonth)
|
|
821
830
|
const hasMonthToggle = mode === "single"
|
|
@@ -864,13 +873,20 @@ function CalendarMonth({
|
|
|
864
873
|
className={monthButton()}
|
|
865
874
|
onClick={() => onDisplayMonthChange(addMonths(displayMonth, -1))}
|
|
866
875
|
>
|
|
867
|
-
<
|
|
876
|
+
<span
|
|
877
|
+
aria-hidden="true"
|
|
878
|
+
data-slot="date-picker-nav-icon"
|
|
879
|
+
className="inline-flex size-[var(--bh-date-nav-icon-slot-size)] items-center justify-center"
|
|
880
|
+
>
|
|
881
|
+
<ChevronLeftIcon data-rtl-flip="true" />
|
|
882
|
+
</span>
|
|
868
883
|
</button>
|
|
869
884
|
|
|
870
885
|
{hasMonthToggle ? (
|
|
871
886
|
<button
|
|
872
887
|
aria-expanded={view === "month-year"}
|
|
873
888
|
data-slot="calendar-title"
|
|
889
|
+
id={titleId}
|
|
874
890
|
type="button"
|
|
875
891
|
className={calendarTitleButton({
|
|
876
892
|
active: view === "month-year",
|
|
@@ -888,6 +904,7 @@ function CalendarMonth({
|
|
|
888
904
|
) : (
|
|
889
905
|
<div
|
|
890
906
|
data-slot="calendar-title"
|
|
907
|
+
id={titleId}
|
|
891
908
|
className={calendarTitleButton({ framed: false })}
|
|
892
909
|
>
|
|
893
910
|
<span className="min-w-0 truncate">{monthTitle}</span>
|
|
@@ -901,46 +918,67 @@ function CalendarMonth({
|
|
|
901
918
|
className={monthButton()}
|
|
902
919
|
onClick={() => onDisplayMonthChange(addMonths(displayMonth, 1))}
|
|
903
920
|
>
|
|
904
|
-
<
|
|
921
|
+
<span
|
|
922
|
+
aria-hidden="true"
|
|
923
|
+
data-slot="date-picker-nav-icon"
|
|
924
|
+
className="inline-flex size-[var(--bh-date-nav-icon-slot-size)] items-center justify-center"
|
|
925
|
+
>
|
|
926
|
+
<ChevronRightIcon data-rtl-flip="true" />
|
|
927
|
+
</span>
|
|
905
928
|
</button>
|
|
906
929
|
</div>
|
|
907
930
|
|
|
908
931
|
{view === "days" ? (
|
|
909
932
|
<>
|
|
910
933
|
<div
|
|
911
|
-
aria-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
>
|
|
915
|
-
{defaultDayLabels.map((day, index) => (
|
|
916
|
-
<div
|
|
917
|
-
className="flex h-[var(--bh-date-day-cell-height)] min-w-0 items-center justify-center rounded-[var(--bh-radius-lg-8)] text-center text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
|
|
918
|
-
key={`${day}-${index}`}
|
|
919
|
-
>
|
|
920
|
-
{day}
|
|
921
|
-
</div>
|
|
922
|
-
))}
|
|
923
|
-
</div>
|
|
924
|
-
|
|
925
|
-
<div
|
|
934
|
+
aria-colcount={7}
|
|
935
|
+
aria-labelledby={titleId}
|
|
936
|
+
aria-rowcount={Math.ceil(cells.length / 7) + 1}
|
|
926
937
|
data-slot="calendar-grid"
|
|
927
938
|
role="grid"
|
|
928
939
|
ref={gridRef}
|
|
929
|
-
className="grid w-full
|
|
940
|
+
className="grid w-full gap-[var(--bh-space-md-8)]"
|
|
930
941
|
>
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
942
|
+
<div
|
|
943
|
+
data-slot="calendar-weekdays"
|
|
944
|
+
role="row"
|
|
945
|
+
className="grid w-full grid-cols-7"
|
|
946
|
+
>
|
|
947
|
+
{defaultDayLabels.map((day) => (
|
|
948
|
+
<div
|
|
949
|
+
aria-label={day.full}
|
|
950
|
+
className="flex h-[var(--bh-date-day-cell-height)] min-w-0 items-center justify-center rounded-[var(--bh-radius-lg-8)] text-center text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
|
|
951
|
+
key={day.full}
|
|
952
|
+
role="columnheader"
|
|
953
|
+
>
|
|
954
|
+
{day.short}
|
|
955
|
+
</div>
|
|
956
|
+
))}
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<div role="rowgroup" className="grid w-full">
|
|
960
|
+
{chunkCalendarRows(cells).map((row, rowIndex) => (
|
|
961
|
+
<div
|
|
962
|
+
className="grid w-full grid-cols-7"
|
|
963
|
+
key={row[0]?.date.toISOString() ?? rowIndex}
|
|
964
|
+
role="row"
|
|
965
|
+
>
|
|
966
|
+
{row.map((cell) => (
|
|
967
|
+
<CalendarDayCell
|
|
968
|
+
cell={cell}
|
|
969
|
+
displayMonth={displayMonth}
|
|
970
|
+
eventDates={eventDates}
|
|
971
|
+
key={cell.date.toISOString()}
|
|
972
|
+
mode={mode}
|
|
973
|
+
onKeyboardNavigateDate={handleKeyboardNavigateDate}
|
|
974
|
+
onSelectDate={onSelectDate}
|
|
975
|
+
range={range}
|
|
976
|
+
selectedDate={selectedDate}
|
|
977
|
+
/>
|
|
978
|
+
))}
|
|
979
|
+
</div>
|
|
980
|
+
))}
|
|
981
|
+
</div>
|
|
944
982
|
</div>
|
|
945
983
|
|
|
946
984
|
{showTime ? (
|
|
@@ -955,6 +993,16 @@ function CalendarMonth({
|
|
|
955
993
|
)
|
|
956
994
|
}
|
|
957
995
|
|
|
996
|
+
function chunkCalendarRows(cells: CalendarCell[]) {
|
|
997
|
+
const rows: CalendarCell[][] = []
|
|
998
|
+
|
|
999
|
+
for (let index = 0; index < cells.length; index += 7) {
|
|
1000
|
+
rows.push(cells.slice(index, index + 7))
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return rows
|
|
1004
|
+
}
|
|
1005
|
+
|
|
958
1006
|
function CalendarDayCell({
|
|
959
1007
|
cell,
|
|
960
1008
|
displayMonth,
|
|
@@ -149,7 +149,6 @@ export function ActivityFeedItem({
|
|
|
149
149
|
type = "caption",
|
|
150
150
|
}: ActivityFeedItemProps) {
|
|
151
151
|
const actionItems = actions === false || !hasActionButtons ? [] : (actions ?? []).slice(0, hasTwoActionButtons ? 2 : 1);
|
|
152
|
-
const rtl = dir === "rtl";
|
|
153
152
|
|
|
154
153
|
const textParts = [
|
|
155
154
|
label && <strong key="label">{label}</strong>,
|
|
@@ -162,10 +161,9 @@ export function ActivityFeedItem({
|
|
|
162
161
|
className={classes(
|
|
163
162
|
"ds-activity-feed-item",
|
|
164
163
|
`ds-activity-feed-item--${type}`,
|
|
165
|
-
rtl ? "ds-activity-feed-item--rtl" : "ds-activity-feed-item--ltr",
|
|
166
164
|
className,
|
|
167
165
|
)}
|
|
168
|
-
dir={
|
|
166
|
+
dir={dir}
|
|
169
167
|
>
|
|
170
168
|
<Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} showLine={showLine} />
|
|
171
169
|
|
|
@@ -210,13 +208,11 @@ export function ActivityFeed({
|
|
|
210
208
|
dir,
|
|
211
209
|
items = [],
|
|
212
210
|
}: ActivityFeedProps) {
|
|
213
|
-
const rtl = dir === "rtl";
|
|
214
|
-
|
|
215
211
|
return (
|
|
216
212
|
<section
|
|
217
213
|
aria-label={ariaLabel}
|
|
218
|
-
className={classes("ds-activity-feed",
|
|
219
|
-
dir={
|
|
214
|
+
className={classes("ds-activity-feed", className)}
|
|
215
|
+
dir={dir}
|
|
220
216
|
>
|
|
221
217
|
{items.map((item, index) => (
|
|
222
218
|
<ActivityFeedItem
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import "./activityHeatmap.css";
|
|
3
|
+
|
|
4
|
+
export type ActivityHeatmapLevel = 0 | 1 | 2 | 3 | 4;
|
|
5
|
+
|
|
6
|
+
export type ActivityHeatmapItem = {
|
|
7
|
+
id?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
level: ActivityHeatmapLevel;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ActivityHeatmapProps = Omit<
|
|
13
|
+
ComponentPropsWithoutRef<"div">,
|
|
14
|
+
"children" | "dir"
|
|
15
|
+
> & {
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
dir?: "ltr" | "rtl";
|
|
18
|
+
items?: readonly ActivityHeatmapItem[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function classes(...names: Array<string | false | undefined>) {
|
|
22
|
+
return names.filter(Boolean).join(" ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeLevel(level: ActivityHeatmapLevel): ActivityHeatmapLevel {
|
|
26
|
+
return Math.max(0, Math.min(4, Math.round(level))) as ActivityHeatmapLevel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ActivityHeatmap({
|
|
30
|
+
ariaLabel = "Activity heatmap",
|
|
31
|
+
className,
|
|
32
|
+
dir,
|
|
33
|
+
items = [],
|
|
34
|
+
tabIndex = 0,
|
|
35
|
+
...props
|
|
36
|
+
}: ActivityHeatmapProps) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
{...props}
|
|
40
|
+
aria-label={ariaLabel}
|
|
41
|
+
className={classes("ds-activity-heatmap", className)}
|
|
42
|
+
data-slot="activity-heatmap"
|
|
43
|
+
dir={dir}
|
|
44
|
+
role="img"
|
|
45
|
+
tabIndex={tabIndex}
|
|
46
|
+
>
|
|
47
|
+
<span
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
className="ds-activity-heatmap__grid"
|
|
50
|
+
data-slot="activity-heatmap-grid"
|
|
51
|
+
>
|
|
52
|
+
{items.map((item, index) => (
|
|
53
|
+
<span
|
|
54
|
+
className="ds-activity-heatmap__cell"
|
|
55
|
+
data-level={normalizeLevel(item.level)}
|
|
56
|
+
data-slot="activity-heatmap-cell"
|
|
57
|
+
key={item.id ?? index}
|
|
58
|
+
title={item.label}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -19,6 +19,7 @@ export type BreadcrumbItem = {
|
|
|
19
19
|
|
|
20
20
|
export type BreadcrumbsProps = {
|
|
21
21
|
"aria-label"?: string;
|
|
22
|
+
className?: string;
|
|
22
23
|
dir?: "ltr" | "rtl" | "auto";
|
|
23
24
|
iconOnly?: boolean;
|
|
24
25
|
items: BreadcrumbItem[];
|
|
@@ -49,6 +50,7 @@ function visibleItems(
|
|
|
49
50
|
|
|
50
51
|
export function Breadcrumbs({
|
|
51
52
|
"aria-label": ariaLabel,
|
|
53
|
+
className,
|
|
52
54
|
dir,
|
|
53
55
|
iconOnly = false,
|
|
54
56
|
items,
|
|
@@ -67,6 +69,7 @@ export function Breadcrumbs({
|
|
|
67
69
|
`ds-breadcrumbs--${surfaceStyle}`,
|
|
68
70
|
`ds-breadcrumbs--${separator}`,
|
|
69
71
|
iconOnly && "ds-breadcrumbs--icon-only",
|
|
72
|
+
className,
|
|
70
73
|
)}
|
|
71
74
|
dir={dir}
|
|
72
75
|
>
|
|
@@ -32,7 +32,6 @@ const rtlBreadcrumbItems: BreadcrumbItem[] = [
|
|
|
32
32
|
{ label: "\u0627\u0644\u0645\u0643\u0648\u0646\u0627\u062a", current: true },
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
-
const breadcrumbAriaLabel = "Catalog breadcrumbs";
|
|
36
35
|
const rtlBreadcrumbAriaLabel = "\u0627\u0644\u0645\u0633\u0627\u0631";
|
|
37
36
|
|
|
38
37
|
const uploadFiles: FileUploadFile[] = [
|
|
@@ -109,9 +108,9 @@ export function CatalogComponentsShowcase() {
|
|
|
109
108
|
<div className="catalog-showcase">
|
|
110
109
|
<ShowcaseCard title="Breadcrumbs" description="Default, raised, icon-only, separator, and RTL variants.">
|
|
111
110
|
<div className="catalog-showcase__stack">
|
|
112
|
-
<Breadcrumbs aria-label=
|
|
113
|
-
<Breadcrumbs aria-label=
|
|
114
|
-
<Breadcrumbs aria-label=
|
|
111
|
+
<Breadcrumbs aria-label="Default catalog breadcrumbs" items={breadcrumbItems} overflowLabel="More" />
|
|
112
|
+
<Breadcrumbs aria-label="Raised catalog breadcrumbs" items={breadcrumbItems} overflowLabel="More" separator="chevron" style="raised" />
|
|
113
|
+
<Breadcrumbs aria-label="Icon-only catalog breadcrumbs" iconOnly items={breadcrumbItems} overflowLabel="More" separator="chevron" style="raised" />
|
|
115
114
|
<Breadcrumbs
|
|
116
115
|
aria-label={rtlBreadcrumbAriaLabel}
|
|
117
116
|
dir="rtl"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button";
|
|
1
2
|
import "./divider.css";
|
|
2
3
|
|
|
3
4
|
export type DividerBorderStyle = "solid" | "dotted";
|
|
@@ -36,9 +37,9 @@ export function CatalogContentDivider({
|
|
|
36
37
|
<div className={`ds-content-divider ds-content-divider--${borderStyle}`} dir={dir}>
|
|
37
38
|
<span aria-hidden="true" className="ds-content-divider__rule" />
|
|
38
39
|
{actionLabel && onAction ? (
|
|
39
|
-
<
|
|
40
|
+
<Button className="ds-content-divider__button" disabled={disabled} onClick={onAction} size="sm" type="button" variant="soft">
|
|
40
41
|
{actionLabel}
|
|
41
|
-
</
|
|
42
|
+
</Button>
|
|
42
43
|
) : label ? (
|
|
43
44
|
<span className="ds-content-divider__label">{label}</span>
|
|
44
45
|
) : null}
|
|
@@ -154,9 +154,9 @@ export function FileUpload({
|
|
|
154
154
|
<p className="ds-file-upload__title">
|
|
155
155
|
{resolvedPrompt}{" "}
|
|
156
156
|
{resolvedBrowseLabel && (
|
|
157
|
-
<
|
|
157
|
+
<Button className="ds-file-upload__browse" disabled={disabled} onClick={browse} size="xs" type="button" variant="link">
|
|
158
158
|
{resolvedBrowseLabel}
|
|
159
|
-
</
|
|
159
|
+
</Button>
|
|
160
160
|
)}
|
|
161
161
|
</p>
|
|
162
162
|
)}
|
|
@@ -267,9 +267,9 @@ function FileUploadRow({
|
|
|
267
267
|
)}
|
|
268
268
|
|
|
269
269
|
{!isSmall && file.status === "error" && (
|
|
270
|
-
<
|
|
270
|
+
<Button className="ds-file-upload-row__retry" onClick={() => onRetry?.(file.id)} size="xs" type="button" variant="link">
|
|
271
271
|
{retryLabel}
|
|
272
|
-
</
|
|
272
|
+
</Button>
|
|
273
273
|
)}
|
|
274
274
|
</div>
|
|
275
275
|
</div>
|
|
@@ -404,12 +404,14 @@ function StatusIcon({
|
|
|
404
404
|
}) {
|
|
405
405
|
return (
|
|
406
406
|
<span
|
|
407
|
+
aria-hidden={standalone ? undefined : "true"}
|
|
408
|
+
aria-label={standalone ? getStatusLabel(status) : undefined}
|
|
407
409
|
className={cx(
|
|
408
410
|
"ds-file-upload-row__status",
|
|
409
411
|
standalone && "ds-file-upload-row__status--standalone",
|
|
410
412
|
`ds-file-upload-row__status--${status}`
|
|
411
413
|
)}
|
|
412
|
-
|
|
414
|
+
role={standalone ? "img" : undefined}
|
|
413
415
|
>
|
|
414
416
|
{status === "uploaded" && <CheckboxCircleFillIcon />}
|
|
415
417
|
{status === "uploading" && <Loader2LineIcon />}
|
|
@@ -21,7 +21,6 @@ export function OnboardingStepListItem({
|
|
|
21
21
|
showTrailingLine = true,
|
|
22
22
|
state = "finished",
|
|
23
23
|
}: OnboardingStepListItemProps) {
|
|
24
|
-
const rtl = dir === "rtl";
|
|
25
24
|
const isFinished = state === "finished";
|
|
26
25
|
const isActive = state === "active";
|
|
27
26
|
const isInactive = state === "inactive";
|
|
@@ -31,7 +30,6 @@ export function OnboardingStepListItem({
|
|
|
31
30
|
className={[
|
|
32
31
|
"ds-onboarding-step",
|
|
33
32
|
`ds-onboarding-step--${state}`,
|
|
34
|
-
rtl ? "ds-onboarding-step--rtl" : "ds-onboarding-step--ltr",
|
|
35
33
|
className,
|
|
36
34
|
]
|
|
37
35
|
.filter(Boolean)
|
|
@@ -52,10 +50,11 @@ export function OnboardingStepListItem({
|
|
|
52
50
|
|
|
53
51
|
function CheckCircleIcon() {
|
|
54
52
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
<span className="ds-onboarding-step__check-slot" aria-hidden="true">
|
|
54
|
+
<CircleCheckIcon
|
|
55
|
+
className="ds-onboarding-step__check"
|
|
56
|
+
strokeWidth="var(--bh-icon-stroke-225)"
|
|
57
|
+
/>
|
|
58
|
+
</span>
|
|
60
59
|
);
|
|
61
60
|
}
|