@sia-ui/cli 0.2.1 → 0.3.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 (100) hide show
  1. package/LICENSE +21 -21
  2. package/dist/index.js +2 -0
  3. package/dist/registry/package/components/Alert/index.tsx +7 -0
  4. package/dist/registry/package/components/AmountDisplay/index.tsx +8 -0
  5. package/dist/registry/package/components/AppShell/index.tsx +413 -3
  6. package/dist/registry/package/components/AppShell/styles.css +259 -7
  7. package/dist/registry/package/components/AppShell/tab-bar.tsx +134 -0
  8. package/dist/registry/package/components/AsyncState/index.tsx +7 -0
  9. package/dist/registry/package/components/AuditMeta/index.tsx +8 -0
  10. package/dist/registry/package/components/AuthLayout/index.tsx +7 -0
  11. package/dist/registry/package/components/Autocomplete/index.tsx +7 -0
  12. package/dist/registry/package/components/Avatar/index.tsx +333 -12
  13. package/dist/registry/package/components/Avatar/styles.css +236 -5
  14. package/dist/registry/package/components/Badge/index.tsx +7 -0
  15. package/dist/registry/package/components/Button/index.tsx +7 -0
  16. package/dist/registry/package/components/Calendar/index.tsx +11 -0
  17. package/dist/registry/package/components/Card/index.tsx +8 -0
  18. package/dist/registry/package/components/Checkbox/index.tsx +8 -0
  19. package/dist/registry/package/components/ColorPicker/index.tsx +7 -0
  20. package/dist/registry/package/components/CommandPalette/index.tsx +7 -0
  21. package/dist/registry/package/components/ConfirmDialog/index.tsx +7 -0
  22. package/dist/registry/package/components/Container/index.tsx +7 -0
  23. package/dist/registry/package/components/Countdown/index.tsx +7 -0
  24. package/dist/registry/package/components/CrudPage/dialogs.tsx +230 -0
  25. package/dist/registry/package/components/CrudPage/index.tsx +546 -5
  26. package/dist/registry/package/components/CrudPage/styles.css +25 -1
  27. package/dist/registry/package/components/CurrencyInput/index.tsx +7 -0
  28. package/dist/registry/package/components/DataTable/index.tsx +548 -200
  29. package/dist/registry/package/components/DataTable/query.ts +68 -0
  30. package/dist/registry/package/components/DataTable/row-actions.tsx +158 -0
  31. package/dist/registry/package/components/DataTable/styles.css +285 -73
  32. package/dist/registry/package/components/DataTable/types.ts +194 -0
  33. package/dist/registry/package/components/DataTable/values.ts +28 -0
  34. package/dist/registry/package/components/DatePicker/index.tsx +7 -0
  35. package/dist/registry/package/components/DateRangeFilter/index.tsx +7 -0
  36. package/dist/registry/package/components/DateRangePicker/index.tsx +7 -0
  37. package/dist/registry/package/components/DateTimePicker/index.tsx +7 -0
  38. package/dist/registry/package/components/Descriptions/index.tsx +7 -0
  39. package/dist/registry/package/components/Divider/index.tsx +7 -0
  40. package/dist/registry/package/components/Drawer/index.tsx +8 -0
  41. package/dist/registry/package/components/DurationDisplay/index.tsx +6 -0
  42. package/dist/registry/package/components/EmptyState/index.tsx +7 -0
  43. package/dist/registry/package/components/EntityMeta/index.tsx +7 -0
  44. package/dist/registry/package/components/ErrorState/index.tsx +8 -0
  45. package/dist/registry/package/components/EventCalendar/index.tsx +7 -0
  46. package/dist/registry/package/components/Field/index.tsx +12 -0
  47. package/dist/registry/package/components/FileUpload/index.tsx +7 -0
  48. package/dist/registry/package/components/FiltersBar/index.tsx +7 -0
  49. package/dist/registry/package/components/HoverCard/index.tsx +14 -37
  50. package/dist/registry/package/components/IconButton/index.tsx +8 -0
  51. package/dist/registry/package/components/Icons/index.tsx +15 -0
  52. package/dist/registry/package/components/ImageUpload/index.tsx +7 -0
  53. package/dist/registry/package/components/Input/index.tsx +7 -0
  54. package/dist/registry/package/components/JsonEditor/index.tsx +7 -0
  55. package/dist/registry/package/components/KpiGrid/index.tsx +7 -0
  56. package/dist/registry/package/components/Label/index.tsx +7 -0
  57. package/dist/registry/package/components/MarkdownEditor/index.tsx +7 -0
  58. package/dist/registry/package/components/MiniCalendar/index.tsx +7 -0
  59. package/dist/registry/package/components/Modal/index.tsx +8 -0
  60. package/dist/registry/package/components/MonthPicker/index.tsx +7 -0
  61. package/dist/registry/package/components/MultiSelect/index.tsx +7 -0
  62. package/dist/registry/package/components/OtpInput/index.tsx +7 -0
  63. package/dist/registry/package/components/Overlay/index.tsx +34 -24
  64. package/dist/registry/package/components/PageHeader/index.tsx +7 -0
  65. package/dist/registry/package/components/PermissionGate/index.tsx +6 -0
  66. package/dist/registry/package/components/Popover/index.tsx +7 -0
  67. package/dist/registry/package/components/QrCode/index.tsx +7 -0
  68. package/dist/registry/package/components/RadioGroup/index.tsx +7 -0
  69. package/dist/registry/package/components/Rating/index.tsx +6 -0
  70. package/dist/registry/package/components/ReferenceSelect/index.tsx +7 -0
  71. package/dist/registry/package/components/RelativeTime/index.tsx +6 -0
  72. package/dist/registry/package/components/ResizablePanel/index.tsx +7 -0
  73. package/dist/registry/package/components/Resource/index.tsx +514 -0
  74. package/dist/registry/package/components/RichTextEditor/index.tsx +7 -0
  75. package/dist/registry/package/components/SearchInput/index.tsx +8 -0
  76. package/dist/registry/package/components/Select/index.tsx +11 -0
  77. package/dist/registry/package/components/Sidebar/index.tsx +489 -113
  78. package/dist/registry/package/components/Sidebar/styles.css +96 -4
  79. package/dist/registry/package/components/Skeleton/index.tsx +7 -0
  80. package/dist/registry/package/components/Spinner/index.tsx +7 -0
  81. package/dist/registry/package/components/StatCard/index.tsx +7 -0
  82. package/dist/registry/package/components/Statistic/index.tsx +7 -0
  83. package/dist/registry/package/components/Switch/index.tsx +7 -0
  84. package/dist/registry/package/components/Tabs/index.tsx +7 -0
  85. package/dist/registry/package/components/TagsInput/index.tsx +7 -0
  86. package/dist/registry/package/components/Textarea/index.tsx +7 -0
  87. package/dist/registry/package/components/TimePicker/index.tsx +7 -0
  88. package/dist/registry/package/components/TimeRangePicker/index.tsx +6 -0
  89. package/dist/registry/package/components/Timeline/index.tsx +7 -0
  90. package/dist/registry/package/components/Timer/index.tsx +7 -0
  91. package/dist/registry/package/components/Tour/index.tsx +7 -0
  92. package/dist/registry/package/components/Transfer/index.tsx +7 -0
  93. package/dist/registry/package/components/Tree/index.tsx +7 -0
  94. package/dist/registry/package/components/TreeSelect/index.tsx +7 -0
  95. package/dist/registry/package/components/Typography/index.tsx +8 -0
  96. package/dist/registry/package/components/UnsavedChangesGuard/index.tsx +8 -0
  97. package/dist/registry/package/components/WeekPicker/index.tsx +7 -0
  98. package/dist/registry/package/components/YearPicker/index.tsx +7 -0
  99. package/dist/registry/registry.json +69 -14
  100. package/package.json +14 -14
