banhaten 0.1.0 → 0.1.1

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 (58) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/registry/components/accordion.tsx +37 -1
  4. package/registry/components/alert.tsx +14 -28
  5. package/registry/components/attribute.tsx +6 -10
  6. package/registry/components/avatar.tsx +1 -2
  7. package/registry/components/button-group.tsx +1 -1
  8. package/registry/components/card.tsx +1 -1
  9. package/registry/components/checkbox.tsx +19 -16
  10. package/registry/components/expanded/ActivityFeed.tsx +37 -23
  11. package/registry/components/expanded/Banner.tsx +54 -19
  12. package/registry/components/expanded/Breadcrumbs.tsx +10 -38
  13. package/registry/components/expanded/CatalogComponentsShowcase.tsx +11 -16
  14. package/registry/components/expanded/CatalogTag.tsx +4 -11
  15. package/registry/components/expanded/CommandBar.tsx +33 -53
  16. package/registry/components/expanded/FileUpload.tsx +362 -59
  17. package/registry/components/expanded/OnboardingStepListItem.tsx +6 -10
  18. package/registry/components/expanded/PageHeader.tsx +2 -11
  19. package/registry/components/expanded/Slideout.tsx +12 -23
  20. package/registry/components/expanded/Steps.tsx +6 -8
  21. package/registry/components/expanded/Table.tsx +18 -40
  22. package/registry/components/expanded/Timeline.tsx +5 -24
  23. package/registry/components/expanded/activityFeed.css +10 -54
  24. package/registry/components/expanded/banner.css +8 -75
  25. package/registry/components/expanded/breadcrumbs.css +1 -1
  26. package/registry/components/expanded/commandBar.css +23 -26
  27. package/registry/components/expanded/divider.css +1 -1
  28. package/registry/components/expanded/fileUpload.css +304 -75
  29. package/registry/components/expanded/pageHeader.css +1 -1
  30. package/registry/components/expanded/steps.css +15 -51
  31. package/registry/components/expanded/table.css +5 -1
  32. package/registry/components/expanded/timeline.css +18 -15
  33. package/registry/components/input.tsx +125 -54
  34. package/registry/components/menu.tsx +99 -72
  35. package/registry/components/pagination.tsx +6 -18
  36. package/registry/components/radio-card.tsx +25 -31
  37. package/registry/components/select-content.tsx +28 -123
  38. package/registry/components/select.tsx +9 -9
  39. package/registry/components/social-button.tsx +24 -90
  40. package/registry/components/spinner.tsx +21 -5
  41. package/registry/components/textarea.tsx +20 -35
  42. package/registry/components/toggle.tsx +7 -23
  43. package/registry/components/tooltip.tsx +4 -4
  44. package/registry/examples/attribute-demo.tsx +2 -2
  45. package/registry/examples/checkbox-demo.tsx +3 -8
  46. package/registry/examples/date-picker-demo.tsx +75 -22
  47. package/registry/examples/expanded/banner-demo.tsx +31 -6
  48. package/registry/examples/expanded/breadcrumbs-demo.tsx +59 -0
  49. package/registry/examples/expanded/file-upload-demo.tsx +60 -0
  50. package/registry/examples/expanded/steps-demo.tsx +11 -0
  51. package/registry/examples/expanded/table-demo.tsx +142 -0
  52. package/registry/examples/progress-demo.tsx +2 -2
  53. package/registry/examples/select-demo.tsx +32 -18
  54. package/registry/examples/social-button-demo.tsx +33 -33
  55. package/registry/examples/spinner-demo.tsx +37 -0
  56. package/registry/examples/tag-demo.tsx +1 -1
  57. package/registry/index.json +26 -12
  58. package/registry/styles/globals.css +5 -3
package/README.md CHANGED
@@ -53,7 +53,7 @@ The current scaffold includes:
53
53
  - A Figma-matched `tag` registry item with simple, dot, flag, avatar, and icon types, default/active/disabled states, native hover/focus-visible styling, xs/md/lg sizes, optional close button, and RTL-safe composition.
54
54
  - A Radix Switch-backed `toggle` registry item with sm/md geometry, active/disabled states, optional handle icons, labeled fields, supporting text, and RTL-safe label placement.
55
55
  - A Figma-matched `textarea` registry item with label, helper text, error, default and tags modes, placeholder, filled, disabled, and RTL states.
