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.
Files changed (101) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +1 -1
  3. package/docs/design-system/component-implementation-ownership-audit.md +244 -0
  4. package/docs/design-system/component-quality-audit-2026-08-20.md +223 -0
  5. package/docs/design-system/component-showcase-consistency-report.md +2 -0
  6. package/docs/design-system/components/README.md +1 -0
  7. package/docs/design-system/components/activity-feed.md +2 -0
  8. package/docs/design-system/components/activity-heatmap.md +73 -0
  9. package/docs/design-system/components/alert-dialog.md +1 -1
  10. package/docs/design-system/components/alert.md +2 -2
  11. package/docs/design-system/components/avatar.md +1 -1
  12. package/docs/design-system/components/badge.md +1 -1
  13. package/docs/design-system/components/banner.md +5 -1
  14. package/docs/design-system/components/breadcrumbs.md +3 -0
  15. package/docs/design-system/components/button-group.md +1 -0
  16. package/docs/design-system/components/button.md +1 -1
  17. package/docs/design-system/components/card.md +11 -3
  18. package/docs/design-system/components/catalog-components.md +3 -0
  19. package/docs/design-system/components/date-picker.md +1 -1
  20. package/docs/design-system/components/file-upload.md +4 -0
  21. package/docs/design-system/components/modal.md +1 -1
  22. package/docs/design-system/components/navigation-menu.md +1 -1
  23. package/docs/design-system/components/onboarding-step-list-item.md +1 -0
  24. package/docs/design-system/components/page-header.md +3 -1
  25. package/docs/design-system/components/pagination.md +1 -0
  26. package/docs/design-system/components/resizable.md +1 -1
  27. package/docs/design-system/components/scroll-area.md +7 -3
  28. package/docs/design-system/components/sidebar.md +3 -1
  29. package/docs/design-system/components/slideout.md +4 -1
  30. package/docs/design-system/components/steps.md +7 -2
  31. package/docs/design-system/components/table.md +25 -7
  32. package/docs/design-system/components/tag.md +1 -1
  33. package/docs/design-system/components/timeline.md +4 -1
  34. package/docs/design-system/components/toggle.md +1 -1
  35. package/docs/design-system/components/toolbar.md +1 -1
  36. package/docs/design-system/components/tooltip.md +1 -1
  37. package/docs/design-system/density-and-elevation.md +18 -1
  38. package/docs/design-system/gap-promotion-report.md +1 -67
  39. package/docs/design-system/launch-comparison-shadcn.md +10 -10
  40. package/package.json +3 -2
  41. package/registry/components/alert-dialog.tsx +1 -1
  42. package/registry/components/alert.tsx +15 -11
  43. package/registry/components/avatar.tsx +7 -3
  44. package/registry/components/badge.tsx +17 -17
  45. package/registry/components/button-group.tsx +3 -1
  46. package/registry/components/button.tsx +2 -2
  47. package/registry/components/card.tsx +19 -3
  48. package/registry/components/carousel.tsx +14 -0
  49. package/registry/components/command.tsx +37 -3
  50. package/registry/components/date-picker.tsx +67 -31
  51. package/registry/components/expanded/ActivityFeed.tsx +3 -7
  52. package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
  53. package/registry/components/expanded/Banner.tsx +0 -1
  54. package/registry/components/expanded/Breadcrumbs.tsx +3 -0
  55. package/registry/components/expanded/CatalogComponentsShowcase.tsx +3 -4
  56. package/registry/components/expanded/CatalogDivider.tsx +3 -2
  57. package/registry/components/expanded/FileUpload.tsx +7 -5
  58. package/registry/components/expanded/OnboardingStepListItem.tsx +0 -2
  59. package/registry/components/expanded/PageHeader.tsx +35 -44
  60. package/registry/components/expanded/Slideout.tsx +18 -13
  61. package/registry/components/expanded/Steps.tsx +147 -9
  62. package/registry/components/expanded/Table.tsx +50 -18
  63. package/registry/components/expanded/Timeline.tsx +11 -24
  64. package/registry/components/expanded/activityFeed.css +8 -8
  65. package/registry/components/expanded/activityHeatmap.css +66 -0
  66. package/registry/components/expanded/banner.css +21 -11
  67. package/registry/components/expanded/breadcrumbs.css +1 -1
  68. package/registry/components/expanded/fileUpload.css +1 -1
  69. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  70. package/registry/components/expanded/pageHeader.css +14 -11
  71. package/registry/components/expanded/slideout.css +3 -7
  72. package/registry/components/expanded/steps.css +75 -12
  73. package/registry/components/expanded/table.css +58 -67
  74. package/registry/components/expanded/timeline.css +17 -16
  75. package/registry/components/field.tsx +61 -8
  76. package/registry/components/menu.tsx +56 -6
  77. package/registry/components/modal.tsx +1 -1
  78. package/registry/components/navigation-menu.tsx +1 -1
  79. package/registry/components/pagination.css +36 -0
  80. package/registry/components/pagination.tsx +8 -6
  81. package/registry/components/progress-slider.tsx +1 -1
  82. package/registry/components/resizable.tsx +36 -2
  83. package/registry/components/scroll-area.tsx +30 -6
  84. package/registry/components/select.tsx +34 -11
  85. package/registry/components/sidebar.tsx +11 -5
  86. package/registry/components/social-button.tsx +12 -5
  87. package/registry/components/table-elements.tsx +128 -0
  88. package/registry/components/tabs.tsx +5 -10
  89. package/registry/components/tag.tsx +1 -1
  90. package/registry/components/toggle.tsx +2 -2
  91. package/registry/components/toolbar.tsx +5 -5
  92. package/registry/components/tooltip.tsx +1 -1
  93. package/registry/examples/avatar-demo.tsx +7 -4
  94. package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
  95. package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
  96. package/registry/examples/pagination-demo.tsx +5 -5
  97. package/registry/examples/sidebar-demo.tsx +4 -2
  98. package/registry/index.json +392 -33
  99. package/registry/styles/globals.css +68 -42
  100. package/registry.json +40 -3
  101. package/tokens/banhaten.tokens.json +165 -55
