@sia-ui/cli 0.1.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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/diff.mjs +91 -0
  4. package/dist/index.js +325 -0
  5. package/dist/lifecycle.mjs +242 -0
  6. package/dist/lock.mjs +92 -0
  7. package/dist/registry/package/components/Accordion/index.tsx +172 -0
  8. package/dist/registry/package/components/Accordion/styles.css +97 -0
  9. package/dist/registry/package/components/Alert/index.tsx +31 -0
  10. package/dist/registry/package/components/Alert/styles.css +12 -0
  11. package/dist/registry/package/components/AmountDisplay/index.tsx +11 -0
  12. package/dist/registry/package/components/AmountDisplay/styles.css +9 -0
  13. package/dist/registry/package/components/AppShell/index.tsx +6 -0
  14. package/dist/registry/package/components/AppShell/styles.css +7 -0
  15. package/dist/registry/package/components/AspectRatio/index.tsx +29 -0
  16. package/dist/registry/package/components/AspectRatio/styles.css +15 -0
  17. package/dist/registry/package/components/AsyncState/index.tsx +22 -0
  18. package/dist/registry/package/components/AsyncState/styles.css +1 -0
  19. package/dist/registry/package/components/AuditMeta/index.tsx +44 -0
  20. package/dist/registry/package/components/AuditMeta/styles.css +12 -0
  21. package/dist/registry/package/components/AuthLayout/index.tsx +39 -0
  22. package/dist/registry/package/components/AuthLayout/styles.css +6 -0
  23. package/dist/registry/package/components/Autocomplete/index.tsx +8 -0
  24. package/dist/registry/package/components/Avatar/index.tsx +23 -0
  25. package/dist/registry/package/components/Avatar/styles.css +5 -0
  26. package/dist/registry/package/components/Badge/index.tsx +62 -0
  27. package/dist/registry/package/components/Badge/styles.css +26 -0
  28. package/dist/registry/package/components/Breadcrumb/index.tsx +131 -0
  29. package/dist/registry/package/components/Breadcrumb/styles.css +76 -0
  30. package/dist/registry/package/components/Button/index.tsx +91 -0
  31. package/dist/registry/package/components/Button/styles.css +111 -0
  32. package/dist/registry/package/components/Calendar/index.tsx +192 -0
  33. package/dist/registry/package/components/Card/index.tsx +8 -0
  34. package/dist/registry/package/components/Card/styles.css +4 -0
  35. package/dist/registry/package/components/Chart/index.tsx +437 -0
  36. package/dist/registry/package/components/Chart/styles.css +182 -0
  37. package/dist/registry/package/components/Checkbox/index.tsx +193 -0
  38. package/dist/registry/package/components/Checkbox/styles.css +39 -0
  39. package/dist/registry/package/components/ColorPicker/index.tsx +6 -0
  40. package/dist/registry/package/components/ColorPicker/styles.css +2 -0
  41. package/dist/registry/package/components/CommandPalette/index.tsx +7 -0
  42. package/dist/registry/package/components/CommandPalette/styles.css +8 -0
  43. package/dist/registry/package/components/ConfirmDialog/index.tsx +24 -0
  44. package/dist/registry/package/components/Container/index.tsx +11 -0
  45. package/dist/registry/package/components/Container/styles.css +1 -0
  46. package/dist/registry/package/components/Countdown/index.tsx +3 -0
  47. package/dist/registry/package/components/CrudPage/index.tsx +9 -0
  48. package/dist/registry/package/components/CrudPage/styles.css +1 -0
  49. package/dist/registry/package/components/CurrencyInput/index.tsx +15 -0
  50. package/dist/registry/package/components/CurrencyInput/styles.css +1 -0
  51. package/dist/registry/package/components/DataTable/index.tsx +240 -0
  52. package/dist/registry/package/components/DataTable/styles.css +98 -0
  53. package/dist/registry/package/components/DatePicker/index.tsx +102 -0
  54. package/dist/registry/package/components/DateRangeFilter/index.tsx +8 -0
  55. package/dist/registry/package/components/DateRangeFilter/styles.css +4 -0
  56. package/dist/registry/package/components/DateRangePicker/index.tsx +36 -0
  57. package/dist/registry/package/components/DateTimePicker/index.tsx +32 -0
  58. package/dist/registry/package/components/Descriptions/index.tsx +7 -0
  59. package/dist/registry/package/components/Descriptions/styles.css +8 -0
  60. package/dist/registry/package/components/Divider/index.tsx +12 -0
  61. package/dist/registry/package/components/Divider/styles.css +3 -0
  62. package/dist/registry/package/components/Drawer/context.ts +14 -0
  63. package/dist/registry/package/components/Drawer/index.tsx +209 -0
  64. package/dist/registry/package/components/Drawer/styles.css +151 -0
  65. package/dist/registry/package/components/DropdownMenu/index.tsx +350 -0
  66. package/dist/registry/package/components/DropdownMenu/styles.css +100 -0
  67. package/dist/registry/package/components/DurationDisplay/index.tsx +3 -0
  68. package/dist/registry/package/components/EmptyState/index.tsx +23 -0
  69. package/dist/registry/package/components/EmptyState/styles.css +6 -0
  70. package/dist/registry/package/components/EntityMeta/index.tsx +65 -0
  71. package/dist/registry/package/components/EntityMeta/styles.css +9 -0
  72. package/dist/registry/package/components/ErrorState/index.tsx +30 -0
  73. package/dist/registry/package/components/ErrorState/styles.css +6 -0
  74. package/dist/registry/package/components/EventCalendar/index.tsx +8 -0
  75. package/dist/registry/package/components/EventCalendar/styles.css +8 -0
  76. package/dist/registry/package/components/Field/index.tsx +664 -0
  77. package/dist/registry/package/components/Field/styles.css +14 -0
  78. package/dist/registry/package/components/FileUpload/index.tsx +68 -0
  79. package/dist/registry/package/components/FileUpload/styles.css +4 -0
  80. package/dist/registry/package/components/FiltersBar/index.tsx +20 -0
  81. package/dist/registry/package/components/FiltersBar/styles.css +6 -0
  82. package/dist/registry/package/components/Form/index.tsx +211 -0
  83. package/dist/registry/package/components/Form/styles.css +80 -0
  84. package/dist/registry/package/components/FormField/index.tsx +2 -0
  85. package/dist/registry/package/components/HoverCard/index.tsx +121 -0
  86. package/dist/registry/package/components/HoverCard/styles.css +26 -0
  87. package/dist/registry/package/components/IconButton/index.tsx +27 -0
  88. package/dist/registry/package/components/IconButton/styles.css +7 -0
  89. package/dist/registry/package/components/Icons/index.tsx +185 -0
  90. package/dist/registry/package/components/ImageUpload/index.tsx +6 -0
  91. package/dist/registry/package/components/ImageUpload/styles.css +4 -0
  92. package/dist/registry/package/components/Input/index.tsx +103 -0
  93. package/dist/registry/package/components/Input/styles.css +8 -0
  94. package/dist/registry/package/components/InputNumber/index.tsx +196 -0
  95. package/dist/registry/package/components/InputNumber/styles.css +54 -0
  96. package/dist/registry/package/components/JsonEditor/index.tsx +6 -0
  97. package/dist/registry/package/components/JsonEditor/styles.css +2 -0
  98. package/dist/registry/package/components/KpiGrid/index.tsx +11 -0
  99. package/dist/registry/package/components/KpiGrid/styles.css +5 -0
  100. package/dist/registry/package/components/Label/index.tsx +43 -0
  101. package/dist/registry/package/components/Label/styles.css +25 -0
  102. package/dist/registry/package/components/MarkdownEditor/index.tsx +5 -0
  103. package/dist/registry/package/components/MiniCalendar/index.tsx +3 -0
  104. package/dist/registry/package/components/Modal/index.tsx +104 -0
  105. package/dist/registry/package/components/Modal/styles.css +20 -0
  106. package/dist/registry/package/components/MonthPicker/index.tsx +5 -0
  107. package/dist/registry/package/components/MultiSelect/index.tsx +133 -0
  108. package/dist/registry/package/components/MultiSelect/styles.css +3 -0
  109. package/dist/registry/package/components/OtpInput/index.tsx +5 -0
  110. package/dist/registry/package/components/OtpInput/styles.css +2 -0
  111. package/dist/registry/package/components/Overlay/index.tsx +140 -0
  112. package/dist/registry/package/components/Overlay/styles.css +1 -0
  113. package/dist/registry/package/components/PageHeader/index.tsx +24 -0
  114. package/dist/registry/package/components/PageHeader/styles.css +5 -0
  115. package/dist/registry/package/components/Pagination/index.tsx +155 -0
  116. package/dist/registry/package/components/Pagination/styles.css +103 -0
  117. package/dist/registry/package/components/PasswordInput/index.tsx +125 -0
  118. package/dist/registry/package/components/PasswordInput/styles.css +58 -0
  119. package/dist/registry/package/components/PermissionGate/index.tsx +3 -0
  120. package/dist/registry/package/components/PhoneInput/index.tsx +189 -0
  121. package/dist/registry/package/components/PhoneInput/styles.css +35 -0
  122. package/dist/registry/package/components/Popover/index.tsx +12 -0
  123. package/dist/registry/package/components/Popover/styles.css +12 -0
  124. package/dist/registry/package/components/Progress/index.tsx +149 -0
  125. package/dist/registry/package/components/Progress/styles.css +101 -0
  126. package/dist/registry/package/components/QrCode/index.tsx +6 -0
  127. package/dist/registry/package/components/QrCode/styles.css +5 -0
  128. package/dist/registry/package/components/RadioGroup/index.tsx +29 -0
  129. package/dist/registry/package/components/RadioGroup/styles.css +20 -0
  130. package/dist/registry/package/components/Rating/index.tsx +82 -0
  131. package/dist/registry/package/components/Rating/styles.css +41 -0
  132. package/dist/registry/package/components/ReferenceSelect/index.tsx +7 -0
  133. package/dist/registry/package/components/ReferenceSelect/styles.css +2 -0
  134. package/dist/registry/package/components/RelativeTime/index.tsx +22 -0
  135. package/dist/registry/package/components/RelativeTime/styles.css +1 -0
  136. package/dist/registry/package/components/RequireSession/index.tsx +127 -0
  137. package/dist/registry/package/components/RequireSession/styles.css +13 -0
  138. package/dist/registry/package/components/ResizablePanel/index.tsx +6 -0
  139. package/dist/registry/package/components/ResizablePanel/styles.css +5 -0
  140. package/dist/registry/package/components/RichTextEditor/index.tsx +5 -0
  141. package/dist/registry/package/components/RichTextEditor/styles.css +2 -0
  142. package/dist/registry/package/components/ScrollArea/index.tsx +114 -0
  143. package/dist/registry/package/components/ScrollArea/styles.css +74 -0
  144. package/dist/registry/package/components/SearchInput/index.tsx +121 -0
  145. package/dist/registry/package/components/SearchInput/styles.css +6 -0
  146. package/dist/registry/package/components/Select/index.tsx +324 -0
  147. package/dist/registry/package/components/Select/styles.css +58 -0
  148. package/dist/registry/package/components/Sidebar/index.tsx +232 -0
  149. package/dist/registry/package/components/Sidebar/styles.css +140 -0
  150. package/dist/registry/package/components/Skeleton/index.tsx +21 -0
  151. package/dist/registry/package/components/Skeleton/styles.css +2 -0
  152. package/dist/registry/package/components/Slider/index.tsx +193 -0
  153. package/dist/registry/package/components/Slider/styles.css +53 -0
  154. package/dist/registry/package/components/Spinner/index.tsx +28 -0
  155. package/dist/registry/package/components/Spinner/styles.css +8 -0
  156. package/dist/registry/package/components/StatCard/index.tsx +15 -0
  157. package/dist/registry/package/components/StatCard/styles.css +3 -0
  158. package/dist/registry/package/components/Statistic/index.tsx +23 -0
  159. package/dist/registry/package/components/Statistic/styles.css +6 -0
  160. package/dist/registry/package/components/Steps/index.tsx +108 -0
  161. package/dist/registry/package/components/Steps/styles.css +138 -0
  162. package/dist/registry/package/components/Switch/index.tsx +46 -0
  163. package/dist/registry/package/components/Switch/styles.css +26 -0
  164. package/dist/registry/package/components/Tabs/index.tsx +44 -0
  165. package/dist/registry/package/components/Tabs/styles.css +41 -0
  166. package/dist/registry/package/components/TagsInput/index.tsx +6 -0
  167. package/dist/registry/package/components/TagsInput/styles.css +2 -0
  168. package/dist/registry/package/components/Textarea/index.tsx +21 -0
  169. package/dist/registry/package/components/Textarea/styles.css +7 -0
  170. package/dist/registry/package/components/TimePicker/index.tsx +76 -0
  171. package/dist/registry/package/components/TimePicker/styles.css +13 -0
  172. package/dist/registry/package/components/TimeRangePicker/index.tsx +32 -0
  173. package/dist/registry/package/components/TimeRangePicker/styles.css +1 -0
  174. package/dist/registry/package/components/Timeline/index.tsx +8 -0
  175. package/dist/registry/package/components/Timeline/styles.css +12 -0
  176. package/dist/registry/package/components/Timer/index.tsx +9 -0
  177. package/dist/registry/package/components/Toast/index.tsx +426 -0
  178. package/dist/registry/package/components/Toast/styles.css +175 -0
  179. package/dist/registry/package/components/Toggle/index.tsx +181 -0
  180. package/dist/registry/package/components/Toggle/styles.css +124 -0
  181. package/dist/registry/package/components/Tooltip/index.tsx +163 -0
  182. package/dist/registry/package/components/Tooltip/styles.css +57 -0
  183. package/dist/registry/package/components/Tour/index.tsx +8 -0
  184. package/dist/registry/package/components/Tour/styles.css +6 -0
  185. package/dist/registry/package/components/Transfer/index.tsx +7 -0
  186. package/dist/registry/package/components/Transfer/styles.css +6 -0
  187. package/dist/registry/package/components/Tree/index.tsx +8 -0
  188. package/dist/registry/package/components/Tree/styles.css +6 -0
  189. package/dist/registry/package/components/TreeSelect/index.tsx +10 -0
  190. package/dist/registry/package/components/TreeSelect/styles.css +3 -0
  191. package/dist/registry/package/components/Typography/index.tsx +27 -0
  192. package/dist/registry/package/components/Typography/styles.css +16 -0
  193. package/dist/registry/package/components/UnsavedChangesGuard/index.tsx +4 -0
  194. package/dist/registry/package/components/WeekPicker/index.tsx +5 -0
  195. package/dist/registry/package/components/YearPicker/index.tsx +3 -0
  196. package/dist/registry/package/lib/form-adapter-rhf.tsx +141 -0
  197. package/dist/registry/registry.json +1780 -0
  198. package/dist/registry/styles/sia-ui.css +361 -0
  199. package/dist/source.mjs +93 -0
  200. package/package.json +55 -0
