@sia-ui/cli 0.7.1 → 0.8.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 +6 -0
- package/README.md +6 -0
- package/dist/registry/package/components/AccountMenu/index.tsx +107 -0
- package/dist/registry/package/components/AccountMenu/styles.css +30 -0
- package/dist/registry/package/components/ActivityLog/index.tsx +96 -25
- package/dist/registry/package/components/AmountDisplay/index.tsx +5 -2
- package/dist/registry/package/components/AppShell/index.tsx +44 -8
- package/dist/registry/package/components/AppShell/tab-bar.tsx +6 -3
- package/dist/registry/package/components/AsyncState/index.tsx +29 -7
- package/dist/registry/package/components/AuditMeta/index.tsx +12 -2
- package/dist/registry/package/components/Avatar/index.tsx +4 -9
- package/dist/registry/package/components/Badge/index.tsx +3 -2
- package/dist/registry/package/components/Badge/styles.css +4 -4
- package/dist/registry/package/components/BrandIcon/index.tsx +100 -0
- package/dist/registry/package/components/BrandIcon/styles.css +31 -0
- package/dist/registry/package/components/Breadcrumb/index.tsx +14 -2
- package/dist/registry/package/components/Button/index.tsx +3 -2
- package/dist/registry/package/components/Button/styles.css +4 -4
- package/dist/registry/package/components/Calendar/index.tsx +7 -3
- package/dist/registry/package/components/Card/index.tsx +14 -4
- package/dist/registry/package/components/ColorModeToggle/index.tsx +44 -0
- package/dist/registry/package/components/CommandPalette/index.tsx +3 -2
- package/dist/registry/package/components/ConfirmDialog/index.tsx +24 -4
- package/dist/registry/package/components/CrudPage/dialogs.tsx +53 -8
- package/dist/registry/package/components/CrudPage/index.tsx +99 -25
- package/dist/registry/package/components/CurrencyInput/index.tsx +5 -2
- package/dist/registry/package/components/DataTable/index.tsx +53 -18
- package/dist/registry/package/components/DataTable/row-actions.tsx +20 -8
- package/dist/registry/package/components/DataTable/types.ts +100 -0
- package/dist/registry/package/components/DatePicker/index.tsx +14 -6
- package/dist/registry/package/components/DateRangeFilter/index.tsx +12 -3
- package/dist/registry/package/components/DateRangePicker/index.tsx +13 -4
- package/dist/registry/package/components/DateTimePicker/index.tsx +3 -1
- package/dist/registry/package/components/Drawer/index.tsx +16 -6
- package/dist/registry/package/components/DropdownMenu/index.tsx +37 -4
- package/dist/registry/package/components/DropdownMenu/styles.css +10 -0
- package/dist/registry/package/components/DurationDisplay/index.tsx +20 -2
- package/dist/registry/package/components/EntityMeta/index.tsx +14 -9
- package/dist/registry/package/components/ErrorState/index.tsx +9 -3
- package/dist/registry/package/components/ErrorState/styles.css +1 -1
- package/dist/registry/package/components/EventCalendar/index.tsx +657 -9
- package/dist/registry/package/components/EventCalendar/styles.css +360 -8
- package/dist/registry/package/components/EventManager/index.tsx +456 -0
- package/dist/registry/package/components/EventManager/styles.css +12 -0
- package/dist/registry/package/components/Field/index.tsx +74 -27
- package/dist/registry/package/components/FileUpload/index.tsx +13 -3
- package/dist/registry/package/components/FiltersBar/index.tsx +8 -3
- package/dist/registry/package/components/FiltersBar/styles.css +7 -0
- package/dist/registry/package/components/Form/index.tsx +118 -14
- package/dist/registry/package/components/Form/styles.css +3 -0
- package/dist/registry/package/components/HoverCard/index.tsx +8 -4
- package/dist/registry/package/components/Icons/index.tsx +18 -0
- package/dist/registry/package/components/ImageUpload/index.tsx +17 -2
- package/dist/registry/package/components/Input/index.tsx +3 -2
- package/dist/registry/package/components/JobProgress/index.tsx +289 -0
- package/dist/registry/package/components/JobProgress/styles.css +58 -0
- package/dist/registry/package/components/JsonEditor/index.tsx +18 -3
- package/dist/registry/package/components/JsonView/index.tsx +282 -0
- package/dist/registry/package/components/JsonView/styles.css +99 -0
- package/dist/registry/package/components/LiveIndicator/index.tsx +43 -0
- package/dist/registry/package/components/LiveIndicator/styles.css +41 -0
- package/dist/registry/package/components/LogStream/index.tsx +545 -0
- package/dist/registry/package/components/LogStream/styles.css +152 -0
- package/dist/registry/package/components/MarkdownEditor/index.tsx +14 -3
- package/dist/registry/package/components/Modal/index.tsx +7 -1
- package/dist/registry/package/components/MultiSelect/index.tsx +14 -3
- package/dist/registry/package/components/OtpInput/index.tsx +2 -1
- package/dist/registry/package/components/Pagination/index.tsx +13 -2
- package/dist/registry/package/components/PasswordInput/index.tsx +23 -9
- package/dist/registry/package/components/PhoneInput/index.tsx +66 -30
- package/dist/registry/package/components/Popover/index.tsx +19 -4
- package/dist/registry/package/components/QrCode/index.tsx +3 -2
- package/dist/registry/package/components/RadioGroup/styles.css +3 -0
- package/dist/registry/package/components/Rating/index.tsx +6 -2
- package/dist/registry/package/components/ReferenceSelect/index.tsx +8 -1
- package/dist/registry/package/components/RelativeTime/index.tsx +5 -1
- package/dist/registry/package/components/RequireSession/index.tsx +10 -4
- package/dist/registry/package/components/Resource/index.tsx +70 -17
- package/dist/registry/package/components/RichTextEditor/index.tsx +2 -1
- package/dist/registry/package/components/SearchInput/index.tsx +4 -1
- package/dist/registry/package/components/SecretFields/index.tsx +70 -17
- package/dist/registry/package/components/Select/index.tsx +34 -12
- package/dist/registry/package/components/Sidebar/index.tsx +22 -6
- package/dist/registry/package/components/Slider/index.tsx +26 -5
- package/dist/registry/package/components/Spinner/index.tsx +3 -2
- package/dist/registry/package/components/StatCard/index.tsx +11 -3
- package/dist/registry/package/components/Statistic/index.tsx +5 -1
- package/dist/registry/package/components/Steps/styles.css +2 -2
- package/dist/registry/package/components/TagsInput/index.tsx +2 -1
- package/dist/registry/package/components/TimePicker/index.tsx +10 -4
- package/dist/registry/package/components/Toast/index.tsx +20 -5
- package/dist/registry/package/components/Tour/index.tsx +2 -1
- package/dist/registry/package/components/Transfer/index.tsx +2 -1
- package/dist/registry/package/components/Tree/index.tsx +3 -2
- package/dist/registry/package/components/TreeSelect/index.tsx +17 -4
- package/dist/registry/package/components/UnsavedChangesGuard/index.tsx +34 -3
- package/dist/registry/registry.json +257 -75
- package/dist/registry/styles/sia-ui.css +23 -4
- package/package.json +5 -5
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useMemo, useState, type Key, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
useSiaLocale,
|
|
5
|
+
type FormSubmitResult,
|
|
6
|
+
type FormValidator,
|
|
7
|
+
type PermissionRule,
|
|
7
8
|
} from "@sia-ui/headless";
|
|
8
|
-
import { PageHeader } from "../PageHeader";
|
|
9
|
-
import { Button } from "../Button";
|
|
10
|
-
import { Pagination } from "../Pagination";
|
|
9
|
+
import { PageHeader, type PageHeaderProps } from "../PageHeader";
|
|
10
|
+
import { Button, type ButtonProps } from "../Button";
|
|
11
|
+
import { Pagination, type PaginationProps } from "../Pagination";
|
|
11
12
|
import { DataTable } from "../DataTable";
|
|
12
13
|
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "../Icons";
|
|
13
14
|
import type {
|
|
@@ -29,15 +30,21 @@ import {
|
|
|
29
30
|
} from "../Resource";
|
|
30
31
|
import {
|
|
31
32
|
CrudDialogs,
|
|
33
|
+
type CrudDescriptionsProps,
|
|
32
34
|
type CrudDialogOptions,
|
|
33
35
|
type CrudDialogState,
|
|
36
|
+
type CrudFormProps,
|
|
37
|
+
type CrudModalProps,
|
|
34
38
|
} from "./dialogs";
|
|
35
39
|
import "./styles.css";
|
|
36
40
|
|
|
37
41
|
export {
|
|
38
42
|
CrudDialogs,
|
|
43
|
+
type CrudDescriptionsProps,
|
|
39
44
|
type CrudDialogOptions,
|
|
40
45
|
type CrudDialogState,
|
|
46
|
+
type CrudFormProps,
|
|
47
|
+
type CrudModalProps,
|
|
41
48
|
} from "./dialogs";
|
|
42
49
|
|
|
43
50
|
/**
|
|
@@ -217,6 +224,40 @@ interface CrudPageBase<T> {
|
|
|
217
224
|
* ni ressource ne montrerait rien, et le dirait à l'exécution.
|
|
218
225
|
*/
|
|
219
226
|
export type CrudPageProps<T> = CrudPageBase<T> &
|
|
227
|
+
/*
|
|
228
|
+
Les props des composants que la page assemble, transmises telles quelles.
|
|
229
|
+
Chacune omet ce que la page calcule elle-même, et qu'une surcharge
|
|
230
|
+
casserait — une pagination qui ne suit plus la page, une boîte qui ne se
|
|
231
|
+
ferme plus.
|
|
232
|
+
*/
|
|
233
|
+
{
|
|
234
|
+
/**
|
|
235
|
+
* Les props du `PageHeader`. Sans `actions`, composées de
|
|
236
|
+
* `headerActions` et du bouton de création. `className` s'ajoute.
|
|
237
|
+
*/
|
|
238
|
+
pageHeaderProps?: Partial<Omit<PageHeaderProps, "actions">>;
|
|
239
|
+
/**
|
|
240
|
+
* Les props du bouton de création. Sans `onClick`, qui ouvre le
|
|
241
|
+
* formulaire, ni `children` : le libellé passe par
|
|
242
|
+
* `operations.create.label`.
|
|
243
|
+
*/
|
|
244
|
+
createButtonProps?: Partial<Omit<ButtonProps, "onClick" | "children">>;
|
|
245
|
+
/** Les props du bouton de suppression groupée. Sans `onClick`. */
|
|
246
|
+
bulkDeleteButtonProps?: Partial<Omit<ButtonProps, "onClick">>;
|
|
247
|
+
/**
|
|
248
|
+
* Les props de la `Pagination` — `jumpTo`, `compact`, `siblingCount`.
|
|
249
|
+
* Sans `page`, `totalPages` ni `onPageChange`, qui ont leurs props ici.
|
|
250
|
+
*/
|
|
251
|
+
paginationProps?: Partial<
|
|
252
|
+
Omit<PaginationProps, "page" | "totalPages" | "onPageChange">
|
|
253
|
+
>;
|
|
254
|
+
/** Les props des `Modal` de formulaire et de détail — voir `CrudModalProps`. */
|
|
255
|
+
modalProps?: CrudModalProps;
|
|
256
|
+
/** Les props du `Form` des boîtes — voir `CrudFormProps`. */
|
|
257
|
+
formProps?: CrudFormProps;
|
|
258
|
+
/** Les props de la vue de détail par défaut — voir `CrudDescriptionsProps`. */
|
|
259
|
+
descriptionsProps?: CrudDescriptionsProps;
|
|
260
|
+
} &
|
|
220
261
|
(
|
|
221
262
|
| {
|
|
222
263
|
/**
|
|
@@ -235,6 +276,24 @@ export type CrudPageProps<T> = CrudPageBase<T> &
|
|
|
235
276
|
}
|
|
236
277
|
);
|
|
237
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Glisse un nom — texte ou élément — à la place de `{name}` dans un message
|
|
281
|
+
* de la locale. `formatMessage` ne sait placer que du texte ; le nom d'une
|
|
282
|
+
* ressource peut être un élément.
|
|
283
|
+
*/
|
|
284
|
+
function avecNom(modele: string, nom: ReactNode): ReactNode {
|
|
285
|
+
const [avant, ...apres] = modele.split("{name}");
|
|
286
|
+
return apres.length === 0 ? (
|
|
287
|
+
modele
|
|
288
|
+
) : (
|
|
289
|
+
<>
|
|
290
|
+
{avant}
|
|
291
|
+
{nom}
|
|
292
|
+
{apres.join("{name}")}
|
|
293
|
+
</>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
238
297
|
/**
|
|
239
298
|
* Une page de liste, avec ses quatre opérations.
|
|
240
299
|
*
|
|
@@ -288,7 +347,15 @@ export function CrudPage<T>({
|
|
|
288
347
|
table,
|
|
289
348
|
children,
|
|
290
349
|
className,
|
|
350
|
+
pageHeaderProps,
|
|
351
|
+
createButtonProps,
|
|
352
|
+
bulkDeleteButtonProps,
|
|
353
|
+
paginationProps,
|
|
354
|
+
modalProps,
|
|
355
|
+
formProps,
|
|
356
|
+
descriptionsProps,
|
|
291
357
|
}: CrudPageProps<T>) {
|
|
358
|
+
const locale = useSiaLocale();
|
|
292
359
|
const [boite, setBoite] = useState<CrudDialogState<T> | null>(null);
|
|
293
360
|
|
|
294
361
|
/**
|
|
@@ -306,9 +373,9 @@ export function CrudPage<T>({
|
|
|
306
373
|
columns: resourceColumns(resource),
|
|
307
374
|
detailColumns: resourceDetailColumns(resource),
|
|
308
375
|
fields: resourceFormFields(resource, "create"),
|
|
309
|
-
validate: resourceValidator(resource, "create"),
|
|
376
|
+
validate: resourceValidator(resource, "create", locale.resource),
|
|
310
377
|
editFields: resourceFormFields(resource, "edit"),
|
|
311
|
-
editValidate: resourceValidator(resource, "edit"),
|
|
378
|
+
editValidate: resourceValidator(resource, "edit", locale.resource),
|
|
312
379
|
searchKeys: resourceSearchKeys(resource),
|
|
313
380
|
defaults: resourceDefaults(resource),
|
|
314
381
|
getRowKey: resourceRowKey(resource),
|
|
@@ -317,7 +384,7 @@ export function CrudPage<T>({
|
|
|
317
384
|
label: resource.label,
|
|
318
385
|
}
|
|
319
386
|
: undefined,
|
|
320
|
-
[resource],
|
|
387
|
+
[resource, locale.resource],
|
|
321
388
|
);
|
|
322
389
|
|
|
323
390
|
const columns = colonnesFournies ?? derive?.columns ?? [];
|
|
@@ -406,24 +473,24 @@ export function CrudPage<T>({
|
|
|
406
473
|
});
|
|
407
474
|
};
|
|
408
475
|
|
|
409
|
-
ajouter("view", consulter, { label:
|
|
410
|
-
ajouter("edit", modifier, { label:
|
|
476
|
+
ajouter("view", consulter, { label: locale.crudPage.view, icon: <EyeIcon /> });
|
|
477
|
+
ajouter("edit", modifier, { label: locale.edit, icon: <PencilIcon /> });
|
|
411
478
|
ajouter("delete", onDelete, {
|
|
412
|
-
label:
|
|
479
|
+
label: locale.delete,
|
|
413
480
|
icon: <TrashIcon />,
|
|
414
481
|
tone: "danger",
|
|
415
482
|
// Détruire sans demander est une faute. On peut la retirer — encore
|
|
416
483
|
// faut-il l'avoir écrit.
|
|
417
484
|
confirm: {
|
|
418
|
-
title:
|
|
419
|
-
description:
|
|
420
|
-
confirmLabel:
|
|
485
|
+
title: locale.crudPage.deleteTitle,
|
|
486
|
+
description: locale.crudPage.deleteDescription,
|
|
487
|
+
confirmLabel: locale.delete,
|
|
421
488
|
destructive: true,
|
|
422
489
|
},
|
|
423
490
|
});
|
|
424
491
|
|
|
425
492
|
return liste;
|
|
426
|
-
}, [operations, onDelete, modifier, consulter, derive]);
|
|
493
|
+
}, [operations, onDelete, modifier, consulter, derive, locale]);
|
|
427
494
|
|
|
428
495
|
const toutesLesActions = useMemo(() => {
|
|
429
496
|
if (!extraRowActions) {
|
|
@@ -459,11 +526,12 @@ export function CrudPage<T>({
|
|
|
459
526
|
const boutonCreer =
|
|
460
527
|
creer && creation && creationAutorisee ? (
|
|
461
528
|
<Button
|
|
462
|
-
onClick={creer}
|
|
463
529
|
disabled={creation.disabled ?? false}
|
|
464
530
|
leftIcon={creation.icon ?? <PlusIcon />}
|
|
531
|
+
{...createButtonProps}
|
|
532
|
+
onClick={creer}
|
|
465
533
|
>
|
|
466
|
-
{creation.label ?? derive?.label?.create ??
|
|
534
|
+
{creation.label ?? derive?.label?.create ?? locale.create}
|
|
467
535
|
</Button>
|
|
468
536
|
) : null;
|
|
469
537
|
const enTeteActions =
|
|
@@ -483,6 +551,7 @@ export function CrudPage<T>({
|
|
|
483
551
|
: {})}
|
|
484
552
|
{...(eyebrow ? { eyebrow } : {})}
|
|
485
553
|
{...(headingLevel ? { level: headingLevel } : {})}
|
|
554
|
+
{...pageHeaderProps}
|
|
486
555
|
{...(enTeteActions ? { actions: enTeteActions } : {})}
|
|
487
556
|
/>
|
|
488
557
|
|
|
@@ -511,12 +580,13 @@ export function CrudPage<T>({
|
|
|
511
580
|
variant="outline"
|
|
512
581
|
tone="danger"
|
|
513
582
|
leftIcon={<TrashIcon />}
|
|
583
|
+
{...bulkDeleteButtonProps}
|
|
514
584
|
onClick={async () => {
|
|
515
585
|
await onBulkDelete(keys, rows);
|
|
516
586
|
clear();
|
|
517
587
|
}}
|
|
518
588
|
>
|
|
519
|
-
|
|
589
|
+
{bulkDeleteButtonProps?.children ?? locale.delete}
|
|
520
590
|
</Button>
|
|
521
591
|
),
|
|
522
592
|
}
|
|
@@ -547,6 +617,9 @@ export function CrudPage<T>({
|
|
|
547
617
|
toFormValues={toFormValues}
|
|
548
618
|
renderDetail={typeof detail === "function" ? detail : undefined}
|
|
549
619
|
getRowKey={getRowKey}
|
|
620
|
+
modalProps={modalProps}
|
|
621
|
+
formProps={formProps}
|
|
622
|
+
descriptionsProps={descriptionsProps}
|
|
550
623
|
/*
|
|
551
624
|
Les titres des boîtes reprennent le nom de la ressource, faute de
|
|
552
625
|
mieux : « Modifier la facture » se lit mieux que « Modifier », et
|
|
@@ -556,8 +629,8 @@ export function CrudPage<T>({
|
|
|
556
629
|
dialog={{
|
|
557
630
|
...(derive?.label?.singular
|
|
558
631
|
? {
|
|
559
|
-
createTitle: derive.label.create ??
|
|
560
|
-
editTitle:
|
|
632
|
+
createTitle: derive.label.create ?? locale.create,
|
|
633
|
+
editTitle: avecNom(locale.crudPage.editTitle, derive.label.singular),
|
|
561
634
|
viewTitle: derive.label.singular,
|
|
562
635
|
}
|
|
563
636
|
: {}),
|
|
@@ -568,13 +641,14 @@ export function CrudPage<T>({
|
|
|
568
641
|
|
|
569
642
|
{page !== undefined && totalPages !== undefined && onPageChange && (
|
|
570
643
|
<Pagination
|
|
571
|
-
page={page}
|
|
572
|
-
totalPages={totalPages}
|
|
573
|
-
onPageChange={onPageChange}
|
|
574
644
|
showTotal
|
|
575
645
|
{...(pageSize !== undefined ? { pageSize } : {})}
|
|
576
646
|
{...(onPageSizeChange ? { onPageSizeChange } : {})}
|
|
577
647
|
{...(pageSizeOptions ? { pageSizeOptions } : {})}
|
|
648
|
+
{...paginationProps}
|
|
649
|
+
page={page}
|
|
650
|
+
totalPages={totalPages}
|
|
651
|
+
onPageChange={onPageChange}
|
|
578
652
|
/>
|
|
579
653
|
)}
|
|
580
654
|
</section>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { forwardRef, useState, type InputHTMLAttributes } from "react";
|
|
2
2
|
import { cn, formatCurrency, parseCurrency } from "@sia-ui/utils";
|
|
3
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
4
|
import "./styles.css";
|
|
4
5
|
|
|
5
|
-
export interface CurrencyInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "inputMode"> { value?: number | null; defaultValue?: number | null; currency?: string; locale?: string; onValueChange?: (value: number | null) => void; invalid?: boolean; }
|
|
6
|
+
export interface CurrencyInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "inputMode"> { value?: number | null; defaultValue?: number | null; currency?: string; /** La langue du format et de la saisie. Par défaut, celle de la locale SIA. */ locale?: string; onValueChange?: (value: number | null) => void; invalid?: boolean; }
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* La saisie d'un montant.
|
|
@@ -11,7 +12,9 @@ export interface CurrencyInputProps extends Omit<InputHTMLAttributes<HTMLInputEl
|
|
|
11
12
|
* de milliers et le symbole appartiennent à l'affichage, et un serveur qui
|
|
12
13
|
* reçoit « 1 250 000 F CFA » ne peut rien en faire.
|
|
13
14
|
*/
|
|
14
|
-
export const CurrencyInput = forwardRef<HTMLInputElement, CurrencyInputProps>(({ value, defaultValue = null, currency = "XAF", locale
|
|
15
|
+
export const CurrencyInput = forwardRef<HTMLInputElement, CurrencyInputProps>(({ value, defaultValue = null, currency = "XAF", locale: localeProp, onValueChange, invalid, className, onBlur, onFocus, ...props }, ref) => {
|
|
16
|
+
const messages = useSiaLocale();
|
|
17
|
+
const locale = localeProp ?? messages.language;
|
|
15
18
|
const [focused, setFocused] = useState(false);
|
|
16
19
|
const [internal, setInternal] = useState<number | null>(defaultValue);
|
|
17
20
|
const current = value === undefined ? internal : value;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState, type Key, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
|
-
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
|
+
import { plural, useSiaLocale } from "@sia-ui/headless";
|
|
4
4
|
import { useMediaQuery } from "@sia-ui/react";
|
|
5
5
|
import { Checkbox } from "../Checkbox";
|
|
6
6
|
import { SearchInput } from "../SearchInput";
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
DataTableProps,
|
|
15
15
|
DataTableView,
|
|
16
16
|
RowAction,
|
|
17
|
+
RowActionsSlots,
|
|
17
18
|
} from "./types";
|
|
18
19
|
import { renduCellule, valeurDe } from "./values";
|
|
19
20
|
import "./styles.css";
|
|
@@ -57,6 +58,7 @@ export function DataTable<T>({
|
|
|
57
58
|
searchPlaceholder,
|
|
58
59
|
onSearch,
|
|
59
60
|
searchDelay = 0,
|
|
61
|
+
searchInputProps,
|
|
60
62
|
layout = "auto",
|
|
61
63
|
maxHeight,
|
|
62
64
|
stickyHeader = false,
|
|
@@ -67,6 +69,10 @@ export function DataTable<T>({
|
|
|
67
69
|
inlineActionsLimit = 2,
|
|
68
70
|
actionsHeader,
|
|
69
71
|
actionsDisplay = "icon",
|
|
72
|
+
rowActionButtonProps,
|
|
73
|
+
rowActionIconButtonProps,
|
|
74
|
+
rowActionsMenuProps,
|
|
75
|
+
confirmDialogProps,
|
|
70
76
|
can,
|
|
71
77
|
view: viewProp,
|
|
72
78
|
defaultView = "table",
|
|
@@ -77,14 +83,18 @@ export function DataTable<T>({
|
|
|
77
83
|
renderCard,
|
|
78
84
|
loading = false,
|
|
79
85
|
skeletonRows = 5,
|
|
86
|
+
skeletonProps,
|
|
80
87
|
error,
|
|
81
88
|
onRetry,
|
|
89
|
+
errorStateProps,
|
|
82
90
|
empty,
|
|
91
|
+
emptyStateProps,
|
|
83
92
|
selectable = false,
|
|
84
93
|
selectedKeys: selectedProp,
|
|
85
94
|
defaultSelectedKeys,
|
|
86
95
|
onSelectionChange,
|
|
87
96
|
isRowSelectable,
|
|
97
|
+
checkboxProps,
|
|
88
98
|
selectionActions,
|
|
89
99
|
sort,
|
|
90
100
|
onSortChange,
|
|
@@ -181,6 +191,12 @@ export function DataTable<T>({
|
|
|
181
191
|
typeof rowActions === "function" ? rowActions(row, index) : (rowActions ?? []);
|
|
182
192
|
|
|
183
193
|
const aDesActions = rowActions !== undefined;
|
|
194
|
+
const slotsActions: RowActionsSlots = {
|
|
195
|
+
button: rowActionButtonProps,
|
|
196
|
+
iconButton: rowActionIconButtonProps,
|
|
197
|
+
menu: rowActionsMenuProps,
|
|
198
|
+
confirmDialog: confirmDialogProps,
|
|
199
|
+
};
|
|
184
200
|
const vide = !loading && !error && lignes.length === 0;
|
|
185
201
|
|
|
186
202
|
const enTete =
|
|
@@ -211,8 +227,9 @@ export function DataTable<T>({
|
|
|
211
227
|
<div className="sia-table__tools">
|
|
212
228
|
{searchKeys && (
|
|
213
229
|
<SearchInput
|
|
214
|
-
value={recherche}
|
|
215
230
|
placeholder={searchPlaceholder ?? locale.search}
|
|
231
|
+
{...searchInputProps}
|
|
232
|
+
value={recherche}
|
|
216
233
|
// Avec un délai, c'est le champ qui temporise : il sait aussi
|
|
217
234
|
// déclencher tout de suite sur Entrée et à l'effacement.
|
|
218
235
|
{...(onSearch && searchDelay > 0 ? { onSearch, searchDelay } : {})}
|
|
@@ -225,7 +242,7 @@ export function DataTable<T>({
|
|
|
225
242
|
{toolbar}
|
|
226
243
|
|
|
227
244
|
{showViewToggle && !(responsive && etroit) && (
|
|
228
|
-
<div className="sia-table__views" role="group" aria-label=
|
|
245
|
+
<div className="sia-table__views" role="group" aria-label={locale.dataTable.viewToggle}>
|
|
229
246
|
{(["table", "cards"] as const).map((mode) => (
|
|
230
247
|
<button
|
|
231
248
|
key={mode}
|
|
@@ -239,7 +256,7 @@ export function DataTable<T>({
|
|
|
239
256
|
>
|
|
240
257
|
{mode === "table" ? <IconeListe /> : <IconeGrille />}
|
|
241
258
|
<span className="sia-visually-hidden">
|
|
242
|
-
{mode === "table" ?
|
|
259
|
+
{mode === "table" ? locale.dataTable.tableView : locale.dataTable.cardsView}
|
|
243
260
|
</span>
|
|
244
261
|
</button>
|
|
245
262
|
))}
|
|
@@ -256,7 +273,7 @@ export function DataTable<T>({
|
|
|
256
273
|
{selectable && selection.length > 0 && selectionActions && (
|
|
257
274
|
<div className="sia-table__selection">
|
|
258
275
|
<span className="sia-table__selection-count">
|
|
259
|
-
{
|
|
276
|
+
{plural(locale.dataTable, "selected", selection.length, locale.language)}
|
|
260
277
|
</span>
|
|
261
278
|
{selectionActions({
|
|
262
279
|
keys: selection,
|
|
@@ -267,12 +284,20 @@ export function DataTable<T>({
|
|
|
267
284
|
)}
|
|
268
285
|
|
|
269
286
|
{error ? (
|
|
270
|
-
<ErrorState
|
|
287
|
+
<ErrorState
|
|
288
|
+
{...errorStateProps}
|
|
289
|
+
description={error}
|
|
290
|
+
{...(onRetry ? { onRetry } : {})}
|
|
291
|
+
/>
|
|
271
292
|
) : vide ? (
|
|
272
293
|
<EmptyState
|
|
273
|
-
|
|
294
|
+
{...emptyStateProps}
|
|
295
|
+
// `empty` est la voie courte : là où il déclare une clé, il
|
|
296
|
+
// l'emporte sur le réglage complet.
|
|
297
|
+
title={empty?.title ?? emptyStateProps?.title ?? locale.empty}
|
|
274
298
|
{...(empty?.description ? { description: empty.description } : {})}
|
|
275
299
|
{...(empty?.action ? { action: empty.action } : {})}
|
|
300
|
+
{...(empty?.icon ? { icon: empty.icon } : {})}
|
|
276
301
|
/>
|
|
277
302
|
) : vue === "cards" ? (
|
|
278
303
|
<CartesVue
|
|
@@ -280,6 +305,8 @@ export function DataTable<T>({
|
|
|
280
305
|
colonnes={colonnes}
|
|
281
306
|
loading={loading}
|
|
282
307
|
skeletonRows={skeletonRows}
|
|
308
|
+
skeletonProps={skeletonProps}
|
|
309
|
+
slotsActions={slotsActions}
|
|
283
310
|
cle={cle}
|
|
284
311
|
{...(renderCard ? { renderCard } : {})}
|
|
285
312
|
{...(onRowClick ? { onRowClick } : {})}
|
|
@@ -307,11 +334,12 @@ export function DataTable<T>({
|
|
|
307
334
|
{selectable && (
|
|
308
335
|
<th className="sia-table__cell--select" scope="col">
|
|
309
336
|
<Checkbox
|
|
337
|
+
label=""
|
|
338
|
+
aria-label={locale.dataTable.selectAll}
|
|
339
|
+
{...checkboxProps}
|
|
310
340
|
checked={toutesCochees}
|
|
311
341
|
indeterminate={partiellement}
|
|
312
342
|
onValueChange={basculerTout}
|
|
313
|
-
label=""
|
|
314
|
-
aria-label="Tout sélectionner"
|
|
315
343
|
/>
|
|
316
344
|
</th>
|
|
317
345
|
)}
|
|
@@ -361,7 +389,7 @@ export function DataTable<T>({
|
|
|
361
389
|
{aDesActions && (
|
|
362
390
|
<th className="sia-table__cell--actions" scope="col">
|
|
363
391
|
{actionsHeader ?? (
|
|
364
|
-
<span className="sia-visually-hidden">
|
|
392
|
+
<span className="sia-visually-hidden">{locale.dataTable.actions}</span>
|
|
365
393
|
)}
|
|
366
394
|
</th>
|
|
367
395
|
)}
|
|
@@ -374,17 +402,17 @@ export function DataTable<T>({
|
|
|
374
402
|
<tr key={`fantome-${i}`} className="sia-table__row--skeleton">
|
|
375
403
|
{selectable && (
|
|
376
404
|
<td>
|
|
377
|
-
<Skeleton width="1.1rem" height="1.1rem" />
|
|
405
|
+
<Skeleton width="1.1rem" height="1.1rem" {...skeletonProps} />
|
|
378
406
|
</td>
|
|
379
407
|
)}
|
|
380
408
|
{colonnes.map((c) => (
|
|
381
409
|
<td key={c.key}>
|
|
382
|
-
<Skeleton height=".8rem" />
|
|
410
|
+
<Skeleton height=".8rem" {...skeletonProps} />
|
|
383
411
|
</td>
|
|
384
412
|
))}
|
|
385
413
|
{aDesActions && (
|
|
386
414
|
<td>
|
|
387
|
-
<Skeleton width="3rem" height=".8rem" />
|
|
415
|
+
<Skeleton width="3rem" height=".8rem" {...skeletonProps} />
|
|
388
416
|
</td>
|
|
389
417
|
)}
|
|
390
418
|
</tr>
|
|
@@ -411,11 +439,12 @@ export function DataTable<T>({
|
|
|
411
439
|
{selectable && (
|
|
412
440
|
<td className="sia-table__cell--select">
|
|
413
441
|
<Checkbox
|
|
442
|
+
label=""
|
|
443
|
+
aria-label={locale.dataTable.selectRow}
|
|
444
|
+
{...checkboxProps}
|
|
414
445
|
checked={cochee}
|
|
415
446
|
disabled={!cochable}
|
|
416
447
|
onValueChange={() => basculerLigne(k)}
|
|
417
|
-
label=""
|
|
418
|
-
aria-label="Sélectionner la ligne"
|
|
419
448
|
onClick={(e) => e.stopPropagation()}
|
|
420
449
|
/>
|
|
421
450
|
</td>
|
|
@@ -443,6 +472,7 @@ export function DataTable<T>({
|
|
|
443
472
|
inlineLimit={inlineActionsLimit}
|
|
444
473
|
display={actionsDisplay}
|
|
445
474
|
can={can}
|
|
475
|
+
slots={slotsActions}
|
|
446
476
|
/>
|
|
447
477
|
</td>
|
|
448
478
|
)}
|
|
@@ -462,6 +492,8 @@ interface CartesVueProps<T> {
|
|
|
462
492
|
colonnes: Array<DataTableColumn<T>>;
|
|
463
493
|
loading: boolean;
|
|
464
494
|
skeletonRows: number;
|
|
495
|
+
skeletonProps: DataTableProps<T>["skeletonProps"];
|
|
496
|
+
slotsActions: RowActionsSlots;
|
|
465
497
|
cle: (row: T, index: number) => Key;
|
|
466
498
|
renderCard?: (row: T, index: number) => ReactNode;
|
|
467
499
|
onRowClick?: (row: T, index: number) => void;
|
|
@@ -484,6 +516,8 @@ function CartesVue<T>({
|
|
|
484
516
|
colonnes,
|
|
485
517
|
loading,
|
|
486
518
|
skeletonRows,
|
|
519
|
+
skeletonProps,
|
|
520
|
+
slotsActions,
|
|
487
521
|
cle,
|
|
488
522
|
renderCard,
|
|
489
523
|
onRowClick,
|
|
@@ -505,9 +539,9 @@ function CartesVue<T>({
|
|
|
505
539
|
<div className="sia-table__cards">
|
|
506
540
|
{Array.from({ length: skeletonRows }, (_, i) => (
|
|
507
541
|
<div key={i} className="sia-table-card sia-table-card--skeleton">
|
|
508
|
-
<Skeleton height="1rem" width="60%" />
|
|
509
|
-
<Skeleton height=".75rem" />
|
|
510
|
-
<Skeleton height=".75rem" width="80%" />
|
|
542
|
+
<Skeleton height="1rem" width="60%" {...skeletonProps} />
|
|
543
|
+
<Skeleton height=".75rem" {...skeletonProps} />
|
|
544
|
+
<Skeleton height=".75rem" width="80%" {...skeletonProps} />
|
|
511
545
|
</div>
|
|
512
546
|
))}
|
|
513
547
|
</div>
|
|
@@ -556,6 +590,7 @@ function CartesVue<T>({
|
|
|
556
590
|
inlineLimit={inlineActionsLimit}
|
|
557
591
|
display={actionsDisplay}
|
|
558
592
|
can={can}
|
|
593
|
+
slots={slotsActions}
|
|
559
594
|
/>
|
|
560
595
|
</footer>
|
|
561
596
|
)}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useState, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
|
-
import type
|
|
3
|
+
import { useSiaLocale, type PermissionRule } from "@sia-ui/headless";
|
|
4
4
|
import { Button } from "../Button";
|
|
5
5
|
import { IconButton } from "../IconButton";
|
|
6
6
|
import { DropdownMenu } from "../DropdownMenu";
|
|
7
7
|
import { ConfirmDialog } from "../ConfirmDialog";
|
|
8
8
|
import { MoreHorizontalIcon } from "../Icons";
|
|
9
|
-
import type { RowAction } from "./types";
|
|
9
|
+
import type { RowAction, RowActionsSlots } from "./types";
|
|
10
10
|
|
|
11
11
|
interface RowActionsProps<T> {
|
|
12
12
|
row: T;
|
|
@@ -15,6 +15,7 @@ interface RowActionsProps<T> {
|
|
|
15
15
|
inlineLimit: number;
|
|
16
16
|
display: "icon" | "label";
|
|
17
17
|
can: ((rule: PermissionRule) => boolean) | undefined;
|
|
18
|
+
slots: RowActionsSlots;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -31,7 +32,9 @@ export function RowActions<T>({
|
|
|
31
32
|
inlineLimit,
|
|
32
33
|
display,
|
|
33
34
|
can,
|
|
35
|
+
slots,
|
|
34
36
|
}: RowActionsProps<T>) {
|
|
37
|
+
const locale = useSiaLocale();
|
|
35
38
|
// Une confirmation en attente. Une seule à la fois : deux dialogues
|
|
36
39
|
// superposés sur la même ligne n'ont aucun sens.
|
|
37
40
|
const [enAttente, setEnAttente] = useState<RowAction<T> | null>(null);
|
|
@@ -68,12 +71,15 @@ export function RowActions<T>({
|
|
|
68
71
|
return display === "icon" && action.icon ? (
|
|
69
72
|
<IconButton
|
|
70
73
|
key={action.key}
|
|
71
|
-
label={nom}
|
|
72
|
-
icon={action.icon}
|
|
73
74
|
variant="ghost"
|
|
74
75
|
size="sm"
|
|
76
|
+
{...slots.iconButton}
|
|
77
|
+
// L'infobulle réglée pour tout le tableau garde le libellé de
|
|
78
|
+
// l'action : sans lui, elle n'aurait plus rien à dire.
|
|
79
|
+
tooltip={{ ...slots.iconButton?.tooltip, content: action.label }}
|
|
80
|
+
label={nom}
|
|
81
|
+
icon={action.icon}
|
|
75
82
|
disabled={desactivee}
|
|
76
|
-
tooltip={{ content: action.label }}
|
|
77
83
|
onClick={() => declencher(action)}
|
|
78
84
|
/>
|
|
79
85
|
) : (
|
|
@@ -81,9 +87,12 @@ export function RowActions<T>({
|
|
|
81
87
|
key={action.key}
|
|
82
88
|
variant="ghost"
|
|
83
89
|
size="sm"
|
|
90
|
+
{...slots.button}
|
|
91
|
+
// Le ton et l'icône déclarés sur l'action sont plus précis que le
|
|
92
|
+
// réglage commun à tout le tableau.
|
|
84
93
|
{...(action.tone ? { tone: action.tone } : {})}
|
|
85
|
-
disabled={desactivee}
|
|
86
94
|
{...(action.icon ? { leftIcon: action.icon } : {})}
|
|
95
|
+
disabled={desactivee}
|
|
87
96
|
onClick={() => declencher(action)}
|
|
88
97
|
>
|
|
89
98
|
{action.label}
|
|
@@ -94,6 +103,7 @@ export function RowActions<T>({
|
|
|
94
103
|
{dansLeMenu.length > 0 && (
|
|
95
104
|
<DropdownMenu
|
|
96
105
|
placement="bottom-end"
|
|
106
|
+
{...slots.menu}
|
|
97
107
|
items={dansLeMenu.map((action) => ({
|
|
98
108
|
key: action.key,
|
|
99
109
|
label: action.label,
|
|
@@ -104,16 +114,18 @@ export function RowActions<T>({
|
|
|
104
114
|
}))}
|
|
105
115
|
>
|
|
106
116
|
<IconButton
|
|
107
|
-
label="Autres actions"
|
|
108
|
-
icon={<MoreHorizontalIcon />}
|
|
109
117
|
variant="ghost"
|
|
110
118
|
size="sm"
|
|
119
|
+
{...slots.iconButton}
|
|
120
|
+
label={locale.dataTable.moreActions}
|
|
121
|
+
icon={<MoreHorizontalIcon />}
|
|
111
122
|
/>
|
|
112
123
|
</DropdownMenu>
|
|
113
124
|
)}
|
|
114
125
|
|
|
115
126
|
{enAttente?.confirm && (
|
|
116
127
|
<ConfirmDialog
|
|
128
|
+
{...slots.confirmDialog}
|
|
117
129
|
open
|
|
118
130
|
onOpenChange={(ouvert) => {
|
|
119
131
|
if (!ouvert) setEnAttente(null);
|