banhaten 0.1.0 → 0.1.2

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 (81) hide show
  1. package/README.md +21 -9
  2. package/package.json +8 -2
  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/autocomplete.tsx +637 -0
  7. package/registry/components/avatar.tsx +259 -24
  8. package/registry/components/badge.tsx +97 -35
  9. package/registry/components/button-group.tsx +1 -1
  10. package/registry/components/card.tsx +1 -1
  11. package/registry/components/checkbox.tsx +19 -16
  12. package/registry/components/date-picker-state.ts +253 -0
  13. package/registry/components/date-picker.tsx +115 -158
  14. package/registry/components/expanded/ActivityFeed.tsx +37 -23
  15. package/registry/components/expanded/Banner.tsx +54 -19
  16. package/registry/components/expanded/Breadcrumbs.tsx +10 -38
  17. package/registry/components/expanded/CatalogComponentsShowcase.tsx +11 -16
  18. package/registry/components/expanded/CatalogTag.tsx +4 -11
  19. package/registry/components/expanded/CommandBar.tsx +33 -53
  20. package/registry/components/expanded/EmptyState.tsx +155 -0
  21. package/registry/components/expanded/FileUpload.tsx +362 -59
  22. package/registry/components/expanded/OnboardingStepListItem.tsx +6 -10
  23. package/registry/components/expanded/PageHeader.tsx +2 -11
  24. package/registry/components/expanded/Slideout.tsx +12 -23
  25. package/registry/components/expanded/Steps.tsx +6 -8
  26. package/registry/components/expanded/Table.tsx +18 -40
  27. package/registry/components/expanded/Timeline.tsx +5 -24
  28. package/registry/components/expanded/activityFeed.css +10 -54
  29. package/registry/components/expanded/banner.css +8 -75
  30. package/registry/components/expanded/breadcrumbs.css +1 -1
  31. package/registry/components/expanded/commandBar.css +23 -26
  32. package/registry/components/expanded/divider.css +1 -1
  33. package/registry/components/expanded/emptyState.css +111 -0
  34. package/registry/components/expanded/fileUpload.css +304 -75
  35. package/registry/components/expanded/pageHeader.css +1 -1
  36. package/registry/components/expanded/slideout.css +1 -0
  37. package/registry/components/expanded/steps.css +15 -51
  38. package/registry/components/expanded/table.css +6 -1
  39. package/registry/components/expanded/timeline.css +18 -15
  40. package/registry/components/input-otp.tsx +574 -0
  41. package/registry/components/input.tsx +140 -59
  42. package/registry/components/menu.tsx +470 -80
  43. package/registry/components/pagination.tsx +6 -18
  44. package/registry/components/popover.tsx +840 -0
  45. package/registry/components/radio-card.tsx +25 -31
  46. package/registry/components/select-content.tsx +28 -123
  47. package/registry/components/select.tsx +13 -9
  48. package/registry/components/skeleton.css +57 -0
  49. package/registry/components/skeleton.tsx +482 -0
  50. package/registry/components/social-button.tsx +24 -90
  51. package/registry/components/spinner.tsx +91 -7
  52. package/registry/components/textarea.tsx +21 -36
  53. package/registry/components/toggle.tsx +7 -23
  54. package/registry/components/tooltip.tsx +8 -4
  55. package/registry/examples/attribute-demo.tsx +2 -2
  56. package/registry/examples/autocomplete-demo.tsx +109 -0
  57. package/registry/examples/avatar-demo.tsx +102 -47
  58. package/registry/examples/badge-demo.tsx +16 -0
  59. package/registry/examples/checkbox-demo.tsx +3 -8
  60. package/registry/examples/date-picker-demo.tsx +75 -22
  61. package/registry/examples/expanded/banner-demo.tsx +31 -6
  62. package/registry/examples/expanded/breadcrumbs-demo.tsx +59 -0
  63. package/registry/examples/expanded/command-bar-demo.tsx +236 -0
  64. package/registry/examples/expanded/empty-state-demo.tsx +39 -0
  65. package/registry/examples/expanded/file-upload-demo.tsx +60 -0
  66. package/registry/examples/expanded/steps-demo.tsx +11 -0
  67. package/registry/examples/expanded/table-demo.tsx +142 -0
  68. package/registry/examples/input-demo.tsx +1 -1
  69. package/registry/examples/input-otp-demo.tsx +72 -0
  70. package/registry/examples/menu-demo.tsx +101 -88
  71. package/registry/examples/popover-demo.tsx +546 -0
  72. package/registry/examples/progress-demo.tsx +2 -2
  73. package/registry/examples/select-demo.tsx +32 -18
  74. package/registry/examples/skeleton-demo.tsx +56 -0
  75. package/registry/examples/social-button-demo.tsx +33 -33
  76. package/registry/examples/spinner-demo.tsx +59 -0
  77. package/registry/examples/tag-demo.tsx +1 -1
  78. package/registry/examples/textarea-demo.tsx +1 -1
  79. package/registry/index.json +266 -20
  80. package/registry/styles/globals.css +93 -3
  81. package/src/cli/index.js +997 -62
