@sia-ui/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/index.js +2 -0
- package/dist/registry/package/components/Alert/index.tsx +7 -0
- package/dist/registry/package/components/AmountDisplay/index.tsx +8 -0
- package/dist/registry/package/components/AppShell/index.tsx +413 -3
- package/dist/registry/package/components/AppShell/styles.css +259 -7
- package/dist/registry/package/components/AppShell/tab-bar.tsx +134 -0
- package/dist/registry/package/components/AsyncState/index.tsx +7 -0
- package/dist/registry/package/components/AuditMeta/index.tsx +8 -0
- package/dist/registry/package/components/AuthLayout/index.tsx +7 -0
- package/dist/registry/package/components/Autocomplete/index.tsx +7 -0
- package/dist/registry/package/components/Avatar/index.tsx +333 -12
- package/dist/registry/package/components/Avatar/styles.css +236 -5
- package/dist/registry/package/components/Badge/index.tsx +7 -0
- package/dist/registry/package/components/Button/index.tsx +7 -0
- package/dist/registry/package/components/Calendar/index.tsx +11 -0
- package/dist/registry/package/components/Card/index.tsx +8 -0
- package/dist/registry/package/components/Checkbox/index.tsx +8 -0
- package/dist/registry/package/components/ColorPicker/index.tsx +7 -0
- package/dist/registry/package/components/CommandPalette/index.tsx +7 -0
- package/dist/registry/package/components/ConfirmDialog/index.tsx +7 -0
- package/dist/registry/package/components/Container/index.tsx +7 -0
- package/dist/registry/package/components/Countdown/index.tsx +7 -0
- package/dist/registry/package/components/CrudPage/dialogs.tsx +230 -0
- package/dist/registry/package/components/CrudPage/index.tsx +546 -5
- package/dist/registry/package/components/CrudPage/styles.css +25 -1
- package/dist/registry/package/components/CurrencyInput/index.tsx +7 -0
- package/dist/registry/package/components/DataTable/index.tsx +548 -200
- package/dist/registry/package/components/DataTable/query.ts +68 -0
- package/dist/registry/package/components/DataTable/row-actions.tsx +158 -0
- package/dist/registry/package/components/DataTable/styles.css +285 -73
- package/dist/registry/package/components/DataTable/types.ts +194 -0
- package/dist/registry/package/components/DataTable/values.ts +28 -0
- package/dist/registry/package/components/DatePicker/index.tsx +7 -0
- package/dist/registry/package/components/DateRangeFilter/index.tsx +7 -0
- package/dist/registry/package/components/DateRangePicker/index.tsx +7 -0
- package/dist/registry/package/components/DateTimePicker/index.tsx +7 -0
- package/dist/registry/package/components/Descriptions/index.tsx +7 -0
- package/dist/registry/package/components/Divider/index.tsx +7 -0
- package/dist/registry/package/components/Drawer/index.tsx +8 -0
- package/dist/registry/package/components/DurationDisplay/index.tsx +6 -0
- package/dist/registry/package/components/EmptyState/index.tsx +7 -0
- package/dist/registry/package/components/EntityMeta/index.tsx +7 -0
- package/dist/registry/package/components/ErrorState/index.tsx +8 -0
- package/dist/registry/package/components/EventCalendar/index.tsx +7 -0
- package/dist/registry/package/components/Field/index.tsx +12 -0
- package/dist/registry/package/components/FileUpload/index.tsx +7 -0
- package/dist/registry/package/components/FiltersBar/index.tsx +7 -0
- package/dist/registry/package/components/HoverCard/index.tsx +14 -37
- package/dist/registry/package/components/IconButton/index.tsx +8 -0
- package/dist/registry/package/components/Icons/index.tsx +15 -0
- package/dist/registry/package/components/ImageUpload/index.tsx +7 -0
- package/dist/registry/package/components/Input/index.tsx +7 -0
- package/dist/registry/package/components/JsonEditor/index.tsx +7 -0
- package/dist/registry/package/components/KpiGrid/index.tsx +7 -0
- package/dist/registry/package/components/Label/index.tsx +7 -0
- package/dist/registry/package/components/MarkdownEditor/index.tsx +7 -0
- package/dist/registry/package/components/MiniCalendar/index.tsx +7 -0
- package/dist/registry/package/components/Modal/index.tsx +8 -0
- package/dist/registry/package/components/MonthPicker/index.tsx +7 -0
- package/dist/registry/package/components/MultiSelect/index.tsx +7 -0
- package/dist/registry/package/components/OtpInput/index.tsx +7 -0
- package/dist/registry/package/components/Overlay/index.tsx +34 -24
- package/dist/registry/package/components/PageHeader/index.tsx +7 -0
- package/dist/registry/package/components/PermissionGate/index.tsx +6 -0
- package/dist/registry/package/components/Popover/index.tsx +7 -0
- package/dist/registry/package/components/QrCode/index.tsx +7 -0
- package/dist/registry/package/components/RadioGroup/index.tsx +7 -0
- package/dist/registry/package/components/Rating/index.tsx +6 -0
- package/dist/registry/package/components/ReferenceSelect/index.tsx +7 -0
- package/dist/registry/package/components/RelativeTime/index.tsx +6 -0
- package/dist/registry/package/components/ResizablePanel/index.tsx +7 -0
- package/dist/registry/package/components/Resource/index.tsx +514 -0
- package/dist/registry/package/components/RichTextEditor/index.tsx +7 -0
- package/dist/registry/package/components/SearchInput/index.tsx +8 -0
- package/dist/registry/package/components/Select/index.tsx +11 -0
- package/dist/registry/package/components/Sidebar/index.tsx +489 -113
- package/dist/registry/package/components/Sidebar/styles.css +96 -4
- package/dist/registry/package/components/Skeleton/index.tsx +7 -0
- package/dist/registry/package/components/Spinner/index.tsx +7 -0
- package/dist/registry/package/components/StatCard/index.tsx +7 -0
- package/dist/registry/package/components/Statistic/index.tsx +7 -0
- package/dist/registry/package/components/Switch/index.tsx +7 -0
- package/dist/registry/package/components/Tabs/index.tsx +7 -0
- package/dist/registry/package/components/TagsInput/index.tsx +7 -0
- package/dist/registry/package/components/Textarea/index.tsx +7 -0
- package/dist/registry/package/components/TimePicker/index.tsx +7 -0
- package/dist/registry/package/components/TimeRangePicker/index.tsx +6 -0
- package/dist/registry/package/components/Timeline/index.tsx +7 -0
- package/dist/registry/package/components/Timer/index.tsx +7 -0
- package/dist/registry/package/components/Tour/index.tsx +7 -0
- package/dist/registry/package/components/Transfer/index.tsx +7 -0
- package/dist/registry/package/components/Tree/index.tsx +7 -0
- package/dist/registry/package/components/TreeSelect/index.tsx +7 -0
- package/dist/registry/package/components/Typography/index.tsx +8 -0
- package/dist/registry/package/components/UnsavedChangesGuard/index.tsx +8 -0
- package/dist/registry/package/components/WeekPicker/index.tsx +7 -0
- package/dist/registry/package/components/YearPicker/index.tsx +7 -0
- package/dist/registry/registry.json +69 -14
- package/package.json +14 -14
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
.sia-sidebar__body { flex: 1; min-height: 0; overflow-y: auto; display: grid; gap: .125rem; align-content: start; }
|
|
12
|
+
/* Rognés plutôt que débordants : en mode rail, un en-tête ou un pied qui
|
|
13
|
+
n'a pas été adapté déborderait sur le contenu. Le `data-collapsed` posé
|
|
14
|
+
par `AppShell` permet de l'adapter; ceci garantit que ne pas l'adapter
|
|
15
|
+
reste sans conséquence. */
|
|
12
16
|
.sia-sidebar__header,
|
|
13
|
-
.sia-sidebar__footer { flex: none; padding: .25rem; }
|
|
17
|
+
.sia-sidebar__footer { flex: none; padding: .25rem; overflow: hidden; }
|
|
14
18
|
|
|
15
19
|
.sia-sidebar__section { display: grid; gap: .125rem; }
|
|
16
20
|
.sia-sidebar__section + .sia-sidebar__section { margin-top: .75rem; }
|
|
@@ -112,8 +116,17 @@
|
|
|
112
116
|
.sia-sidebar__node[data-open] .sia-sidebar__panel { grid-template-rows: 1fr; }
|
|
113
117
|
.sia-sidebar__sub { overflow: hidden; min-height: 0; display: grid; gap: .125rem; }
|
|
114
118
|
|
|
119
|
+
/* L'indentation suit la profondeur : `--sia-sidebar-depth` est posé par le
|
|
120
|
+
nœud, et vaut 1 au premier sous-niveau. Une marge figée aurait bloqué
|
|
121
|
+
l'arbre à deux étages.
|
|
122
|
+
|
|
123
|
+
Elle se fait en `padding`, jamais en `margin` : l'entrée est en
|
|
124
|
+
`width: 100%`, et une marge s'y ajoute au lieu de s'y prendre — la ligne
|
|
125
|
+
débordait alors du volet d'exactement son indentation, et la pastille de
|
|
126
|
+
droite se retrouvait coupée. La zone cliquable y gagne aussi : elle va
|
|
127
|
+
jusqu'au bord. */
|
|
115
128
|
.sia-sidebar__item--child {
|
|
116
|
-
|
|
129
|
+
padding-left: calc(.625rem + var(--sia-sidebar-depth, 1) * .75rem);
|
|
117
130
|
padding-block: .375rem;
|
|
118
131
|
font-size: .825rem;
|
|
119
132
|
}
|
|
@@ -136,5 +149,84 @@
|
|
|
136
149
|
.sia-sidebar--collapsed .sia-sidebar__badge,
|
|
137
150
|
.sia-sidebar--collapsed .sia-sidebar__chevron { display: none; }
|
|
138
151
|
.sia-sidebar--collapsed .sia-sidebar__section-title { height: 0; padding: 0; opacity: 0; overflow: hidden; }
|
|
139
|
-
/*
|
|
140
|
-
|
|
152
|
+
/* Le rail ne déplie plus rien en ligne — et surtout, il ne force plus le
|
|
153
|
+
panneau à zéro. C'était le défaut central : les sous-entrées devenaient
|
|
154
|
+
inatteignables dès qu'on repliait la barre. Elles sortent maintenant en
|
|
155
|
+
volet, hors du flux, via `Overlay`. */
|
|
156
|
+
.sia-sidebar--collapsed .sia-sidebar__item { position: relative; }
|
|
157
|
+
|
|
158
|
+
/* La pastille ne tient pas sur un rail : elle devient un point. */
|
|
159
|
+
.sia-sidebar__dot {
|
|
160
|
+
position: absolute;
|
|
161
|
+
top: .3rem;
|
|
162
|
+
right: .3rem;
|
|
163
|
+
width: .45rem;
|
|
164
|
+
height: .45rem;
|
|
165
|
+
border-radius: 50%;
|
|
166
|
+
background: var(--sia-danger, #e11d48);
|
|
167
|
+
box-shadow: 0 0 0 2px var(--sia-background);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ── Le volet du rail ──────────────────────────────────────────────────── */
|
|
171
|
+
|
|
172
|
+
.sia-sidebar-flyout {
|
|
173
|
+
min-width: 13rem;
|
|
174
|
+
max-width: 18rem;
|
|
175
|
+
padding: .3rem;
|
|
176
|
+
border: 1px solid var(--sia-border);
|
|
177
|
+
border-radius: var(--sia-radius);
|
|
178
|
+
background: var(--sia-background);
|
|
179
|
+
box-shadow: var(--sia-shadow-lg, 0 12px 32px rgb(15 23 42 / .16));
|
|
180
|
+
font-size: .875rem;
|
|
181
|
+
animation: sia-sidebar-flyout var(--sia-duration-fast) var(--sia-easing-standard);
|
|
182
|
+
}
|
|
183
|
+
@keyframes sia-sidebar-flyout {
|
|
184
|
+
from { opacity: 0; transform: translateX(-.25rem); }
|
|
185
|
+
}
|
|
186
|
+
.sia-sidebar--right .sia-sidebar-flyout { animation-name: sia-sidebar-flyout-right; }
|
|
187
|
+
@keyframes sia-sidebar-flyout-right {
|
|
188
|
+
from { opacity: 0; transform: translateX(.25rem); }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.sia-sidebar-flyout__title {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: .5rem;
|
|
195
|
+
padding: .4rem .625rem .5rem;
|
|
196
|
+
color: var(--sia-foreground);
|
|
197
|
+
font-weight: 650;
|
|
198
|
+
}
|
|
199
|
+
.sia-sidebar-flyout__body { display: grid; gap: .125rem; }
|
|
200
|
+
/* Dans le volet, le filet vertical du parent n'a plus de parent à relier. */
|
|
201
|
+
.sia-sidebar-flyout .sia-sidebar__sub::before { display: none; }
|
|
202
|
+
.sia-sidebar-flyout .sia-sidebar__item--child { padding-left: calc(.625rem + var(--sia-sidebar-depth, 1) * .6rem); }
|
|
203
|
+
|
|
204
|
+
/* ── Variantes ─────────────────────────────────────────────────────────── */
|
|
205
|
+
|
|
206
|
+
.sia-sidebar--filled { background: var(--sia-surface); }
|
|
207
|
+
.sia-sidebar--filled .sia-sidebar__item:hover:not(:disabled) { background: var(--sia-background); }
|
|
208
|
+
|
|
209
|
+
.sia-sidebar--floating {
|
|
210
|
+
margin: .5rem;
|
|
211
|
+
border: 1px solid var(--sia-border);
|
|
212
|
+
border-radius: calc(var(--sia-radius) * 1.5);
|
|
213
|
+
background: var(--sia-background);
|
|
214
|
+
box-shadow: var(--sia-shadow-sm, 0 1px 2px rgb(15 23 42 / .06));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ── Tailles ───────────────────────────────────────────────────────────── */
|
|
218
|
+
|
|
219
|
+
.sia-sidebar--sm { font-size: .8125rem; }
|
|
220
|
+
.sia-sidebar--sm .sia-sidebar__item { padding-block: .375rem; }
|
|
221
|
+
.sia-sidebar--lg { font-size: .9375rem; }
|
|
222
|
+
.sia-sidebar--lg .sia-sidebar__item { padding-block: .625rem; }
|
|
223
|
+
.sia-sidebar--lg .sia-sidebar__icon { width: 1.25rem; font-size: 1.15rem; }
|
|
224
|
+
|
|
225
|
+
/* Le ton pilote la couleur de l'entrée courante. */
|
|
226
|
+
.sia-sidebar__item--active { color: var(--sia-accent, var(--sia-primary)); }
|
|
227
|
+
.sia-sidebar__item--active::before { background: var(--sia-accent, var(--sia-primary)); }
|
|
228
|
+
.sia-sidebar__item--active { background: color-mix(in srgb, var(--sia-accent, var(--sia-primary)) 10%, transparent); }
|
|
229
|
+
|
|
230
|
+
/* Une branche repliée qui contient la page courante : assez pour la
|
|
231
|
+
retrouver, pas assez pour être confondue avec elle. */
|
|
232
|
+
.sia-sidebar__item--in-branch { color: var(--sia-foreground); font-weight: 600; }
|
|
@@ -9,6 +9,13 @@ export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
9
9
|
circle?: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* La forme de ce qui n'est pas encore là.
|
|
14
|
+
*
|
|
15
|
+
* Aux dimensions du contenu attendu, et non d'une taille générique : c'est
|
|
16
|
+
* ce qui évite le sursaut de mise en page à l'arrivée des données, lequel
|
|
17
|
+
* fait cliquer à côté.
|
|
18
|
+
*/
|
|
12
19
|
export function Skeleton({ className, width = "100%", height = "1rem", radius, circle, style, ...props }: SkeletonProps) {
|
|
13
20
|
return (
|
|
14
21
|
<div
|
|
@@ -11,6 +11,13 @@ export interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
11
11
|
tone?: ComponentTone | "current";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Une attente sans durée connue.
|
|
16
|
+
*
|
|
17
|
+
* Quand la durée est connue, une barre de progression en dit plus. Le ton
|
|
18
|
+
* `current` reprend la couleur du texte environnant : un indicateur posé
|
|
19
|
+
* dans un bouton doit suivre la couleur du bouton, pas celle du thème.
|
|
20
|
+
*/
|
|
14
21
|
export function Spinner({ label: labelProp, size: sizeProp, variant: variantProp, tone: toneProp, className, ...props }: SpinnerProps) {
|
|
15
22
|
const defaults = useComponentDefaults<SpinnerProps>("spinner");
|
|
16
23
|
const label = labelProp ?? defaults.label ?? "Chargement";
|
|
@@ -10,6 +10,13 @@ export interface StatCardProps extends StatisticProps {
|
|
|
10
10
|
action?: ReactNode;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Un indicateur, dans une carte.
|
|
15
|
+
*
|
|
16
|
+
* L'assemblage d'une `Card` et d'un `Statistic` — la forme qu'ils prennent
|
|
17
|
+
* presque toujours ensemble, et qu'il serait fastidieux de recomposer à
|
|
18
|
+
* chaque tableau de bord.
|
|
19
|
+
*/
|
|
13
20
|
export function StatCard({ icon, description, action, className, ...statisticProps }: StatCardProps) {
|
|
14
21
|
return <Card className={cn("sia-stat-card", className)}><CardBody><div className="sia-stat-card__top">{icon && <span className="sia-stat-card__icon" aria-hidden="true">{icon}</span>}{action}</div><Statistic {...statisticProps} />{description && <div className="sia-stat-card__description">{description}</div>}</CardBody></Card>;
|
|
15
22
|
}
|
|
@@ -15,6 +15,13 @@ export interface StatisticProps {
|
|
|
15
15
|
className?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Un nombre qu'on lit d'un coup d'œil.
|
|
20
|
+
*
|
|
21
|
+
* La tendance est un écart, pas une flèche : c'est le signe du nombre qui
|
|
22
|
+
* décide du sens et de la couleur. Une flèche passée en prop finit par
|
|
23
|
+
* pointer vers le haut sur une baisse.
|
|
24
|
+
*/
|
|
18
25
|
export function Statistic({ value, label, format = "number", locale = "fr-FR", currency = "XAF", precision, prefix, suffix, trend, className }: StatisticProps) {
|
|
19
26
|
const formatted = format === "currency"
|
|
20
27
|
? formatCurrency(value, { locale, currency, ...(precision !== undefined ? { minimumFractionDigits: precision, maximumFractionDigits: precision } : {}) })
|
|
@@ -9,6 +9,13 @@ export interface SwitchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement
|
|
|
9
9
|
onCheckedChange?: (checked: boolean) => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Une bascule à effet immédiat.
|
|
14
|
+
*
|
|
15
|
+
* À la différence d'une case à cocher, elle n'attend pas de validation :
|
|
16
|
+
* ce qu'on bascule s'applique tout de suite. Une case dans un formulaire,
|
|
17
|
+
* une bascule dans des réglages.
|
|
18
|
+
*/
|
|
12
19
|
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
|
|
13
20
|
({ className, checked, defaultChecked = false, label, disabled, onCheckedChange, onClick, ...props }, ref) => {
|
|
14
21
|
const [internalChecked, setInternalChecked] = useState(defaultChecked);
|
|
@@ -7,6 +7,13 @@ const TabsContext = createContext<TabsContextValue | null>(null);
|
|
|
7
7
|
|
|
8
8
|
export interface TabsProps { value?: string; defaultValue: string; onValueChange?: (value: string) => void; className?: string; children: ReactNode; }
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Des vues qui se remplacent au même endroit.
|
|
12
|
+
*
|
|
13
|
+
* Elles servent à des contenus de même rang — jamais à des étapes, qui ont
|
|
14
|
+
* un ordre et des `Steps`, ni à une navigation entre écrans, qui change
|
|
15
|
+
* l'adresse.
|
|
16
|
+
*/
|
|
10
17
|
export function Tabs({ value, defaultValue, onValueChange, className, children }: TabsProps) {
|
|
11
18
|
const [internal, setInternal] = useState(defaultValue);
|
|
12
19
|
const baseId = useId();
|
|
@@ -3,4 +3,11 @@ import { cn } from "@sia-ui/utils";
|
|
|
3
3
|
import { XIcon } from "../Icons";
|
|
4
4
|
import "./styles.css";
|
|
5
5
|
export interface TagsInputProps { id?: string; value?: string[]; defaultValue?: string[]; onValueChange?: (value: string[]) => void; placeholder?: string; separators?: string[]; maxTags?: number; disabled?: boolean; className?: string; "aria-describedby"?: string; "aria-labelledby"?: string; "aria-invalid"?: boolean; "aria-required"?: boolean; }
|
|
6
|
+
/**
|
|
7
|
+
* Une liste de mots, saisis à la volée.
|
|
8
|
+
*
|
|
9
|
+
* Les séparateurs sont déclarés : la virgule et l'entrée par défaut, parce
|
|
10
|
+
* que ce sont les deux touches que l'on presse sans y penser après avoir
|
|
11
|
+
* tapé un mot.
|
|
12
|
+
*/
|
|
6
13
|
export function TagsInput({ id, value, defaultValue = [], onValueChange, placeholder = "Ajouter un tag", separators = ["Enter", ","], maxTags, disabled, className, ...ariaProps }: TagsInputProps) { const [internal, setInternal] = useState(defaultValue); const [draft, setDraft] = useState(""); const current = value ?? internal; const update = (next: string[]) => { if (value === undefined) setInternal(next); onValueChange?.(next); }; const add = () => { const tag = draft.trim().replace(/,$/, ""); if (!tag || current.includes(tag) || (maxTags !== undefined && current.length >= maxTags)) return; update([...current, tag]); setDraft(""); }; return <div id={id} className={cn("sia-tags-input", className)} {...ariaProps}>{current.map((tag) => <span key={tag}>{tag}<button type="button" disabled={disabled} aria-label={`Retirer ${tag}`} onClick={() => update(current.filter((item) => item !== tag))}><XIcon /></button></span>)}<input value={draft} disabled={disabled} placeholder={placeholder} onChange={(event) => setDraft(event.target.value)} onBlur={add} onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { if (separators.includes(event.key)) { event.preventDefault(); add(); } if (event.key === "Backspace" && !draft && current.length) update(current.slice(0, -1)); }} /></div>; }
|
|
@@ -7,6 +7,13 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
7
7
|
resize?: "none" | "vertical" | "horizontal" | "both";
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* La saisie de plusieurs lignes.
|
|
12
|
+
*
|
|
13
|
+
* Elle garde la hauteur qu'on lui donne : un redimensionnement automatique
|
|
14
|
+
* fait sauter la page à chaque retour à la ligne, et déplace ce qu'on
|
|
15
|
+
* s'apprêtait à cliquer.
|
|
16
|
+
*/
|
|
10
17
|
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
11
18
|
({ className, invalid, resize = "vertical", ...props }, ref) => (
|
|
12
19
|
<textarea
|
|
@@ -42,6 +42,13 @@ function steppedValues(limit: number, step: number) {
|
|
|
42
42
|
return Array.from({ length: Math.ceil(limit / Math.max(step, 1)) }, (_, index) => index * Math.max(step, 1)).filter((value) => value < limit);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Une heure, au pas voulu.
|
|
47
|
+
*
|
|
48
|
+
* `minuteStep` évite de faire défiler soixante minutes pour un rendez-vous
|
|
49
|
+
* qui tombe au quart d'heure. Les secondes ne s'affichent que si on les
|
|
50
|
+
* demande — la plupart des heures métier n'en ont pas.
|
|
51
|
+
*/
|
|
45
52
|
export const TimePicker = forwardRef<HTMLButtonElement, TimePickerProps>(function TimePicker({
|
|
46
53
|
id, name, value, defaultValue = "", min, max, showSeconds = false, minuteStep = 1, secondStep = 1, hourFormat = 24, needConfirm = false, allowClear = true, placeholder = "Sélectionner une heure", disabledTime, renderExtraFooter, onValueChange, onOpenChange, open: openProp, placement = "bottom-start", tone = "primary", invalid = false, disabled = false, required = false, className, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, ...ariaProps
|
|
47
54
|
}, forwardedRef) {
|
|
@@ -19,6 +19,12 @@ export interface TimeRangePickerProps extends Pick<TimePickerProps, "minuteStep"
|
|
|
19
19
|
disabled?: boolean;
|
|
20
20
|
className?: string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Une heure de début et une heure de fin.
|
|
24
|
+
*
|
|
25
|
+
* `order` impose que la fin suive le début. On peut le lever — une garde
|
|
26
|
+
* de nuit finit le lendemain — mais il faut l'avoir écrit.
|
|
27
|
+
*/
|
|
22
28
|
export function TimeRangePicker({ value, defaultValue = { start: "", end: "" }, onValueChange, order = true, startLabel: startLabelProp, endLabel: endLabelProp, minuteStep = 1, secondStep = 1, showSeconds = false, hourFormat = 24, needConfirm = false, allowClear = true, tone = "primary", invalid, disabled, className }: TimeRangePickerProps) {
|
|
23
29
|
const locale = useSiaLocale();
|
|
24
30
|
const endLabel = endLabelProp ?? locale.endTime;
|
|
@@ -5,4 +5,11 @@ import "./styles.css";
|
|
|
5
5
|
|
|
6
6
|
export interface TimelineItem { key: string; title: ReactNode; description?: ReactNode; time?: ReactNode; tone?: ComponentTone; }
|
|
7
7
|
export interface TimelineProps { items: TimelineItem[]; pending?: ReactNode; className?: string; }
|
|
8
|
+
/**
|
|
9
|
+
* Une suite d'événements, dans l'ordre.
|
|
10
|
+
*
|
|
11
|
+
* Le ton porte le sens de chaque étape : une chronologie de paiement se lit
|
|
12
|
+
* d'abord par ses couleurs — ce qui a abouti, ce qui a échoué — avant qu'on
|
|
13
|
+
* en lise les libellés.
|
|
14
|
+
*/
|
|
8
15
|
export function Timeline({ items, pending, className }: TimelineProps) { return <ol className={cn("sia-timeline", className)}>{items.map((item) => <li key={item.key} className={`sia-timeline__item sia-timeline__item--${item.tone ?? "primary"}`}><span className="sia-timeline__dot" /><div><strong>{item.title}</strong>{item.description && <p>{item.description}</p>}{item.time && <small>{item.time}</small>}</div></li>)}{pending && <li className="sia-timeline__item sia-timeline__item--pending"><span className="sia-timeline__dot" /><div>{pending}</div></li>}</ol>; }
|
|
@@ -6,4 +6,11 @@ export interface TimeParts { hours: number; minutes: number; seconds: number; }
|
|
|
6
6
|
export function parseTimeValue(value = ""): TimeParts { const [hours = 0, minutes = 0, seconds = 0] = value.split(":").map(Number); return { hours: Math.min(Math.max(hours || 0, 0), 23), minutes: Math.min(Math.max(minutes || 0, 0), 59), seconds: Math.min(Math.max(seconds || 0, 0), 59) }; }
|
|
7
7
|
export function formatTimeValue(parts: TimeParts, showSeconds = false) { const base = `${String(parts.hours).padStart(2, "0")}:${String(parts.minutes).padStart(2, "0")}`; return showSeconds ? `${base}:${String(parts.seconds).padStart(2, "0")}` : base; }
|
|
8
8
|
export function formatDuration(milliseconds: number, showMilliseconds = false) { const total = Math.max(0, milliseconds); const hours = Math.floor(total / 3600000); const minutes = Math.floor((total % 3600000) / 60000); const seconds = Math.floor((total % 60000) / 1000); const ms = Math.floor((total % 1000) / 10); return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}${showMilliseconds ? `.${String(ms).padStart(2, "0")}` : ""}`; }
|
|
9
|
+
/**
|
|
10
|
+
* Un temps qui s'écoule, dans un sens ou dans l'autre.
|
|
11
|
+
*
|
|
12
|
+
* L'intervalle est réglable et vaut 250 ms par défaut : une pendule au
|
|
13
|
+
* dixième de seconde redessine quarante fois par seconde pour un chiffre
|
|
14
|
+
* que personne ne lit.
|
|
15
|
+
*/
|
|
9
16
|
export function Timer({ duration = 60000, initialValue = 0, direction = "down", running = true, interval = 250, onChange, onFinish, format = formatDuration, className }: TimerProps) { const initial = direction === "down" ? duration : initialValue; const [value, setValue] = useState(initial); const finishCalled = useRef(false); useEffect(() => { setValue(direction === "down" ? duration : initialValue); finishCalled.current = false; }, [direction, duration, initialValue]); useEffect(() => { if (!running) return; const startedAt = Date.now(); const base = value; const update = () => { const elapsed = Date.now() - startedAt; const next = direction === "down" ? Math.max(0, base - elapsed) : base + elapsed; setValue(next); onChange?.(next); if (direction === "down" && next === 0 && !finishCalled.current) { finishCalled.current = true; onFinish?.(); } }; update(); const id = window.setInterval(update, interval); return () => window.clearInterval(id); }, [running, direction, interval]); return <time className={cn("sia-timer", className)} aria-live="polite">{format(value)}</time>; }
|
|
@@ -5,4 +5,11 @@ import "./styles.css";
|
|
|
5
5
|
|
|
6
6
|
export interface TourStep { target: string; title: ReactNode; description?: ReactNode; }
|
|
7
7
|
export interface TourProps { open: boolean; onOpenChange: (open: boolean) => void; steps: TourStep[]; initialStep?: number; onStepChange?: (step: number) => void; className?: string; }
|
|
8
|
+
/**
|
|
9
|
+
* Une visite guidée de l'écran.
|
|
10
|
+
*
|
|
11
|
+
* Chaque étape vise un élément par son sélecteur plutôt que par une
|
|
12
|
+
* référence : une visite se déclare souvent loin des composants qu'elle
|
|
13
|
+
* montre, et parfois avant qu'ils n'existent.
|
|
14
|
+
*/
|
|
8
15
|
export function Tour({ open, onOpenChange, steps, initialStep = 0, onStepChange, className }: TourProps) { const [index, setIndex] = useState(initialStep); useEffect(() => { if (open) setIndex(initialStep); }, [open, initialStep]); useEffect(() => { if (!open) return; const target = document.querySelector<HTMLElement>(steps[index]?.target ?? ""); target?.scrollIntoView({ block: "center", behavior: "smooth" }); target?.setAttribute("data-sia-tour-active", "true"); return () => target?.removeAttribute("data-sia-tour-active"); }, [open, index, steps]); if (!open || typeof document === "undefined" || steps.length === 0) return null; const step = steps[index]; const move = (next: number) => { if (next >= steps.length) { onOpenChange(false); return; } setIndex(next); onStepChange?.(next); }; return createPortal(<div className="sia-tour-backdrop"><section className={cn("sia-tour", className)} role="dialog" aria-modal="true"><small>{index + 1} / {steps.length}</small><h2>{step?.title}</h2>{step?.description && <p>{step.description}</p>}<footer><button type="button" onClick={() => onOpenChange(false)}>Fermer</button><button type="button" disabled={index === 0} onClick={() => move(index - 1)}>Précédent</button><button type="button" onClick={() => move(index + 1)}>{index === steps.length - 1 ? "Terminer" : "Suivant"}</button></footer></section></div>, document.body); }
|
|
@@ -4,4 +4,11 @@ import "./styles.css";
|
|
|
4
4
|
|
|
5
5
|
export interface TransferItem { key: Key; label: ReactNode; disabled?: boolean; }
|
|
6
6
|
export interface TransferProps { items: TransferItem[]; targetKeys: Key[]; onChange: (targetKeys: Key[]) => void; sourceTitle?: string; targetTitle?: string; className?: string; }
|
|
7
|
+
/**
|
|
8
|
+
* Deux listes, et ce qui passe de l'une à l'autre.
|
|
9
|
+
*
|
|
10
|
+
* La forme qui convient quand le choix se compte en dizaines : une liste à
|
|
11
|
+
* cocher de deux cents lignes ne montre plus ce qui est retenu, alors que
|
|
12
|
+
* la colonne de droite ne contient que cela.
|
|
13
|
+
*/
|
|
7
14
|
export function Transfer({ items, targetKeys, onChange, sourceTitle = "Disponibles", targetTitle = "Sélectionnés", className }: TransferProps) { const [checked, setChecked] = useState<Key[]>([]); const target = useMemo(() => new Set(targetKeys), [targetKeys]); const renderList = (list: TransferItem[], title: string) => <section><header>{title} <span>{list.length}</span></header>{list.map((item) => <label key={item.key}><input type="checkbox" disabled={item.disabled} checked={checked.includes(item.key)} onChange={(event) => setChecked((current) => event.target.checked ? [...current, item.key] : current.filter((key) => key !== item.key))} />{item.label}</label>)}</section>; const sourceItems = items.filter((item) => !target.has(item.key)); const targetItems = items.filter((item) => target.has(item.key)); return <div className={cn("sia-transfer", className)}>{renderList(sourceItems, sourceTitle)}<div className="sia-transfer__actions"><button type="button" aria-label="Ajouter" onClick={() => { onChange([...new Set([...targetKeys, ...checked])]); setChecked([]); }}>→</button><button type="button" aria-label="Retirer" onClick={() => { onChange(targetKeys.filter((key) => !checked.includes(key))); setChecked([]); }}>←</button></div>{renderList(targetItems, targetTitle)}</div>; }
|
|
@@ -5,4 +5,11 @@ import "./styles.css";
|
|
|
5
5
|
export interface TreeNode { key: Key; label: ReactNode; children?: TreeNode[]; disabled?: boolean; }
|
|
6
6
|
export interface TreeProps { nodes: TreeNode[]; defaultExpandedKeys?: Key[]; selectedKey?: Key | undefined; onSelect?: ((node: TreeNode) => void) | undefined; className?: string; }
|
|
7
7
|
function TreeBranch({ nodes, level, expanded, toggle, selectedKey, onSelect }: { nodes: TreeNode[]; level: number; expanded: Set<Key>; toggle: (key: Key) => void; selectedKey?: Key | undefined; onSelect?: ((node: TreeNode) => void) | undefined }) { return <ul role={level === 1 ? "tree" : "group"}>{nodes.map((node) => { const hasChildren = Boolean(node.children?.length); const isExpanded = expanded.has(node.key); return <li key={node.key} role="treeitem" aria-expanded={hasChildren ? isExpanded : undefined} aria-selected={selectedKey === node.key} aria-disabled={node.disabled || undefined}><div className="sia-tree__row" style={{ paddingInlineStart: `${(level - 1) * 1.25}rem` }}>{hasChildren ? <button type="button" aria-label={isExpanded ? "Réduire" : "Développer"} onClick={() => toggle(node.key)}>{isExpanded ? "−" : "+"}</button> : <span className="sia-tree__spacer" />}<button type="button" disabled={node.disabled} className="sia-tree__label" onClick={() => onSelect?.(node)}>{node.label}</button></div>{hasChildren && isExpanded && <TreeBranch nodes={node.children ?? []} level={level + 1} expanded={expanded} toggle={toggle} selectedKey={selectedKey} onSelect={onSelect} />}</li>; })}</ul>; }
|
|
8
|
+
/**
|
|
9
|
+
* Une arborescence qu'on déplie.
|
|
10
|
+
*
|
|
11
|
+
* Les branches ouvertes sont un état à part de la sélection : replier un
|
|
12
|
+
* nœud ne désélectionne pas ce qu'il contient, et rouvrir l'écran ne doit
|
|
13
|
+
* pas tout refermer.
|
|
14
|
+
*/
|
|
8
15
|
export function Tree({ nodes, defaultExpandedKeys = [], selectedKey, onSelect, className }: TreeProps) { const [expanded, setExpanded] = useState(new Set(defaultExpandedKeys)); const toggle = (key: Key) => setExpanded((current) => { const next = new Set(current); if (next.has(key)) next.delete(key); else next.add(key); return next; }); return <div className={cn("sia-tree", className)}><TreeBranch nodes={nodes} level={1} expanded={expanded} toggle={toggle} selectedKey={selectedKey} onSelect={onSelect} /></div>; }
|
|
@@ -5,6 +5,13 @@ import { useSiaLocale } from "@sia-ui/headless";
|
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
|
|
7
7
|
export interface TreeSelectProps { nodes: TreeNode[]; value?: Key | undefined; onValueChange?: (value: Key, node: TreeNode) => void; placeholder?: string; className?: string; }
|
|
8
|
+
/**
|
|
9
|
+
* Un choix dans une arborescence.
|
|
10
|
+
*
|
|
11
|
+
* Là où une liste plate perdrait le chemin : « Ventes › Règlements › En
|
|
12
|
+
* attente » ne se réduit pas à « En attente » sans devenir ambigu dès qu'un
|
|
13
|
+
* autre nœud porte le même nom.
|
|
14
|
+
*/
|
|
8
15
|
export function TreeSelect({ nodes, value, onValueChange, placeholder: placeholderProp, className }: TreeSelectProps) {
|
|
9
16
|
const locale = useSiaLocale();
|
|
10
17
|
const placeholder = placeholderProp ?? locale.select; const [open, setOpen] = useState(false); const find = (items: TreeNode[]): TreeNode | undefined => { for (const item of items) { if (item.key === value) return item; const child = item.children ? find(item.children) : undefined; if (child) return child; } }; const selected = find(nodes); return <div className={cn("sia-tree-select", className)}><button type="button" className="sia-tree-select__trigger" aria-expanded={open} onClick={() => setOpen(!open)}>{selected?.label ?? placeholder}</button>{open && <div className="sia-tree-select__panel"><Tree nodes={nodes} defaultExpandedKeys={nodes.map((node) => node.key)} selectedKey={value} onSelect={(node) => { onValueChange?.(node.key, node); setOpen(false); }} /></div>}</div>; }
|
|
@@ -8,6 +8,14 @@ export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
|
|
|
8
8
|
size?: "sm" | "md" | "lg" | "xl";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Un titre.
|
|
13
|
+
*
|
|
14
|
+
* Le niveau et la taille sont deux props distinctes : le niveau dit la
|
|
15
|
+
* place dans la hiérarchie du document, la taille ce qu'on voit. Les lier
|
|
16
|
+
* forcerait à sauter un niveau pour obtenir un titre plus petit — et à
|
|
17
|
+
* casser le parcours au lecteur d'écran.
|
|
18
|
+
*/
|
|
11
19
|
export function Heading({ level = 2, size = "lg", className, ...props }: HeadingProps) {
|
|
12
20
|
const Component = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
13
21
|
return <Component className={cn("sia-heading", `sia-heading--${size}`, className)} {...props} />;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
export interface UnsavedChangesGuardProps { when: boolean; message?: string; }
|
|
3
|
+
/**
|
|
4
|
+
* L'avertissement avant de quitter une saisie en cours.
|
|
5
|
+
*
|
|
6
|
+
* Le navigateur n'autorise cette interruption que sur un geste explicite de
|
|
7
|
+
* quitter, et impose son propre libellé : le message passé ici sert à la
|
|
8
|
+
* confirmation interne, que `confirmUnsavedNavigation` déclenche avant une
|
|
9
|
+
* navigation du routeur.
|
|
10
|
+
*/
|
|
3
11
|
export function UnsavedChangesGuard({ when, message = "Des modifications non enregistrées seront perdues." }: UnsavedChangesGuardProps) { useEffect(() => { if (!when) return; const handler = (event: BeforeUnloadEvent) => { event.preventDefault(); event.returnValue = message; return message; }; window.addEventListener("beforeunload", handler); return () => window.removeEventListener("beforeunload", handler); }, [when, message]); return null; }
|
|
4
12
|
export function confirmUnsavedNavigation(when: boolean, message = "Des modifications non enregistrées seront perdues.") { return !when || window.confirm(message); }
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { forwardRef, type InputHTMLAttributes } from "react";
|
|
2
2
|
import { cn } from "@sia-ui/utils";
|
|
3
3
|
export interface WeekPickerProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> { onValueChange?: (value: string) => void; invalid?: boolean; }
|
|
4
|
+
/**
|
|
5
|
+
* Une semaine, désignée par son numéro.
|
|
6
|
+
*
|
|
7
|
+
* Le numéro de semaine est l'unité des plannings et des relevés d'activité,
|
|
8
|
+
* et il ne se déduit pas d'une date sans convention — celle du navigateur
|
|
9
|
+
* est celle de la norme ISO.
|
|
10
|
+
*/
|
|
4
11
|
export const WeekPicker = forwardRef<HTMLInputElement, WeekPickerProps>(({ onValueChange, invalid, className, ...props }, ref) => <input {...props} ref={ref} type="week" className={cn("sia-week-picker", invalid && "sia-picker--invalid", className)} aria-invalid={invalid || undefined} onChange={(event) => onValueChange?.(event.target.value)} />);
|
|
5
12
|
WeekPicker.displayName = "WeekPicker";
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import { cn } from "@sia-ui/utils";
|
|
2
2
|
export interface YearPickerProps { value?: number; defaultValue?: number; min?: number; max?: number; onValueChange?: (year: number) => void; className?: string; }
|
|
3
|
+
/**
|
|
4
|
+
* Une année, choisie dans une plage.
|
|
5
|
+
*
|
|
6
|
+
* Les bornes sont déclarées parce qu'elles dépendent du sujet : une année
|
|
7
|
+
* d'exercice comptable et une année de naissance n'ouvrent pas la même
|
|
8
|
+
* liste.
|
|
9
|
+
*/
|
|
3
10
|
export function YearPicker({ value, defaultValue = new Date().getFullYear(), min = 1970, max = new Date().getFullYear() + 20, onValueChange, className }: YearPickerProps) { const years = Array.from({ length: Math.max(0, max - min + 1) }, (_, index) => max - index); return <select className={cn("sia-year-picker", className)} value={value} defaultValue={value === undefined ? defaultValue : undefined} onChange={(event) => onValueChange?.(Number(event.target.value))}>{years.map((year) => <option key={year} value={year}>{year}</option>)}</select>; }
|
|
@@ -60,17 +60,24 @@
|
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"name": "app-shell",
|
|
63
|
-
"description": "
|
|
63
|
+
"description": "Application shell: three sidebar states, permission filtering, router-agnostic active route, and mobile drawer or bottom tabs.",
|
|
64
64
|
"type": "pattern",
|
|
65
|
-
"version": "0.
|
|
65
|
+
"version": "0.2.0",
|
|
66
66
|
"status": "stable",
|
|
67
67
|
"source": "package",
|
|
68
68
|
"files": [
|
|
69
69
|
"components/AppShell/index.tsx",
|
|
70
|
+
"components/AppShell/tab-bar.tsx",
|
|
70
71
|
"components/AppShell/styles.css"
|
|
71
72
|
],
|
|
72
73
|
"dependencies": [
|
|
74
|
+
"@sia-ui/headless",
|
|
75
|
+
"@sia-ui/react",
|
|
73
76
|
"@sia-ui/utils"
|
|
77
|
+
],
|
|
78
|
+
"registryDependencies": [
|
|
79
|
+
"drawer",
|
|
80
|
+
"sidebar"
|
|
74
81
|
]
|
|
75
82
|
},
|
|
76
83
|
{
|
|
@@ -155,9 +162,9 @@
|
|
|
155
162
|
},
|
|
156
163
|
{
|
|
157
164
|
"name": "avatar",
|
|
158
|
-
"description": "
|
|
165
|
+
"description": "Avatar and stacked group: name-derived tint, initials, presence, three shapes and variants.",
|
|
159
166
|
"type": "primitive",
|
|
160
|
-
"version": "0.
|
|
167
|
+
"version": "0.2.0",
|
|
161
168
|
"status": "stable",
|
|
162
169
|
"source": "package",
|
|
163
170
|
"files": [
|
|
@@ -374,22 +381,30 @@
|
|
|
374
381
|
},
|
|
375
382
|
{
|
|
376
383
|
"name": "crud-page",
|
|
377
|
-
"description": "
|
|
384
|
+
"description": "List page for a declared resource: create, view, edit and delete open their own dialogs, each removable and permission-aware.",
|
|
378
385
|
"type": "pattern",
|
|
379
|
-
"version": "0.
|
|
386
|
+
"version": "0.2.0",
|
|
380
387
|
"status": "stable",
|
|
381
388
|
"source": "package",
|
|
382
389
|
"files": [
|
|
383
390
|
"components/CrudPage/index.tsx",
|
|
391
|
+
"components/CrudPage/dialogs.tsx",
|
|
384
392
|
"components/CrudPage/styles.css"
|
|
385
393
|
],
|
|
386
394
|
"dependencies": [
|
|
395
|
+
"@sia-ui/headless",
|
|
387
396
|
"@sia-ui/utils"
|
|
388
397
|
],
|
|
389
398
|
"registryDependencies": [
|
|
390
|
-
"
|
|
399
|
+
"button",
|
|
400
|
+
"data-table",
|
|
401
|
+
"descriptions",
|
|
402
|
+
"form",
|
|
403
|
+
"icons",
|
|
404
|
+
"modal",
|
|
391
405
|
"page-header",
|
|
392
|
-
"pagination"
|
|
406
|
+
"pagination",
|
|
407
|
+
"resource"
|
|
393
408
|
]
|
|
394
409
|
},
|
|
395
410
|
{
|
|
@@ -409,21 +424,35 @@
|
|
|
409
424
|
},
|
|
410
425
|
{
|
|
411
426
|
"name": "data-table",
|
|
412
|
-
"description": "
|
|
427
|
+
"description": "Data table: row actions with permissions and confirmation, selection, card view, density and sorting.",
|
|
413
428
|
"type": "component",
|
|
414
|
-
"version": "0.
|
|
429
|
+
"version": "0.2.0",
|
|
415
430
|
"status": "stable",
|
|
416
431
|
"source": "package",
|
|
417
432
|
"files": [
|
|
418
433
|
"components/DataTable/index.tsx",
|
|
434
|
+
"components/DataTable/types.ts",
|
|
435
|
+
"components/DataTable/values.ts",
|
|
436
|
+
"components/DataTable/row-actions.tsx",
|
|
437
|
+
"components/DataTable/query.ts",
|
|
419
438
|
"components/DataTable/styles.css"
|
|
420
439
|
],
|
|
421
440
|
"dependencies": [
|
|
422
441
|
"@sia-ui/headless",
|
|
442
|
+
"@sia-ui/react",
|
|
423
443
|
"@sia-ui/utils"
|
|
424
444
|
],
|
|
425
445
|
"registryDependencies": [
|
|
426
|
-
"
|
|
446
|
+
"button",
|
|
447
|
+
"checkbox",
|
|
448
|
+
"confirm-dialog",
|
|
449
|
+
"dropdown-menu",
|
|
450
|
+
"empty-state",
|
|
451
|
+
"error-state",
|
|
452
|
+
"icon-button",
|
|
453
|
+
"icons",
|
|
454
|
+
"search-input",
|
|
455
|
+
"skeleton"
|
|
427
456
|
]
|
|
428
457
|
},
|
|
429
458
|
{
|
|
@@ -1276,6 +1305,30 @@
|
|
|
1276
1305
|
"@sia-ui/utils"
|
|
1277
1306
|
]
|
|
1278
1307
|
},
|
|
1308
|
+
{
|
|
1309
|
+
"name": "resource",
|
|
1310
|
+
"description": "Declare a resource once: table columns, form fields, detail view, validation, search keys and permissions are all derived from it.",
|
|
1311
|
+
"type": "pattern",
|
|
1312
|
+
"version": "0.2.0",
|
|
1313
|
+
"status": "stable",
|
|
1314
|
+
"source": "package",
|
|
1315
|
+
"files": [
|
|
1316
|
+
"components/Resource/index.tsx"
|
|
1317
|
+
],
|
|
1318
|
+
"dependencies": [
|
|
1319
|
+
"@sia-ui/headless",
|
|
1320
|
+
"@sia-ui/tokens",
|
|
1321
|
+
"@sia-ui/utils"
|
|
1322
|
+
],
|
|
1323
|
+
"registryDependencies": [
|
|
1324
|
+
"amount-display",
|
|
1325
|
+
"badge",
|
|
1326
|
+
"data-table",
|
|
1327
|
+
"field",
|
|
1328
|
+
"form",
|
|
1329
|
+
"select"
|
|
1330
|
+
]
|
|
1331
|
+
},
|
|
1279
1332
|
{
|
|
1280
1333
|
"name": "rich-text-editor",
|
|
1281
1334
|
"description": "Lightweight rich text input with basic formatting commands.",
|
|
@@ -1347,9 +1400,9 @@
|
|
|
1347
1400
|
},
|
|
1348
1401
|
{
|
|
1349
1402
|
"name": "sidebar",
|
|
1350
|
-
"description": "
|
|
1403
|
+
"description": "Application navigation: unlimited depth, permission filtering, and a rail whose sub-menus open in a flyout.",
|
|
1351
1404
|
"type": "pattern",
|
|
1352
|
-
"version": "0.
|
|
1405
|
+
"version": "0.2.0",
|
|
1353
1406
|
"status": "stable",
|
|
1354
1407
|
"source": "package",
|
|
1355
1408
|
"files": [
|
|
@@ -1358,10 +1411,12 @@
|
|
|
1358
1411
|
],
|
|
1359
1412
|
"dependencies": [
|
|
1360
1413
|
"@sia-ui/headless",
|
|
1414
|
+
"@sia-ui/tokens",
|
|
1361
1415
|
"@sia-ui/utils"
|
|
1362
1416
|
],
|
|
1363
1417
|
"registryDependencies": [
|
|
1364
|
-
"icons"
|
|
1418
|
+
"icons",
|
|
1419
|
+
"overlay"
|
|
1365
1420
|
]
|
|
1366
1421
|
},
|
|
1367
1422
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sia-ui/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Install SIA UI components progressively into an application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sia-ui",
|
|
@@ -32,6 +32,18 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.0.3",
|
|
37
|
+
"@types/react": "^19.2.7",
|
|
38
|
+
"react": "^19.2.3",
|
|
39
|
+
"react-dom": "^19.2.3",
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vitest": "^4.0.16",
|
|
42
|
+
"@sia-ui/tokens": "0.3.0",
|
|
43
|
+
"@sia-ui/react": "0.3.0",
|
|
44
|
+
"@sia-ui/headless": "0.3.0",
|
|
45
|
+
"@sia-ui/utils": "0.3.0"
|
|
46
|
+
},
|
|
35
47
|
"scripts": {
|
|
36
48
|
"build": "node scripts/build.mjs",
|
|
37
49
|
"dev": "pnpm build && node dist/index.js",
|
|
@@ -39,17 +51,5 @@
|
|
|
39
51
|
"test": "vitest run",
|
|
40
52
|
"lint": "eslint src && node --check src/index.mjs",
|
|
41
53
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@sia-ui/headless": "workspace:*",
|
|
45
|
-
"@sia-ui/react": "workspace:*",
|
|
46
|
-
"@sia-ui/tokens": "workspace:*",
|
|
47
|
-
"@sia-ui/utils": "workspace:*",
|
|
48
|
-
"@types/node": "^25.0.3",
|
|
49
|
-
"@types/react": "^19.2.7",
|
|
50
|
-
"react": "^19.2.3",
|
|
51
|
-
"react-dom": "^19.2.3",
|
|
52
|
-
"typescript": "^5.9.3",
|
|
53
|
-
"vitest": "^4.0.16"
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|