@@ -0,0 +1,68 @@
1
+ import { useMemo } from "react";
2
+ import {
3
+ useTableQuery,
4
+ type TableQuery,
5
+ type TableQueryState,
6
+ type TableQueryStorage,
7
+ } from "@sia-ui/headless";
8
+
9
+ /**
10
+ * L'état du tableau dans la barre d'adresse.
11
+ *
12
+ * `history.replaceState` plutôt que `pushState` : trier une colonne trois
13
+ * fois de suite ne doit pas demander trois retours en arrière pour revenir
14
+ * d'où l'on vient.
15
+ */
16
+ export function createUrlTableStorage(
17
+ mode: "replace" | "push" = "replace",
18
+ ): TableQueryStorage {
19
+ return {
20
+ read: () => (typeof window === "undefined" ? "" : window.location.search),
21
+ write: (value) => {
22
+ if (typeof window === "undefined") return;
23
+ const url = `${window.location.pathname}${value ? `?${value}` : ""}${window.location.hash}`;
24
+ if (mode === "push") window.history.pushState(null, "", url);
25
+ else window.history.replaceState(null, "", url);
26
+ },
27
+ subscribe: (listener) => {
28
+ if (typeof window === "undefined") return () => {};
29
+ window.addEventListener("popstate", listener);
30
+ return () => window.removeEventListener("popstate", listener);
31
+ },
32
+ };
33
+ }
34
+
35
+ export interface UseDataTableQueryOptions {
36
+ defaultState?: Partial<TableQueryState>;
37
+ /** `false` garde l'état en mémoire — utile dans une modale. */
38
+ syncUrl?: boolean;
39
+ /** Empile une entrée d'historique à chaque changement. */
40
+ history?: "replace" | "push";
41
+ /** Préfixe des clés, pour deux tableaux sur la même page. */
42
+ prefix?: string;
43
+ }
44
+
45
+ /**
46
+ * Tri, filtres et pagination d'un tableau, synchronisés avec l'URL.
47
+ *
48
+ * Le calcul vit dans `@sia-ui/headless`; il n'y a ici que le branchement sur
49
+ * la barre d'adresse, qui est la seule partie propre au navigateur.
50
+ */
51
+ export function useDataTableQuery(
52
+ options: UseDataTableQueryOptions = {},
53
+ ): TableQuery {
54
+ const { defaultState, syncUrl = true, history = "replace", prefix } = options;
55
+
56
+ // Créé une fois : un objet reconstruit à chaque rendu réabonnerait
57
+ // l'écouteur `popstate` en boucle.
58
+ const storage = useMemo(
59
+ () => (syncUrl ? createUrlTableStorage(history) : undefined),
60
+ [history, syncUrl],
61
+ );
62
+
63
+ return useTableQuery({
64
+ ...(defaultState ? { defaultState } : {}),
65
+ ...(storage ? { storage } : {}),
66
+ ...(prefix ? { prefix } : {}),
67
+ });
68
+ }
@@ -0,0 +1,158 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import { cn } from "@sia-ui/utils";
3
+ import type { PermissionRule } from "@sia-ui/headless";
4
+ import { Button } from "../Button";
5
+ import { IconButton } from "../IconButton";
6
+ import { DropdownMenu } from "../DropdownMenu";
7
+ import { ConfirmDialog } from "../ConfirmDialog";
8
+ import { MoreHorizontalIcon } from "../Icons";
9
+ import type { RowAction } from "./types";
10
+
11
+ interface RowActionsProps<T> {
12
+ row: T;
13
+ index: number;
14
+ actions: Array<RowAction<T>>;
15
+ inlineLimit: number;
16
+ display: "icon" | "label";
17
+ can: ((rule: PermissionRule) => boolean) | undefined;
18
+ }
19
+
20
+ /**
21
+ * Ce qu'on peut faire d'une ligne.
22
+ *
23
+ * Trois choses se décident ici, et aucune n'est cosmétique : ce que la
24
+ * session a le droit de faire, ce qui demande confirmation, et ce qui ne
25
+ * tient plus dans la largeur et doit passer en menu.
26
+ */
27
+ export function RowActions<T>({
28
+ row,
29
+ index,
30
+ actions,
31
+ inlineLimit,
32
+ display,
33
+ can,
34
+ }: RowActionsProps<T>) {
35
+ // Une confirmation en attente. Une seule à la fois : deux dialogues
36
+ // superposés sur la même ligne n'ont aucun sens.
37
+ const [enAttente, setEnAttente] = useState<RowAction<T> | null>(null);
38
+
39
+ const visibles = actions.filter((action) => {
40
+ if (action.permission !== undefined && can && !can(action.permission)) {
41
+ return false;
42
+ }
43
+ return !action.hidden?.(row, index);
44
+ });
45
+
46
+ if (visibles.length === 0) return null;
47
+
48
+ const declencher = (action: RowAction<T>) => {
49
+ if (action.confirm) {
50
+ setEnAttente(action);
51
+ return;
52
+ }
53
+ void action.onSelect(row, index);
54
+ };
55
+
56
+ const enLigne = visibles.slice(0, inlineLimit);
57
+ const dansLeMenu = visibles.slice(inlineLimit);
58
+
59
+ return (
60
+ <div className="sia-table__actions" onClick={(e) => e.stopPropagation()}>
61
+ {enLigne.map((action) => {
62
+ const desactivee = action.disabled?.(row, index) ?? false;
63
+ const nom =
64
+ typeof action.label === "string" ? action.label : action.key;
65
+
66
+ // Sans icône, on retombe sur le libellé : un bouton vide ne se clique
67
+ // pas, quelle que soit la consigne d'affichage.
68
+ return display === "icon" && action.icon ? (
69
+ <IconButton
70
+ key={action.key}
71
+ label={nom}
72
+ icon={action.icon}
73
+ variant="ghost"
74
+ size="sm"
75
+ disabled={desactivee}
76
+ tooltip={{ content: action.label }}
77
+ onClick={() => declencher(action)}
78
+ />
79
+ ) : (
80
+ <Button
81
+ key={action.key}
82
+ variant="ghost"
83
+ size="sm"
84
+ {...(action.tone ? { tone: action.tone } : {})}
85
+ disabled={desactivee}
86
+ onClick={() => declencher(action)}
87
+ >
88
+ {action.icon}
89
+ {action.label}
90
+ </Button>
91
+ );
92
+ })}
93
+
94
+ {dansLeMenu.length > 0 && (
95
+ <DropdownMenu
96
+ placement="bottom-end"
97
+ items={dansLeMenu.map((action) => ({
98
+ key: action.key,
99
+ label: action.label,
100
+ ...(action.icon ? { icon: action.icon } : {}),
101
+ disabled: action.disabled?.(row, index) ?? false,
102
+ danger: action.tone === "danger",
103
+ onSelect: () => declencher(action),
104
+ }))}
105
+ >
106
+ <IconButton
107
+ label="Autres actions"
108
+ icon={<MoreHorizontalIcon />}
109
+ variant="ghost"
110
+ size="sm"
111
+ />
112
+ </DropdownMenu>
113
+ )}
114
+
115
+ {enAttente?.confirm && (
116
+ <ConfirmDialog
117
+ open
118
+ onOpenChange={(ouvert) => {
119
+ if (!ouvert) setEnAttente(null);
120
+ }}
121
+ title={enAttente.confirm.title}
122
+ {...(enAttente.confirm.description
123
+ ? { description: enAttente.confirm.description }
124
+ : {})}
125
+ {...(enAttente.confirm.confirmLabel
126
+ ? { confirmLabel: enAttente.confirm.confirmLabel }
127
+ : {})}
128
+ {...(enAttente.confirm.cancelLabel
129
+ ? { cancelLabel: enAttente.confirm.cancelLabel }
130
+ : {})}
131
+ tone={enAttente.confirm.destructive ? "danger" : "primary"}
132
+ onConfirm={async () => {
133
+ await enAttente.onSelect(row, index);
134
+ setEnAttente(null);
135
+ }}
136
+ />
137
+ )}
138
+ </div>
139
+ );
140
+ }
141
+
142
+ /** Une cellule de carte : libellé au-dessus, valeur en dessous. */
143
+ export function CardMeta({
144
+ label,
145
+ value,
146
+ className,
147
+ }: {
148
+ label: ReactNode;
149
+ value: ReactNode;
150
+ className?: string;
151
+ }) {
152
+ return (
153
+ <div className={cn("sia-table-card__meta", className)}>
154
+ <span className="sia-table-card__label">{label}</span>
155
+ <span className="sia-table-card__value">{value}</span>
156
+ </div>
157
+ );
158
+ }
@@ -1,98 +1,310 @@
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 {
1
+ /**
2
+ * Le tableau de données.
3
+ *
4
+ * Une variable porte la hauteur de ligne — `--sia-table-row-height` — et les
5
+ * trois densités ne font que la redéfinir. Le reste du fichier ne la connaît
6
+ * pas : changer de densité ne touche donc à aucune autre règle.
7
+ */
8
+
9
+ .sia-table {
10
+ --sia-table-row-height: 3rem;
11
+ --sia-table-pad-y: .75rem;
12
+ --sia-table-pad-x: .875rem;
13
+
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: .75rem;
17
+ min-width: 0;
18
+ font-size: .875rem;
19
+ }
20
+
21
+ .sia-table--compact {
22
+ --sia-table-row-height: 2.375rem;
23
+ --sia-table-pad-y: .45rem;
24
+ font-size: .825rem;
25
+ }
26
+ .sia-table--comfortable {
27
+ --sia-table-row-height: 3.75rem;
28
+ --sia-table-pad-y: 1.05rem;
29
+ }
30
+
31
+ /* ── L'en-tête ──────────────────────────────────────────────────────────── */
32
+
33
+ .sia-table__header {
34
+ display: flex;
35
+ align-items: flex-end;
36
+ justify-content: space-between;
37
+ gap: 1rem;
38
+ flex-wrap: wrap;
39
+ }
40
+
41
+ .sia-table__heading { min-width: 0; display: grid; gap: .15rem; }
42
+ .sia-table__title { margin: 0; font-size: 1.05rem; font-weight: 650; }
43
+ .sia-table__description { margin: 0; color: var(--sia-muted); font-size: .85rem; }
44
+
45
+ .sia-table__tools {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: .5rem;
49
+ flex-wrap: wrap;
50
+ margin-left: auto;
51
+ }
52
+ .sia-table__actions-bar { display: flex; align-items: center; gap: .5rem; }
53
+
54
+ /* Deux boutons dans un même sillon, avec un repère qui glisse dessous. */
55
+ .sia-table__views {
56
+ display: inline-flex;
57
+ padding: .2rem;
58
+ border: 1px solid var(--sia-border);
59
+ border-radius: var(--sia-radius);
60
+ background: var(--sia-surface);
61
+ }
62
+ .sia-table__view {
63
+ display: grid;
64
+ place-items: center;
65
+ width: 1.9rem;
66
+ height: 1.9rem;
67
+ border: 0;
68
+ border-radius: calc(var(--sia-radius) - .15rem);
69
+ background: transparent;
70
+ color: var(--sia-muted);
71
+ cursor: pointer;
72
+ transition:
73
+ background-color var(--sia-duration-fast) var(--sia-easing-standard),
74
+ color var(--sia-duration-fast) var(--sia-easing-standard);
75
+ }
76
+ .sia-table__view--active {
77
+ background: var(--sia-background);
78
+ color: var(--sia-foreground);
79
+ box-shadow: 0 1px 2px rgb(15 23 42 / .08);
80
+ }
81
+
82
+ /* ── La barre de sélection ──────────────────────────────────────────────── */
83
+
84
+ .sia-table__selection {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: .75rem;
88
+ padding: .5rem .875rem;
89
+ border: 1px solid color-mix(in srgb, var(--sia-primary) 30%, transparent);
90
+ border-radius: var(--sia-radius);
91
+ background: color-mix(in srgb, var(--sia-primary) 8%, var(--sia-background));
92
+ /* Elle se glisse entre l'en-tête et le corps : la voir apparaître d'un bloc
93
+ donnerait l'impression que la page a sauté. */
94
+ animation: sia-table-selection var(--sia-duration-base) var(--sia-easing-spring);
95
+ }
96
+ @keyframes sia-table-selection {
97
+ from { opacity: 0; transform: translateY(-.35rem); }
98
+ }
99
+ .sia-table__selection-count {
100
+ color: var(--sia-primary);
101
+ font-weight: 600;
102
+ font-variant-numeric: tabular-nums;
103
+ }
104
+
105
+ /* ── Le tableau ─────────────────────────────────────────────────────────── */
106
+
107
+ .sia-table__scroll {
108
+ min-width: 0;
109
+ overflow: auto;
110
+ border-radius: var(--sia-radius);
111
+ }
112
+ .sia-table--bordered .sia-table__scroll { border: 1px solid var(--sia-border); }
113
+
114
+ .sia-table__table {
115
+ width: 100%;
116
+ border-collapse: separate;
117
+ border-spacing: 0;
118
+ }
119
+
120
+ .sia-table__table th {
121
+ padding: var(--sia-table-pad-y) var(--sia-table-pad-x);
122
+ border-bottom: 1px solid var(--sia-border);
123
+ background: var(--sia-surface);
124
+ color: var(--sia-muted);
125
+ font-size: .75rem;
126
+ font-weight: 600;
127
+ letter-spacing: .03em;
128
+ text-align: left;
129
+ text-transform: uppercase;
130
+ white-space: nowrap;
131
+ }
132
+
133
+ .sia-table__scroll--sticky th {
134
+ position: sticky;
135
+ top: 0;
136
+ z-index: 2;
137
+ }
138
+
139
+ .sia-table__table td {
140
+ height: var(--sia-table-row-height);
141
+ padding: var(--sia-table-pad-y) var(--sia-table-pad-x);
142
+ border-bottom: 1px solid var(--sia-border);
143
+ vertical-align: middle;
144
+ }
145
+ .sia-table__table tbody tr:last-child td { border-bottom: 0; }
146
+
147
+ .sia-table--striped tbody tr:nth-child(even) td {
148
+ background: color-mix(in srgb, var(--sia-surface) 55%, transparent);
149
+ }
150
+
151
+ .sia-table__table tbody tr {
13
152
  transition: background-color var(--sia-duration-fast) var(--sia-easing-standard);
14
153
  }
154
+ .sia-table__table tbody tr:hover td {
155
+ background: color-mix(in srgb, var(--sia-primary) 4%, var(--sia-surface));
156
+ }
15
157
 
16
- .sia-data-table tbody tr:hover {
17
- background: color-mix(in srgb, var(--sia-primary) 4%, transparent);
158
+ .sia-table__row--clickable { cursor: pointer; }
159
+ .sia-table__row--selected td {
160
+ background: color-mix(in srgb, var(--sia-primary) 7%, transparent);
18
161
  }
19
162
 
20
- .sia-data-table tbody tr[aria-selected="true"] {
21
- background: color-mix(in srgb, var(--sia-primary) 9%, transparent);
163
+ .sia-table__cell--center { text-align: center; }
164
+ .sia-table__cell--end { text-align: right; }
165
+ .sia-table__cell--truncate {
166
+ max-width: 1px;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ white-space: nowrap;
22
170
  }
23
171
 
172
+ .sia-table__cell--select { width: 1px; padding-right: 0; }
173
+ .sia-table__cell--actions { width: 1px; white-space: nowrap; text-align: right; }
174
+
175
+ .sia-table__actions {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: .15rem;
179
+ justify-content: flex-end;
180
+ }
24
181
 
25
- /* ── Tri, servi par le serveur ─────────────────────────────────────────── */
182
+ /* Les actions n'apparaissent qu'au survol de leur ligne : une colonne pleine
183
+ de boutons attire l'œil plus que les données. Elles restent visibles au
184
+ clavier, et sur tactile, où il n'y a pas de survol. */
185
+ @media (hover: hover) and (pointer: fine) {
186
+ .sia-table__table tbody .sia-table__actions {
187
+ opacity: .35;
188
+ transition: opacity var(--sia-duration-fast) var(--sia-easing-standard);
189
+ }
190
+ .sia-table__table tbody tr:hover .sia-table__actions,
191
+ .sia-table__table tbody .sia-table__actions:focus-within { opacity: 1; }
192
+ }
26
193
 
27
- .sia-data-table { position: relative; }
194
+ /* ── Le tri ─────────────────────────────────────────────────────────────── */
28
195
 
29
- .sia-data-table__sort {
196
+ .sia-table__sort {
30
197
  display: inline-flex;
31
198
  align-items: center;
32
- gap: .35rem;
199
+ gap: .3rem;
33
200
  padding: 0;
34
201
  border: 0;
35
- background: transparent;
202
+ background: none;
36
203
  color: inherit;
37
204
  font: inherit;
38
- text-transform: inherit;
39
205
  letter-spacing: inherit;
206
+ text-transform: inherit;
40
207
  cursor: pointer;
41
- transition: color var(--sia-duration-fast) var(--sia-easing-standard);
42
208
  }
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 {
209
+ .sia-table__sort:hover { color: var(--sia-foreground); }
210
+ .sia-table__sort-icon {
49
211
  display: grid;
50
212
  place-items: center;
51
- font-size: .75rem;
52
- opacity: 0;
53
- transform: translateY(2px);
213
+ opacity: .35;
214
+ font-size: .7rem;
215
+ transition: opacity var(--sia-duration-fast) var(--sia-easing-standard);
216
+ }
217
+ .sia-table__sort:hover .sia-table__sort-icon { opacity: .7; }
218
+ [aria-sort] .sia-table__sort { color: var(--sia-primary); }
219
+ [aria-sort] .sia-table__sort-icon { opacity: 1; }
220
+
221
+ /* ── L'apparition des lignes ────────────────────────────────────────────── */
222
+
223
+ /**
224
+ * Chaque ligne arrive quelques millisecondes après la précédente.
225
+ *
226
+ * Le décalage est plafonné : au-delà d'une dizaine de lignes, l'escalier
227
+ * devient une attente. Les suivantes arrivent ensemble, et personne ne le
228
+ * remarque — on ne regarde jamais la vingtième ligne en premier.
229
+ */
230
+ .sia-table__table tbody tr,
231
+ .sia-table-card {
232
+ animation: sia-table-row var(--sia-duration-base) var(--sia-easing-standard) backwards;
233
+ animation-delay: calc(min(var(--sia-table-row, 0), 10) * 22ms);
234
+ }
235
+ @keyframes sia-table-row {
236
+ from { opacity: 0; transform: translateY(.35rem); }
237
+ }
238
+
239
+ .sia-table__row--skeleton { animation: none; }
240
+
241
+ /* ── Les cartes ─────────────────────────────────────────────────────────── */
242
+
243
+ .sia-table__cards {
244
+ display: grid;
245
+ gap: .75rem;
246
+ grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
247
+ }
248
+
249
+ .sia-table-card {
250
+ display: flex;
251
+ flex-direction: column;
252
+ gap: .6rem;
253
+ padding: .875rem 1rem;
254
+ border: 1px solid var(--sia-border);
255
+ border-radius: calc(var(--sia-radius) * 1.25);
256
+ background: var(--sia-background);
54
257
  transition:
55
- opacity var(--sia-duration-fast) var(--sia-easing-standard),
56
- transform var(--sia-duration-base) var(--sia-easing-spring);
258
+ border-color var(--sia-duration-fast) var(--sia-easing-standard),
259
+ box-shadow var(--sia-duration-base) var(--sia-easing-standard),
260
+ transform var(--sia-duration-fast) var(--sia-easing-standard);
261
+ }
262
+ .sia-table-card--clickable { cursor: pointer; }
263
+ .sia-table-card--clickable:hover {
264
+ border-color: color-mix(in srgb, var(--sia-primary) 35%, var(--sia-border));
265
+ box-shadow: 0 6px 20px rgb(15 23 42 / .07);
266
+ transform: translateY(-2px);
57
267
  }
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
268
 
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;
269
+ .sia-table-card__title {
270
+ font-weight: 650;
271
+ font-size: .95rem;
272
+ overflow: hidden;
273
+ text-overflow: ellipsis;
274
+ white-space: nowrap;
275
+ }
276
+
277
+ .sia-table-card__body {
278
+ display: grid;
279
+ gap: .4rem .875rem;
280
+ grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
281
+ }
282
+
283
+ .sia-table-card__meta { display: grid; gap: .1rem; min-width: 0; }
284
+ .sia-table-card__label {
285
+ color: var(--sia-muted);
286
+ font-size: .68rem;
287
+ letter-spacing: .03em;
288
+ text-transform: uppercase;
289
+ }
290
+ .sia-table-card__value {
291
+ overflow: hidden;
292
+ text-overflow: ellipsis;
293
+ white-space: nowrap;
294
+ }
295
+
296
+ .sia-table-card__footer {
297
+ display: flex;
298
+ justify-content: flex-end;
299
+ padding-top: .35rem;
300
+ border-top: 1px solid var(--sia-border);
301
+ }
302
+
303
+ .sia-table-card--skeleton { gap: .5rem; }
304
+
305
+ @media (prefers-reduced-motion: reduce) {
306
+ .sia-table__table tbody tr,
307
+ .sia-table-card,
308
+ .sia-table__selection { animation: none; }
309
+ .sia-table-card--clickable:hover { transform: none; }
98
310
  }