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