@eifi1/ui-kit 0.17.0 → 0.19.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 +83 -4
- package/dist/chart.d.ts +13 -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 +13 -1
- package/dist/components/button-group.d.ts +13 -1
- package/dist/components/calculator.d.ts +13 -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 +13 -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 +13 -1
- package/dist/components/file-button.d.ts +13 -1
- package/dist/components/file-dropzone.d.ts +13 -1
- package/dist/components/floating-panel.d.ts +4 -1
- package/dist/components/floating-panel.js +15 -1
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +13 -1
- package/dist/components/form-actions.js +3 -0
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/legal.d.ts +84 -0
- package/dist/components/legal.js +55 -0
- package/dist/components/legal.js.map +1 -0
- package/dist/components/money-field.d.ts +13 -1
- package/dist/components/number-field.d.ts +14 -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 +13 -1
- package/dist/components/numpad-sheet.d.ts +13 -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 +13 -1
- package/dist/components/server-wake.d.ts +60 -0
- package/dist/components/server-wake.js +79 -0
- package/dist/components/server-wake.js.map +1 -0
- package/dist/components/settings-fields.d.ts +13 -1
- package/dist/components/share-card.d.ts +64 -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 +13 -1
- package/dist/components/time-input.d.ts +13 -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/translation-review-editor.d.ts +56 -0
- package/dist/components/translation-review-editor.js +159 -0
- package/dist/components/translation-review-editor.js.map +1 -0
- package/dist/components/translation-review-labels.d.ts +92 -0
- package/dist/components/translation-review-labels.js +67 -0
- package/dist/components/translation-review-labels.js.map +1 -0
- package/dist/components/translation-review-parts.d.ts +97 -0
- package/dist/components/translation-review-parts.js +163 -0
- package/dist/components/translation-review-parts.js.map +1 -0
- package/dist/components/translation-review.d.ts +94 -0
- package/dist/components/translation-review.js +344 -0
- package/dist/components/translation-review.js.map +1 -0
- package/dist/components/ui.d.ts +13 -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 +13 -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 +13 -1
- package/dist/i18n/defaults.js +15 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +81 -0
- package/dist/i18n/{locales/de.js → german.js} +110 -9
- package/dist/i18n/german.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +13 -1
- package/dist/i18n/kit-labels.js +5 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +243 -0
- package/dist/i18n/languages.js +70 -0
- package/dist/i18n/languages.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +26 -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 +13 -1
- package/dist/i18n/locales/en.js +8 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +13 -1
- package/dist/i18n/locales/es.js +127 -27
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +13 -1
- package/dist/i18n/locales/fr.js +101 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +13 -1
- package/dist/i18n/locales/hu.js +105 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +13 -1
- package/dist/i18n/locales/it.js +134 -34
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +13 -1
- package/dist/i18n/locales/zh.js +101 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +102 -0
- package/dist/i18n/review.js +158 -0
- package/dist/i18n/review.js.map +1 -0
- package/dist/i18n/swiss.d.ts +3 -3
- package/dist/i18n/swiss.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +97 -1
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-D1J2_jyD.d.ts} +236 -9
- package/dist/lib/floating-stack.d.ts +31 -0
- package/dist/lib/floating-stack.js +42 -0
- package/dist/lib/floating-stack.js.map +1 -0
- package/dist/lib/server-wake.d.ts +146 -0
- package/dist/lib/server-wake.js +122 -0
- package/dist/lib/server-wake.js.map +1 -0
- package/dist/lib/translation-review.d.ts +232 -0
- package/dist/lib/translation-review.js +194 -0
- package/dist/lib/translation-review.js.map +1 -0
- package/dist/rhf/fields.d.ts +13 -1
- package/dist/rhf/form.d.ts +13 -1
- package/dist/rhf.d.ts +13 -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 +13 -1
- package/dist/shell/top-bar-brand.d.ts +13 -1
- package/dist/shell/topbar-action-menu.d.ts +2 -0
- package/dist/shell.d.ts +13 -1
- package/dist/wizard/stepper-nav.d.ts +13 -1
- package/dist/wizard.d.ts +13 -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/floating-panel.tsx +19 -1
- package/src/components/form-actions.tsx +22 -3
- package/src/components/legal.tsx +159 -0
- 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 +156 -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/translation-review-editor.tsx +261 -0
- package/src/components/translation-review-labels.ts +152 -0
- package/src/components/translation-review-parts.tsx +269 -0
- package/src/components/translation-review.tsx +502 -0
- 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 +14 -0
- package/src/i18n/{locales/de.ts → german.ts} +120 -13
- package/src/i18n/kit-labels.tsx +29 -2
- package/src/i18n/languages.ts +285 -0
- package/src/i18n/locales/de-CH.ts +17 -8
- package/src/i18n/locales/en.ts +8 -0
- package/src/i18n/locales/es.ts +128 -26
- package/src/i18n/locales/fr.ts +103 -0
- package/src/i18n/locales/hu.ts +107 -0
- package/src/i18n/locales/it.ts +135 -33
- package/src/i18n/locales/zh.ts +102 -0
- package/src/i18n/review.ts +193 -0
- package/src/i18n/swiss.ts +3 -3
- package/src/index.ts +86 -1
- package/src/lib/floating-stack.ts +66 -0
- package/src/lib/server-wake.ts +291 -0
- package/src/lib/translation-review.ts +465 -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/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,13 @@ 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_LEGAL_LABELS } from "../components/legal";
|
|
51
|
+
import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips";
|
|
52
|
+
import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card";
|
|
53
|
+
import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog";
|
|
54
|
+
import { DEFAULT_SERVER_WAKE_LABELS } from "../components/server-wake";
|
|
55
|
+
import { DEFAULT_TRANSLATION_REVIEW_LABELS } from "../components/translation-review-labels";
|
|
49
56
|
import {
|
|
50
57
|
DEFAULT_APP_SHELL_LABELS,
|
|
51
58
|
DEFAULT_CALCULATOR_LABELS,
|
|
@@ -128,4 +135,11 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
128
135
|
authedImage: DEFAULT_AUTHED_IMAGE_LABELS,
|
|
129
136
|
imageGrid: DEFAULT_IMAGE_GRID_LABELS,
|
|
130
137
|
lightbox: DEFAULT_LIGHTBOX_LABELS,
|
|
138
|
+
writeLock: DEFAULT_WRITE_LOCK_LABELS,
|
|
139
|
+
accountState: DEFAULT_ACCOUNT_STATE_LABELS,
|
|
140
|
+
shareCard: DEFAULT_SHARE_CARD_LABELS,
|
|
141
|
+
reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,
|
|
142
|
+
serverWake: DEFAULT_SERVER_WAKE_LABELS,
|
|
143
|
+
translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,
|
|
144
|
+
legal: DEFAULT_LEGAL_LABELS,
|
|
131
145
|
};
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { formatFileSize } from "
|
|
2
|
-
import type { UiKitLabels } from "
|
|
1
|
+
import { formatFileSize } from "./kit-labels";
|
|
2
|
+
import type { UiKitLabels } from "./kit-labels";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The kit's
|
|
6
|
-
*
|
|
5
|
+
* The kit's German, formal ("Sie"), written in standard spelling with "ß" — the SOURCE
|
|
6
|
+
* the public `de-CH` catalogue is derived from (via `swiss()`), not a catalogue itself.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Internal on purpose: it lives outside `src/i18n/locales/` because every file there is
|
|
9
|
+
* a public `@eifi1/ui-kit/i18n/<code>` subpath, and the kit ships ONE German — Swiss,
|
|
10
|
+
* formal (0.18.0 removed `/i18n/de`, `/i18n/de-informal` and `/i18n/de-CH-informal`).
|
|
11
|
+
* The text stays written with "ß" because `swiss()` respells at runtime, so a Swiss app
|
|
12
|
+
* also gets an argument it passes in (a file name, a phrase to type) respelled; writing
|
|
13
|
+
* "ss" here instead would change that behaviour for every function label.
|
|
14
|
+
*
|
|
15
|
+
* Counts and sizes are formatted with `numberLocale`'s digits; the words never change.
|
|
12
16
|
*/
|
|
13
|
-
export function
|
|
17
|
+
export function germanLabels(numberLocale: string): UiKitLabels {
|
|
14
18
|
const num = new Intl.NumberFormat(numberLocale);
|
|
15
19
|
const n = (value: number) => num.format(value);
|
|
16
20
|
|
|
@@ -40,8 +44,8 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
40
44
|
thread: "Kommentare",
|
|
41
45
|
empty: "Noch keine Kommentare",
|
|
42
46
|
loading: "Kommentare werden geladen…",
|
|
43
|
-
// The reader's own messages. "Ich" rather than "Sie"
|
|
44
|
-
//
|
|
47
|
+
// The reader's own messages. "Ich" rather than "Sie": it reads the same whether
|
|
48
|
+
// the app addresses its users with "Sie" or "du", so neither has to override it.
|
|
45
49
|
you: "Ich",
|
|
46
50
|
staff: "Team",
|
|
47
51
|
attachments: "Anhänge",
|
|
@@ -91,6 +95,7 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
91
95
|
passkeys: {
|
|
92
96
|
title: "Passkeys",
|
|
93
97
|
description: "Mit Fingerabdruck, Gesichtserkennung oder Geräte-PIN statt Passwort anmelden.",
|
|
98
|
+
descriptionAlongside: "Mit Fingerabdruck, Gesichtserkennung oder Geräte-PIN anmelden. Das Passwort funktioniert weiterhin.",
|
|
94
99
|
empty: "Noch keine Passkeys",
|
|
95
100
|
loading: "Passkeys werden geladen…",
|
|
96
101
|
list: "Registrierte Passkeys",
|
|
@@ -506,6 +511,7 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
506
511
|
confirmDialog: {
|
|
507
512
|
confirm: "Bestätigen",
|
|
508
513
|
cancel: "Abbrechen",
|
|
514
|
+
typed: (text) => `Geben Sie zur Bestätigung „${text}“ ein`,
|
|
509
515
|
},
|
|
510
516
|
floatingPanel: {
|
|
511
517
|
close: "Schließen",
|
|
@@ -591,7 +597,108 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
|
|
|
591
597
|
noPreview: "Für diese Datei ist keine Vorschau verfügbar",
|
|
592
598
|
openInNewTab: "In neuem Tab öffnen",
|
|
593
599
|
},
|
|
600
|
+
writeLock: {
|
|
601
|
+
// Impersonal, like the rest of this catalogue where it can be: an app that
|
|
602
|
+
// addresses its users with "du" then has fewer keys to override.
|
|
603
|
+
reason: "Nur zum Ansehen – Änderungen sind hier nicht möglich.",
|
|
604
|
+
},
|
|
605
|
+
accountState: {
|
|
606
|
+
active: "Aktiv",
|
|
607
|
+
inactive: "Inaktiv",
|
|
608
|
+
invited: "Eingeladen",
|
|
609
|
+
registered: "Registriert",
|
|
610
|
+
unverified: "Nicht bestätigt",
|
|
611
|
+
passwordChange: "Passwortänderung erforderlich",
|
|
612
|
+
},
|
|
613
|
+
shareCard: {
|
|
614
|
+
dialogTitle: "Teilen",
|
|
615
|
+
close: "Schließen",
|
|
616
|
+
email: "E-Mail-Adresse",
|
|
617
|
+
emailOptional: "E-Mail-Adresse (optional)",
|
|
618
|
+
// example.com is reserved for examples (RFC 2606); a German-looking domain is
|
|
619
|
+
// somebody's real one.
|
|
620
|
+
emailPlaceholder: "name@example.com",
|
|
621
|
+
invalidEmail: "Bitte eine vollständige E-Mail-Adresse eingeben.",
|
|
622
|
+
role: "Rolle",
|
|
623
|
+
roleOf: (name) => `Rolle von ${name}`,
|
|
624
|
+
add: "Teilen",
|
|
625
|
+
whoHasAccess: "Wer hat Zugriff",
|
|
626
|
+
nobodyYet: "Bisher hat niemand sonst Zugriff.",
|
|
627
|
+
pending: "Ausstehend",
|
|
628
|
+
openInvite: "Offener Einladungslink",
|
|
629
|
+
copyLink: "Link kopieren",
|
|
630
|
+
team: "Team",
|
|
631
|
+
teamHint: (name) => `Alle in ${name} erhalten Zugriff.`,
|
|
632
|
+
remove: "Zugriff entfernen",
|
|
633
|
+
removeConfirm: (name) => `Zugriff für ${name} entfernen?`,
|
|
634
|
+
revokePending: "Einladung zurückziehen",
|
|
635
|
+
revokePendingConfirm: (name) => `Einladung für ${name} zurückziehen?`,
|
|
636
|
+
failed: "Das hat nicht geklappt. Bitte erneut versuchen.",
|
|
637
|
+
loading: "Wird geladen…",
|
|
638
|
+
},
|
|
639
|
+
reauthDialog: {
|
|
640
|
+
title: "Identität bestätigen",
|
|
641
|
+
description: "Zum Fortfahren das aktuelle Passwort eingeben.",
|
|
642
|
+
password: "Aktuelles Passwort",
|
|
643
|
+
submit: "Weiter",
|
|
644
|
+
cancel: "Abbrechen",
|
|
645
|
+
},
|
|
646
|
+
serverWake: {
|
|
647
|
+
slow: "Lädt noch – das dauert länger als sonst.",
|
|
648
|
+
waking: (appName) =>
|
|
649
|
+
`Der Server schläft ein, wenn ${appName ?? "die App"} gerade niemand benutzt. Die erste Anfrage nach einer Pause muss ihn erst wieder starten. Das kann einen Moment dauern – es geht nichts verloren, die Seite füllt sich von selbst.`,
|
|
650
|
+
},
|
|
651
|
+
legal: {
|
|
652
|
+
navLabel: "Rechtliches",
|
|
653
|
+
},
|
|
654
|
+
translationReview: {
|
|
655
|
+
statusMissing: "Fehlt",
|
|
656
|
+
statusUnreviewed: "Ungeprüft",
|
|
657
|
+
statusChanged: "Seit der Prüfung geändert",
|
|
658
|
+
statusNeedsChange: "Änderung nötig",
|
|
659
|
+
statusApproved: "Bestätigt",
|
|
660
|
+
statusFilter: "Status",
|
|
661
|
+
all: "Alle",
|
|
662
|
+
filterCount: (label, count) => `${label} · ${n(count)}`,
|
|
663
|
+
source: "Texte",
|
|
664
|
+
allSources: "Alle Texte",
|
|
665
|
+
namespace: "Bereich",
|
|
666
|
+
allNamespaces: "Alle Bereiche",
|
|
667
|
+
search: "Schlüssel und Texte durchsuchen",
|
|
668
|
+
placeholdersOnly: (count) => `Nur Platzhalter-Probleme (${n(count)})`,
|
|
669
|
+
placeholderChip: "Platzhalter",
|
|
670
|
+
key: "Schlüssel",
|
|
671
|
+
statusColumn: "Status",
|
|
672
|
+
empty: "Keine Texte passen zu diesen Filtern.",
|
|
673
|
+
missingText: "Fehlt in dieser Sprache",
|
|
674
|
+
progress: (approved, total) => `${n(approved)} von ${n(total)} bestätigt`,
|
|
675
|
+
localeProgress: (approved, total) => `${n(approved)}/${n(total)}`,
|
|
676
|
+
locales: "Sprachen",
|
|
677
|
+
approve: "Bestätigen",
|
|
678
|
+
flag: "Änderung nötig",
|
|
679
|
+
suggest: "Übersetzung vorschlagen",
|
|
680
|
+
reset: "Als ungeprüft markieren",
|
|
681
|
+
cancel: "Abbrechen",
|
|
682
|
+
approveSelected: "Auswahl bestätigen",
|
|
683
|
+
resetSelected: "Auswahl als ungeprüft markieren",
|
|
684
|
+
selectShown: "Alle angezeigten auswählen",
|
|
685
|
+
changedSince: "Dieser Text wurde nach der Prüfung geändert. Bitte lesen Sie ihn noch einmal.",
|
|
686
|
+
reviewedWording: "Wortlaut bei der Prüfung",
|
|
687
|
+
reviewedReference: "Vorlage bei der Prüfung",
|
|
688
|
+
placeholderMismatch: (reference, text) =>
|
|
689
|
+
`Die Platzhalter weichen von der Vorlage ab – Vorlage: ${reference}; dieser Text: ${text}. Die App setzt dort Werte ein, deshalb müssen sie genau so bleiben.`,
|
|
690
|
+
lastApproved: (name, date) => `Bestätigt von ${name} am ${date}`,
|
|
691
|
+
lastFlagged: (name, date) => `Als änderungsbedürftig markiert von ${name} am ${date}`,
|
|
692
|
+
// Dative, after "von": "Bestätigt von einem gelöschten Konto".
|
|
693
|
+
erasedReviewer: "einem gelöschten Konto",
|
|
694
|
+
suggestion: "Bessere Formulierung",
|
|
695
|
+
translation: "Übersetzung",
|
|
696
|
+
note: "Notiz (optional)",
|
|
697
|
+
notePlaceholder: "Was falsch ist oder worauf zu achten ist",
|
|
698
|
+
readOnly: "Sie können diese Sprache lesen, aber nicht prüfen.",
|
|
699
|
+
scope: (areas) => `Ihre Prüfung ist beschränkt auf: ${areas}.`,
|
|
700
|
+
exportCorrections: (count) => `Korrekturen exportieren (${n(count)})`,
|
|
701
|
+
failed: "Das hat nicht geklappt. Bitte erneut versuchen.",
|
|
702
|
+
},
|
|
594
703
|
};
|
|
595
704
|
}
|
|
596
|
-
|
|
597
|
-
export const UI_KIT_LABELS_DE: UiKitLabels = uiKitLabelsDe();
|