@@ -25,6 +25,34 @@ export type StepsProps = {
25
25
  items?: StepItem[];
26
26
  };
27
27
 
28
+ export type ProgressStepState = "complete" | "active" | "todo";
29
+
30
+ export type ProgressStep = {
31
+ label?: ReactNode;
32
+ marker?: ReactNode;
33
+ state?: ProgressStepState;
34
+ };
35
+
36
+ export type ProgressStepsClassNames = {
37
+ connector?: string;
38
+ item?: string;
39
+ label?: string;
40
+ marker?: string;
41
+ segment?: string;
42
+ track?: string;
43
+ };
44
+
45
+ export type ProgressStepsProps = {
46
+ activeIndex?: number;
47
+ ariaLabel: string;
48
+ className?: string;
49
+ classNames?: ProgressStepsClassNames;
50
+ dir?: "ltr" | "rtl";
51
+ items: ProgressStep[];
52
+ label?: ReactNode;
53
+ variant?: "bars" | "markers";
54
+ };
55
+
28
56
  export type HorizontalStepItemProps = {
29
57
  alignment?: StepAlignment;
30
58
  caption?: ReactNode;
@@ -66,13 +94,12 @@ export function Steps({
66
94
  indicator = "icon",
67
95
  items,
68
96
  }: StepsProps) {
69
- const rtl = dir === "rtl";
70
97
  const stepItems = items ?? [];
71
98
 
72
99
  if (direction === "vertical") {
73
100
  return (
74
101
  <div
75
- className={["ds-steps", "ds-steps--vertical", rtl ? "ds-steps--rtl" : "ds-steps--ltr", className]
102
+ className={["ds-steps", "ds-steps--vertical", className]
76
103
  .filter(Boolean)
77
104
  .join(" ")}
78
105
  dir={dir}
@@ -102,7 +129,7 @@ export function Steps({
102
129
 
103
130
  return (
104
131
  <div
105
- className={["ds-steps", "ds-steps--horizontal", rtl ? "ds-steps--rtl" : "ds-steps--ltr", className]
132
+ className={["ds-steps", "ds-steps--horizontal", className]
106
133
  .filter(Boolean)
107
134
  .join(" ")}
108
135
  dir={dir}
@@ -122,6 +149,122 @@ export function Steps({
122
149
  );
123
150
  }
124
151
 
152
+ export function ProgressSteps({
153
+ activeIndex = 0,
154
+ ariaLabel,
155
+ className = "",
156
+ classNames = {},
157
+ dir,
158
+ items,
159
+ label,
160
+ variant = "markers",
161
+ }: ProgressStepsProps) {
162
+ const rootClassName = [
163
+ "ds-progress-steps",
164
+ `ds-progress-steps--${variant}`,
165
+ className,
166
+ ]
167
+ .filter(Boolean)
168
+ .join(" ");
169
+
170
+ if (variant === "bars") {
171
+ return (
172
+ <div
173
+ aria-label={ariaLabel}
174
+ aria-valuemax={items.length}
175
+ aria-valuemin={1}
176
+ aria-valuenow={Math.min(activeIndex + 1, items.length)}
177
+ className={rootClassName}
178
+ dir={dir}
179
+ role="progressbar"
180
+ >
181
+ {label ? <span data-slot="progress-steps-label">{label}</span> : null}
182
+ <div
183
+ aria-hidden="true"
184
+ className={["ds-progress-steps__track", classNames.track]
185
+ .filter(Boolean)
186
+ .join(" ")}
187
+ data-slot="progress-steps-track"
188
+ >
189
+ {items.map((item, index) => (
190
+ <span
191
+ className={["ds-progress-steps__segment", classNames.segment]
192
+ .filter(Boolean)
193
+ .join(" ")}
194
+ data-active={index <= activeIndex ? "true" : "false"}
195
+ data-slot="progress-steps-segment"
196
+ data-state={item.state ?? (index < activeIndex ? "complete" : index === activeIndex ? "active" : "todo")}
197
+ key={index}
198
+ />
199
+ ))}
200
+ </div>
201
+ </div>
202
+ );
203
+ }
204
+
205
+ return (
206
+ <ol aria-label={ariaLabel} className={rootClassName} dir={dir}>
207
+ {items.map((item, index) => {
208
+ const state =
209
+ item.state ??
210
+ (index < activeIndex
211
+ ? "complete"
212
+ : index === activeIndex
213
+ ? "active"
214
+ : "todo");
215
+
216
+ return (
217
+ <li
218
+ className={[
219
+ "ds-progress-steps__item",
220
+ `is-${state}`,
221
+ classNames.item,
222
+ ]
223
+ .filter(Boolean)
224
+ .join(" ")}
225
+ data-slot="progress-steps-item"
226
+ data-state={state}
227
+ key={index}
228
+ >
229
+ <span
230
+ aria-hidden="true"
231
+ className={["ds-progress-steps__marker", classNames.marker]
232
+ .filter(Boolean)
233
+ .join(" ")}
234
+ data-slot="progress-steps-marker"
235
+ >
236
+ {item.marker ?? index + 1}
237
+ </span>
238
+ {item.label ? (
239
+ <span
240
+ className={["ds-progress-steps__label", classNames.label]
241
+ .filter(Boolean)
242
+ .join(" ")}
243
+ data-slot="progress-steps-item-label"
244
+ >
245
+ {item.label}
246
+ </span>
247
+ ) : null}
248
+ {index < items.length - 1 ? (
249
+ <span
250
+ aria-hidden="true"
251
+ className={[
252
+ "ds-progress-steps__connector",
253
+ state === "complete" && "is-complete",
254
+ classNames.connector,
255
+ ]
256
+ .filter(Boolean)
257
+ .join(" ")}
258
+ data-slot="progress-steps-connector"
259
+ />
260
+ ) : null}
261
+ </li>
262
+ );
263
+ })}
264
+ </ol>
265
+ );
266
+ }
267
+
125
268
  export function HorizontalStepItem({
126
269
  alignment = "leading",
127
270
  caption,
@@ -135,7 +278,6 @@ export function HorizontalStepItem({
135
278
  showLeadingLine = true,
136
279
  showTrailingLine = true,
137
280
  }: HorizontalStepItemProps) {
138
- const rtl = dir === "rtl";
139
281
  const linePlacement = getHorizontalLinePlacement(alignment, showLeadingLine, showTrailingLine);
140
282
  const text = (
141
283
  <div className="ds-horizontal-step-item__text">
@@ -157,7 +299,6 @@ export function HorizontalStepItem({
157
299
  "ds-horizontal-step-item",
158
300
  `ds-horizontal-step-item--${alignment}`,
159
301
  `ds-horizontal-step-item--${position}`,
160
- rtl ? "ds-horizontal-step-item--rtl" : "ds-horizontal-step-item--ltr",
161
302
  className,
162
303
  ]
163
304
  .filter(Boolean)
@@ -189,7 +330,6 @@ export function VerticalStepItem({
189
330
  slotContent,
190
331
  supportText,
191
332
  }: VerticalStepItemProps & { children?: ReactNode }) {
192
- const rtl = dir === "rtl";
193
333
  const marker = (
194
334
  <div className="ds-vertical-step-item__rail" aria-hidden="true">
195
335
  <StepMarker indicator={indicator} number={number} />
@@ -205,7 +345,6 @@ export function VerticalStepItem({
205
345
  className={[
206
346
  "ds-vertical-step-item",
207
347
  `ds-vertical-step-item--${alignment}`,
208
- rtl ? "ds-vertical-step-item--rtl" : "ds-vertical-step-item--ltr",
209
348
  className,
210
349
  ]
211
350
  .filter(Boolean)
@@ -216,9 +355,8 @@ export function VerticalStepItem({
216
355
  <div className="ds-vertical-step-item__content">
217
356
  <div className="ds-vertical-step-item__copy">
218
357
  <div className="ds-vertical-step-item__header">
219
- {rtl && hasSupportText && supportText && <div className="ds-vertical-step-item__support">{supportText}</div>}
220
358
  {label && <div className="ds-vertical-step-item__label">{label}</div>}
221
- {!rtl && hasSupportText && supportText && <div className="ds-vertical-step-item__support">{supportText}</div>}
359
+ {hasSupportText && supportText && <div className="ds-vertical-step-item__support">{supportText}</div>}
222
360
  </div>
223
361
  {hasCaption && caption && <div className="ds-vertical-step-item__caption">{caption}</div>}
224
362
  </div>
@@ -5,6 +5,7 @@ import {
5
5
  EllipsisIcon,
6
6
  EyeIcon,
7
7
  FileIcon as LucideFileIcon,
8
+ StarIcon,
8
9
  Trash2Icon,
9
10
  } from "lucide-react";
10
11
  import { Pagination, type PaginationPage, type PaginationProps } from "@/components/ui/pagination";
@@ -135,6 +136,7 @@ export type DataTableSearch<Row extends TableRowBase> =
135
136
  };
136
137
 
137
138
  export type DataTablePagination = {
139
+ ariaLabel?: string;
138
140
  defaultPage?: number;
139
141
  manual?: boolean;
140
142
  onPageChange?: (page: number) => void;
@@ -216,7 +218,12 @@ export function Table<Row extends TableRowBase>({
216
218
  }
217
219
 
218
220
  return (
219
- <div className={`ds-table-wrap ds-table-wrap--${size}`} dir={dir}>
221
+ <div
222
+ aria-label="Scrollable table"
223
+ className={`ds-table-wrap ds-table-wrap--${size}`}
224
+ dir={dir}
225
+ tabIndex={0}
226
+ >
220
227
  <table className="ds-table">
221
228
  <thead>
222
229
  <tr>
@@ -238,18 +245,20 @@ export function Table<Row extends TableRowBase>({
238
245
  ) : column.header ? (
239
246
  column.sortable ? (
240
247
  onSortChange ? (
241
- <button
248
+ <Button
242
249
  aria-label={`Sort by ${column.header}`}
243
250
  className={cx(
244
251
  "ds-table__sort",
245
252
  sort?.columnId === column.id && `ds-table__sort--${sort.direction}`,
246
253
  )}
247
254
  onClick={() => onSortChange(getNextSort(sort, column.id))}
255
+ size="xs"
248
256
  type="button"
257
+ variant="ghost"
249
258
  >
250
259
  <span>{column.header}</span>
251
260
  <SortIcon direction={sort?.columnId === column.id ? sort.direction : undefined} />
252
- </button>
261
+ </Button>
253
262
  ) : (
254
263
  <span className="ds-table__sort ds-table__sort--static">
255
264
  <span>{column.header}</span>
@@ -259,7 +268,11 @@ export function Table<Row extends TableRowBase>({
259
268
  ) : (
260
269
  column.header
261
270
  )
262
- ) : null}
271
+ ) : (
272
+ <span className="sr-only">
273
+ {column.kind === "actions" ? "Actions" : column.id}
274
+ </span>
275
+ )}
263
276
  </th>
264
277
  ))}
265
278
  </tr>
@@ -283,7 +296,7 @@ export function Table<Row extends TableRowBase>({
283
296
  ) : column.renderCell ? (
284
297
  column.renderCell(row)
285
298
  ) : column.item ? (
286
- renderTableItem(column.item(row))
299
+ renderTableItem(column.item(row), size)
287
300
  ) : null}
288
301
  </td>
289
302
  ))}
@@ -492,6 +505,10 @@ export function DataTable<Row extends TableRowBase>({
492
505
  <footer className="ds-data-table__footer">
493
506
  <ToolbarText>{labels.resultsSummary({ from: visibleFrom, to: visibleTo, total: totalRows })}</ToolbarText>
494
507
  <Pagination
508
+ aria-label={
509
+ paginationConfig.ariaLabel ??
510
+ (typeof title === "string" ? `${title} pagination` : "Table pagination")
511
+ }
495
512
  dir={dir}
496
513
  nextDisabled={currentPage >= pageCount}
497
514
  onPageChange={setDataTablePage}
@@ -755,7 +772,7 @@ function clampNumber(value: number, min: number, max: number) {
755
772
  return Math.min(max, Math.max(min, Math.floor(value)));
756
773
  }
757
774
 
758
- function renderTableItem(item: TableItem) {
775
+ function renderTableItem(item: TableItem, tableSize: TableSize) {
759
776
  switch (item.type) {
760
777
  case "checkbox":
761
778
  return (
@@ -772,7 +789,7 @@ function renderTableItem(item: TableItem) {
772
789
  case "avatarText":
773
790
  return (
774
791
  <span className="ds-table-avatar-text">
775
- <Avatar name={item.name} src={item.src} />
792
+ <Avatar name={item.name} size={tableSize === "lg" ? "md" : "sm"} src={item.src} />
776
793
  <span>
777
794
  <strong>{item.name}</strong>
778
795
  {item.caption && <em>{item.caption}</em>}
@@ -796,7 +813,7 @@ function renderTableItem(item: TableItem) {
796
813
  case "badges":
797
814
  return <BadgeList items={item.items} maxVisible={item.maxVisible} />;
798
815
  case "onlyAvatar":
799
- return <Avatar name={item.alt} src={item.src} />;
816
+ return <Avatar name={item.alt} size={tableSize === "lg" ? "md" : "xs"} src={item.src} />;
800
817
  case "avatarStack":
801
818
  return (
802
819
  <AvatarStack
@@ -805,6 +822,7 @@ function renderTableItem(item: TableItem) {
805
822
  canAdd={item.canAdd}
806
823
  count={item.count}
807
824
  onAdd={item.onAdd}
825
+ size={tableSize}
808
826
  />
809
827
  );
810
828
  case "tags":
@@ -855,7 +873,9 @@ function SelectionCheckbox({
855
873
  );
856
874
  }
857
875
 
858
- function Avatar({ name, src }: { name: string; src?: string }) {
876
+ type TableAvatarSize = "xs" | "sm" | "md";
877
+
878
+ function Avatar({ name, size, src }: { name: string; size: TableAvatarSize; src?: string }) {
859
879
  const initials = name
860
880
  .split(" ")
861
881
  .map((part) => part[0])
@@ -864,9 +884,9 @@ function Avatar({ name, src }: { name: string; src?: string }) {
864
884
  .toUpperCase();
865
885
 
866
886
  return (
867
- <AvatarPrimitive className="ds-table-avatar" size="sm">
887
+ <AvatarPrimitive className="ds-table-avatar" size={size}>
868
888
  {src ? <AvatarImage alt="" src={src} /> : null}
869
- <AvatarFallback>{initials}</AvatarFallback>
889
+ <AvatarFallback size={size}>{initials}</AvatarFallback>
870
890
  </AvatarPrimitive>
871
891
  );
872
892
  }
@@ -877,23 +897,25 @@ function AvatarStack({
877
897
  canAdd,
878
898
  count,
879
899
  onAdd,
900
+ size,
880
901
  }: {
881
902
  addLabel?: string;
882
903
  avatars: AvatarItem[];
883
904
  canAdd?: boolean;
884
905
  count?: number;
885
906
  onAdd?: () => void;
907
+ size: TableSize;
886
908
  }) {
887
909
  return (
888
- <span className="ds-table-avatar-stack">
910
+ <span className={`ds-table-avatar-stack ds-table-avatar-stack--${size}`}>
889
911
  {avatars.slice(0, 3).map((avatar) => (
890
- <Avatar key={avatar.alt} name={avatar.alt} src={avatar.src} />
912
+ <Avatar key={avatar.alt} name={avatar.alt} size={size === "lg" ? "md" : "xs"} src={avatar.src} />
891
913
  ))}
892
914
  {count ? <span className="ds-table-avatar ds-table-avatar--count">+{count}</span> : null}
893
915
  {canAdd ? (
894
- <button aria-label={addLabel} disabled={!onAdd} onClick={onAdd} type="button">
916
+ <Button aria-label={addLabel} disabled={!onAdd} onClick={onAdd} size="icon-xs" type="button" variant="ghost">
895
917
  +
896
- </button>
918
+ </Button>
897
919
  ) : null}
898
920
  </span>
899
921
  );
@@ -938,10 +960,11 @@ function TagList({ items, maxVisible = items.length }: { items: string[]; maxVis
938
960
  function Progress({ label, tone, value }: { label?: string; tone: "success" | "brand"; value: number }) {
939
961
  return (
940
962
  <ProgressPrimitive
963
+ aria-label={label ?? "Progress"}
941
964
  className={`ds-table-progress ds-table-progress--${tone}`}
942
965
  indicator={label}
943
966
  labelPosition="inline"
944
- size="sm"
967
+ size="lg"
945
968
  value={value}
946
969
  />
947
970
  );
@@ -949,9 +972,18 @@ function Progress({ label, tone, value }: { label?: string; tone: "success" | "b
949
972
 
950
973
  function Rating({ max, value }: { max: number; value: number }) {
951
974
  return (
952
- <span className="ds-table-rating" aria-label={`${value} out of ${max}`}>
975
+ <span
976
+ aria-label={`${value} out of ${max}`}
977
+ className="ds-table-rating"
978
+ role="img"
979
+ >
953
980
  {Array.from({ length: max }, (_, index) => (
954
- <span className={index < value ? "is-filled" : undefined} key={index}>*</span>
981
+ <StarIcon
982
+ aria-hidden="true"
983
+ className={index < value ? "is-filled" : undefined}
984
+ key={index}
985
+ strokeWidth="var(--bh-icon-stroke-190)"
986
+ />
955
987
  ))}
956
988
  </span>
957
989
  );
@@ -8,11 +8,12 @@ export type TimelineItemState = "finished" | "active" | "inactive";
8
8
 
9
9
  export type TimelineStep = {
10
10
  bottomDate?: string;
11
- caption?: string;
11
+ caption?: ReactNode;
12
+ className?: string;
12
13
  date?: string;
13
14
  icon?: ReactNode;
14
15
  id?: string;
15
- label?: string;
16
+ label?: ReactNode;
16
17
  showBottomDate?: boolean;
17
18
  showCaption?: boolean;
18
19
  showLeadingDate?: boolean;
@@ -27,6 +28,7 @@ export type TimelineItemProps = TimelineStep & {
27
28
  className?: string;
28
29
  dir?: "ltr" | "rtl";
29
30
  orientation?: TimelineOrientation;
31
+ role?: "listitem" | "none" | "presentation";
30
32
  variant?: TimelineVariant;
31
33
  };
32
34
 
@@ -46,14 +48,12 @@ function classes(...names: Array<string | false | undefined>) {
46
48
  function Indicator({
47
49
  icon,
48
50
  orientation,
49
- rtl,
50
51
  showLine,
51
52
  state,
52
53
  variant,
53
54
  }: {
54
55
  icon?: ReactNode;
55
56
  orientation: TimelineOrientation;
56
- rtl: boolean;
57
57
  showLine: boolean;
58
58
  state: TimelineItemState;
59
59
  variant: TimelineVariant;
@@ -79,21 +79,11 @@ function Indicator({
79
79
  className={classes(
80
80
  "ds-timeline__indicator",
81
81
  `ds-timeline__indicator--${orientation}`,
82
- rtl && "ds-timeline__indicator--rtl",
83
82
  )}
84
83
  aria-hidden="true"
85
84
  >
86
- {orientation === "horizontal" && rtl ? (
87
- <>
88
- {line}
89
- {marker}
90
- </>
91
- ) : (
92
- <>
93
- {marker}
94
- {line}
95
- </>
96
- )}
85
+ {marker}
86
+ {line}
97
87
  </span>
98
88
  );
99
89
  }
@@ -107,6 +97,7 @@ export function TimelineItem({
107
97
  icon,
108
98
  label,
109
99
  orientation = "vertical",
100
+ role,
110
101
  showBottomDate = false,
111
102
  showCaption = true,
112
103
  showLeadingDate = true,
@@ -117,7 +108,6 @@ export function TimelineItem({
117
108
  topDate,
118
109
  variant = "default",
119
110
  }: TimelineItemProps) {
120
- const rtl = dir === "rtl";
121
111
  const displayDate = date;
122
112
  const displayLabel = label;
123
113
  const displayCaption = caption;
@@ -141,7 +131,6 @@ export function TimelineItem({
141
131
  <Indicator
142
132
  icon={icon}
143
133
  orientation={orientation}
144
- rtl={rtl}
145
134
  showLine={showLine}
146
135
  state={state}
147
136
  variant={variant}
@@ -159,11 +148,10 @@ export function TimelineItem({
159
148
  `ds-timeline-item--${orientation}`,
160
149
  `ds-timeline-item--${variant}`,
161
150
  `ds-timeline-item--${state}`,
162
- rtl ? "ds-timeline-item--rtl" : "ds-timeline-item--ltr",
163
151
  className,
164
152
  )}
165
153
  dir={dir}
166
- role="listitem"
154
+ role={role}
167
155
  >
168
156
  {leadingDate}
169
157
  {indicator}
@@ -173,15 +161,13 @@ export function TimelineItem({
173
161
  }
174
162
 
175
163
  export function Timeline({
176
- ariaLabel,
164
+ ariaLabel = "Timeline",
177
165
  className,
178
166
  dir,
179
167
  orientation = "vertical",
180
168
  steps = [],
181
169
  variant = "default",
182
170
  }: TimelineProps) {
183
- const rtl = dir === "rtl";
184
-
185
171
  return (
186
172
  <div
187
173
  aria-label={ariaLabel}
@@ -189,11 +175,11 @@ export function Timeline({
189
175
  "ds-timeline",
190
176
  `ds-timeline--${orientation}`,
191
177
  `ds-timeline--${variant}`,
192
- rtl ? "ds-timeline--rtl" : "ds-timeline--ltr",
193
178
  className,
194
179
  )}
195
180
  dir={dir}
196
181
  role="list"
182
+ tabIndex={orientation === "horizontal" ? 0 : undefined}
197
183
  >
198
184
  {steps.map((step, index) => (
199
185
  <TimelineItem
@@ -201,6 +187,7 @@ export function Timeline({
201
187
  key={step.id ?? index}
202
188
  dir={dir}
203
189
  orientation={orientation}
190
+ role="listitem"
204
191
  showTopDate={step.showTopDate ?? orientation === "horizontal"}
205
192
  showBottomDate={step.showBottomDate ?? false}
206
193
  showPaddingBottom={step.showPaddingBottom ?? orientation === "vertical"}
@@ -43,7 +43,7 @@
43
43
  text-decoration: none;
44
44
  }
45
45
 
46
- .ds-activity-feed--rtl {
46
+ .ds-activity-feed:dir(rtl) {
47
47
  font-family: var(--bh-font-family);
48
48
  margin-inline-end: auto;
49
49
  }
@@ -102,7 +102,7 @@
102
102
  gap: var(--activity-feed-space-lg);
103
103
  }
104
104
 
105
- .ds-activity-feed-item--rtl .ds-activity-feed-item__content {
105
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__content {
106
106
  align-items: flex-start;
107
107
  }
108
108
 
@@ -121,7 +121,7 @@
121
121
  gap: var(--bh-space-sm-6);
122
122
  }
123
123
 
124
- .ds-activity-feed-item--rtl .ds-activity-feed-item__topline {
124
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__topline {
125
125
  justify-content: flex-start;
126
126
  }
127
127
 
@@ -139,7 +139,7 @@
139
139
  word-break: break-word;
140
140
  }
141
141
 
142
- .ds-activity-feed-item--rtl .ds-activity-feed-item__text {
142
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__text {
143
143
  justify-content: flex-start;
144
144
  text-align: right;
145
145
  }
@@ -149,7 +149,7 @@
149
149
  font-weight: var(--bh-font-weight-medium);
150
150
  }
151
151
 
152
- .ds-activity-feed-item--rtl .ds-activity-feed-item__text strong {
152
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__text strong {
153
153
  font-weight: var(--bh-font-weight-semibold);
154
154
  }
155
155
 
@@ -199,7 +199,7 @@
199
199
  padding: var(--activity-feed-space-xl);
200
200
  }
201
201
 
202
- .ds-activity-feed-item--rtl .ds-activity-feed-item__caption {
202
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__caption {
203
203
  justify-content: flex-start;
204
204
  text-align: right;
205
205
  }
@@ -216,7 +216,7 @@
216
216
  line-height: var(--bh-text-body-sm-regular-line-height);
217
217
  }
218
218
 
219
- .ds-activity-feed-item--rtl .ds-activity-feed-item__slot {
219
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__slot {
220
220
  place-items: center start;
221
221
  text-align: right;
222
222
  }
@@ -230,7 +230,7 @@
230
230
  line-height: var(--bh-text-body-sm-regular-line-height);
231
231
  }
232
232
 
233
- .ds-activity-feed-item--rtl .ds-activity-feed-item__timestamp {
233
+ .ds-activity-feed-item:dir(rtl) .ds-activity-feed-item__timestamp {
234
234
  text-align: right;
235
235
  }
236
236
 
@@ -0,0 +1,66 @@
1
+ .ds-activity-heatmap {
2
+ --activity-heatmap-cell-size: var(--bh-space-3xl-16);
3
+ --activity-heatmap-gap: var(--bh-space-xs-4);
4
+ --activity-heatmap-radius: var(--bh-radius-sm-4);
5
+ --activity-heatmap-level-0: var(--bh-bg-neutral-medium);
6
+ --activity-heatmap-level-1: var(--bh-bg-accent-purple-subtle);
7
+ --activity-heatmap-level-2: var(--bh-bg-accent-purple-soft);
8
+ --activity-heatmap-level-3: var(--bh-bg-accent-purple-default);
9
+ --activity-heatmap-level-4: var(--bh-content-accent-purple-strong);
10
+
11
+ box-sizing: border-box;
12
+ width: 100%;
13
+ min-width: 0;
14
+ max-width: 100%;
15
+ overflow-x: auto;
16
+ overflow-y: hidden;
17
+ outline: none;
18
+ color: var(--bh-content-default);
19
+ font-family: var(--bh-font-family);
20
+ }
21
+
22
+ .ds-activity-heatmap:focus-visible {
23
+ box-shadow: var(--shadow-component-focus-ring);
24
+ }
25
+
26
+ .ds-activity-heatmap *,
27
+ .ds-activity-heatmap *::before,
28
+ .ds-activity-heatmap *::after {
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ .ds-activity-heatmap__grid {
33
+ display: grid;
34
+ grid-auto-flow: column;
35
+ grid-auto-columns: var(--activity-heatmap-cell-size);
36
+ grid-template-rows: repeat(7, var(--activity-heatmap-cell-size));
37
+ justify-content: start;
38
+ gap: var(--activity-heatmap-gap);
39
+ width: max-content;
40
+ min-width: 100%;
41
+ }
42
+
43
+ .ds-activity-heatmap__cell {
44
+ display: block;
45
+ inline-size: var(--activity-heatmap-cell-size);
46
+ block-size: var(--activity-heatmap-cell-size);
47
+ aspect-ratio: 1;
48
+ border-radius: var(--activity-heatmap-radius);
49
+ background: var(--activity-heatmap-level-0);
50
+ }
51
+
52
+ .ds-activity-heatmap__cell[data-level="1"] {
53
+ background: var(--activity-heatmap-level-1);
54
+ }
55
+
56
+ .ds-activity-heatmap__cell[data-level="2"] {
57
+ background: var(--activity-heatmap-level-2);
58
+ }
59
+
60
+ .ds-activity-heatmap__cell[data-level="3"] {
61
+ background: var(--activity-heatmap-level-3);
62
+ }
63
+
64
+ .ds-activity-heatmap__cell[data-level="4"] {
65
+ background: var(--activity-heatmap-level-4);
66
+ }