56
- - A Radix Select-backed, Figma-matched `select` registry item with default and soft triggers, error/filled/disabled states, menu surface, checkbox and check selection, and item media for text, users, flags, companies, payment methods, and status dots.
56
+ - A Radix Select-backed, Figma-matched `select` registry item with default and soft triggers, error/filled/disabled states, menu surface, checkbox and check selection, and item media for text, icons, users, companies, payment methods, and status dots.
57
57
  - A Figma-matched `date-picker` registry item with Calendar, Range Calendar, Date Picker, Date Range Picker, presets, time rows, open states, month/year view, and RTL.
58
58
  - A Radix Slider-backed `slider` registry item with single, range, multi-thumb, text/tooltip indicators, ticks, vertical layout, pill treatment, tones, and RTL behavior.
59
59
  - A read-only `progress` registry item with value meters, label placements, helper text, indicators, spinner, and RTL fill direction.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "banhaten",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Banhaten design system: an Arabic and English-first React design system with shadcn-style component installation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -67,11 +67,15 @@ function AccordionItem({
67
67
  function AccordionTrigger({
68
68
  className,
69
69
  children,
70
+ dir,
70
71
  ...props
71
72
  }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
73
+ const triggerDirection = dir ?? getTextDirection(children)
74
+
72
75
  return (
73
76
  <AccordionPrimitive.Header className="flex">
74
77
  <AccordionPrimitive.Trigger
78
+ dir={triggerDirection}
75
79
  data-slot="accordion-trigger"
76
80
  className={cn(
77
81
  "flex flex-1 items-start justify-between gap-[var(--bh-space-3xl-16)] rounded-[var(--bh-radius-md-6)] py-[var(--bh-space-3xl-16)] text-start text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-content-default)] outline-none transition-[background-color,color,box-shadow] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:opacity-[var(--bh-opacity-50)] data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--bh-opacity-50)] [&[data-state=open]>svg]:rotate-180 [&_svg]:size-[var(--bh-space-3xl-16)]",
@@ -79,7 +83,7 @@ function AccordionTrigger({
79
83
  )}
80
84
  {...props}
81
85
  >
82
- <span dir="auto" className="min-w-0">
86
+ <span dir="auto" className="min-w-0 flex-1 text-start">
83
87
  {children}
84
88
  </span>
85
89
  <ChevronDownIcon
@@ -110,6 +114,38 @@ function AccordionContent({
110
114
  )
111
115
  }
112
116
 
117
+ function getTextDirection(children: React.ReactNode) {
118
+ const text = getNodeText(children)
119
+
120
+ for (const character of text) {
121
+ if (/[\u0590-\u08ff]/.test(character)) {
122
+ return "rtl"
123
+ }
124
+
125
+ if (/[A-Za-z]/.test(character)) {
126
+ return "ltr"
127
+ }
128
+ }
129
+
130
+ return undefined
131
+ }
132
+
133
+ function getNodeText(node: React.ReactNode): string {
134
+ if (typeof node === "string" || typeof node === "number") {
135
+ return String(node)
136
+ }
137
+
138
+ if (Array.isArray(node)) {
139
+ return node.map(getNodeText).join("")
140
+ }
141
+
142
+ if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
143
+ return getNodeText(node.props.children)
144
+ }
145
+
146
+ return ""
147
+ }
148
+
113
149
  export {
114
150
  Accordion,
115
151
  AccordionItem,
@@ -1,5 +1,13 @@
1
1
  import * as React from "react"
2
2
  import { cva, type VariantProps } from "class-variance-authority"
3
+ import {
4
+ CircleAlertIcon,
5
+ CircleCheckIcon,
6
+ InfoIcon,
7
+ StarIcon,
8
+ TriangleAlertIcon,
9
+ XIcon,
10
+ } from "lucide-react"
3
11
 
4
12
  import { cn } from "@/lib/utils"
5
13
 
@@ -226,49 +234,27 @@ function AlertActions({ actions }: { actions: AlertAction[] }) {
226
234
 
227
235
  function AlertStatusIcon({ status }: { status: AlertStatus }) {
228
236
  if (status === "success") {
229
- return (
230
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 16.6667 16.6667">
231
- <path d="M8.33333 16.6667C12.9357 16.6667 16.6667 12.9357 16.6667 8.33333C16.6667 3.73096 12.9357 0 8.33333 0C3.73096 0 0 3.73096 0 8.33333C0 12.9357 3.73096 16.6667 8.33333 16.6667ZM12.8809 6.21426L7.5 11.5952L3.99407 8.08925L5.17259 6.91075L7.5 9.23817L11.7024 5.03574L12.8809 6.21426Z" />
232
- </svg>
233
- )
237
+ return <CircleCheckIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
234
238
  }
235
239
 
236
240
  if (status === "warning") {
237
- return (
238
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 17.544 15.4168">
239
- <path d="M9.49356 0.416809L17.4321 14.1668C17.6623 14.5654 17.5257 15.0751 17.1271 15.3052C17.0005 15.3783 16.8567 15.4168 16.7105 15.4168H0.833333C0.3731 15.4168 0 15.0438 0 14.5835C0 14.4372 0.0385084 14.2935 0.11165 14.1668L8.05022 0.416809C8.28031 0.0182258 8.78997 -0.118333 9.18856 0.111784C9.31522 0.184926 9.42047 0.290126 9.49356 0.416809ZM7.93856 11.2502V12.9168H9.60522V11.2502H7.93856ZM7.93856 5.41681V9.5835H9.60522V5.41681H7.93856Z" />
240
- </svg>
241
- )
241
+ return <TriangleAlertIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
242
242
  }
243
243
 
244
244
  if (status === "info") {
245
- return (
246
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 16.6667 16.6667">
247
- <path d="M8.33333 16.6667C3.73096 16.6667 0 12.9357 0 8.33333C0 3.73096 3.73096 0 8.33333 0C12.9357 0 16.6667 3.73096 16.6667 8.33333C16.6667 12.9357 12.9357 16.6667 8.33333 16.6667ZM8.33333 6.25C9.02367 6.25 9.58333 5.69036 9.58333 5C9.58333 4.30964 9.02367 3.75 8.33333 3.75C7.643 3.75 7.08333 4.30964 7.08333 5C7.08333 5.69036 7.643 6.25 8.33333 6.25ZM10 10.8333H9.16667V7.08333H6.66667V8.75H7.5V10.8333H6.66667V12.5H10V10.8333Z" />
248
- </svg>
249
- )
245
+ return <InfoIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
250
246
  }
251
247
 
252
248
  if (status === "neutral") {
253
- return (
254
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 15.851 15.0752">
255
- <path d="M7.92545 12.0833L3.02726 15.0752L4.35901 9.49217L0 5.75819L5.72128 5.29952L7.92545 0L10.1297 5.29952L15.851 5.75819L11.492 9.49217L12.8237 15.0752L7.92545 12.0833Z" />
256
- </svg>
257
- )
249
+ return <StarIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
258
250
  }
259
251
 
260
- return (
261
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 16.6667 16.6667">
262
- <path d="M8.33333 16.6667C3.73096 16.6667 0 12.9357 0 8.33333C0 3.73096 3.73096 0 8.33333 0C12.9357 0 16.6667 3.73096 16.6667 8.33333C16.6667 12.9357 12.9357 16.6667 8.33333 16.6667ZM7.5 10.8333V12.5H9.16667V10.8333H7.5ZM7.5 4.16667V9.16667H9.16667V4.16667H7.5Z" />
263
- </svg>
264
- )
252
+ return <CircleAlertIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
265
253
  }
266
254
 
267
255
  function CloseIcon() {
268
256
  return (
269
- <svg aria-hidden="true" className="size-[var(--bh-alert-icon-size)] fill-current" viewBox="0 0 16 16">
270
- <path d="M8.00004 7.05723L11.2999 3.75739L12.2427 4.7002L8.94284 8.00003L12.2427 11.2998L11.2999 12.2427L8.00004 8.94283L4.70021 12.2427L3.75739 11.2998L7.05723 8.00003L3.75739 4.7002L4.70021 3.75739L8.00004 7.05723Z" />
271
- </svg>
257
+ <XIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth={2.25} />
272
258
  )
273
259
  }
274
260
 
@@ -3,9 +3,9 @@ import { cva, type VariantProps } from "class-variance-authority"
3
3
 
4
4
  import { cn } from "@/lib/utils"
5
5
 
6
- type AttributeLayout = "inline" | "end" | "stacked" | "two-column"
6
+ type AttributeLayout = "inline" | "stacked" | "two-column"
7
7
  type AttributeDensity = "compact" | "default" | "comfortable"
8
- type AttributeAlign = "start" | "end" | "stretch"
8
+ type AttributeAlign = "start" | "stretch"
9
9
  type AttributeValueDir = "auto" | "ltr" | "rtl"
10
10
 
11
11
  type AttributeContextValue = {
@@ -70,13 +70,13 @@ const attributeListVariants = cva("grid min-w-0", {
70
70
  variants: {
71
71
  layout: {
72
72
  inline: "grid-cols-1",
73
- end: "grid-cols-1",
74
73
  stacked: "grid-cols-1",
75
74
  "two-column":
76
75
  "grid-cols-1 gap-x-[var(--bh-space-6xl-32)] sm:grid-cols-2",
77
76
  },
78
77
  bordered: {
79
- true: "overflow-hidden rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-subtle)]",
78
+ true:
79
+ "overflow-hidden rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-subtle)] [&>[data-slot='attribute-item']]:px-[var(--bh-space-3xl-16)] sm:[&>[data-slot='attribute-item']]:px-[var(--bh-space-4xl-20)]",
80
80
  false: "",
81
81
  },
82
82
  },
@@ -91,8 +91,6 @@ const attributeItemVariants = cva("min-w-0", {
91
91
  layout: {
92
92
  inline:
93
93
  "grid grid-cols-1 items-center gap-x-[var(--bh-space-xs-4)] sm:grid-cols-[var(--bh-attribute-label-width)_minmax(0,1fr)]",
94
- end:
95
- "grid grid-cols-1 items-center gap-x-[var(--bh-space-xs-4)] sm:grid-cols-[var(--bh-attribute-label-width)_minmax(0,1fr)]",
96
94
  stacked:
97
95
  "flex flex-col items-start justify-center gap-[var(--bh-space-xs-4)]",
98
96
  "two-column":
@@ -100,7 +98,7 @@ const attributeItemVariants = cva("min-w-0", {
100
98
  },
101
99
  density: {
102
100
  compact: "py-[var(--bh-space-md-8)]",
103
- default: "py-[var(--bh-space-xl-12)] sm:py-[var(--bh-space-3xl-16)]",
101
+ default: "py-[var(--bh-space-3xl-16)]",
104
102
  comfortable: "py-[var(--bh-space-3xl-16)]",
105
103
  },
106
104
  divided: {
@@ -154,7 +152,6 @@ const attributeValueVariants = cva(
154
152
  variants: {
155
153
  align: {
156
154
  start: "justify-self-start text-start",
157
- end: "justify-self-end justify-end text-end rtl:justify-self-start rtl:justify-start rtl:text-right",
158
155
  stretch: "w-full justify-self-stretch text-start",
159
156
  },
160
157
  truncate: {
@@ -323,8 +320,7 @@ const AttributeItem = React.forwardRef<HTMLDivElement, AttributeItemProps>(
323
320
  const selectedLayout = layout || context.layout
324
321
  const selectedDensity = density || context.density
325
322
  const selectedDivided = divided ?? context.dividers
326
- const selectedAlign =
327
- valueAlign || (selectedLayout === "end" ? "end" : "start")
323
+ const selectedAlign = valueAlign || "start"
328
324
 
329
325
  return (
330
326
  <div
@@ -119,14 +119,13 @@ function AvatarGroupCount({
119
119
  return (
120
120
  <span
121
121
  data-slot="avatar-group-count"
122
- dir={dir}
123
122
  className={cn(
124
123
  "-ms-[var(--bh-space-md-8)] flex size-[var(--bh-space-7xl-40)] items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-content-default)] ring-[var(--bh-space-xxs-2)] ring-[var(--bh-bg-default)]",
125
124
  className
126
125
  )}
127
126
  {...props}
128
127
  >
129
- {children}
128
+ <span dir={dir}>{children}</span>
130
129
  </span>
131
130
  )
132
131
  }
@@ -18,7 +18,7 @@ const buttonGroupContext = React.createContext<ButtonGroupContextValue>({
18
18
  })
19
19
 
20
20
  const buttonGroupVariants = cva(
21
- "inline-flex items-center overflow-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--shadow-button-group)] [--shadow-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--shadow-component-default)]",
21
+ "inline-flex w-fit items-center overflow-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--shadow-button-group)] [--shadow-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--shadow-component-default)]",
22
22
  {
23
23
  variants: {
24
24
  size: {
@@ -393,7 +393,7 @@ function CardBody({
393
393
  </div>
394
394
 
395
395
  {showFooter ? (
396
- <CardFooter>
396
+ <CardFooter className={horizontal ? "mt-auto" : undefined}>
397
397
  {footer ??
398
398
  actions ?? (
399
399
  <CardActions
@@ -3,6 +3,7 @@
3
3
  import * as React from "react"
4
4
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
5
  import { cva, type VariantProps } from "class-variance-authority"
6
+ import { CheckIcon, MinusIcon } from "lucide-react"
6
7
 
7
8
  import { cn } from "@/lib/utils"
8
9
 
@@ -58,8 +59,18 @@ const Checkbox = React.forwardRef<
58
59
  data-slot="checkbox-indicator"
59
60
  className="pointer-events-none absolute top-1/2 left-1/2 flex size-[var(--bh-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 items-center justify-center text-[var(--bh-content-on-color)] group-data-[disabled]/checkbox:text-[var(--bh-content-disabled)]"
60
61
  >
61
- <CheckFillIcon className="size-[var(--bh-checkbox-icon-size)] fill-current group-data-[state=indeterminate]/checkbox:hidden" />
62
- <span className="hidden h-[var(--bh-checkbox-indeterminate-height)] w-[var(--bh-checkbox-indeterminate-width)] rounded-full bg-current group-data-[state=indeterminate]/checkbox:block" />
62
+ <CheckIcon
63
+ aria-hidden="true"
64
+ className="size-[var(--bh-checkbox-icon-size)] group-data-[state=indeterminate]/checkbox:hidden"
65
+ focusable="false"
66
+ strokeWidth={3}
67
+ />
68
+ <MinusIcon
69
+ aria-hidden="true"
70
+ className="hidden size-[var(--bh-checkbox-icon-size)] group-data-[state=indeterminate]/checkbox:block"
71
+ focusable="false"
72
+ strokeWidth={3}
73
+ />
63
74
  </CheckboxPrimitive.Indicator>
64
75
  </CheckboxPrimitive.Root>
65
76
  )
@@ -216,25 +227,17 @@ function CheckboxCardIndicator({
216
227
  className="pointer-events-none absolute top-1/2 left-1/2 size-[var(--bh-checkbox-control-size)] -translate-x-1/2 -translate-y-1/2 rounded-[var(--bh-checkbox-control-radius)] bg-[var(--bh-bg-default)] shadow-[var(--shadow-checkbox-control)] [--bh-checkbox-control-border:var(--bh-border-input)] [--shadow-checkbox-control:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-checkbox-control-border,var(--bh-border-input)),var(--shadow-component-default)] data-[state=checked]:[--bh-checkbox-control-border:transparent] data-[state=checked]:bg-[var(--bh-interactive-checkbox-default)] data-[disabled=true]:bg-[var(--bh-bg-disabled)] data-[disabled=true]:shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]"
217
228
  />
218
229
  {checked ? (
219
- <CheckFillIcon className="absolute top-1/2 left-1/2 size-[var(--bh-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 fill-current text-[var(--bh-content-on-color)]" />
230
+ <CheckIcon
231
+ aria-hidden="true"
232
+ className="absolute top-1/2 left-1/2 size-[var(--bh-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 text-[var(--bh-content-on-color)]"
233
+ focusable="false"
234
+ strokeWidth={3}
235
+ />
220
236
  ) : null}
221
237
  </span>
222
238
  )
223
239
  }
224
240
 
225
- function CheckFillIcon({ className }: { className?: string }) {
226
- return (
227
- <svg
228
- aria-hidden="true"
229
- className={className}
230
- focusable="false"
231
- viewBox="0 0 24 24"
232
- >
233
- <path d="M9.9997 15.1709L19.1921 5.97852L20.6063 7.39273L9.9997 17.9993L3.63574 11.6354L5.04996 10.2212L9.9997 15.1709Z" />
234
- </svg>
235
- )
236
- }
237
-
238
241
  export { Checkbox, CheckboxCard, checkboxCardVariants, checkboxVariants }
239
242
  export type {
240
243
  CheckboxCardControlPosition,
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
+ import { Button } from "@/components/ui/button";
2
3
  import "./activityFeed.css";
3
4
 
4
5
  const defaultAvatarSrc = new URL("../assets/activity-feed-avatar.png", import.meta.url).href;
@@ -85,26 +86,48 @@ function Indicator({
85
86
  }
86
87
 
87
88
  function ActionButton({ action }: { action: ActivityFeedAction }) {
88
- const className = classes("ds-activity-feed-item__button", `ds-activity-feed-item__button--${action.variant ?? "secondary"}`);
89
+ const variant = action.variant === "primary" ? "default" : "outline";
89
90
 
90
91
  if (action.href) {
91
92
  return (
92
- <a aria-label={action.ariaLabel} className={className} href={action.href}>
93
- {action.label}
94
- </a>
93
+ <Button
94
+ aria-label={action.ariaLabel}
95
+ asChild
96
+ className="ds-activity-feed-item__button"
97
+ size="sm"
98
+ variant={variant}
99
+ >
100
+ <a
101
+ aria-disabled={action.disabled || undefined}
102
+ href={action.disabled ? undefined : action.href}
103
+ onClick={(event) => {
104
+ if (action.disabled) {
105
+ event.preventDefault();
106
+ return;
107
+ }
108
+
109
+ action.onClick?.();
110
+ }}
111
+ tabIndex={action.disabled ? -1 : undefined}
112
+ >
113
+ {action.label}
114
+ </a>
115
+ </Button>
95
116
  );
96
117
  }
97
118
 
98
119
  return (
99
- <button
120
+ <Button
100
121
  aria-label={action.ariaLabel}
101
- className={className}
122
+ className="ds-activity-feed-item__button"
102
123
  disabled={action.disabled}
103
124
  onClick={action.onClick}
125
+ size="sm"
104
126
  type="button"
127
+ variant={variant}
105
128
  >
106
129
  {action.label}
107
- </button>
130
+ </Button>
108
131
  );
109
132
  }
110
133
 
@@ -135,17 +158,11 @@ export function ActivityFeedItem({
135
158
  const rtl = dir === "rtl";
136
159
  const nodeId = rtl ? (type === "caption" ? "587:18720" : "587:18701") : type === "caption" ? "584:18591" : "584:10411";
137
160
 
138
- const textParts = rtl
139
- ? [
140
- hasLink && link && <span className="ds-activity-feed-item__link" key="link">{link}</span>,
141
- hasSupportText && supportText && <span className="ds-activity-feed-item__support" key="support">{supportText}</span>,
142
- label && <strong key="label">{label}</strong>,
143
- ]
144
- : [
145
- label && <strong key="label">{label}</strong>,
146
- hasSupportText && supportText && <span className="ds-activity-feed-item__support" key="support">{supportText}</span>,
147
- hasLink && link && <span className="ds-activity-feed-item__link" key="link">{link}</span>,
148
- ];
161
+ const textParts = [
162
+ label && <strong key="label">{label}</strong>,
163
+ hasSupportText && supportText && <span className="ds-activity-feed-item__support" key="support">{supportText}</span>,
164
+ hasLink && link && <span className="ds-activity-feed-item__link" key="link">{link}</span>,
165
+ ];
149
166
 
150
167
  return (
151
168
  <article
@@ -158,14 +175,13 @@ export function ActivityFeedItem({
158
175
  data-node-id={nodeId}
159
176
  dir={rtl ? "rtl" : "ltr"}
160
177
  >
161
- {!rtl && <Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} rtl={rtl} showLine={showLine} type={type} />}
178
+ <Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} rtl={rtl} showLine={showLine} type={type} />
162
179
 
163
180
  <div className="ds-activity-feed-item__content">
164
181
  <div className="ds-activity-feed-item__container">
165
182
  <div className="ds-activity-feed-item__topline">
166
- {rtl && hasStatus && <StatusDot />}
167
183
  <div className="ds-activity-feed-item__text">{textParts}</div>
168
- {!rtl && hasStatus && <StatusDot />}
184
+ {hasStatus && <StatusDot />}
169
185
  </div>
170
186
 
171
187
  {type === "slot" ? (
@@ -191,8 +207,6 @@ export function ActivityFeedItem({
191
207
 
192
208
  {showPaddingBottom && <div className="ds-activity-feed-item__bottom-space" aria-hidden="true" />}
193
209
  </div>
194
-
195
- {rtl && <Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} rtl={rtl} showLine={showLine} type={type} />}
196
210
  </article>
197
211
  );
198
212
  }
@@ -1,4 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
+ import { MegaphoneIcon as LucideMegaphoneIcon, XIcon } from "lucide-react";
3
+
4
+ import { Button, type ButtonProps } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+
2
7
  import "./banner.css";
3
8
 
4
9
  export type BannerType = "slim" | "single-action-inline" | "single-action" | "input";
@@ -30,19 +35,17 @@ export type BannerProps = {
30
35
  };
31
36
 
32
37
  function MegaphoneIcon() {
33
- return (
34
- <svg aria-hidden="true" className="ds-banner__icon" viewBox="0 0 24 24">
35
- <path d="M3 10.5v3a2.5 2.5 0 0 0 2.5 2.5h.63l1.15 3.44A1.5 1.5 0 0 0 8.7 20.5h1.1a1 1 0 0 0 .95-1.32L9.7 16H11l7.24 3.1A1.25 1.25 0 0 0 20 17.95V6.05a1.25 1.25 0 0 0-1.76-1.14L11 8H5.5A2.5 2.5 0 0 0 3 10.5Zm15 6.69-6-2.58V9.39l6-2.58v10.38ZM5 10.5A.5.5 0 0 1 5.5 10H10v4H5.5a.5.5 0 0 1-.5-.5v-3Z" />
36
- </svg>
37
- );
38
+ return <LucideMegaphoneIcon aria-hidden="true" className="ds-banner__icon" strokeWidth={2.1} />;
38
39
  }
39
40
 
40
41
  function CloseIcon() {
41
- return (
42
- <svg aria-hidden="true" className="ds-banner__close-icon" viewBox="0 0 24 24">
43
- <path d="m12 10.59 5.3-5.3 1.41 1.42-5.3 5.29 5.3 5.3-1.41 1.41-5.3-5.3-5.29 5.3-1.42-1.41 5.3-5.3-5.3-5.29 1.42-1.42 5.29 5.3Z" />
44
- </svg>
45
- );
42
+ return <XIcon aria-hidden="true" className="ds-banner__close-icon" strokeWidth={2.25} />;
43
+ }
44
+
45
+ function getBannerActionVariant(color: BannerColor): ButtonProps["variant"] {
46
+ if (color === "brand-light") return "default";
47
+ if (color === "grey") return "secondary";
48
+ return "white";
46
49
  }
47
50
 
48
51
  function cx(...classes: Array<string | false | undefined>) {
@@ -75,10 +78,18 @@ export function Banner({
75
78
  const isInlineAction = type === "slim" || type === "single-action-inline";
76
79
  const inlineActionLabel = type === "single-action-inline" ? actionLabel : linkLabel;
77
80
  const hasInlineAction = isInlineAction && (showInlineAction ?? true) && Boolean(inlineActionLabel);
81
+ const actionVariant = getBannerActionVariant(color);
78
82
  const closeButton = (
79
- <button className="ds-banner__close" onClick={onClose} type="button" aria-label={closeLabel}>
83
+ <Button
84
+ aria-label={closeLabel ?? "Dismiss banner"}
85
+ className="ds-banner__close"
86
+ onClick={onClose}
87
+ size="icon-xs"
88
+ type="button"
89
+ variant="ghost"
90
+ >
80
91
  <CloseIcon />
81
- </button>
92
+ </Button>
82
93
  );
83
94
 
84
95
  return (
@@ -110,18 +121,30 @@ export function Banner({
110
121
  </div>
111
122
 
112
123
  {hasInlineAction && (
113
- <button className="ds-banner__link" onClick={onAction} type="button">
124
+ <Button
125
+ className="ds-banner__link"
126
+ onClick={onAction}
127
+ size="sm"
128
+ type="button"
129
+ variant={type === "single-action-inline" ? actionVariant : "link"}
130
+ >
114
131
  {inlineActionLabel}
115
- </button>
132
+ </Button>
116
133
  )}
117
134
  </div>
118
135
 
119
136
  {type === "single-action" && (
120
137
  <div className="ds-banner__tail">
121
138
  {actionLabel && (
122
- <button className="ds-banner__action" onClick={onAction} type="button">
139
+ <Button
140
+ className="ds-banner__action"
141
+ onClick={onAction}
142
+ size="sm"
143
+ type="button"
144
+ variant={actionVariant}
145
+ >
123
146
  {actionLabel}
124
- </button>
147
+ </Button>
125
148
  )}
126
149
  {closeButton}
127
150
  </div>
@@ -129,11 +152,23 @@ export function Banner({
129
152
 
130
153
  {type === "input" && (
131
154
  <div className="ds-banner__tail ds-banner__tail--input">
132
- <input aria-label={inputAriaLabel} className="ds-banner__input" placeholder={inputPlaceholder} type="text" />
155
+ <Input
156
+ aria-label={inputAriaLabel}
157
+ className="ds-banner__input"
158
+ hasInformationIcon={false}
159
+ placeholder={inputPlaceholder}
160
+ size="md"
161
+ />
133
162
  {actionLabel && (
134
- <button className="ds-banner__action" onClick={onAction} type="button">
163
+ <Button
164
+ className="ds-banner__action"
165
+ onClick={onAction}
166
+ size="sm"
167
+ type="button"
168
+ variant={actionVariant}
169
+ >
135
170
  {actionLabel}
136
- </button>
171
+ </Button>
137
172
  )}
138
173
  {closeButton}
139
174
  </div>
@@ -1,4 +1,10 @@
1
1
  import type { ReactNode } from "react";
2
+ import {
3
+ ChevronRightIcon,
4
+ EllipsisIcon,
5
+ FolderIcon,
6
+ HomeIcon,
7
+ } from "lucide-react";
2
8
  import "./breadcrumbs.css";
3
9
 
4
10
  export type BreadcrumbSeparator = "slash" | "chevron";
@@ -106,51 +112,17 @@ function Separator({ separator }: { separator: BreadcrumbSeparator }) {
106
112
  }
107
113
 
108
114
  export function BreadcrumbHomeIcon() {
109
- return (
110
- <svg aria-hidden="true" viewBox="0 0 20 20">
111
- <path
112
- d="M3.5 8.7 10 3.4l6.5 5.3v7.1a.9.9 0 0 1-.9.9h-3.2v-4.9H7.6v4.9H4.4a.9.9 0 0 1-.9-.9V8.7Z"
113
- fill="none"
114
- stroke="currentColor"
115
- strokeLinejoin="round"
116
- strokeWidth="1.7"
117
- />
118
- </svg>
119
- );
115
+ return <HomeIcon aria-hidden="true" strokeWidth={1.9} />;
120
116
  }
121
117
 
122
118
  export function BreadcrumbFolderIcon() {
123
- return (
124
- <svg aria-hidden="true" viewBox="0 0 20 20">
125
- <path
126
- d="M2.8 5.7c0-.9.7-1.6 1.6-1.6h3.8l1.5 1.8h5.9c.9 0 1.6.7 1.6 1.6v6.8c0 .9-.7 1.6-1.6 1.6H4.4c-.9 0-1.6-.7-1.6-1.6V5.7Z"
127
- fill="none"
128
- stroke="currentColor"
129
- strokeLinejoin="round"
130
- strokeWidth="1.7"
131
- />
132
- </svg>
133
- );
119
+ return <FolderIcon aria-hidden="true" strokeWidth={1.9} />;
134
120
  }
135
121
 
136
122
  function ChevronIcon() {
137
- return (
138
- <svg aria-hidden="true" viewBox="0 0 16 16">
139
- <path d="m6 3.5 4 4.5-4 4.5" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.6" />
140
- </svg>
141
- );
123
+ return <ChevronRightIcon aria-hidden="true" strokeWidth={2} />;
142
124
  }
143
125
 
144
126
  function OverflowIcon() {
145
- return (
146
- <svg aria-hidden="true" viewBox="0 0 20 20">
147
- <path
148
- d="M5.2 10h.1M10 10h.1M14.8 10h.1"
149
- fill="none"
150
- stroke="currentColor"
151
- strokeLinecap="round"
152
- strokeWidth="2.4"
153
- />
154
- </svg>
155
- );
127
+ return <EllipsisIcon aria-hidden="true" strokeWidth={2.25} />;
156
128
  }