@@ -1,53 +1,85 @@
1
- import { useRef, useState, type ChangeEvent, type DragEvent } from "react";
1
+ import { useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type MouseEvent, type ReactNode } from "react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+
2
5
  import "./fileUpload.css";
3
6
 
4
7
  export type FileUploadSize = "lg" | "sm";
5
- export type FileUploadItemStatus = "uploaded" | "uploading" | "error";
8
+ export type FileUploadVariant = "default" | "button";
9
+ export type FileUploadDropzoneState = "default" | "hover";
10
+ export type FileUploadItemStatus = "uploaded" | "uploading" | "uploading-progress" | "error";
6
11
 
7
12
  export type FileUploadFile = {
8
13
  errorLabel?: string;
14
+ extensionLabel?: string;
9
15
  id: string;
10
16
  name: string;
17
+ progress?: number;
18
+ progressLabel?: string;
11
19
  sizeLabel: string;
12
20
  status: FileUploadItemStatus;
21
+ uploadedLabel?: string;
22
+ uploadLabel?: string;
13
23
  versionLabel?: string;
14
24
  };
15
25
 
16
26
  export type FileUploadProps = {
17
27
  accept?: string;
18
28
  browseLabel?: string;
29
+ cancelLabel?: string;
19
30
  dir?: "ltr" | "rtl" | "auto";
20
31
  disabled?: boolean;
32
+ dropzoneState?: FileUploadDropzoneState;
21
33
  files?: FileUploadFile[];
22
34
  helperText?: string;
35
+ moreLabel?: string;
23
36
  multiple?: boolean;
24
37
  onBrowse?: () => void;
25
38
  onCancel?: (fileId: string) => void;
26
39
  onDrop?: (files: FileList) => void;
40
+ onMore?: (fileId: string) => void;
27
41
  onRemove?: (fileId: string) => void;
28
42
  onRetry?: (fileId: string) => void;
29
43
  prompt?: string;
44
+ removeLabel?: string;
45
+ retryLabel?: string;
30
46
  size?: FileUploadSize;
47
+ title?: string;
48
+ variant?: FileUploadVariant;
31
49
  };
32
50
 
