@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,58 @@
|
|
|
1
|
+
.sia-select { position:relative; width:100%; min-width:0; }
|
|
2
|
+
.sia-select--disabled { opacity:.6; }
|
|
3
|
+
.sia-select__trigger { display:flex; align-items:center; justify-content:space-between; gap:.75rem; width:100%; min-height:2.5rem; padding:.55rem .7rem .55rem .75rem; border:1px solid var(--sia-border); border-radius:var(--sia-radius); color:var(--sia-foreground); background:var(--sia-background); font:inherit; text-align:left; cursor:pointer; }
|
|
4
|
+
.sia-select__trigger:hover:not(:disabled) { border-color:color-mix(in srgb,var(--sia-primary) 55%,var(--sia-border)); }
|
|
5
|
+
.sia-select__trigger:focus-visible { outline:3px solid color-mix(in srgb,var(--sia-primary) 30%,transparent); outline-offset:2px; }
|
|
6
|
+
.sia-select__trigger--invalid { border-color:var(--sia-danger); }
|
|
7
|
+
.sia-select__trigger:disabled { cursor:not-allowed; }
|
|
8
|
+
.sia-select__value { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
9
|
+
.sia-select__placeholder { color:var(--sia-muted); }
|
|
10
|
+
.sia-select__actions { display:inline-flex; align-items:center; gap:.35rem; flex:none; color:var(--sia-muted); }
|
|
11
|
+
.sia-select__clear { display:inline-grid; place-items:center; width:1.35rem; height:1.35rem; border-radius:50%; font-size:1.05rem; line-height:1; }
|
|
12
|
+
.sia-select__clear:hover { color:var(--sia-foreground); background:var(--sia-surface); }
|
|
13
|
+
.sia-select__chevron { transform:translateY(-.5rem); font-size:1.1rem; }
|
|
14
|
+
.sia-select__overlay { overflow:hidden; border:1px solid var(--sia-border); border-radius:var(--sia-radius); color:var(--sia-foreground); background:var(--sia-background); box-shadow:0 14px 36px rgb(15 23 42 / .16); }
|
|
15
|
+
.sia-select__panel { display:grid; min-width:12rem; max-height:min(22rem,calc(100vh - 1rem)); }
|
|
16
|
+
.sia-select__search-wrap { display:flex; align-items:center; gap:.5rem; padding:.55rem .65rem; color:var(--sia-muted); border-bottom:1px solid var(--sia-border); }
|
|
17
|
+
.sia-select__search { min-width:0; width:100%; padding:.2rem 0; border:0; outline:0; color:var(--sia-foreground); background:transparent; font:inherit; }
|
|
18
|
+
.sia-select__options { overflow-y:auto; padding:.35rem; }
|
|
19
|
+
.sia-select__option { display:flex; align-items:center; justify-content:space-between; gap:.75rem; width:100%; padding:.55rem .65rem; border:0; border-radius:calc(var(--sia-radius) * .7); color:inherit; background:transparent; font:inherit; text-align:left; cursor:pointer; }
|
|
20
|
+
.sia-select__option:hover:not(:disabled),.sia-select__option--active:not(:disabled) { background:var(--sia-surface); }
|
|
21
|
+
.sia-select__option[aria-selected="true"] { color:var(--sia-primary); background:color-mix(in srgb,var(--sia-primary) 10%,transparent); }
|
|
22
|
+
.sia-select__option:disabled { opacity:.5; cursor:not-allowed; }
|
|
23
|
+
.sia-select__option-content { display:grid; gap:.15rem; min-width:0; }
|
|
24
|
+
.sia-select__option-content small { color:var(--sia-muted); font-size:.78rem; line-height:1.35; }
|
|
25
|
+
.sia-select__status { padding:1rem; color:var(--sia-muted); font-size:.875rem; text-align:center; }
|
|
26
|
+
.sia-select__status--error { color:var(--sia-danger); }
|
|
27
|
+
.sia-select__chevron { display:block; line-height:1; transform:none; }
|
|
28
|
+
|
|
29
|
+
/* Le chevron tourne à l'ouverture — l'affordance standard d'un déclencheur de
|
|
30
|
+
menu. Impossible avec l'ancien caractère : son décalage de ligne de base
|
|
31
|
+
dépendait de la police, donc le centre de rotation aussi. */
|
|
32
|
+
.sia-select__chevron {
|
|
33
|
+
width: 1.05em;
|
|
34
|
+
height: 1.05em;
|
|
35
|
+
flex: none;
|
|
36
|
+
color: var(--sia-muted);
|
|
37
|
+
transition:
|
|
38
|
+
transform var(--sia-duration-base) var(--sia-easing-standard),
|
|
39
|
+
color var(--sia-duration-fast) var(--sia-easing-standard);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[aria-expanded="true"] > .sia-select__chevron {
|
|
43
|
+
transform: rotate(180deg);
|
|
44
|
+
color: var(--sia-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sia-select__trigger:hover:not(:disabled) .sia-select__chevron {
|
|
48
|
+
color: var(--sia-foreground);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Le panneau entre au lieu d'apparaître d'un coup. */
|
|
52
|
+
.sia-select__menu,
|
|
53
|
+
.sia-select__panel,
|
|
54
|
+
.sia-select__list {
|
|
55
|
+
animation: sia-scale-in var(--sia-duration-base) var(--sia-easing-entrance);
|
|
56
|
+
transform-origin: top center;
|
|
57
|
+
box-shadow: var(--sia-elevation-overlay);
|
|
58
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { useState, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { useAccordionState } from "@sia-ui/headless";
|
|
4
|
+
import { ChevronDownIcon } from "../Icons";
|
|
5
|
+
import "./styles.css";
|
|
6
|
+
|
|
7
|
+
export interface SidebarItem {
|
|
8
|
+
key: string;
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
/** Rend l'entrée comme un lien. Sans lui, c'est un bouton. */
|
|
12
|
+
href?: string;
|
|
13
|
+
badge?: ReactNode;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** Sous-entrées. Une seule profondeur : au-delà, c'est un menu, pas une barre. */
|
|
16
|
+
items?: Array<Omit<SidebarItem, "items">>;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SidebarSection {
|
|
21
|
+
key: string;
|
|
22
|
+
/** Le titre du groupe. Masqué en mode réduit. */
|
|
23
|
+
title: ReactNode;
|
|
24
|
+
items: SidebarItem[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SidebarEntry = SidebarItem | SidebarSection;
|
|
28
|
+
|
|
29
|
+
function isSection(entry: SidebarEntry): entry is SidebarSection {
|
|
30
|
+
return "title" in entry && Array.isArray(entry.items);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SidebarProps {
|
|
34
|
+
items: SidebarEntry[];
|
|
35
|
+
/** La clé de l'entrée courante. Un parent s'ouvre si un enfant l'est. */
|
|
36
|
+
activeKey?: string;
|
|
37
|
+
collapsed?: boolean;
|
|
38
|
+
header?: ReactNode;
|
|
39
|
+
footer?: ReactNode;
|
|
40
|
+
onNavigate?: (item: SidebarItem) => void;
|
|
41
|
+
ariaLabel?: string;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* La navigation principale.
|
|
47
|
+
*
|
|
48
|
+
* Elle se pose dans le `navigation` d'`AppShell`, qui gère déjà la largeur et
|
|
49
|
+
* le repli. Ici, seulement les entrées : ce qui est actif, ce qui est ouvert,
|
|
50
|
+
* ce qui se replie en icônes.
|
|
51
|
+
*/
|
|
52
|
+
export function Sidebar({
|
|
53
|
+
items,
|
|
54
|
+
activeKey,
|
|
55
|
+
collapsed = false,
|
|
56
|
+
header,
|
|
57
|
+
footer,
|
|
58
|
+
onNavigate,
|
|
59
|
+
ariaLabel,
|
|
60
|
+
className,
|
|
61
|
+
}: SidebarProps) {
|
|
62
|
+
return (
|
|
63
|
+
<nav
|
|
64
|
+
className={cn("sia-sidebar", collapsed && "sia-sidebar--collapsed", className)}
|
|
65
|
+
{...(ariaLabel ? { "aria-label": ariaLabel } : {})}
|
|
66
|
+
>
|
|
67
|
+
{header && <div className="sia-sidebar__header">{header}</div>}
|
|
68
|
+
|
|
69
|
+
<div className="sia-sidebar__body">
|
|
70
|
+
{items.map((entry) =>
|
|
71
|
+
isSection(entry) ? (
|
|
72
|
+
<div key={entry.key} className="sia-sidebar__section">
|
|
73
|
+
<div className="sia-sidebar__section-title">{entry.title}</div>
|
|
74
|
+
{entry.items.map((item) => (
|
|
75
|
+
<SidebarNode
|
|
76
|
+
key={item.key}
|
|
77
|
+
item={item}
|
|
78
|
+
{...(activeKey !== undefined ? { activeKey } : {})}
|
|
79
|
+
collapsed={collapsed}
|
|
80
|
+
{...(onNavigate ? { onNavigate } : {})}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
<SidebarNode
|
|
86
|
+
key={entry.key}
|
|
87
|
+
item={entry}
|
|
88
|
+
{...(activeKey !== undefined ? { activeKey } : {})}
|
|
89
|
+
collapsed={collapsed}
|
|
90
|
+
{...(onNavigate ? { onNavigate } : {})}
|
|
91
|
+
/>
|
|
92
|
+
),
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{footer && <div className="sia-sidebar__footer">{footer}</div>}
|
|
97
|
+
</nav>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface SidebarNodeProps {
|
|
102
|
+
item: SidebarItem;
|
|
103
|
+
activeKey?: string;
|
|
104
|
+
collapsed: boolean;
|
|
105
|
+
onNavigate?: (item: SidebarItem) => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function SidebarNode({
|
|
109
|
+
item,
|
|
110
|
+
activeKey,
|
|
111
|
+
collapsed,
|
|
112
|
+
onNavigate,
|
|
113
|
+
}: SidebarNodeProps) {
|
|
114
|
+
const children = item.items ?? [];
|
|
115
|
+
const hasChildren = children.length > 0;
|
|
116
|
+
const holdsActive = children.some((child) => child.key === activeKey);
|
|
117
|
+
const active = item.key === activeKey;
|
|
118
|
+
|
|
119
|
+
// Le groupe qui contient la page courante s'ouvre de lui-même : arriver sur
|
|
120
|
+
// une page par un lien direct doit montrer où l'on est dans l'arbre.
|
|
121
|
+
const disclosure = useAccordionState({
|
|
122
|
+
type: "multiple",
|
|
123
|
+
defaultValue: holdsActive ? [item.key] : [],
|
|
124
|
+
});
|
|
125
|
+
const [everToggled, setEverToggled] = useState(false);
|
|
126
|
+
const open = everToggled ? disclosure.isOpen(item.key) : holdsActive;
|
|
127
|
+
|
|
128
|
+
const content = (
|
|
129
|
+
<>
|
|
130
|
+
{item.icon && <span className="sia-sidebar__icon" aria-hidden="true">{item.icon}</span>}
|
|
131
|
+
<span className="sia-sidebar__label">{item.label}</span>
|
|
132
|
+
{item.badge !== undefined && (
|
|
133
|
+
<span className="sia-sidebar__badge">{item.badge}</span>
|
|
134
|
+
)}
|
|
135
|
+
{hasChildren && (
|
|
136
|
+
<span className="sia-sidebar__chevron" aria-hidden="true">
|
|
137
|
+
<ChevronDownIcon />
|
|
138
|
+
</span>
|
|
139
|
+
)}
|
|
140
|
+
</>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Le titre natif suffit en mode réduit : un vrai tooltip demanderait un
|
|
144
|
+
// portail et un moteur de placement pour afficher trois mots.
|
|
145
|
+
const tooltip =
|
|
146
|
+
collapsed && typeof item.label === "string" ? item.label : undefined;
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="sia-sidebar__node" data-open={open || undefined}>
|
|
150
|
+
{item.href && !hasChildren ? (
|
|
151
|
+
<a
|
|
152
|
+
href={item.href}
|
|
153
|
+
className={cn("sia-sidebar__item", active && "sia-sidebar__item--active")}
|
|
154
|
+
aria-current={active ? "page" : undefined}
|
|
155
|
+
{...(tooltip ? { title: tooltip } : {})}
|
|
156
|
+
onClick={() => onNavigate?.(item)}
|
|
157
|
+
>
|
|
158
|
+
{content}
|
|
159
|
+
</a>
|
|
160
|
+
) : (
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
className={cn(
|
|
164
|
+
"sia-sidebar__item",
|
|
165
|
+
(active || (holdsActive && collapsed)) && "sia-sidebar__item--active",
|
|
166
|
+
)}
|
|
167
|
+
disabled={item.disabled}
|
|
168
|
+
{...(hasChildren ? { "aria-expanded": open } : {})}
|
|
169
|
+
{...(active ? { "aria-current": "page" as const } : {})}
|
|
170
|
+
{...(tooltip ? { title: tooltip } : {})}
|
|
171
|
+
onClick={() => {
|
|
172
|
+
if (hasChildren) {
|
|
173
|
+
setEverToggled(true);
|
|
174
|
+
disclosure.toggle(item.key);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
item.onClick?.();
|
|
178
|
+
onNavigate?.(item);
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{content}
|
|
182
|
+
</button>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{hasChildren && (
|
|
186
|
+
<div className="sia-sidebar__panel">
|
|
187
|
+
<div className="sia-sidebar__sub" inert={open ? undefined : true}>
|
|
188
|
+
{children.map((child) => {
|
|
189
|
+
const childActive = child.key === activeKey;
|
|
190
|
+
const childClass = cn(
|
|
191
|
+
"sia-sidebar__item",
|
|
192
|
+
"sia-sidebar__item--child",
|
|
193
|
+
childActive && "sia-sidebar__item--active",
|
|
194
|
+
);
|
|
195
|
+
return child.href ? (
|
|
196
|
+
<a
|
|
197
|
+
key={child.key}
|
|
198
|
+
href={child.href}
|
|
199
|
+
className={childClass}
|
|
200
|
+
aria-current={childActive ? "page" : undefined}
|
|
201
|
+
onClick={() => onNavigate?.(child)}
|
|
202
|
+
>
|
|
203
|
+
<span className="sia-sidebar__label">{child.label}</span>
|
|
204
|
+
{child.badge !== undefined && (
|
|
205
|
+
<span className="sia-sidebar__badge">{child.badge}</span>
|
|
206
|
+
)}
|
|
207
|
+
</a>
|
|
208
|
+
) : (
|
|
209
|
+
<button
|
|
210
|
+
key={child.key}
|
|
211
|
+
type="button"
|
|
212
|
+
className={childClass}
|
|
213
|
+
disabled={child.disabled}
|
|
214
|
+
{...(childActive ? { "aria-current": "page" as const } : {})}
|
|
215
|
+
onClick={() => {
|
|
216
|
+
child.onClick?.();
|
|
217
|
+
onNavigate?.(child);
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<span className="sia-sidebar__label">{child.label}</span>
|
|
221
|
+
{child.badge !== undefined && (
|
|
222
|
+
<span className="sia-sidebar__badge">{child.badge}</span>
|
|
223
|
+
)}
|
|
224
|
+
</button>
|
|
225
|
+
);
|
|
226
|
+
})}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
.sia-sidebar {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100%;
|
|
5
|
+
min-height: 0;
|
|
6
|
+
padding: .5rem;
|
|
7
|
+
gap: .5rem;
|
|
8
|
+
font-size: .875rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sia-sidebar__body { flex: 1; min-height: 0; overflow-y: auto; display: grid; gap: .125rem; align-content: start; }
|
|
12
|
+
.sia-sidebar__header,
|
|
13
|
+
.sia-sidebar__footer { flex: none; padding: .25rem; }
|
|
14
|
+
|
|
15
|
+
.sia-sidebar__section { display: grid; gap: .125rem; }
|
|
16
|
+
.sia-sidebar__section + .sia-sidebar__section { margin-top: .75rem; }
|
|
17
|
+
.sia-sidebar__section-title {
|
|
18
|
+
padding: .5rem .625rem .25rem;
|
|
19
|
+
color: var(--sia-muted);
|
|
20
|
+
font-size: .7rem;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
letter-spacing: .05em;
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
/* En mode réduit le titre disparaît sans laisser de trou. */
|
|
25
|
+
transition:
|
|
26
|
+
opacity var(--sia-duration-fast) var(--sia-easing-standard),
|
|
27
|
+
height var(--sia-duration-base) var(--sia-easing-standard);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sia-sidebar__item {
|
|
31
|
+
position: relative;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: .625rem;
|
|
35
|
+
width: 100%;
|
|
36
|
+
padding: .5rem .625rem;
|
|
37
|
+
border: 0;
|
|
38
|
+
border-radius: var(--sia-radius);
|
|
39
|
+
background: transparent;
|
|
40
|
+
color: var(--sia-muted);
|
|
41
|
+
font: inherit;
|
|
42
|
+
text-align: left;
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
transition:
|
|
46
|
+
background-color var(--sia-duration-fast) var(--sia-easing-standard),
|
|
47
|
+
color var(--sia-duration-fast) var(--sia-easing-standard);
|
|
48
|
+
}
|
|
49
|
+
.sia-sidebar__item:hover:not(:disabled) { background: var(--sia-surface); color: var(--sia-foreground); }
|
|
50
|
+
.sia-sidebar__item:disabled { opacity: .45; cursor: not-allowed; }
|
|
51
|
+
|
|
52
|
+
.sia-sidebar__item--active {
|
|
53
|
+
background: color-mix(in srgb, var(--sia-primary) 10%, transparent);
|
|
54
|
+
color: var(--sia-primary);
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Le repère de l'entrée courante : il grandit depuis le centre plutôt que
|
|
59
|
+
d'apparaître d'un coup. */
|
|
60
|
+
.sia-sidebar__item--active::before {
|
|
61
|
+
content: "";
|
|
62
|
+
position: absolute;
|
|
63
|
+
left: 0;
|
|
64
|
+
top: 50%;
|
|
65
|
+
width: 3px;
|
|
66
|
+
height: 1.125rem;
|
|
67
|
+
border-radius: 0 3px 3px 0;
|
|
68
|
+
background: var(--sia-primary);
|
|
69
|
+
transform: translateY(-50%) scaleY(0);
|
|
70
|
+
animation: sia-sidebar-mark var(--sia-duration-base) var(--sia-easing-spring) forwards;
|
|
71
|
+
}
|
|
72
|
+
@keyframes sia-sidebar-mark {
|
|
73
|
+
to { transform: translateY(-50%) scaleY(1); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.sia-sidebar__icon { display: grid; place-items: center; flex: none; width: 1.125rem; font-size: 1.05rem; }
|
|
77
|
+
.sia-sidebar__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
78
|
+
|
|
79
|
+
.sia-sidebar__badge {
|
|
80
|
+
flex: none;
|
|
81
|
+
min-width: 1.25rem;
|
|
82
|
+
padding: 0 .35rem;
|
|
83
|
+
border-radius: 999px;
|
|
84
|
+
background: var(--sia-surface);
|
|
85
|
+
color: var(--sia-muted);
|
|
86
|
+
font-size: .7rem;
|
|
87
|
+
font-variant-numeric: tabular-nums;
|
|
88
|
+
line-height: 1.25rem;
|
|
89
|
+
text-align: center;
|
|
90
|
+
}
|
|
91
|
+
.sia-sidebar__item--active .sia-sidebar__badge {
|
|
92
|
+
background: color-mix(in srgb, var(--sia-primary) 16%, transparent);
|
|
93
|
+
color: var(--sia-primary);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.sia-sidebar__chevron {
|
|
97
|
+
display: grid;
|
|
98
|
+
place-items: center;
|
|
99
|
+
flex: none;
|
|
100
|
+
color: var(--sia-muted);
|
|
101
|
+
font-size: .8rem;
|
|
102
|
+
transition: transform var(--sia-duration-base) var(--sia-easing-standard);
|
|
103
|
+
}
|
|
104
|
+
.sia-sidebar__node[data-open] .sia-sidebar__chevron { transform: rotate(180deg); }
|
|
105
|
+
|
|
106
|
+
/* Même technique que l'accordéon : la hauteur s'interpole sans être mesurée. */
|
|
107
|
+
.sia-sidebar__panel {
|
|
108
|
+
display: grid;
|
|
109
|
+
grid-template-rows: 0fr;
|
|
110
|
+
transition: grid-template-rows var(--sia-duration-base) var(--sia-easing-standard);
|
|
111
|
+
}
|
|
112
|
+
.sia-sidebar__node[data-open] .sia-sidebar__panel { grid-template-rows: 1fr; }
|
|
113
|
+
.sia-sidebar__sub { overflow: hidden; min-height: 0; display: grid; gap: .125rem; }
|
|
114
|
+
|
|
115
|
+
.sia-sidebar__item--child {
|
|
116
|
+
margin-left: 1.5rem;
|
|
117
|
+
padding-block: .375rem;
|
|
118
|
+
font-size: .825rem;
|
|
119
|
+
}
|
|
120
|
+
/* Le filet qui relie les sous-entrées à leur parent. */
|
|
121
|
+
.sia-sidebar__sub { position: relative; }
|
|
122
|
+
.sia-sidebar__sub::before {
|
|
123
|
+
content: "";
|
|
124
|
+
position: absolute;
|
|
125
|
+
top: .375rem;
|
|
126
|
+
bottom: .375rem;
|
|
127
|
+
left: 1.25rem;
|
|
128
|
+
width: 1px;
|
|
129
|
+
background: var(--sia-border);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ── Mode réduit ───────────────────────────────────────────────────────── */
|
|
133
|
+
|
|
134
|
+
.sia-sidebar--collapsed .sia-sidebar__item { justify-content: center; padding-inline: .5rem; }
|
|
135
|
+
.sia-sidebar--collapsed .sia-sidebar__label,
|
|
136
|
+
.sia-sidebar--collapsed .sia-sidebar__badge,
|
|
137
|
+
.sia-sidebar--collapsed .sia-sidebar__chevron { display: none; }
|
|
138
|
+
.sia-sidebar--collapsed .sia-sidebar__section-title { height: 0; padding: 0; opacity: 0; overflow: hidden; }
|
|
139
|
+
/* Replier ferme les sous-entrées : elles n'auraient plus de libellé. */
|
|
140
|
+
.sia-sidebar--collapsed .sia-sidebar__panel { grid-template-rows: 0fr; }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CSSProperties, type HTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
|
|
5
|
+
export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
width?: CSSProperties["width"];
|
|
7
|
+
height?: CSSProperties["height"];
|
|
8
|
+
radius?: CSSProperties["borderRadius"];
|
|
9
|
+
circle?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Skeleton({ className, width = "100%", height = "1rem", radius, circle, style, ...props }: SkeletonProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={cn("sia-skeleton", circle && "sia-skeleton--circle", className)}
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
style={{ width, height, borderRadius: circle ? "9999px" : radius, ...style }}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.sia-skeleton { display:block; max-width:100%; border-radius:var(--sia-radius); background:linear-gradient(90deg,var(--sia-border),color-mix(in srgb,var(--sia-border) 45%,var(--sia-background)),var(--sia-border)); background-size:200% 100%; animation:sia-skeleton 1.4s ease-in-out infinite; }
|
|
2
|
+
.sia-skeleton--circle { aspect-ratio:1; }
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { useSliderState, type SliderValue } from "@sia-ui/headless";
|
|
4
|
+
|
|
5
|
+
export type { SliderValue };
|
|
6
|
+
export type SliderRangeValue = [number, number];
|
|
7
|
+
|
|
8
|
+
export interface SliderProps {
|
|
9
|
+
/** Nom du champ, quand le slider est posé dans un formulaire. */
|
|
10
|
+
name?: string | undefined;
|
|
11
|
+
/** Mode contrôlé. Laisser vide pour un slider non contrôlé. */
|
|
12
|
+
value?: SliderValue | undefined;
|
|
13
|
+
/** Valeur de départ en mode non contrôlé. */
|
|
14
|
+
defaultValue?: SliderValue | undefined;
|
|
15
|
+
min?: number | undefined;
|
|
16
|
+
max?: number | undefined;
|
|
17
|
+
step?: number | undefined;
|
|
18
|
+
range?: boolean | undefined;
|
|
19
|
+
onChange?: ((value: SliderValue) => void) | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Alias de `onChange`, tel que l'attendaient les composants du registre.
|
|
22
|
+
* Les deux sont appelés.
|
|
23
|
+
*/
|
|
24
|
+
onValueChange?: ((value: SliderValue) => void) | undefined;
|
|
25
|
+
/** Écart minimal entre les deux poignées, en mode plage. */
|
|
26
|
+
minDistance?: number | undefined;
|
|
27
|
+
/** Affiche la valeur courante à côté de la piste. */
|
|
28
|
+
showValue?: boolean | undefined;
|
|
29
|
+
/** Rendu de la valeur affichée — devise, unité, arrondi. */
|
|
30
|
+
formatValue?: ((value: number) => string) | undefined;
|
|
31
|
+
/** Libellé de la poignée basse, annoncé aux lecteurs d'écran. */
|
|
32
|
+
startLabel?: string | undefined;
|
|
33
|
+
/** Libellé de la poignée haute, en mode plage. */
|
|
34
|
+
endLabel?: string | undefined;
|
|
35
|
+
disabled?: boolean | undefined;
|
|
36
|
+
className?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
import { Tooltip } from "../Tooltip";
|
|
39
|
+
import "./styles.css";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Rendu DOM du slider.
|
|
43
|
+
*
|
|
44
|
+
* Tout le calcul — pas, bornes, pourcentages, poignée la plus proche,
|
|
45
|
+
* non-croisement — vit dans `useSliderState`. Ce fichier ne garde que ce qui
|
|
46
|
+
* est propre au web : la mesure de la piste, les événements souris et le JSX.
|
|
47
|
+
*/
|
|
48
|
+
export function Slider({
|
|
49
|
+
name,
|
|
50
|
+
value,
|
|
51
|
+
defaultValue,
|
|
52
|
+
min = 0,
|
|
53
|
+
max = 100,
|
|
54
|
+
step = 1,
|
|
55
|
+
range = false,
|
|
56
|
+
minDistance = 0,
|
|
57
|
+
showValue = false,
|
|
58
|
+
formatValue,
|
|
59
|
+
startLabel,
|
|
60
|
+
endLabel,
|
|
61
|
+
onChange,
|
|
62
|
+
onValueChange,
|
|
63
|
+
disabled,
|
|
64
|
+
className,
|
|
65
|
+
}: SliderProps) {
|
|
66
|
+
const trackRef = useRef<HTMLDivElement>(null);
|
|
67
|
+
const [activeThumb, setActiveThumb] = useState<number | null>(null);
|
|
68
|
+
|
|
69
|
+
// `onValueChange` est le nom qu'employaient les composants du registre.
|
|
70
|
+
// Les deux sont notifiés plutôt que d'imposer un renommage à l'appelant.
|
|
71
|
+
const notify = (next: SliderValue) => {
|
|
72
|
+
onChange?.(next);
|
|
73
|
+
onValueChange?.(next);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const slider = useSliderState({
|
|
77
|
+
value,
|
|
78
|
+
defaultValue,
|
|
79
|
+
min,
|
|
80
|
+
max,
|
|
81
|
+
step,
|
|
82
|
+
range,
|
|
83
|
+
minDistance,
|
|
84
|
+
onChange: notify,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/** Le seul pont entre le pointeur et la logique : un ratio de 0 à 1. */
|
|
88
|
+
const valueFromClientX = (clientX: number) => {
|
|
89
|
+
const rect = trackRef.current?.getBoundingClientRect();
|
|
90
|
+
if (!rect || rect.width === 0) return slider.values[0] ?? min;
|
|
91
|
+
return slider.valueAt((clientX - rect.left) / rect.width);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (activeThumb === null) return undefined;
|
|
96
|
+
|
|
97
|
+
const onMove = (event: MouseEvent) => {
|
|
98
|
+
slider.moveThumb(activeThumb, valueFromClientX(event.clientX));
|
|
99
|
+
};
|
|
100
|
+
const onUp = () => setActiveThumb(null);
|
|
101
|
+
|
|
102
|
+
window.addEventListener("mousemove", onMove);
|
|
103
|
+
window.addEventListener("mouseup", onUp);
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
window.removeEventListener("mousemove", onMove);
|
|
107
|
+
window.removeEventListener("mouseup", onUp);
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const onTrackDown = (event: React.MouseEvent) => {
|
|
112
|
+
if (disabled) return;
|
|
113
|
+
|
|
114
|
+
const next = valueFromClientX(event.clientX);
|
|
115
|
+
const thumb = slider.nearestThumb(next);
|
|
116
|
+
|
|
117
|
+
setActiveThumb(thumb);
|
|
118
|
+
slider.moveThumb(thumb, next);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const [first = 0, second = 0] = slider.percents;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={cn("sia-slider", className, disabled && "is-disabled")}
|
|
126
|
+
data-name={name}
|
|
127
|
+
>
|
|
128
|
+
<div ref={trackRef} className="sia-slider-track" onMouseDown={onTrackDown}>
|
|
129
|
+
<div
|
|
130
|
+
className="sia-slider-range"
|
|
131
|
+
style={
|
|
132
|
+
range
|
|
133
|
+
? { left: `${first}%`, width: `${second - first}%` }
|
|
134
|
+
: { width: `${first}%` }
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{showValue && (
|
|
140
|
+
<span className="sia-slider-value">
|
|
141
|
+
{slider.values
|
|
142
|
+
.map((one) => (formatValue ? formatValue(one) : String(one)))
|
|
143
|
+
.join(" – ")}
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{slider.values.map((one, index) => (
|
|
148
|
+
<Thumb
|
|
149
|
+
key={index}
|
|
150
|
+
percent={slider.percents[index] ?? 0}
|
|
151
|
+
value={one}
|
|
152
|
+
label={formatValue ? formatValue(one) : String(one)}
|
|
153
|
+
name={(index === 0 ? startLabel : endLabel) ?? `Valeur ${index + 1}`}
|
|
154
|
+
disabled={disabled}
|
|
155
|
+
onGrab={() => {
|
|
156
|
+
if (!disabled) setActiveThumb(index);
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function Thumb({
|
|
165
|
+
percent,
|
|
166
|
+
value,
|
|
167
|
+
label,
|
|
168
|
+
name,
|
|
169
|
+
disabled,
|
|
170
|
+
onGrab,
|
|
171
|
+
}: {
|
|
172
|
+
percent: number;
|
|
173
|
+
value: number;
|
|
174
|
+
label: string;
|
|
175
|
+
name: string;
|
|
176
|
+
disabled?: boolean | undefined;
|
|
177
|
+
onGrab: () => void;
|
|
178
|
+
}) {
|
|
179
|
+
return (
|
|
180
|
+
<Tooltip content={label} placement="top">
|
|
181
|
+
<span
|
|
182
|
+
role="slider"
|
|
183
|
+
aria-valuenow={value}
|
|
184
|
+
aria-label={name}
|
|
185
|
+
aria-disabled={disabled || undefined}
|
|
186
|
+
tabIndex={disabled ? -1 : 0}
|
|
187
|
+
className="sia-slider-thumb"
|
|
188
|
+
style={{ left: `${percent}%` }}
|
|
189
|
+
onMouseDown={onGrab}
|
|
190
|
+
/>
|
|
191
|
+
</Tooltip>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.sia-slider {
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 32px;
|
|
5
|
+
user-select: none;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Track */
|
|
9
|
+
.sia-slider-track {
|
|
10
|
+
position: absolute;
|
|
11
|
+
top: 50%;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 4px;
|
|
14
|
+
background: var(--sia-color-neutral-200);
|
|
15
|
+
border-radius: 999px;
|
|
16
|
+
transform: translateY(-50%);
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sia-slider-range {
|
|
21
|
+
position: absolute;
|
|
22
|
+
height: 100%;
|
|
23
|
+
background: var(--sia-color-primary-500);
|
|
24
|
+
border-radius: inherit;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Thumb */
|
|
28
|
+
.sia-slider-thumb {
|
|
29
|
+
position: absolute;
|
|
30
|
+
top: 50%;
|
|
31
|
+
transform: translate(-50%, -50%);
|
|
32
|
+
width: 16px;
|
|
33
|
+
height: 16px;
|
|
34
|
+
border-radius: 50%;
|
|
35
|
+
background: var(--sia-color-primary-500);
|
|
36
|
+
border: 2px solid white;
|
|
37
|
+
cursor: grab;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sia-slider-thumb:active {
|
|
41
|
+
cursor: grabbing;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sia-slider-thumb-trigger {
|
|
45
|
+
position: absolute;
|
|
46
|
+
inset: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Disabled */
|
|
50
|
+
.sia-slider.is-disabled {
|
|
51
|
+
opacity: 0.5;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|