@sia-ui/cli 0.4.0 → 0.5.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/CHANGELOG.md +34 -0
- package/README.md +34 -0
- package/dist/registry/package/components/Calendar/index.tsx +4 -4
- package/dist/registry/package/components/CrudPage/dialogs.tsx +20 -20
- package/dist/registry/package/components/CrudPage/index.tsx +34 -4
- package/dist/registry/package/components/DataTable/index.tsx +5 -1
- package/dist/registry/package/components/DataTable/types.ts +8 -0
- package/dist/registry/package/components/Drawer/index.tsx +9 -3
- package/dist/registry/package/components/Form/index.tsx +69 -3
- package/dist/registry/package/components/Pagination/index.tsx +168 -61
- package/dist/registry/package/components/Pagination/styles.css +133 -38
- package/dist/registry/package/components/PhoneInput/index.tsx +46 -29
- package/dist/registry/package/components/PhoneInput/styles.css +7 -21
- package/dist/registry/package/components/Select/index.tsx +34 -8
- package/dist/registry/package/components/Select/styles.css +18 -0
- package/dist/registry/package/components/Timer/index.tsx +1 -1
- package/dist/registry/package/lib/form-adapter-rhf.tsx +23 -10
- package/dist/registry/registry.json +19 -12
- package/dist/registry/styles/sia-ui.css +6 -0
- package/package.json +5 -5
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useId,
|
|
4
|
+
useMemo,
|
|
5
|
+
useState,
|
|
6
|
+
type FormEvent,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
2
9
|
import { cn } from "@sia-ui/utils";
|
|
3
10
|
import { paginationItems, useSiaLocale } from "@sia-ui/headless";
|
|
4
11
|
import { ChevronLeftIcon, ChevronRightIcon } from "../Icons";
|
|
12
|
+
import { Select } from "../Select";
|
|
5
13
|
import "./styles.css";
|
|
6
14
|
|
|
7
15
|
export interface PaginationProps {
|
|
@@ -22,10 +30,46 @@ export interface PaginationProps {
|
|
|
22
30
|
showTotal?: boolean;
|
|
23
31
|
/** Masque les libellés et ne garde que les flèches. */
|
|
24
32
|
compact?: boolean;
|
|
33
|
+
|
|
34
|
+
/* ─────────────────────────────────────────────── taille de page */
|
|
35
|
+
|
|
36
|
+
/** Le nombre de lignes par page en vigueur. */
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Fourni avec `pageSize`, ajoute le choix du nombre de lignes par page.
|
|
40
|
+
*
|
|
41
|
+
* Seul le nouveau nombre est remonté : revenir en page 1 est l'affaire de
|
|
42
|
+
* qui tient l'état — `setPerPage` de `useTableQuery` le fait déjà.
|
|
43
|
+
*/
|
|
44
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Les tailles proposées. Par défaut `10, 20, 50, 100`. Une taille en
|
|
47
|
+
* vigueur absente de la liste y est ajoutée, pour que le champ ne paraisse
|
|
48
|
+
* jamais vide.
|
|
49
|
+
*/
|
|
50
|
+
pageSizeOptions?: number[];
|
|
51
|
+
/** Le libellé du choix. Par défaut `locale.perPage` — « Par page ». */
|
|
52
|
+
pageSizeLabel?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Remplace entièrement le contrôle — des boutons segmentés, un champ libre.
|
|
55
|
+
* Le choix des tailles et leur tri sont déjà faits.
|
|
56
|
+
*/
|
|
57
|
+
renderPageSize?: (context: PageSizeContext) => ReactNode;
|
|
58
|
+
|
|
25
59
|
ariaLabel?: string;
|
|
26
60
|
className?: string;
|
|
27
61
|
}
|
|
28
62
|
|
|
63
|
+
/** Ce que reçoit `renderPageSize`. */
|
|
64
|
+
export interface PageSizeContext {
|
|
65
|
+
pageSize: number;
|
|
66
|
+
options: number[];
|
|
67
|
+
onChange: (pageSize: number) => void;
|
|
68
|
+
label: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
|
|
72
|
+
|
|
29
73
|
/**
|
|
30
74
|
* La navigation entre les pages.
|
|
31
75
|
*
|
|
@@ -42,6 +86,11 @@ export function Pagination({
|
|
|
42
86
|
jumpTo = false,
|
|
43
87
|
showTotal = false,
|
|
44
88
|
compact = false,
|
|
89
|
+
pageSize,
|
|
90
|
+
onPageSizeChange,
|
|
91
|
+
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
92
|
+
pageSizeLabel,
|
|
93
|
+
renderPageSize,
|
|
45
94
|
ariaLabel,
|
|
46
95
|
className,
|
|
47
96
|
}: PaginationProps) {
|
|
@@ -59,9 +108,25 @@ export function Pagination({
|
|
|
59
108
|
// un retour arrière du navigateur.
|
|
60
109
|
useEffect(() => setDraft(String(current)), [current]);
|
|
61
110
|
|
|
62
|
-
|
|
111
|
+
const tailles = useMemo(() => {
|
|
112
|
+
if (pageSize === undefined || pageSizeOptions.includes(pageSize)) {
|
|
113
|
+
return pageSizeOptions;
|
|
114
|
+
}
|
|
115
|
+
return [...pageSizeOptions, pageSize].sort((a, b) => a - b);
|
|
116
|
+
}, [pageSize, pageSizeOptions]);
|
|
117
|
+
|
|
118
|
+
const choixTaille =
|
|
119
|
+
pageSize !== undefined && onPageSizeChange && tailles.length > 0;
|
|
120
|
+
const plusieursPages = totalPages > 1;
|
|
121
|
+
const tailleLibelle = pageSizeLabel ?? locale.perPage;
|
|
63
122
|
|
|
64
|
-
|
|
123
|
+
// Une seule page n'a rien à paginer — sauf si l'on peut encore changer de
|
|
124
|
+
// taille : après avoir choisi 100 lignes, il faut pouvoir revenir à 10.
|
|
125
|
+
if (!plusieursPages && !choixTaille) return null;
|
|
126
|
+
|
|
127
|
+
const items = plusieursPages
|
|
128
|
+
? paginationItems(current, totalPages, siblingCount)
|
|
129
|
+
: [];
|
|
65
130
|
|
|
66
131
|
const jump = (event: FormEvent) => {
|
|
67
132
|
event.preventDefault();
|
|
@@ -84,71 +149,113 @@ export function Pagination({
|
|
|
84
149
|
)}
|
|
85
150
|
aria-label={ariaLabel ?? "Pagination"}
|
|
86
151
|
>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
{!compact && <span>{previousLabel}</span>}
|
|
96
|
-
</button>
|
|
97
|
-
|
|
98
|
-
{items.map((item) =>
|
|
99
|
-
typeof item === "number" ? (
|
|
152
|
+
{/*
|
|
153
|
+
Deux groupes, qui passent à la ligne chacun d'un seul tenant : la
|
|
154
|
+
navigation d'un côté, les réglages de l'autre. Sans eux, un écran un
|
|
155
|
+
peu étroit renvoyait « Par page » seul sur une seconde ligne, loin de
|
|
156
|
+
son champ.
|
|
157
|
+
*/}
|
|
158
|
+
{plusieursPages && (
|
|
159
|
+
<div className="sia-pagination__pages">
|
|
100
160
|
<button
|
|
101
161
|
type="button"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
aria-
|
|
105
|
-
onClick={() => onPageChange(
|
|
162
|
+
className="sia-pagination__step"
|
|
163
|
+
disabled={current === 1}
|
|
164
|
+
aria-label={previousLabel}
|
|
165
|
+
onClick={() => onPageChange(current - 1)}
|
|
106
166
|
>
|
|
107
|
-
|
|
167
|
+
<ChevronLeftIcon />
|
|
168
|
+
{!compact && <span>{previousLabel}</span>}
|
|
108
169
|
</button>
|
|
109
|
-
) : (
|
|
110
|
-
<span key={item} className="sia-pagination__gap" aria-hidden="true">
|
|
111
|
-
…
|
|
112
|
-
</span>
|
|
113
|
-
),
|
|
114
|
-
)}
|
|
115
170
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
171
|
+
{items.map((item) =>
|
|
172
|
+
typeof item === "number" ? (
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
key={item}
|
|
176
|
+
className="sia-pagination__page"
|
|
177
|
+
aria-current={item === current ? "page" : undefined}
|
|
178
|
+
onClick={() => onPageChange(item)}
|
|
179
|
+
>
|
|
180
|
+
{item}
|
|
181
|
+
</button>
|
|
182
|
+
) : (
|
|
183
|
+
<span key={item} className="sia-pagination__gap" aria-hidden="true">
|
|
184
|
+
…
|
|
185
|
+
</span>
|
|
186
|
+
),
|
|
187
|
+
)}
|
|
188
|
+
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className="sia-pagination__step"
|
|
192
|
+
disabled={current === totalPages}
|
|
193
|
+
aria-label={nextLabel}
|
|
194
|
+
onClick={() => onPageChange(current + 1)}
|
|
195
|
+
>
|
|
196
|
+
{!compact && <span>{nextLabel}</span>}
|
|
197
|
+
<ChevronRightIcon />
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
131
200
|
)}
|
|
132
201
|
|
|
133
|
-
{jumpTo && (
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
202
|
+
{(choixTaille || (plusieursPages && (showTotal || jumpTo))) && (
|
|
203
|
+
<div className="sia-pagination__settings">
|
|
204
|
+
{plusieursPages && showTotal && (
|
|
205
|
+
<span className="sia-pagination__total">
|
|
206
|
+
{locale.page} {current} / {totalPages}
|
|
207
|
+
</span>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
{choixTaille && (
|
|
211
|
+
<div className="sia-pagination__size">
|
|
212
|
+
{renderPageSize ? (
|
|
213
|
+
renderPageSize({
|
|
214
|
+
pageSize,
|
|
215
|
+
options: tailles,
|
|
216
|
+
onChange: onPageSizeChange,
|
|
217
|
+
label: tailleLibelle,
|
|
218
|
+
})
|
|
219
|
+
) : (
|
|
220
|
+
<>
|
|
221
|
+
<span aria-hidden="true">{tailleLibelle}</span>
|
|
222
|
+
<Select
|
|
223
|
+
variant="embedded"
|
|
224
|
+
aria-label={tailleLibelle}
|
|
225
|
+
placement="top-end"
|
|
226
|
+
value={String(pageSize)}
|
|
227
|
+
options={tailles.map((taille) => ({
|
|
228
|
+
value: String(taille),
|
|
229
|
+
label: String(taille),
|
|
230
|
+
}))}
|
|
231
|
+
onValueChange={(valeur) => onPageSizeChange(Number(valeur))}
|
|
232
|
+
/>
|
|
233
|
+
</>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{plusieursPages && jumpTo && (
|
|
239
|
+
<form className="sia-pagination__jump" onSubmit={jump}>
|
|
240
|
+
<label htmlFor={jumpId}>{locale.goToPage}</label>
|
|
241
|
+
<input
|
|
242
|
+
id={jumpId}
|
|
243
|
+
// `text` plutôt que `number` : le champ numérique natif accepte
|
|
244
|
+
// « 1e5 » et « -- », et vide sa valeur sans prévenir quand elle
|
|
245
|
+
// ne lui plaît pas.
|
|
246
|
+
type="text"
|
|
247
|
+
inputMode="numeric"
|
|
248
|
+
value={draft}
|
|
249
|
+
aria-label={locale.goToPage}
|
|
250
|
+
onChange={(event) => setDraft(event.target.value.replace(/\D/g, ""))}
|
|
251
|
+
onBlur={jump}
|
|
252
|
+
/>
|
|
253
|
+
<span className="sia-pagination__jump-total" aria-hidden="true">
|
|
254
|
+
/ {totalPages}
|
|
255
|
+
</span>
|
|
256
|
+
</form>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
152
259
|
)}
|
|
153
260
|
</nav>
|
|
154
261
|
);
|
|
@@ -1,19 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
/* ── Disposition ──────────────────────────────────────────────────────────
|
|
2
|
+
Deux groupes : la navigation à gauche, les réglages à droite. Chacun passe
|
|
3
|
+
à la ligne d'un seul tenant — jamais un libellé séparé de son champ. Tout
|
|
4
|
+
partage une hauteur, `--sia-pagination-size`, pour que boutons, liste et
|
|
5
|
+
champ s'alignent au pixel. */
|
|
6
|
+
.sia-pagination {
|
|
7
|
+
--sia-pagination-size: 2.25rem;
|
|
8
|
+
container: sia-pagination / inline-size;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
gap: .75rem 1.5rem;
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sia-pagination__pages {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: .3rem;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sia-pagination__settings {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: flex-end;
|
|
30
|
+
gap: .5rem 1.25rem;
|
|
31
|
+
margin-left: auto;
|
|
32
|
+
color: var(--sia-muted);
|
|
33
|
+
font-size: .8rem;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── Boutons de page ──────────────────────────────────────────────────── */
|
|
2
38
|
|
|
3
|
-
.sia-
|
|
39
|
+
.sia-pagination__step,
|
|
40
|
+
.sia-pagination__page {
|
|
4
41
|
display: inline-flex;
|
|
5
42
|
align-items: center;
|
|
6
43
|
justify-content: center;
|
|
7
44
|
gap: .35rem;
|
|
8
|
-
min-width:
|
|
9
|
-
|
|
45
|
+
min-width: var(--sia-pagination-size);
|
|
46
|
+
height: var(--sia-pagination-size);
|
|
10
47
|
padding: 0 .65rem;
|
|
11
48
|
border: 1px solid var(--sia-border);
|
|
12
49
|
border-radius: var(--sia-radius);
|
|
13
50
|
color: var(--sia-foreground);
|
|
14
51
|
background: var(--sia-background);
|
|
15
52
|
font: inherit;
|
|
53
|
+
font-size: .875rem;
|
|
16
54
|
font-variant-numeric: tabular-nums;
|
|
55
|
+
line-height: 1;
|
|
17
56
|
cursor: pointer;
|
|
18
57
|
transition:
|
|
19
58
|
background-color var(--sia-duration-fast) var(--sia-easing-standard),
|
|
@@ -21,15 +60,24 @@
|
|
|
21
60
|
color var(--sia-duration-fast) var(--sia-easing-standard),
|
|
22
61
|
transform var(--sia-duration-instant) var(--sia-easing-standard);
|
|
23
62
|
}
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.sia-
|
|
63
|
+
/* Le survol ne touche pas la page courante. Écrit `button:hover`, il passait
|
|
64
|
+
devant `[aria-current]` par spécificité : fond clair sous un chiffre resté
|
|
65
|
+
blanc, donc illisible. */
|
|
66
|
+
.sia-pagination__step:hover:not(:disabled),
|
|
67
|
+
.sia-pagination__page:hover:not([aria-current="page"]) {
|
|
68
|
+
border-color: var(--sia-border-strong);
|
|
69
|
+
background: var(--sia-surface);
|
|
70
|
+
}
|
|
71
|
+
.sia-pagination__step:active:not(:disabled),
|
|
72
|
+
.sia-pagination__page:active { transform: scale(.94); }
|
|
73
|
+
.sia-pagination__step:disabled { cursor: not-allowed; opacity: .5; }
|
|
74
|
+
.sia-pagination__step:focus-visible,
|
|
75
|
+
.sia-pagination__page:focus-visible {
|
|
28
76
|
outline: 3px solid color-mix(in srgb, var(--sia-primary) 30%, transparent);
|
|
29
77
|
outline-offset: 2px;
|
|
30
78
|
}
|
|
31
79
|
|
|
32
|
-
.sia-
|
|
80
|
+
.sia-pagination__page[aria-current="page"] {
|
|
33
81
|
color: var(--sia-primary-foreground);
|
|
34
82
|
border-color: var(--sia-primary);
|
|
35
83
|
background: var(--sia-primary);
|
|
@@ -38,6 +86,14 @@
|
|
|
38
86
|
le seul changement de couleur se remarque mal. */
|
|
39
87
|
animation: sia-pagination-current var(--sia-duration-base) var(--sia-easing-spring);
|
|
40
88
|
}
|
|
89
|
+
/* La page courante garde sa couleur au survol, un ton plus sombre pour dire
|
|
90
|
+
qu'elle reste un bouton. */
|
|
91
|
+
.sia-pagination__page[aria-current="page"]:hover {
|
|
92
|
+
color: var(--sia-primary-foreground);
|
|
93
|
+
border-color: color-mix(in srgb, var(--sia-primary) 85%, var(--sia-foreground));
|
|
94
|
+
background: color-mix(in srgb, var(--sia-primary) 85%, var(--sia-foreground));
|
|
95
|
+
}
|
|
96
|
+
|
|
41
97
|
@keyframes sia-pagination-current {
|
|
42
98
|
0% { transform: scale(.85); }
|
|
43
99
|
60% { transform: scale(1.06); }
|
|
@@ -45,49 +101,64 @@
|
|
|
45
101
|
}
|
|
46
102
|
|
|
47
103
|
.sia-pagination__step svg { flex: none; font-size: .9em; }
|
|
48
|
-
.sia-pagination__gap {
|
|
104
|
+
.sia-pagination__gap {
|
|
105
|
+
display: inline-flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
min-width: 1.25rem;
|
|
109
|
+
height: var(--sia-pagination-size);
|
|
110
|
+
color: var(--sia-muted);
|
|
111
|
+
}
|
|
49
112
|
|
|
50
113
|
.sia-pagination--compact .sia-pagination__step { padding: 0 .5rem; }
|
|
51
114
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
115
|
+
/* ── Réglages ─────────────────────────────────────────────────────────────
|
|
116
|
+
Libellé discret à gauche, contrôle à droite, même hauteur que les boutons.
|
|
117
|
+
Taille de page et saut direct sont habillés à l'identique. */
|
|
56
118
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.sia-pagination__total {
|
|
60
|
-
margin-left: .5rem;
|
|
61
|
-
color: var(--sia-muted);
|
|
62
|
-
font-size: .8rem;
|
|
63
|
-
font-variant-numeric: tabular-nums;
|
|
64
|
-
white-space: nowrap;
|
|
65
|
-
}
|
|
119
|
+
.sia-pagination__total { font-variant-numeric: tabular-nums; }
|
|
66
120
|
|
|
121
|
+
.sia-pagination__size,
|
|
67
122
|
.sia-pagination__jump {
|
|
68
|
-
display: flex;
|
|
123
|
+
display: inline-flex;
|
|
69
124
|
align-items: center;
|
|
70
|
-
gap: .
|
|
71
|
-
margin-left: .5rem;
|
|
72
|
-
color: var(--sia-muted);
|
|
73
|
-
font-size: .8rem;
|
|
74
|
-
white-space: nowrap;
|
|
125
|
+
gap: .5rem;
|
|
75
126
|
}
|
|
76
127
|
.sia-pagination__jump label { cursor: pointer; }
|
|
77
128
|
|
|
129
|
+
.sia-pagination__size .sia-select__trigger,
|
|
78
130
|
.sia-pagination__jump input {
|
|
79
|
-
|
|
80
|
-
min-height: 2.25rem;
|
|
81
|
-
padding: 0 .5rem;
|
|
131
|
+
height: var(--sia-pagination-size);
|
|
82
132
|
border: 1px solid var(--sia-border);
|
|
83
133
|
border-radius: var(--sia-radius);
|
|
84
|
-
background: var(--sia-background);
|
|
85
134
|
color: var(--sia-foreground);
|
|
135
|
+
background: var(--sia-background);
|
|
86
136
|
font: inherit;
|
|
137
|
+
font-size: .875rem;
|
|
87
138
|
font-variant-numeric: tabular-nums;
|
|
139
|
+
transition:
|
|
140
|
+
background-color var(--sia-duration-fast) var(--sia-easing-standard),
|
|
141
|
+
border-color var(--sia-duration-fast) var(--sia-easing-standard);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Le Select logé prend l'allure d'un champ de la barre. */
|
|
145
|
+
.sia-pagination__size .sia-select__trigger {
|
|
146
|
+
min-height: 0;
|
|
147
|
+
min-width: 4.25rem;
|
|
148
|
+
padding: 0 .5rem 0 .7rem;
|
|
149
|
+
justify-content: space-between;
|
|
150
|
+
}
|
|
151
|
+
.sia-pagination__size .sia-select__trigger:hover:not(:disabled) {
|
|
152
|
+
border-color: var(--sia-border-strong);
|
|
153
|
+
background: var(--sia-surface);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.sia-pagination__jump input {
|
|
157
|
+
width: 3.25rem;
|
|
158
|
+
padding: 0 .5rem;
|
|
88
159
|
text-align: center;
|
|
89
|
-
transition: border-color var(--sia-duration-fast) var(--sia-easing-standard);
|
|
90
160
|
}
|
|
161
|
+
.sia-pagination__jump input:hover { border-color: var(--sia-border-strong); }
|
|
91
162
|
.sia-pagination__jump input:focus-visible {
|
|
92
163
|
border-color: var(--sia-primary);
|
|
93
164
|
outline: 3px solid color-mix(in srgb, var(--sia-primary) 30%, transparent);
|
|
@@ -96,8 +167,32 @@
|
|
|
96
167
|
|
|
97
168
|
.sia-pagination__jump-total { font-variant-numeric: tabular-nums; }
|
|
98
169
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
170
|
+
/* ── Largeur disponible ───────────────────────────────────────────────────
|
|
171
|
+
Réglé sur la largeur de la barre, pas de l'écran : une pagination posée
|
|
172
|
+
dans une carte étroite se replie comme sur un téléphone.
|
|
173
|
+
|
|
174
|
+
Sous 52rem, les deux groupes ne tiennent plus côte à côte : les réglages
|
|
175
|
+
prennent leur propre ligne, pleine largeur : le total à gauche, sous la
|
|
176
|
+
navigation, les contrôles groupés à droite. */
|
|
177
|
+
@container sia-pagination (max-width: 52rem) {
|
|
178
|
+
.sia-pagination__settings {
|
|
179
|
+
justify-content: flex-end;
|
|
180
|
+
width: 100%;
|
|
181
|
+
margin-left: 0;
|
|
182
|
+
}
|
|
183
|
+
/* L'information à gauche, les contrôles groupés à droite. */
|
|
184
|
+
.sia-pagination__total { margin-right: auto; }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Sous 34rem, tout se centre : la navigation sur sa ligne, les réglages sur
|
|
188
|
+
les suivantes, sans élément isolé contre un bord. */
|
|
189
|
+
@container sia-pagination (max-width: 34rem) {
|
|
190
|
+
.sia-pagination__pages { justify-content: center; width: 100%; }
|
|
191
|
+
.sia-pagination__settings { justify-content: center; column-gap: 1.5rem; }
|
|
192
|
+
.sia-pagination__total { margin-right: 0; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Sous 26rem, les libellés des flèches tombent ; les flèches restent. */
|
|
196
|
+
@container sia-pagination (max-width: 26rem) {
|
|
197
|
+
.sia-pagination__step span { display: none; }
|
|
103
198
|
}
|
|
@@ -2,6 +2,7 @@ import { forwardRef, useMemo, useState } from "react";
|
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
import { useSiaLocale } from "@sia-ui/headless";
|
|
4
4
|
import { Input, type InputProps } from "../Input";
|
|
5
|
+
import { Select, type SelectOption } from "../Select";
|
|
5
6
|
import "./styles.css";
|
|
6
7
|
|
|
7
8
|
export interface PhoneCountry {
|
|
@@ -128,6 +129,18 @@ export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
|
128
129
|
? value.slice(country.dial.length)
|
|
129
130
|
: digitsOnly(value);
|
|
130
131
|
|
|
132
|
+
// La recherche porte sur le nom, l'indicatif et le code ISO : « cam »,
|
|
133
|
+
// « 237 » et « cm » trouvent tous le Cameroun.
|
|
134
|
+
const options = useMemo<SelectOption[]>(
|
|
135
|
+
() =>
|
|
136
|
+
countries.map((entry) => ({
|
|
137
|
+
value: entry.code,
|
|
138
|
+
label: `${entry.flag} ${entry.label} (${entry.dial})`,
|
|
139
|
+
keywords: [entry.code],
|
|
140
|
+
})),
|
|
141
|
+
[countries],
|
|
142
|
+
);
|
|
143
|
+
|
|
131
144
|
const commit = (next: string) => {
|
|
132
145
|
if (!controlled) setInternal(next);
|
|
133
146
|
onValueChange?.(next);
|
|
@@ -150,36 +163,40 @@ export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
|
150
163
|
commit(next ? `${country.dial}${next}` : "");
|
|
151
164
|
}}
|
|
152
165
|
left={
|
|
153
|
-
|
|
154
|
-
<span className="sia-
|
|
155
|
-
|
|
166
|
+
lockCountry ? (
|
|
167
|
+
<span className="sia-phone__country">
|
|
168
|
+
<span className="sia-phone__flag" aria-hidden="true">
|
|
169
|
+
{country.flag}
|
|
170
|
+
</span>
|
|
171
|
+
<span className="sia-phone__dial">{country.dial}</span>
|
|
156
172
|
</span>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
) : (
|
|
174
|
+
<Select
|
|
175
|
+
variant="embedded"
|
|
176
|
+
searchable
|
|
177
|
+
className="sia-phone__country"
|
|
178
|
+
aria-label={locale.countryCode}
|
|
179
|
+
options={options}
|
|
180
|
+
value={country.code}
|
|
181
|
+
disabled={disabled ?? false}
|
|
182
|
+
renderValue={() => (
|
|
183
|
+
<span className="sia-phone__value">
|
|
184
|
+
<span className="sia-phone__flag" aria-hidden="true">
|
|
185
|
+
{country.flag}
|
|
186
|
+
</span>
|
|
187
|
+
<span className="sia-phone__dial">{country.dial}</span>
|
|
188
|
+
</span>
|
|
189
|
+
)}
|
|
190
|
+
onValueChange={(code) => {
|
|
191
|
+
const next = countries.find((entry) => entry.code === code);
|
|
192
|
+
if (!next) return;
|
|
193
|
+
setPicked(next.code);
|
|
194
|
+
// Le numéro national suit le changement d'indicatif : il est
|
|
195
|
+
// rare mais courant de corriger le pays après coup.
|
|
196
|
+
commit(national ? `${next.dial}${national}` : "");
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
183
200
|
}
|
|
184
201
|
/>
|
|
185
202
|
);
|
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
.sia-phone__field { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
|
|
2
2
|
|
|
3
|
+
/* L'indicatif, séparé du numéro par un filet. Choisi dans le `Select` du
|
|
4
|
+
système, en variante logée : même panneau, même recherche, même clavier
|
|
5
|
+
que partout ailleurs — là où le `<select>` natif n'offrait ni recherche ni
|
|
6
|
+
le thème. */
|
|
3
7
|
.sia-phone__country {
|
|
4
|
-
position: relative;
|
|
5
8
|
display: inline-flex;
|
|
6
9
|
align-items: center;
|
|
7
10
|
gap: .3rem;
|
|
8
|
-
padding-right: .
|
|
11
|
+
padding-right: .35rem;
|
|
9
12
|
border-right: 1px solid var(--sia-border);
|
|
10
13
|
color: var(--sia-foreground);
|
|
11
14
|
font-size: .85rem;
|
|
12
15
|
white-space: nowrap;
|
|
13
|
-
transition: background-color var(--sia-duration-fast) var(--sia-easing-standard);
|
|
14
|
-
border-radius: var(--sia-radius) 0 0 var(--sia-radius);
|
|
15
16
|
}
|
|
16
|
-
.sia-phone__country
|
|
17
|
+
.sia-phone__country .sia-select__trigger { align-self: stretch; }
|
|
17
18
|
|
|
19
|
+
.sia-phone__value { display: inline-flex; align-items: center; gap: .3rem; }
|
|
18
20
|
.sia-phone__flag { font-size: 1.05rem; line-height: 1; }
|
|
19
21
|
.sia-phone__dial { color: var(--sia-muted); font-variant-numeric: tabular-nums; }
|
|
20
|
-
|
|
21
|
-
/* Le `select` natif recouvre l'affichage : il ouvre la liste du système, qui
|
|
22
|
-
est la seule à fonctionner correctement sur mobile — un menu réimplémenté
|
|
23
|
-
se retrouve sous le clavier virtuel. */
|
|
24
|
-
.sia-phone__select {
|
|
25
|
-
position: absolute;
|
|
26
|
-
inset: 0;
|
|
27
|
-
width: 100%;
|
|
28
|
-
height: 100%;
|
|
29
|
-
border: 0;
|
|
30
|
-
opacity: 0;
|
|
31
|
-
cursor: pointer;
|
|
32
|
-
appearance: none;
|
|
33
|
-
}
|
|
34
|
-
.sia-phone__select:disabled { cursor: not-allowed; }
|
|
35
|
-
.sia-phone__select:focus-visible + * { outline: 2px solid var(--sia-primary); }
|