@sia-ui/cli 0.7.0 → 0.8.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 (100) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +12 -0
  3. package/dist/registry/package/components/AccountMenu/index.tsx +107 -0
  4. package/dist/registry/package/components/AccountMenu/styles.css +30 -0
  5. package/dist/registry/package/components/ActivityLog/index.tsx +96 -25
  6. package/dist/registry/package/components/AmountDisplay/index.tsx +5 -2
  7. package/dist/registry/package/components/AppShell/index.tsx +44 -8
  8. package/dist/registry/package/components/AppShell/tab-bar.tsx +6 -3
  9. package/dist/registry/package/components/AsyncState/index.tsx +29 -7
  10. package/dist/registry/package/components/AuditMeta/index.tsx +12 -2
  11. package/dist/registry/package/components/Avatar/index.tsx +4 -9
  12. package/dist/registry/package/components/Badge/index.tsx +3 -2
  13. package/dist/registry/package/components/Badge/styles.css +4 -4
  14. package/dist/registry/package/components/BrandIcon/index.tsx +100 -0
  15. package/dist/registry/package/components/BrandIcon/styles.css +31 -0
  16. package/dist/registry/package/components/Breadcrumb/index.tsx +14 -2
  17. package/dist/registry/package/components/Button/index.tsx +3 -2
  18. package/dist/registry/package/components/Button/styles.css +4 -4
  19. package/dist/registry/package/components/Calendar/index.tsx +7 -3
  20. package/dist/registry/package/components/Card/index.tsx +14 -4
  21. package/dist/registry/package/components/ColorModeToggle/index.tsx +44 -0
  22. package/dist/registry/package/components/CommandPalette/index.tsx +3 -2
  23. package/dist/registry/package/components/ConfirmDialog/index.tsx +24 -4
  24. package/dist/registry/package/components/CrudPage/dialogs.tsx +53 -8
  25. package/dist/registry/package/components/CrudPage/index.tsx +99 -25
  26. package/dist/registry/package/components/CurrencyInput/index.tsx +5 -2
  27. package/dist/registry/package/components/DataTable/index.tsx +53 -18
  28. package/dist/registry/package/components/DataTable/row-actions.tsx +20 -8
  29. package/dist/registry/package/components/DataTable/types.ts +100 -0
  30. package/dist/registry/package/components/DatePicker/index.tsx +14 -6
  31. package/dist/registry/package/components/DateRangeFilter/index.tsx +12 -3
  32. package/dist/registry/package/components/DateRangePicker/index.tsx +13 -4
  33. package/dist/registry/package/components/DateTimePicker/index.tsx +3 -1
  34. package/dist/registry/package/components/Drawer/index.tsx +16 -6
  35. package/dist/registry/package/components/DropdownMenu/index.tsx +37 -4
  36. package/dist/registry/package/components/DropdownMenu/styles.css +10 -0
  37. package/dist/registry/package/components/DurationDisplay/index.tsx +20 -2
  38. package/dist/registry/package/components/EntityMeta/index.tsx +14 -9
  39. package/dist/registry/package/components/ErrorState/index.tsx +9 -3
  40. package/dist/registry/package/components/ErrorState/styles.css +1 -1
  41. package/dist/registry/package/components/EventCalendar/index.tsx +657 -9
  42. package/dist/registry/package/components/EventCalendar/styles.css +360 -8
  43. package/dist/registry/package/components/EventManager/index.tsx +456 -0
  44. package/dist/registry/package/components/EventManager/styles.css +12 -0
  45. package/dist/registry/package/components/Field/index.tsx +74 -27
  46. package/dist/registry/package/components/FileUpload/index.tsx +13 -3
  47. package/dist/registry/package/components/FiltersBar/index.tsx +8 -3
  48. package/dist/registry/package/components/FiltersBar/styles.css +7 -0
  49. package/dist/registry/package/components/Form/index.tsx +118 -14
  50. package/dist/registry/package/components/Form/styles.css +3 -0
  51. package/dist/registry/package/components/HoverCard/index.tsx +8 -4
  52. package/dist/registry/package/components/Icons/index.tsx +18 -0
  53. package/dist/registry/package/components/ImageUpload/index.tsx +17 -2
  54. package/dist/registry/package/components/Input/index.tsx +3 -2
  55. package/dist/registry/package/components/JobProgress/index.tsx +289 -0
  56. package/dist/registry/package/components/JobProgress/styles.css +58 -0
  57. package/dist/registry/package/components/JsonEditor/index.tsx +18 -3
  58. package/dist/registry/package/components/JsonView/index.tsx +282 -0
  59. package/dist/registry/package/components/JsonView/styles.css +99 -0
  60. package/dist/registry/package/components/LiveIndicator/index.tsx +43 -0
  61. package/dist/registry/package/components/LiveIndicator/styles.css +41 -0
  62. package/dist/registry/package/components/LogStream/index.tsx +545 -0
  63. package/dist/registry/package/components/LogStream/styles.css +152 -0
  64. package/dist/registry/package/components/MarkdownEditor/index.tsx +14 -3
  65. package/dist/registry/package/components/Modal/index.tsx +7 -1
  66. package/dist/registry/package/components/MultiSelect/index.tsx +14 -3
  67. package/dist/registry/package/components/OtpInput/index.tsx +2 -1
  68. package/dist/registry/package/components/Pagination/index.tsx +13 -2
  69. package/dist/registry/package/components/PasswordInput/index.tsx +23 -9
  70. package/dist/registry/package/components/PhoneInput/index.tsx +66 -30
  71. package/dist/registry/package/components/Popover/index.tsx +19 -4
  72. package/dist/registry/package/components/QrCode/index.tsx +3 -2
  73. package/dist/registry/package/components/RadioGroup/styles.css +3 -0
  74. package/dist/registry/package/components/Rating/index.tsx +6 -2
  75. package/dist/registry/package/components/ReferenceSelect/index.tsx +8 -1
  76. package/dist/registry/package/components/RelativeTime/index.tsx +5 -1
  77. package/dist/registry/package/components/RequireSession/index.tsx +10 -4
  78. package/dist/registry/package/components/Resource/index.tsx +70 -17
  79. package/dist/registry/package/components/RichTextEditor/index.tsx +2 -1
  80. package/dist/registry/package/components/SearchInput/index.tsx +4 -1
  81. package/dist/registry/package/components/SecretFields/index.tsx +70 -17
  82. package/dist/registry/package/components/Select/index.tsx +44 -12
  83. package/dist/registry/package/components/Select/styles.css +6 -3
  84. package/dist/registry/package/components/Sidebar/index.tsx +22 -6
  85. package/dist/registry/package/components/Slider/index.tsx +26 -5
  86. package/dist/registry/package/components/Spinner/index.tsx +3 -2
  87. package/dist/registry/package/components/StatCard/index.tsx +11 -3
  88. package/dist/registry/package/components/Statistic/index.tsx +5 -1
  89. package/dist/registry/package/components/Steps/styles.css +2 -2
  90. package/dist/registry/package/components/TagsInput/index.tsx +2 -1
  91. package/dist/registry/package/components/TimePicker/index.tsx +10 -4
  92. package/dist/registry/package/components/Toast/index.tsx +20 -5
  93. package/dist/registry/package/components/Tour/index.tsx +2 -1
  94. package/dist/registry/package/components/Transfer/index.tsx +2 -1
  95. package/dist/registry/package/components/Tree/index.tsx +3 -2
  96. package/dist/registry/package/components/TreeSelect/index.tsx +17 -4
  97. package/dist/registry/package/components/UnsavedChangesGuard/index.tsx +34 -3
  98. package/dist/registry/registry.json +257 -75
  99. package/dist/registry/styles/sia-ui.css +23 -4
  100. package/package.json +5 -5
