@sia-ui/cli 0.7.0 → 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 +12 -0
- package/README.md +12 -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 +44 -12
- package/dist/registry/package/components/Select/styles.css +6 -3
- 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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { cn } from "@sia-ui/utils";
|
|
11
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
11
12
|
import "./styles.css";
|
|
12
13
|
|
|
13
14
|
export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
@@ -22,13 +23,6 @@ export type AvatarVariant = "soft" | "solid" | "outline";
|
|
|
22
23
|
*/
|
|
23
24
|
export type AvatarStatus = "online" | "offline" | "busy" | "away";
|
|
24
25
|
|
|
25
|
-
const LIBELLES_STATUT: Record<AvatarStatus, string> = {
|
|
26
|
-
online: "en ligne",
|
|
27
|
-
offline: "hors ligne",
|
|
28
|
-
busy: "occupé",
|
|
29
|
-
away: "absent",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
26
|
/**
|
|
33
27
|
* Les teintes dérivées du nom.
|
|
34
28
|
*
|
|
@@ -121,7 +115,7 @@ export interface AvatarProps
|
|
|
121
115
|
initials?: 1 | 2;
|
|
122
116
|
|
|
123
117
|
status?: AvatarStatus;
|
|
124
|
-
/** Le libellé du statut, pour les lecteurs d'écran. */
|
|
118
|
+
/** Le libellé du statut, pour les lecteurs d'écran. Par défaut, celui de la locale. */
|
|
125
119
|
statusLabel?: string;
|
|
126
120
|
|
|
127
121
|
/** Un liseré qui détache l'avatar de son fond. */
|
|
@@ -159,6 +153,7 @@ export function Avatar({
|
|
|
159
153
|
style,
|
|
160
154
|
...props
|
|
161
155
|
}: AvatarProps) {
|
|
156
|
+
const libellesStatut = useSiaLocale().avatar;
|
|
162
157
|
const groupe = useContext(AvatarGroupContext);
|
|
163
158
|
const [etatImage, setEtatImage] = useState<"attente" | "prete" | "echouee">(
|
|
164
159
|
src ? "attente" : "echouee",
|
|
@@ -227,7 +222,7 @@ export function Avatar({
|
|
|
227
222
|
annoncent la personne une seule fois, statut compris. */}
|
|
228
223
|
<span className="sia-visually-hidden">
|
|
229
224
|
{name}
|
|
230
|
-
{status ? ` — ${statusLabel ??
|
|
225
|
+
{status ? ` — ${statusLabel ?? libellesStatut[status]}` : ""}
|
|
231
226
|
</span>
|
|
232
227
|
</>
|
|
233
228
|
);
|
|
@@ -2,7 +2,7 @@ import { type HTMLAttributes, type ReactNode } from "react";
|
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
import { Spinner, type SpinnerProps } from "../Spinner";
|
|
4
4
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
5
|
-
import { useComponentDefaults } from "@sia-ui/headless";
|
|
5
|
+
import { useComponentDefaults, useSiaLocale } from "@sia-ui/headless";
|
|
6
6
|
import "./styles.css";
|
|
7
7
|
|
|
8
8
|
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
@@ -37,7 +37,8 @@ export function Badge({
|
|
|
37
37
|
const defaults = useComponentDefaults<BadgeProps>("badge");
|
|
38
38
|
const tone = toneProp ?? defaults.tone ?? "neutral";
|
|
39
39
|
const variant = variantProp ?? defaults.variant ?? "soft";
|
|
40
|
-
const
|
|
40
|
+
const locale = useSiaLocale();
|
|
41
|
+
const loadingLabel = loadingLabelProp ?? defaults.loadingLabel ?? locale.loading;
|
|
41
42
|
return (
|
|
42
43
|
<span
|
|
43
44
|
className={cn(
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
.sia-badge--soft.sia-badge--danger { color:var(--sia-danger); background:color-mix(in srgb,var(--sia-danger) 12%,transparent); }
|
|
7
7
|
.sia-badge--solid.sia-badge--neutral { color:var(--sia-background); background:var(--sia-foreground); }
|
|
8
8
|
.sia-badge--solid.sia-badge--primary { color:var(--sia-primary-foreground); background:var(--sia-primary); }
|
|
9
|
-
.sia-badge--solid.sia-badge--success { color
|
|
10
|
-
.sia-badge--solid.sia-badge--warning { color
|
|
11
|
-
.sia-badge--solid.sia-badge--danger { color
|
|
9
|
+
.sia-badge--solid.sia-badge--success { color:var(--sia-success-foreground); background:var(--sia-success); }
|
|
10
|
+
.sia-badge--solid.sia-badge--warning { color:var(--sia-warning-foreground); background:var(--sia-warning); }
|
|
11
|
+
.sia-badge--solid.sia-badge--danger { color:var(--sia-danger-foreground); background:var(--sia-danger); }
|
|
12
12
|
.sia-badge--outline { border-color:currentColor; background:transparent; }
|
|
13
13
|
.sia-badge--outline.sia-badge--primary { color:var(--sia-primary); }
|
|
14
14
|
.sia-badge--outline.sia-badge--success { color:var(--sia-success); }
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
.sia-badge--loading { cursor:progress; }
|
|
22
22
|
/* ComponentTone completeness for components created before the shared contract. */
|
|
23
23
|
.sia-badge--soft.sia-badge--info { color:var(--sia-info); background:color-mix(in srgb,var(--sia-info) 12%,transparent); }
|
|
24
|
-
.sia-badge--solid.sia-badge--info { color
|
|
24
|
+
.sia-badge--solid.sia-badge--info { color:var(--sia-info-foreground); background:var(--sia-info); }
|
|
25
25
|
.sia-badge--outline.sia-badge--neutral { color:var(--sia-muted); }
|
|
26
26
|
.sia-badge--outline.sia-badge--info { color:var(--sia-info); }
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type CSSProperties, type SVGAttributes } from "react";
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { luminance } from "@sia-ui/tokens";
|
|
4
|
+
import "./styles.css";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Une icône de marque, dans la forme d'un objet `simple-icons` : `path` est
|
|
8
|
+
* le tracé d'un SVG 24×24, `hex` la couleur de la marque, sans dièse.
|
|
9
|
+
*/
|
|
10
|
+
export interface BrandIconData {
|
|
11
|
+
title: string;
|
|
12
|
+
path: string;
|
|
13
|
+
hex: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BrandIconProps
|
|
17
|
+
extends Omit<SVGAttributes<SVGSVGElement>, "children" | "title"> {
|
|
18
|
+
icon: BrandIconData;
|
|
19
|
+
/** La taille du carré. `1em` par défaut : l'icône suit le texte. */
|
|
20
|
+
size?: number | string;
|
|
21
|
+
/** La couleur de la marque (défaut), ou `currentColor` quand il vaut `false`. */
|
|
22
|
+
colored?: boolean;
|
|
23
|
+
/** Le nom accessible. Par défaut, `icon.title`. */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Masquée des lecteurs d'écran : l'icône accompagne un texte qui la nomme déjà. */
|
|
26
|
+
decorative?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** En dessous, la marque est quasi noire ; au-dessus, quasi blanche. */
|
|
30
|
+
const SOMBRE = 0.08;
|
|
31
|
+
const CLAIRE = 0.85;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Le logo d'un service tiers — Vercel, Supabase, GitHub — sans dépendance.
|
|
35
|
+
*
|
|
36
|
+
* Le système ne livre aucun logo : il prend l'objet d'icône que le projet
|
|
37
|
+
* importe lui-même, et ne dépend donc ni d'un paquet d'icônes ni de sa
|
|
38
|
+
* version.
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { siVercel } from "simple-icons";
|
|
42
|
+
* <BrandIcon icon={siVercel} />
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Une marque quasi noire (Vercel, GitHub) disparaîtrait sur un fond sombre,
|
|
46
|
+
* une marque quasi blanche sur un fond clair : sur le thème où elle serait
|
|
47
|
+
* illisible, elle prend la couleur du texte plutôt que la sienne.
|
|
48
|
+
*/
|
|
49
|
+
export function BrandIcon({
|
|
50
|
+
icon,
|
|
51
|
+
size = "1em",
|
|
52
|
+
colored = true,
|
|
53
|
+
title,
|
|
54
|
+
decorative = false,
|
|
55
|
+
className,
|
|
56
|
+
style,
|
|
57
|
+
...props
|
|
58
|
+
}: BrandIconProps) {
|
|
59
|
+
const name = title ?? icon.title;
|
|
60
|
+
const lum = colored ? luminance(icon.hex) : undefined;
|
|
61
|
+
const contrast =
|
|
62
|
+
lum === undefined || Number.isNaN(lum)
|
|
63
|
+
? undefined
|
|
64
|
+
: lum < SOMBRE
|
|
65
|
+
? "dark"
|
|
66
|
+
: lum > CLAIRE
|
|
67
|
+
? "light"
|
|
68
|
+
: undefined;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<svg
|
|
72
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
+
viewBox="0 0 24 24"
|
|
74
|
+
width={size}
|
|
75
|
+
height={size}
|
|
76
|
+
focusable="false"
|
|
77
|
+
{...(decorative
|
|
78
|
+
? { "aria-hidden": true }
|
|
79
|
+
: { role: "img", "aria-label": name })}
|
|
80
|
+
{...props}
|
|
81
|
+
className={cn(
|
|
82
|
+
"sia-brand-icon",
|
|
83
|
+
colored && "sia-brand-icon--colored",
|
|
84
|
+
contrast && `sia-brand-icon--${contrast}`,
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
style={
|
|
88
|
+
colored
|
|
89
|
+
? ({
|
|
90
|
+
"--sia-brand-icon-color": `#${icon.hex.replace(/^#/, "")}`,
|
|
91
|
+
...style,
|
|
92
|
+
} as CSSProperties)
|
|
93
|
+
: style
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
{!decorative && <title>{name}</title>}
|
|
97
|
+
<path d={icon.path} />
|
|
98
|
+
</svg>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Un glyphe dans le texte : aligné sur la ligne de base comme une lettre,
|
|
2
|
+
jamais étiré ni rétréci par un parent flex. */
|
|
3
|
+
.sia-brand-icon {
|
|
4
|
+
display: inline-block;
|
|
5
|
+
flex: none;
|
|
6
|
+
vertical-align: -.125em;
|
|
7
|
+
fill: currentColor;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* La couleur de la marque arrive par une variable posée sur l'élément : la
|
|
11
|
+
feuille garde la main pour la retirer là où elle serait illisible. */
|
|
12
|
+
.sia-brand-icon--colored { fill: var(--sia-brand-icon-color, currentColor); }
|
|
13
|
+
|
|
14
|
+
/* Quasi blanche : illisible sur le thème clair, qui est celui par défaut. */
|
|
15
|
+
.sia-brand-icon--colored.sia-brand-icon--light { fill: currentColor; }
|
|
16
|
+
|
|
17
|
+
/* Thème sombre, choisi par l'utilisateur ou suivi du système : la marque
|
|
18
|
+
claire retrouve sa couleur, la marque quasi noire prend celle du texte. */
|
|
19
|
+
[data-sia-color-mode="dark"] .sia-brand-icon--colored.sia-brand-icon--light {
|
|
20
|
+
fill: var(--sia-brand-icon-color, currentColor);
|
|
21
|
+
}
|
|
22
|
+
[data-sia-color-mode="dark"] .sia-brand-icon--colored.sia-brand-icon--dark { fill: currentColor; }
|
|
23
|
+
|
|
24
|
+
@media (prefers-color-scheme: dark) {
|
|
25
|
+
:root:not([data-sia-color-mode="light"]) .sia-brand-icon--colored.sia-brand-icon--light {
|
|
26
|
+
fill: var(--sia-brand-icon-color, currentColor);
|
|
27
|
+
}
|
|
28
|
+
:root:not([data-sia-color-mode="light"]) .sia-brand-icon--colored.sia-brand-icon--dark {
|
|
29
|
+
fill: currentColor;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -2,7 +2,11 @@ import { Fragment, type ReactNode } from "react";
|
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
import { useSiaLocale } from "@sia-ui/headless";
|
|
4
4
|
import { ChevronRightIcon, MoreHorizontalIcon } from "../Icons";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
type DropdownMenuProps,
|
|
8
|
+
type MenuEntry,
|
|
9
|
+
} from "../DropdownMenu";
|
|
6
10
|
import "./styles.css";
|
|
7
11
|
|
|
8
12
|
export interface BreadcrumbItem {
|
|
@@ -24,6 +28,13 @@ export interface BreadcrumbProps {
|
|
|
24
28
|
separator?: ReactNode;
|
|
25
29
|
ariaLabel?: string;
|
|
26
30
|
className?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Le menu des niveaux repliés : placement, décalage, classe. Ses entrées
|
|
33
|
+
* et son déclencheur viennent de `items`.
|
|
34
|
+
*/
|
|
35
|
+
dropdownMenuProps?: Partial<
|
|
36
|
+
Omit<DropdownMenuProps, "items" | "children" | "onSelect">
|
|
37
|
+
>;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
/**
|
|
@@ -38,6 +49,7 @@ export function Breadcrumb({
|
|
|
38
49
|
separator,
|
|
39
50
|
ariaLabel,
|
|
40
51
|
className,
|
|
52
|
+
dropdownMenuProps,
|
|
41
53
|
}: BreadcrumbProps) {
|
|
42
54
|
const locale = useSiaLocale();
|
|
43
55
|
const mark = separator ?? <ChevronRightIcon />;
|
|
@@ -71,7 +83,7 @@ export function Breadcrumb({
|
|
|
71
83
|
<Fragment key={item ? (item.key ?? index) : "overflow"}>
|
|
72
84
|
<li className="sia-breadcrumb__item">
|
|
73
85
|
{item === null ? (
|
|
74
|
-
<DropdownMenu items={hidden}>
|
|
86
|
+
<DropdownMenu {...dropdownMenuProps} items={hidden}>
|
|
75
87
|
<button
|
|
76
88
|
type="button"
|
|
77
89
|
className="sia-breadcrumb__more"
|
|
@@ -2,7 +2,7 @@ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
import { Spinner, type SpinnerProps } from "../Spinner";
|
|
4
4
|
import { Tooltip, type TooltipProps } from "../Tooltip";
|
|
5
|
-
import { useComponentDefaults } from "@sia-ui/headless";
|
|
5
|
+
import { useComponentDefaults, useSiaLocale } from "@sia-ui/headless";
|
|
6
6
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
7
7
|
import "./styles.css";
|
|
8
8
|
|
|
@@ -58,8 +58,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
58
58
|
const tone = toneProp ?? defaults.tone ?? "primary";
|
|
59
59
|
const size = sizeProp ?? defaults.size ?? "md";
|
|
60
60
|
const radius = radiusProp ?? defaults.radius ?? "default";
|
|
61
|
+
const locale = useSiaLocale();
|
|
61
62
|
const loadingLabel =
|
|
62
|
-
loadingLabelProp ?? defaults.loadingLabel ??
|
|
63
|
+
loadingLabelProp ?? defaults.loadingLabel ?? locale.loading;
|
|
63
64
|
const button = (
|
|
64
65
|
<button
|
|
65
66
|
ref={ref}
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
|
|
36
36
|
.sia-button--primary { --sia-button-accent: var(--sia-primary); }
|
|
37
37
|
.sia-button--neutral { --sia-button-accent: var(--sia-foreground); --sia-button-on-accent: var(--sia-background); }
|
|
38
|
-
.sia-button--info { --sia-button-accent: var(--sia-info); }
|
|
39
|
-
.sia-button--success { --sia-button-accent: var(--sia-success); }
|
|
40
|
-
.sia-button--warning { --sia-button-accent: var(--sia-warning); }
|
|
41
|
-
.sia-button--danger { --sia-button-accent: var(--sia-danger); }
|
|
38
|
+
.sia-button--info { --sia-button-accent: var(--sia-info); --sia-button-on-accent: var(--sia-info-foreground); }
|
|
39
|
+
.sia-button--success { --sia-button-accent: var(--sia-success); --sia-button-on-accent: var(--sia-success-foreground); }
|
|
40
|
+
.sia-button--warning { --sia-button-accent: var(--sia-warning); --sia-button-on-accent: var(--sia-warning-foreground); }
|
|
41
|
+
.sia-button--danger { --sia-button-accent: var(--sia-danger); --sia-button-on-accent: var(--sia-danger-foreground); }
|
|
42
42
|
|
|
43
43
|
/* Variantes ----------------------------------------------------------- */
|
|
44
44
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
2
2
|
import { cn, toneClass } from "@sia-ui/utils";
|
|
3
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
4
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
4
5
|
import { ChevronLeftIcon, ChevronRightIcon } from "../Icons";
|
|
5
6
|
|
|
@@ -36,6 +37,7 @@ export interface CalendarProps {
|
|
|
36
37
|
cellRender?: (day: CalendarDay, originNode: ReactNode) => ReactNode;
|
|
37
38
|
headerRender?: (props: { month: string; mode: CalendarMode; onMonthChange: (month: string) => void; onModeChange: (mode: CalendarMode) => void }) => ReactNode;
|
|
38
39
|
renderExtraFooter?: () => ReactNode;
|
|
40
|
+
/** La langue des jours et des mois. Par défaut, celle de la locale SIA. */
|
|
39
41
|
locale?: string;
|
|
40
42
|
firstDayOfWeek?: 0 | 1;
|
|
41
43
|
showWeek?: boolean;
|
|
@@ -91,13 +93,15 @@ export function Calendar({
|
|
|
91
93
|
cellRender,
|
|
92
94
|
headerRender,
|
|
93
95
|
renderExtraFooter,
|
|
94
|
-
locale
|
|
96
|
+
locale: localeProp,
|
|
95
97
|
firstDayOfWeek = 1,
|
|
96
98
|
showWeek = false,
|
|
97
99
|
fullscreen = false,
|
|
98
100
|
tone = "primary",
|
|
99
101
|
className,
|
|
100
102
|
}: CalendarProps) {
|
|
103
|
+
const messages = useSiaLocale();
|
|
104
|
+
const locale = localeProp ?? messages.language;
|
|
101
105
|
const [selected, setSelected] = useState(defaultValue ?? "");
|
|
102
106
|
const [selectedRange, setSelectedRange] = useState(defaultRange);
|
|
103
107
|
const [view, setView] = useState(() => parseMonth(month ?? defaultMonth ?? value ?? defaultValue));
|
|
@@ -169,12 +173,12 @@ export function Calendar({
|
|
|
169
173
|
<section className={cn("sia-calendar", `sia-calendar--${currentMode}`, fullscreen && "sia-calendar--fullscreen", toneClass("sia-tone", tone), className)}>
|
|
170
174
|
{headerRender?.({ month: monthIso(controlledMonth), mode: currentMode, onMonthChange: updateFromString, onModeChange: changeMode }) ?? (
|
|
171
175
|
<header className="sia-calendar__header">
|
|
172
|
-
<button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ?
|
|
176
|
+
<button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ? messages.calendar.previousMonth : messages.calendar.previousYear} onClick={() => currentMode === "month" ? changeMonth(-1) : setMonth(new Date(year - 1, monthIndex, 1, 12))}><ChevronLeftIcon /></button>
|
|
173
177
|
<div className="sia-calendar__heading">
|
|
174
178
|
<button type="button" onClick={() => changeMode(currentMode === "month" ? "year" : "month")}>{currentMode === "month" ? monthName : year}</button>
|
|
175
179
|
{currentMode === "month" && <button type="button" onClick={() => changeMode("year")}>{year}</button>}
|
|
176
180
|
</div>
|
|
177
|
-
<button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ?
|
|
181
|
+
<button type="button" className="sia-calendar__nav" aria-label={currentMode === "month" ? messages.calendar.nextMonth : messages.calendar.nextYear} onClick={() => currentMode === "month" ? changeMonth(1) : setMonth(new Date(year + 1, monthIndex, 1, 12))}><ChevronRightIcon /></button>
|
|
178
182
|
</header>
|
|
179
183
|
)}
|
|
180
184
|
{currentMode === "year" ? (
|
|
@@ -2,6 +2,16 @@ import { type HTMLAttributes } from "react";
|
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
Les quatre parties ne sont que des `div` : ces alias ne changent rien au
|
|
7
|
+
typage, ils donnent un nom aux props qu'un composant composé laisse passer
|
|
8
|
+
(`cardBodyProps?: Partial<CardBodyProps>`).
|
|
9
|
+
*/
|
|
10
|
+
export type CardProps = HTMLAttributes<HTMLDivElement>;
|
|
11
|
+
export type CardHeaderProps = HTMLAttributes<HTMLDivElement>;
|
|
12
|
+
export type CardBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
13
|
+
export type CardFooterProps = HTMLAttributes<HTMLDivElement>;
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* Une surface qui regroupe ce qui va ensemble.
|
|
7
17
|
*
|
|
@@ -10,7 +20,7 @@ import "./styles.css";
|
|
|
10
20
|
* deux boutons et une pastille, une prop ne suffit plus, et l'on se retrouve
|
|
11
21
|
* à passer du JSX à travers une chaîne.
|
|
12
22
|
*/
|
|
13
|
-
export function Card({ className, ...props }:
|
|
14
|
-
export function CardHeader({ className, ...props }:
|
|
15
|
-
export function CardBody({ className, ...props }:
|
|
16
|
-
export function CardFooter({ className, ...props }:
|
|
23
|
+
export function Card({ className, ...props }: CardProps) { return <div className={cn("sia-card", className)} {...props} />; }
|
|
24
|
+
export function CardHeader({ className, ...props }: CardHeaderProps) { return <div className={cn("sia-card__header", className)} {...props} />; }
|
|
25
|
+
export function CardBody({ className, ...props }: CardBodyProps) { return <div className={cn("sia-card__body", className)} {...props} />; }
|
|
26
|
+
export function CardFooter({ className, ...props }: CardFooterProps) { return <div className={cn("sia-card__footer", className)} {...props} />; }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
2
|
+
import { useColorMode } from "@sia-ui/react";
|
|
3
|
+
import { IconButton, type IconButtonProps } from "../IconButton";
|
|
4
|
+
import { MoonIcon, SunIcon } from "../Icons";
|
|
5
|
+
|
|
6
|
+
export interface ColorModeToggleProps
|
|
7
|
+
extends Partial<Omit<IconButtonProps, "icon" | "onClick" | "aria-pressed">> {
|
|
8
|
+
/** Le nom du bouton en mode clair. Par défaut, celui de la locale. */
|
|
9
|
+
darkLabel?: string;
|
|
10
|
+
/** Le nom du bouton en mode sombre. Par défaut, celui de la locale. */
|
|
11
|
+
lightLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Le bouton de bascule clair / sombre.
|
|
16
|
+
*
|
|
17
|
+
* Il lit et écrit le mode de `SiaProvider` — persistance et script
|
|
18
|
+
* anti-clignotement compris — plutôt que de tenir le sien. L'icône montre le
|
|
19
|
+
* mode vers lequel on part, comme le libellé : le soleil en mode sombre.
|
|
20
|
+
* Toutes les props d'`IconButton` passent, sauf ce qu'il pilote.
|
|
21
|
+
*/
|
|
22
|
+
export function ColorModeToggle({
|
|
23
|
+
darkLabel: darkLabelProp,
|
|
24
|
+
lightLabel: lightLabelProp,
|
|
25
|
+
variant = "ghost",
|
|
26
|
+
...props
|
|
27
|
+
}: ColorModeToggleProps) {
|
|
28
|
+
const m = useSiaLocale().colorModeToggle;
|
|
29
|
+
const darkLabel = darkLabelProp ?? m.toDark;
|
|
30
|
+
const lightLabel = lightLabelProp ?? m.toLight;
|
|
31
|
+
const { resolvedColorMode, toggleColorMode } = useColorMode();
|
|
32
|
+
const sombre = resolvedColorMode === "dark";
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<IconButton
|
|
36
|
+
variant={variant}
|
|
37
|
+
{...props}
|
|
38
|
+
label={props.label ?? (sombre ? lightLabel : darkLabel)}
|
|
39
|
+
icon={sombre ? <SunIcon /> : <MoonIcon />}
|
|
40
|
+
aria-pressed={sombre}
|
|
41
|
+
onClick={toggleColorMode}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
4
|
import "./styles.css";
|
|
4
5
|
|
|
5
6
|
export interface CommandItem { id: string; label: string; description?: string; keywords?: string[]; icon?: ReactNode; disabled?: boolean; onSelect: () => void; }
|
|
6
|
-
export interface CommandPaletteProps { open: boolean; onOpenChange: (open: boolean) => void; items: CommandItem[]; placeholder?: string; emptyLabel?: string; className?: string; }
|
|
7
|
+
export interface CommandPaletteProps { open: boolean; onOpenChange: (open: boolean) => void; items: CommandItem[]; /** Par défaut, celui de la locale. */ placeholder?: string; /** Par défaut, celui de la locale. */ emptyLabel?: string; className?: string; }
|
|
7
8
|
/**
|
|
8
9
|
* Tout ce que l'application sait faire, au clavier.
|
|
9
10
|
*
|
|
@@ -11,4 +12,4 @@ export interface CommandPaletteProps { open: boolean; onOpenChange: (open: boole
|
|
|
11
12
|
* « facture » doit trouver « Nouvelle vente » sans avoir appris comment
|
|
12
13
|
* l'action s'appelle ici.
|
|
13
14
|
*/
|
|
14
|
-
export function CommandPalette({ open, onOpenChange, items, placeholder
|
|
15
|
+
export function CommandPalette({ open, onOpenChange, items, placeholder: placeholderProp, emptyLabel: emptyLabelProp, className }: CommandPaletteProps) { const m = useSiaLocale().commandPalette; const placeholder = placeholderProp ?? m.placeholder; const emptyLabel = emptyLabelProp ?? m.empty; const [query, setQuery] = useState(""); const [active, setActive] = useState(0); const inputRef = useRef<HTMLInputElement>(null); const filtered = useMemo(() => { const needle = query.trim().toLowerCase(); return items.filter((item) => !needle || [item.label, item.description, ...(item.keywords ?? [])].filter(Boolean).join(" ").toLowerCase().includes(needle)); }, [items, query]); useEffect(() => { if (!open) return; setQuery(""); setActive(0); requestAnimationFrame(() => inputRef.current?.focus()); }, [open]); if (!open) return null; const select = (item: CommandItem | undefined) => { if (!item || item.disabled) return; item.onSelect(); onOpenChange(false); }; return <div className="sia-command-palette__backdrop" onMouseDown={(event) => event.target === event.currentTarget && onOpenChange(false)}><section className={cn("sia-command-palette", className)} role="dialog" aria-modal="true" aria-label={m.ariaLabel}><input ref={inputRef} value={query} placeholder={placeholder} aria-label={placeholder} onChange={(event) => { setQuery(event.target.value); setActive(0); }} onKeyDown={(event) => { if (event.key === "Escape") onOpenChange(false); if (event.key === "ArrowDown") { event.preventDefault(); setActive((value) => Math.min(value + 1, filtered.length - 1)); } if (event.key === "ArrowUp") { event.preventDefault(); setActive((value) => Math.max(value - 1, 0)); } if (event.key === "Enter") select(filtered[active]); }} /><div role="listbox">{filtered.length === 0 ? <p>{emptyLabel}</p> : filtered.map((item, index) => <button type="button" role="option" aria-selected={index === active} key={item.id} disabled={item.disabled} onMouseEnter={() => setActive(index)} onClick={() => select(item)}>{item.icon}<span><strong>{item.label}</strong>{item.description && <small>{item.description}</small>}</span></button>)}</div></section></div>; }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { Button, type ButtonProps } from "../Button";
|
|
3
4
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
4
|
-
import { Modal } from "../Modal";
|
|
5
|
+
import { Modal, type ModalProps } from "../Modal";
|
|
5
6
|
import { useSiaLocale } from "@sia-ui/headless";
|
|
6
7
|
|
|
7
8
|
export interface ConfirmDialogProps {
|
|
@@ -14,6 +15,24 @@ export interface ConfirmDialogProps {
|
|
|
14
15
|
tone?: Extract<ComponentTone, "primary" | "danger">;
|
|
15
16
|
loading?: boolean;
|
|
16
17
|
onConfirm: () => void | Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Les props de la `Modal`. Sans `open`, `onOpenChange`, `children` ni
|
|
20
|
+
* `footer`, que la boîte de confirmation tient elle-même. Pendant
|
|
21
|
+
* `loading`, un clic sur le voile ne ferme jamais, quel que soit
|
|
22
|
+
* `closeOnBackdrop` : ce serait abandonner un appel encore en vol.
|
|
23
|
+
*/
|
|
24
|
+
modalProps?: Partial<Omit<ModalProps, "open" | "onOpenChange" | "children" | "footer">>;
|
|
25
|
+
/**
|
|
26
|
+
* Les props du bouton de confirmation. Sans `onClick` ni `loading`,
|
|
27
|
+
* branchés sur `onConfirm` et `loading` ; le libellé passe par
|
|
28
|
+
* `confirmLabel`.
|
|
29
|
+
*/
|
|
30
|
+
confirmButtonProps?: Partial<Omit<ButtonProps, "onClick" | "loading" | "children">>;
|
|
31
|
+
/**
|
|
32
|
+
* Les props du bouton d'annulation. Sans `onClick`, qui referme la boîte ;
|
|
33
|
+
* `disabled` s'ajoute à `loading` au lieu de le remplacer.
|
|
34
|
+
*/
|
|
35
|
+
cancelButtonProps?: Partial<Omit<ButtonProps, "onClick" | "children">>;
|
|
17
36
|
}
|
|
18
37
|
|
|
19
38
|
/**
|
|
@@ -23,9 +42,10 @@ export interface ConfirmDialogProps {
|
|
|
23
42
|
* n'a pas abouti. Sans cela, la boîte se referme sur un appel encore en
|
|
24
43
|
* vol, et l'on ignore si la suppression a eu lieu.
|
|
25
44
|
*/
|
|
26
|
-
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel: confirmLabelProp, cancelLabel: cancelLabelProp, tone = "primary", loading = false, onConfirm }: ConfirmDialogProps) {
|
|
45
|
+
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel: confirmLabelProp, cancelLabel: cancelLabelProp, tone = "primary", loading = false, onConfirm, modalProps, confirmButtonProps, cancelButtonProps }: ConfirmDialogProps) {
|
|
27
46
|
const locale = useSiaLocale();
|
|
28
47
|
const cancelLabel = cancelLabelProp ?? locale.cancel;
|
|
29
48
|
const confirmLabel = confirmLabelProp ?? locale.confirm;
|
|
30
|
-
|
|
49
|
+
const confirmClassName = cn(tone === "danger" && "sia-button--danger", confirmButtonProps?.className);
|
|
50
|
+
return <Modal title={title} description={description} {...modalProps} open={open} onOpenChange={onOpenChange} closeOnBackdrop={!loading && (modalProps?.closeOnBackdrop ?? true)} footer={<><Button variant="ghost" {...cancelButtonProps} disabled={loading || Boolean(cancelButtonProps?.disabled)} onClick={() => onOpenChange(false)}>{cancelLabel}</Button><Button {...confirmButtonProps} {...(confirmClassName ? { className: confirmClassName } : {})} loading={loading} onClick={() => void onConfirm()}>{confirmLabel}</Button></>}>{description ? null : <p>{locale.confirmDialog.defaultDescription}</p>}</Modal>;
|
|
31
51
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { type Key, type ReactNode } from "react";
|
|
2
2
|
import {
|
|
3
3
|
hasFormErrors,
|
|
4
|
+
useSiaLocale,
|
|
4
5
|
type FormSubmitResult,
|
|
5
6
|
type FormValidator,
|
|
6
7
|
} from "@sia-ui/headless";
|
|
7
|
-
import {
|
|
8
|
+
import { cn } from "@sia-ui/utils";
|
|
9
|
+
import { Modal, type ModalProps } from "../Modal";
|
|
8
10
|
import {
|
|
9
11
|
Form,
|
|
10
12
|
formFieldNames,
|
|
11
13
|
type FormEntry,
|
|
14
|
+
type FormProps,
|
|
12
15
|
type FormShape,
|
|
13
16
|
} from "../Form";
|
|
14
|
-
import { Descriptions } from "../Descriptions";
|
|
17
|
+
import { Descriptions, type DescriptionsProps } from "../Descriptions";
|
|
15
18
|
import type { DataTableColumn } from "../DataTable/types";
|
|
16
19
|
import { renduCellule } from "../DataTable/values";
|
|
17
20
|
|
|
@@ -70,6 +73,33 @@ export function valeursDeLigne<T>(row: T, fields: FormEntry[]): FormShape {
|
|
|
70
73
|
return valeurs;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Les props des deux `Modal` — formulaire et détail — qu'on peut régler.
|
|
78
|
+
*
|
|
79
|
+
* Sans `open` ni `onOpenChange`, qui suivent l'état des boîtes, ni
|
|
80
|
+
* `children`. Sans `title` ni `description` non plus : ils diffèrent d'une
|
|
81
|
+
* boîte à l'autre et se règlent par `dialog`. `className` s'ajoute à celui
|
|
82
|
+
* des boîtes.
|
|
83
|
+
*/
|
|
84
|
+
export type CrudModalProps = Partial<
|
|
85
|
+
Omit<ModalProps, "open" | "onOpenChange" | "children" | "title" | "description">
|
|
86
|
+
>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Les props du `Form` de création et de modification qu'on peut régler —
|
|
90
|
+
* `showClear`, `submitProps`, `fieldProps`.
|
|
91
|
+
*
|
|
92
|
+
* Sans ce que la boîte branche elle-même : les champs, les valeurs de
|
|
93
|
+
* départ, la validation et l'envoi, qui ont leurs props sur la page ; ni
|
|
94
|
+
* l'adaptateur `form`, qui couperait la boîte de ses propres valeurs.
|
|
95
|
+
*/
|
|
96
|
+
export type CrudFormProps = Partial<
|
|
97
|
+
Omit<FormProps, "fields" | "defaultValues" | "validate" | "onSubmit" | "form">
|
|
98
|
+
>;
|
|
99
|
+
|
|
100
|
+
/** Les props de la vue de détail par défaut. Sans `items`, tirés des colonnes. */
|
|
101
|
+
export type CrudDescriptionsProps = Partial<Omit<DescriptionsProps, "items">>;
|
|
102
|
+
|
|
73
103
|
export interface CrudDialogsProps<T> {
|
|
74
104
|
state: CrudDialogState<T> | null;
|
|
75
105
|
onClose: () => void;
|
|
@@ -97,6 +127,13 @@ export interface CrudDialogsProps<T> {
|
|
|
97
127
|
|
|
98
128
|
getRowKey?: ((row: T, index: number) => Key) | undefined;
|
|
99
129
|
dialog?: CrudDialogOptions | undefined;
|
|
130
|
+
|
|
131
|
+
/** Voir `CrudModalProps`. */
|
|
132
|
+
modalProps?: CrudModalProps | undefined;
|
|
133
|
+
/** Voir `CrudFormProps`. */
|
|
134
|
+
formProps?: CrudFormProps | undefined;
|
|
135
|
+
/** Voir `CrudDescriptionsProps`. */
|
|
136
|
+
descriptionsProps?: CrudDescriptionsProps | undefined;
|
|
100
137
|
}
|
|
101
138
|
|
|
102
139
|
/**
|
|
@@ -121,7 +158,11 @@ export function CrudDialogs<T>({
|
|
|
121
158
|
renderDetail,
|
|
122
159
|
getRowKey,
|
|
123
160
|
dialog,
|
|
161
|
+
modalProps,
|
|
162
|
+
formProps,
|
|
163
|
+
descriptionsProps,
|
|
124
164
|
}: CrudDialogsProps<T>) {
|
|
165
|
+
const locale = useSiaLocale();
|
|
125
166
|
const creation = state?.mode === "create";
|
|
126
167
|
const modification = state?.mode === "edit";
|
|
127
168
|
const consultation = state?.mode === "view";
|
|
@@ -155,14 +196,15 @@ export function CrudDialogs<T>({
|
|
|
155
196
|
<>
|
|
156
197
|
{fields && (creation || modification) && (
|
|
157
198
|
<Modal
|
|
199
|
+
{...modalProps}
|
|
158
200
|
open
|
|
159
201
|
onOpenChange={(ouvert) => {
|
|
160
202
|
if (!ouvert) onClose();
|
|
161
203
|
}}
|
|
162
204
|
title={
|
|
163
205
|
creation
|
|
164
|
-
? (dialog?.createTitle ??
|
|
165
|
-
: (dialog?.editTitle ??
|
|
206
|
+
? (dialog?.createTitle ?? locale.create)
|
|
207
|
+
: (dialog?.editTitle ?? locale.edit)
|
|
166
208
|
}
|
|
167
209
|
{...(creation
|
|
168
210
|
? dialog?.createDescription
|
|
@@ -171,7 +213,7 @@ export function CrudDialogs<T>({
|
|
|
171
213
|
: dialog?.editDescription
|
|
172
214
|
? { description: dialog.editDescription }
|
|
173
215
|
: {})}
|
|
174
|
-
className="sia-crud-dialog"
|
|
216
|
+
className={cn("sia-crud-dialog", modalProps?.className)}
|
|
175
217
|
>
|
|
176
218
|
<Form
|
|
177
219
|
key={cle}
|
|
@@ -180,12 +222,13 @@ export function CrudDialogs<T>({
|
|
|
180
222
|
{...(validate ? { validate } : {})}
|
|
181
223
|
columns={dialog?.columns ?? 1}
|
|
182
224
|
submitText={
|
|
183
|
-
dialog?.submitText ?? (creation ?
|
|
225
|
+
dialog?.submitText ?? (creation ? locale.create : locale.save)
|
|
184
226
|
}
|
|
185
227
|
// Une création part de champs vides : exiger une modification
|
|
186
228
|
// bloquerait l'envoi d'un formulaire dont tous les défauts
|
|
187
229
|
// conviennent.
|
|
188
230
|
requireDirty={modification}
|
|
231
|
+
{...formProps}
|
|
189
232
|
onSubmit={async (values) => {
|
|
190
233
|
const resultat = await onSubmit?.(values, {
|
|
191
234
|
mode: creation ? "create" : "edit",
|
|
@@ -203,15 +246,16 @@ export function CrudDialogs<T>({
|
|
|
203
246
|
|
|
204
247
|
{consultation && state?.row !== undefined && (
|
|
205
248
|
<Modal
|
|
249
|
+
{...modalProps}
|
|
206
250
|
open
|
|
207
251
|
onOpenChange={(ouvert) => {
|
|
208
252
|
if (!ouvert) onClose();
|
|
209
253
|
}}
|
|
210
|
-
title={dialog?.viewTitle ??
|
|
254
|
+
title={dialog?.viewTitle ?? locale.crudPage.detailTitle}
|
|
211
255
|
{...(dialog?.viewDescription
|
|
212
256
|
? { description: dialog.viewDescription }
|
|
213
257
|
: {})}
|
|
214
|
-
className="sia-crud-dialog"
|
|
258
|
+
className={cn("sia-crud-dialog", modalProps?.className)}
|
|
215
259
|
>
|
|
216
260
|
{renderDetail?.(state.row, state.index ?? 0) ?? (
|
|
217
261
|
/*
|
|
@@ -223,6 +267,7 @@ export function CrudDialogs<T>({
|
|
|
223
267
|
*/
|
|
224
268
|
<Descriptions
|
|
225
269
|
columns={dialog?.detailColumns ?? 2}
|
|
270
|
+
{...descriptionsProps}
|
|
226
271
|
items={columns
|
|
227
272
|
.filter((colonne) => colonne.card !== "hidden")
|
|
228
273
|
.map((colonne) => ({
|