@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,111 @@
1
+ /* Bouton
2
+ ==========================================================================
3
+ La version précédente déclarait une transition sur trois propriétés puis
4
+ n'écrivait aucune règle `:hover` ni `:active` : il n'y avait rien à animer.
5
+ C'est ce qui donnait l'impression de composants plats. */
6
+
7
+ .sia-button {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ gap: 0.5rem;
12
+ min-height: 2.5rem;
13
+ padding: 0 1rem;
14
+ border: 1px solid transparent;
15
+ border-radius: var(--sia-radius);
16
+ font: inherit;
17
+ line-height: 1.25;
18
+ font-weight: 600;
19
+ cursor: pointer;
20
+ user-select: none;
21
+
22
+ /* Chaque ton fixe sa couleur une fois; les variantes s'y réfèrent. */
23
+ --sia-button-accent: var(--sia-primary);
24
+ --sia-button-on-accent: var(--sia-primary-foreground);
25
+
26
+ transition:
27
+ transform var(--sia-duration-fast) var(--sia-easing-standard),
28
+ box-shadow var(--sia-duration-base) var(--sia-easing-standard),
29
+ background-color var(--sia-duration-fast) var(--sia-easing-standard),
30
+ border-color var(--sia-duration-fast) var(--sia-easing-standard),
31
+ color var(--sia-duration-fast) var(--sia-easing-standard);
32
+ }
33
+
34
+ /* Tons ---------------------------------------------------------------- */
35
+
36
+ .sia-button--primary { --sia-button-accent: var(--sia-primary); }
37
+ .sia-button--neutral { --sia-button-accent: var(--sia-foreground); --sia-button-on-accent: var(--sia-background); }
38
+ .sia-button--info { --sia-button-accent: var(--sia-info); }
39
+ .sia-button--success { --sia-button-accent: var(--sia-success); }
40
+ .sia-button--warning { --sia-button-accent: var(--sia-warning); }
41
+ .sia-button--danger { --sia-button-accent: var(--sia-danger); }
42
+
43
+ /* Variantes ----------------------------------------------------------- */
44
+
45
+ .sia-button--solid {
46
+ color: var(--sia-button-on-accent);
47
+ /* Un dégradé d'un seul degré : la surface cesse d'être un aplat mort sans
48
+ pour autant attirer l'œil. */
49
+ background-image: linear-gradient(
50
+ 180deg,
51
+ color-mix(in srgb, var(--sia-button-accent) 92%, #fff) 0%,
52
+ var(--sia-button-accent) 100%
53
+ );
54
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--sia-button-accent) 20%, transparent);
55
+ }
56
+
57
+ .sia-button--outline {
58
+ color: var(--sia-foreground);
59
+ background: var(--sia-background);
60
+ border-color: var(--sia-border-strong);
61
+ box-shadow: var(--sia-elevation-rest);
62
+ }
63
+
64
+ .sia-button--ghost {
65
+ color: var(--sia-button-accent);
66
+ background: transparent;
67
+ }
68
+
69
+ /* États ---------------------------------------------------------------- */
70
+
71
+ .sia-button:hover:not(:disabled) {
72
+ /* Un pixel. Assez pour que le bouton réponde, trop peu pour déplacer la
73
+ mise en page autour de lui. */
74
+ transform: translateY(-1px);
75
+ }
76
+
77
+ .sia-button--solid:hover:not(:disabled) {
78
+ filter: brightness(1.03);
79
+ box-shadow: 0 2px 6px color-mix(in srgb, var(--sia-button-accent) 22%, transparent);
80
+ }
81
+
82
+ .sia-button--outline:hover:not(:disabled) {
83
+ border-color: var(--sia-button-accent);
84
+ color: var(--sia-button-accent);
85
+ background: color-mix(in srgb, var(--sia-button-accent) 5%, var(--sia-background));
86
+ }
87
+
88
+ .sia-button--ghost:hover:not(:disabled) {
89
+ background: color-mix(in srgb, var(--sia-button-accent) 10%, transparent);
90
+ }
91
+
92
+ .sia-button:active:not(:disabled) {
93
+ transform: translateY(0) scale(0.985);
94
+ box-shadow: var(--sia-elevation-press);
95
+ transition-duration: var(--sia-duration-instant);
96
+ }
97
+
98
+ .sia-button:disabled {
99
+ cursor: not-allowed;
100
+ opacity: 0.55;
101
+ box-shadow: none;
102
+ }
103
+
104
+ .sia-button[aria-busy="true"] {
105
+ cursor: progress;
106
+ }
107
+
108
+ /* Tailles -------------------------------------------------------------- */
109
+
110
+ .sia-button--sm { min-height: 2rem; padding: 0 0.75rem; font-size: 0.875rem; }
111
+ .sia-button--lg { min-height: 3rem; padding: 0 1.25rem; }
@@ -0,0 +1,192 @@
1
+ import { useMemo, useState, type ReactNode } from "react";
2
+ import { cn, toneClass } from "@sia-ui/utils";
3
+ import type { ComponentTone } from "@sia-ui/tokens";
4
+ import { ChevronLeftIcon, ChevronRightIcon } from "../Icons";
5
+
6
+ export interface CalendarDay {
7
+ date: string;
8
+ day: number;
9
+ inCurrentMonth: boolean;
10
+ isToday: boolean;
11
+ disabled: boolean;
12
+ selected: boolean;
13
+ inRange: boolean;
14
+ rangeEdge?: "start" | "end";
15
+ }
16
+ export interface CalendarRangeValue { start: string; end: string; }
17
+ export type CalendarMode = "month" | "year";
18
+ export interface CalendarProps {
19
+ value?: string;
20
+ defaultValue?: string;
21
+ range?: CalendarRangeValue;
22
+ defaultRange?: CalendarRangeValue;
23
+ selectionMode?: "single" | "range";
24
+ month?: string;
25
+ defaultMonth?: string;
26
+ mode?: CalendarMode;
27
+ defaultMode?: CalendarMode;
28
+ onValueChange?: (value: string) => void;
29
+ onRangeChange?: (value: CalendarRangeValue) => void;
30
+ onSelect?: (value: string, source: "date" | "month" | "year") => void;
31
+ onMonthChange?: (month: string) => void;
32
+ onPanelChange?: (month: string, mode: CalendarMode) => void;
33
+ disabledDate?: (date: string) => boolean;
34
+ validRange?: [string, string];
35
+ renderDay?: (day: CalendarDay) => ReactNode;
36
+ cellRender?: (day: CalendarDay, originNode: ReactNode) => ReactNode;
37
+ headerRender?: (props: { month: string; mode: CalendarMode; onMonthChange: (month: string) => void; onModeChange: (mode: CalendarMode) => void }) => ReactNode;
38
+ renderExtraFooter?: () => ReactNode;
39
+ locale?: string;
40
+ firstDayOfWeek?: 0 | 1;
41
+ showWeek?: boolean;
42
+ fullscreen?: boolean;
43
+ tone?: ComponentTone;
44
+ className?: string;
45
+ }
46
+ const pad = (value: number) => String(value).padStart(2, "0");
47
+ const iso = (date: Date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
48
+ const monthIso = (date: Date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
49
+ const parseMonth = (value?: string) => {
50
+ const source = value ? new Date(`${value.slice(0, 7)}-01T12:00:00`) : new Date();
51
+ return Number.isNaN(source.getTime()) ? new Date() : source;
52
+ };
53
+ function weekNumber(value: string) {
54
+ const date = new Date(`${value}T12:00:00`);
55
+ const target = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
56
+ const day = target.getUTCDay() || 7;
57
+ target.setUTCDate(target.getUTCDate() + 4 - day);
58
+ const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1));
59
+ return Math.ceil((((target.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
60
+ }
61
+
62
+ export function Calendar({
63
+ value,
64
+ defaultValue,
65
+ range,
66
+ defaultRange = { start: "", end: "" },
67
+ selectionMode = "single",
68
+ month,
69
+ defaultMonth,
70
+ mode,
71
+ defaultMode = "month",
72
+ onValueChange,
73
+ onRangeChange,
74
+ onSelect,
75
+ onMonthChange,
76
+ onPanelChange,
77
+ disabledDate,
78
+ validRange,
79
+ renderDay,
80
+ cellRender,
81
+ headerRender,
82
+ renderExtraFooter,
83
+ locale = "fr-FR",
84
+ firstDayOfWeek = 1,
85
+ showWeek = false,
86
+ fullscreen = false,
87
+ tone = "primary",
88
+ className,
89
+ }: CalendarProps) {
90
+ const [selected, setSelected] = useState(defaultValue ?? "");
91
+ const [selectedRange, setSelectedRange] = useState(defaultRange);
92
+ const [view, setView] = useState(() => parseMonth(month ?? defaultMonth ?? value ?? defaultValue));
93
+ const [panelMode, setPanelMode] = useState(defaultMode);
94
+ const currentValue = value ?? selected;
95
+ const currentRange = range ?? selectedRange;
96
+ const currentMode = mode ?? panelMode;
97
+ const controlledMonth = month ? parseMonth(month) : view;
98
+ const year = controlledMonth.getFullYear();
99
+ const monthIndex = controlledMonth.getMonth();
100
+ const setMonth = (next: Date, sourceMode = currentMode) => {
101
+ if (!month) setView(next);
102
+ const nextMonth = monthIso(next);
103
+ onMonthChange?.(nextMonth);
104
+ onPanelChange?.(nextMonth, sourceMode);
105
+ };
106
+ const changeMonth = (offset: number) => setMonth(new Date(year, monthIndex + offset, 1, 12));
107
+ const changeMode = (next: CalendarMode) => {
108
+ if (mode === undefined) setPanelMode(next);
109
+ onPanelChange?.(monthIso(controlledMonth), next);
110
+ };
111
+ const isDisabled = (date: string) => Boolean(
112
+ disabledDate?.(date) ||
113
+ (validRange && (date < validRange[0] || date > validRange[1])),
114
+ );
115
+ const selectDate = (date: string) => {
116
+ if (isDisabled(date)) return;
117
+ if (selectionMode === "range") {
118
+ const next = !currentRange.start || currentRange.end
119
+ ? { start: date, end: "" }
120
+ : date < currentRange.start
121
+ ? { start: date, end: currentRange.start }
122
+ : { start: currentRange.start, end: date };
123
+ if (range === undefined) setSelectedRange(next);
124
+ onRangeChange?.(next);
125
+ } else {
126
+ if (value === undefined) setSelected(date);
127
+ onValueChange?.(date);
128
+ }
129
+ onSelect?.(date, "date");
130
+ };
131
+ const days = useMemo(() => {
132
+ const first = new Date(year, monthIndex, 1, 12);
133
+ const offset = (first.getDay() - firstDayOfWeek + 7) % 7;
134
+ const start = new Date(year, monthIndex, 1 - offset, 12);
135
+ const today = iso(new Date());
136
+ return Array.from({ length: 42 }, (_, index): CalendarDay => {
137
+ const date = new Date(start);
138
+ date.setDate(start.getDate() + index);
139
+ const dateString = iso(date);
140
+ const rangeEdge = currentRange.start === dateString ? "start" : currentRange.end === dateString ? "end" : undefined;
141
+ return {
142
+ date: dateString,
143
+ day: date.getDate(),
144
+ inCurrentMonth: date.getMonth() === monthIndex,
145
+ isToday: dateString === today,
146
+ disabled: isDisabled(dateString),
147
+ selected: currentValue === dateString || Boolean(rangeEdge),
148
+ inRange: Boolean(currentRange.start && currentRange.end && dateString >= currentRange.start && dateString <= currentRange.end),
149
+ ...(rangeEdge ? { rangeEdge } : {}),
150
+ };
151
+ });
152
+ }, [year, monthIndex, firstDayOfWeek, disabledDate, validRange, currentValue, currentRange.start, currentRange.end]);
153
+ const weekdays = Array.from({ length: 7 }, (_, index) => new Intl.DateTimeFormat(locale, { weekday: "short" }).format(new Date(2024, 0, 7 + firstDayOfWeek + index)));
154
+ const monthName = new Intl.DateTimeFormat(locale, { month: "long" }).format(controlledMonth);
155
+ const updateFromString = (next: string) => setMonth(parseMonth(next));
156
+
157
+ return (
158
+ <section className={cn("sia-calendar", `sia-calendar--${currentMode}`, fullscreen && "sia-calendar--fullscreen", toneClass("sia-tone", tone), className)}>
159
+ {headerRender?.({ month: monthIso(controlledMonth), mode: currentMode, onMonthChange: updateFromString, onModeChange: changeMode }) ?? (
160
+ <header className="sia-calendar__header">
161
+ <button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ? "Mois précédent" : "Année précédente"} onClick={() => currentMode === "month" ? changeMonth(-1) : setMonth(new Date(year - 1, monthIndex, 1, 12))}><ChevronLeftIcon /></button>
162
+ <div className="sia-calendar__heading">
163
+ <button type="button" onClick={() => changeMode(currentMode === "month" ? "year" : "month")}>{currentMode === "month" ? monthName : year}</button>
164
+ {currentMode === "month" && <button type="button" onClick={() => changeMode("year")}>{year}</button>}
165
+ </div>
166
+ <button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ? "Mois suivant" : "Année suivante"} onClick={() => currentMode === "month" ? changeMonth(1) : setMonth(new Date(year + 1, monthIndex, 1, 12))}><ChevronRightIcon /></button>
167
+ </header>
168
+ )}
169
+ {currentMode === "year" ? (
170
+ <div className="sia-calendar__months">
171
+ {Array.from({ length: 12 }, (_, index) => {
172
+ const date = new Date(year, index, 1, 12);
173
+ return <button type="button" key={index} data-selected={index === monthIndex || undefined} onClick={() => { setMonth(date, "month"); changeMode("month"); onSelect?.(monthIso(date), "month"); }}>{new Intl.DateTimeFormat(locale, { month: "short" }).format(date)}</button>;
174
+ })}
175
+ </div>
176
+ ) : (
177
+ <>
178
+ <div className={cn("sia-calendar__weekdays", showWeek && "sia-calendar__with-week")}>
179
+ {weekdays.map((day) => <span key={day}>{day}</span>)}
180
+ </div>
181
+ <div className={cn("sia-calendar__grid", showWeek && "sia-calendar__with-week")}>
182
+ {days.map((day, index) => {
183
+ const originNode = <button type="button" disabled={day.disabled} aria-label={day.date} aria-pressed={day.selected} onClick={() => selectDate(day.date)}>{day.day}</button>;
184
+ return <div className="sia-calendar__cell" key={day.date} data-outside={!day.inCurrentMonth || undefined} data-today={day.isToday || undefined} data-in-range={day.inRange || undefined} data-range-edge={day.rangeEdge}>{showWeek && index % 7 === 0 && <span className="sia-calendar__week-number">{weekNumber(day.date)}</span>}{cellRender?.(day, originNode) ?? originNode}{renderDay?.(day)}</div>;
185
+ })}
186
+ </div>
187
+ </>
188
+ )}
189
+ {renderExtraFooter && <footer className="sia-calendar__footer">{renderExtraFooter()}</footer>}
190
+ </section>
191
+ );
192
+ }
@@ -0,0 +1,8 @@
1
+ import { type HTMLAttributes } from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import "./styles.css";
4
+
5
+ export function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("sia-card", className)} {...props} />; }
6
+ export function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("sia-card__header", className)} {...props} />; }
7
+ export function CardBody({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("sia-card__body", className)} {...props} />; }
8
+ export function CardFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("sia-card__footer", className)} {...props} />; }
@@ -0,0 +1,4 @@
1
+ .sia-card { overflow:hidden; border:1px solid var(--sia-border); border-radius:calc(var(--sia-radius) * 1.25); background:var(--sia-background); box-shadow:0 1px 2px rgb(15 23 42 / .06); }
2
+ .sia-card__header,.sia-card__body,.sia-card__footer { padding:1rem 1.25rem; }
3
+ .sia-card__header { border-bottom:1px solid var(--sia-border); }
4
+ .sia-card__footer { border-top:1px solid var(--sia-border); background:var(--sia-surface); }