@@ -1,6 +1,15 @@
1
1
  import type { Key, ReactNode } from "react";
2
2
  import type { PermissionRule } from "@sia-ui/headless";
3
3
  import type { ComponentTone } from "@sia-ui/tokens";
4
+ import type { SearchInputProps } from "../SearchInput";
5
+ import type { EmptyStateProps } from "../EmptyState";
6
+ import type { ErrorStateProps } from "../ErrorState";
7
+ import type { SkeletonProps } from "../Skeleton";
8
+ import type { CheckboxProps } from "../Checkbox";
9
+ import type { ButtonProps } from "../Button";
10
+ import type { IconButtonProps } from "../IconButton";
11
+ import type { DropdownMenuProps } from "../DropdownMenu";
12
+ import type { ConfirmDialogProps } from "../ConfirmDialog";
4
13
 
5
14
  /** Liste dense, ou grille de cartes. */
6
15
  export type DataTableView = "table" | "cards";
@@ -115,6 +124,18 @@ export interface DataTableProps<T> {
115
124
  * lettre ; Entrée et l'effacement déclenchent sans attendre.
116
125
  */
117
126
  searchDelay?: number;
127
+ /**
128
+ * Le champ de recherche : placeholder, libellés, `clearable`, classe.
129
+ *
130
+ * La valeur et les rappels restent au tableau — c'est lui qui filtre et
131
+ * qui temporise ; les déléguer ici ferait deux recherches concurrentes.
132
+ */
133
+ searchInputProps?: Partial<
134
+ Omit<
135
+ SearchInputProps,
136
+ "value" | "defaultValue" | "onValueChange" | "onSearch" | "searchDelay"
137
+ >
138
+ >;
118
139
 
119
140
  /* ─────────────────────────────────────────────────────── mise en page */
120
141
 
@@ -135,6 +156,40 @@ export interface DataTableProps<T> {
135
156
  actionsHeader?: ReactNode;
136
157
  /** `icon` garde la colonne étroite sur un tableau déjà large. */
137
158
  actionsDisplay?: "icon" | "label";
159
+ /**
160
+ * Les boutons d'action à libellé : variante, taille, classe.
161
+ *
162
+ * Le clic, le libellé et l'état désactivé viennent de chaque `RowAction`.
163
+ */
164
+ rowActionButtonProps?: Partial<
165
+ Omit<ButtonProps, "onClick" | "children" | "disabled">
166
+ >;
167
+ /**
168
+ * Les boutons d'action réduits à l'icône, déclencheur du menu « autres
169
+ * actions » compris. Une `tooltip` passée ici se fusionne avec celle de
170
+ * l'action, qui garde son libellé.
171
+ */
172
+ rowActionIconButtonProps?: Partial<
173
+ Omit<IconButtonProps, "onClick" | "label" | "icon" | "disabled">
174
+ >;
175
+ /**
176
+ * Le menu des actions qui ne tiennent pas en ligne : placement, décalage.
177
+ *
178
+ * Ni `open` ni `defaultOpen` : partagés par toutes les lignes, ils
179
+ * ouvriraient tous les menus à la fois.
180
+ */
181
+ rowActionsMenuProps?: Partial<
182
+ Omit<DropdownMenuProps, "items" | "children" | "onSelect" | "open" | "defaultOpen">
183
+ >;
184
+ /**
185
+ * La confirmation d'une action : libellés par défaut, `loading`.
186
+ *
187
+ * Ce que déclare `confirm` sur l'action l'emporte ; le ton suit
188
+ * `confirm.destructive`, l'ouverture et la validation restent au tableau.
189
+ */
190
+ confirmDialogProps?: Partial<
191
+ Omit<ConfirmDialogProps, "open" | "onOpenChange" | "onConfirm" | "title" | "tone">
192
+ >;
138
193
 
139
194
  /** Évalue les règles de permission des actions. */
140
195
  can?: (rule: PermissionRule) => boolean;
@@ -161,13 +216,28 @@ export interface DataTableProps<T> {
161
216
  loading?: boolean;
162
217
  /** Lignes fantômes pendant le premier chargement. */
163
218
  skeletonRows?: number;
219
+ /** Les blocs fantômes : rayon, classe, style. Leurs tailles ne sont que des défauts. */
220
+ skeletonProps?: Partial<SkeletonProps>;
164
221
  error?: ReactNode;
165
222
  onRetry?: () => void;
223
+ /**
224
+ * L'état d'erreur : titre, libellé du bouton, action, `compact`.
225
+ *
226
+ * Le message vient de `error` ; `onRetry` du tableau l'emporte s'il est
227
+ * fourni.
228
+ */
229
+ errorStateProps?: Partial<Omit<ErrorStateProps, "description" | "error">>;
166
230
  empty?: {
167
231
  title?: ReactNode;
168
232
  description?: ReactNode;
169
233
  action?: ReactNode;
234
+ icon?: ReactNode;
170
235
  };
236
+ /**
237
+ * L'état vide au complet : icône, `compact`, classe. Ce que déclare `empty`
238
+ * l'emporte sur les mêmes clés.
239
+ */
240
+ emptyStateProps?: Partial<EmptyStateProps>;
171
241
 
172
242
  /* ───────────────────────────────────────────────────────── sélection */
173
243
 
@@ -183,6 +253,25 @@ export interface DataTableProps<T> {
183
253
  defaultSelectedKeys?: Key[];
184
254
  onSelectionChange?: (keys: Key[], rows: T[]) => void;
185
255
  isRowSelectable?: (row: T, index: number) => boolean;
256
+ /**
257
+ * Les cases de sélection, en-tête comprise : taille, ton, classe.
258
+ *
259
+ * L'état coché, l'indétermination et le rappel sont calculés par le
260
+ * tableau ; le clic reste retenu pour ne pas déclencher `onRowClick`.
261
+ */
262
+ checkboxProps?: Partial<
263
+ Omit<
264
+ CheckboxProps,
265
+ | "checked"
266
+ | "defaultChecked"
267
+ | "indeterminate"
268
+ | "onValueChange"
269
+ | "onChange"
270
+ | "onClick"
271
+ | "disabled"
272
+ | "value"
273
+ >
274
+ >;
186
275
  /** Barre affichée dès qu'une ligne est cochée. */
187
276
  selectionActions?: (context: {
188
277
  keys: Key[];
@@ -200,3 +289,14 @@ export interface DataTableProps<T> {
200
289
  caption?: string;
201
290
  className?: string;
202
291
  }
292
+
293
+ /**
294
+ * Les réglages des sous-composants d'une action de ligne. Regroupés, ils
295
+ * traversent la vue cartes sans une prop par sous-composant.
296
+ */
297
+ export interface RowActionsSlots {
298
+ button: DataTableProps<unknown>["rowActionButtonProps"];
299
+ iconButton: DataTableProps<unknown>["rowActionIconButtonProps"];
300
+ menu: DataTableProps<unknown>["rowActionsMenuProps"];
301
+ confirmDialog: DataTableProps<unknown>["confirmDialogProps"];
302
+ }
@@ -1,9 +1,10 @@
1
1
  import { forwardRef, useId, useRef, useState, type ReactNode } from "react";
2
2
  import { cn, toneClass } from "@sia-ui/utils";
3
+ import { useSiaLocale } from "@sia-ui/headless";
3
4
  import { Calendar, type CalendarProps } from "../Calendar";
4
5
  import type { ComponentTone } from "@sia-ui/tokens";
5
6
  import { CalendarIcon, ChevronDownIcon, XIcon } from "../Icons";
6
- import { Overlay, type OverlayPlacement } from "../Overlay";
7
+ import { Overlay, type OverlayPlacement, type OverlayProps } from "../Overlay";
7
8
 
8
9
  export interface DatePickerProps
9
10
  extends Pick<CalendarProps, "locale" | "firstDayOfWeek" | "disabledDate" | "cellRender" | "showWeek"> {
@@ -16,12 +17,15 @@ export interface DatePickerProps
16
17
  onValueChange?: (value: string) => void;
17
18
  onOpenChange?: (open: boolean) => void;
18
19
  open?: boolean;
20
+ /** Par défaut, « Sélectionner une date » de la locale SIA. */
19
21
  placeholder?: string;
20
22
  formatValue?: (value: string, locale: string) => ReactNode;
21
23
  allowClear?: boolean;
22
24
  showToday?: boolean;
23
25
  renderExtraFooter?: () => ReactNode;
24
26
  placement?: OverlayPlacement;
27
+ /** Réglages du panneau flottant — décalage, marge au bord, classe. */
28
+ overlayProps?: Partial<Omit<OverlayProps, "open" | "anchorRef" | "onOpenChange" | "children">>;
25
29
  tone?: ComponentTone;
26
30
  invalid?: boolean;
27
31
  disabled?: boolean;
@@ -57,17 +61,18 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(functio
57
61
  onValueChange,
58
62
  onOpenChange,
59
63
  open: openProp,
60
- placeholder = "Sélectionner une date",
64
+ placeholder: placeholderProp,
61
65
  formatValue = defaultFormat,
62
66
  allowClear = true,
63
67
  showToday = true,
64
68
  renderExtraFooter,
65
69
  placement = "bottom-start",
70
+ overlayProps,
66
71
  tone = "primary",
67
72
  invalid = false,
68
73
  disabled = false,
69
74
  required = false,
70
- locale = "fr-FR",
75
+ locale: localeProp,
71
76
  firstDayOfWeek = 1,
72
77
  disabledDate,
73
78
  cellRender,
@@ -77,6 +82,9 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(functio
77
82
  "aria-required": ariaRequired,
78
83
  ...ariaProps
79
84
  }, forwardedRef) {
85
+ const messages = useSiaLocale();
86
+ const placeholder = placeholderProp ?? messages.selectDate;
87
+ const locale = localeProp ?? messages.language;
80
88
  const generatedId = useId();
81
89
  const controlId = id ?? `sia-date-picker-${generatedId.replace(/:/g, "")}`;
82
90
  const anchorRef = useRef<HTMLButtonElement>(null);
@@ -96,12 +104,12 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(functio
96
104
  <CalendarIcon className="sia-picker__icon" />
97
105
  <span className={cn("sia-picker__value", !current && "sia-picker__placeholder")}>{current ? formatValue(current, locale) : placeholder}</span>
98
106
  <span className="sia-picker__actions">
99
- {allowClear && current && <span className="sia-picker__clear" title="Effacer" aria-hidden="true" onClick={(event) => { event.stopPropagation(); update(""); }}><XIcon /></span>}
107
+ {allowClear && current && <span className="sia-picker__clear" title={messages.clear} aria-hidden="true" onClick={(event) => { event.stopPropagation(); update(""); }}><XIcon /></span>}
100
108
  <ChevronDownIcon />
101
109
  </span>
102
110
  </button>
103
- <Overlay open={open} anchorRef={anchorRef} onOpenChange={setOpen} placement={placement} className="sia-picker__overlay">
104
- <Calendar value={current} defaultMonth={current} {...(min && max ? { validRange: [min, max] as [string, string] } : {})} disabledDate={(date) => Boolean((min && date < min) || (max && date > max) || disabledDate?.(date))} locale={locale} firstDayOfWeek={firstDayOfWeek} {...(cellRender ? { cellRender } : {})} {...(showWeek !== undefined ? { showWeek } : {})} tone={tone} onValueChange={(next) => { update(next); setOpen(false); }} renderExtraFooter={() => <div className="sia-picker__footer">{showToday && <button type="button" disabled={Boolean((min && todayValue < min) || (max && todayValue > max) || disabledDate?.(todayValue))} onClick={() => { update(todayValue); setOpen(false); }}>Aujourd'hui</button>}{renderExtraFooter?.()}</div>} />
111
+ <Overlay placement={placement} {...overlayProps} className={cn("sia-picker__overlay", overlayProps?.className)} open={open} anchorRef={anchorRef} onOpenChange={setOpen}>
112
+ <Calendar value={current} defaultMonth={current} {...(min && max ? { validRange: [min, max] as [string, string] } : {})} disabledDate={(date) => Boolean((min && date < min) || (max && date > max) || disabledDate?.(date))} locale={locale} firstDayOfWeek={firstDayOfWeek} {...(cellRender ? { cellRender } : {})} {...(showWeek !== undefined ? { showWeek } : {})} tone={tone} onValueChange={(next) => { update(next); setOpen(false); }} renderExtraFooter={() => <div className="sia-picker__footer">{showToday && <button type="button" disabled={Boolean((min && todayValue < min) || (max && todayValue > max) || disabledDate?.(todayValue))} onClick={() => { update(todayValue); setOpen(false); }}>{messages.today}</button>}{renderExtraFooter?.()}</div>} />
105
113
  </Overlay>
106
114
  </div>
107
115
  );
@@ -1,10 +1,19 @@
1
1
  import { type ReactNode } from "react";
2
- import { DateRangePicker, type DateRangeValue } from "../DateRangePicker";
2
+ import { DateRangePicker, type DateRangePickerProps, type DateRangeValue } from "../DateRangePicker";
3
3
  import { cn } from "@sia-ui/utils";
4
+ import { useSiaLocale } from "@sia-ui/headless";
4
5
  import "./styles.css";
5
6
 
6
7
  export interface DateRangePreset { label: ReactNode; value: DateRangeValue; }
7
- export interface DateRangeFilterProps { value: DateRangeValue; onValueChange: (value: DateRangeValue) => void; presets?: DateRangePreset[]; onClear?: () => void; className?: string; }
8
+ export interface DateRangeFilterProps {
9
+ value: DateRangeValue;
10
+ onValueChange: (value: DateRangeValue) => void;
11
+ presets?: DateRangePreset[];
12
+ onClear?: () => void;
13
+ /** Réglages du sélecteur de période — bornes, libellés, jours désactivés. */
14
+ dateRangePickerProps?: Partial<Omit<DateRangePickerProps, "value" | "defaultValue" | "onValueChange">>;
15
+ className?: string;
16
+ }
8
17
  /**
9
18
  * Une période, avec ses raccourcis.
10
19
  *
@@ -12,4 +21,4 @@ export interface DateRangeFilterProps { value: DateRangeValue; onValueChange: (v
12
21
  * l'exercice — et les proposer évite de saisir deux dates pour la question
13
22
  * que tout le monde pose en premier.
14
23
  */
15
- export function DateRangeFilter({ value, onValueChange, presets = [], onClear, className }: DateRangeFilterProps) { return <section className={cn("sia-date-range-filter", className)}><div className="sia-date-range-filter__presets">{presets.map((preset, index) => <button type="button" key={index} onClick={() => onValueChange(preset.value)}>{preset.label}</button>)}</div><DateRangePicker value={value} onValueChange={onValueChange} />{onClear && (value.start || value.end) && <button type="button" className="sia-date-range-filter__clear" onClick={onClear}>Effacer</button>}</section>; }
24
+ export function DateRangeFilter({ value, onValueChange, presets = [], onClear, dateRangePickerProps, className }: DateRangeFilterProps) { const locale = useSiaLocale(); return <section className={cn("sia-date-range-filter", className)}><div className="sia-date-range-filter__presets">{presets.map((preset, index) => <button type="button" key={index} onClick={() => onValueChange(preset.value)}>{preset.label}</button>)}</div><DateRangePicker {...dateRangePickerProps} value={value} onValueChange={onValueChange} />{onClear && (value.start || value.end) && <button type="button" className="sia-date-range-filter__clear" onClick={onClear}>{locale.clear}</button>}</section>; }
@@ -1,9 +1,10 @@
1
1
  import { useRef, useState } from "react";
2
2
  import { cn, toneClass } from "@sia-ui/utils";
3
+ import { useSiaLocale } from "@sia-ui/headless";
3
4
  import { Calendar, type CalendarProps, type CalendarRangeValue } from "../Calendar";
4
5
  import type { ComponentTone } from "@sia-ui/tokens";
5
6
  import { ArrowRightIcon, CalendarIcon, ChevronDownIcon, XIcon } from "../Icons";
6
- import { Overlay, type OverlayPlacement } from "../Overlay";
7
+ import { Overlay, type OverlayPlacement, type OverlayProps } from "../Overlay";
7
8
 
8
9
  export type DateRangeValue = CalendarRangeValue;
9
10
  export interface DateRangePickerProps extends Pick<CalendarProps, "locale" | "firstDayOfWeek" | "disabledDate" | "cellRender" | "showWeek"> {
@@ -12,10 +13,14 @@ export interface DateRangePickerProps extends Pick<CalendarProps, "locale" | "fi
12
13
  onValueChange?: (value: DateRangeValue) => void;
13
14
  min?: string;
14
15
  max?: string;
16
+ /** Par défaut, « Date de début » de la locale SIA. */
15
17
  startLabel?: string;
18
+ /** Par défaut, « Date de fin » de la locale SIA. */
16
19
  endLabel?: string;
17
20
  allowClear?: boolean;
18
21
  placement?: OverlayPlacement;
22
+ /** Réglages du panneau flottant — décalage, marge au bord, classe. */
23
+ overlayProps?: Partial<Omit<OverlayProps, "open" | "anchorRef" | "onOpenChange" | "children">>;
19
24
  tone?: ComponentTone;
20
25
  invalid?: boolean;
21
26
  disabled?: boolean;
@@ -28,7 +33,11 @@ export interface DateRangePickerProps extends Pick<CalendarProps, "locale" | "fi
28
33
  * faut alors décider lequel des deux avait tort. Ici les deux bornes sont
29
34
  * une seule valeur, et le calendrier les tient dans l'ordre.
30
35
  */
31
- export function DateRangePicker({ value, defaultValue = { start: "", end: "" }, onValueChange, min, max, startLabel = "Date de début", endLabel = "Date de fin", allowClear = true, placement = "bottom-start", tone = "primary", invalid, disabled, locale = "fr-FR", firstDayOfWeek = 1, disabledDate, cellRender, showWeek, className }: DateRangePickerProps) {
36
+ export function DateRangePicker({ value, defaultValue = { start: "", end: "" }, onValueChange, min, max, startLabel: startLabelProp, endLabel: endLabelProp, allowClear = true, placement = "bottom-start", overlayProps, tone = "primary", invalid, disabled, locale: localeProp, firstDayOfWeek = 1, disabledDate, cellRender, showWeek, className }: DateRangePickerProps) {
37
+ const messages = useSiaLocale();
38
+ const startLabel = startLabelProp ?? messages.startDate;
39
+ const endLabel = endLabelProp ?? messages.endDate;
40
+ const locale = localeProp ?? messages.language;
32
41
  const anchorRef = useRef<HTMLButtonElement>(null);
33
42
  const [internal, setInternal] = useState(defaultValue);
34
43
  const [open, setOpen] = useState(false);
@@ -37,7 +46,7 @@ export function DateRangePicker({ value, defaultValue = { start: "", end: "" },
37
46
  const rangeInvalid = Boolean(invalid || (current.start && current.end && current.start > current.end));
38
47
  const format = (date: string) => date ? new Intl.DateTimeFormat(locale, { day: "2-digit", month: "short", year: "numeric" }).format(new Date(`${date}T12:00:00`)) : null;
39
48
  return <div className={cn("sia-picker", "sia-range-picker", "sia-date-range-picker", toneClass("sia-tone", tone), rangeInvalid && "sia-picker--invalid", disabled && "sia-picker--disabled", className)}>
40
- <button ref={anchorRef} type="button" className="sia-picker__trigger sia-range-picker__trigger" disabled={disabled} aria-haspopup="dialog" aria-expanded={open} aria-invalid={rangeInvalid || undefined} onClick={() => setOpen(!open)}><CalendarIcon className="sia-picker__icon" /><span className={cn("sia-range-picker__part", !current.start && "sia-picker__placeholder")}>{format(current.start) ?? startLabel}</span><ArrowRightIcon className="sia-range-picker__separator" /><span className={cn("sia-range-picker__part", !current.end && "sia-picker__placeholder")}>{format(current.end) ?? endLabel}</span><span className="sia-picker__actions">{allowClear && (current.start || current.end) && <span className="sia-picker__clear" title="Effacer" aria-hidden="true" onClick={(event) => { event.stopPropagation(); update({ start: "", end: "" }); }}><XIcon /></span>}<ChevronDownIcon /></span></button>
41
- <Overlay open={open} anchorRef={anchorRef} onOpenChange={setOpen} placement={placement} className="sia-picker__overlay"><Calendar selectionMode="range" range={current} defaultMonth={current.start || current.end} locale={locale} firstDayOfWeek={firstDayOfWeek} disabledDate={(date) => Boolean((min && date < min) || (max && date > max) || disabledDate?.(date))} {...(cellRender ? { cellRender } : {})} {...(showWeek !== undefined ? { showWeek } : {})} tone={tone} onRangeChange={(next) => { update(next); if (next.end) setOpen(false); }} /></Overlay>
49
+ <button ref={anchorRef} type="button" className="sia-picker__trigger sia-range-picker__trigger" disabled={disabled} aria-haspopup="dialog" aria-expanded={open} aria-invalid={rangeInvalid || undefined} onClick={() => setOpen(!open)}><CalendarIcon className="sia-picker__icon" /><span className={cn("sia-range-picker__part", !current.start && "sia-picker__placeholder")}>{format(current.start) ?? startLabel}</span><ArrowRightIcon className="sia-range-picker__separator" /><span className={cn("sia-range-picker__part", !current.end && "sia-picker__placeholder")}>{format(current.end) ?? endLabel}</span><span className="sia-picker__actions">{allowClear && (current.start || current.end) && <span className="sia-picker__clear" title={messages.clear} aria-hidden="true" onClick={(event) => { event.stopPropagation(); update({ start: "", end: "" }); }}><XIcon /></span>}<ChevronDownIcon /></span></button>
50
+ <Overlay placement={placement} {...overlayProps} className={cn("sia-picker__overlay", overlayProps?.className)} open={open} anchorRef={anchorRef} onOpenChange={setOpen}><Calendar selectionMode="range" range={current} defaultMonth={current.start || current.end} locale={locale} firstDayOfWeek={firstDayOfWeek} disabledDate={(date) => Boolean((min && date < min) || (max && date > max) || disabledDate?.(date))} {...(cellRender ? { cellRender } : {})} {...(showWeek !== undefined ? { showWeek } : {})} tone={tone} onRangeChange={(next) => { update(next); if (next.end) setOpen(false); }} /></Overlay>
42
51
  </div>;
43
52
  }
@@ -2,6 +2,7 @@ import { useState } from "react";
2
2
  import { DatePicker, type DatePickerProps } from "../DatePicker";
3
3
  import { TimePicker, type TimePickerProps } from "../TimePicker";
4
4
  import { cn, toneClass } from "@sia-ui/utils";
5
+ import { useSiaLocale } from "@sia-ui/headless";
5
6
  import type { ComponentTone } from "@sia-ui/tokens";
6
7
 
7
8
  export interface DateTimeValue { date: string; time: string; }
@@ -31,9 +32,10 @@ export interface DateTimePickerProps {
31
32
  * celle de la veille.
32
33
  */
33
34
  export function DateTimePicker({ value, defaultValue = { date: "", time: "" }, onValueChange, minuteStep = 1, secondStep = 1, showSeconds = false, hourFormat = 24, needConfirm = false, minDate, maxDate, tone = "primary", invalid, disabled, dateProps, timeProps, className }: DateTimePickerProps) {
35
+ const locale = useSiaLocale();
34
36
  const [internal, setInternal] = useState(defaultValue);
35
37
  const current = value ?? internal;
36
38
  const update = (next: DateTimeValue) => { if (value === undefined) setInternal(next); onValueChange?.(next); };
37
39
  const stateProps = { ...(invalid !== undefined ? { invalid } : {}), ...(disabled !== undefined ? { disabled } : {}) };
38
- return <div className={cn("sia-date-time-picker", toneClass("sia-tone", tone), className)}><DatePicker {...dateProps} value={current.date} {...(minDate ? { min: minDate } : {})} {...(maxDate ? { max: maxDate } : {})} tone={tone} {...stateProps} aria-label="Date" onValueChange={(date) => update({ ...current, date })} /><TimePicker {...timeProps} value={current.time} minuteStep={minuteStep} secondStep={secondStep} showSeconds={showSeconds} hourFormat={hourFormat} needConfirm={needConfirm} tone={tone} {...stateProps} aria-label="Heure" onValueChange={(time) => update({ ...current, time })} /></div>;
40
+ return <div className={cn("sia-date-time-picker", toneClass("sia-tone", tone), className)}><DatePicker {...dateProps} value={current.date} {...(minDate ? { min: minDate } : {})} {...(maxDate ? { max: maxDate } : {})} tone={tone} {...stateProps} aria-label={locale.dateTimePicker.date} onValueChange={(date) => update({ ...current, date })} /><TimePicker {...timeProps} value={current.time} minuteStep={minuteStep} secondStep={secondStep} showSeconds={showSeconds} hourFormat={hourFormat} needConfirm={needConfirm} tone={tone} {...stateProps} aria-label={locale.dateTimePicker.time} onValueChange={(time) => update({ ...current, time })} /></div>;
39
41
  }
@@ -11,7 +11,7 @@ import { Portal } from "@sia-ui/react";
11
11
  import "./styles.css";
12
12
  import { useOutsideClick } from "@sia-ui/react";
13
13
  import { DrawerContext } from "./context";
14
- import { overlayStack, useDisclosureState } from "@sia-ui/headless";
14
+ import { overlayStack, useDisclosureState, useSiaLocale } from "@sia-ui/headless";
15
15
  import { XIcon } from "../Icons";
16
16
  /** Ce que la réf impérative expose — ouvrir un tiroir depuis l'extérieur. */
17
17
  export type DrawerRef = {
@@ -76,10 +76,12 @@ export const Drawer = forwardRef<DrawerRef, IDrawerProps>(
76
76
  // même mécanique.
77
77
  const {
78
78
  isOpen,
79
+ present,
79
80
  phase: state,
80
81
  open: doOpen,
81
82
  close: doClose,
82
83
  toggle,
84
+ recentlyOpened,
83
85
  } = useDisclosureState({
84
86
  ...(open !== undefined ? { open } : {}),
85
87
  defaultOpen,
@@ -107,9 +109,10 @@ export const Drawer = forwardRef<DrawerRef, IDrawerProps>(
107
109
  [isOpen, doOpen, doClose, toggle],
108
110
  );
109
111
 
110
- /* Overlay stack */
112
+ /* Overlay stack — tenu tant que le tiroir est monté, sortie comprise :
113
+ rendu trop tôt, il passait sous la page à la fin de son animation. */
111
114
  useEffect(() => {
112
- if (!isOpen) return;
115
+ if (!present) return;
113
116
 
114
117
  zIndexRef.current = overlayStack.acquire("drawer");
115
118
 
@@ -117,7 +120,7 @@ export const Drawer = forwardRef<DrawerRef, IDrawerProps>(
117
120
  overlayStack.release("drawer");
118
121
  zIndexRef.current = null;
119
122
  };
120
- }, [isOpen]);
123
+ }, [present]);
121
124
 
122
125
  /* ESC + body lock */
123
126
  useEffect(() => {
@@ -145,13 +148,19 @@ export const Drawer = forwardRef<DrawerRef, IDrawerProps>(
145
148
  useOutsideClick([drawerRef], () => {
146
149
  if (!closeOnOutsideClick || !isOpen) return;
147
150
 
151
+ // Le second clic d'un double clic sur la ligne qui ouvre le tiroir, ou
152
+ // le rebond d'une souris : il tombe sur le fond et refermait aussitôt.
153
+ if (recentlyOpened()) return;
154
+
148
155
  // 🔒 Seul le drawer au top peut se fermer
149
156
  if (!overlayStack.isTop(zIndexRef.current)) return;
150
157
 
151
158
  doClose();
152
159
  });
153
160
 
154
- if (!isOpen) return null;
161
+ // Monté tant que la sortie se joue : `isOpen` est déjà faux, le parent
162
+ // a déjà été prévenu, mais l'animation n'est pas finie.
163
+ if (!present) return null;
155
164
 
156
165
  return (
157
166
  <Portal>
@@ -206,6 +215,7 @@ export function DrawerFooter({ children }: { children: React.ReactNode }) {
206
215
 
207
216
  export function DrawerClose() {
208
217
  const ctx = useContext(DrawerContext);
218
+ const locale = useSiaLocale();
209
219
  if (!ctx) return null;
210
220
 
211
221
  return (
@@ -213,7 +223,7 @@ export function DrawerClose() {
213
223
  type="button"
214
224
  className="sia-drawer-close"
215
225
  onClick={ctx.close}
216
- aria-label="Fermer"
226
+ aria-label={locale.close}
217
227
  >
218
228
  <XIcon aria-hidden="true" />
219
229
  </button>
@@ -11,7 +11,7 @@ import {
11
11
  } from "react";
12
12
  import { cn } from "@sia-ui/utils";
13
13
  import { useRovingIndex } from "@sia-ui/headless";
14
- import { Overlay, type OverlayPlacement } from "../Overlay";
14
+ import { Overlay, type OverlayPlacement, type OverlayProps } from "../Overlay";
15
15
  import { CheckIcon } from "../Icons";
16
16
  import "./styles.css";
17
17
 
@@ -40,7 +40,25 @@ export interface MenuLabel {
40
40
  label: ReactNode;
41
41
  }
42
42
 
43
- export type MenuEntry = MenuItem | MenuSeparator | MenuLabel;
43
+ /**
44
+ * Un en-tête à contenu libre : le profil d'un menu de compte, un résumé.
45
+ *
46
+ * `label` est un titre de section — petit, en capitales, sur une ligne — et
47
+ * n'a pas vocation à porter un avatar, un nom, une adresse et un rôle.
48
+ * L'en-tête ne se sélectionne pas et le clavier le saute.
49
+ */
50
+ export interface MenuHeader {
51
+ key: string;
52
+ type: "header";
53
+ content: ReactNode;
54
+ }
55
+
56
+ export type MenuEntry = MenuItem | MenuSeparator | MenuLabel | MenuHeader;
57
+
58
+ /** Ce qu'on règle de l'`Overlay` d'un menu : tout sauf ce que le menu pilote. */
59
+ export type MenuOverlayProps = Partial<
60
+ Omit<OverlayProps, "open" | "anchorRef" | "onOpenChange" | "children">
61
+ >;
44
62
 
45
63
  function isActionable(entry: MenuEntry): entry is MenuItem {
46
64
  return !("type" in entry) && !entry.disabled;
@@ -78,6 +96,13 @@ export function MenuList({
78
96
  if ("type" in entry && entry.type === "separator") {
79
97
  return <div key={entry.key} className="sia-menu__separator" role="separator" />;
80
98
  }
99
+ if ("type" in entry && entry.type === "header") {
100
+ return (
101
+ <div key={entry.key} className="sia-menu__header">
102
+ {entry.content}
103
+ </div>
104
+ );
105
+ }
81
106
  if ("type" in entry && entry.type === "label") {
82
107
  return (
83
108
  <div key={entry.key} className="sia-menu__group-label">
@@ -203,6 +228,8 @@ export interface DropdownMenuProps {
203
228
  onOpenChange?: (open: boolean) => void;
204
229
  placement?: OverlayPlacement;
205
230
  matchTriggerWidth?: boolean;
231
+ /** Le positionnement du panneau : décalage, marge au bord de l'écran, id. */
232
+ overlayProps?: MenuOverlayProps;
206
233
  className?: string;
207
234
  }
208
235
 
@@ -222,6 +249,7 @@ export function DropdownMenu({
222
249
  onOpenChange,
223
250
  placement = "bottom-start",
224
251
  matchTriggerWidth = false,
252
+ overlayProps,
225
253
  className,
226
254
  }: DropdownMenuProps) {
227
255
  const anchorRef = useRef<HTMLElement>(null);
@@ -265,7 +293,8 @@ export function DropdownMenu({
265
293
  onOpenChange={setOpen}
266
294
  placement={placement}
267
295
  matchAnchorWidth={matchTriggerWidth}
268
- className={cn("sia-menu-overlay", className)}
296
+ {...overlayProps}
297
+ className={cn("sia-menu-overlay", className, overlayProps?.className)}
269
298
  >
270
299
  <MenuList
271
300
  items={items}
@@ -283,6 +312,8 @@ export interface ContextMenuProps {
283
312
  items: MenuEntry[];
284
313
  children: ReactNode;
285
314
  onSelect?: (item: MenuItem) => void;
315
+ /** Le positionnement du panneau, comme pour `DropdownMenu`. */
316
+ overlayProps?: MenuOverlayProps;
286
317
  className?: string;
287
318
  }
288
319
 
@@ -297,6 +328,7 @@ export function ContextMenu({
297
328
  items,
298
329
  children,
299
330
  onSelect,
331
+ overlayProps,
300
332
  className,
301
333
  }: ContextMenuProps) {
302
334
  const anchorRef = useRef<HTMLSpanElement>(null);
@@ -335,7 +367,8 @@ export function ContextMenu({
335
367
  }}
336
368
  placement="bottom-start"
337
369
  offset={2}
338
- className={cn("sia-menu-overlay", className)}
370
+ {...overlayProps}
371
+ className={cn("sia-menu-overlay", className, overlayProps?.className)}
339
372
  >
340
373
  <MenuList
341
374
  items={items}
@@ -90,6 +90,16 @@
90
90
  }
91
91
 
92
92
  .sia-menu__separator { height: 1px; margin: .3rem -.3rem; background: var(--sia-border); }
93
+ /* L'en-tête libre : aucune typographie imposée, seulement de l'air et un
94
+ filet qui le sépare des entrées — il porte un profil, pas un titre. */
95
+ .sia-menu__header {
96
+ padding: .55rem .6rem .6rem;
97
+ margin: -.3rem -.3rem .3rem;
98
+ border-bottom: 1px solid var(--sia-border);
99
+ color: var(--sia-foreground);
100
+ font-size: .875rem;
101
+ }
102
+
93
103
  .sia-menu__group-label {
94
104
  padding: .4rem .55rem .25rem;
95
105
  color: var(--sia-muted);
@@ -1,9 +1,27 @@
1
1
  import { cn } from "@sia-ui/utils";
2
- export interface DurationDisplayProps { milliseconds: number; locale?: string; style?: "short" | "long" | "clock"; className?: string; }
2
+ import { formatMessage, plural, useSiaLocale } from "@sia-ui/headless";
3
+ export interface DurationDisplayProps { milliseconds: number; /** La langue des nombres et des unités. Par défaut, celle de la locale SIA. */ locale?: string; style?: "short" | "long" | "clock"; className?: string; }
3
4
  /**
4
5
  * Une durée, en mots ou en pendule.
5
6
  *
6
7
  * Trois styles pour trois usages : `clock` pour un chronomètre qu'on lit en
7
8
  * continu, `short` pour une colonne de tableau, `long` pour une phrase.
8
9
  */
9
- export function DurationDisplay({ milliseconds, locale = "fr-FR", style = "short", className }: DurationDisplayProps) { const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; if (style === "clock") return <span className={cn("sia-duration-display", className)}>{[hours, minutes, seconds].map((value) => String(value).padStart(2, "0")).join(":")}</span>; const formatter = new Intl.NumberFormat(locale); const parts = [[hours, style === "long" ? "heure" : "h"], [minutes, style === "long" ? "minute" : "min"], [seconds, style === "long" ? "seconde" : "s"]] as const; return <span className={cn("sia-duration-display", className)}>{parts.filter(([value]) => value > 0).map(([value, unit]) => `${formatter.format(value)} ${unit}${style === "long" && value > 1 ? "s" : ""}`).join(" ") || `0 ${style === "long" ? "seconde" : "s"}`}</span>; }
10
+ export function DurationDisplay({ milliseconds, locale: localeProp, style = "short", className }: DurationDisplayProps) {
11
+ const messages = useSiaLocale();
12
+ const locale = localeProp ?? messages.language;
13
+ const unites = messages.durationDisplay;
14
+ const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
15
+ const hours = Math.floor(totalSeconds / 3600);
16
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
17
+ const seconds = totalSeconds % 60;
18
+ if (style === "clock") return <span className={cn("sia-duration-display", className)}>{[hours, minutes, seconds].map((value) => String(value).padStart(2, "0")).join(":")}</span>;
19
+ const formatter = new Intl.NumberFormat(locale);
20
+ // Le nombre formaté remplace `{count}` ; le pluriel se choisit sur la valeur brute.
21
+ const dire = (value: number, cle: "hours" | "minutes" | "seconds") =>
22
+ style === "long"
23
+ ? plural(unites, cle, value, locale, { count: formatter.format(value) })
24
+ : formatMessage(unites[`${cle}Short`], { count: formatter.format(value) });
25
+ const parts = [[hours, "hours"], [minutes, "minutes"], [seconds, "seconds"]] as const;
26
+ return <span className={cn("sia-duration-display", className)}>{parts.filter(([value]) => value > 0).map(([value, cle]) => dire(value, cle)).join(" ") || dire(0, "seconds")}</span>;
27
+ }
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { cn, formatDate, formatTimeAgo, normalizeIdentifier, truncate } from "@sia-ui/utils";
3
+ import { useSiaLocale } from "@sia-ui/headless";
3
4
  import "./styles.css";
4
5
 
5
6
  export interface EntityMetaProps {
@@ -9,6 +10,7 @@ export interface EntityMetaProps {
9
10
  updatedBy?: ReactNode;
10
11
  identifier?: string;
11
12
  status?: ReactNode;
13
+ /** La langue des dates. Par défaut, celle de la locale SIA. */
12
14
  locale?: string;
13
15
  className?: string;
14
16
  }
@@ -46,26 +48,29 @@ export function EntityMeta({
46
48
  updatedBy,
47
49
  identifier,
48
50
  status,
49
- locale = "fr-FR",
51
+ locale: localeProp,
50
52
  className
51
53
  }: EntityMetaProps) {
54
+ const messages = useSiaLocale();
55
+ const locale = localeProp ?? messages.language;
56
+ const textes = messages.entityMeta;
52
57
  const normalizedIdentifier = identifier ? normalizeIdentifier(identifier) : undefined;
53
- const createdLabel = createdAt ? formatTimeAgo(createdAt, { locale, prefix: "Créée" }) : undefined;
54
- const updatedLabel = updatedAt ? formatTimeAgo(updatedAt, { locale, prefix: "Mise à jour" }) : undefined;
58
+ const createdLabel = createdAt ? formatTimeAgo(createdAt, { locale, prefix: textes.createdPrefix }) : undefined;
59
+ const updatedLabel = updatedAt ? formatTimeAgo(updatedAt, { locale, prefix: textes.updatedPrefix }) : undefined;
55
60
  const updatedDate = updatedAt ? formatDate(updatedAt, { locale, dateStyle: "medium", timeStyle: "short" }) : undefined;
56
61
 
57
62
  return (
58
- <section className={cn("sia-entity-meta", className)} aria-label="Métadonnées">
63
+ <section className={cn("sia-entity-meta", className)} aria-label={textes.label}>
59
64
  <div className="sia-entity-meta__header">
60
65
  {status && <span className="sia-entity-meta__status">{status}</span>}
61
66
  {normalizedIdentifier && <code className="sia-entity-meta__identifier">{truncate(normalizedIdentifier, 36)}</code>}
62
67
  </div>
63
68
  <div className="sia-entity-meta__grid">
64
- <Detail label="Création" value={createdLabel} emphasized />
65
- <Detail label="Créé par" value={createdBy} />
66
- <Detail label="Modification" value={updatedLabel} emphasized />
67
- <Detail label="Modifié par" value={updatedBy} />
68
- <Detail label="Dernière date" value={updatedDate} />
69
+ <Detail label={textes.created} value={createdLabel} emphasized />
70
+ <Detail label={textes.createdBy} value={createdBy} />
71
+ <Detail label={textes.updated} value={updatedLabel} emphasized />
72
+ <Detail label={textes.updatedBy} value={updatedBy} />
73
+ <Detail label={textes.lastDate} value={updatedDate} />
69
74
  </div>
70
75
  </section>
71
76
  );
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { cn } from "@sia-ui/utils";
3
- import { Button } from "../Button";
3
+ import { Button, type ButtonProps } from "../Button";
4
4
  import { useSiaLocale } from "@sia-ui/headless";
5
5
  import "./styles.css";
6
6
 
@@ -13,6 +13,12 @@ export interface ErrorStateProps {
13
13
  retryLabel?: string;
14
14
  compact?: boolean;
15
15
  className?: string;
16
+ /**
17
+ * Les props du bouton « Réessayer » — variante, taille, icône. Sans
18
+ * `onClick`, branché sur `onRetry`, ni `children` : le libellé passe par
19
+ * `retryLabel`.
20
+ */
21
+ retryButtonProps?: Partial<Omit<ButtonProps, "onClick" | "children">>;
16
22
  }
17
23
 
18
24
  function getErrorMessage(error: unknown): string | undefined {
@@ -29,10 +35,10 @@ function getErrorMessage(error: unknown): string | undefined {
29
35
  * exiger un type ferait écrire un `try` autour de chaque affichage
30
36
  * d'erreur.
31
37
  */
32
- export function ErrorState({ title: titleProp, description, error, action, onRetry, retryLabel: retryLabelProp, compact, className }: ErrorStateProps) {
38
+ export function ErrorState({ title: titleProp, description, error, action, onRetry, retryLabel: retryLabelProp, compact, className, retryButtonProps }: ErrorStateProps) {
33
39
  const locale = useSiaLocale();
34
40
  const retryLabel = retryLabelProp ?? locale.retry;
35
41
  const title = titleProp ?? locale.error;
36
42
  const message = description ?? getErrorMessage(error);
37
- return <section className={cn("sia-error-state", compact && "sia-error-state--compact", className)} role="alert"><div className="sia-error-state__icon" aria-hidden="true">!</div><strong className="sia-error-state__title">{title}</strong>{message && <p className="sia-error-state__description">{message}</p>}<div className="sia-error-state__actions">{action}{onRetry && <Button variant="outline" onClick={onRetry}>{retryLabel}</Button>}</div></section>;
43
+ return <section className={cn("sia-error-state", compact && "sia-error-state--compact", className)} role="alert"><div className="sia-error-state__icon" aria-hidden="true">!</div><strong className="sia-error-state__title">{title}</strong>{message && <p className="sia-error-state__description">{message}</p>}<div className="sia-error-state__actions">{action}{onRetry && <Button variant="outline" {...retryButtonProps} onClick={onRetry}>{retryLabel}</Button>}</div></section>;
38
44
  }