@minimalstuff/ui 5.0.1 → 5.2.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/README.md +21 -0
- package/dist/index.d.ts +144 -12
- package/dist/minimalstuff-ui.css +1 -1
- package/dist/minimalstuff-ui.js +1660 -1525
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,10 +42,31 @@ export function Root() {
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
For French, pass the bundled preset: `<UIProvider labels={FRENCH_UI_LABELS}>` (import `FRENCH_UI_LABELS` from `@minimalstuff/ui`).
|
|
46
|
+
|
|
45
47
|
### Dark mode
|
|
46
48
|
|
|
47
49
|
Dark styles apply when `<html>` has the `dark` class. `useThemeStore` (and the `ThemeToggle` built on it) sets that class from the `light`, `dark` or `system` preference and keeps it in `localStorage`.
|
|
48
50
|
|
|
51
|
+
To avoid a light flash before your bundle runs, inline `THEME_INIT_SCRIPT` in `<head>`, before the stylesheet. It reads the same `localStorage` key as the store and sets the `dark` class the same way.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { THEME_INIT_SCRIPT } from '@minimalstuff/ui';
|
|
55
|
+
|
|
56
|
+
<head>
|
|
57
|
+
<script dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
|
|
58
|
+
<link rel="stylesheet" href="/style.css" />
|
|
59
|
+
</head>;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
With a strict Content-Security-Policy, either add the script's SHA-256 hash to `script-src` (`'sha256-<hash>'`), or write `THEME_INIT_SCRIPT` to a file at build time and load it with `<script src>`. Compute the hash with:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
node --input-type=module -e "import { THEME_INIT_SCRIPT } from '@minimalstuff/ui'; import { createHash } from 'node:crypto'; console.log(createHash('sha256').update(THEME_INIT_SCRIPT).digest('base64'))"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The hash changes if a release changes the script: recompute it when you upgrade.
|
|
69
|
+
|
|
49
70
|
### Modals and drawers lock page scroll
|
|
50
71
|
|
|
51
72
|
While a `Modal` or `Drawer` is open, page scroll is locked on `<html>`. The scrollbar width this frees is compensated with right padding on `<html>`, so the page does not shift. That padding does not move `position: fixed` elements: anchor right-aligned ones with `right: var(--scrollbar-compensation, 0px)`. Paint the page background on `<html>` or `<body>` rather than only on a wrapper element, or the padding shows the root's background as a strip on the right.
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,27 @@ import { RefObject } from 'react';
|
|
|
13
13
|
import { StoreApi } from 'zustand';
|
|
14
14
|
import { UseBoundStore } from 'zustand';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A thin application bar with a `start` slot on the left, a centered `title`
|
|
18
|
+
* and an `end` slot on the right. It is a plain `<header>` (the `banner`
|
|
19
|
+
* landmark when it is not nested in another sectioning element), not a
|
|
20
|
+
* WAI-ARIA toolbar: put your own links and buttons in the slots, or use
|
|
21
|
+
* `Toolbar` for a group of controls with arrow-key navigation. Its minimum
|
|
22
|
+
* height grows with enlarged text and its horizontal padding honours the
|
|
23
|
+
* device safe area.
|
|
24
|
+
*/
|
|
25
|
+
export declare function AppBar(appBarProps: Readonly<AppBarProps>): JSX.Element;
|
|
26
|
+
|
|
27
|
+
export declare type AppBarProps = Omit<ComponentPropsWithRef<'header'>, 'title'> & {
|
|
28
|
+
start?: ReactNode;
|
|
29
|
+
title?: ReactNode;
|
|
30
|
+
end?: ReactNode;
|
|
31
|
+
startClassName?: string;
|
|
32
|
+
titleClassName?: string;
|
|
33
|
+
endClassName?: string;
|
|
34
|
+
unstyled?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
16
37
|
/**
|
|
17
38
|
* The picture of a person, or the initial of `name` when there is none.
|
|
18
39
|
* Labelled with the full `name` so screen readers announce the person rather
|
|
@@ -64,6 +85,31 @@ export declare const BASE_INPUT_STYLES = "w-full border border-gray-300 dark:bor
|
|
|
64
85
|
|
|
65
86
|
export declare function Button(buttonProps: Readonly<ButtonProps>): JSX.Element;
|
|
66
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The exact class string `Button` renders, for styling another element as a
|
|
90
|
+
* button, typically a router `<Link>` or an `<a>`. Keyboard focus ring included.
|
|
91
|
+
* For a selected link, add `pressed` and `aria-current`, not `aria-pressed`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function buttonClassName(options?: Readonly<ButtonClassNameOptions>): string;
|
|
94
|
+
|
|
95
|
+
export declare type ButtonClassNameOptions = {
|
|
96
|
+
variant?: ButtonVariant;
|
|
97
|
+
color?: ButtonColor;
|
|
98
|
+
size?: ControlSize;
|
|
99
|
+
radius?: Radius;
|
|
100
|
+
fullWidth?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Applies the pressed look, for a real `<button>` toggle paired with
|
|
103
|
+
* `aria-pressed`. Never put `aria-pressed` on a link (not allowed on role
|
|
104
|
+
* link): on an `<a>`, pair the selected look with `aria-current`, for
|
|
105
|
+
* example `aria-current="page"`.
|
|
106
|
+
*/
|
|
107
|
+
pressed?: boolean;
|
|
108
|
+
/** Keeps only the layout classes, like `Button`'s `unstyled` prop. */
|
|
109
|
+
unstyled?: boolean;
|
|
110
|
+
className?: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
67
113
|
declare type ButtonColor =
|
|
68
114
|
| 'primary'
|
|
69
115
|
| 'neutral'
|
|
@@ -85,6 +131,11 @@ export declare type ButtonProps = Omit<ComponentPropsWithRef<'button'>, 'childre
|
|
|
85
131
|
* the tab order, exposed as `aria-disabled`.
|
|
86
132
|
*/
|
|
87
133
|
loading?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Turns the button into a toggle: sets `aria-pressed` and a pressed look (dark fill and inset ring).
|
|
136
|
+
* Leave it `undefined` for a regular button (no attribute).
|
|
137
|
+
*/
|
|
138
|
+
pressed?: boolean;
|
|
88
139
|
startIcon?: string;
|
|
89
140
|
endIcon?: string;
|
|
90
141
|
} & ButtonStyle;
|
|
@@ -153,7 +204,7 @@ export declare type CheckboxOption = ChoiceOption;
|
|
|
153
204
|
export declare function CheckboxOptions({ options, orientation, values, defaultValues, onChange, name: nameProp, label, error, required, requiredLabel, disabled, radius, unstyled, className, wrapperClassName, id, 'aria-describedby': callerDescribedBy, ...props }: Readonly<CheckboxOptionsProps>): JSX.Element;
|
|
154
205
|
|
|
155
206
|
export declare interface CheckboxOptionsProps extends Omit<ComponentPropsWithRef<'fieldset'>, 'onChange'> {
|
|
156
|
-
options: string[] | CheckboxOption[];
|
|
207
|
+
options: readonly string[] | readonly CheckboxOption[];
|
|
157
208
|
orientation?: 'vertical' | 'horizontal';
|
|
158
209
|
values?: readonly string[];
|
|
159
210
|
defaultValues?: readonly string[];
|
|
@@ -193,13 +244,13 @@ export declare interface ChoiceOption {
|
|
|
193
244
|
disabled?: boolean;
|
|
194
245
|
}
|
|
195
246
|
|
|
196
|
-
export declare function ClientOnly({ children, fallback }: Readonly<ClientOnlyProps>): string | number | bigint | boolean |
|
|
247
|
+
export declare function ClientOnly({ children, fallback }: Readonly<ClientOnlyProps>): string | number | bigint | boolean | Iterable< ReactNode> | Promise<string | number | bigint | boolean | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable< ReactNode> | null | undefined> | JSX.Element | null;
|
|
197
248
|
|
|
198
249
|
export declare interface ClientOnlyProps extends React.PropsWithChildren {
|
|
199
250
|
fallback?: React.ReactNode;
|
|
200
251
|
}
|
|
201
252
|
|
|
202
|
-
export declare function Combobox({ options, label, description, 'aria-label': ariaLabel, error, placeholder, noResultsText, clearLabel, radius, unstyled, disabled, required, className, wrapperClassName, value, defaultValue, onChange, id, }: Readonly<ComboboxProps>): JSX.Element;
|
|
253
|
+
export declare function Combobox({ options, filterOption, label, description, 'aria-label': ariaLabel, error, placeholder, noResultsText, clearLabel, radius, unstyled, disabled, required, className, wrapperClassName, value, defaultValue, onChange, id, }: Readonly<ComboboxProps>): JSX.Element;
|
|
203
254
|
|
|
204
255
|
export declare interface ComboboxOption {
|
|
205
256
|
value: string;
|
|
@@ -207,7 +258,13 @@ export declare interface ComboboxOption {
|
|
|
207
258
|
}
|
|
208
259
|
|
|
209
260
|
export declare interface ComboboxProps {
|
|
210
|
-
options: ComboboxOption[];
|
|
261
|
+
options: readonly ComboboxOption[];
|
|
262
|
+
/**
|
|
263
|
+
* Decides whether an option matches the text typed in the field. Replaces
|
|
264
|
+
* the default case-insensitive "label contains the query" match, for
|
|
265
|
+
* example to ignore accents.
|
|
266
|
+
*/
|
|
267
|
+
filterOption?: (option: ComboboxOption, query: string) => boolean;
|
|
211
268
|
label?: string | ReactNode;
|
|
212
269
|
description?: string | ReactNode;
|
|
213
270
|
'aria-label'?: string;
|
|
@@ -289,14 +346,20 @@ export declare interface ContextMenuProps {
|
|
|
289
346
|
/** The size scale every inline control shares (Button, IconButton, Badge, Kbd). */
|
|
290
347
|
declare type ControlSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
291
348
|
|
|
292
|
-
export declare function CopyButton({ value, children, onError, }: Readonly<CopyButtonProps>): JSX.Element;
|
|
349
|
+
export declare function CopyButton({ value, children, label, onError, }: Readonly<CopyButtonProps>): JSX.Element;
|
|
293
350
|
|
|
294
351
|
export declare interface CopyButtonProps {
|
|
295
352
|
value: string;
|
|
296
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Render prop for a custom control. Omit it to get the default icon button,
|
|
355
|
+
* which swaps its copy icon for a check while `copied`.
|
|
356
|
+
*/
|
|
357
|
+
children?: (props: {
|
|
297
358
|
copied: boolean;
|
|
298
359
|
copy: () => void | Promise<void>;
|
|
299
360
|
}) => ReactNode;
|
|
361
|
+
/** Accessible name of the default icon button. Defaults to `labels.copy`. */
|
|
362
|
+
label?: string;
|
|
300
363
|
onError?: (error: unknown) => void;
|
|
301
364
|
}
|
|
302
365
|
|
|
@@ -307,8 +370,24 @@ export declare interface CopyButtonProps {
|
|
|
307
370
|
*/
|
|
308
371
|
export declare type DismissGuard = () => boolean | Promise<boolean>;
|
|
309
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Asks the topmost open dialog (`Modal`, `ConfirmModal`, `Drawer`) to close,
|
|
375
|
+
* exactly as an Escape press would: dismiss guards are honoured, and a
|
|
376
|
+
* dialog with `dismissible={false}` stays open. Only the topmost dialog is
|
|
377
|
+
* affected, and a dialog already closing does not count.
|
|
378
|
+
*
|
|
379
|
+
* Call it first from a hardware back button handler (for example Capacitor's
|
|
380
|
+
* `backButton` listener on Android) and navigate back only when it returns
|
|
381
|
+
* `false`.
|
|
382
|
+
*
|
|
383
|
+
* @returns `true` when a dialog was open and the press was consumed (even if
|
|
384
|
+
* it stayed open because it is not dismissible or a guard refused), `false`
|
|
385
|
+
* when no dialog is open.
|
|
386
|
+
*/
|
|
387
|
+
export declare function dismissTopmostModal(): boolean;
|
|
388
|
+
|
|
310
389
|
/** A modal side panel with the same focus handling, stacking and footer slot as `Modal`. */
|
|
311
|
-
export declare function Drawer({ isOpen, onClose, size, side, radius, dismissible, ...dialogProps }: Readonly<DrawerProps>): JSX.Element | null;
|
|
390
|
+
export declare function Drawer({ isOpen, onClose, size, side, radius, dismissible, onExited, ...dialogProps }: Readonly<DrawerProps>): JSX.Element | null;
|
|
312
391
|
|
|
313
392
|
export declare interface DrawerProps {
|
|
314
393
|
isOpen: boolean;
|
|
@@ -334,6 +413,8 @@ export declare interface DrawerProps {
|
|
|
334
413
|
/** `false` hides the close button and blocks Escape and backdrop dismissal. */
|
|
335
414
|
dismissible?: boolean;
|
|
336
415
|
closeLabel?: string;
|
|
416
|
+
/** Called once per close, when the exit has finished and the panel is unmounted. Not called when the drawer reopens during the exit, nor on an initial render with `isOpen={false}`. */
|
|
417
|
+
onExited?: () => void;
|
|
337
418
|
/** Wins over the first-tabbable rule for where focus lands on open; may point at an element in the body or in the footer. */
|
|
338
419
|
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
339
420
|
}
|
|
@@ -342,6 +423,9 @@ export declare type DrawerSide = 'right' | 'left';
|
|
|
342
423
|
|
|
343
424
|
export declare type DrawerSize = 'sm' | 'md' | 'lg';
|
|
344
425
|
|
|
426
|
+
/** French translation of every `UILabels` entry, ready for `<UIProvider labels={FRENCH_UI_LABELS}>`. */
|
|
427
|
+
export declare const FRENCH_UI_LABELS: UILabels;
|
|
428
|
+
|
|
345
429
|
declare interface GlobalHotkeysStore {
|
|
346
430
|
globalHotkeysEnabled: boolean;
|
|
347
431
|
setGlobalHotkeysEnabled: (value: boolean) => void;
|
|
@@ -383,6 +467,11 @@ export declare type IconButtonProps = ComponentPropsWithRef<'button'> & {
|
|
|
383
467
|
size?: ControlSize;
|
|
384
468
|
children?: ReactNode;
|
|
385
469
|
tooltip?: IconButtonTooltip;
|
|
470
|
+
/**
|
|
471
|
+
* Turns the button into a toggle: sets `aria-pressed` and a pressed look (dark fill and inset ring).
|
|
472
|
+
* Leave it `undefined` for a regular button (no attribute).
|
|
473
|
+
*/
|
|
474
|
+
pressed?: boolean;
|
|
386
475
|
} & IconButtonStyle;
|
|
387
476
|
|
|
388
477
|
declare type IconButtonStyle = {
|
|
@@ -670,7 +759,7 @@ export declare type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
|
670
759
|
* pills opens a searchable dropdown of checkbox-style options. Selecting an
|
|
671
760
|
* option never closes the dropdown, unlike the single-select `Combobox`.
|
|
672
761
|
*/
|
|
673
|
-
export declare function MultiCombobox({ options, label, description, 'aria-label': ariaLabel, error, placeholder, searchPlaceholder, noResultsText, clearLabel, clearText, maxSelectedValues, isOptionDisabled, maxDisplayedValues, formatOverflowCount, formatSelectedCount, formatMaxSelectedHint, radius, unstyled, disabled, required, className, wrapperClassName, values, defaultValues, onChange, onDropdownClose, id, }: Readonly<MultiComboboxProps>): ReactElement;
|
|
762
|
+
export declare function MultiCombobox({ options, filterOption, label, description, 'aria-label': ariaLabel, error, placeholder, searchPlaceholder, noResultsText, clearLabel, clearText, maxSelectedValues, isOptionDisabled, maxDisplayedValues, formatOverflowCount, formatSelectedCount, formatMaxSelectedHint, radius, unstyled, disabled, required, className, wrapperClassName, values, defaultValues, onChange, onDropdownClose, id, }: Readonly<MultiComboboxProps>): ReactElement;
|
|
674
763
|
|
|
675
764
|
export declare interface MultiComboboxOption {
|
|
676
765
|
value: string;
|
|
@@ -679,6 +768,12 @@ export declare interface MultiComboboxOption {
|
|
|
679
768
|
|
|
680
769
|
export declare interface MultiComboboxProps {
|
|
681
770
|
options: readonly MultiComboboxOption[];
|
|
771
|
+
/**
|
|
772
|
+
* Decides whether an option matches the text typed in the search field.
|
|
773
|
+
* Replaces the default case-insensitive "label contains the query" match,
|
|
774
|
+
* for example to ignore accents.
|
|
775
|
+
*/
|
|
776
|
+
filterOption?: (option: MultiComboboxOption, query: string) => boolean;
|
|
682
777
|
label?: string | ReactNode;
|
|
683
778
|
description?: string | ReactNode;
|
|
684
779
|
'aria-label'?: string;
|
|
@@ -718,7 +813,7 @@ export declare type RadioOption = ChoiceOption;
|
|
|
718
813
|
export declare function RadioOptions({ options, orientation, value, defaultValue, onChange, name: nameProp, label, error, required, disabled, radius, unstyled, className, wrapperClassName, id, 'aria-describedby': callerDescribedBy, ...props }: Readonly<RadioOptionsProps>): JSX.Element;
|
|
719
814
|
|
|
720
815
|
export declare interface RadioOptionsProps extends Omit<ComponentPropsWithRef<'fieldset'>, 'onChange'> {
|
|
721
|
-
options: string[] | RadioOption[];
|
|
816
|
+
options: readonly string[] | readonly RadioOption[];
|
|
722
817
|
orientation?: 'vertical' | 'horizontal';
|
|
723
818
|
value?: string;
|
|
724
819
|
defaultValue?: string;
|
|
@@ -744,7 +839,7 @@ export declare interface SelectOption {
|
|
|
744
839
|
}
|
|
745
840
|
|
|
746
841
|
export declare interface SelectProps extends Omit<ComponentPropsWithRef<'select'>, 'children'> {
|
|
747
|
-
options: SelectOption[];
|
|
842
|
+
options: readonly SelectOption[];
|
|
748
843
|
label?: string | ReactNode;
|
|
749
844
|
description?: string | ReactNode;
|
|
750
845
|
error?: string;
|
|
@@ -755,6 +850,28 @@ export declare interface SelectProps extends Omit<ComponentPropsWithRef<'select'
|
|
|
755
850
|
wrapperClassName?: string;
|
|
756
851
|
}
|
|
757
852
|
|
|
853
|
+
/**
|
|
854
|
+
* A loading indicator exposed as a `role="status"` live region. It uses the
|
|
855
|
+
* icon `Button` shows while loading; under `prefers-reduced-motion` it swaps
|
|
856
|
+
* to a static icon.
|
|
857
|
+
*
|
|
858
|
+
* The region is empty on first render and filled right after mount, because
|
|
859
|
+
* many screen readers ignore a live region inserted already filled. The most
|
|
860
|
+
* robust setup is still a persistent `role="status"` wrapper in your own
|
|
861
|
+
* markup that stays mounted while a `Spinner` appears and disappears inside it.
|
|
862
|
+
*/
|
|
863
|
+
export declare function Spinner(spinnerProps: Readonly<SpinnerProps>): JSX.Element;
|
|
864
|
+
|
|
865
|
+
export declare type SpinnerProps = Omit<ComponentPropsWithRef<'span'>, 'children'> & {
|
|
866
|
+
size?: ControlSize;
|
|
867
|
+
/** Visible text next to the icon. Without it, `labels.loading` is announced instead. */
|
|
868
|
+
label?: string;
|
|
869
|
+
/** Strips the built-in layout, text size and icon size; size the icon with `iconClassName`. */
|
|
870
|
+
unstyled?: boolean;
|
|
871
|
+
iconClassName?: string;
|
|
872
|
+
labelClassName?: string;
|
|
873
|
+
};
|
|
874
|
+
|
|
758
875
|
/**
|
|
759
876
|
* A primary action joined to a chevron `Menu` trigger that offers secondary
|
|
760
877
|
* variants of that action. Both halves share variant, color, size and radius:
|
|
@@ -869,7 +986,7 @@ export declare function Tabs({ items, value, defaultIndex, variant, orientation,
|
|
|
869
986
|
export declare type TabsOrientation = 'horizontal' | 'vertical';
|
|
870
987
|
|
|
871
988
|
export declare interface TabsProps {
|
|
872
|
-
items: TabItem[];
|
|
989
|
+
items: readonly TabItem[];
|
|
873
990
|
value?: number;
|
|
874
991
|
defaultIndex?: number;
|
|
875
992
|
variant?: TabsVariant;
|
|
@@ -930,6 +1047,15 @@ export declare interface TextareaProps extends ComponentPropsWithRef<'textarea'>
|
|
|
930
1047
|
|
|
931
1048
|
export declare type Theme = 'light' | 'dark' | 'system';
|
|
932
1049
|
|
|
1050
|
+
/**
|
|
1051
|
+
* A self-contained script that applies the saved theme to `<html>` before the
|
|
1052
|
+
* first paint, so a dark-mode visitor never sees a light flash. It mirrors
|
|
1053
|
+
* `useThemeStore`: the `dark` class is set for a stored `dark`, or for a
|
|
1054
|
+
* stored `system` (or a missing, invalid or unreadable value) while the OS
|
|
1055
|
+
* prefers dark. Inline it in `<head>` before the stylesheet.
|
|
1056
|
+
*/
|
|
1057
|
+
export declare const THEME_INIT_SCRIPT: string;
|
|
1058
|
+
|
|
933
1059
|
declare interface ThemeStore {
|
|
934
1060
|
theme: Theme;
|
|
935
1061
|
setTheme: (theme: Theme) => void;
|
|
@@ -1060,8 +1186,14 @@ export declare interface UILabels {
|
|
|
1060
1186
|
required: string;
|
|
1061
1187
|
/** Screen-reader announcement of `CopyButton` after a successful copy. */
|
|
1062
1188
|
copied: string;
|
|
1063
|
-
/** Screen-reader text added to a `Button` while it is loading. */
|
|
1189
|
+
/** Screen-reader text added to a `Button` while it is loading, and announced by `Spinner` without a visible label. */
|
|
1064
1190
|
loading: string;
|
|
1191
|
+
/**
|
|
1192
|
+
* Accessible name of the default `CopyButton` icon button. Optional so that
|
|
1193
|
+
* existing complete label objects keep compiling: a translated label set
|
|
1194
|
+
* must add it, otherwise the English "Copy" is used.
|
|
1195
|
+
*/
|
|
1196
|
+
copy?: string;
|
|
1065
1197
|
/** Screen-reader announcement of `CharacterCount` near and at the limit. */
|
|
1066
1198
|
charactersRemaining: (remainingCount: number) => string;
|
|
1067
1199
|
/** Accessible name of `ThemeToggle` for the current theme. */
|