@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,664 @@
1
+ import {
2
+ cloneElement,
3
+ isValidElement,
4
+ useId,
5
+ type ReactElement,
6
+ type ReactNode,
7
+ } from "react";
8
+ import { cn } from "@sia-ui/utils";
9
+ import { Input, type InputProps } from "../Input";
10
+ import { Textarea, type TextareaProps } from "../Textarea";
11
+ import { CurrencyInput, type CurrencyInputProps } from "../CurrencyInput";
12
+ import { DatePicker, type DatePickerProps } from "../DatePicker";
13
+ import { TimePicker, type TimePickerProps } from "../TimePicker";
14
+ import "./styles.css";
15
+ import {
16
+ DateTimePicker,
17
+ type DateTimePickerProps,
18
+ type DateTimeValue,
19
+ } from "../DateTimePicker";
20
+ import { Select, type SelectOption, type SelectProps } from "../Select";
21
+ import {
22
+ MultiSelect,
23
+ type MultiSelectProps,
24
+ } from "../MultiSelect";
25
+ import { Radio, RadioGroup, type RadioGroupProps } from "../RadioGroup";
26
+ import { Checkbox, type CheckboxProps } from "../Checkbox";
27
+ import { Switch, type SwitchProps } from "../Switch";
28
+ import { FileUpload, type FileUploadProps } from "../FileUpload";
29
+ import { ImageUpload, type ImageUploadProps } from "../ImageUpload";
30
+ import { Slider, type SliderProps, type SliderRangeValue } from "../Slider";
31
+ import { Rating, type RatingProps } from "../Rating";
32
+ import { ColorPicker, type ColorPickerProps } from "../ColorPicker";
33
+ import { RichTextEditor, type RichTextEditorProps } from "../RichTextEditor";
34
+ import { MarkdownEditor, type MarkdownEditorProps } from "../MarkdownEditor";
35
+ import { JsonEditor, type JsonEditorProps } from "../JsonEditor";
36
+ import { OtpInput, type OtpInputProps } from "../OtpInput";
37
+ import { TagsInput, type TagsInputProps } from "../TagsInput";
38
+ import { Autocomplete, type AutocompleteProps } from "../Autocomplete";
39
+ import {
40
+ ReferenceSelect,
41
+ type ReferenceOption,
42
+ type ReferenceSelectProps,
43
+ } from "../ReferenceSelect";
44
+
45
+ export type FieldType =
46
+ | "text"
47
+ | "textarea"
48
+ | "email"
49
+ | "password"
50
+ | "phone"
51
+ | "number"
52
+ | "currency"
53
+ | "date"
54
+ | "time"
55
+ | "datetime"
56
+ | "select"
57
+ | "multiselect"
58
+ | "radio"
59
+ | "checkbox"
60
+ | "switch"
61
+ | "file"
62
+ | "image"
63
+ | "slider"
64
+ | "rating"
65
+ | "color"
66
+ | "rich-text"
67
+ | "markdown"
68
+ | "json"
69
+ | "otp"
70
+ | "tags"
71
+ | "autocomplete"
72
+ | "reference"
73
+ | "hidden";
74
+ export type FieldStatus = "default" | "error" | "success" | "warning";
75
+
76
+ type FieldControlProps = {
77
+ id?: string;
78
+ "aria-describedby"?: string;
79
+ "aria-labelledby"?: string;
80
+ "aria-invalid"?: boolean;
81
+ "aria-required"?: boolean;
82
+ };
83
+ export type FieldValue =
84
+ | string
85
+ | number
86
+ | SliderRangeValue
87
+ | boolean
88
+ | string[]
89
+ | DateTimeValue
90
+ | File[]
91
+ | File
92
+ | null
93
+ | undefined;
94
+ type ControlProps = Partial<
95
+ InputProps &
96
+ TextareaProps &
97
+ CurrencyInputProps &
98
+ DatePickerProps &
99
+ TimePickerProps &
100
+ DateTimePickerProps &
101
+ SelectProps &
102
+ MultiSelectProps &
103
+ RadioGroupProps &
104
+ CheckboxProps &
105
+ SwitchProps &
106
+ FileUploadProps &
107
+ ImageUploadProps &
108
+ SliderProps &
109
+ RatingProps &
110
+ ColorPickerProps &
111
+ RichTextEditorProps &
112
+ MarkdownEditorProps &
113
+ JsonEditorProps &
114
+ OtpInputProps &
115
+ TagsInputProps &
116
+ AutocompleteProps &
117
+ ReferenceSelectProps
118
+ >;
119
+
120
+ /**
121
+ * Un branchement de formulaire, quel que soit le type exact de sa valeur.
122
+ *
123
+ * `onChange` est écrit en syntaxe de méthode, et ce n'est pas un détail :
124
+ * TypeScript compare alors ses paramètres de façon bivariante. Sans cela, un
125
+ * `FieldBinding<string>` — ce que rend `form.bind("email")` — serait refusé
126
+ * ici, puisque le champ manipule une valeur plus large que la chaîne. Il
127
+ * faudrait un `as` à chaque branchement.
128
+ */
129
+ export interface FieldBindingLike {
130
+ name: string;
131
+ value: FieldValue;
132
+ onChange(value: unknown): void;
133
+ onBlur(): void;
134
+ error?: string | undefined;
135
+ disabled?: boolean | undefined;
136
+ required?: boolean | undefined;
137
+ }
138
+
139
+ export interface FieldProps {
140
+ /**
141
+ * Le branchement d'un formulaire, en une prop.
142
+ *
143
+ * `<Field label="Courriel" field={form.bind("email")} />` : nom, valeur,
144
+ * changement, sortie de champ et message d'erreur arrivent ensemble. Le
145
+ * champ ne sait pas d'où vient l'objet — `useLocalForm`, un adaptateur
146
+ * react-hook-form, ou trois `useState`. C'est tout l'intérêt du contrat :
147
+ * aucune bibliothèque de formulaires n'est importée ici.
148
+ *
149
+ * Les props écrites explicitement l'emportent, pour corriger un cas isolé
150
+ * sans démonter le branchement.
151
+ */
152
+ field?: FieldBindingLike | undefined;
153
+ /** Appelé quand le champ est quitté. Déclenche la validation « au blur ». */
154
+ onBlur?: (() => void) | undefined;
155
+ type?: FieldType;
156
+ label?: ReactNode;
157
+ helpText?: ReactNode;
158
+ description?: ReactNode;
159
+ status?: FieldStatus;
160
+ message?: ReactNode;
161
+ error?: ReactNode;
162
+ warning?: ReactNode;
163
+ success?: ReactNode;
164
+ required?: boolean;
165
+ optional?: boolean;
166
+ optionalLabel?: ReactNode;
167
+ htmlFor?: string;
168
+ orientation?: "vertical" | "horizontal";
169
+ className?: string;
170
+ name?: string;
171
+ value?: FieldValue;
172
+ defaultValue?: FieldValue;
173
+ onValueChange?: (value: FieldValue) => void;
174
+ options?: SelectOption[];
175
+ referenceOptions?: ReferenceOption[];
176
+ controlLabel?: ReactNode;
177
+ placeholder?: string;
178
+ disabled?: boolean;
179
+ controlProps?: ControlProps;
180
+ children?: ReactElement<FieldControlProps>;
181
+ }
182
+
183
+ function joinIds(...values: Array<string | undefined>) {
184
+ return values.filter(Boolean).join(" ") || undefined;
185
+ }
186
+
187
+ function createControl(props: FieldProps): ReactElement<FieldControlProps> {
188
+ const {
189
+ type = "text",
190
+ name,
191
+ value,
192
+ defaultValue,
193
+ onValueChange,
194
+ options = [],
195
+ referenceOptions = [],
196
+ controlLabel,
197
+ placeholder,
198
+ disabled,
199
+ controlProps = {},
200
+ } = props;
201
+ const stringValue = typeof value === "string" ? value : undefined;
202
+ const stringDefault =
203
+ typeof defaultValue === "string" ? defaultValue : undefined;
204
+ const numberValue = typeof value === "number" ? value : undefined;
205
+ const numberDefault =
206
+ typeof defaultValue === "number" ? defaultValue : undefined;
207
+ const rangeValue =
208
+ Array.isArray(value) &&
209
+ value.length === 2 &&
210
+ value.every((item) => typeof item === "number")
211
+ ? (value as SliderRangeValue)
212
+ : undefined;
213
+ const rangeDefault =
214
+ Array.isArray(defaultValue) &&
215
+ defaultValue.length === 2 &&
216
+ defaultValue.every((item) => typeof item === "number")
217
+ ? (defaultValue as SliderRangeValue)
218
+ : undefined;
219
+ const booleanValue = typeof value === "boolean" ? value : undefined;
220
+ const booleanDefault =
221
+ typeof defaultValue === "boolean" ? defaultValue : undefined;
222
+ const arrayValue =
223
+ Array.isArray(value) && value.every((item) => typeof item === "string")
224
+ ? (value as string[])
225
+ : undefined;
226
+ const arrayDefault =
227
+ Array.isArray(defaultValue) &&
228
+ defaultValue.every((item) => typeof item === "string")
229
+ ? (defaultValue as string[])
230
+ : undefined;
231
+ const inputProps = controlProps as InputProps;
232
+ const stringState = {
233
+ ...(stringValue !== undefined ? { value: stringValue } : {}),
234
+ ...(stringDefault !== undefined ? { defaultValue: stringDefault } : {}),
235
+ };
236
+ const numberState = {
237
+ ...(numberValue !== undefined ? { value: numberValue } : {}),
238
+ ...(numberDefault !== undefined ? { defaultValue: numberDefault } : {}),
239
+ };
240
+ const arrayState = {
241
+ ...(arrayValue !== undefined ? { value: arrayValue } : {}),
242
+ ...(arrayDefault !== undefined ? { defaultValue: arrayDefault } : {}),
243
+ };
244
+ const common = {
245
+ ...(name ? { name } : {}),
246
+ ...(placeholder !== undefined ? { placeholder } : {}),
247
+ ...(disabled !== undefined ? { disabled } : {}),
248
+ };
249
+
250
+ if (["text", "email", "password", "phone", "number", "hidden"].includes(type))
251
+ return (
252
+ <Input
253
+ {...inputProps}
254
+ type={type === "phone" ? "tel" : type}
255
+ name={name}
256
+ value={typeof value === "number" ? value : stringValue}
257
+ defaultValue={
258
+ typeof defaultValue === "number" ? defaultValue : stringDefault
259
+ }
260
+ placeholder={placeholder}
261
+ disabled={disabled}
262
+ onChange={(event) =>
263
+ onValueChange?.(
264
+ type === "number" ? event.target.valueAsNumber : event.target.value,
265
+ )
266
+ }
267
+ />
268
+ );
269
+ if (type === "textarea")
270
+ return (
271
+ <Textarea
272
+ {...(controlProps as TextareaProps)}
273
+ name={name}
274
+ value={stringValue}
275
+ defaultValue={stringDefault}
276
+ placeholder={placeholder}
277
+ disabled={disabled}
278
+ onChange={(event) => onValueChange?.(event.target.value)}
279
+ />
280
+ );
281
+ if (type === "currency")
282
+ return (
283
+ <CurrencyInput
284
+ {...(controlProps as CurrencyInputProps)}
285
+ name={name}
286
+ value={numberValue ?? null}
287
+ defaultValue={numberDefault ?? null}
288
+ placeholder={placeholder}
289
+ disabled={disabled}
290
+ onValueChange={(next) => onValueChange?.(next ?? undefined)}
291
+ />
292
+ );
293
+ if (type === "date")
294
+ return (
295
+ <DatePicker
296
+ {...(controlProps as DatePickerProps)}
297
+ {...common}
298
+ {...stringState}
299
+ onValueChange={(next) => onValueChange?.(next)}
300
+ />
301
+ );
302
+ if (type === "time")
303
+ return (
304
+ <TimePicker
305
+ {...(controlProps as TimePickerProps)}
306
+ {...common}
307
+ {...stringState}
308
+ onValueChange={(next) => onValueChange?.(next)}
309
+ />
310
+ );
311
+ if (type === "datetime") {
312
+ const dateTimeState = {
313
+ ...(value !== undefined ? { value: value as DateTimeValue } : {}),
314
+ ...(defaultValue !== undefined
315
+ ? { defaultValue: defaultValue as DateTimeValue }
316
+ : {}),
317
+ };
318
+ return (
319
+ <DateTimePicker
320
+ {...(controlProps as DateTimePickerProps)}
321
+ {...dateTimeState}
322
+ onValueChange={(next) => onValueChange?.(next)}
323
+ />
324
+ );
325
+ }
326
+ if (type === "select")
327
+ return (
328
+ <Select
329
+ {...(controlProps as SelectProps)}
330
+ options={options}
331
+ {...common}
332
+ {...stringState}
333
+ onValueChange={(next) => onValueChange?.(next)}
334
+ />
335
+ );
336
+ if (type === "multiselect")
337
+ return (
338
+ <MultiSelect
339
+ {...(controlProps as MultiSelectProps)}
340
+ options={options}
341
+ {...arrayState}
342
+ {...(placeholder !== undefined ? { placeholder } : {})}
343
+ {...(disabled !== undefined ? { disabled } : {})}
344
+ onValueChange={(next) => onValueChange?.(next)}
345
+ />
346
+ );
347
+ if (type === "radio")
348
+ return (
349
+ <RadioGroup
350
+ {...(controlProps as RadioGroupProps)}
351
+ {...(name ? { name } : {})}
352
+ {...(disabled !== undefined ? { disabled } : {})}
353
+ {...(stringValue !== undefined ? { value: stringValue } : {})}
354
+ {...(stringDefault !== undefined ? { defaultValue: stringDefault } : {})}
355
+ onValueChange={(next) => onValueChange?.(next)}
356
+ >
357
+ {options.map((option) => (
358
+ <Radio
359
+ key={option.value}
360
+ value={option.value}
361
+ label={option.label}
362
+ {...(option.disabled !== undefined
363
+ ? { disabled: option.disabled }
364
+ : {})}
365
+ />
366
+ ))}
367
+ </RadioGroup>
368
+ );
369
+ if (type === "checkbox")
370
+ return (
371
+ <Checkbox
372
+ {...(controlProps as CheckboxProps)}
373
+ name={name}
374
+ label={controlLabel}
375
+ disabled={disabled}
376
+ checked={booleanValue}
377
+ defaultChecked={booleanDefault}
378
+ onChange={(event) => onValueChange?.(event.target.checked)}
379
+ />
380
+ );
381
+ if (type === "switch")
382
+ return (
383
+ <Switch
384
+ {...(controlProps as SwitchProps)}
385
+ {...(name ? { name } : {})}
386
+ {...(controlLabel ? { label: controlLabel } : {})}
387
+ {...(disabled !== undefined ? { disabled } : {})}
388
+ {...(booleanValue !== undefined ? { checked: booleanValue } : {})}
389
+ {...(booleanDefault !== undefined
390
+ ? { defaultChecked: booleanDefault }
391
+ : {})}
392
+ onCheckedChange={(next) => onValueChange?.(next)}
393
+ />
394
+ );
395
+ if (type === "file")
396
+ return (
397
+ <FileUpload
398
+ {...(controlProps as FileUploadProps)}
399
+ name={name}
400
+ disabled={disabled}
401
+ onFilesChange={(files) => onValueChange?.(files)}
402
+ />
403
+ );
404
+ if (type === "image")
405
+ return (
406
+ <ImageUpload
407
+ {...(controlProps as ImageUploadProps)}
408
+ {...(disabled !== undefined ? { disabled } : {})}
409
+ {...(stringValue !== undefined ? { value: stringValue } : {})}
410
+ onValueChange={(file) => onValueChange?.(file)}
411
+ />
412
+ );
413
+ if (type === "slider") {
414
+ const {
415
+ value: _controlValue,
416
+ defaultValue: _controlDefaultValue,
417
+ onValueChange: _controlOnValueChange,
418
+ name: _controlName,
419
+ disabled: _controlDisabled,
420
+ ...sliderControlProps
421
+ } = controlProps as SliderProps;
422
+ return (
423
+ <Slider
424
+ {...sliderControlProps}
425
+ {...(name !== undefined ? { name } : {})}
426
+ {...(disabled !== undefined ? { disabled } : {})}
427
+ {...(rangeValue !== undefined || numberValue !== undefined
428
+ ? { value: (rangeValue ?? numberValue)! }
429
+ : {})}
430
+ {...(rangeDefault !== undefined || numberDefault !== undefined
431
+ ? { defaultValue: (rangeDefault ?? numberDefault)! }
432
+ : {})}
433
+ onValueChange={(next) => onValueChange?.(next)}
434
+ />
435
+ );
436
+ }
437
+ if (type === "rating")
438
+ return (
439
+ <Rating
440
+ {...(controlProps as RatingProps)}
441
+ {...(disabled !== undefined ? { disabled } : {})}
442
+ {...numberState}
443
+ onValueChange={(next) => onValueChange?.(next)}
444
+ />
445
+ );
446
+ if (type === "color")
447
+ return (
448
+ <ColorPicker
449
+ {...(controlProps as ColorPickerProps)}
450
+ name={name}
451
+ disabled={disabled}
452
+ value={stringValue}
453
+ defaultValue={stringDefault}
454
+ onValueChange={(next) => onValueChange?.(next)}
455
+ />
456
+ );
457
+ if (type === "rich-text")
458
+ return (
459
+ <RichTextEditor
460
+ {...(controlProps as RichTextEditorProps)}
461
+ {...stringState}
462
+ {...(placeholder !== undefined ? { placeholder } : {})}
463
+ {...(disabled !== undefined ? { disabled } : {})}
464
+ onValueChange={(next) => onValueChange?.(next)}
465
+ />
466
+ );
467
+ if (type === "markdown")
468
+ return (
469
+ <MarkdownEditor
470
+ {...(controlProps as MarkdownEditorProps)}
471
+ {...stringState}
472
+ {...(placeholder !== undefined ? { placeholder } : {})}
473
+ {...(disabled !== undefined ? { disabled } : {})}
474
+ onValueChange={(next) => onValueChange?.(next)}
475
+ />
476
+ );
477
+ if (type === "json")
478
+ return (
479
+ <JsonEditor
480
+ {...(controlProps as JsonEditorProps)}
481
+ {...stringState}
482
+ {...(placeholder !== undefined ? { placeholder } : {})}
483
+ {...(disabled !== undefined ? { disabled } : {})}
484
+ onValueChange={(next) => onValueChange?.(next)}
485
+ />
486
+ );
487
+ if (type === "otp")
488
+ return (
489
+ <OtpInput
490
+ {...(controlProps as OtpInputProps)}
491
+ {...stringState}
492
+ {...(disabled !== undefined ? { disabled } : {})}
493
+ onValueChange={(next) => onValueChange?.(next)}
494
+ />
495
+ );
496
+ if (type === "tags")
497
+ return (
498
+ <TagsInput
499
+ {...(controlProps as TagsInputProps)}
500
+ {...arrayState}
501
+ {...(placeholder !== undefined ? { placeholder } : {})}
502
+ {...(disabled !== undefined ? { disabled } : {})}
503
+ onValueChange={(next) => onValueChange?.(next)}
504
+ />
505
+ );
506
+ if (type === "autocomplete")
507
+ return (
508
+ <Autocomplete
509
+ {...(controlProps as AutocompleteProps)}
510
+ options={options}
511
+ {...stringState}
512
+ {...(placeholder !== undefined ? { placeholder } : {})}
513
+ {...(disabled !== undefined ? { disabled } : {})}
514
+ onValueChange={(next) => onValueChange?.(next)}
515
+ />
516
+ );
517
+ return (
518
+ <ReferenceSelect
519
+ {...(controlProps as ReferenceSelectProps)}
520
+ options={referenceOptions.length ? referenceOptions : options}
521
+ {...stringState}
522
+ {...(placeholder !== undefined ? { placeholder } : {})}
523
+ {...(disabled !== undefined ? { disabled } : {})}
524
+ onValueChange={(next) => onValueChange?.(next)}
525
+ />
526
+ );
527
+ }
528
+
529
+ /**
530
+ * Fond le `field` dans les props, sans jamais écraser ce qui est écrit.
531
+ *
532
+ * Fait une fois, en tête de `Field` : `createControl` reçoit ensuite des
533
+ * props ordinaires et n'a pas à connaître le contrat.
534
+ */
535
+ function applyBinding(props: FieldProps): FieldProps {
536
+ const { field } = props;
537
+ if (!field) return props;
538
+
539
+ return {
540
+ ...props,
541
+ name: props.name ?? field.name,
542
+ value: props.value !== undefined ? props.value : field.value,
543
+ onValueChange: (next) => {
544
+ field.onChange(next);
545
+ props.onValueChange?.(next);
546
+ },
547
+ onBlur: () => {
548
+ field.onBlur();
549
+ props.onBlur?.();
550
+ },
551
+ error: props.error ?? field.error,
552
+ ...pick("required", props.required ?? field.required),
553
+ ...pick("disabled", props.disabled ?? field.disabled),
554
+ };
555
+ }
556
+
557
+ /** Omet la clé plutôt que d'écrire `undefined` — `exactOptionalPropertyTypes`. */
558
+ function pick<K extends string, V>(key: K, value: V | undefined) {
559
+ return (value === undefined ? {} : { [key]: value }) as { [P in K]?: V };
560
+ }
561
+
562
+ export function Field(rawProps: FieldProps) {
563
+ const props = applyBinding(rawProps);
564
+ const {
565
+ onBlur,
566
+ type = "text",
567
+ label,
568
+ helpText,
569
+ description,
570
+ status = "default",
571
+ message,
572
+ error,
573
+ warning,
574
+ success,
575
+ required = false,
576
+ optional = false,
577
+ optionalLabel = "Optionnel",
578
+ htmlFor,
579
+ orientation = "vertical",
580
+ className,
581
+ children,
582
+ } = props;
583
+ const generatedControlId = useId();
584
+ const labelId = useId();
585
+ const helpTextId = useId();
586
+ const messageId = useId();
587
+ const source = children ?? createControl(props);
588
+ const controlId = htmlFor ?? source.props.id ?? generatedControlId;
589
+ const resolvedHelpText = helpText ?? description;
590
+ const resolvedStatus: FieldStatus = error
591
+ ? "error"
592
+ : warning
593
+ ? "warning"
594
+ : success
595
+ ? "success"
596
+ : status;
597
+ const resolvedMessage = error ?? warning ?? success ?? message;
598
+ const describedBy = joinIds(
599
+ source.props["aria-describedby"],
600
+ resolvedHelpText ? helpTextId : undefined,
601
+ resolvedMessage ? messageId : undefined,
602
+ );
603
+ const labelledBy = joinIds(
604
+ source.props["aria-labelledby"],
605
+ label ? labelId : undefined,
606
+ );
607
+ const controlProps: FieldControlProps = { id: controlId };
608
+ if (describedBy) controlProps["aria-describedby"] = describedBy;
609
+ if (labelledBy) controlProps["aria-labelledby"] = labelledBy;
610
+ if (resolvedStatus === "error") controlProps["aria-invalid"] = true;
611
+ else if (source.props["aria-invalid"] !== undefined)
612
+ controlProps["aria-invalid"] = source.props["aria-invalid"];
613
+ if (required) controlProps["aria-required"] = true;
614
+ const control = isValidElement<FieldControlProps>(source)
615
+ ? cloneElement(source, controlProps)
616
+ : source;
617
+ if (type === "hidden" && !children) return control;
618
+ return (
619
+ <div
620
+ className={cn(
621
+ "sia-field",
622
+ `sia-field--${orientation}`,
623
+ `sia-field--${resolvedStatus}`,
624
+ className,
625
+ )}
626
+ data-status={resolvedStatus}
627
+ data-required={required || undefined}
628
+ >
629
+ <div className="sia-field__header">
630
+ {label && (
631
+ <label id={labelId} htmlFor={controlId} className="sia-field__label">
632
+ {label}
633
+ {required && (
634
+ <span className="sia-field__required" aria-hidden="true">
635
+ {" "}
636
+ *
637
+ </span>
638
+ )}
639
+ </label>
640
+ )}
641
+ {optional && !required && (
642
+ <span className="sia-field__optional">{optionalLabel}</span>
643
+ )}
644
+ </div>
645
+ <div className="sia-field__control" {...(onBlur ? { onBlur } : {})}>
646
+ {control}
647
+ </div>
648
+ {resolvedHelpText && (
649
+ <div id={helpTextId} className="sia-field__help">
650
+ {resolvedHelpText}
651
+ </div>
652
+ )}
653
+ {resolvedMessage && (
654
+ <div
655
+ id={messageId}
656
+ role={resolvedStatus === "error" ? "alert" : "status"}
657
+ className="sia-field__message"
658
+ >
659
+ {resolvedMessage}
660
+ </div>
661
+ )}
662
+ </div>
663
+ );
664
+ }
@@ -0,0 +1,14 @@
1
+ .sia-field__header { display:flex; align-items:baseline; justify-content:space-between; gap:.75rem; }
2
+ .sia-field__label,.sia-form-field__label { font-weight:600; }
3
+ .sia-field__required { color:var(--sia-danger); }
4
+ .sia-field__optional { color:var(--sia-muted); font-size:.75rem; }
5
+ .sia-field__control { min-width:0; }
6
+ .sia-field__help,.sia-form-field__description { color:var(--sia-muted); font-size:.875rem; }
7
+ .sia-field__message,.sia-form-field__error { font-size:.875rem; }
8
+ .sia-field--error .sia-field__message { color:var(--sia-danger); }
9
+ .sia-field--success .sia-field__message { color:var(--sia-success); }
10
+ .sia-field--warning .sia-field__message { color:var(--sia-warning); }
11
+ .sia-field--horizontal { display:grid; grid-template-columns:minmax(8rem,14rem) minmax(0,1fr); align-items:start; }
12
+ .sia-field--horizontal .sia-field__header { grid-column:1; }
13
+ .sia-field--horizontal .sia-field__control { grid-column:2; }
14
+ .sia-field--horizontal .sia-field__help,.sia-field--horizontal .sia-field__message { grid-column:2; }