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
|
@@ -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
|
[
|
|
@@ -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
|
},
|
|
@@ -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"
|
|
@@ -871,6 +880,7 @@ function CalendarMonth({
|
|
|
871
880
|
<button
|
|
872
881
|
aria-expanded={view === "month-year"}
|
|
873
882
|
data-slot="calendar-title"
|
|
883
|
+
id={titleId}
|
|
874
884
|
type="button"
|
|
875
885
|
className={calendarTitleButton({
|
|
876
886
|
active: view === "month-year",
|
|
@@ -888,6 +898,7 @@ function CalendarMonth({
|
|
|
888
898
|
) : (
|
|
889
899
|
<div
|
|
890
900
|
data-slot="calendar-title"
|
|
901
|
+
id={titleId}
|
|
891
902
|
className={calendarTitleButton({ framed: false })}
|
|
892
903
|
>
|
|
893
904
|
<span className="min-w-0 truncate">{monthTitle}</span>
|
|
@@ -908,39 +919,54 @@ function CalendarMonth({
|
|
|
908
919
|
{view === "days" ? (
|
|
909
920
|
<>
|
|
910
921
|
<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
|
|
922
|
+
aria-colcount={7}
|
|
923
|
+
aria-labelledby={titleId}
|
|
924
|
+
aria-rowcount={Math.ceil(cells.length / 7) + 1}
|
|
926
925
|
data-slot="calendar-grid"
|
|
927
926
|
role="grid"
|
|
928
927
|
ref={gridRef}
|
|
929
|
-
className="grid w-full
|
|
928
|
+
className="grid w-full gap-[var(--bh-space-md-8)]"
|
|
930
929
|
>
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
930
|
+
<div
|
|
931
|
+
data-slot="calendar-weekdays"
|
|
932
|
+
role="row"
|
|
933
|
+
className="grid w-full grid-cols-7"
|
|
934
|
+
>
|
|
935
|
+
{defaultDayLabels.map((day) => (
|
|
936
|
+
<div
|
|
937
|
+
aria-label={day.full}
|
|
938
|
+
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)]"
|
|
939
|
+
key={day.full}
|
|
940
|
+
role="columnheader"
|
|
941
|
+
>
|
|
942
|
+
{day.short}
|
|
943
|
+
</div>
|
|
944
|
+
))}
|
|
945
|
+
</div>
|
|
946
|
+
|
|
947
|
+
<div role="rowgroup" className="grid w-full">
|
|
948
|
+
{chunkCalendarRows(cells).map((row, rowIndex) => (
|
|
949
|
+
<div
|
|
950
|
+
className="grid w-full grid-cols-7"
|
|
951
|
+
key={row[0]?.date.toISOString() ?? rowIndex}
|
|
952
|
+
role="row"
|
|
953
|
+
>
|
|
954
|
+
{row.map((cell) => (
|
|
955
|
+
<CalendarDayCell
|
|
956
|
+
cell={cell}
|
|
957
|
+
displayMonth={displayMonth}
|
|
958
|
+
eventDates={eventDates}
|
|
959
|
+
key={cell.date.toISOString()}
|
|
960
|
+
mode={mode}
|
|
961
|
+
onKeyboardNavigateDate={handleKeyboardNavigateDate}
|
|
962
|
+
onSelectDate={onSelectDate}
|
|
963
|
+
range={range}
|
|
964
|
+
selectedDate={selectedDate}
|
|
965
|
+
/>
|
|
966
|
+
))}
|
|
967
|
+
</div>
|
|
968
|
+
))}
|
|
969
|
+
</div>
|
|
944
970
|
</div>
|
|
945
971
|
|
|
946
972
|
{showTime ? (
|
|
@@ -955,6 +981,16 @@ function CalendarMonth({
|
|
|
955
981
|
)
|
|
956
982
|
}
|
|
957
983
|
|
|
984
|
+
function chunkCalendarRows(cells: CalendarCell[]) {
|
|
985
|
+
const rows: CalendarCell[][] = []
|
|
986
|
+
|
|
987
|
+
for (let index = 0; index < cells.length; index += 7) {
|
|
988
|
+
rows.push(cells.slice(index, index + 7))
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return rows
|
|
992
|
+
}
|
|
993
|
+
|
|
958
994
|
function CalendarDayCell({
|
|
959
995
|
cell,
|
|
960
996
|
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)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { ElementType, ReactNode } from "react";
|
|
2
2
|
import { HomeIcon as LucideHomeIcon } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { Breadcrumbs as OwnedBreadcrumbs } from "./Breadcrumbs";
|
|
3
5
|
import { Tabs, type TabsProps } from "../tabs";
|
|
4
6
|
import "./pageHeader.css";
|
|
5
7
|
|
|
@@ -31,6 +33,7 @@ export type PageHeaderProps = {
|
|
|
31
33
|
description?: string;
|
|
32
34
|
dir?: "ltr" | "rtl";
|
|
33
35
|
icon?: ReactNode | false;
|
|
36
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
34
37
|
metaInfo?: string;
|
|
35
38
|
tabs?: PageHeaderTabs | false;
|
|
36
39
|
title?: string;
|
|
@@ -44,64 +47,43 @@ function HomeIcon() {
|
|
|
44
47
|
return <LucideHomeIcon aria-hidden="true" className="ds-page-header__home-icon" strokeWidth="var(--bh-icon-stroke-190)" />;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
function Breadcrumbs({ items, label }: { items: PageHeaderBreadcrumb[]; label?: string }) {
|
|
48
|
-
return (
|
|
49
|
-
<nav aria-label={label} className="ds-page-header__breadcrumbs">
|
|
50
|
-
<ol>
|
|
51
|
-
{items.map((item, index) => {
|
|
52
|
-
const isLast = index === items.length - 1;
|
|
53
|
-
const isCurrent = item.current === true;
|
|
54
|
-
const content = (
|
|
55
|
-
<>
|
|
56
|
-
{item.icon && <span className="ds-page-header__breadcrumb-icon">{item.icon}</span>}
|
|
57
|
-
<span className="ds-page-header__breadcrumb-label">{item.label}</span>
|
|
58
|
-
</>
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<li key={`${item.label}-${index}`}>
|
|
63
|
-
{item.href && !isCurrent ? (
|
|
64
|
-
<a href={item.href}>{content}</a>
|
|
65
|
-
) : (
|
|
66
|
-
<span aria-current={isCurrent ? "page" : undefined} className={isCurrent ? "is-current" : undefined}>
|
|
67
|
-
{content}
|
|
68
|
-
</span>
|
|
69
|
-
)}
|
|
70
|
-
{!isLast && (
|
|
71
|
-
<span aria-hidden="true" className="ds-page-header__breadcrumb-separator">
|
|
72
|
-
/
|
|
73
|
-
</span>
|
|
74
|
-
)}
|
|
75
|
-
</li>
|
|
76
|
-
);
|
|
77
|
-
})}
|
|
78
|
-
</ol>
|
|
79
|
-
</nav>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
50
|
function Action({ action }: { action: PageHeaderAction }) {
|
|
84
51
|
const className = "ds-page-header__action";
|
|
85
52
|
const label = <span>{action.label}</span>;
|
|
86
53
|
|
|
87
54
|
if (action.href) {
|
|
88
55
|
return (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
56
|
+
<Button asChild className={className} variant="secondary">
|
|
57
|
+
<a
|
|
58
|
+
aria-disabled={action.disabled || undefined}
|
|
59
|
+
aria-label={action.ariaLabel}
|
|
60
|
+
href={action.disabled ? undefined : action.href}
|
|
61
|
+
onClick={(event) => {
|
|
62
|
+
if (action.disabled) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
action.onClick?.();
|
|
67
|
+
}}
|
|
68
|
+
tabIndex={action.disabled ? -1 : undefined}
|
|
69
|
+
>
|
|
70
|
+
{label}
|
|
71
|
+
</a>
|
|
72
|
+
</Button>
|
|
92
73
|
);
|
|
93
74
|
}
|
|
94
75
|
|
|
95
76
|
return (
|
|
96
|
-
<
|
|
77
|
+
<Button
|
|
97
78
|
aria-label={action.ariaLabel}
|
|
98
79
|
className={className}
|
|
99
80
|
disabled={action.disabled}
|
|
100
81
|
onClick={action.onClick}
|
|
101
82
|
type="button"
|
|
83
|
+
variant="secondary"
|
|
102
84
|
>
|
|
103
85
|
{label}
|
|
104
|
-
</
|
|
86
|
+
</Button>
|
|
105
87
|
);
|
|
106
88
|
}
|
|
107
89
|
|
|
@@ -112,6 +94,7 @@ export function PageHeader({
|
|
|
112
94
|
className,
|
|
113
95
|
description,
|
|
114
96
|
dir,
|
|
97
|
+
headingLevel = 1,
|
|
115
98
|
icon,
|
|
116
99
|
metaInfo,
|
|
117
100
|
tabs = {},
|
|
@@ -120,10 +103,18 @@ export function PageHeader({
|
|
|
120
103
|
const breadcrumbItems = breadcrumbs === false ? [] : breadcrumbs;
|
|
121
104
|
const actionItems = actions === false ? [] : actions;
|
|
122
105
|
const showIcon = icon !== false;
|
|
106
|
+
const Heading = `h${headingLevel}` as ElementType;
|
|
123
107
|
|
|
124
108
|
return (
|
|
125
109
|
<header className={classes("ds-page-header", className)} dir={dir}>
|
|
126
|
-
{breadcrumbItems.length > 0 &&
|
|
110
|
+
{breadcrumbItems.length > 0 && (
|
|
111
|
+
<OwnedBreadcrumbs
|
|
112
|
+
aria-label={breadcrumbLabel ?? "Breadcrumb"}
|
|
113
|
+
className="ds-page-header__breadcrumbs"
|
|
114
|
+
dir={dir}
|
|
115
|
+
items={breadcrumbItems}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
127
118
|
|
|
128
119
|
<div className="ds-page-header__section">
|
|
129
120
|
<div className="ds-page-header__topline">
|
|
@@ -131,7 +122,7 @@ export function PageHeader({
|
|
|
131
122
|
<div className="ds-page-header__heading-row">
|
|
132
123
|
<div className="ds-page-header__heading-main">
|
|
133
124
|
{showIcon && <span className="ds-page-header__icon">{icon ?? <HomeIcon />}</span>}
|
|
134
|
-
{title && <
|
|
125
|
+
{title && <Heading className="ds-page-header__title">{title}</Heading>}
|
|
135
126
|
</div>
|
|
136
127
|
{metaInfo && (
|
|
137
128
|
<span className="ds-page-header__meta">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useId, type ReactNode } from "react";
|
|
2
2
|
import { XIcon } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
3
4
|
import "./slideout.css";
|
|
4
5
|
|
|
5
6
|
export type SlideoutSize = "small" | "medium" | "large";
|
|
@@ -48,7 +49,7 @@ export function Slideout({
|
|
|
48
49
|
size = "small",
|
|
49
50
|
title,
|
|
50
51
|
}: SlideoutProps) {
|
|
51
|
-
const
|
|
52
|
+
const titleId = useId();
|
|
52
53
|
const hasFooterActions = Boolean(primaryActionLabel || (secondaryAction && secondaryActionLabel));
|
|
53
54
|
|
|
54
55
|
return (
|
|
@@ -57,39 +58,43 @@ export function Slideout({
|
|
|
57
58
|
"ds-slideout",
|
|
58
59
|
`ds-slideout--${size}`,
|
|
59
60
|
`ds-slideout--${breakpoint}`,
|
|
60
|
-
rtl && "ds-slideout--rtl",
|
|
61
61
|
className,
|
|
62
62
|
)}
|
|
63
63
|
dir={dir}
|
|
64
64
|
>
|
|
65
|
-
<section
|
|
65
|
+
<section
|
|
66
|
+
aria-labelledby={title ? titleId : undefined}
|
|
67
|
+
className="ds-slideout__panel"
|
|
68
|
+
data-slot="slideout-panel"
|
|
69
|
+
role={title ? "region" : undefined}
|
|
70
|
+
>
|
|
66
71
|
<header className="ds-slideout__header">
|
|
67
72
|
<div className="ds-slideout__heading-wrap">
|
|
68
|
-
<h2 className="ds-slideout__title">{title}</h2>
|
|
73
|
+
{title ? <h2 className="ds-slideout__title" id={titleId}>{title}</h2> : null}
|
|
69
74
|
{headerAction && actionLabel && (
|
|
70
|
-
<
|
|
75
|
+
<Button className="ds-slideout__text-button" size="sm" type="button" variant="link" onClick={onAction}>
|
|
71
76
|
{actionLabel}
|
|
72
|
-
</
|
|
77
|
+
</Button>
|
|
73
78
|
)}
|
|
74
79
|
</div>
|
|
75
80
|
{closeLabel && (
|
|
76
|
-
<
|
|
81
|
+
<Button aria-label={closeLabel} className="ds-slideout__close" size="icon-sm" type="button" variant="ghost" onClick={onClose}>
|
|
77
82
|
<CloseIcon />
|
|
78
|
-
</
|
|
83
|
+
</Button>
|
|
79
84
|
)}
|
|
80
85
|
</header>
|
|
81
86
|
<div className="ds-slideout__body">{children}</div>
|
|
82
87
|
{footer && hasFooterActions && (
|
|
83
88
|
<footer className="ds-slideout__footer">
|
|
84
89
|
{primaryActionLabel && (
|
|
85
|
-
<
|
|
90
|
+
<Button className="ds-slideout__button ds-slideout__button--primary" size="sm" type="button" onClick={onPrimaryAction}>
|
|
86
91
|
{primaryActionLabel}
|
|
87
|
-
</
|
|
92
|
+
</Button>
|
|
88
93
|
)}
|
|
89
94
|
{secondaryAction && secondaryActionLabel && (
|
|
90
|
-
<
|
|
95
|
+
<Button className="ds-slideout__button ds-slideout__button--secondary" size="sm" type="button" variant="secondary" onClick={onSecondaryAction}>
|
|
91
96
|
{secondaryActionLabel}
|
|
92
|
-
</
|
|
97
|
+
</Button>
|
|
93
98
|
)}
|
|
94
99
|
</footer>
|
|
95
100
|
)}
|