33
51
  export function FileUpload({
34
52
  accept,
35
53
  browseLabel,
54
+ cancelLabel = "Cancel upload",
36
55
  dir = "ltr",
37
56
  disabled = false,
57
+ dropzoneState = "default",
38
58
  files = [],
39
59
  helperText,
60
+ moreLabel = "More file actions",
40
61
  multiple = false,
41
62
  onBrowse,
42
63
  onCancel,
43
64
  onDrop,
65
+ onMore,
44
66
  onRemove,
45
67
  onRetry,
46
68
  prompt,
69
+ removeLabel = "Remove file",
70
+ retryLabel = "Try again",
47
71
  size = "lg",
72
+ title,
73
+ variant = "default",
48
74
  }: FileUploadProps) {
49
75
  const inputRef = useRef<HTMLInputElement | null>(null);
50
76
  const [isDragging, setIsDragging] = useState(false);
77
+ const isHover = dropzoneState === "hover" || isDragging;
78
+
79
+ const resolvedPrompt =
80
+ prompt ?? (variant === "button" ? "Drag and drop your files" : "Drop your files here, or");
81
+ const resolvedBrowseLabel = browseLabel ?? (variant === "button" ? "Or choose files" : "click to browse");
82
+ const resolvedHelperText = helperText ?? "Unlimited files, 5GB total limit";
51
83
 
52
84
  function browse() {
53
85
  if (disabled) return;
@@ -55,6 +87,11 @@ export function FileUpload({
55
87
  inputRef.current?.click();
56
88
  }
57
89
 
90
+ function handleDropzoneClick(event: MouseEvent<HTMLDivElement>) {
91
+ if (event.target instanceof HTMLElement && event.target.closest("button")) return;
92
+ browse();
93
+ }
94
+
58
95
  function handleInputChange(event: ChangeEvent<HTMLInputElement>) {
59
96
  const selectedFiles = event.currentTarget.files;
60
97
  if (selectedFiles?.length) {
@@ -79,10 +116,11 @@ export function FileUpload({
79
116
  }
80
117
 
81
118
  return (
82
- <div className={`ds-file-upload ds-file-upload--${size}`} dir={dir}>
119
+ <div className={cx("ds-file-upload", `ds-file-upload--${size}`, `ds-file-upload--${variant}`)} dir={dir}>
83
120
  <div
84
121
  aria-disabled={disabled || undefined}
85
- className={isDragging ? "ds-file-upload__dropzone is-dragging" : "ds-file-upload__dropzone"}
122
+ className={cx("ds-file-upload__dropzone", isHover && "is-hover")}
123
+ onClick={handleDropzoneClick}
86
124
  onDragLeave={() => setIsDragging(false)}
87
125
  onDragOver={handleDragOver}
88
126
  onDrop={handleDrop}
@@ -97,30 +135,50 @@ export function FileUpload({
97
135
  type="file"
98
136
  />
99
137
  <span className="ds-file-upload__icon" aria-hidden="true">
100
- <UploadIcon />
138
+ <UploadCloudIcon />
101
139
  </span>
102
- {(prompt || browseLabel) && (
103
- <p>
104
- {prompt}{" "}
105
- {browseLabel && (
106
- <button className="ds-file-upload__browse" disabled={disabled} onClick={browse} type="button">
107
- {browseLabel}
108
- </button>
140
+
141
+ {variant === "button" ? (
142
+ <>
143
+ <div className="ds-file-upload__copy">
144
+ <p className="ds-file-upload__title">{title ?? resolvedPrompt}</p>
145
+ {resolvedHelperText && <span className="ds-file-upload__helper">{resolvedHelperText}</span>}
146
+ </div>
147
+ <Button className="ds-file-upload__button" disabled={disabled} onClick={browse} type="button">
148
+ {resolvedBrowseLabel}
149
+ </Button>
150
+ </>
151
+ ) : (
152
+ <div className="ds-file-upload__copy">
153
+ {(resolvedPrompt || resolvedBrowseLabel) && (
154
+ <p className="ds-file-upload__title">
155
+ {resolvedPrompt}{" "}
156
+ {resolvedBrowseLabel && (
157
+ <button className="ds-file-upload__browse" disabled={disabled} onClick={browse} type="button">
158
+ {resolvedBrowseLabel}
159
+ </button>
160
+ )}
161
+ </p>
109
162
  )}
110
- </p>
163
+ {resolvedHelperText && <span className="ds-file-upload__helper">{resolvedHelperText}</span>}
164
+ </div>
111
165
  )}
112
- {helperText && <span className="ds-file-upload__helper">{helperText}</span>}
113
166
  </div>
114
167
 
115
168
  {files.length > 0 && (
116
169
  <ul className="ds-file-upload__list" aria-label="Queued files">
117
170
  {files.map((file) => (
118
171
  <FileUploadRow
172
+ cancelLabel={cancelLabel}
119
173
  file={file}
120
174
  key={file.id}
175
+ moreLabel={moreLabel}
121
176
  onCancel={onCancel}
177
+ onMore={onMore}
122
178
  onRemove={onRemove}
123
179
  onRetry={onRetry}
180
+ removeLabel={removeLabel}
181
+ retryLabel={retryLabel}
124
182
  size={size}
125
183
  />
126
184
  ))}
@@ -131,101 +189,346 @@ export function FileUpload({
131
189
  }
132
190
 
133
191
  function FileUploadRow({
192
+ cancelLabel,
134
193
  file,
194
+ moreLabel,
135
195
  onCancel,
196
+ onMore,
136
197
  onRemove,
137
198
  onRetry,
199
+ removeLabel,
200
+ retryLabel,
138
201
  size,
139
202
  }: {
203
+ cancelLabel: string;
140
204
  file: FileUploadFile;
205
+ moreLabel: string;
141
206
  onCancel?: (fileId: string) => void;
207
+ onMore?: (fileId: string) => void;
142
208
  onRemove?: (fileId: string) => void;
143
209
  onRetry?: (fileId: string) => void;
210
+ removeLabel: string;
211
+ retryLabel: string;
144
212
  size: FileUploadSize;
145
213
  }) {
214
+ const isSmall = size === "sm";
215
+ const isProgress = file.status === "uploading-progress";
216
+ const progressValue = clampProgress(file.progress ?? 40);
217
+ const progressLabel = file.progressLabel ?? `${progressValue}%`;
218
+ const uploadedLabel = file.uploadedLabel ?? "Uploaded";
219
+ const uploadLabel = file.uploadLabel ?? "Uploading...";
220
+ const errorLabel = file.errorLabel ?? "Upload failed";
221
+
146
222
  return (
147
- <li className={`ds-file-upload-row ds-file-upload-row--${file.status} ds-file-upload-row--${size}`}>
148
- <span className="ds-file-upload-row__file-icon" aria-hidden="true">
149
- <FileIcon />
150
- </span>
151
- <div className="ds-file-upload-row__copy">
152
- <strong title={file.name}>{file.name}</strong>
153
- <span>
154
- {file.sizeLabel}
155
- {file.versionLabel ? ` - ${file.versionLabel}` : ""}
156
- </span>
157
- {file.status === "error" && file.errorLabel && <em>{file.errorLabel}</em>}
223
+ <li className={cx("ds-file-upload-row", `ds-file-upload-row--${size}`, `ds-file-upload-row--${file.status}`)}>
224
+ <div className="ds-file-upload-row__content">
225
+ <FileTypeIcon label={file.extensionLabel ?? getExtensionLabel(file.name)} size={size} />
226
+
227
+ <div className="ds-file-upload-row__copy">
228
+ {isSmall || isProgress ? (
229
+ <div className="ds-file-upload-row__headline">
230
+ <strong title={file.name}>{file.name}</strong>
231
+ <span aria-hidden="true" className="ds-file-upload-row__dot">
232
+ {"\u00b7"}
233
+ </span>
234
+ <span>{file.sizeLabel}</span>
235
+ </div>
236
+ ) : (
237
+ <>
238
+ <strong title={file.name}>{file.name}</strong>
239
+ <div className="ds-file-upload-row__details">
240
+ <span>{getMetaLabel(file)}</span>
241
+ {(file.status === "uploaded" || file.status === "uploading" || file.status === "error") && (
242
+ <span className="ds-file-upload-row__state">
243
+ <StatusIcon status={file.status} />
244
+ {file.status === "uploaded" && <span>{uploadedLabel}</span>}
245
+ {file.status === "uploading" && <span>{uploadLabel}</span>}
246
+ {file.status === "error" && <span>{errorLabel}</span>}
247
+ </span>
248
+ )}
249
+ </div>
250
+ </>
251
+ )}
252
+
253
+ {isProgress && (
254
+ <div className="ds-file-upload-row__progress" aria-label={`Upload progress ${progressLabel}`}>
255
+ <span className="ds-file-upload-row__progress-track">
256
+ <span className="ds-file-upload-row__progress-fill" style={{ width: `${progressValue}%` }} />
257
+ </span>
258
+ <span className="ds-file-upload-row__progress-label">{progressLabel}</span>
259
+ </div>
260
+ )}
261
+
262
+ {!isSmall && file.status === "error" && (
263
+ <button className="ds-file-upload-row__retry" onClick={() => onRetry?.(file.id)} type="button">
264
+ {retryLabel}
265
+ </button>
266
+ )}
267
+ </div>
158
268
  </div>
159
- <StatusIcon status={file.status} />
269
+
270
+ {isSmall && file.status !== "uploaded" && file.status !== "uploading-progress" && (
271
+ <StatusIcon status={file.status} standalone />
272
+ )}
273
+
160
274
  <div className="ds-file-upload-row__actions">
161
- {file.status === "uploading" && (
162
- <button aria-label={`Cancel upload for ${file.name}`} onClick={() => onCancel?.(file.id)} type="button">
163
- Cancel
164
- </button>
275
+ {file.status === "uploaded" && (
276
+ <IconAction ariaLabel={`${moreLabel}: ${file.name}`} icon={<MoreFillIcon />} onClick={() => onMore?.(file.id)} />
165
277
  )}
166
- {file.status === "error" && (
167
- <button aria-label={`Retry upload for ${file.name}`} onClick={() => onRetry?.(file.id)} type="button">
168
- Retry
169
- </button>
278
+ {file.status === "uploading" && (
279
+ <IconAction ariaLabel={`${cancelLabel}: ${file.name}`} icon={<CloseFillIcon />} onClick={() => onCancel?.(file.id)} />
170
280
  )}
171
281
  {file.status !== "uploading" && (
172
- <button aria-label={`Remove ${file.name}`} onClick={() => onRemove?.(file.id)} type="button">
173
- Remove
174
- </button>
282
+ <IconAction ariaLabel={`${removeLabel}: ${file.name}`} icon={<DeleteBinLineIcon />} onClick={() => onRemove?.(file.id)} />
175
283
  )}
176
284
  </div>
177
285
  </li>
178
286
  );
179
287
  }
180
288
 
181
- function StatusIcon({ status }: { status: FileUploadItemStatus }) {
289
+ function FileTypeIcon({ label, size }: { label: string; size: FileUploadSize }) {
290
+ const iconLabel = normalizeExtensionLabel(label);
291
+ const icon = resolveFileIconVariant(iconLabel);
292
+ const badgeX = icon.badgeX ?? getFileIconBadgeX(icon.badgeWidth);
293
+ const badgeTextX = badgeX + icon.badgeWidth / 2;
294
+ const badgeStyle = {
295
+ "--ds-file-upload-file-badge-bg": icon.badgeFill,
296
+ } as CSSProperties;
297
+
298
+ return (
299
+ <span className={cx("ds-file-upload-file-icon", `ds-file-upload-file-icon--${size}`)} style={badgeStyle} aria-hidden="true">
300
+ <svg className="ds-file-upload-file-icon__svg" viewBox="0 0 40 40" fill="none" focusable="false">
301
+ <path
302
+ className="ds-file-upload-file-icon__page"
303
+ d="M13 3.5H24.1719C24.5696 3.50008 24.9512 3.65822 25.2324 3.93945L34.0605 12.7676C34.3418 13.0488 34.4999 13.4304 34.5 13.8281V33C34.5 34.933 32.933 36.5 31 36.5H13C11.067 36.5 9.5 34.933 9.5 33V7C9.5 5.067 11.067 3.5 13 3.5Z"
304
+ />
305
+ <path
306
+ className="ds-file-upload-file-icon__fold"
307
+ d="M24.5 3.59117V9.21432C24.5 11.4235 26.2909 13.2143 28.5 13.2143H34.0414"
308
+ />
309
+ <rect className="ds-file-upload-file-icon__badge" x={badgeX} y="20" width={icon.badgeWidth} height="12" rx="3" />
310
+ <text className="ds-file-upload-file-icon__label" x={badgeTextX} y="26.55" textAnchor="middle" dominantBaseline="middle">
311
+ {iconLabel}
312
+ </text>
313
+ </svg>
314
+ </span>
315
+ );
316
+ }
317
+
318
+ type FileIconVariant = {
319
+ badgeFill: string;
320
+ badgeWidth: number;
321
+ badgeX?: number;
322
+ };
323
+
324
+ const FILE_ICON_VARIANTS = {
325
+ AI: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 20 },
326
+ AVI: { badgeFill: "var(--bh-bg-accent-lime-default)", badgeWidth: 21 },
327
+ BMP: { badgeFill: "var(--bh-bg-accent-lime-default)", badgeWidth: 24 },
328
+ CRD: { badgeFill: "var(--bh-bg-accent-green-default)", badgeWidth: 23 },
329
+ CSV: { badgeFill: "var(--bh-bg-accent-green-default)", badgeWidth: 23 },
330
+ DLL: { badgeFill: "var(--bh-bg-accent-purple-default)", badgeWidth: 21 },
331
+ DOC: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 24 },
332
+ DOCX: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 29 },
333
+ EPS: { badgeFill: "var(--bh-bg-accent-purple-default)", badgeWidth: 21 },
334
+ EXE: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 21 },
335
+ FIG: { badgeFill: "var(--bh-bg-accent-purple-default)", badgeWidth: 20 },
336
+ FLV: { badgeFill: "var(--bh-bg-accent-purple-default)", badgeWidth: 20 },
337
+ GIF: { badgeFill: "var(--bh-bg-accent-rose-default)", badgeWidth: 19 },
338
+ HTML: { badgeFill: "var(--bh-bg-accent-lime-default)", badgeWidth: 30, badgeX: 3 },
339
+ IMG: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 22 },
340
+ ISO: { badgeFill: "var(--bh-bg-neutral-default)", badgeWidth: 19 },
341
+ JAVA: { badgeFill: "var(--bh-bg-accent-red-default)", badgeWidth: 27 },
342
+ JPG: { badgeFill: "var(--bh-bg-accent-fuchsia-default)", badgeWidth: 22 },
343
+ JSON: { badgeFill: "var(--bh-bg-neutral-default)", badgeWidth: 28 },
344
+ MID: { badgeFill: "var(--bh-bg-accent-fuchsia-default)", badgeWidth: 23 },
345
+ MOV: { badgeFill: "var(--bh-bg-accent-red-default)", badgeWidth: 25 },
346
+ MP3: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 24 },
347
+ MP4: { badgeFill: "var(--bh-bg-accent-rose-default)", badgeWidth: 24 },
348
+ MPEG: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 30, badgeX: 3 },
349
+ PDF: { badgeFill: "var(--bh-bg-accent-red-default)", badgeWidth: 22 },
350
+ PNG: { badgeFill: "var(--bh-bg-accent-aqua-default)", badgeWidth: 23 },
351
+ PPT: { badgeFill: "var(--bh-bg-accent-red-default)", badgeWidth: 22 },
352
+ PPTX: { badgeFill: "var(--bh-bg-accent-red-default)", badgeWidth: 28 },
353
+ PSD: { badgeFill: "var(--bh-bg-accent-blue-default)", badgeWidth: 22 },
354
+ PUB: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 22 },
355
+ RAR: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 22 },
356
+ RAW: { badgeFill: "var(--bh-bg-accent-sky-base)", badgeWidth: 25 },
357
+ RSS: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 22 },
358
+ SVG: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 23 },
359
+ TIFF: { badgeFill: "var(--bh-bg-accent-fuchsia-default)", badgeWidth: 23 },
360
+ TXT: { badgeFill: "var(--bh-bg-neutral-default)", badgeWidth: 22 },
361
+ WAV: { badgeFill: "var(--bh-bg-accent-amber-default)", badgeWidth: 25 },
362
+ WMA: { badgeFill: "var(--bh-bg-accent-golden-default)", badgeWidth: 28 },
363
+ XML: { badgeFill: "var(--bh-bg-accent-purple-default)", badgeWidth: 23 },
364
+ XSL: { badgeFill: "var(--bh-bg-accent-green-default)", badgeWidth: 21 },
365
+ XSLX: { badgeFill: "var(--bh-bg-accent-green-default)", badgeWidth: 27 },
366
+ ZIP: { badgeFill: "var(--bh-bg-accent-aqua-default)", badgeWidth: 21 },
367
+ } satisfies Record<string, FileIconVariant>;
368
+
369
+ const FILE_ICON_ALIASES: Record<string, keyof typeof FILE_ICON_VARIANTS> = {
370
+ CDR: "CRD",
371
+ JPEG: "JPG",
372
+ XLS: "XSL",
373
+ XLSX: "XSLX",
374
+ };
375
+
376
+ function resolveFileIconVariant(label: string): FileIconVariant {
377
+ const directVariant = FILE_ICON_VARIANTS[label as keyof typeof FILE_ICON_VARIANTS];
378
+ if (directVariant) return directVariant;
379
+
380
+ const aliasedVariant = FILE_ICON_ALIASES[label];
381
+ if (aliasedVariant) return FILE_ICON_VARIANTS[aliasedVariant];
382
+
383
+ const badgeWidth = label.length <= 2 ? 20 : label.length >= 4 ? 30 : 23;
384
+ return { badgeFill: "var(--bh-bg-neutral-default)", badgeWidth, badgeX: getFileIconBadgeX(badgeWidth) };
385
+ }
386
+
387
+ function getFileIconBadgeX(badgeWidth: number) {
388
+ return badgeWidth >= 30 ? 3 : 5;
389
+ }
390
+
391
+ function StatusIcon({
392
+ standalone = false,
393
+ status,
394
+ }: {
395
+ standalone?: boolean;
396
+ status: Exclude<FileUploadItemStatus, "uploading-progress">;
397
+ }) {
182
398
  return (
183
- <span className={`ds-file-upload-row__status ds-file-upload-row__status--${status}`} aria-label={status}>
184
- {status === "uploaded" && <CheckIcon />}
185
- {status === "uploading" && <SpinnerIcon />}
186
- {status === "error" && <WarningIcon />}
399
+ <span
400
+ className={cx(
401
+ "ds-file-upload-row__status",
402
+ standalone && "ds-file-upload-row__status--standalone",
403
+ `ds-file-upload-row__status--${status}`
404
+ )}
405
+ aria-label={getStatusLabel(status)}
406
+ >
407
+ {status === "uploaded" && <CheckboxCircleFillIcon />}
408
+ {status === "uploading" && <Loader2LineIcon />}
409
+ {status === "error" && <ErrorWarningFillIcon />}
187
410
  </span>
188
411
  );
189
412
  }
190
413
 
191
- function UploadIcon() {
414
+ function UploadCloudIcon() {
192
415
  return (
193
- <svg viewBox="0 0 24 24">
194
- <path d="M12 16V5m0 0 4 4m-4-4-4 4M5 16v2.5A1.5 1.5 0 0 0 6.5 20h11a1.5 1.5 0 0 0 1.5-1.5V16" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" />
416
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 24 24">
417
+ <path
418
+ d="M1 14.5C1 12.1716 2.22429 10.1291 4.06426 8.9812C4.56469 5.044 7.92686 2 12 2C16.0731 2 19.4353 5.044 19.9357 8.9812C21.7757 10.1291 23 12.1716 23 14.5C23 17.9216 20.3562 20.7257 17 20.9811L7 21C3.64378 20.7257 1 17.9216 1 14.5ZM16.8483 18.9868C19.1817 18.8093 21 16.8561 21 14.5C21 12.927 20.1884 11.4962 18.8771 10.6781L18.0714 10.1754L17.9517 9.23338C17.5735 6.25803 15.0288 4 12 4C8.97116 4 6.42647 6.25803 6.0483 9.23338L5.92856 10.1754L5.12288 10.6781C3.81156 11.4962 3 12.927 3 14.5C3 16.8561 4.81833 18.8093 7.1517 18.9868L7.325 19H16.675L16.8483 18.9868ZM13 13V17H11V13H8L12 8L16 13H13Z"
419
+ fill="currentColor"
420
+ />
195
421
  </svg>
196
422
  );
197
423
  }
198
424
 
199
- function FileIcon() {
425
+ function CheckboxCircleFillIcon() {
200
426
  return (
201
- <svg viewBox="0 0 24 24">
202
- <path d="M7 3.5h6.8L18 7.7v12.8H7V3.5Z" fill="none" stroke="currentColor" strokeLinejoin="round" strokeWidth="1.8" />
203
- <path d="M13.5 3.8v4.4h4.2" fill="none" stroke="currentColor" strokeLinejoin="round" strokeWidth="1.8" />
427
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 16 16">
428
+ <path
429
+ d="M8.00016 14.6667C11.682 14.6667 14.6668 11.6819 14.6668 8.00001C14.6668 4.31811 11.682 1.33334 8.00016 1.33334C4.31826 1.33334 1.3335 4.31811 1.3335 8.00001C1.3335 11.6819 4.31826 14.6667 8.00016 14.6667ZM11.6382 6.30475L7.3335 10.6095L4.52876 7.80474L5.47157 6.86194L7.3335 8.72388L10.6954 5.36194L11.6382 6.30475Z"
430
+ fill="currentColor"
431
+ />
204
432
  </svg>
205
433
  );
206
434
  }
207
435
 
208
- function CheckIcon() {
436
+ function Loader2LineIcon() {
209
437
  return (
210
- <svg viewBox="0 0 20 20">
211
- <path d="m4.5 10 3.4 3.4 7.6-7.8" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.9" />
438
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 16 16">
439
+ <path
440
+ d="M8.00016 1.33333C8.36836 1.33333 8.66683 1.63181 8.66683 1.99999V3.99999C8.66683 4.36818 8.36836 4.66666 8.00016 4.66666C7.63196 4.66666 7.3335 4.36818 7.3335 3.99999V1.99999C7.3335 1.63181 7.63196 1.33333 8.00016 1.33333ZM8.00016 11.3333C8.36836 11.3333 8.66683 11.6318 8.66683 12V14C8.66683 14.3682 8.36836 14.6667 8.00016 14.6667C7.63196 14.6667 7.3335 14.3682 7.3335 14V12C7.3335 11.6318 7.63196 11.3333 8.00016 11.3333ZM14.6668 7.99999C14.6668 8.36819 14.3684 8.66666 14.0002 8.66666H12.0002C11.632 8.66666 11.3335 8.36819 11.3335 7.99999C11.3335 7.63179 11.632 7.33333 12.0002 7.33333H14.0002C14.3684 7.33333 14.6668 7.63179 14.6668 7.99999ZM4.66683 7.99999C4.66683 8.36819 4.36835 8.66666 4.00016 8.66666H2.00016C1.63198 8.66666 1.3335 8.36819 1.3335 7.99999C1.3335 7.63179 1.63198 7.33333 2.00016 7.33333H4.00016C4.36835 7.33333 4.66683 7.63179 4.66683 7.99999ZM12.7142 12.7141C12.4538 12.9744 12.0318 12.9744 11.7714 12.7141L10.3572 11.2998C10.0968 11.0395 10.0968 10.6174 10.3572 10.357C10.6176 10.0967 11.0396 10.0967 11.3 10.357L12.7142 11.7713C12.9746 12.0316 12.9746 12.4537 12.7142 12.7141ZM5.64314 5.64297C5.38279 5.90332 4.96068 5.90332 4.70033 5.64297L3.28612 4.22876C3.02577 3.96841 3.02577 3.5463 3.28612 3.28595C3.54647 3.0256 3.96858 3.0256 4.22893 3.28595L5.64314 4.70016C5.90349 4.96051 5.90349 5.38262 5.64314 5.64297ZM3.28612 12.7141C3.02577 12.4537 3.02577 12.0316 3.28612 11.7713L4.70033 10.357C4.96068 10.0967 5.38279 10.0967 5.64314 10.357C5.90349 10.6174 5.90349 11.0395 5.64314 11.2998L4.22893 12.7141C3.96858 12.9744 3.54647 12.9744 3.28612 12.7141ZM10.3572 5.64297C10.0968 5.38262 10.0968 4.96051 10.3572 4.70016L11.7714 3.28595C12.0318 3.0256 12.4538 3.0256 12.7142 3.28595C12.9746 3.5463 12.9746 3.96841 12.7142 4.22876L11.3 5.64297C11.0396 5.90332 10.6176 5.90332 10.3572 5.64297Z"
441
+ fill="currentColor"
442
+ />
212
443
  </svg>
213
444
  );
214
445
  }
215
446
 
216
- function SpinnerIcon() {
447
+ function ErrorWarningFillIcon() {
217
448
  return (
218
- <svg viewBox="0 0 20 20">
219
- <path d="M10 3a7 7 0 1 1-6.2 3.8" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="1.8" />
449
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 16 16">
450
+ <path
451
+ d="M8.00016 14.6667C4.31826 14.6667 1.3335 11.6819 1.3335 8.00001C1.3335 4.31811 4.31826 1.33334 8.00016 1.33334C11.682 1.33334 14.6668 4.31811 14.6668 8.00001C14.6668 11.6819 11.682 14.6667 8.00016 14.6667ZM7.3335 10V11.3333H8.66683V10H7.3335ZM7.3335 4.66668V8.66668H8.66683V4.66668H7.3335Z"
452
+ fill="currentColor"
453
+ />
220
454
  </svg>
221
455
  );
222
456
  }
223
457
 
224
- function WarningIcon() {
458
+ function MoreFillIcon() {
225
459
  return (
226
- <svg viewBox="0 0 20 20">
227
- <path d="M10 3.2 18 17H2l8-13.8Z" fill="none" stroke="currentColor" strokeLinejoin="round" strokeWidth="1.6" />
228
- <path d="M10 8v4m0 2.5h.1" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="1.9" />
460
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 20 20">
461
+ <path
462
+ d="M4.16667 8.33334C3.25 8.33334 2.5 9.08334 2.5 10C2.5 10.9167 3.25 11.6667 4.16667 11.6667C5.08333 11.6667 5.83333 10.9167 5.83333 10C5.83333 9.08334 5.08333 8.33334 4.16667 8.33334ZM15.8333 8.33334C14.9167 8.33334 14.1667 9.08334 14.1667 10C14.1667 10.9167 14.9167 11.6667 15.8333 11.6667C16.75 11.6667 17.5 10.9167 17.5 10C17.5 9.08334 16.75 8.33334 15.8333 8.33334ZM10 8.33334C9.08333 8.33334 8.33333 9.08334 8.33333 10C8.33333 10.9167 9.08333 11.6667 10 11.6667C10.9167 11.6667 11.6667 10.9167 11.6667 10C11.6667 9.08334 10.9167 8.33334 10 8.33334Z"
463
+ fill="currentColor"
464
+ />
229
465
  </svg>
230
466
  );
231
467
  }
468
+
469
+ function CloseFillIcon() {
470
+ return (
471
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 20 20">
472
+ <path
473
+ d="M9.99959 8.82208L14.1244 4.69727L15.3029 5.87577L11.1781 10.0006L15.3029 14.1253L14.1244 15.3038L9.99959 11.1791L5.87481 15.3038L4.69629 14.1253L8.82109 10.0006L4.69629 5.87577L5.87481 4.69727L9.99959 8.82208Z"
474
+ fill="currentColor"
475
+ />
476
+ </svg>
477
+ );
478
+ }
479
+
480
+ function DeleteBinLineIcon() {
481
+ return (
482
+ <svg aria-hidden="true" fill="none" focusable="false" viewBox="0 0 20 20">
483
+ <path
484
+ d="M14.1665 4.99999H18.3332V6.66666H16.6665V17.5C16.6665 17.9602 16.2934 18.3333 15.8332 18.3333H4.1665C3.70627 18.3333 3.33317 17.9602 3.33317 17.5V6.66666H1.6665V4.99999H5.83317V2.49999C5.83317 2.03976 6.20627 1.66666 6.6665 1.66666H13.3332C13.7934 1.66666 14.1665 2.03976 14.1665 2.49999V4.99999ZM14.9998 6.66666H4.99984V16.6667H14.9998V6.66666ZM7.49984 9.16666H9.1665V14.1667H7.49984V9.16666ZM10.8332 9.16666H12.4998V14.1667H10.8332V9.16666ZM7.49984 3.33332V4.99999H12.4998V3.33332H7.49984Z"
485
+ fill="currentColor"
486
+ />
487
+ </svg>
488
+ );
489
+ }
490
+
491
+ function IconAction({
492
+ ariaLabel,
493
+ icon,
494
+ onClick,
495
+ }: {
496
+ ariaLabel: string;
497
+ icon: ReactNode;
498
+ onClick: () => void;
499
+ }) {
500
+ return (
501
+ <Button aria-label={ariaLabel} className="ds-file-upload-row__action" onClick={onClick} size="icon-sm" type="button" variant="ghost">
502
+ {icon}
503
+ </Button>
504
+ );
505
+ }
506
+
507
+ function getExtensionLabel(name: string) {
508
+ const extension = name.split(".").pop();
509
+ if (!extension || extension === name) return "FILE";
510
+ return normalizeExtensionLabel(extension);
511
+ }
512
+
513
+ function normalizeExtensionLabel(label: string) {
514
+ const normalized = label.trim().replace(/^\./, "").toUpperCase().replace(/[^A-Z0-9]/g, "");
515
+ return normalized ? normalized.slice(0, 4) : "FILE";
516
+ }
517
+
518
+ function getMetaLabel(file: FileUploadFile) {
519
+ return [file.sizeLabel, file.versionLabel].filter(Boolean).join(" \u00b7 ");
520
+ }
521
+
522
+ function getStatusLabel(status: Exclude<FileUploadItemStatus, "uploading-progress">) {
523
+ if (status === "uploaded") return "Uploaded";
524
+ if (status === "uploading") return "Uploading";
525
+ return "Upload failed";
526
+ }
527
+
528
+ function clampProgress(value: number) {
529
+ return Math.min(100, Math.max(0, Math.round(value)));
530
+ }
531
+
532
+ function cx(...classes: Array<string | false | null | undefined>) {
533
+ return classes.filter(Boolean).join(" ");
534
+ }
@@ -1,3 +1,5 @@
1
+ import { CircleCheckIcon } from "lucide-react";
2
+
1
3
  import "./onboardingStepListItem.css";
2
4
 
3
5
  export type OnboardingStepState = "finished" | "active" | "inactive";
@@ -50,18 +52,12 @@ export function OnboardingStepListItem({
50
52
 
51
53
  function CheckCircleIcon() {
52
54
  return (
53
- <svg
55
+ <CircleCheckIcon
56
+ aria-hidden="true"
54
57
  className="ds-onboarding-step__check"
55
- fill="none"
56
58
  height="20"
57
- viewBox="0 0 20 20"
59
+ strokeWidth={2.25}
58
60
  width="20"
59
- xmlns="http://www.w3.org/2000/svg"
60
- >
61
- <path
62
- d="M10 1.667A8.333 8.333 0 1 1 10 18.333 8.333 8.333 0 0 1 10 1.667Zm-.831 11.667 5.89-5.893-1.179-1.178-4.711 4.714-2.357-2.357-1.179 1.178 3.536 3.536Z"
63
- fill="currentColor"
64
- />
65
- </svg>
61
+ />
66
62
  );
67
63
  }
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
+ import { HomeIcon as LucideHomeIcon } from "lucide-react";
2
3
  import { Tabs, type TabsProps } from "./Tabs";
3
4
  import "./pageHeader.css";
4
5
 
@@ -40,17 +41,7 @@ function classes(...names: Array<string | false | undefined>) {
40
41
  }
41
42
 
42
43
  function HomeIcon() {
43
- return (
44
- <svg aria-hidden="true" className="ds-page-header__home-icon" fill="none" viewBox="0 0 24 24">
45
- <path
46
- d="M3.75 10.5 12 3.75l8.25 6.75v8.25a1.5 1.5 0 0 1-1.5 1.5H15v-6h-6v6H5.25a1.5 1.5 0 0 1-1.5-1.5V10.5Z"
47
- stroke="currentColor"
48
- strokeLinecap="round"
49
- strokeLinejoin="round"
50
- strokeWidth="1.8"
51
- />
52
- </svg>
53
- );
44
+ return <LucideHomeIcon aria-hidden="true" className="ds-page-header__home-icon" strokeWidth={1.9} />;
54
45
  }
55
46
 
56
47
  function Breadcrumbs({ items, label }: { items: PageHeaderBreadcrumb[]; label?: string }) {