@@ -0,0 +1,98 @@
1
+ /* Waves 3 and 4: data, advanced date/time and application patterns */
2
+ .sia-data-table { width:100%; overflow:auto; border:1px solid var(--sia-border); border-radius:var(--sia-radius); }
3
+ .sia-data-table table { width:100%; border-collapse:collapse; }
4
+ .sia-data-table caption { padding:.75rem; text-align:left; font-weight:700; }
5
+ .sia-data-table th,.sia-data-table td { padding:.75rem 1rem; border-bottom:1px solid var(--sia-border); }
6
+ .sia-data-table th { color:var(--sia-muted); background:var(--sia-surface); font-size:.78rem; text-transform:uppercase; letter-spacing:.04em; }
7
+ .sia-data-table tbody tr:last-child td { border-bottom:0; }
8
+ .sia-data-table__state { color:var(--sia-muted); text-align:center; }
9
+
10
+ /* Les lignes répondent au survol et à la sélection : sans ça, un tableau de
11
+ trente lignes ne dit pas où se trouve le curseur. */
12
+ .sia-data-table tbody tr {
13
+ transition: background-color var(--sia-duration-fast) var(--sia-easing-standard);
14
+ }
15
+
16
+ .sia-data-table tbody tr:hover {
17
+ background: color-mix(in srgb, var(--sia-primary) 4%, transparent);
18
+ }
19
+
20
+ .sia-data-table tbody tr[aria-selected="true"] {
21
+ background: color-mix(in srgb, var(--sia-primary) 9%, transparent);
22
+ }
23
+
24
+
25
+ /* ── Tri, servi par le serveur ─────────────────────────────────────────── */
26
+
27
+ .sia-data-table { position: relative; }
28
+
29
+ .sia-data-table__sort {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ gap: .35rem;
33
+ padding: 0;
34
+ border: 0;
35
+ background: transparent;
36
+ color: inherit;
37
+ font: inherit;
38
+ text-transform: inherit;
39
+ letter-spacing: inherit;
40
+ cursor: pointer;
41
+ transition: color var(--sia-duration-fast) var(--sia-easing-standard);
42
+ }
43
+ .sia-data-table__sort:hover { color: var(--sia-foreground); }
44
+ .sia-data-table__sort[data-active] { color: var(--sia-primary); }
45
+
46
+ /* La flèche n'apparaît qu'au survol ou quand le tri est actif : trois
47
+ chevrons gris en permanence dans un entête n'aident personne. */
48
+ .sia-data-table__arrow {
49
+ display: grid;
50
+ place-items: center;
51
+ font-size: .75rem;
52
+ opacity: 0;
53
+ transform: translateY(2px);
54
+ transition:
55
+ opacity var(--sia-duration-fast) var(--sia-easing-standard),
56
+ transform var(--sia-duration-base) var(--sia-easing-spring);
57
+ }
58
+ .sia-data-table__sort:hover .sia-data-table__arrow { opacity: .45; transform: none; }
59
+ .sia-data-table__sort[data-active] .sia-data-table__arrow { opacity: 1; transform: none; }
60
+
61
+ .sia-data-table--sticky thead th {
62
+ position: sticky;
63
+ top: 0;
64
+ z-index: 1;
65
+ /* Sans fond opaque, les lignes défilent visiblement sous l'entête. */
66
+ background: var(--sia-surface);
67
+ box-shadow: inset 0 -1px var(--sia-border);
68
+ }
69
+
70
+ .sia-data-table--clickable tbody tr { cursor: pointer; }
71
+
72
+ /* ── Chargement ────────────────────────────────────────────────────────── */
73
+
74
+ .sia-data-table__ghost-bar {
75
+ display: block;
76
+ height: .75rem;
77
+ border-radius: 999px;
78
+ background: linear-gradient(
79
+ 90deg,
80
+ var(--sia-surface) 25%,
81
+ var(--sia-border) 37%,
82
+ var(--sia-surface) 63%
83
+ );
84
+ background-size: 400% 100%;
85
+ animation: sia-skeleton 1.4s ease infinite;
86
+ }
87
+ /* Chaque ligne fantôme est un peu plus courte : une colonne de barres
88
+ strictement identiques se lit comme un défaut d'affichage. */
89
+ .sia-data-table__ghost:nth-child(2n) .sia-data-table__ghost-bar { width: 78%; }
90
+ .sia-data-table__ghost:nth-child(3n) .sia-data-table__ghost-bar { width: 62%; }
91
+
92
+ .sia-data-table__veil {
93
+ position: absolute;
94
+ inset: 0;
95
+ background: color-mix(in srgb, var(--sia-background) 55%, transparent);
96
+ animation: sia-fade-in var(--sia-duration-fast) var(--sia-easing-standard);
97
+ pointer-events: none;
98
+ }
@@ -0,0 +1,102 @@
1
+ import { forwardRef, useId, useRef, useState, type ReactNode } from "react";
2
+ import { cn, toneClass } from "@sia-ui/utils";
3
+ import { Calendar, type CalendarProps } from "../Calendar";
4
+ import type { ComponentTone } from "@sia-ui/tokens";
5
+ import { CalendarIcon, ChevronDownIcon, XIcon } from "../Icons";
6
+ import { Overlay, type OverlayPlacement } from "../Overlay";
7
+
8
+ export interface DatePickerProps
9
+ extends Pick<CalendarProps, "locale" | "firstDayOfWeek" | "disabledDate" | "cellRender" | "showWeek"> {
10
+ id?: string;
11
+ name?: string;
12
+ value?: string;
13
+ defaultValue?: string;
14
+ min?: string;
15
+ max?: string;
16
+ onValueChange?: (value: string) => void;
17
+ onOpenChange?: (open: boolean) => void;
18
+ open?: boolean;
19
+ placeholder?: string;
20
+ formatValue?: (value: string, locale: string) => ReactNode;
21
+ allowClear?: boolean;
22
+ showToday?: boolean;
23
+ renderExtraFooter?: () => ReactNode;
24
+ placement?: OverlayPlacement;
25
+ tone?: ComponentTone;
26
+ invalid?: boolean;
27
+ disabled?: boolean;
28
+ required?: boolean;
29
+ className?: string;
30
+ "aria-label"?: string;
31
+ "aria-describedby"?: string;
32
+ "aria-labelledby"?: string;
33
+ "aria-invalid"?: boolean;
34
+ "aria-required"?: boolean;
35
+ }
36
+
37
+ function defaultFormat(value: string, locale: string) {
38
+ if (!value) return null;
39
+ const date = new Date(`${value}T12:00:00`);
40
+ return Number.isNaN(date.getTime()) ? value : new Intl.DateTimeFormat(locale, { day: "2-digit", month: "short", year: "numeric" }).format(date);
41
+ }
42
+
43
+ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(function DatePicker({
44
+ id,
45
+ name,
46
+ value,
47
+ defaultValue = "",
48
+ min,
49
+ max,
50
+ onValueChange,
51
+ onOpenChange,
52
+ open: openProp,
53
+ placeholder = "Sélectionner une date",
54
+ formatValue = defaultFormat,
55
+ allowClear = true,
56
+ showToday = true,
57
+ renderExtraFooter,
58
+ placement = "bottom-start",
59
+ tone = "primary",
60
+ invalid = false,
61
+ disabled = false,
62
+ required = false,
63
+ locale = "fr-FR",
64
+ firstDayOfWeek = 1,
65
+ disabledDate,
66
+ cellRender,
67
+ showWeek,
68
+ className,
69
+ "aria-invalid": ariaInvalid,
70
+ "aria-required": ariaRequired,
71
+ ...ariaProps
72
+ }, forwardedRef) {
73
+ const generatedId = useId();
74
+ const controlId = id ?? `sia-date-picker-${generatedId.replace(/:/g, "")}`;
75
+ const anchorRef = useRef<HTMLButtonElement>(null);
76
+ const [internalValue, setInternalValue] = useState(defaultValue);
77
+ const [internalOpen, setInternalOpen] = useState(false);
78
+ const current = value ?? internalValue;
79
+ const open = openProp ?? internalOpen;
80
+ const setOpen = (next: boolean) => { if (openProp === undefined) setInternalOpen(next); onOpenChange?.(next); };
81
+ const update = (next: string) => { if (value === undefined) setInternalValue(next); onValueChange?.(next); };
82
+ const setRef = (node: HTMLButtonElement | null) => { anchorRef.current = node; if (typeof forwardedRef === "function") forwardedRef(node); else if (forwardedRef) forwardedRef.current = node; };
83
+ const today = new Date();
84
+ const todayValue = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
85
+ return (
86
+ <div className={cn("sia-picker", "sia-date-picker", toneClass("sia-tone", tone), invalid && "sia-picker--invalid", disabled && "sia-picker--disabled", className)}>
87
+ {name && <input type="hidden" name={name} value={current} />}
88
+ <button ref={setRef} id={controlId} type="button" className="sia-picker__trigger" disabled={disabled} aria-haspopup="dialog" aria-expanded={open} aria-invalid={ariaInvalid ?? (invalid || undefined)} aria-required={ariaRequired ?? (required || undefined)} onClick={() => setOpen(!open)} {...ariaProps}>
89
+ <CalendarIcon className="sia-picker__icon" />
90
+ <span className={cn("sia-picker__value", !current && "sia-picker__placeholder")}>{current ? formatValue(current, locale) : placeholder}</span>
91
+ <span className="sia-picker__actions">
92
+ {allowClear && current && <span className="sia-picker__clear" title="Effacer" aria-hidden="true" onClick={(event) => { event.stopPropagation(); update(""); }}><XIcon /></span>}
93
+ <ChevronDownIcon />
94
+ </span>
95
+ </button>
96
+ <Overlay open={open} anchorRef={anchorRef} onOpenChange={setOpen} placement={placement} className="sia-picker__overlay">
97
+ <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>} />
98
+ </Overlay>
99
+ </div>
100
+ );
101
+ });
102
+ DatePicker.displayName = "DatePicker";
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ import { DateRangePicker, type DateRangeValue } from "../DateRangePicker";
3
+ import { cn } from "@sia-ui/utils";
4
+ import "./styles.css";
5
+
6
+ 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 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>; }
@@ -0,0 +1,4 @@
1
+ .sia-date-range-filter { display:flex; align-items:end; gap:.75rem; flex-wrap:wrap; }
2
+ .sia-date-range-filter__presets { display:flex; gap:.4rem; flex-wrap:wrap; }
3
+ .sia-date-range-filter button { min-height:2.25rem; padding:0 .7rem; border:1px solid var(--sia-border); border-radius:var(--sia-radius); background:var(--sia-background); }
4
+ .sia-date-range-filter__clear { color:var(--sia-danger); }
@@ -0,0 +1,36 @@
1
+ import { useRef, useState } from "react";
2
+ import { cn, toneClass } from "@sia-ui/utils";
3
+ import { Calendar, type CalendarProps, type CalendarRangeValue } from "../Calendar";
4
+ import type { ComponentTone } from "@sia-ui/tokens";
5
+ import { ArrowRightIcon, CalendarIcon, ChevronDownIcon, XIcon } from "../Icons";
6
+ import { Overlay, type OverlayPlacement } from "../Overlay";
7
+
8
+ export type DateRangeValue = CalendarRangeValue;
9
+ export interface DateRangePickerProps extends Pick<CalendarProps, "locale" | "firstDayOfWeek" | "disabledDate" | "cellRender" | "showWeek"> {
10
+ value?: DateRangeValue;
11
+ defaultValue?: DateRangeValue;
12
+ onValueChange?: (value: DateRangeValue) => void;
13
+ min?: string;
14
+ max?: string;
15
+ startLabel?: string;
16
+ endLabel?: string;
17
+ allowClear?: boolean;
18
+ placement?: OverlayPlacement;
19
+ tone?: ComponentTone;
20
+ invalid?: boolean;
21
+ disabled?: boolean;
22
+ className?: string;
23
+ }
24
+ 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) {
25
+ const anchorRef = useRef<HTMLButtonElement>(null);
26
+ const [internal, setInternal] = useState(defaultValue);
27
+ const [open, setOpen] = useState(false);
28
+ const current = value ?? internal;
29
+ const update = (next: DateRangeValue) => { if (value === undefined) setInternal(next); onValueChange?.(next); };
30
+ const rangeInvalid = Boolean(invalid || (current.start && current.end && current.start > current.end));
31
+ const format = (date: string) => date ? new Intl.DateTimeFormat(locale, { day: "2-digit", month: "short", year: "numeric" }).format(new Date(`${date}T12:00:00`)) : null;
32
+ 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)}>
33
+ <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>
34
+ <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>
35
+ </div>;
36
+ }
@@ -0,0 +1,32 @@
1
+ import { useState } from "react";
2
+ import { DatePicker, type DatePickerProps } from "../DatePicker";
3
+ import { TimePicker, type TimePickerProps } from "../TimePicker";
4
+ import { cn, toneClass } from "@sia-ui/utils";
5
+ import type { ComponentTone } from "@sia-ui/tokens";
6
+
7
+ export interface DateTimeValue { date: string; time: string; }
8
+ export interface DateTimePickerProps {
9
+ value?: DateTimeValue;
10
+ defaultValue?: DateTimeValue;
11
+ onValueChange?: (value: DateTimeValue) => void;
12
+ minuteStep?: number;
13
+ secondStep?: number;
14
+ showSeconds?: boolean;
15
+ hourFormat?: 12 | 24;
16
+ needConfirm?: boolean;
17
+ minDate?: string;
18
+ maxDate?: string;
19
+ tone?: ComponentTone;
20
+ invalid?: boolean;
21
+ disabled?: boolean;
22
+ dateProps?: Omit<DatePickerProps, "value" | "defaultValue" | "onValueChange" | "tone" | "invalid" | "disabled">;
23
+ timeProps?: Omit<TimePickerProps, "value" | "defaultValue" | "onValueChange" | "tone" | "invalid" | "disabled">;
24
+ className?: string;
25
+ }
26
+ 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) {
27
+ const [internal, setInternal] = useState(defaultValue);
28
+ const current = value ?? internal;
29
+ const update = (next: DateTimeValue) => { if (value === undefined) setInternal(next); onValueChange?.(next); };
30
+ const stateProps = { ...(invalid !== undefined ? { invalid } : {}), ...(disabled !== undefined ? { disabled } : {}) };
31
+ 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>;
32
+ }
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import "./styles.css";
4
+
5
+ export interface DescriptionItem { key: string; label: ReactNode; value: ReactNode; span?: 1 | 2 | 3; }
6
+ export interface DescriptionsProps { items: DescriptionItem[]; title?: ReactNode; columns?: 1 | 2 | 3; bordered?: boolean; className?: string; }
7
+ export function Descriptions({ items, title, columns = 2, bordered = false, className }: DescriptionsProps) { return <section className={cn("sia-descriptions", bordered && "sia-descriptions--bordered", className)}>{title && <h3>{title}</h3>}<dl className={`sia-descriptions__grid sia-descriptions__grid--${columns}`}>{items.map((item) => <div key={item.key} style={{ gridColumn: `span ${item.span ?? 1}` }}><dt>{item.label}</dt><dd>{item.value}</dd></div>)}</dl></section>; }
@@ -0,0 +1,8 @@
1
+ .sia-descriptions { display:grid; gap:1rem; }
2
+ .sia-descriptions h3 { margin:0; }
3
+ .sia-descriptions__grid { display:grid; gap:1rem; margin:0; }
4
+ .sia-descriptions__grid--2 { grid-template-columns:repeat(2,minmax(0,1fr)); }
5
+ .sia-descriptions__grid--3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
6
+ .sia-descriptions dt { color:var(--sia-muted); font-size:.78rem; font-weight:700; }
7
+ .sia-descriptions dd { margin:.25rem 0 0; }
8
+ .sia-descriptions--bordered .sia-descriptions__grid > div { padding:1rem; border:1px solid var(--sia-border); }
@@ -0,0 +1,12 @@
1
+ import { type HTMLAttributes } from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import "./styles.css";
4
+
5
+ export interface DividerProps extends HTMLAttributes<HTMLDivElement> {
6
+ orientation?: "horizontal" | "vertical";
7
+ decorative?: boolean;
8
+ }
9
+
10
+ export function Divider({ orientation = "horizontal", decorative = true, className, ...props }: DividerProps) {
11
+ return <div className={cn("sia-divider", `sia-divider--${orientation}`, className)} role={decorative ? "presentation" : "separator"} aria-orientation={decorative ? undefined : orientation} {...props} />;
12
+ }
@@ -0,0 +1,3 @@
1
+ .sia-divider { flex:none; background:var(--sia-border); }
2
+ .sia-divider--horizontal { width:100%; height:1px; }
3
+ .sia-divider--vertical { align-self:stretch; width:1px; min-height:1.5rem; }
@@ -0,0 +1,14 @@
1
+ import { createContext } from "react";
2
+
3
+ /**
4
+ * Ce que les sous-composants du tiroir ont besoin de savoir.
5
+ *
6
+ * Déclaré ici plutôt que dans un dossier `contexts/` partagé : le registre ne
7
+ * copie que `components/<Nom>/`, et un import hors de ce dossier rendait
8
+ * l'entrée `drawer` impossible à installer.
9
+ */
10
+ export type DrawerContextValue = {
11
+ close: () => void;
12
+ };
13
+
14
+ export const DrawerContext = createContext<DrawerContextValue | null>(null);
@@ -0,0 +1,209 @@
1
+ import {
2
+ forwardRef,
3
+ useContext,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ } from "react";
8
+ import type { ForwardRefExoticComponent, RefAttributes } from "react";
9
+ import { cn } from "@sia-ui/utils";
10
+ import { Portal } from "@sia-ui/react";
11
+ import "./styles.css";
12
+ import { useOutsideClick } from "@sia-ui/react";
13
+ import { DrawerContext } from "./context";
14
+ import { overlayStack, useDisclosureState } from "@sia-ui/headless";
15
+ /** Ce que la réf impérative expose — ouvrir un tiroir depuis l'extérieur. */
16
+ export type DrawerRef = {
17
+ open: () => void;
18
+ close: () => void;
19
+ toggle: () => void;
20
+ isOpen: () => boolean;
21
+ };
22
+
23
+ export type DrawerPosition = "left" | "right" | "top" | "bottom";
24
+ export type DrawerSize = "sm" | "md" | "lg" | "xl";
25
+
26
+ export interface DrawerProps extends React.HTMLAttributes<HTMLDivElement> {
27
+ open?: boolean;
28
+ defaultOpen?: boolean;
29
+ onOpenChange?: (open: boolean) => void;
30
+ position?: DrawerPosition;
31
+ size?: DrawerSize;
32
+ closeOnOutsideClick?: boolean;
33
+ closeOnEsc?: boolean;
34
+ }
35
+
36
+ /** Alias historique, attendu par les composants venus du registre. */
37
+ export type IDrawerProps = DrawerProps;
38
+
39
+ export type DrawerComponent = ForwardRefExoticComponent<
40
+ IDrawerProps & RefAttributes<DrawerRef>
41
+ > & {
42
+ Header: typeof DrawerHeader;
43
+ Body: typeof DrawerBody;
44
+ Footer: typeof DrawerFooter;
45
+ Title: typeof DrawerTitle;
46
+ Close: typeof DrawerClose;
47
+ };
48
+
49
+ export const Drawer = forwardRef<DrawerRef, IDrawerProps>(
50
+ (
51
+ {
52
+ open,
53
+ defaultOpen = false,
54
+ onOpenChange,
55
+ position = "right",
56
+ size = "md",
57
+ closeOnOutsideClick = true,
58
+ closeOnEsc = true,
59
+ children,
60
+ ...rest
61
+ },
62
+ ref
63
+ ) => {
64
+ // Ouverture, fermeture et phase de sortie vivent dans @sia-ui/headless :
65
+ // rien là-dedans n'est propre au DOM, et le pendant mobile réutilisera la
66
+ // même mécanique.
67
+ const {
68
+ isOpen,
69
+ phase: state,
70
+ open: doOpen,
71
+ close: doClose,
72
+ } = useDisclosureState({
73
+ ...(open !== undefined ? { open } : {}),
74
+ defaultOpen,
75
+ ...(onOpenChange ? { onOpenChange } : {}),
76
+ exitMs: 200,
77
+ });
78
+
79
+ const drawerRef = useRef<HTMLDivElement | null>(null);
80
+ const zIndexRef = useRef<number | null>(null);
81
+
82
+ /* 🔌 API IMPÉRATIVE */
83
+ useImperativeHandle(
84
+ ref,
85
+ () => ({
86
+ open: doOpen,
87
+ close: doClose,
88
+ toggle: () => (isOpen ? doClose() : doOpen()),
89
+ isOpen: () => isOpen,
90
+ }),
91
+ [isOpen, open]
92
+ );
93
+
94
+ /* Overlay stack */
95
+ useEffect(() => {
96
+ if (!isOpen) return;
97
+
98
+ zIndexRef.current = overlayStack.acquire("drawer");
99
+
100
+ return () => {
101
+ overlayStack.release("drawer");
102
+ zIndexRef.current = null;
103
+ };
104
+ }, [isOpen]);
105
+
106
+ /* ESC + body lock */
107
+ useEffect(() => {
108
+ if (!isOpen) return;
109
+
110
+ const prevOverflow = document.body.style.overflow;
111
+ document.body.style.overflow = "hidden";
112
+
113
+ const onKeyDown = (e: KeyboardEvent) => {
114
+ if (!closeOnEsc || e.key !== "Escape") return;
115
+ if (overlayStack.isTop(zIndexRef.current)) {
116
+ doClose();
117
+ }
118
+ };
119
+
120
+ window.addEventListener("keydown", onKeyDown);
121
+
122
+ return () => {
123
+ document.body.style.overflow = prevOverflow;
124
+ window.removeEventListener("keydown", onKeyDown);
125
+ };
126
+ }, [isOpen, closeOnEsc]);
127
+
128
+ /* Outside click */
129
+ useOutsideClick([drawerRef], () => {
130
+ if (!closeOnOutsideClick || !isOpen) return;
131
+
132
+ // 🔒 Seul le drawer au top peut se fermer
133
+ if (!overlayStack.isTop(zIndexRef.current)) return;
134
+
135
+ doClose();
136
+ });
137
+
138
+ if (!isOpen) return null;
139
+
140
+ return (
141
+ <Portal>
142
+ <DrawerContext.Provider value={{ close: doClose }}>
143
+ <div
144
+ className="sia-drawer-root"
145
+ style={{ zIndex: zIndexRef.current ?? undefined }}
146
+ >
147
+ <div className="sia-drawer-overlay" />
148
+
149
+ <div
150
+ ref={drawerRef}
151
+ role="dialog"
152
+ aria-modal="true"
153
+ data-state={state}
154
+ className={cn(
155
+ "sia-drawer",
156
+ `sia-drawer-${position}`,
157
+ `sia-drawer-${size}`,
158
+ state === "closing" && "is-closing"
159
+ )}
160
+ {...rest}
161
+ >
162
+ {children}
163
+ </div>
164
+ </div>
165
+ </DrawerContext.Provider>
166
+ </Portal>
167
+ );
168
+ }
169
+ ) as DrawerComponent;
170
+
171
+ Drawer.displayName = "SiaDrawer";
172
+
173
+ export function DrawerHeader({ children }: { children: React.ReactNode }) {
174
+ return <div className="sia-drawer-header">{children}</div>;
175
+ }
176
+
177
+ export function DrawerTitle({ children }: { children: React.ReactNode }) {
178
+ return <h2 className="sia-drawer-title">{children}</h2>;
179
+ }
180
+
181
+ export function DrawerBody({ children }: { children: React.ReactNode }) {
182
+ return <div className="sia-drawer-body">{children}</div>;
183
+ }
184
+
185
+ export function DrawerFooter({ children }: { children: React.ReactNode }) {
186
+ return <div className="sia-drawer-footer">{children}</div>;
187
+ }
188
+
189
+ export function DrawerClose() {
190
+ const ctx = useContext(DrawerContext);
191
+ if (!ctx) return null;
192
+
193
+ return (
194
+ <button
195
+ type="button"
196
+ className="sia-drawer-close"
197
+ onClick={ctx.close}
198
+ aria-label="Fermer"
199
+ >
200
+ ✕
201
+ </button>
202
+ );
203
+ }
204
+
205
+ Drawer.Header = DrawerHeader;
206
+ Drawer.Title = DrawerTitle;
207
+ Drawer.Body = DrawerBody;
208
+ Drawer.Footer = DrawerFooter;
209
+ Drawer.Close = DrawerClose;