@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,185 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ type IconProps = SVGProps<SVGSVGElement>;
4
+
5
+ function IconBase({ children, ...props }: IconProps) {
6
+ return (
7
+ <svg
8
+ viewBox="0 0 24 24"
9
+ width="1em"
10
+ height="1em"
11
+ fill="none"
12
+ stroke="currentColor"
13
+ strokeWidth="1.8"
14
+ strokeLinecap="round"
15
+ strokeLinejoin="round"
16
+ aria-hidden="true"
17
+ focusable="false"
18
+ {...props}
19
+ >
20
+ {children}
21
+ </svg>
22
+ );
23
+ }
24
+
25
+ export function CalendarIcon(props: IconProps) {
26
+ return (
27
+ <IconBase {...props}>
28
+ <path d="M7 2v3M17 2v3M3.5 9h17" />
29
+ <rect x="3.5" y="4" width="17" height="17" rx="2.5" />
30
+ <path d="M8 13h.01M12 13h.01M16 13h.01M8 17h.01M12 17h.01" />
31
+ </IconBase>
32
+ );
33
+ }
34
+ export function ClockIcon(props: IconProps) {
35
+ return (
36
+ <IconBase {...props}>
37
+ <circle cx="12" cy="12" r="9" />
38
+ <path d="M12 7v5l3 2" />
39
+ </IconBase>
40
+ );
41
+ }
42
+ export function ChevronLeftIcon(props: IconProps) {
43
+ return (
44
+ <IconBase {...props}>
45
+ <path d="m15 18-6-6 6-6" />
46
+ </IconBase>
47
+ );
48
+ }
49
+ export function ChevronRightIcon(props: IconProps) {
50
+ return (
51
+ <IconBase {...props}>
52
+ <path d="m9 18 6-6-6-6" />
53
+ </IconBase>
54
+ );
55
+ }
56
+ export function ChevronDownIcon(props: IconProps) {
57
+ return (
58
+ <IconBase {...props}>
59
+ <path d="m6 9 6 6 6-6" />
60
+ </IconBase>
61
+ );
62
+ }
63
+ export function XIcon(props: IconProps) {
64
+ return (
65
+ <IconBase {...props}>
66
+ <path d="m6 6 12 12M18 6 6 18" />
67
+ </IconBase>
68
+ );
69
+ }
70
+ export function ArrowRightIcon(props: IconProps) {
71
+ return (
72
+ <IconBase {...props}>
73
+ <path d="M5 12h14M13 6l6 6-6 6" />
74
+ </IconBase>
75
+ );
76
+ }
77
+ export function CheckIcon(props: IconProps) {
78
+ return (
79
+ <IconBase {...props}>
80
+ <path d="m5 12 4 4L19 6" />
81
+ </IconBase>
82
+ );
83
+ }
84
+
85
+ /* Ajoutées pour remplacer les caractères de texte qui servaient d'icônes.
86
+ Un glyphe hérite de la police du système : il ne se dimensionne pas, ne
87
+ s'anime pas, et sur certaines fontes il ne s'affiche pas du tout. */
88
+
89
+ export function ChevronUpIcon(props: IconProps) {
90
+ return (
91
+ <IconBase {...props}>
92
+ <path d="m18 15-6-6-6 6" />
93
+ </IconBase>
94
+ );
95
+ }
96
+ export function SearchIcon(props: IconProps) {
97
+ return (
98
+ <IconBase {...props}>
99
+ <circle cx="11" cy="11" r="7" />
100
+ <path d="m20 20-3.5-3.5" />
101
+ </IconBase>
102
+ );
103
+ }
104
+ export function StarIcon({ fill = "none", ...props }: IconProps) {
105
+ return (
106
+ <IconBase {...props}>
107
+ <path
108
+ fill={fill}
109
+ d="m12 3.5 2.6 5.3 5.9.9-4.3 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.3-4.1 5.9-.9Z"
110
+ />
111
+ </IconBase>
112
+ );
113
+ }
114
+ export function PlusIcon(props: IconProps) {
115
+ return (
116
+ <IconBase {...props}>
117
+ <path d="M12 5v14M5 12h14" />
118
+ </IconBase>
119
+ );
120
+ }
121
+ export function MinusIcon(props: IconProps) {
122
+ return (
123
+ <IconBase {...props}>
124
+ <path d="M5 12h14" />
125
+ </IconBase>
126
+ );
127
+ }
128
+ export function MoreHorizontalIcon(props: IconProps) {
129
+ return (
130
+ <IconBase {...props}>
131
+ <circle cx="5" cy="12" r="1" />
132
+ <circle cx="12" cy="12" r="1" />
133
+ <circle cx="19" cy="12" r="1" />
134
+ </IconBase>
135
+ );
136
+ }
137
+ export function TrashIcon(props: IconProps) {
138
+ return (
139
+ <IconBase {...props}>
140
+ <path d="M4 7h16M10 11v6M14 11v6" />
141
+ <path d="M6 7l1 13h10l1-13" />
142
+ <path d="M9 7V4h6v3" />
143
+ </IconBase>
144
+ );
145
+ }
146
+ export function UploadIcon(props: IconProps) {
147
+ return (
148
+ <IconBase {...props}>
149
+ <path d="M12 16V4M8 8l4-4 4 4" />
150
+ <path d="M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3" />
151
+ </IconBase>
152
+ );
153
+ }
154
+ export function AlertCircleIcon(props: IconProps) {
155
+ return (
156
+ <IconBase {...props}>
157
+ <circle cx="12" cy="12" r="9" />
158
+ <path d="M12 7v6M12 16.5h.01" />
159
+ </IconBase>
160
+ );
161
+ }
162
+ export function InfoIcon(props: IconProps) {
163
+ return (
164
+ <IconBase {...props}>
165
+ <circle cx="12" cy="12" r="9" />
166
+ <path d="M12 11v5M12 7.5h.01" />
167
+ </IconBase>
168
+ );
169
+ }
170
+ export function EyeIcon(props: IconProps) {
171
+ return (
172
+ <IconBase {...props}>
173
+ <path d="M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12Z" />
174
+ <circle cx="12" cy="12" r="3" />
175
+ </IconBase>
176
+ );
177
+ }
178
+ export function EyeOffIcon(props: IconProps) {
179
+ return (
180
+ <IconBase {...props}>
181
+ <path d="M3 3l18 18" />
182
+ <path d="M10.6 6.1A9.9 9.9 0 0 1 12 6c6.5 0 10 6 10 6a17 17 0 0 1-3.1 3.9M6.6 6.7A17 17 0 0 0 2 12s3.5 6 10 6a9.6 9.6 0 0 0 3.4-.6" />
183
+ </IconBase>
184
+ );
185
+ }
@@ -0,0 +1,6 @@
1
+ import { useEffect, useState } from "react";
2
+ import { FileUpload, type FileUploadProps } from "../FileUpload";
3
+ import { cn } from "@sia-ui/utils";
4
+ import "./styles.css";
5
+ export interface ImageUploadProps extends Omit<FileUploadProps, "accept" | "onFilesChange"> { accept?: string; value?: string; onValueChange?: (file: File | null, previewUrl?: string) => void; previewAlt?: string; }
6
+ export function ImageUpload({ accept = "image/*", value, onValueChange, previewAlt = "Aperçu de l'image", className, ...props }: ImageUploadProps) { const [preview, setPreview] = useState(value); useEffect(() => setPreview(value), [value]); useEffect(() => () => { if (preview?.startsWith("blob:")) URL.revokeObjectURL(preview); }, [preview]); const select = (files: File[]) => { const file = files[0]; if (!file) return; const next = URL.createObjectURL(file); setPreview(next); onValueChange?.(file, next); }; return <div className={cn("sia-image-upload", className)}>{preview && <div className="sia-image-upload__preview"><img src={preview} alt={previewAlt} /><button type="button" onClick={() => { setPreview(undefined); onValueChange?.(null); }}>Supprimer</button></div>}<FileUpload {...props} accept={accept} multiple={false} onFilesChange={select} /></div>; }
@@ -0,0 +1,4 @@
1
+ .sia-image-upload { display:grid; gap:.75rem; }
2
+ .sia-image-upload__preview { position:relative; width:min(100%,20rem); overflow:hidden; border:1px solid var(--sia-border); border-radius:var(--sia-radius); }
3
+ .sia-image-upload__preview img { display:block; width:100%; max-height:16rem; object-fit:cover; }
4
+ .sia-image-upload__preview button { position:absolute; top:.5rem; right:.5rem; padding:.35rem .55rem; border:0; border-radius:.35rem; color:#fff; background:rgb(15 23 42 / .75); }
@@ -0,0 +1,103 @@
1
+ import {
2
+ forwardRef,
3
+ useId,
4
+ type InputHTMLAttributes,
5
+ type ReactNode,
6
+ } from "react";
7
+ import { cn, toneClass } from "@sia-ui/utils";
8
+ import { Spinner, type SpinnerProps } from "../Spinner";
9
+ import type { ComponentTone } from "@sia-ui/tokens";
10
+ import { useComponentDefaults } from "@sia-ui/headless";
11
+ import "./styles.css";
12
+
13
+ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
14
+ invalid?: boolean;
15
+ left?: ReactNode;
16
+ right?: ReactNode;
17
+ loading?: boolean;
18
+ loadingLabel?: string;
19
+ spinnerProps?: Omit<SpinnerProps, "label">;
20
+ variant?: "solid" | "outline" | "ghost" | "underline";
21
+ tone?: ComponentTone;
22
+ inputSize?: "sm" | "md" | "lg";
23
+ radius?: "default" | "none" | "sm" | "lg" | "full";
24
+ }
25
+
26
+ export const Input = forwardRef<HTMLInputElement, InputProps>(
27
+ (
28
+ {
29
+ className,
30
+ invalid,
31
+ left,
32
+ right,
33
+ loading = false,
34
+ loadingLabel: loadingLabelProp,
35
+ spinnerProps,
36
+ id: providedId,
37
+ variant = "outline",
38
+ tone = "neutral",
39
+ inputSize = "md",
40
+ radius = "default",
41
+ ...props
42
+ },
43
+ ref,
44
+ ) => {
45
+ const defaults = useComponentDefaults<InputProps>("input");
46
+ const loadingLabel = loadingLabelProp ?? defaults.loadingLabel ?? "Chargement";
47
+ const generatedId = useId();
48
+ const id = providedId ?? generatedId;
49
+ const decorated = Boolean(left || right || loading);
50
+ const visualClasses = [
51
+ `sia-input--${variant}`,
52
+ `sia-input--${inputSize}`,
53
+ `sia-radius--${radius}`,
54
+ toneClass("sia-tone", tone),
55
+ ];
56
+ const input = (
57
+ <input
58
+ ref={ref}
59
+ id={id}
60
+ className={cn(
61
+ "sia-input",
62
+ ...visualClasses,
63
+ decorated && "sia-input--embedded",
64
+ invalid && "sia-input--invalid",
65
+ className,
66
+ )}
67
+ aria-invalid={invalid || undefined}
68
+ aria-busy={loading || undefined}
69
+ {...props}
70
+ />
71
+ );
72
+
73
+ if (!decorated) return input;
74
+
75
+ return (
76
+ <span
77
+ className={cn(
78
+ "sia-input-wrapper",
79
+ ...visualClasses,
80
+ invalid && "sia-input-wrapper--invalid",
81
+ loading && "sia-input-wrapper--loading",
82
+ )}
83
+ >
84
+ {loading ? (
85
+ <span className="sia-input-wrapper__left">
86
+ <Spinner
87
+ size="sm"
88
+ tone="current"
89
+ {...spinnerProps}
90
+ label={loadingLabel}
91
+ />
92
+ </span>
93
+ ) : (
94
+ left && <span className="sia-input-wrapper__left">{left}</span>
95
+ )}
96
+ {input}
97
+ {right && <span className="sia-input-wrapper__right">{right}</span>}
98
+ </span>
99
+ );
100
+ },
101
+ );
102
+
103
+ Input.displayName = "Input";
@@ -0,0 +1,8 @@
1
+ .sia-input-wrapper { display:flex; align-items:center; width:100%; min-height:2.5rem; overflow:hidden; border:1px solid var(--sia-border); border-radius:var(--sia-radius); color:var(--sia-muted); background:var(--sia-background); }
2
+ .sia-input-wrapper:focus-within { outline:3px solid color-mix(in srgb,var(--sia-primary) 30%,transparent); outline-offset:2px; }
3
+ .sia-input-wrapper--invalid { border-color:var(--sia-danger); }
4
+ .sia-input-wrapper--loading { cursor:progress; }
5
+ .sia-input-wrapper__left,.sia-input-wrapper__right { display:inline-flex; align-items:center; justify-content:center; flex:none; padding:0 .75rem; }
6
+ .sia-input-wrapper__left { padding-right:0; }
7
+ .sia-input-wrapper__right { padding-left:0; }
8
+ .sia-input-wrapper__left svg,.sia-input-wrapper__right svg { display:block; width:1rem; height:1rem; flex:none; }
@@ -0,0 +1,196 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ type KeyboardEvent,
8
+ } from "react";
9
+ import { cn } from "@sia-ui/utils";
10
+ import { useSiaLocale } from "@sia-ui/headless";
11
+ import { Input, type InputProps } from "../Input";
12
+ import { MinusIcon, PlusIcon } from "../Icons";
13
+ import "./styles.css";
14
+
15
+ export interface InputNumberProps
16
+ extends Omit<InputProps, "value" | "defaultValue" | "onChange" | "type" | "right"> {
17
+ value?: number | null;
18
+ defaultValue?: number | null;
19
+ onValueChange?: (value: number | null) => void;
20
+ min?: number;
21
+ max?: number;
22
+ step?: number;
23
+ /** Nombre de décimales. Omis, la saisie est laissée telle quelle. */
24
+ precision?: number;
25
+ /** Cache les boutons et ne garde que les flèches du clavier. */
26
+ hideControls?: boolean;
27
+ /** Unité affichée à droite : kg, %, jours. */
28
+ suffix?: string;
29
+ }
30
+
31
+ function clamp(value: number, min: number | undefined, max: number | undefined) {
32
+ if (min !== undefined && value < min) return min;
33
+ if (max !== undefined && value > max) return max;
34
+ return value;
35
+ }
36
+
37
+ /**
38
+ * Une saisie numérique avec incrément.
39
+ *
40
+ * Le champ reste en `type="text"` avec `inputMode="decimal"` : le `number`
41
+ * natif refuse `maxLength`, accepte `1e5` et `--`, et perd silencieusement sa
42
+ * valeur quand elle ne lui plaît pas. On garde le clavier numérique sur
43
+ * mobile, on renonce au reste.
44
+ */
45
+ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
46
+ (
47
+ {
48
+ value: valueProp,
49
+ defaultValue = null,
50
+ onValueChange,
51
+ min,
52
+ max,
53
+ step = 1,
54
+ precision,
55
+ hideControls = false,
56
+ suffix,
57
+ disabled,
58
+ className,
59
+ onBlur,
60
+ onKeyDown,
61
+ ...props
62
+ },
63
+ ref,
64
+ ) => {
65
+ const locale = useSiaLocale();
66
+ const controlled = valueProp !== undefined;
67
+ const [internal, setInternal] = useState<number | null>(defaultValue);
68
+ const value = controlled ? valueProp : internal;
69
+
70
+ const format = useCallback(
71
+ (next: number | null) =>
72
+ next === null
73
+ ? ""
74
+ : precision !== undefined
75
+ ? next.toFixed(precision)
76
+ : String(next),
77
+ [precision],
78
+ );
79
+
80
+ // Le texte est tenu à part du nombre : « 12, » et « 0, » sont des états
81
+ // de frappe valides qui ne correspondent encore à aucune valeur.
82
+ const [text, setText] = useState(() => format(value));
83
+ const editing = useRef(false);
84
+
85
+ useEffect(() => {
86
+ if (!editing.current) setText(format(value));
87
+ }, [format, value]);
88
+
89
+ const commit = useCallback(
90
+ (next: number | null) => {
91
+ if (!controlled) setInternal(next);
92
+ onValueChange?.(next);
93
+ },
94
+ [controlled, onValueChange],
95
+ );
96
+
97
+ const nudge = useCallback(
98
+ (direction: 1 | -1) => {
99
+ const base = value ?? min ?? 0;
100
+ const raw = base + direction * step;
101
+ // Le pas flottant produit 0.30000000000000004 : on arrondit au
102
+ // nombre de décimales du pas, qui est la précision voulue.
103
+ const decimals =
104
+ precision ?? (String(step).split(".")[1]?.length ?? 0);
105
+ const next = clamp(Number(raw.toFixed(decimals)), min, max);
106
+ commit(next);
107
+ setText(format(next));
108
+ },
109
+ [commit, format, max, min, precision, step, value],
110
+ );
111
+
112
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
113
+ onKeyDown?.(event);
114
+ if (event.defaultPrevented) return;
115
+ if (event.key === "ArrowUp") {
116
+ event.preventDefault();
117
+ nudge(1);
118
+ } else if (event.key === "ArrowDown") {
119
+ event.preventDefault();
120
+ nudge(-1);
121
+ }
122
+ };
123
+
124
+ const atMin = min !== undefined && value !== null && value <= min;
125
+ const atMax = max !== undefined && value !== null && value >= max;
126
+
127
+ return (
128
+ <Input
129
+ {...props}
130
+ ref={ref}
131
+ type="text"
132
+ inputMode="decimal"
133
+ className={cn("sia-input-number", className)}
134
+ disabled={disabled}
135
+ value={text}
136
+ role="spinbutton"
137
+ {...(min !== undefined ? { "aria-valuemin": min } : {})}
138
+ {...(max !== undefined ? { "aria-valuemax": max } : {})}
139
+ {...(value !== null ? { "aria-valuenow": value } : {})}
140
+ onChange={(event) => {
141
+ editing.current = true;
142
+ const raw = event.target.value;
143
+ setText(raw);
144
+ if (raw.trim() === "") {
145
+ commit(null);
146
+ return;
147
+ }
148
+ // La virgule est le séparateur décimal du clavier français.
149
+ const parsed = Number(raw.replace(",", "."));
150
+ if (Number.isFinite(parsed)) commit(parsed);
151
+ }}
152
+ onBlur={(event) => {
153
+ editing.current = false;
154
+ // Le recadrage attend la sortie du champ : borner pendant la frappe
155
+ // transforme un « 5 » en route vers « 50 » en « 9 » sous les doigts.
156
+ const next = value === null ? null : clamp(value, min, max);
157
+ if (next !== value) commit(next);
158
+ setText(format(next));
159
+ onBlur?.(event);
160
+ }}
161
+ onKeyDown={handleKeyDown}
162
+ right={
163
+ hideControls && !suffix ? undefined : (
164
+ <span className="sia-input-number__side">
165
+ {suffix && <span className="sia-input-number__suffix">{suffix}</span>}
166
+ {!hideControls && (
167
+ <span className="sia-input-number__controls">
168
+ <button
169
+ type="button"
170
+ tabIndex={-1}
171
+ aria-label={locale.decrement}
172
+ disabled={disabled || atMin}
173
+ onClick={() => nudge(-1)}
174
+ >
175
+ <MinusIcon />
176
+ </button>
177
+ <button
178
+ type="button"
179
+ tabIndex={-1}
180
+ aria-label={locale.increment}
181
+ disabled={disabled || atMax}
182
+ onClick={() => nudge(1)}
183
+ >
184
+ <PlusIcon />
185
+ </button>
186
+ </span>
187
+ )}
188
+ </span>
189
+ )
190
+ }
191
+ />
192
+ );
193
+ },
194
+ );
195
+
196
+ InputNumber.displayName = "InputNumber";
@@ -0,0 +1,54 @@
1
+ .sia-input-number { text-align: left; font-variant-numeric: tabular-nums; }
2
+
3
+ .sia-input-number__side { display: inline-flex; align-items: center; gap: .4rem; }
4
+ .sia-input-number__suffix { color: var(--sia-muted); font-size: .8rem; white-space: nowrap; }
5
+
6
+ /* Un seul contrôle, pas deux pastilles côte à côte.
7
+
8
+ Bordées chacune et séparées d'un écart, les deux touches affichaient
9
+ quatre pixels de filets entre deux signes qui n'en sont distants que de
10
+ vingt-quatre : l'œil y lisait deux boutons sans rapport plutôt qu'un pas
11
+ vers le haut et un pas vers le bas. Le cadre est donc porté par le
12
+ groupe, et les touches ne partagent qu'un filet. */
13
+ .sia-input-number__controls {
14
+ display: flex;
15
+ overflow: hidden;
16
+ border: 1px solid var(--sia-border);
17
+ border-radius: calc(var(--sia-radius) - 2px);
18
+ background: var(--sia-surface);
19
+ }
20
+ .sia-input-number__controls button {
21
+ display: grid;
22
+ place-items: center;
23
+ width: 1.6rem;
24
+ height: 1.6rem;
25
+ border: 0;
26
+ background: transparent;
27
+ color: var(--sia-muted);
28
+ font-size: .72rem;
29
+ cursor: pointer;
30
+ transition:
31
+ background-color var(--sia-duration-fast) var(--sia-easing-standard),
32
+ color var(--sia-duration-fast) var(--sia-easing-standard);
33
+ }
34
+ .sia-input-number__controls button + button {
35
+ border-left: 1px solid var(--sia-border);
36
+ }
37
+ .sia-input-number__controls button:hover:not(:disabled) {
38
+ background: var(--sia-surface-raised);
39
+ color: var(--sia-foreground);
40
+ }
41
+ .sia-input-number__controls button:active:not(:disabled) {
42
+ background: color-mix(in srgb, var(--sia-primary) 12%, transparent);
43
+ color: var(--sia-primary);
44
+ }
45
+ /* Le retour tactile du pas : on doit sentir que le nombre a bougé même
46
+ quand on clique vite plusieurs fois de suite. C'est le signe qui
47
+ s'enfonce, pas la touche — le groupe rogne ce qui dépasse. */
48
+ .sia-input-number__controls button svg {
49
+ transition: transform var(--sia-duration-instant) var(--sia-easing-standard);
50
+ }
51
+ .sia-input-number__controls button:active:not(:disabled) svg {
52
+ transform: scale(.78);
53
+ }
54
+ .sia-input-number__controls button:disabled { opacity: .4; cursor: not-allowed; }
@@ -0,0 +1,6 @@
1
+ import { useMemo, useState, type TextareaHTMLAttributes } from "react";
2
+ import { Textarea } from "../Textarea";
3
+ import { cn } from "@sia-ui/utils";
4
+ import "./styles.css";
5
+ export interface JsonEditorProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "defaultValue" | "onChange"> { value?: string; defaultValue?: string; onValueChange?: (value: string, parsed: unknown | undefined) => void; formatOnBlur?: boolean; }
6
+ export function JsonEditor({ value, defaultValue = "{}", onValueChange, formatOnBlur = true, className, ...props }: JsonEditorProps) { const [internal, setInternal] = useState(defaultValue); const current = value ?? internal; const result = useMemo(() => { try { return { valid: true, parsed: JSON.parse(current) as unknown }; } catch { return { valid: false, parsed: undefined }; } }, [current]); const update = (next: string) => { let parsed: unknown | undefined; try { parsed = JSON.parse(next) as unknown; } catch { parsed = undefined; } if (value === undefined) setInternal(next); onValueChange?.(next, parsed); }; return <div className={cn("sia-code-editor", className)}><Textarea {...props} value={current} invalid={!result.valid} resize="vertical" spellCheck={false} onChange={(event) => update(event.target.value)} onBlur={(event) => { if (formatOnBlur && result.valid) update(JSON.stringify(result.parsed, null, 2)); props.onBlur?.(event); }} /><small className={result.valid ? "is-valid" : "is-invalid"}>{result.valid ? "JSON valide" : "JSON invalide"}</small></div>; }
@@ -0,0 +1,2 @@
1
+ .sia-code-editor small.is-valid { color:var(--sia-success); }
2
+ .sia-code-editor small.is-invalid { color:var(--sia-danger); }
@@ -0,0 +1,11 @@
1
+ import { type HTMLAttributes } from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import "./styles.css";
4
+
5
+ export interface KpiGridProps extends HTMLAttributes<HTMLDivElement> {
6
+ columns?: 1 | 2 | 3 | 4;
7
+ }
8
+
9
+ export function KpiGrid({ columns = 4, className, ...props }: KpiGridProps) {
10
+ return <div className={cn("sia-kpi-grid", `sia-kpi-grid--${columns}`, className)} {...props} />;
11
+ }
@@ -0,0 +1,5 @@
1
+ .sia-kpi-grid { display:grid; gap:1rem; }
2
+ .sia-kpi-grid--1 { grid-template-columns:1fr; }
3
+ .sia-kpi-grid--2 { grid-template-columns:repeat(2,minmax(0,1fr)); }
4
+ .sia-kpi-grid--3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
5
+ .sia-kpi-grid--4 { grid-template-columns:repeat(4,minmax(0,1fr)); }
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import type { ComponentTone } from "@sia-ui/tokens";
4
+ import "./styles.css";
5
+
6
+ export interface LabelProps
7
+ extends React.LabelHTMLAttributes<HTMLLabelElement> {
8
+ /** Le ton du libellé. Par défaut, la couleur du texte courant. */
9
+ tone?: ComponentTone;
10
+ /** Marque le champ comme obligatoire d'une astérisque. */
11
+ required?: boolean;
12
+ /** Atténue le libellé — une mention secondaire, une unité. */
13
+ muted?: boolean;
14
+ }
15
+
16
+ /** Alias historique, attendu par les composants venus du registre. */
17
+ export type ILabelProps = LabelProps;
18
+
19
+ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
20
+ ({ className, children, required, muted, tone, ...props }, ref) => {
21
+ return (
22
+ <label
23
+ ref={ref}
24
+ className={cn(
25
+ "sia-label",
26
+ muted && "sia-label--muted",
27
+ tone && `sia-label--${tone}`,
28
+ className,
29
+ )}
30
+ {...props}
31
+ >
32
+ {children}
33
+ {required && (
34
+ <span className="sia-label__required" aria-hidden="true">
35
+ *
36
+ </span>
37
+ )}
38
+ </label>
39
+ );
40
+ }
41
+ );
42
+
43
+ Label.displayName = "SiaLabel";
@@ -0,0 +1,25 @@
1
+ /* Les variables de ce fichier venaient d'un jeu de tokens disparu —
2
+ `--sia-color-text-900`, `--sia-font-size-md`, `--sia-color-primary-500`.
3
+ Aucune n'existe plus dans le thème généré : le libellé ne prenait donc
4
+ aucune couleur, quel que soit le ton demandé. */
5
+
6
+ .sia-label {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ gap: .25rem;
10
+ color: var(--sia-foreground);
11
+ font-size: .875rem;
12
+ font-weight: 600;
13
+ line-height: 1.4;
14
+ }
15
+
16
+ .sia-label--muted { color: var(--sia-muted); font-weight: 500; }
17
+
18
+ .sia-label__required { color: var(--sia-danger); }
19
+
20
+ .sia-label--neutral { color: var(--sia-muted); }
21
+ .sia-label--primary { color: var(--sia-primary); }
22
+ .sia-label--info { color: var(--sia-info); }
23
+ .sia-label--success { color: var(--sia-success); }
24
+ .sia-label--warning { color: var(--sia-warning); }
25
+ .sia-label--danger { color: var(--sia-danger); }
@@ -0,0 +1,5 @@
1
+ import { useState, type ReactNode, type TextareaHTMLAttributes } from "react";
2
+ import { Textarea } from "../Textarea";
3
+ import { cn } from "@sia-ui/utils";
4
+ export interface MarkdownEditorProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "defaultValue" | "onChange"> { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; preview?: (value: string) => ReactNode; }
5
+ export function MarkdownEditor({ value, defaultValue = "", onValueChange, preview, className, ...props }: MarkdownEditorProps) { const [internal, setInternal] = useState(defaultValue); const [mode, setMode] = useState<"write" | "preview">("write"); const current = value ?? internal; const update = (next: string) => { if (value === undefined) setInternal(next); onValueChange?.(next); }; return <div className={cn("sia-markdown-editor", className)}><div className="sia-editor-toolbar"><button type="button" aria-pressed={mode === "write"} onClick={() => setMode("write")}>Écrire</button><button type="button" aria-pressed={mode === "preview"} onClick={() => setMode("preview")}>Aperçu</button></div>{mode === "write" ? <Textarea {...props} value={current} onChange={(event) => update(event.target.value)} /> : <div className="sia-editor-preview">{preview ? preview(current) : <pre>{current}</pre>}</div>}</div>; }
@@ -0,0 +1,3 @@
1
+ import { Calendar, type CalendarProps } from "../Calendar";
2
+ import { cn } from "@sia-ui/utils";
3
+ export function MiniCalendar({ className, ...props }: CalendarProps) { return <Calendar {...props} className={cn("sia-calendar--mini", className)} />; }