@eifi1/ui-kit 0.17.0 → 0.18.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 +29 -4
- package/dist/chart.d.ts +10 -1
- package/dist/components/account-chips.d.ts +153 -0
- package/dist/components/account-chips.js +135 -0
- package/dist/components/account-chips.js.map +1 -0
- package/dist/components/account-settings-labels.d.ts +10 -2
- package/dist/components/account-settings-labels.js +3 -0
- package/dist/components/account-settings-labels.js.map +1 -1
- package/dist/components/amount-input.d.ts +10 -1
- package/dist/components/button-group.d.ts +10 -1
- package/dist/components/calculator.d.ts +10 -1
- package/dist/components/chip.d.ts +25 -1
- package/dist/components/chip.js +143 -6
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +42 -1
- package/dist/components/confirm-dialog.js +36 -5
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +10 -1
- package/dist/components/danger-confirm.d.ts +55 -4
- package/dist/components/danger-confirm.js +67 -30
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +2 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/facing-pair.d.ts +10 -1
- package/dist/components/file-button.d.ts +10 -1
- package/dist/components/file-dropzone.d.ts +10 -1
- package/dist/components/form-actions.d.ts +10 -1
- package/dist/components/form-actions.js +3 -0
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/money-field.d.ts +10 -1
- package/dist/components/number-field.d.ts +11 -2
- package/dist/components/number-field.js +23 -22
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +10 -1
- package/dist/components/numpad-sheet.d.ts +10 -1
- package/dist/components/passkeys-setting.d.ts +27 -1
- package/dist/components/passkeys-setting.js +31 -5
- package/dist/components/passkeys-setting.js.map +1 -1
- package/dist/components/progress-bar.d.ts +15 -1
- package/dist/components/progress-bar.js +8 -1
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.d.ts +90 -0
- package/dist/components/reauth-dialog.js +158 -0
- package/dist/components/reauth-dialog.js.map +1 -0
- package/dist/components/series-chart.d.ts +10 -1
- package/dist/components/server-wake.d.ts +58 -0
- package/dist/components/server-wake.js +58 -0
- package/dist/components/server-wake.js.map +1 -0
- package/dist/components/settings-fields.d.ts +10 -1
- package/dist/components/share-card.d.ts +61 -0
- package/dist/components/share-card.js +350 -0
- package/dist/components/share-card.js.map +1 -0
- package/dist/components/status-dot.d.ts +2 -0
- package/dist/components/text-link.d.ts +10 -1
- package/dist/components/time-input.d.ts +10 -1
- package/dist/components/toast.d.ts +10 -1
- package/dist/components/toast.js +42 -2
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.js +5 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/ui.d.ts +10 -1
- package/dist/components/ui.js +43 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/user-avatar.d.ts +2 -0
- package/dist/components/write-lock.d.ts +68 -0
- package/dist/components/write-lock.js +33 -0
- package/dist/components/write-lock.js.map +1 -0
- package/dist/{data-table-CpkWUJBY.d.ts → data-table-3mi2R3so.d.ts} +19 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/hooks/use-file-drop.d.ts +10 -1
- package/dist/hooks/use-overlay-history.js +16 -0
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.d.ts +10 -1
- package/dist/i18n/defaults.js +11 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +78 -0
- package/dist/i18n/{locales/de.js → german.js} +59 -9
- package/dist/i18n/german.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +10 -1
- package/dist/i18n/kit-labels.js +5 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH.d.ts +23 -8
- package/dist/i18n/locales/de-CH.js +7 -3
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +10 -1
- package/dist/i18n/locales/es.d.ts +10 -1
- package/dist/i18n/locales/es.js +51 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +10 -1
- package/dist/i18n/locales/fr.js +51 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +10 -1
- package/dist/i18n/locales/hu.js +54 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +10 -1
- package/dist/i18n/locales/it.js +51 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +10 -1
- package/dist/i18n/locales/zh.js +50 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/swiss.d.ts +3 -3
- package/dist/i18n/swiss.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-CPbsjlXr.d.ts} +229 -9
- package/dist/lib/server-wake.d.ts +130 -0
- package/dist/lib/server-wake.js +121 -0
- package/dist/lib/server-wake.js.map +1 -0
- package/dist/rhf/fields.d.ts +10 -1
- package/dist/rhf/form.d.ts +10 -1
- package/dist/rhf.d.ts +10 -1
- package/dist/search/global-search.d.ts +3 -1
- package/dist/search/global-search.js.map +1 -1
- package/dist/shell/app-shell.d.ts +10 -1
- package/dist/shell/top-bar-brand.d.ts +10 -1
- package/dist/shell/topbar-action-menu.d.ts +2 -0
- package/dist/shell.d.ts +10 -1
- package/dist/wizard/stepper-nav.d.ts +10 -1
- package/dist/wizard.d.ts +10 -1
- package/package.json +1 -1
- package/src/components/account-chips.tsx +302 -0
- package/src/components/account-settings-labels.ts +16 -2
- package/src/components/chip.tsx +239 -6
- package/src/components/confirm-dialog.tsx +82 -7
- package/src/components/danger-confirm.tsx +118 -33
- package/src/components/data-table.tsx +20 -0
- package/src/components/form-actions.tsx +22 -3
- package/src/components/number-field.tsx +32 -26
- package/src/components/passkeys-setting.tsx +68 -7
- package/src/components/progress-bar.tsx +21 -1
- package/src/components/reauth-dialog.tsx +273 -0
- package/src/components/server-wake.tsx +124 -0
- package/src/components/share-card.tsx +615 -0
- package/src/components/toast.tsx +72 -2
- package/src/components/toggle-group.tsx +5 -2
- package/src/components/ui.tsx +89 -32
- package/src/components/write-lock.tsx +96 -0
- package/src/hooks/use-overlay-history.ts +51 -0
- package/src/i18n/defaults.ts +10 -0
- package/src/i18n/{locales/de.ts → german.ts} +68 -13
- package/src/i18n/kit-labels.tsx +22 -2
- package/src/i18n/locales/de-CH.ts +17 -8
- package/src/i18n/locales/es.ts +51 -0
- package/src/i18n/locales/fr.ts +52 -0
- package/src/i18n/locales/hu.ts +55 -0
- package/src/i18n/locales/it.ts +51 -0
- package/src/i18n/locales/zh.ts +50 -0
- package/src/i18n/swiss.ts +3 -3
- package/src/index.ts +23 -1
- package/src/lib/server-wake.ts +270 -0
- package/src/search/global-search.tsx +3 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +0 -61
- package/dist/i18n/locales/de-CH-informal.js +0 -8
- package/dist/i18n/locales/de-CH-informal.js.map +0 -1
- package/dist/i18n/locales/de-informal.d.ts +0 -82
- package/dist/i18n/locales/de-informal.js +0 -37
- package/dist/i18n/locales/de-informal.js.map +0 -1
- package/dist/i18n/locales/de.d.ts +0 -66
- package/dist/i18n/locales/de.js.map +0 -1
- package/src/i18n/locales/de-CH-informal.ts +0 -10
- package/src/i18n/locales/de-informal.ts +0 -61
package/src/components/toast.tsx
CHANGED
|
@@ -135,9 +135,33 @@ type Kind = "default" | "success" | "error" | "warning" | "info" | "loading";
|
|
|
135
135
|
|
|
136
136
|
const privately = (node: ReactNode) => <span data-private="">{node}</span>;
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* The toast's id, written onto its `<li>` as a class — the one per-toast attribute
|
|
140
|
+
* sonner passes through untouched besides `testId`, which is the app's (sonner keeps the
|
|
141
|
+
* id in React state only). `<Toaster dismissOnMiddleClick>` reads it back to dismiss
|
|
142
|
+
* the toast under the pointer, close button or not. The type is kept (`s`/`n`) because
|
|
143
|
+
* sonner matches ids with `===`, and the value is URI-encoded so any string id is one
|
|
144
|
+
* whitespace-free class token.
|
|
145
|
+
*/
|
|
146
|
+
const ID_CLASS = "kit-toast-id-";
|
|
147
|
+
const idClass = (id: ToastId) =>
|
|
148
|
+
`${ID_CLASS}${typeof id === "number" ? "n" : "s"}-${encodeURIComponent(String(id))}`;
|
|
149
|
+
|
|
150
|
+
/** The id {@link idClass} stamped on a toast element, or null for a toast the kit did
|
|
151
|
+
* not make (an app still calling sonner directly). */
|
|
152
|
+
function readToastId(el: Element): ToastId | null {
|
|
153
|
+
for (const token of el.classList) {
|
|
154
|
+
if (!token.startsWith(ID_CLASS)) continue;
|
|
155
|
+
const kind = token.charAt(ID_CLASS.length);
|
|
156
|
+
const raw = decodeURIComponent(token.slice(ID_CLASS.length + 2));
|
|
157
|
+
return kind === "n" ? Number(raw) : raw;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
138
162
|
function toSonnerData(opts: ToastOptions | undefined, id: ToastId): SonnerData {
|
|
139
|
-
const { redact, description, duration, action, ...rest } = opts ?? {};
|
|
140
|
-
const data: SonnerData = { ...rest, id };
|
|
163
|
+
const { redact, description, duration, action, className, ...rest } = opts ?? {};
|
|
164
|
+
const data: SonnerData = { ...rest, id, className: className ? `${idClass(id)} ${className}` : idClass(id) };
|
|
141
165
|
if (description !== undefined) data.description = redact ? privately(description) : description;
|
|
142
166
|
if (action) data.action = action;
|
|
143
167
|
const resolved = duration ?? (action ? TOAST_ACTION_DURATION : undefined);
|
|
@@ -312,6 +336,15 @@ export interface ToasterProps {
|
|
|
312
336
|
hotkey?: string[];
|
|
313
337
|
gap?: number;
|
|
314
338
|
dir?: "ltr" | "rtl" | "auto";
|
|
339
|
+
/**
|
|
340
|
+
* A middle click on a toast dismisses it (0.18.0) — the gesture that closes a tab,
|
|
341
|
+
* which is what keksdose's users reached for (dev #583). By id, so it works on a
|
|
342
|
+
* toast with `closeButton: false` too; it counts as the user closing it, so
|
|
343
|
+
* `onDismiss` runs. Not on a link or a button inside the toast — a middle click on a
|
|
344
|
+
* link still opens it in a new tab — and not on a `dismissible: false` toast.
|
|
345
|
+
* Default true.
|
|
346
|
+
*/
|
|
347
|
+
dismissOnMiddleClick?: boolean;
|
|
315
348
|
className?: string;
|
|
316
349
|
style?: CSSProperties;
|
|
317
350
|
}
|
|
@@ -385,6 +418,7 @@ export function Toaster({
|
|
|
385
418
|
swipeDirections = ALL_DIRECTIONS,
|
|
386
419
|
offset,
|
|
387
420
|
mobileOffset,
|
|
421
|
+
dismissOnMiddleClick = true,
|
|
388
422
|
className,
|
|
389
423
|
style,
|
|
390
424
|
...rest
|
|
@@ -409,6 +443,42 @@ export function Toaster({
|
|
|
409
443
|
};
|
|
410
444
|
}, [labels]);
|
|
411
445
|
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
if (!dismissOnMiddleClick || !mod) return;
|
|
448
|
+
// On the document rather than the toaster: sonner owns the `<ol>`, and a listener
|
|
449
|
+
// there would have to be re-attached whenever it remounts it.
|
|
450
|
+
const toastUnder = (e: globalThis.MouseEvent) => {
|
|
451
|
+
if (e.button !== 1 || !(e.target instanceof Element)) return null;
|
|
452
|
+
const li = e.target.closest<HTMLElement>("[data-sonner-toast]");
|
|
453
|
+
if (!li || li.dataset.dismissible === "false") return null;
|
|
454
|
+
// A link keeps its middle click (open in a new tab); a button or a field inside
|
|
455
|
+
// the toast is the app's, not a background to click.
|
|
456
|
+
const control = e.target.closest("a[href], button, input, select, textarea, [role='button'], [role='link']");
|
|
457
|
+
if (control && li.contains(control)) return null;
|
|
458
|
+
return li;
|
|
459
|
+
};
|
|
460
|
+
// `mousedown`: the middle button starts autoscroll (Windows) before any click.
|
|
461
|
+
const onDown = (e: globalThis.MouseEvent) => {
|
|
462
|
+
if (toastUnder(e)) e.preventDefault();
|
|
463
|
+
};
|
|
464
|
+
const onAux = (e: globalThis.MouseEvent) => {
|
|
465
|
+
const li = toastUnder(e);
|
|
466
|
+
if (!li) return;
|
|
467
|
+
// No paste of the primary selection on Linux, no other handler's idea of it.
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
const id = readToastId(li);
|
|
470
|
+
if (id !== null) mod.toast.dismiss(id);
|
|
471
|
+
// A toast made by sonner directly carries no id: its close button, if any.
|
|
472
|
+
else li.querySelector<HTMLButtonElement>("[data-close-button]")?.click();
|
|
473
|
+
};
|
|
474
|
+
document.addEventListener("mousedown", onDown);
|
|
475
|
+
document.addEventListener("auxclick", onAux);
|
|
476
|
+
return () => {
|
|
477
|
+
document.removeEventListener("mousedown", onDown);
|
|
478
|
+
document.removeEventListener("auxclick", onAux);
|
|
479
|
+
};
|
|
480
|
+
}, [dismissOnMiddleClick, mod]);
|
|
481
|
+
|
|
412
482
|
const documentTheme = useSyncExternalStore(subscribeDocumentTheme, readDocumentTheme, () => "light" as const);
|
|
413
483
|
const phone = useMediaQuery(PHONE_QUERY, false);
|
|
414
484
|
const resolved: ToastPosition = position ?? (phone ? "bottom-center" : "top-center");
|
|
@@ -340,8 +340,11 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
|
|
|
340
340
|
? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]"
|
|
341
341
|
: "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
|
|
342
342
|
// No hover fill on a group that cannot be changed — a segment that
|
|
343
|
-
// lights up under the pointer is an offer, and there is none here.
|
|
344
|
-
|
|
343
|
+
// lights up under the pointer is an offer, and there is none here. Not on
|
|
344
|
+
// the chosen segment: its fill IS its state, and its inverse text would
|
|
345
|
+
// vanish on a transparent hover.
|
|
346
|
+
disabled && "cursor-not-allowed",
|
|
347
|
+
disabled && !active && "hover:bg-transparent dark:hover:bg-transparent",
|
|
345
348
|
optionClassName,
|
|
346
349
|
opt.className,
|
|
347
350
|
)}
|
package/src/components/ui.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { Tooltip, type TooltipSide } from "./tooltip";
|
|
|
9
9
|
import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink } from "../i18n/kit-labels";
|
|
10
10
|
import type { KitLinkComponent, KitLinkProps } from "../i18n/kit-labels";
|
|
11
11
|
import { pickLinkRenderer, replacingClick, routerLinkNavigation } from "./text-link";
|
|
12
|
+
import { useCommitReason } from "./write-lock";
|
|
12
13
|
|
|
13
14
|
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
|
|
14
15
|
|
|
@@ -196,6 +197,13 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
196
197
|
* DangerConfirm keeps its own.
|
|
197
198
|
*/
|
|
198
199
|
disabledReason?: ReactNode;
|
|
200
|
+
/**
|
|
201
|
+
* This button COMMITS — it saves, creates, deletes. Under a locked
|
|
202
|
+
* {@link WriteLockProvider} it is disabled the `disabledReason` way, with the lock's
|
|
203
|
+
* reason (which wins over a `disabledReason` of its own). No provider, or an
|
|
204
|
+
* unlocked one: no effect. See {@link WriteLockProvider}.
|
|
205
|
+
*/
|
|
206
|
+
commit?: boolean;
|
|
199
207
|
/**
|
|
200
208
|
* Busy — the save is in flight: a spinner, `aria-busy`, and no second submit.
|
|
201
209
|
*
|
|
@@ -271,6 +279,7 @@ export interface ButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorEle
|
|
|
271
279
|
pending?: never;
|
|
272
280
|
pressed?: never;
|
|
273
281
|
disabledReason?: never;
|
|
282
|
+
commit?: never;
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
function hasContent(node: ReactNode): boolean {
|
|
@@ -396,7 +405,8 @@ function ButtonElement({
|
|
|
396
405
|
stretch,
|
|
397
406
|
tone = "default",
|
|
398
407
|
pressed,
|
|
399
|
-
disabledReason,
|
|
408
|
+
disabledReason: ownDisabledReason,
|
|
409
|
+
commit,
|
|
400
410
|
pending,
|
|
401
411
|
className,
|
|
402
412
|
onClick,
|
|
@@ -409,6 +419,7 @@ function ButtonElement({
|
|
|
409
419
|
...rest
|
|
410
420
|
}: ButtonProps) {
|
|
411
421
|
const reasonId = useId();
|
|
422
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
412
423
|
const locked = hasContent(disabledReason);
|
|
413
424
|
const inert = locked || Boolean(pending);
|
|
414
425
|
const ownDescribedBy = rest["aria-describedby"];
|
|
@@ -874,6 +885,10 @@ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>
|
|
|
874
885
|
* reason nobody can see is not one. Wins over `disabled`, as on Button.
|
|
875
886
|
*/
|
|
876
887
|
disabledReason?: ReactNode;
|
|
888
|
+
/** This action COMMITS — {@link Button}'s `commit`: under a locked
|
|
889
|
+
* {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
|
|
890
|
+
* reason. No effect without a lock. */
|
|
891
|
+
commit?: boolean;
|
|
877
892
|
/** The `<button>` element — a prop in React 19, as on {@link Button}. */
|
|
878
893
|
ref?: Ref<HTMLButtonElement>;
|
|
879
894
|
/** Only on the link form — see {@link IconButtonLinkProps}. */
|
|
@@ -937,6 +952,7 @@ export interface IconButtonLinkProps
|
|
|
937
952
|
form?: never;
|
|
938
953
|
pressed?: never;
|
|
939
954
|
disabledReason?: never;
|
|
955
|
+
commit?: never;
|
|
940
956
|
disabledStyle?: never;
|
|
941
957
|
pending?: never;
|
|
942
958
|
}
|
|
@@ -1164,7 +1180,8 @@ function IconButtonElement(
|
|
|
1164
1180
|
tooltipSide,
|
|
1165
1181
|
tooltipPortal,
|
|
1166
1182
|
tooltipLazy = true,
|
|
1167
|
-
disabledReason,
|
|
1183
|
+
disabledReason: ownDisabledReason,
|
|
1184
|
+
commit,
|
|
1168
1185
|
pending,
|
|
1169
1186
|
glyphSize,
|
|
1170
1187
|
badge,
|
|
@@ -1186,6 +1203,7 @@ function IconButtonElement(
|
|
|
1186
1203
|
const tone = toneProp ?? (toneColor !== undefined ? "custom" : "default");
|
|
1187
1204
|
const keep = disabledStyle === "keep";
|
|
1188
1205
|
const reasonId = useId();
|
|
1206
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
1189
1207
|
const locked = hasContent(disabledReason);
|
|
1190
1208
|
// Pending is inert like a locked button — focusable, every activation swallowed —
|
|
1191
1209
|
// without the locked look: the spinner is the state.
|
|
@@ -1750,15 +1768,48 @@ function useFieldError(
|
|
|
1750
1768
|
* down out of the field, between the value and the text. So the field keeps its box
|
|
1751
1769
|
* and this wraps the pair.
|
|
1752
1770
|
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
* down to the bare `<input>` an unlabelled {@link Input} drops straight into a
|
|
1756
|
-
* caller's flex row.
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1771
|
+
* WHEN IT WRAPS. A field that never takes `error` renders nothing of its own here,
|
|
1772
|
+
* which is what keeps the prop additive: it is exactly the DOM it was before the prop
|
|
1773
|
+
* existed, down to the bare `<input>` an unlabelled {@link Input} drops straight into a
|
|
1774
|
+
* caller's flex row. A field that DOES take `error` — the key is passed, whatever its
|
|
1775
|
+
* value — is wrapped for as long as it is mounted, message or not (`reserve`).
|
|
1776
|
+
*
|
|
1777
|
+
* It used to wrap only while a message showed, and that cost the control its
|
|
1778
|
+
* identity: a fragment and a `<div>` are different elements at the same spot in the
|
|
1779
|
+
* tree, so React threw the `<input>` away and mounted a new one every time the message
|
|
1780
|
+
* came or went. Focus fell to `<body>`, the caret went with it, and an uncontrolled
|
|
1781
|
+
* field lost what had been typed — Kurvenschmiede's confirm-password dialog clears its
|
|
1782
|
+
* "Wrong password" on change, so the first keystroke of the retry dropped the user out
|
|
1783
|
+
* of the field. Keeping the box stops that; the field is never reparented.
|
|
1784
|
+
*
|
|
1785
|
+
* The other ways to keep it stable were worse. A box on EVERY field changes what every
|
|
1786
|
+
* flex row and grid in three apps lays out (`flex-1`, `w-56`, `col-span-2` on the
|
|
1787
|
+
* field would land on a child of the item instead of the item). A `display: contents`
|
|
1788
|
+
* box would keep the flex/grid item, but a contents box takes no margin, so a parent's
|
|
1789
|
+
* `space-y-*` stops spacing the field — the shape of Kurvenschmiede's reset-password
|
|
1790
|
+
* form and gear wizard, both `space-y-*` stacks of fields with `error` — and it moves
|
|
1791
|
+
* `first:`/`last:`/`> *` matches. A field passing `error` already took this plain box
|
|
1792
|
+
* whenever its message showed, so what changes is only that it keeps it while none
|
|
1793
|
+
* does; every such call site in the three apps sits in a block or a `space-y`/grid
|
|
1794
|
+
* stack where a full-width block box is the field's own size. The case it does change
|
|
1795
|
+
* is a field passing `error` whose `className` sizes it as a flex item (`flex-1` in a
|
|
1796
|
+
* row): that caller already lost the sizing while a message showed, and now sees the
|
|
1797
|
+
* same box without one. Pass `error` only where a message can actually appear.
|
|
1798
|
+
*
|
|
1799
|
+
* `className` is NOT moved out here, for the same reason — it goes on the field, as it
|
|
1800
|
+
* always has, so the box cannot silently change what that prop styles.
|
|
1759
1801
|
*/
|
|
1760
|
-
function FieldGroup({
|
|
1761
|
-
|
|
1802
|
+
function FieldGroup({
|
|
1803
|
+
errorEl,
|
|
1804
|
+
reserve = false,
|
|
1805
|
+
children,
|
|
1806
|
+
}: {
|
|
1807
|
+
errorEl: ReactNode;
|
|
1808
|
+
/** The caller passed `error`: keep the box even while there is no message. */
|
|
1809
|
+
reserve?: boolean;
|
|
1810
|
+
children: ReactNode;
|
|
1811
|
+
}) {
|
|
1812
|
+
if (errorEl === null && !reserve) return <>{children}</>;
|
|
1762
1813
|
return (
|
|
1763
1814
|
<div>
|
|
1764
1815
|
{children}
|
|
@@ -1829,8 +1880,8 @@ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
|
1829
1880
|
passwordLabels?: Partial<PasswordRevealLabels>;
|
|
1830
1881
|
}
|
|
1831
1882
|
|
|
1832
|
-
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
1833
|
-
{
|
|
1883
|
+
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) {
|
|
1884
|
+
const {
|
|
1834
1885
|
className,
|
|
1835
1886
|
inputClassName,
|
|
1836
1887
|
label,
|
|
@@ -1842,9 +1893,9 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
|
1842
1893
|
error,
|
|
1843
1894
|
passwordLabels,
|
|
1844
1895
|
...rest
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1896
|
+
} = props;
|
|
1897
|
+
// Passed at all — even as `undefined` — the field keeps its box; see FieldGroup.
|
|
1898
|
+
const reserve = "error" in props;
|
|
1848
1899
|
const generated = useId();
|
|
1849
1900
|
const fieldId = id ?? generated;
|
|
1850
1901
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
@@ -1894,7 +1945,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
|
1894
1945
|
if (label === undefined) {
|
|
1895
1946
|
if (!isPassword) {
|
|
1896
1947
|
return (
|
|
1897
|
-
<FieldGroup errorEl={errorEl}>
|
|
1948
|
+
<FieldGroup errorEl={errorEl} reserve={reserve}>
|
|
1898
1949
|
<input
|
|
1899
1950
|
ref={ref}
|
|
1900
1951
|
id={id}
|
|
@@ -1915,7 +1966,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
|
1915
1966
|
);
|
|
1916
1967
|
}
|
|
1917
1968
|
return (
|
|
1918
|
-
<FieldGroup errorEl={errorEl}>
|
|
1969
|
+
<FieldGroup errorEl={errorEl} reserve={reserve}>
|
|
1919
1970
|
<div className={cn("relative", className)}>
|
|
1920
1971
|
<input
|
|
1921
1972
|
ref={ref}
|
|
@@ -1933,7 +1984,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
|
1933
1984
|
);
|
|
1934
1985
|
}
|
|
1935
1986
|
return (
|
|
1936
|
-
<FieldGroup errorEl={errorEl}>
|
|
1987
|
+
<FieldGroup errorEl={errorEl} reserve={reserve}>
|
|
1937
1988
|
<FloatingField className={className} htmlFor={fieldId} label={label} srOnlyLabel={asDisplay}>
|
|
1938
1989
|
<input
|
|
1939
1990
|
ref={ref}
|
|
@@ -2001,10 +2052,11 @@ export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement
|
|
|
2001
2052
|
// height the caller's type brings with it.
|
|
2002
2053
|
const SELECT_SM = "h-7 py-0 ps-2 pe-7 text-xs";
|
|
2003
2054
|
|
|
2004
|
-
export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(
|
|
2005
|
-
{ className, label, id, children, invalid, error, hint, size, selectClassName, ...rest }
|
|
2006
|
-
|
|
2007
|
-
|
|
2055
|
+
export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(props, ref) {
|
|
2056
|
+
const { className, label, id, children, invalid, error, hint, size, selectClassName, ...rest } =
|
|
2057
|
+
props;
|
|
2058
|
+
// See Input's `reserve`.
|
|
2059
|
+
const reserve = "error" in props;
|
|
2008
2060
|
const generated = useId();
|
|
2009
2061
|
// Only a number reaches the DOM; the two words are this component's own.
|
|
2010
2062
|
const nativeSize = typeof size === "number" ? size : undefined;
|
|
@@ -2100,7 +2152,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select
|
|
|
2100
2152
|
// box, as the label line's own hint goes at the end of the label.
|
|
2101
2153
|
if (hasContent(hint) && !textHint) {
|
|
2102
2154
|
return (
|
|
2103
|
-
<FieldGroup errorEl={below}>
|
|
2155
|
+
<FieldGroup errorEl={below} reserve={reserve}>
|
|
2104
2156
|
<div className={cn("flex items-center gap-1.5", className)}>
|
|
2105
2157
|
<div className="relative min-w-0 flex-1">
|
|
2106
2158
|
{select}
|
|
@@ -2112,7 +2164,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select
|
|
|
2112
2164
|
);
|
|
2113
2165
|
}
|
|
2114
2166
|
return (
|
|
2115
|
-
<FieldGroup errorEl={below}>
|
|
2167
|
+
<FieldGroup errorEl={below} reserve={reserve}>
|
|
2116
2168
|
<div className={cn("relative", className)}>
|
|
2117
2169
|
{select}
|
|
2118
2170
|
{chevron}
|
|
@@ -2121,7 +2173,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select
|
|
|
2121
2173
|
);
|
|
2122
2174
|
}
|
|
2123
2175
|
return (
|
|
2124
|
-
<FieldGroup errorEl={below}>
|
|
2176
|
+
<FieldGroup errorEl={below} reserve={reserve}>
|
|
2125
2177
|
<FloatingField className={className} htmlFor={fieldId} label={label} staticLabel hint={textHint ? undefined : hint}>
|
|
2126
2178
|
<select
|
|
2127
2179
|
ref={ref}
|
|
@@ -2159,10 +2211,10 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
2159
2211
|
error?: ReactNode;
|
|
2160
2212
|
}
|
|
2161
2213
|
|
|
2162
|
-
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
|
|
2163
|
-
{ className, label, id, placeholder, invalid, error, ...rest }
|
|
2164
|
-
|
|
2165
|
-
|
|
2214
|
+
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(props, ref) {
|
|
2215
|
+
const { className, label, id, placeholder, invalid, error, ...rest } = props;
|
|
2216
|
+
// See Input's `reserve`.
|
|
2217
|
+
const reserve = "error" in props;
|
|
2166
2218
|
const generated = useId();
|
|
2167
2219
|
const fieldId = id ?? generated;
|
|
2168
2220
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
@@ -2173,7 +2225,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function
|
|
|
2173
2225
|
);
|
|
2174
2226
|
if (label === undefined) {
|
|
2175
2227
|
return (
|
|
2176
|
-
<FieldGroup errorEl={errorEl}>
|
|
2228
|
+
<FieldGroup errorEl={errorEl} reserve={reserve}>
|
|
2177
2229
|
<textarea
|
|
2178
2230
|
ref={ref}
|
|
2179
2231
|
id={id}
|
|
@@ -2187,7 +2239,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function
|
|
|
2187
2239
|
);
|
|
2188
2240
|
}
|
|
2189
2241
|
return (
|
|
2190
|
-
<FieldGroup errorEl={errorEl}>
|
|
2242
|
+
<FieldGroup errorEl={errorEl} reserve={reserve}>
|
|
2191
2243
|
<FloatingField className={className} htmlFor={fieldId} label={label}>
|
|
2192
2244
|
<textarea
|
|
2193
2245
|
ref={ref}
|
|
@@ -2496,7 +2548,12 @@ export function CardTitle({ className, as: Tag = "div", ...props }: CardTitlePro
|
|
|
2496
2548
|
return (
|
|
2497
2549
|
<Tag
|
|
2498
2550
|
data-slot="card-title"
|
|
2499
|
-
|
|
2551
|
+
// Compact is `leading-snug`, not `leading-none`: there the title sits on a
|
|
2552
|
+
// 12px description `gap-0.5` below, and a line box exactly the cap height left
|
|
2553
|
+
// the hint hugging the title's descenders (every keksdose admin card added
|
|
2554
|
+
// `leading-normal` by hand). Comfortable keeps `leading-none` — its 14px
|
|
2555
|
+
// description is `gap-1.5` away and has always read right.
|
|
2556
|
+
className={cn(compact ? "text-sm font-medium leading-snug" : "font-semibold leading-none", className)}
|
|
2500
2557
|
{...props}
|
|
2501
2558
|
/>
|
|
2502
2559
|
);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { useKitLabels } from "../i18n/kit-labels";
|
|
4
|
+
|
|
5
|
+
/** The words {@link WriteLockProvider} renders on its own behalf. */
|
|
6
|
+
export interface WriteLockLabels {
|
|
7
|
+
/** The tooltip of a locked commit when the provider gives no `reason`. */
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_WRITE_LOCK_LABELS: WriteLockLabels = {
|
|
12
|
+
reason: "You can view this but not change it.",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** What {@link useWriteLock} returns: whether commits are locked here, and why. */
|
|
16
|
+
export interface WriteLock {
|
|
17
|
+
locked: boolean;
|
|
18
|
+
/** The one sentence a locked commit shows in its tooltip. Always set while
|
|
19
|
+
* `locked`; `undefined` when not. */
|
|
20
|
+
reason: ReactNode | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const UNLOCKED: WriteLock = { locked: false, reason: undefined };
|
|
24
|
+
|
|
25
|
+
const WriteLockContext = createContext<WriteLock>(UNLOCKED);
|
|
26
|
+
|
|
27
|
+
export interface WriteLockProviderProps {
|
|
28
|
+
/** Commits below are locked. `false` unlocks a subtree under an outer lock. */
|
|
29
|
+
locked: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The one sentence that says why — "Shared with you to read", "Read-only demo —
|
|
32
|
+
* saving is disabled", "A customer account cannot create curves". The app's policy,
|
|
33
|
+
* already translated. Left out: `labels.reason`.
|
|
34
|
+
*/
|
|
35
|
+
reason?: ReactNode;
|
|
36
|
+
labels?: Partial<WriteLockLabels>;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The read-only lock — **everything renders; only the commit is locked.**
|
|
42
|
+
*
|
|
43
|
+
* keksdose (features/budgets/write-lock.tsx, the read-only demo budget) and
|
|
44
|
+
* Kurvenschmiede (features/sharing/write-lock.tsx, a curve shared to view, a customer
|
|
45
|
+
* account that owns nothing) each built the same `SaveGuard`: the page, its tabs,
|
|
46
|
+
* forms and dialogs render exactly as for an owner, because a viewer looking at half
|
|
47
|
+
* a page is not looking at what they were sent; what changes is that the control
|
|
48
|
+
* that SAVES is off and says why on hover or focus. Both wrapped each Save in a
|
|
49
|
+
* Tooltip and forced a native `disabled` on it — out of the tab order, so the reason
|
|
50
|
+
* never reached a keyboard — and both had to remember it at thirty call sites.
|
|
51
|
+
*
|
|
52
|
+
* Here the page mounts one provider and each commit control opts in with `commit`
|
|
53
|
+
* ({@link Button}, {@link IconButton}, {@link FormActions}): under a lock it takes the
|
|
54
|
+
* kit's `disabledReason` path with this reason — `aria-disabled` and focusable,
|
|
55
|
+
* every activation swallowed, the reason in the kit Tooltip and its description.
|
|
56
|
+
*
|
|
57
|
+
* Who is locked is the app's: the provider takes a boolean, never a role. Fields stay
|
|
58
|
+
* editable — nothing in them reaches the server until the commit is pressed, and a
|
|
59
|
+
* form you cannot fill in is not a demo of the form (keksdose accounts-page). A
|
|
60
|
+
* control that IS its own commit (a toggle that saves on change) reads
|
|
61
|
+
* {@link useWriteLock} and disables itself. The lock is a courtesy, not a guard: the
|
|
62
|
+
* server still refuses the write.
|
|
63
|
+
*
|
|
64
|
+
* Nested providers: the nearest wins, so an inner `locked={false}` reopens a section
|
|
65
|
+
* the caller may write (a viewer's own notes on a shared curve), and an inner lock
|
|
66
|
+
* with its own reason (Kurvenschmiede's owner lock beside the viewer lock) overrides
|
|
67
|
+
* the outer one's sentence.
|
|
68
|
+
*/
|
|
69
|
+
export function WriteLockProvider({ locked, reason, labels, children }: WriteLockProviderProps) {
|
|
70
|
+
// The `writeLock` namespace: prop > `<UiKitProvider labels>` > English.
|
|
71
|
+
const fallback = useKitLabels("writeLock", DEFAULT_WRITE_LOCK_LABELS, labels).reason;
|
|
72
|
+
// `null`/`""`/`false` count as "no reason given": a lock whose tooltip is empty
|
|
73
|
+
// would be a disabled button that cannot say why, the thing this replaces.
|
|
74
|
+
const given = reason !== undefined && reason !== null && reason !== false && reason !== "";
|
|
75
|
+
const resolved = locked ? (given ? reason : fallback) : undefined;
|
|
76
|
+
const value = useMemo<WriteLock>(
|
|
77
|
+
() => (locked ? { locked: true, reason: resolved } : UNLOCKED),
|
|
78
|
+
[locked, resolved],
|
|
79
|
+
);
|
|
80
|
+
return <WriteLockContext.Provider value={value}>{children}</WriteLockContext.Provider>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The nearest {@link WriteLockProvider}'s lock; unlocked with no provider. */
|
|
84
|
+
export function useWriteLock(): WriteLock {
|
|
85
|
+
return useContext(WriteLockContext);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The `disabledReason` a control with `commit` should use: the lock's reason while
|
|
90
|
+
* locked — it wins over the control's own, since it is the answer to the question the
|
|
91
|
+
* hover is asking — else the control's own. Internal to the kit's commit controls.
|
|
92
|
+
*/
|
|
93
|
+
export function useCommitReason(commit: boolean | undefined, own: ReactNode): ReactNode {
|
|
94
|
+
const lock = useWriteLock();
|
|
95
|
+
return commit && lock.locked ? lock.reason : own;
|
|
96
|
+
}
|
|
@@ -38,6 +38,16 @@ import { useEffect, useRef } from "react";
|
|
|
38
38
|
* link while the overlay was open, the router has pushed on top of us, and a blind
|
|
39
39
|
* `history.back()` there would undo their navigation.
|
|
40
40
|
*
|
|
41
|
+
* ## Not undoing the owner's own close
|
|
42
|
+
*
|
|
43
|
+
* Nor when the address under the entry changed in the same breath as the close
|
|
44
|
+
* (keksdose dev #584). A page that opens its editor by PUSHING `?row=x` and closes it
|
|
45
|
+
* by REPLACING the clean URL over the top entry rewrites OUR entry — the overlay sits
|
|
46
|
+
* above `?row=x` — and that rewrite is what unmounts the overlay. Going back from there
|
|
47
|
+
* lands on `?row=x` and the router opens the editor that was just closed. See
|
|
48
|
+
* {@link noteAddress}: an address the overlay LIVED THROUGH (a filter the table wrote
|
|
49
|
+
* while its sheet was up) is still unwound, because the user has acted since.
|
|
50
|
+
*
|
|
41
51
|
* ## The traversal is deferred
|
|
42
52
|
*
|
|
43
53
|
* And it is *scheduled* rather than issued, because `history.go()` lands a task
|
|
@@ -78,6 +88,8 @@ const stack: OverlayEntry[] = [];
|
|
|
78
88
|
*/
|
|
79
89
|
interface PushedEntry {
|
|
80
90
|
id: string;
|
|
91
|
+
/** The full address of the entry (query and hash included) as of the last time the
|
|
92
|
+
* user acted while it was current — see {@link noteAddress}. */
|
|
81
93
|
href: string;
|
|
82
94
|
/** Its overlay is gone but the entry is not — someone above owes this pop. */
|
|
83
95
|
dead: boolean;
|
|
@@ -383,6 +395,30 @@ function currentSentinel(): string | null {
|
|
|
383
395
|
return standing;
|
|
384
396
|
}
|
|
385
397
|
|
|
398
|
+
/**
|
|
399
|
+
* The user is about to act: what the current entry's address is NOW is what the
|
|
400
|
+
* overlay standing on it has lived through.
|
|
401
|
+
*
|
|
402
|
+
* The cleanup needs to tell two same-page query rewrites apart, and the address alone
|
|
403
|
+
* cannot. A `DataTable` with `urlSync` replaces `f.*` over the entry while its filter
|
|
404
|
+
* sheet is up, and the sheet is closed LATER, by a press of its own — that entry is
|
|
405
|
+
* still the sheet's and must be unwound (the audit's drift fix, below). A page whose
|
|
406
|
+
* `?row=` drives the overlay replaces the clean URL over the entry and the overlay
|
|
407
|
+
* unmounts BECAUSE of it (keksdose dev #584) — that entry is the owner's now, and
|
|
408
|
+
* unwinding lands on `?row=` beneath and reopens what was just closed. What separates
|
|
409
|
+
* them is whether the user acted in between: the rewrite that closes an overlay is
|
|
410
|
+
* made by the very press that closes it, so it postdates this snapshot.
|
|
411
|
+
*
|
|
412
|
+
* Capture phase, so the snapshot is taken before any handler of the press runs.
|
|
413
|
+
*/
|
|
414
|
+
function noteAddress(): void {
|
|
415
|
+
const top = pushed[pushed.length - 1];
|
|
416
|
+
if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;
|
|
417
|
+
const tag = taggedSentinel();
|
|
418
|
+
// Read without `currentSentinel`'s repair: an observation must not write history.
|
|
419
|
+
if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) top.href = currentHref();
|
|
420
|
+
}
|
|
421
|
+
|
|
386
422
|
function handlePop() {
|
|
387
423
|
// A pop MOVES us, so the belief is stale by definition: re-read it from the
|
|
388
424
|
// entry we have landed on before anything below consults it, or the repair in
|
|
@@ -422,6 +458,8 @@ function closeOnPop() {
|
|
|
422
458
|
function ensureListening() {
|
|
423
459
|
if (listening || typeof window === "undefined") return;
|
|
424
460
|
window.addEventListener("popstate", handlePop);
|
|
461
|
+
window.addEventListener("pointerdown", noteAddress, true);
|
|
462
|
+
window.addEventListener("keydown", noteAddress, true);
|
|
425
463
|
listening = true;
|
|
426
464
|
}
|
|
427
465
|
|
|
@@ -489,6 +527,19 @@ export function useOverlayHistory(open: boolean, onClose: () => void): void {
|
|
|
489
527
|
if (consumed.delete(id)) return;
|
|
490
528
|
|
|
491
529
|
const mine = pushed.findIndex((p) => p.id === id);
|
|
530
|
+
// Still our entry — same page, same router `idx` — but its address is not the one
|
|
531
|
+
// the user last acted on: the owner rewrote it as part of closing us (see
|
|
532
|
+
// {@link noteAddress}). The entry is the owner's page now, and the one beneath is
|
|
533
|
+
// the address it replaced — going back would restore exactly what was closed.
|
|
534
|
+
// Nothing to unwind; forget the record rather than re-stamp a page we left.
|
|
535
|
+
if (mine >= 0 && currentHref() !== pushed[mine].href) {
|
|
536
|
+
const tag = taggedSentinel();
|
|
537
|
+
if (tag === id || (tag === null && standing === id && isStillOn(pushed[mine]))) {
|
|
538
|
+
pushed.splice(mine, 1);
|
|
539
|
+
if (standing === id) standing = null;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
492
543
|
// Only unwind an entry that is still THE current one. If a router push landed
|
|
493
544
|
// on top of it, going back would undo the user's navigation instead — and if
|
|
494
545
|
// a SIBLING overlay's sentinel landed on top, that overlay is about to unwind
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -46,6 +46,11 @@ import { DEFAULT_ERROR_BOUNDARY_LABELS } from "../components/error-boundary";
|
|
|
46
46
|
import { DEFAULT_AUTHED_IMAGE_LABELS } from "../components/authed-image";
|
|
47
47
|
import { DEFAULT_IMAGE_GRID_LABELS } from "../components/image-grid";
|
|
48
48
|
import { DEFAULT_LIGHTBOX_LABELS } from "../components/lightbox";
|
|
49
|
+
import { DEFAULT_WRITE_LOCK_LABELS } from "../components/write-lock";
|
|
50
|
+
import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips";
|
|
51
|
+
import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card";
|
|
52
|
+
import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog";
|
|
53
|
+
import { DEFAULT_SERVER_WAKE_LABELS } from "../components/server-wake";
|
|
49
54
|
import {
|
|
50
55
|
DEFAULT_APP_SHELL_LABELS,
|
|
51
56
|
DEFAULT_CALCULATOR_LABELS,
|
|
@@ -128,4 +133,9 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
128
133
|
authedImage: DEFAULT_AUTHED_IMAGE_LABELS,
|
|
129
134
|
imageGrid: DEFAULT_IMAGE_GRID_LABELS,
|
|
130
135
|
lightbox: DEFAULT_LIGHTBOX_LABELS,
|
|
136
|
+
writeLock: DEFAULT_WRITE_LOCK_LABELS,
|
|
137
|
+
accountState: DEFAULT_ACCOUNT_STATE_LABELS,
|
|
138
|
+
shareCard: DEFAULT_SHARE_CARD_LABELS,
|
|
139
|
+
reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,
|
|
140
|
+
serverWake: DEFAULT_SERVER_WAKE_LABELS,
|
|
131
141
|
};
|