@eifi1/ui-kit 0.9.0 → 0.10.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 -11
- package/dist/components/alert-banner.d.ts +32 -5
- package/dist/components/alert-banner.js +57 -14
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +4 -0
- package/dist/components/breadcrumbs.d.ts +60 -0
- package/dist/components/breadcrumbs.js +88 -0
- package/dist/components/breadcrumbs.js.map +1 -0
- package/dist/components/bulk-action-bar.d.ts +66 -0
- package/dist/components/bulk-action-bar.js +149 -0
- package/dist/components/bulk-action-bar.js.map +1 -0
- package/dist/components/calculator.d.ts +4 -0
- package/dist/components/chip.d.ts +31 -5
- package/dist/components/chip.js +85 -5
- package/dist/components/chip.js.map +1 -1
- package/dist/components/copy-button.d.ts +13 -4
- package/dist/components/copy-button.js +17 -4
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/date-picker.d.ts +22 -2
- package/dist/components/date-picker.js +11 -3
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +43 -6
- package/dist/components/description-list.js +91 -11
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +7 -0
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/disclosure.d.ts +57 -2
- package/dist/components/disclosure.js +24 -12
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +3 -4
- package/dist/components/file-dropzone.js +2 -1
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +23 -4
- package/dist/components/full-bleed-dialog.js +11 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/list.d.ts +174 -0
- package/dist/components/list.js +215 -0
- package/dist/components/list.js.map +1 -0
- package/dist/components/menu-item.d.ts +99 -0
- package/dist/components/menu-item.js +83 -0
- package/dist/components/menu-item.js.map +1 -0
- package/dist/components/modal.d.ts +23 -1
- package/dist/components/modal.js +36 -10
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-field.d.ts +4 -0
- package/dist/components/number-input.d.ts +4 -0
- package/dist/components/numpad-sheet.d.ts +4 -0
- package/dist/components/page-contents.js +1 -2
- package/dist/components/page-contents.js.map +1 -1
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +36 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/series-chart-labels.d.ts +3 -0
- package/dist/components/series-chart-labels.js +2 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +16 -0
- package/dist/components/series-chart.js +137 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/status-dot.d.ts +49 -0
- package/dist/components/status-dot.js +52 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/table.d.ts +39 -6
- package/dist/components/table.js +43 -4
- package/dist/components/table.js.map +1 -1
- package/dist/components/text.d.ts +63 -0
- package/dist/components/text.js +23 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/toast.d.ts +167 -0
- package/dist/components/toast.js +226 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toggle-group.d.ts +29 -1
- package/dist/components/toggle-group.js +52 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/tooltip.d.ts +38 -15
- package/dist/components/tooltip.js +85 -47
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +104 -4
- package/dist/components/ui.js +112 -8
- package/dist/components/ui.js.map +1 -1
- package/dist/hooks/use-close-transition.d.ts +7 -1
- package/dist/hooks/use-close-transition.js +5 -2
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.js +1 -1
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -1
- package/dist/i18n/defaults.d.ts +4 -0
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +8 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +4 -0
- package/dist/i18n/locales/de-CH.d.ts +4 -0
- package/dist/i18n/locales/de-informal.d.ts +4 -0
- package/dist/i18n/locales/de.d.ts +4 -0
- package/dist/i18n/locales/de.js +21 -1
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +4 -0
- package/dist/i18n/locales/es.js +21 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +4 -0
- package/dist/i18n/locales/fr.js +21 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +4 -0
- package/dist/i18n/locales/hu.js +21 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +4 -0
- package/dist/i18n/locales/it.js +21 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +4 -0
- package/dist/i18n/locales/zh.js +21 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/clipping.d.ts +9 -0
- package/dist/lib/clipping.js +14 -0
- package/dist/lib/clipping.js.map +1 -0
- package/dist/search/command-palette.d.ts +19 -2
- package/dist/search/command-palette.js +16 -5
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.d.ts +26 -1
- package/dist/search/global-search.js +29 -13
- package/dist/search/global-search.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +1 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +2 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +1 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-summary.js +3 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/package.json +16 -12
- package/src/components/alert-banner.tsx +117 -18
- package/src/components/breadcrumbs.tsx +168 -0
- package/src/components/bulk-action-bar.tsx +256 -0
- package/src/components/chip.tsx +128 -7
- package/src/components/copy-button.tsx +29 -7
- package/src/components/date-picker.tsx +49 -1
- package/src/components/description-list.tsx +168 -15
- package/src/components/dialog-frame.tsx +7 -0
- package/src/components/disclosure.tsx +93 -21
- package/src/components/file-dropzone.tsx +9 -10
- package/src/components/full-bleed-dialog.tsx +42 -5
- package/src/components/list.tsx +417 -0
- package/src/components/menu-item.tsx +214 -0
- package/src/components/modal.tsx +80 -17
- package/src/components/page-contents.tsx +4 -4
- package/src/components/page-header.tsx +68 -0
- package/src/components/series-chart-labels.ts +4 -0
- package/src/components/series-chart.tsx +253 -3
- package/src/components/status-dot.tsx +99 -0
- package/src/components/table.tsx +91 -6
- package/src/components/text.tsx +89 -0
- package/src/components/toast.tsx +441 -0
- package/src/components/toggle-group.tsx +82 -3
- package/src/components/tooltip.tsx +186 -100
- package/src/components/ui.tsx +237 -8
- package/src/hooks/use-close-transition.ts +14 -5
- package/src/hooks/use-copy-to-clipboard.ts +1 -1
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/kit-labels.tsx +8 -0
- package/src/i18n/locales/de.ts +20 -0
- package/src/i18n/locales/es.ts +20 -0
- package/src/i18n/locales/fr.ts +20 -0
- package/src/i18n/locales/hu.ts +20 -0
- package/src/i18n/locales/it.ts +20 -0
- package/src/i18n/locales/zh.ts +20 -0
- package/src/index.ts +26 -0
- package/src/lib/clipping.ts +16 -0
- package/src/search/command-palette.tsx +42 -5
- package/src/search/global-search.tsx +59 -18
- package/src/wizard/stepper-nav.tsx +2 -1
- package/src/wizard/types.ts +2 -2
- package/src/wizard/use-wizard.ts +3 -3
- package/src/wizard/wizard-summary.tsx +14 -10
- package/tokens.css +97 -0
package/src/components/ui.tsx
CHANGED
|
@@ -57,6 +57,40 @@ const buttonVariantClasses: Record<ButtonVariant, string> = {
|
|
|
57
57
|
"rounded-sm p-0 bg-transparent text-[var(--brand)] underline-offset-4 hover:underline focus:ring-[var(--brand)]",
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The text colour of a `link` or `ghost` button, over the variant's own.
|
|
62
|
+
*
|
|
63
|
+
* `muted` is the QUIET link keksdose hand-rolls in six places (tours-page:321's "Mark
|
|
64
|
+
* undone", transaction-fields:176/215's "Add line" / "Fill total",
|
|
65
|
+
* invoice-lines-table:376, invoice-review:464): secondary text that darkens to the
|
|
66
|
+
* body colour under the pointer, for an action that must be findable but not compete
|
|
67
|
+
* with the brand-coloured one beside it. `danger` is the same quiet look turning
|
|
68
|
+
* `--danger` on hover — transaction-editor:401's "Remove split", which is a text
|
|
69
|
+
* action that destroys something.
|
|
70
|
+
*
|
|
71
|
+
* A `tone` rather than a `link-muted` variant, because it is the axis IconButton
|
|
72
|
+
* already has (`tone="muted"`, `tone="danger"`, same quiet-until-hover meaning), and
|
|
73
|
+
* because the same two looks are wanted on `ghost`. The filled and bordered variants
|
|
74
|
+
* carry their meaning in the box, not the text, so a tone on them does nothing.
|
|
75
|
+
*/
|
|
76
|
+
export type ButtonTone = "default" | "muted" | "danger";
|
|
77
|
+
|
|
78
|
+
const BUTTON_TONES: Record<Exclude<ButtonTone, "default">, string> = {
|
|
79
|
+
muted:
|
|
80
|
+
"text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:hover:text-[var(--text-secondary)]",
|
|
81
|
+
danger:
|
|
82
|
+
"text-[var(--text-secondary)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)] disabled:hover:text-[var(--text-secondary)]",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Only the two transparent variants take a tone; see {@link ButtonTone}. */
|
|
86
|
+
const TONED_VARIANTS = new Set<ButtonVariant>(["link", "ghost"]);
|
|
87
|
+
|
|
88
|
+
// `pressed` on a `link`: the brand colour and a heavier weight, which is exactly what
|
|
89
|
+
// lenkbank's "All speeds" toggle paints by hand (gear/hysteresis-charts.tsx:482). After
|
|
90
|
+
// the tone, so a pressed muted link is brand, and an unpressed one is quiet grey.
|
|
91
|
+
const BUTTON_LINK_PRESSED =
|
|
92
|
+
"font-medium text-[var(--brand)] hover:text-[var(--brand)] disabled:hover:text-[var(--brand)]";
|
|
93
|
+
|
|
60
94
|
/** The second argument of {@link buttonClasses} in its options form. */
|
|
61
95
|
export interface ButtonClassesOptions {
|
|
62
96
|
/** See {@link ButtonProps.size}. */
|
|
@@ -105,12 +139,75 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
105
139
|
* ordinary prop, so it rides `...rest` onto the element with no `forwardRef` —
|
|
106
140
|
* declared here only because `ButtonHTMLAttributes` does not carry it. */
|
|
107
141
|
ref?: Ref<HTMLButtonElement>;
|
|
142
|
+
/** Text colour for `link` and `ghost`: `muted` (quiet, body colour on hover) or
|
|
143
|
+
* `danger` (quiet, `--danger` on hover). Ignored by the other variants. See
|
|
144
|
+
* {@link ButtonTone}. */
|
|
145
|
+
tone?: ButtonTone;
|
|
146
|
+
/**
|
|
147
|
+
* Make it a toggle button, as {@link IconButton}'s `pressed` does. `true` sets
|
|
148
|
+
* `aria-pressed="true"` and draws the "on" look — on a `link`, the brand colour and a
|
|
149
|
+
* medium weight; on the boxed variants, the quiet brand fill IconButton uses — and
|
|
150
|
+
* `false` sets `aria-pressed="false"` with the ordinary look. Left out, no
|
|
151
|
+
* `aria-pressed` (or the caller's own). For lenkbank's "All speeds" text toggle
|
|
152
|
+
* (gear/hysteresis-charts.tsx:482), which is a hand-rolled `<button aria-pressed>`
|
|
153
|
+
* swapping two class strings; with `variant="link" tone="muted"` it is this.
|
|
154
|
+
*/
|
|
155
|
+
pressed?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Why the action is not available — the button's half of DangerConfirm's
|
|
158
|
+
* `lockedReason` (kastlan handover-detail-page.tsx:197 locks a signed handover).
|
|
159
|
+
*
|
|
160
|
+
* A disabled button that cannot say why is a dead end: the native `disabled` takes it
|
|
161
|
+
* out of the tab order, so a keyboard user never lands on it, and a pointer gets a
|
|
162
|
+
* `not-allowed` cursor and nothing else. With a reason the button is `aria-disabled`
|
|
163
|
+
* instead — still focusable, still hoverable — clicks (and the Enter/Space and form
|
|
164
|
+
* submission they stand for) do nothing, and the reason is shown in the kit
|
|
165
|
+
* {@link Tooltip} and attached with `aria-describedby`, so a screen reader hears it
|
|
166
|
+
* on focus. It wins over `disabled`: passing both keeps the button reachable, which is
|
|
167
|
+
* the point of giving a reason.
|
|
168
|
+
*
|
|
169
|
+
* The description is a `hidden` copy of the reason rather than the bubble itself: the
|
|
170
|
+
* bubble exists only while hovered or focused once it is portalled, and a description
|
|
171
|
+
* that comes and goes is read inconsistently. The bubble is kept visual, the way
|
|
172
|
+
* DangerConfirm keeps its own.
|
|
173
|
+
*/
|
|
174
|
+
disabledReason?: ReactNode;
|
|
108
175
|
}
|
|
109
176
|
|
|
110
|
-
|
|
111
|
-
return
|
|
177
|
+
function hasContent(node: ReactNode): boolean {
|
|
178
|
+
return node !== undefined && node !== null && node !== false && node !== "";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function Button({
|
|
182
|
+
variant = "primary",
|
|
183
|
+
size = "md",
|
|
184
|
+
stretch,
|
|
185
|
+
tone = "default",
|
|
186
|
+
pressed,
|
|
187
|
+
disabledReason,
|
|
188
|
+
className,
|
|
189
|
+
onClick,
|
|
190
|
+
...rest
|
|
191
|
+
}: ButtonProps) {
|
|
192
|
+
const reasonId = useId();
|
|
193
|
+
const locked = hasContent(disabledReason);
|
|
194
|
+
const ownDescribedBy = rest["aria-describedby"];
|
|
195
|
+
const button = (
|
|
112
196
|
<button
|
|
113
197
|
{...rest}
|
|
198
|
+
disabled={locked ? undefined : rest.disabled}
|
|
199
|
+
aria-disabled={locked || rest["aria-disabled"]}
|
|
200
|
+
aria-describedby={locked ? (ownDescribedBy ? `${ownDescribedBy} ${reasonId}` : reasonId) : ownDescribedBy}
|
|
201
|
+
aria-pressed={pressed ?? rest["aria-pressed"]}
|
|
202
|
+
onClick={(e) => {
|
|
203
|
+
// `preventDefault` as well as not calling through: a locked submit button must
|
|
204
|
+
// not submit its form, and the browser does that after this handler returns.
|
|
205
|
+
if (locked) {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
onClick?.(e);
|
|
210
|
+
}}
|
|
114
211
|
className={cn(
|
|
115
212
|
BUTTON_BASE,
|
|
116
213
|
BUTTON_SIZES[size],
|
|
@@ -119,10 +216,29 @@ export function Button({ variant = "primary", size = "md", stretch, className, .
|
|
|
119
216
|
// align-items). No effect outside a flex row / when it's already the tallest.
|
|
120
217
|
stretch && "self-stretch",
|
|
121
218
|
buttonVariantClasses[variant],
|
|
219
|
+
tone !== "default" && TONED_VARIANTS.has(variant) && BUTTON_TONES[tone],
|
|
220
|
+
pressed && (variant === "link" ? BUTTON_LINK_PRESSED : ICON_BUTTON_PRESSED),
|
|
221
|
+
// The disabled look for the focusable kind of disabled.
|
|
222
|
+
locked && "cursor-not-allowed opacity-50",
|
|
122
223
|
className,
|
|
123
224
|
)}
|
|
124
225
|
/>
|
|
125
226
|
);
|
|
227
|
+
if (!locked) return button;
|
|
228
|
+
return (
|
|
229
|
+
// The button in a FRAGMENT so Tooltip does not clone its bubble into the
|
|
230
|
+
// description as well — the hidden copy below already describes it, and the same
|
|
231
|
+
// sentence twice would be read on every focus. `stretch` moves to the wrapper,
|
|
232
|
+
// which is now the flex item.
|
|
233
|
+
<Tooltip label={disabledReason} className={stretch ? "self-stretch" : undefined}>
|
|
234
|
+
<>
|
|
235
|
+
{button}
|
|
236
|
+
<span id={reasonId} hidden>
|
|
237
|
+
{disabledReason}
|
|
238
|
+
</span>
|
|
239
|
+
</>
|
|
240
|
+
</Tooltip>
|
|
241
|
+
);
|
|
126
242
|
}
|
|
127
243
|
|
|
128
244
|
// Canonical square icon-only button. <Button> carries TEXT geometry — px-3 py-2
|
|
@@ -305,16 +421,59 @@ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>
|
|
|
305
421
|
* The pair look exactly like the nested version, click exactly like it, and
|
|
306
422
|
* are two tab stops a screen reader can tell apart. */
|
|
307
423
|
stopPropagation?: boolean;
|
|
424
|
+
/**
|
|
425
|
+
* The button's name, said once: it becomes the `aria-label` AND the text of a kit
|
|
426
|
+
* {@link Tooltip} round the button.
|
|
427
|
+
*
|
|
428
|
+
* Every app writes the pair by hand. lenkbank's nine icon actions all carry
|
|
429
|
+
* `aria-label={t(x)} title={t(x)}` (shared/lib/table-columns.tsx:103,
|
|
430
|
+
* projects-page.tsx:83, setpoint/segment-list.tsx:198/207, profile-bar.tsx:128/142,
|
|
431
|
+
* shortcut-dialog.tsx:815/857/1007) — and `title` is the browser's own tooltip,
|
|
432
|
+
* which shows late, never on focus and never on touch, and looks like no other
|
|
433
|
+
* label in the kit. kastlan's RowAction (shared/components/data-table/row-action.tsx)
|
|
434
|
+
* and keksdose's icon wrappers each exist to put a Tooltip round an IconButton.
|
|
435
|
+
*
|
|
436
|
+
* A caller's own `aria-label` still wins, for the rare name that should be longer
|
|
437
|
+
* than the bubble. The bubble is visual only: it would otherwise describe the button
|
|
438
|
+
* with its own name, and a screen reader would read the same word twice. Tooltip's
|
|
439
|
+
* default placement applies — in place, or portalled inside a scroll container —
|
|
440
|
+
* unless `tooltipPortal` says otherwise.
|
|
441
|
+
*/
|
|
442
|
+
label?: string;
|
|
443
|
+
/** Show `label` as a tooltip. Default `true`; `false` keeps `label` as the
|
|
444
|
+
* accessible name only — for a button whose glyph is universally read (a close ✕ in
|
|
445
|
+
* a dialog header) or that already sits under a tooltip of its own. */
|
|
446
|
+
tooltip?: boolean;
|
|
447
|
+
/** Where the `label` tooltip opens. See {@link Tooltip}'s `side`. */
|
|
448
|
+
tooltipSide?: TooltipSide;
|
|
449
|
+
/** Passed to the `label` tooltip's `portal`. Left out, Tooltip decides (see there). */
|
|
450
|
+
tooltipPortal?: boolean;
|
|
308
451
|
}
|
|
309
452
|
|
|
310
453
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(
|
|
311
|
-
{
|
|
454
|
+
{
|
|
455
|
+
variant = "ghost",
|
|
456
|
+
size = "md",
|
|
457
|
+
tone = "default",
|
|
458
|
+
quiet,
|
|
459
|
+
pressed,
|
|
460
|
+
stopPropagation,
|
|
461
|
+
label,
|
|
462
|
+
tooltip = true,
|
|
463
|
+
tooltipSide,
|
|
464
|
+
tooltipPortal,
|
|
465
|
+
className,
|
|
466
|
+
onClick,
|
|
467
|
+
onKeyDown,
|
|
468
|
+
...rest
|
|
469
|
+
},
|
|
312
470
|
ref,
|
|
313
471
|
) {
|
|
314
|
-
|
|
472
|
+
const button = (
|
|
315
473
|
<button
|
|
316
474
|
ref={ref}
|
|
317
475
|
{...rest}
|
|
476
|
+
aria-label={rest["aria-label"] ?? label}
|
|
318
477
|
aria-pressed={pressed ?? rest["aria-pressed"]}
|
|
319
478
|
onClick={(e) => {
|
|
320
479
|
if (stopPropagation) e.stopPropagation();
|
|
@@ -338,6 +497,14 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
|
|
338
497
|
)}
|
|
339
498
|
/>
|
|
340
499
|
);
|
|
500
|
+
if (!tooltip || label === undefined || label === "") return button;
|
|
501
|
+
return (
|
|
502
|
+
// A fragment, so Tooltip leaves the button's description alone: the bubble says
|
|
503
|
+
// exactly what `aria-label` already does.
|
|
504
|
+
<Tooltip label={label} side={tooltipSide} portal={tooltipPortal}>
|
|
505
|
+
<>{button}</>
|
|
506
|
+
</Tooltip>
|
|
507
|
+
);
|
|
341
508
|
});
|
|
342
509
|
IconButton.displayName = "IconButton";
|
|
343
510
|
|
|
@@ -1400,10 +1567,72 @@ export interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "
|
|
|
1400
1567
|
* wrap and centre as one row), kastlan's "Create a rule". Buttons or links the
|
|
1401
1568
|
* caller renders; the box only places them. */
|
|
1402
1569
|
action?: ReactNode;
|
|
1570
|
+
/**
|
|
1571
|
+
* `box` (default): the dashed, padded card that stands in for a whole section.
|
|
1572
|
+
* `inline`: one quiet line with no box — for the empty state INSIDE something that
|
|
1573
|
+
* already has its own frame, where a dashed card would be a box in a box. keksdose
|
|
1574
|
+
* writes that line by hand five times as a muted `<p className="py-6 text-center">`:
|
|
1575
|
+
* the notification inbox (notification-inbox:140), a support thread
|
|
1576
|
+
* (support-thread:144), the assistant (assistant-page:321), the admin support panel
|
|
1577
|
+
* (support-panel:265) and the funding dialog (funding-dialog:89). The icon shrinks
|
|
1578
|
+
* to the text's size and sits before the title, the hint follows on the same line,
|
|
1579
|
+
* and the row wraps only when it must. Centred like the box; `className="justify-start"`
|
|
1580
|
+
* for a list that reads from the start edge (funding-dialog).
|
|
1581
|
+
*/
|
|
1582
|
+
variant?: "box" | "inline";
|
|
1583
|
+
/**
|
|
1584
|
+
* Colour the icon and the title: `danger` for an error standing where the content
|
|
1585
|
+
* should be — kastlan's ErrorState (shared/components/feedback/error-state.tsx) and
|
|
1586
|
+
* its error boundary's fallback (error-boundary.tsx:16), both a red triangle over a
|
|
1587
|
+
* message — and `success` for an empty state that is GOOD news, kastlan's "No defects
|
|
1588
|
+
* recorded for this room" (handover defects-step.tsx:142, a green check). Left out,
|
|
1589
|
+
* both stay muted. The hint stays muted either way: it is the explanation, not the
|
|
1590
|
+
* verdict.
|
|
1591
|
+
*/
|
|
1592
|
+
tone?: "danger" | "success";
|
|
1403
1593
|
}
|
|
1404
1594
|
|
|
1405
|
-
|
|
1595
|
+
const EMPTY_STATE_TONE: Record<"danger" | "success", string> = {
|
|
1596
|
+
danger: "text-[var(--danger)]",
|
|
1597
|
+
success: "text-[var(--success)]",
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
export function EmptyState({
|
|
1601
|
+
title,
|
|
1602
|
+
hint,
|
|
1603
|
+
icon,
|
|
1604
|
+
action,
|
|
1605
|
+
headingAs,
|
|
1606
|
+
variant = "box",
|
|
1607
|
+
tone,
|
|
1608
|
+
className,
|
|
1609
|
+
...rest
|
|
1610
|
+
}: EmptyStateProps) {
|
|
1406
1611
|
const Title = headingAs ?? "div";
|
|
1612
|
+
const toneClass = tone ? EMPTY_STATE_TONE[tone] : undefined;
|
|
1613
|
+
const hasHint = hint != null && hint !== false && hint !== "";
|
|
1614
|
+
if (variant === "inline") {
|
|
1615
|
+
return (
|
|
1616
|
+
<div
|
|
1617
|
+
{...rest}
|
|
1618
|
+
className={cn(
|
|
1619
|
+
"flex flex-wrap items-center justify-center gap-x-2 gap-y-1 py-4 text-center text-sm text-[var(--text-muted)]",
|
|
1620
|
+
className,
|
|
1621
|
+
)}
|
|
1622
|
+
>
|
|
1623
|
+
{icon != null && (
|
|
1624
|
+
<span aria-hidden className={cn("flex text-[var(--text-placeholder)] [&_svg]:size-4", toneClass)}>
|
|
1625
|
+
{icon}
|
|
1626
|
+
</span>
|
|
1627
|
+
)}
|
|
1628
|
+
{/* No weight of its own: one muted line is what these sites were, and a
|
|
1629
|
+
bold title would turn a quiet "nothing here" into a heading. */}
|
|
1630
|
+
<Title className={cn("text-sm", toneClass)}>{title}</Title>
|
|
1631
|
+
{hasHint && <span className="text-xs">{hint}</span>}
|
|
1632
|
+
{action != null && <span className="flex flex-wrap items-center gap-2">{action}</span>}
|
|
1633
|
+
</div>
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1407
1636
|
return (
|
|
1408
1637
|
<div
|
|
1409
1638
|
{...rest}
|
|
@@ -1413,13 +1642,13 @@ export function EmptyState({ title, hint, icon, action, headingAs, className, ..
|
|
|
1413
1642
|
)}
|
|
1414
1643
|
>
|
|
1415
1644
|
{icon != null && (
|
|
1416
|
-
<div aria-hidden className="mb-3 text-[var(--text-placeholder)] [&_svg]:size-8">
|
|
1645
|
+
<div aria-hidden className={cn("mb-3 text-[var(--text-placeholder)] [&_svg]:size-8", toneClass)}>
|
|
1417
1646
|
{icon}
|
|
1418
1647
|
</div>
|
|
1419
1648
|
)}
|
|
1420
1649
|
{/* `text-sm`: a heading keeps the box's size, not whatever a stylesheet gives h2. */}
|
|
1421
|
-
<Title className="text-sm font-medium text-[var(--text-secondary)]">{title}</Title>
|
|
1422
|
-
{
|
|
1650
|
+
<Title className={cn("text-sm font-medium text-[var(--text-secondary)]", toneClass)}>{title}</Title>
|
|
1651
|
+
{hasHint && <div className="mt-1 text-xs">{hint}</div>}
|
|
1423
1652
|
{action != null && (
|
|
1424
1653
|
<div className="mt-4 flex flex-wrap items-center justify-center gap-2">{action}</div>
|
|
1425
1654
|
)}
|
|
@@ -6,6 +6,19 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
6
6
|
* by a copy of it. */
|
|
7
7
|
export const OVERLAY_EXIT_MS = 220;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Whether the user has asked for no motion, read NOW. `true` where `matchMedia` is
|
|
11
|
+
* absent (jsdom, SSR): nothing is painting there, so "no animation" is the only
|
|
12
|
+
* correct answer. Shared by every exit timer in the package, so they agree.
|
|
13
|
+
*/
|
|
14
|
+
export function prefersReducedMotion(): boolean {
|
|
15
|
+
return (
|
|
16
|
+
typeof window === "undefined" ||
|
|
17
|
+
typeof window.matchMedia !== "function" ||
|
|
18
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
/**
|
|
10
23
|
* The state machine an exit animation needs, written once for every overlay in this
|
|
11
24
|
* package (Keksdose live #320 rework: *"Recognized the opening transition. Choose
|
|
@@ -66,11 +79,7 @@ export function useCloseTransition(
|
|
|
66
79
|
// Read at close time, not at mount: the setting can change under a long-lived
|
|
67
80
|
// page, and `matchMedia` is absent in jsdom and in SSR — where "no animation" is
|
|
68
81
|
// also the only correct answer, since nothing is painting.
|
|
69
|
-
|
|
70
|
-
typeof window === "undefined" ||
|
|
71
|
-
typeof window.matchMedia !== "function" ||
|
|
72
|
-
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
73
|
-
if (reduced || ms <= 0) {
|
|
82
|
+
if (prefersReducedMotion() || ms <= 0) {
|
|
74
83
|
latest.current();
|
|
75
84
|
return;
|
|
76
85
|
}
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -25,6 +25,10 @@ import { DEFAULT_CONFIRM_DIALOG_LABELS } from "../components/confirm-dialog";
|
|
|
25
25
|
import { DEFAULT_FLOATING_PANEL_LABELS } from "../components/floating-panel";
|
|
26
26
|
import { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from "../feedback/feedback-attachment";
|
|
27
27
|
import { DEFAULT_COPY_BUTTON_LABELS } from "../components/copy-button";
|
|
28
|
+
import { DEFAULT_BULK_ACTION_BAR_LABELS } from "../components/bulk-action-bar";
|
|
29
|
+
import { DEFAULT_LIST_LABELS } from "../components/list";
|
|
30
|
+
import { DEFAULT_BREADCRUMBS_LABELS } from "../components/breadcrumbs";
|
|
31
|
+
import { DEFAULT_TOAST_LABELS } from "../components/toast";
|
|
28
32
|
import {
|
|
29
33
|
DEFAULT_APP_SHELL_LABELS,
|
|
30
34
|
DEFAULT_CALCULATOR_LABELS,
|
|
@@ -88,4 +92,8 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
88
92
|
confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,
|
|
89
93
|
floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,
|
|
90
94
|
copyButton: DEFAULT_COPY_BUTTON_LABELS,
|
|
95
|
+
bulkActionBar: DEFAULT_BULK_ACTION_BAR_LABELS,
|
|
96
|
+
list: DEFAULT_LIST_LABELS,
|
|
97
|
+
breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,
|
|
98
|
+
toast: DEFAULT_TOAST_LABELS,
|
|
91
99
|
};
|
package/src/i18n/kit-labels.tsx
CHANGED
|
@@ -27,6 +27,10 @@ import type { FeedbackAttachmentFieldLabels } from "../feedback/feedback-attachm
|
|
|
27
27
|
import type { ConfirmDialogLabels } from "../components/confirm-dialog";
|
|
28
28
|
import type { FloatingPanelLabels } from "../components/floating-panel";
|
|
29
29
|
import type { CopyButtonLabels } from "../components/copy-button";
|
|
30
|
+
import type { BulkActionBarLabels } from "../components/bulk-action-bar";
|
|
31
|
+
import type { ListLabels } from "../components/list";
|
|
32
|
+
import type { BreadcrumbsLabels } from "../components/breadcrumbs";
|
|
33
|
+
import type { ToastLabels } from "../components/toast";
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* EVERY string the kit renders, as one typed tree — and an optional provider that
|
|
@@ -229,6 +233,10 @@ export interface UiKitLabels {
|
|
|
229
233
|
confirmDialog: ConfirmDialogLabels;
|
|
230
234
|
floatingPanel: FloatingPanelLabels;
|
|
231
235
|
copyButton: CopyButtonLabels;
|
|
236
|
+
bulkActionBar: BulkActionBarLabels;
|
|
237
|
+
list: ListLabels;
|
|
238
|
+
breadcrumbs: BreadcrumbsLabels;
|
|
239
|
+
toast: ToastLabels;
|
|
232
240
|
}
|
|
233
241
|
|
|
234
242
|
/** Any subset of the tree, one level deep — each namespace may be partial, and a
|
package/src/i18n/locales/de.ts
CHANGED
|
@@ -239,6 +239,11 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
239
239
|
dialogFrame: {
|
|
240
240
|
close: "Schließen",
|
|
241
241
|
},
|
|
242
|
+
bulkActionBar: {
|
|
243
|
+
selected: (count) => `${n(count)} ausgewählt`,
|
|
244
|
+
clear: "Auswahl aufheben",
|
|
245
|
+
cleared: "Auswahl aufgehoben",
|
|
246
|
+
},
|
|
242
247
|
swipeableRow: {
|
|
243
248
|
actions: "Zeilenaktionen",
|
|
244
249
|
},
|
|
@@ -379,6 +384,7 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
379
384
|
"Zum Zoomen ziehen: Eine annähernd quadratische Auswahl zoomt beide Achsen, eine lange, schmale nur ihre eigene. Doppelklick setzt zurück.",
|
|
380
385
|
empty: "Keine Daten",
|
|
381
386
|
legend: "Datenreihen",
|
|
387
|
+
points: "Diagrammwerte",
|
|
382
388
|
},
|
|
383
389
|
confirmDialog: {
|
|
384
390
|
confirm: "Bestätigen",
|
|
@@ -394,6 +400,20 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
394
400
|
copiedAnnouncement: "In die Zwischenablage kopiert",
|
|
395
401
|
failedAnnouncement: "Kopieren in die Zwischenablage fehlgeschlagen",
|
|
396
402
|
},
|
|
403
|
+
list: {
|
|
404
|
+
unread: "Ungelesen",
|
|
405
|
+
opensInNewTab: "öffnet in einem neuen Tab",
|
|
406
|
+
},
|
|
407
|
+
breadcrumbs: {
|
|
408
|
+
label: "Brotkrümelnavigation",
|
|
409
|
+
showAll: "Vollständigen Pfad anzeigen",
|
|
410
|
+
},
|
|
411
|
+
toast: {
|
|
412
|
+
close: "Benachrichtigung schließen",
|
|
413
|
+
notifications: "Benachrichtigungen",
|
|
414
|
+
undo: "Rückgängig",
|
|
415
|
+
redo: "Wiederholen",
|
|
416
|
+
},
|
|
397
417
|
};
|
|
398
418
|
}
|
|
399
419
|
|
package/src/i18n/locales/es.ts
CHANGED
|
@@ -236,6 +236,11 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
236
236
|
dialogFrame: {
|
|
237
237
|
close: "Cerrar",
|
|
238
238
|
},
|
|
239
|
+
bulkActionBar: {
|
|
240
|
+
selected: (count) => `${n(count)} ${count === 1 ? "seleccionado" : "seleccionados"}`,
|
|
241
|
+
clear: "Borrar selección",
|
|
242
|
+
cleared: "Selección borrada",
|
|
243
|
+
},
|
|
239
244
|
swipeableRow: {
|
|
240
245
|
actions: "Acciones de la fila",
|
|
241
246
|
},
|
|
@@ -372,6 +377,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
372
377
|
"Arrastra para ampliar: una selección más o menos cuadrada amplía ambos ejes; una larga y estrecha, solo el suyo. Haz doble clic para restablecer.",
|
|
373
378
|
empty: "Sin datos",
|
|
374
379
|
legend: "Series",
|
|
380
|
+
points: "Valores del gráfico",
|
|
375
381
|
},
|
|
376
382
|
confirmDialog: {
|
|
377
383
|
confirm: "Confirmar",
|
|
@@ -387,6 +393,20 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
387
393
|
copiedAnnouncement: "Copiado al portapapeles",
|
|
388
394
|
failedAnnouncement: "No se pudo copiar al portapapeles",
|
|
389
395
|
},
|
|
396
|
+
list: {
|
|
397
|
+
unread: "No leído",
|
|
398
|
+
opensInNewTab: "se abre en una pestaña nueva",
|
|
399
|
+
},
|
|
400
|
+
breadcrumbs: {
|
|
401
|
+
label: "Ruta de navegación",
|
|
402
|
+
showAll: "Mostrar la ruta completa",
|
|
403
|
+
},
|
|
404
|
+
toast: {
|
|
405
|
+
close: "Cerrar notificación",
|
|
406
|
+
notifications: "Notificaciones",
|
|
407
|
+
undo: "Deshacer",
|
|
408
|
+
redo: "Rehacer",
|
|
409
|
+
},
|
|
390
410
|
};
|
|
391
411
|
}
|
|
392
412
|
|
package/src/i18n/locales/fr.ts
CHANGED
|
@@ -241,6 +241,11 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
241
241
|
dialogFrame: {
|
|
242
242
|
close: "Fermer",
|
|
243
243
|
},
|
|
244
|
+
bulkActionBar: {
|
|
245
|
+
selected: (count) => `${n(count)} ${count === 1 ? "sélectionné" : "sélectionnés"}`,
|
|
246
|
+
clear: "Effacer la sélection",
|
|
247
|
+
cleared: "Sélection effacée",
|
|
248
|
+
},
|
|
244
249
|
swipeableRow: {
|
|
245
250
|
actions: "Actions de la ligne",
|
|
246
251
|
},
|
|
@@ -385,6 +390,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
385
390
|
"Faites glisser pour zoomer\u00a0: une sélection à peu près carrée zoome sur les deux axes, une sélection longue et étroite sur son seul axe. Double-cliquez pour réinitialiser.",
|
|
386
391
|
empty: "Aucune donnée",
|
|
387
392
|
legend: "Séries",
|
|
393
|
+
points: "Valeurs du graphique",
|
|
388
394
|
},
|
|
389
395
|
confirmDialog: {
|
|
390
396
|
confirm: "Confirmer",
|
|
@@ -400,6 +406,20 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
400
406
|
copiedAnnouncement: "Copié dans le presse-papiers",
|
|
401
407
|
failedAnnouncement: "Impossible de copier dans le presse-papiers",
|
|
402
408
|
},
|
|
409
|
+
list: {
|
|
410
|
+
unread: "Non lu",
|
|
411
|
+
opensInNewTab: "s’ouvre dans un nouvel onglet",
|
|
412
|
+
},
|
|
413
|
+
breadcrumbs: {
|
|
414
|
+
label: "Fil d’Ariane",
|
|
415
|
+
showAll: "Afficher le chemin complet",
|
|
416
|
+
},
|
|
417
|
+
toast: {
|
|
418
|
+
close: "Fermer la notification",
|
|
419
|
+
notifications: "Notifications",
|
|
420
|
+
undo: "Annuler",
|
|
421
|
+
redo: "Rétablir",
|
|
422
|
+
},
|
|
403
423
|
};
|
|
404
424
|
}
|
|
405
425
|
|
package/src/i18n/locales/hu.ts
CHANGED
|
@@ -234,6 +234,11 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
234
234
|
dialogFrame: {
|
|
235
235
|
close: "Bezárás",
|
|
236
236
|
},
|
|
237
|
+
bulkActionBar: {
|
|
238
|
+
selected: (count) => `${n(count)} kijelölve`,
|
|
239
|
+
clear: "Kijelölés törlése",
|
|
240
|
+
cleared: "Kijelölés törölve",
|
|
241
|
+
},
|
|
237
242
|
swipeableRow: {
|
|
238
243
|
actions: "Sorműveletek",
|
|
239
244
|
},
|
|
@@ -373,6 +378,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
373
378
|
"Húzással nagyíthat: a nagyjából négyzetes kijelölés mindkét tengelyt nagyítja, a hosszú, keskeny csak a saját tengelyét. Dupla kattintással visszaállíthatja.",
|
|
374
379
|
empty: "Nincs adat",
|
|
375
380
|
legend: "Adatsorok",
|
|
381
|
+
points: "Diagramértékek",
|
|
376
382
|
},
|
|
377
383
|
confirmDialog: {
|
|
378
384
|
confirm: "Megerősítés",
|
|
@@ -388,6 +394,20 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
388
394
|
copiedAnnouncement: "Vágólapra másolva",
|
|
389
395
|
failedAnnouncement: "Nem sikerült a vágólapra másolni",
|
|
390
396
|
},
|
|
397
|
+
list: {
|
|
398
|
+
unread: "Olvasatlan",
|
|
399
|
+
opensInNewTab: "új lapon nyílik meg",
|
|
400
|
+
},
|
|
401
|
+
breadcrumbs: {
|
|
402
|
+
label: "Morzsamenü",
|
|
403
|
+
showAll: "Teljes útvonal megjelenítése",
|
|
404
|
+
},
|
|
405
|
+
toast: {
|
|
406
|
+
close: "Értesítés bezárása",
|
|
407
|
+
notifications: "Értesítések",
|
|
408
|
+
undo: "Visszavonás",
|
|
409
|
+
redo: "Újra",
|
|
410
|
+
},
|
|
391
411
|
};
|
|
392
412
|
}
|
|
393
413
|
|
package/src/i18n/locales/it.ts
CHANGED
|
@@ -237,6 +237,11 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
237
237
|
dialogFrame: {
|
|
238
238
|
close: "Chiudi",
|
|
239
239
|
},
|
|
240
|
+
bulkActionBar: {
|
|
241
|
+
selected: (count) => `${n(count)} ${count === 1 ? "selezionato" : "selezionati"}`,
|
|
242
|
+
clear: "Annulla selezione",
|
|
243
|
+
cleared: "Selezione annullata",
|
|
244
|
+
},
|
|
240
245
|
swipeableRow: {
|
|
241
246
|
actions: "Azioni della riga",
|
|
242
247
|
},
|
|
@@ -374,6 +379,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
374
379
|
"Trascina per ingrandire: una selezione più o meno quadrata ingrandisce entrambi gli assi, una lunga e sottile solo il proprio. Fai doppio clic per ripristinare.",
|
|
375
380
|
empty: "Nessun dato",
|
|
376
381
|
legend: "Serie",
|
|
382
|
+
points: "Valori del grafico",
|
|
377
383
|
},
|
|
378
384
|
confirmDialog: {
|
|
379
385
|
confirm: "Conferma",
|
|
@@ -389,6 +395,20 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
389
395
|
copiedAnnouncement: "Copiato negli appunti",
|
|
390
396
|
failedAnnouncement: "Impossibile copiare negli appunti",
|
|
391
397
|
},
|
|
398
|
+
list: {
|
|
399
|
+
unread: "Non letto",
|
|
400
|
+
opensInNewTab: "si apre in una nuova scheda",
|
|
401
|
+
},
|
|
402
|
+
breadcrumbs: {
|
|
403
|
+
label: "Percorso di navigazione",
|
|
404
|
+
showAll: "Mostra percorso completo",
|
|
405
|
+
},
|
|
406
|
+
toast: {
|
|
407
|
+
close: "Chiudi notifica",
|
|
408
|
+
notifications: "Notifiche",
|
|
409
|
+
undo: "Annulla",
|
|
410
|
+
redo: "Ripeti",
|
|
411
|
+
},
|
|
392
412
|
};
|
|
393
413
|
}
|
|
394
414
|
|
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -231,6 +231,11 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
231
231
|
dialogFrame: {
|
|
232
232
|
close: "关闭",
|
|
233
233
|
},
|
|
234
|
+
bulkActionBar: {
|
|
235
|
+
selected: (count) => `已选择 ${n(count)} 项`,
|
|
236
|
+
clear: "清除选择",
|
|
237
|
+
cleared: "已清除选择",
|
|
238
|
+
},
|
|
234
239
|
swipeableRow: {
|
|
235
240
|
actions: "行操作",
|
|
236
241
|
},
|
|
@@ -362,6 +367,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
362
367
|
"拖动以缩放:近似正方形的选区同时缩放两条坐标轴,细长的选区只缩放其所沿的那条轴。双击可重置。",
|
|
363
368
|
empty: "无数据",
|
|
364
369
|
legend: "数据系列",
|
|
370
|
+
points: "图表数值",
|
|
365
371
|
},
|
|
366
372
|
confirmDialog: {
|
|
367
373
|
confirm: "确认",
|
|
@@ -377,6 +383,20 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
377
383
|
copiedAnnouncement: "已复制到剪贴板",
|
|
378
384
|
failedAnnouncement: "无法复制到剪贴板",
|
|
379
385
|
},
|
|
386
|
+
list: {
|
|
387
|
+
unread: "未读",
|
|
388
|
+
opensInNewTab: "在新标签页中打开",
|
|
389
|
+
},
|
|
390
|
+
breadcrumbs: {
|
|
391
|
+
label: "面包屑导航",
|
|
392
|
+
showAll: "显示完整路径",
|
|
393
|
+
},
|
|
394
|
+
toast: {
|
|
395
|
+
close: "关闭通知",
|
|
396
|
+
notifications: "通知",
|
|
397
|
+
undo: "撤销",
|
|
398
|
+
redo: "重做",
|
|
399
|
+
},
|
|
380
400
|
};
|
|
381
401
|
}
|
|
382
402
|
|