@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
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -68,6 +68,12 @@ Le contenu ci-dessous reprend intégralement `CHANGELOG.md` pour rester visible
|
|
|
68
68
|
<!-- sia:changelog:start -->
|
|
69
69
|
# @sia-ui/cli
|
|
70
70
|
|
|
71
|
+
## 0.8.0
|
|
72
|
+
|
|
73
|
+
### Minor Changes
|
|
74
|
+
|
|
75
|
+
- Version alignée sur les autres paquets SIA UI : tous sortent désormais ensemble, au même numéro.
|
|
76
|
+
|
|
71
77
|
## 0.7.1
|
|
72
78
|
|
|
73
79
|
### Patch Changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@sia-ui/utils";
|
|
3
|
+
import { formatMessage, useSiaLocale } from "@sia-ui/headless";
|
|
4
|
+
import { Avatar, type AvatarProps } from "../Avatar";
|
|
5
|
+
import { Badge, type BadgeProps } from "../Badge";
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
type DropdownMenuProps,
|
|
9
|
+
type MenuEntry,
|
|
10
|
+
type MenuItem,
|
|
11
|
+
} from "../DropdownMenu";
|
|
12
|
+
import "./styles.css";
|
|
13
|
+
|
|
14
|
+
export interface AccountMenuProps {
|
|
15
|
+
/** Le nom de la personne connectée : avatar, en-tête et libellé du bouton. */
|
|
16
|
+
name: string;
|
|
17
|
+
email?: ReactNode;
|
|
18
|
+
/** Le rôle, en pastille sous le nom : « Administrateur ». */
|
|
19
|
+
role?: ReactNode;
|
|
20
|
+
/** La photo. À défaut, les initiales de `name`. */
|
|
21
|
+
avatarSrc?: string;
|
|
22
|
+
/** Les entrées sous l'en-tête : profil, préférences, déconnexion. */
|
|
23
|
+
items: MenuEntry[];
|
|
24
|
+
onSelect?: (item: MenuItem) => void;
|
|
25
|
+
/** Le nom accessible du déclencheur. Par défaut, celui de la locale, `accountMenu.trigger` (« Compte de {name} »). */
|
|
26
|
+
triggerLabel?: string;
|
|
27
|
+
/** L'avatar du déclencheur — taille, forme, variante. */
|
|
28
|
+
avatarProps?: Partial<Omit<AvatarProps, "name">>;
|
|
29
|
+
/** L'avatar de l'en-tête du menu. */
|
|
30
|
+
headerAvatarProps?: Partial<Omit<AvatarProps, "name">>;
|
|
31
|
+
/** La pastille du rôle — ton, variante. */
|
|
32
|
+
roleBadgeProps?: Partial<Omit<BadgeProps, "children">>;
|
|
33
|
+
/** Le menu lui-même, hors entrées et déclencheur qu'il reçoit d'ici. */
|
|
34
|
+
dropdownMenuProps?: Partial<Omit<DropdownMenuProps, "items" | "children" | "onSelect">>;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Le menu du compte connecté : un avatar qui ouvre un menu à en-tête de profil.
|
|
40
|
+
*
|
|
41
|
+
* Chaque application le recomposait — avatar dans un bouton qui lui
|
|
42
|
+
* ajoutait un cadre, en-tête forcé dans un titre de section en capitales.
|
|
43
|
+
* Ici, le déclencheur est l'avatar seul, rond et sans fond, et l'en-tête une
|
|
44
|
+
* entrée `header` à contenu libre du `DropdownMenu`.
|
|
45
|
+
*/
|
|
46
|
+
export function AccountMenu({
|
|
47
|
+
name,
|
|
48
|
+
email,
|
|
49
|
+
role,
|
|
50
|
+
avatarSrc,
|
|
51
|
+
items,
|
|
52
|
+
onSelect,
|
|
53
|
+
triggerLabel,
|
|
54
|
+
avatarProps,
|
|
55
|
+
headerAvatarProps,
|
|
56
|
+
roleBadgeProps,
|
|
57
|
+
dropdownMenuProps,
|
|
58
|
+
className,
|
|
59
|
+
}: AccountMenuProps) {
|
|
60
|
+
const locale = useSiaLocale();
|
|
61
|
+
const entete: MenuEntry = {
|
|
62
|
+
key: "__sia-account-header",
|
|
63
|
+
type: "header",
|
|
64
|
+
content: (
|
|
65
|
+
<div className="sia-account__who">
|
|
66
|
+
<Avatar
|
|
67
|
+
name={name}
|
|
68
|
+
size="md"
|
|
69
|
+
{...(avatarSrc ? { src: avatarSrc } : {})}
|
|
70
|
+
{...headerAvatarProps}
|
|
71
|
+
/>
|
|
72
|
+
<div className="sia-account__identity">
|
|
73
|
+
<strong className="sia-account__name">{name}</strong>
|
|
74
|
+
{email && <span className="sia-account__email">{email}</span>}
|
|
75
|
+
{role && (
|
|
76
|
+
<Badge tone="primary" {...roleBadgeProps} className={cn("sia-account__role", roleBadgeProps?.className)}>
|
|
77
|
+
{role}
|
|
78
|
+
</Badge>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<DropdownMenu
|
|
87
|
+
placement="bottom-end"
|
|
88
|
+
{...dropdownMenuProps}
|
|
89
|
+
items={[entete, ...items]}
|
|
90
|
+
{...(onSelect ? { onSelect } : {})}
|
|
91
|
+
className={cn("sia-account__menu", dropdownMenuProps?.className)}
|
|
92
|
+
>
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
className={cn("sia-account__trigger", className)}
|
|
96
|
+
aria-label={triggerLabel ?? formatMessage(locale.accountMenu.trigger, { name })}
|
|
97
|
+
>
|
|
98
|
+
<Avatar
|
|
99
|
+
name={name}
|
|
100
|
+
size="sm"
|
|
101
|
+
{...(avatarSrc ? { src: avatarSrc } : {})}
|
|
102
|
+
{...avatarProps}
|
|
103
|
+
/>
|
|
104
|
+
</button>
|
|
105
|
+
</DropdownMenu>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Le déclencheur est l'avatar, rien d'autre : pas de cadre ni de fond de
|
|
2
|
+
bouton autour d'un cercle. L'anneau de focus suit sa forme. */
|
|
3
|
+
.sia-account__trigger {
|
|
4
|
+
display: inline-grid;
|
|
5
|
+
place-items: center;
|
|
6
|
+
padding: 0;
|
|
7
|
+
border: 0;
|
|
8
|
+
border-radius: 9999px;
|
|
9
|
+
background: transparent;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
transition: box-shadow var(--sia-duration-fast) var(--sia-easing-standard);
|
|
12
|
+
}
|
|
13
|
+
.sia-account__trigger:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--sia-foreground) 8%, transparent); }
|
|
14
|
+
.sia-account__trigger:focus-visible { outline: 3px solid color-mix(in srgb, var(--sia-primary) 35%, transparent); outline-offset: 2px; }
|
|
15
|
+
.sia-account__trigger[aria-expanded="true"] { box-shadow: 0 0 0 3px color-mix(in srgb, var(--sia-primary) 25%, transparent); }
|
|
16
|
+
|
|
17
|
+
.sia-account__menu { min-width: 15rem; }
|
|
18
|
+
|
|
19
|
+
.sia-account__who { display: flex; align-items: center; gap: .75rem; min-width: 0; }
|
|
20
|
+
.sia-account__identity { display: grid; justify-items: start; gap: .15rem; min-width: 0; }
|
|
21
|
+
.sia-account__name { font-weight: 600; line-height: 1.3; }
|
|
22
|
+
.sia-account__email {
|
|
23
|
+
max-width: 14rem;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
color: var(--sia-muted);
|
|
26
|
+
font-size: .8rem;
|
|
27
|
+
text-overflow: ellipsis;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
}
|
|
30
|
+
.sia-account__role { margin-top: .2rem; }
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { useMemo, type Key, type ReactNode } from "react";
|
|
2
2
|
import { cn, formatDateTime } from "@sia-ui/utils";
|
|
3
|
-
import type
|
|
3
|
+
import { FRENCH, useSiaLocale, type ActivityLogMessages, type TableQuery } from "@sia-ui/headless";
|
|
4
4
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
5
5
|
import { DataTable } from "../DataTable";
|
|
6
6
|
import type { DataTableColumn, DataTableProps } from "../DataTable/types";
|
|
7
|
-
import { FiltersBar } from "../FiltersBar";
|
|
8
|
-
import { PageHeader } from "../PageHeader";
|
|
9
|
-
import { Pagination } from "../Pagination";
|
|
10
|
-
import { SearchInput } from "../SearchInput";
|
|
11
|
-
import { Select, type SelectOption } from "../Select";
|
|
12
|
-
import { StatusBadge } from "../Badge";
|
|
7
|
+
import { FiltersBar, type FiltersBarProps } from "../FiltersBar";
|
|
8
|
+
import { PageHeader, type PageHeaderProps } from "../PageHeader";
|
|
9
|
+
import { Pagination, type PaginationProps } from "../Pagination";
|
|
10
|
+
import { SearchInput, type SearchInputProps } from "../SearchInput";
|
|
11
|
+
import { Select, type SelectOption, type SelectProps } from "../Select";
|
|
12
|
+
import { StatusBadge, type StatusBadgeProps } from "../Badge";
|
|
13
13
|
import "./styles.css";
|
|
14
14
|
|
|
15
15
|
/** Une entrée de journal, dans sa forme la plus courante. */
|
|
@@ -71,6 +71,51 @@ export interface ActivityLogProps<T = ActivityEntry> {
|
|
|
71
71
|
onRetry?: () => void;
|
|
72
72
|
empty?: DataTableProps<T>["empty"];
|
|
73
73
|
className?: string;
|
|
74
|
+
|
|
75
|
+
/** Les props du `PageHeader`, rendu quand `title` est fourni. */
|
|
76
|
+
pageHeaderProps?: Partial<PageHeaderProps>;
|
|
77
|
+
/**
|
|
78
|
+
* Les props de la `FiltersBar` — des actions, un autre libellé de
|
|
79
|
+
* réinitialisation. Sans `search`, `filters`, `activeCount` ni `onReset`,
|
|
80
|
+
* que le journal compose à partir de la requête.
|
|
81
|
+
*/
|
|
82
|
+
filtersBarProps?: Partial<
|
|
83
|
+
Omit<FiltersBarProps, "search" | "filters" | "activeCount" | "onReset">
|
|
84
|
+
>;
|
|
85
|
+
/**
|
|
86
|
+
* Les props du champ de recherche. Sans `value`, `defaultValue`,
|
|
87
|
+
* `onValueChange` ni `onSearch` : la recherche en vigueur vit dans la
|
|
88
|
+
* requête.
|
|
89
|
+
*/
|
|
90
|
+
searchInputProps?: Partial<
|
|
91
|
+
Omit<SearchInputProps, "value" | "defaultValue" | "onValueChange" | "onSearch">
|
|
92
|
+
>;
|
|
93
|
+
/**
|
|
94
|
+
* Les props communes aux `Select` des filtres. Sans `value`,
|
|
95
|
+
* `onValueChange` ni `options`, propres à chaque filtre et branchés sur la
|
|
96
|
+
* requête.
|
|
97
|
+
*/
|
|
98
|
+
filterSelectProps?: Partial<Omit<SelectProps, "value" | "onValueChange" | "options">>;
|
|
99
|
+
/**
|
|
100
|
+
* Les props du `StatusBadge` de la colonne « Action » par défaut. Sans
|
|
101
|
+
* `value`, lue sur l'entrée. Sans effet quand `columns` est fourni.
|
|
102
|
+
*/
|
|
103
|
+
actionBadgeProps?: Partial<Omit<StatusBadgeProps, "value">>;
|
|
104
|
+
/**
|
|
105
|
+
* Les props du `DataTable` — densité, mode cartes, sélection. Sans
|
|
106
|
+
* `columns` ni `data`, qui ont leurs props ici.
|
|
107
|
+
*/
|
|
108
|
+
tableProps?: Partial<Omit<DataTableProps<T>, "columns" | "data">>;
|
|
109
|
+
/**
|
|
110
|
+
* Les props de la `Pagination` — `jumpTo`, `pageSizeOptions`. Sans la page
|
|
111
|
+
* et la taille de page ni leurs changements, branchés sur la requête.
|
|
112
|
+
*/
|
|
113
|
+
paginationProps?: Partial<
|
|
114
|
+
Omit<
|
|
115
|
+
PaginationProps,
|
|
116
|
+
"page" | "totalPages" | "onPageChange" | "pageSize" | "onPageSizeChange"
|
|
117
|
+
>
|
|
118
|
+
>;
|
|
74
119
|
}
|
|
75
120
|
|
|
76
121
|
/** Les colonnes d'une `ActivityEntry` : quand, qui, quoi, sur quoi. */
|
|
@@ -78,36 +123,45 @@ export function activityColumns(
|
|
|
78
123
|
options: {
|
|
79
124
|
actionTones?: Record<string, ComponentTone> | undefined;
|
|
80
125
|
actionLabels?: Record<string, ReactNode> | undefined;
|
|
126
|
+
/** Les props du `StatusBadge` de la colonne « Action », sans `value`. */
|
|
127
|
+
actionBadgeProps?: Partial<Omit<StatusBadgeProps, "value">> | undefined;
|
|
128
|
+
/** Les en-têtes : le groupe `activityLog` de la locale. Français par défaut. */
|
|
129
|
+
messages?: ActivityLogMessages | undefined;
|
|
130
|
+
/** La langue des dates. Par défaut, `fr-FR`. */
|
|
131
|
+
language?: string | undefined;
|
|
81
132
|
} = {},
|
|
82
133
|
): Array<DataTableColumn<ActivityEntry>> {
|
|
134
|
+
const textes = options.messages ?? FRENCH.activityLog;
|
|
135
|
+
const language = options.language ?? FRENCH.language;
|
|
83
136
|
return [
|
|
84
137
|
{
|
|
85
138
|
key: "at",
|
|
86
|
-
header:
|
|
139
|
+
header: textes.date,
|
|
87
140
|
width: "11rem",
|
|
88
141
|
cell: (entry) => (
|
|
89
142
|
<time className="sia-activity__date" dateTime={new Date(entry.at).toISOString()}>
|
|
90
|
-
{formatDateTime(entry.at)}
|
|
143
|
+
{formatDateTime(entry.at, { locale: language })}
|
|
91
144
|
</time>
|
|
92
145
|
),
|
|
93
146
|
card: "meta",
|
|
94
147
|
},
|
|
95
|
-
{ key: "actor", header:
|
|
148
|
+
{ key: "actor", header: textes.actor, card: "title" },
|
|
96
149
|
{
|
|
97
150
|
key: "action",
|
|
98
|
-
header:
|
|
151
|
+
header: textes.action,
|
|
99
152
|
cell: (entry) => (
|
|
100
153
|
<StatusBadge
|
|
101
|
-
value={entry.action}
|
|
102
154
|
{...(options.actionTones ? { tones: options.actionTones } : {})}
|
|
103
155
|
{...(options.actionLabels ? { labels: options.actionLabels } : {})}
|
|
156
|
+
{...options.actionBadgeProps}
|
|
157
|
+
value={entry.action}
|
|
104
158
|
/>
|
|
105
159
|
),
|
|
106
160
|
card: "meta",
|
|
107
161
|
},
|
|
108
162
|
{
|
|
109
163
|
key: "target",
|
|
110
|
-
header:
|
|
164
|
+
header: textes.target,
|
|
111
165
|
cell: (entry) =>
|
|
112
166
|
entry.target || entry.summary ? (
|
|
113
167
|
<span className="sia-activity__target">
|
|
@@ -146,14 +200,27 @@ export function ActivityLog<T = ActivityEntry>({
|
|
|
146
200
|
onRetry,
|
|
147
201
|
empty,
|
|
148
202
|
className,
|
|
203
|
+
pageHeaderProps,
|
|
204
|
+
filtersBarProps,
|
|
205
|
+
searchInputProps,
|
|
206
|
+
filterSelectProps,
|
|
207
|
+
actionBadgeProps,
|
|
208
|
+
tableProps,
|
|
209
|
+
paginationProps,
|
|
149
210
|
}: ActivityLogProps<T>) {
|
|
211
|
+
const locale = useSiaLocale();
|
|
212
|
+
const textes = locale.activityLog;
|
|
150
213
|
const colonnes = useMemo(
|
|
151
214
|
() =>
|
|
152
215
|
columns ??
|
|
153
|
-
(activityColumns({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
216
|
+
(activityColumns({
|
|
217
|
+
actionTones,
|
|
218
|
+
actionLabels,
|
|
219
|
+
actionBadgeProps,
|
|
220
|
+
messages: textes,
|
|
221
|
+
language: locale.language,
|
|
222
|
+
}) as unknown as Array<DataTableColumn<T>>),
|
|
223
|
+
[actionBadgeProps, actionLabels, actionTones, columns, textes, locale.language],
|
|
157
224
|
);
|
|
158
225
|
|
|
159
226
|
const actifs =
|
|
@@ -171,11 +238,13 @@ export function ActivityLog<T = ActivityEntry>({
|
|
|
171
238
|
title={title}
|
|
172
239
|
{...(description ? { description } : {})}
|
|
173
240
|
{...(headingLevel ? { level: headingLevel } : {})}
|
|
241
|
+
{...pageHeaderProps}
|
|
174
242
|
/>
|
|
175
243
|
)}
|
|
176
244
|
|
|
177
245
|
{(filters.length > 0 || searchPlaceholder) && (
|
|
178
246
|
<FiltersBar
|
|
247
|
+
{...filtersBarProps}
|
|
179
248
|
{...(searchPlaceholder
|
|
180
249
|
? {
|
|
181
250
|
search: (
|
|
@@ -183,9 +252,10 @@ export function ActivityLog<T = ActivityEntry>({
|
|
|
183
252
|
// Remonté à la réinitialisation : le champ reprend la
|
|
184
253
|
// recherche en vigueur plutôt que sa saisie d'avant.
|
|
185
254
|
key={query.state.search === "" ? "vide" : "saisie"}
|
|
186
|
-
defaultValue={query.state.search}
|
|
187
255
|
placeholder={searchPlaceholder}
|
|
188
256
|
searchDelay={300}
|
|
257
|
+
{...searchInputProps}
|
|
258
|
+
defaultValue={query.state.search}
|
|
189
259
|
onSearch={query.setSearch}
|
|
190
260
|
/>
|
|
191
261
|
),
|
|
@@ -196,10 +266,11 @@ export function ActivityLog<T = ActivityEntry>({
|
|
|
196
266
|
<Select
|
|
197
267
|
aria-label={filtre.label}
|
|
198
268
|
placeholder={filtre.label}
|
|
199
|
-
options={filtre.options}
|
|
200
|
-
value={query.state.filters[filtre.key] ?? ""}
|
|
201
269
|
clearable
|
|
202
270
|
searchable={filtre.searchable ?? filtre.options.length > 10}
|
|
271
|
+
{...filterSelectProps}
|
|
272
|
+
options={filtre.options}
|
|
273
|
+
value={query.state.filters[filtre.key] ?? ""}
|
|
203
274
|
onValueChange={(valeur) => query.setFilter(filtre.key, valeur)}
|
|
204
275
|
/>
|
|
205
276
|
</div>
|
|
@@ -218,22 +289,22 @@ export function ActivityLog<T = ActivityEntry>({
|
|
|
218
289
|
{...(onRetry ? { onRetry } : {})}
|
|
219
290
|
empty={
|
|
220
291
|
empty ?? {
|
|
221
|
-
title: actifs > 0 ?
|
|
222
|
-
...(actifs > 0
|
|
223
|
-
? { description: "Élargissez la période ou retirez un filtre." }
|
|
224
|
-
: {}),
|
|
292
|
+
title: actifs > 0 ? textes.emptyFiltered : textes.empty,
|
|
293
|
+
...(actifs > 0 ? { description: textes.emptyFilteredHint } : {}),
|
|
225
294
|
}
|
|
226
295
|
}
|
|
296
|
+
{...tableProps}
|
|
227
297
|
/>
|
|
228
298
|
|
|
229
299
|
{totalPages !== undefined && (
|
|
230
300
|
<Pagination
|
|
301
|
+
showTotal
|
|
302
|
+
{...paginationProps}
|
|
231
303
|
page={query.state.page}
|
|
232
304
|
totalPages={totalPages}
|
|
233
305
|
onPageChange={query.setPage}
|
|
234
306
|
pageSize={query.state.perPage}
|
|
235
307
|
onPageSizeChange={query.setPerPage}
|
|
236
|
-
showTotal
|
|
237
308
|
/>
|
|
238
309
|
)}
|
|
239
310
|
</section>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { cn, formatCurrency } from "@sia-ui/utils";
|
|
3
3
|
import type { ComponentTone } from "@sia-ui/tokens";
|
|
4
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
4
5
|
import "./styles.css";
|
|
5
6
|
|
|
6
|
-
export interface AmountDisplayProps { value: number; currency?: string; locale?: string; label?: ReactNode; secondary?: ReactNode; sign?: "auto" | "always"; tone?: ComponentTone; className?: string; }
|
|
7
|
+
export interface AmountDisplayProps { value: number; currency?: string; /** La langue du format. Par défaut, celle de la locale SIA. */ locale?: string; label?: ReactNode; secondary?: ReactNode; sign?: "auto" | "always"; tone?: ComponentTone; className?: string; }
|
|
7
8
|
/**
|
|
8
9
|
* Un montant, lisible d'un coup d'œil.
|
|
9
10
|
*
|
|
@@ -12,7 +13,9 @@ export interface AmountDisplayProps { value: number; currency?: string; locale?:
|
|
|
12
13
|
* rouge seul. La couleur peut manquer — un daltonien, une impression en
|
|
13
14
|
* noir et blanc — le signe, lui, reste.
|
|
14
15
|
*/
|
|
15
|
-
export function AmountDisplay({ value, currency = "XAF", locale
|
|
16
|
+
export function AmountDisplay({ value, currency = "XAF", locale: localeProp, label, secondary, sign = "auto", tone = "neutral", className }: AmountDisplayProps) {
|
|
17
|
+
const messages = useSiaLocale();
|
|
18
|
+
const locale = localeProp ?? messages.language;
|
|
16
19
|
const formatted = formatCurrency(Math.abs(value), { locale, currency });
|
|
17
20
|
const prefix = sign === "always" || value < 0 ? value < 0 ? "-" : "+" : "";
|
|
18
21
|
return <span className={cn("sia-amount-display", `sia-amount-display--${tone}`, className)}>{label && <span className="sia-amount-display__label">{label}</span>}<strong>{prefix}{formatted}</strong>{secondary && <span className="sia-amount-display__secondary">{secondary}</span>}</span>;
|
|
@@ -11,14 +11,16 @@ import { cn } from "@sia-ui/utils";
|
|
|
11
11
|
import {
|
|
12
12
|
filterNavTree,
|
|
13
13
|
navKeyForPath,
|
|
14
|
+
useSiaLocale,
|
|
14
15
|
type PermissionRule,
|
|
15
16
|
} from "@sia-ui/headless";
|
|
16
17
|
import { useMediaQuery } from "@sia-ui/react";
|
|
17
|
-
import { Drawer } from "../Drawer";
|
|
18
|
+
import { Drawer, type DrawerProps } from "../Drawer";
|
|
18
19
|
import {
|
|
19
20
|
Sidebar,
|
|
20
21
|
type SidebarEntry,
|
|
21
22
|
type SidebarItem,
|
|
23
|
+
type SidebarProps,
|
|
22
24
|
type SidebarRenderLink,
|
|
23
25
|
} from "../Sidebar";
|
|
24
26
|
import { BottomTabs } from "./tab-bar";
|
|
@@ -236,6 +238,35 @@ export interface AppShellProps {
|
|
|
236
238
|
/** Sous cette largeur, la barre latérale cède la place. */
|
|
237
239
|
mobileBreakpoint?: number;
|
|
238
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Réglages de la barre latérale — taille, ton, accordéon, branches
|
|
243
|
+
* ouvertes, volets du rail.
|
|
244
|
+
*
|
|
245
|
+
* Ce que la coquille décide seule en est retiré : les entrées filtrées,
|
|
246
|
+
* l'entrée active, l'état réduit, les liens, l'en-tête et le pied, le côté
|
|
247
|
+
* et la variante — tous déjà réglables ici, et qui doivent rester
|
|
248
|
+
* d'accord avec la mise en page de la coquille.
|
|
249
|
+
*/
|
|
250
|
+
sidebarProps?: Partial<
|
|
251
|
+
Omit<
|
|
252
|
+
SidebarProps,
|
|
253
|
+
| "items"
|
|
254
|
+
| "can"
|
|
255
|
+
| "activeKey"
|
|
256
|
+
| "collapsed"
|
|
257
|
+
| "side"
|
|
258
|
+
| "variant"
|
|
259
|
+
| "renderLink"
|
|
260
|
+
| "onNavigate"
|
|
261
|
+
| "header"
|
|
262
|
+
| "footer"
|
|
263
|
+
| "ariaLabel"
|
|
264
|
+
>
|
|
265
|
+
>;
|
|
266
|
+
|
|
267
|
+
/** Réglages du tiroir mobile — taille, fermeture au clic extérieur, classe. */
|
|
268
|
+
drawerProps?: Partial<Omit<DrawerProps, "open" | "defaultOpen" | "onOpenChange" | "children">>;
|
|
269
|
+
|
|
239
270
|
ariaLabel?: string;
|
|
240
271
|
className?: string;
|
|
241
272
|
}
|
|
@@ -273,9 +304,12 @@ export function AppShell({
|
|
|
273
304
|
sidebarCollapsedWidth,
|
|
274
305
|
mobileNav = "drawer",
|
|
275
306
|
mobileBreakpoint = 768,
|
|
307
|
+
sidebarProps,
|
|
308
|
+
drawerProps,
|
|
276
309
|
ariaLabel,
|
|
277
310
|
className,
|
|
278
311
|
}: AppShellProps) {
|
|
312
|
+
const m = useSiaLocale().appShell;
|
|
279
313
|
const compact = useMediaQuery(`(max-width: ${mobileBreakpoint - 1}px)`);
|
|
280
314
|
|
|
281
315
|
// Filtré une fois, consommé par la barre et par les onglets.
|
|
@@ -339,6 +373,7 @@ export function AppShell({
|
|
|
339
373
|
|
|
340
374
|
const barre = (
|
|
341
375
|
<Sidebar
|
|
376
|
+
{...sidebarProps}
|
|
342
377
|
items={visibles}
|
|
343
378
|
collapsed={reduite && !compact}
|
|
344
379
|
side={side}
|
|
@@ -405,7 +440,7 @@ export function AppShell({
|
|
|
405
440
|
<button
|
|
406
441
|
type="button"
|
|
407
442
|
className="sia-shell__toggle"
|
|
408
|
-
aria-label=
|
|
443
|
+
aria-label={m.openNavigation}
|
|
409
444
|
aria-expanded={tiroirOuvert}
|
|
410
445
|
onClick={() => setTiroirOuvert(true)}
|
|
411
446
|
>
|
|
@@ -418,8 +453,8 @@ export function AppShell({
|
|
|
418
453
|
className="sia-shell__toggle"
|
|
419
454
|
aria-label={
|
|
420
455
|
etat === "expanded"
|
|
421
|
-
?
|
|
422
|
-
:
|
|
456
|
+
? m.collapseNavigation
|
|
457
|
+
: m.expandNavigation
|
|
423
458
|
}
|
|
424
459
|
aria-expanded={etat === "expanded"}
|
|
425
460
|
onClick={basculer}
|
|
@@ -458,14 +493,15 @@ export function AppShell({
|
|
|
458
493
|
|
|
459
494
|
{compact && montrerTiroir && (
|
|
460
495
|
<Drawer
|
|
461
|
-
open={tiroirOuvert}
|
|
462
|
-
onOpenChange={setTiroirOuvert}
|
|
463
496
|
position={side}
|
|
464
497
|
size="sm"
|
|
465
|
-
|
|
498
|
+
{...drawerProps}
|
|
499
|
+
className={cn("sia-shell__drawer", drawerProps?.className)}
|
|
500
|
+
open={tiroirOuvert}
|
|
501
|
+
onOpenChange={setTiroirOuvert}
|
|
466
502
|
>
|
|
467
503
|
<Drawer.Header>
|
|
468
|
-
<Drawer.Title>
|
|
504
|
+
<Drawer.Title>{m.navigationTitle}</Drawer.Title>
|
|
469
505
|
<Drawer.Close />
|
|
470
506
|
</Drawer.Header>
|
|
471
507
|
<Drawer.Body>{barre}</Drawer.Body>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { memo, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
|
-
import { navLeaves, navSome } from "@sia-ui/headless";
|
|
3
|
+
import { navLeaves, navSome, useSiaLocale } from "@sia-ui/headless";
|
|
4
4
|
import type { SidebarItem, SidebarRenderLink } from "../Sidebar";
|
|
5
5
|
|
|
6
6
|
export interface BottomTabsProps {
|
|
@@ -23,6 +23,7 @@ export interface BottomTabsProps {
|
|
|
23
23
|
/** Le bouton qui ouvre l'arborescence complète. */
|
|
24
24
|
more?: ReactNode;
|
|
25
25
|
onMore?: () => void;
|
|
26
|
+
/** Le libellé de « Plus ». Par défaut, celui de la locale. */
|
|
26
27
|
moreLabel?: string;
|
|
27
28
|
|
|
28
29
|
className?: string;
|
|
@@ -47,16 +48,18 @@ export const BottomTabs = memo(function BottomTabs({
|
|
|
47
48
|
onNavigate,
|
|
48
49
|
more,
|
|
49
50
|
onMore,
|
|
50
|
-
moreLabel
|
|
51
|
+
moreLabel: moreLabelProp,
|
|
51
52
|
className,
|
|
52
53
|
}: BottomTabsProps) {
|
|
54
|
+
const m = useSiaLocale().appShell;
|
|
55
|
+
const moreLabel = moreLabelProp ?? m.more;
|
|
53
56
|
const feuilles = navLeaves(items);
|
|
54
57
|
const onglets = feuilles.slice(0, max);
|
|
55
58
|
const reste = feuilles.length - onglets.length;
|
|
56
59
|
const afficherPlus = reste > 0 || Boolean(onMore);
|
|
57
60
|
|
|
58
61
|
return (
|
|
59
|
-
<nav className={cn("sia-bottom-tabs", className)} aria-label=
|
|
62
|
+
<nav className={cn("sia-bottom-tabs", className)} aria-label={m.bottomTabsLabel}>
|
|
60
63
|
{onglets.map((item) => {
|
|
61
64
|
const actif = item.key === activeKey;
|
|
62
65
|
const contenu = (
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
|
+
import { Alert, type AlertProps } from "../Alert";
|
|
4
|
+
import { Button, type ButtonProps } from "../Button";
|
|
5
|
+
import { EmptyState, type EmptyStateProps } from "../EmptyState";
|
|
6
|
+
import { Spinner, type SpinnerProps } from "../Spinner";
|
|
5
7
|
import "./styles.css";
|
|
6
8
|
|
|
7
9
|
export interface AsyncStateProps {
|
|
@@ -12,6 +14,21 @@ export interface AsyncStateProps {
|
|
|
12
14
|
emptyDescription?: ReactNode;
|
|
13
15
|
onRetry?: () => void;
|
|
14
16
|
children: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Les props de l'`Alert` d'erreur — un autre titre, un autre ton. Sans
|
|
19
|
+
* `children` : le message est `error`.
|
|
20
|
+
*/
|
|
21
|
+
alertProps?: Partial<Omit<AlertProps, "children">>;
|
|
22
|
+
/**
|
|
23
|
+
* Les props du bouton « Réessayer ». Sans `onClick`, branché sur
|
|
24
|
+
* `onRetry` : un bouton de reprise qui ne reprend rien serait pire que
|
|
25
|
+
* pas de bouton.
|
|
26
|
+
*/
|
|
27
|
+
retryButtonProps?: Partial<Omit<ButtonProps, "onClick">>;
|
|
28
|
+
/** Les props de l'`EmptyState` — une icône, une action, la taille. */
|
|
29
|
+
emptyStateProps?: Partial<EmptyStateProps>;
|
|
30
|
+
/** Le témoin de chargement. Son libellé vient de la locale. */
|
|
31
|
+
spinnerProps?: Partial<Omit<SpinnerProps, "label">>;
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
/**
|
|
@@ -21,9 +38,14 @@ export interface AsyncStateProps {
|
|
|
21
38
|
* écran, et les écrire à la main en oublie toujours un — le plus souvent le
|
|
22
39
|
* vide, qui ressemble à un chargement qui n'en finit pas.
|
|
23
40
|
*/
|
|
24
|
-
export function AsyncState({ loading, error, empty, emptyTitle
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
export function AsyncState({ loading, error, empty, emptyTitle, emptyDescription, onRetry, children, alertProps, retryButtonProps, emptyStateProps, spinnerProps }: AsyncStateProps) {
|
|
42
|
+
// Les libellés viennent de la locale, comme partout ailleurs : ils étaient
|
|
43
|
+
// restés en anglais ici, seuls de toute la bibliothèque.
|
|
44
|
+
const locale = useSiaLocale();
|
|
45
|
+
// Le vrai `Spinner` : un `<span class="sia-spinner">` brut ignorait ses
|
|
46
|
+
// réglages par défaut (`SiaProvider defaults.spinner`).
|
|
47
|
+
if (loading) return <div className="sia-async-state" role="status"><Spinner size="sm" {...spinnerProps} label={locale.loading} /><span aria-hidden="true">{locale.loading}</span></div>;
|
|
48
|
+
if (error) return <Alert tone="danger" title={locale.error} action={onRetry && <Button variant="outline" {...retryButtonProps} onClick={onRetry}>{retryButtonProps?.children ?? locale.retry}</Button>} {...alertProps}>{error}</Alert>;
|
|
49
|
+
if (empty) return <EmptyState compact title={emptyTitle ?? locale.empty} description={emptyDescription} {...emptyStateProps} />;
|
|
28
50
|
return <>{children}</>;
|
|
29
51
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { cn, formatDate, formatTimeAgo } from "@sia-ui/utils";
|
|
3
|
+
import { useSiaLocale } from "@sia-ui/headless";
|
|
3
4
|
import "./styles.css";
|
|
4
5
|
|
|
5
6
|
export interface AuditEvent {
|
|
@@ -11,8 +12,11 @@ export interface AuditEvent {
|
|
|
11
12
|
|
|
12
13
|
export interface AuditMetaProps {
|
|
13
14
|
events: AuditEvent[];
|
|
15
|
+
/** La langue des dates. Par défaut, celle de la locale SIA. */
|
|
14
16
|
locale?: string;
|
|
17
|
+
/** Par défaut, « Historique » de la locale SIA. */
|
|
15
18
|
title?: ReactNode;
|
|
19
|
+
/** Par défaut, « Aucune activité » de la locale SIA. */
|
|
16
20
|
emptyLabel?: ReactNode;
|
|
17
21
|
className?: string;
|
|
18
22
|
}
|
|
@@ -25,7 +29,13 @@ export interface AuditMetaProps {
|
|
|
25
29
|
* veille. Les dates sont relatives — « il y a deux jours » se compare sans
|
|
26
30
|
* calcul mental.
|
|
27
31
|
*/
|
|
28
|
-
export function AuditMeta({ events, locale
|
|
32
|
+
export function AuditMeta({ events, locale: localeProp, title: titleProp, emptyLabel: emptyLabelProp, className }: AuditMetaProps) {
|
|
33
|
+
const messages = useSiaLocale();
|
|
34
|
+
const locale = localeProp ?? messages.language;
|
|
35
|
+
const title = titleProp ?? messages.auditMeta.title;
|
|
36
|
+
const emptyLabel = emptyLabelProp ?? messages.auditMeta.empty;
|
|
37
|
+
// « par {name} » : l'auteur est un nœud React, le texte l'entoure.
|
|
38
|
+
const [avantAuteur = "", apresAuteur = ""] = messages.auditMeta.by.split("{name}");
|
|
29
39
|
return (
|
|
30
40
|
<section className={cn("sia-audit-meta", className)}>
|
|
31
41
|
<h2 className="sia-audit-meta__title">{title}</h2>
|
|
@@ -37,7 +47,7 @@ export function AuditMeta({ events, locale = "fr-FR", title = "Historique", empt
|
|
|
37
47
|
<li className="sia-audit-meta__event" key={`${new Date(event.at).getTime()}-${index}`}>
|
|
38
48
|
<span className="sia-audit-meta__marker" aria-hidden="true" />
|
|
39
49
|
<div className="sia-audit-meta__content">
|
|
40
|
-
<div className="sia-audit-meta__heading"><strong>{event.label}</strong>{event.by && <span>
|
|
50
|
+
<div className="sia-audit-meta__heading"><strong>{event.label}</strong>{event.by && <span>{avantAuteur}{event.by}{apresAuteur}</span>}</div>
|
|
41
51
|
{event.detail && <div className="sia-audit-meta__detail">{event.detail}</div>}
|
|
42
52
|
<time className="sia-audit-meta__time" dateTime={new Date(event.at).toISOString()} title={formatDate(event.at, { locale, dateStyle: "full", timeStyle: "short" })}>
|
|
43
53
|
{formatTimeAgo(event.at, { locale })}
|