@eifi1/ui-kit 0.18.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 +60 -6
- package/dist/chart.d.ts +4 -1
- package/dist/components/amount-input.d.ts +4 -1
- package/dist/components/button-group.d.ts +4 -1
- package/dist/components/calculator.d.ts +4 -1
- package/dist/components/copy-button.d.ts +4 -1
- package/dist/components/facing-pair.d.ts +4 -1
- package/dist/components/file-button.d.ts +4 -1
- package/dist/components/file-dropzone.d.ts +4 -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 +4 -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 +4 -1
- package/dist/components/number-field.d.ts +4 -1
- package/dist/components/number-input.d.ts +4 -1
- package/dist/components/numpad-sheet.d.ts +4 -1
- package/dist/components/series-chart.d.ts +4 -1
- package/dist/components/server-wake.d.ts +4 -2
- package/dist/components/server-wake.js +23 -2
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -1
- package/dist/components/share-card.d.ts +4 -1
- package/dist/components/text-link.d.ts +4 -1
- package/dist/components/time-input.d.ts +4 -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 +4 -1
- package/dist/hooks/use-file-drop.d.ts +4 -1
- package/dist/i18n/defaults.d.ts +4 -1
- package/dist/i18n/defaults.js +5 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +4 -1
- package/dist/i18n/german.js +51 -0
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -1
- 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 +4 -1
- package/dist/i18n/locales/en.d.ts +4 -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 +4 -1
- package/dist/i18n/locales/es.js +83 -33
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +4 -1
- package/dist/i18n/locales/fr.js +50 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +4 -1
- package/dist/i18n/locales/hu.js +51 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +4 -1
- package/dist/i18n/locales/it.js +90 -40
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +4 -1
- package/dist/i18n/locales/zh.js +51 -0
- 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/index.d.ts +9 -1
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-CPbsjlXr.d.ts → kit-labels-D1J2_jyD.d.ts} +7 -0
- 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 +24 -8
- package/dist/lib/server-wake.js +6 -5
- package/dist/lib/server-wake.js.map +1 -1
- 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 +4 -1
- package/dist/rhf/form.d.ts +4 -1
- package/dist/rhf.d.ts +4 -1
- package/dist/shell/app-shell.d.ts +4 -1
- package/dist/shell/top-bar-brand.d.ts +4 -1
- package/dist/shell.d.ts +4 -1
- package/dist/wizard/stepper-nav.d.ts +4 -1
- package/dist/wizard.d.ts +4 -1
- package/package.json +1 -1
- package/src/components/floating-panel.tsx +19 -1
- package/src/components/legal.tsx +159 -0
- package/src/components/server-wake.tsx +37 -5
- 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/i18n/defaults.ts +4 -0
- package/src/i18n/german.ts +52 -0
- package/src/i18n/kit-labels.tsx +7 -0
- package/src/i18n/languages.ts +285 -0
- package/src/i18n/locales/en.ts +8 -0
- package/src/i18n/locales/es.ts +84 -33
- package/src/i18n/locales/fr.ts +51 -0
- package/src/i18n/locales/hu.ts +52 -0
- package/src/i18n/locales/it.ts +91 -40
- package/src/i18n/locales/zh.ts +52 -0
- package/src/i18n/review.ts +193 -0
- package/src/index.ts +63 -0
- package/src/lib/floating-stack.ts +66 -0
- package/src/lib/server-wake.ts +34 -13
- package/src/lib/translation-review.ts +465 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The legal pages' shell — Imprint, Privacy Policy, Terms — for every app (Marcel,
|
|
6
|
+
* 2026-10-02: every app has them; docs/i18n-harmonization.md H10).
|
|
7
|
+
*
|
|
8
|
+
* keksdose and kastlan each carried the same shell, written twice: a back link, the
|
|
9
|
+
* title, a "closed beta, not yet reviewed by a lawyer" notice, sections of heading +
|
|
10
|
+
* running text, and kastlan's row of links to the three pages with the current one
|
|
11
|
+
* marked. Kurvenschmiede had none. The words stay in each app — they are the app's
|
|
12
|
+
* legal statements, reviewed under the `legal` area of the translation review — and
|
|
13
|
+
* only the shape moves here.
|
|
14
|
+
*
|
|
15
|
+
* A CONTENT COLUMN, not a page: keksdose shows its legal pages inside its public
|
|
16
|
+
* landing header and footer, kastlan inside `AuthLayout` (`width="wide" card={false}`,
|
|
17
|
+
* no title of its own). Both put this inside their chrome; a page-owning layout would
|
|
18
|
+
* have fought one of them.
|
|
19
|
+
*/
|
|
20
|
+
interface LegalLabels {
|
|
21
|
+
/** The accessible name of {@link LegalLinks}' `<nav>`. */
|
|
22
|
+
navLabel: string;
|
|
23
|
+
}
|
|
24
|
+
declare const DEFAULT_LEGAL_LABELS: LegalLabels;
|
|
25
|
+
interface LegalLink {
|
|
26
|
+
href: string;
|
|
27
|
+
/** "Imprint", "Privacy Policy", "Terms" — the app's words. */
|
|
28
|
+
label: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
interface LegalLinksProps {
|
|
31
|
+
links: readonly LegalLink[];
|
|
32
|
+
/** The page being shown; its link is marked `aria-current="page"`. Usually the
|
|
33
|
+
* router's pathname. */
|
|
34
|
+
currentHref?: string;
|
|
35
|
+
/** `false` renders the links without the `<nav>` — inside `AuthLayout`'s `footer`,
|
|
36
|
+
* which wraps its footer in a `<nav>` named by `footerLabel` already. Default true. */
|
|
37
|
+
nav?: boolean;
|
|
38
|
+
labels?: Partial<LegalLabels>;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
/** The legal pages as a row of quiet links, the one you are on marked (kastlan's
|
|
42
|
+
* `LegalLinks`). Wraps on a phone. */
|
|
43
|
+
declare function LegalLinks({ links, currentHref, nav, labels: labelsProp, className }: LegalLinksProps): react.JSX.Element;
|
|
44
|
+
interface LegalSectionProps {
|
|
45
|
+
heading: ReactNode;
|
|
46
|
+
/** Running text. Line breaks in it are kept (`white-space: pre-line`), so a
|
|
47
|
+
* translation can hold an address or a list as plain lines. */
|
|
48
|
+
body?: string;
|
|
49
|
+
/** Anything richer than plain text — links, a list — after `body`. */
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
/** The section's `id`, for a link to it from elsewhere ("see §4"). */
|
|
52
|
+
id?: string;
|
|
53
|
+
/** The heading's element. Default `h2`, under the layout's `h1`. */
|
|
54
|
+
headingAs?: "h2" | "h3";
|
|
55
|
+
}
|
|
56
|
+
declare function LegalSection({ heading, body, children, id, headingAs: Heading }: LegalSectionProps): react.JSX.Element;
|
|
57
|
+
interface LegalLayoutProps {
|
|
58
|
+
/** "Imprint", "Privacy Policy", "Terms". */
|
|
59
|
+
title: ReactNode;
|
|
60
|
+
/** Above the title — the app's "← Back" link (`<TextLink icon={ArrowLeft}>`), whose
|
|
61
|
+
* target differs per app (keksdose's start page, kastlan's sign-in). */
|
|
62
|
+
back?: ReactNode;
|
|
63
|
+
/**
|
|
64
|
+
* A notice under the title, as a warning banner with `role="note"` — page furniture,
|
|
65
|
+
* not an alert. Both apps say their legal texts await a lawyer's review; keksdose's
|
|
66
|
+
* privacy page says something more precise than the others (its technical sections
|
|
67
|
+
* describe running code), so the notice is per page, not per layout.
|
|
68
|
+
*/
|
|
69
|
+
notice?: ReactNode;
|
|
70
|
+
/** The sections — {@link LegalSection}s. */
|
|
71
|
+
children: ReactNode;
|
|
72
|
+
/** The three pages, linked at the end of the column with the current one marked.
|
|
73
|
+
* Leave out when the page's chrome already shows them (kastlan's AuthLayout footer). */
|
|
74
|
+
links?: readonly LegalLink[];
|
|
75
|
+
currentHref?: string;
|
|
76
|
+
/** Default `h1`: the page's heading. */
|
|
77
|
+
headingAs?: "h1" | "h2";
|
|
78
|
+
/** Extra classes on the column — `px-0 py-0` inside a chrome that pads already. */
|
|
79
|
+
className?: string;
|
|
80
|
+
labels?: Partial<LegalLabels>;
|
|
81
|
+
}
|
|
82
|
+
declare function LegalLayout({ title, back, notice, children, links, currentHref, headingAs: Heading, className, labels, }: LegalLayoutProps): react.JSX.Element;
|
|
83
|
+
|
|
84
|
+
export { DEFAULT_LEGAL_LABELS, type LegalLabels, LegalLayout, type LegalLayoutProps, type LegalLink, LegalLinks, type LegalLinksProps, LegalSection, type LegalSectionProps };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { AlertBanner } from "./alert-banner.js";
|
|
6
|
+
import { TextLink } from "./text-link.js";
|
|
7
|
+
const DEFAULT_LEGAL_LABELS = {
|
|
8
|
+
navLabel: "Legal"
|
|
9
|
+
};
|
|
10
|
+
function LegalLinks({ links, currentHref, nav = true, labels: labelsProp, className }) {
|
|
11
|
+
const labels = useKitLabels("legal", DEFAULT_LEGAL_LABELS, labelsProp);
|
|
12
|
+
const items = links.map((link) => /* @__PURE__ */ jsx(TextLink, { href: link.href, tone: "muted", current: link.href === currentHref, children: link.label }, link.href));
|
|
13
|
+
if (!nav) return /* @__PURE__ */ jsx(Fragment, { children: items });
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
"nav",
|
|
16
|
+
{
|
|
17
|
+
"aria-label": labels.navLabel,
|
|
18
|
+
className: cn("flex flex-wrap gap-x-4 gap-y-1 text-xs", className),
|
|
19
|
+
children: items
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function LegalSection({ heading, body, children, id, headingAs: Heading = "h2" }) {
|
|
24
|
+
return /* @__PURE__ */ jsxs("section", { id, className: "scroll-mt-20", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold text-[var(--text-primary)]", children: heading }),
|
|
26
|
+
body !== void 0 && /* @__PURE__ */ jsx("p", { className: "mt-2 whitespace-pre-line text-sm leading-relaxed text-[var(--text-secondary)]", children: body }),
|
|
27
|
+
children !== void 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm leading-relaxed text-[var(--text-secondary)]", children })
|
|
28
|
+
] });
|
|
29
|
+
}
|
|
30
|
+
function LegalLayout({
|
|
31
|
+
title,
|
|
32
|
+
back,
|
|
33
|
+
notice,
|
|
34
|
+
children,
|
|
35
|
+
links,
|
|
36
|
+
currentHref,
|
|
37
|
+
headingAs: Heading = "h1",
|
|
38
|
+
className,
|
|
39
|
+
labels
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsxs("article", { className: cn("mx-auto w-full max-w-3xl px-4 py-12", className), children: [
|
|
42
|
+
back !== void 0 && /* @__PURE__ */ jsx("div", { className: "mb-4 text-sm", children: back }),
|
|
43
|
+
/* @__PURE__ */ jsx(Heading, { className: "text-3xl font-bold tracking-tight text-[var(--text-primary)]", children: title }),
|
|
44
|
+
notice !== void 0 && notice !== null && /* @__PURE__ */ jsx(AlertBanner, { tone: "warning", role: "note", className: "mt-4", children: notice }),
|
|
45
|
+
/* @__PURE__ */ jsx("div", { className: "mt-8 space-y-8", children }),
|
|
46
|
+
links !== void 0 && links.length > 0 && /* @__PURE__ */ jsx(LegalLinks, { links, currentHref, labels, className: "mt-12 border-t border-[var(--border)] pt-4" })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
DEFAULT_LEGAL_LABELS,
|
|
51
|
+
LegalLayout,
|
|
52
|
+
LegalLinks,
|
|
53
|
+
LegalSection
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=legal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/legal.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { AlertBanner } from \"./alert-banner\";\nimport { TextLink } from \"./text-link\";\n\n/**\n * The legal pages' shell — Imprint, Privacy Policy, Terms — for every app (Marcel,\n * 2026-10-02: every app has them; docs/i18n-harmonization.md H10).\n *\n * keksdose and kastlan each carried the same shell, written twice: a back link, the\n * title, a \"closed beta, not yet reviewed by a lawyer\" notice, sections of heading +\n * running text, and kastlan's row of links to the three pages with the current one\n * marked. Kurvenschmiede had none. The words stay in each app — they are the app's\n * legal statements, reviewed under the `legal` area of the translation review — and\n * only the shape moves here.\n *\n * A CONTENT COLUMN, not a page: keksdose shows its legal pages inside its public\n * landing header and footer, kastlan inside `AuthLayout` (`width=\"wide\" card={false}`,\n * no title of its own). Both put this inside their chrome; a page-owning layout would\n * have fought one of them.\n */\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\nexport interface LegalLabels {\n /** The accessible name of {@link LegalLinks}' `<nav>`. */\n navLabel: string;\n}\n\nexport const DEFAULT_LEGAL_LABELS: LegalLabels = {\n navLabel: \"Legal\",\n};\n\n// ── Links ─────────────────────────────────────────────────────────────────────\n\nexport interface LegalLink {\n href: string;\n /** \"Imprint\", \"Privacy Policy\", \"Terms\" — the app's words. */\n label: ReactNode;\n}\n\nexport interface LegalLinksProps {\n links: readonly LegalLink[];\n /** The page being shown; its link is marked `aria-current=\"page\"`. Usually the\n * router's pathname. */\n currentHref?: string;\n /** `false` renders the links without the `<nav>` — inside `AuthLayout`'s `footer`,\n * which wraps its footer in a `<nav>` named by `footerLabel` already. Default true. */\n nav?: boolean;\n labels?: Partial<LegalLabels>;\n className?: string;\n}\n\n/** The legal pages as a row of quiet links, the one you are on marked (kastlan's\n * `LegalLinks`). Wraps on a phone. */\nexport function LegalLinks({ links, currentHref, nav = true, labels: labelsProp, className }: LegalLinksProps) {\n const labels = useKitLabels(\"legal\", DEFAULT_LEGAL_LABELS, labelsProp);\n const items = links.map((link) => (\n <TextLink key={link.href} href={link.href} tone=\"muted\" current={link.href === currentHref}>\n {link.label}\n </TextLink>\n ));\n if (!nav) return <>{items}</>;\n return (\n <nav\n aria-label={labels.navLabel}\n className={cn(\"flex flex-wrap gap-x-4 gap-y-1 text-xs\", className)}\n >\n {items}\n </nav>\n );\n}\n\n// ── Section ───────────────────────────────────────────────────────────────────\n\nexport interface LegalSectionProps {\n heading: ReactNode;\n /** Running text. Line breaks in it are kept (`white-space: pre-line`), so a\n * translation can hold an address or a list as plain lines. */\n body?: string;\n /** Anything richer than plain text — links, a list — after `body`. */\n children?: ReactNode;\n /** The section's `id`, for a link to it from elsewhere (\"see §4\"). */\n id?: string;\n /** The heading's element. Default `h2`, under the layout's `h1`. */\n headingAs?: \"h2\" | \"h3\";\n}\n\nexport function LegalSection({ heading, body, children, id, headingAs: Heading = \"h2\" }: LegalSectionProps) {\n return (\n <section id={id} className=\"scroll-mt-20\">\n <Heading className=\"text-lg font-semibold text-[var(--text-primary)]\">{heading}</Heading>\n {body !== undefined && (\n <p className=\"mt-2 whitespace-pre-line text-sm leading-relaxed text-[var(--text-secondary)]\">{body}</p>\n )}\n {children !== undefined && (\n <div className=\"mt-2 text-sm leading-relaxed text-[var(--text-secondary)]\">{children}</div>\n )}\n </section>\n );\n}\n\n// ── Layout ────────────────────────────────────────────────────────────────────\n\nexport interface LegalLayoutProps {\n /** \"Imprint\", \"Privacy Policy\", \"Terms\". */\n title: ReactNode;\n /** Above the title — the app's \"← Back\" link (`<TextLink icon={ArrowLeft}>`), whose\n * target differs per app (keksdose's start page, kastlan's sign-in). */\n back?: ReactNode;\n /**\n * A notice under the title, as a warning banner with `role=\"note\"` — page furniture,\n * not an alert. Both apps say their legal texts await a lawyer's review; keksdose's\n * privacy page says something more precise than the others (its technical sections\n * describe running code), so the notice is per page, not per layout.\n */\n notice?: ReactNode;\n /** The sections — {@link LegalSection}s. */\n children: ReactNode;\n /** The three pages, linked at the end of the column with the current one marked.\n * Leave out when the page's chrome already shows them (kastlan's AuthLayout footer). */\n links?: readonly LegalLink[];\n currentHref?: string;\n /** Default `h1`: the page's heading. */\n headingAs?: \"h1\" | \"h2\";\n /** Extra classes on the column — `px-0 py-0` inside a chrome that pads already. */\n className?: string;\n labels?: Partial<LegalLabels>;\n}\n\nexport function LegalLayout({\n title,\n back,\n notice,\n children,\n links,\n currentHref,\n headingAs: Heading = \"h1\",\n className,\n labels,\n}: LegalLayoutProps) {\n return (\n <article className={cn(\"mx-auto w-full max-w-3xl px-4 py-12\", className)}>\n {back !== undefined && <div className=\"mb-4 text-sm\">{back}</div>}\n <Heading className=\"text-3xl font-bold tracking-tight text-[var(--text-primary)]\">{title}</Heading>\n {notice !== undefined && notice !== null && (\n <AlertBanner tone=\"warning\" role=\"note\" className=\"mt-4\">\n {notice}\n </AlertBanner>\n )}\n <div className=\"mt-8 space-y-8\">{children}</div>\n {links !== undefined && links.length > 0 && (\n <LegalLinks links={links} currentHref={currentHref} labels={labels} className=\"mt-12 border-t border-[var(--border)] pt-4\" />\n )}\n </article>\n );\n}\n"],"mappings":";AA4DI,SAIe,UAJf,KAgCA,YAhCA;AA1DJ,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AA0BlB,MAAM,uBAAoC;AAAA,EAC/C,UAAU;AACZ;AAwBO,SAAS,WAAW,EAAE,OAAO,aAAa,MAAM,MAAM,QAAQ,YAAY,UAAU,GAAoB;AAC7G,QAAM,SAAS,aAAa,SAAS,sBAAsB,UAAU;AACrE,QAAM,QAAQ,MAAM,IAAI,CAAC,SACvB,oBAAC,YAAyB,MAAM,KAAK,MAAM,MAAK,SAAQ,SAAS,KAAK,SAAS,aAC5E,eAAK,SADO,KAAK,IAEpB,CACD;AACD,MAAI,CAAC,IAAK,QAAO,gCAAG,iBAAM;AAC1B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAY,OAAO;AAAA,MACnB,WAAW,GAAG,0CAA0C,SAAS;AAAA,MAEhE;AAAA;AAAA,EACH;AAEJ;AAiBO,SAAS,aAAa,EAAE,SAAS,MAAM,UAAU,IAAI,WAAW,UAAU,KAAK,GAAsB;AAC1G,SACE,qBAAC,aAAQ,IAAQ,WAAU,gBACzB;AAAA,wBAAC,WAAQ,WAAU,oDAAoD,mBAAQ;AAAA,IAC9E,SAAS,UACR,oBAAC,OAAE,WAAU,iFAAiF,gBAAK;AAAA,IAEpG,aAAa,UACZ,oBAAC,SAAI,WAAU,6DAA6D,UAAS;AAAA,KAEzF;AAEJ;AA8BO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,UAAU;AAAA,EACrB;AAAA,EACA;AACF,GAAqB;AACnB,SACE,qBAAC,aAAQ,WAAW,GAAG,uCAAuC,SAAS,GACpE;AAAA,aAAS,UAAa,oBAAC,SAAI,WAAU,gBAAgB,gBAAK;AAAA,IAC3D,oBAAC,WAAQ,WAAU,gEAAgE,iBAAM;AAAA,IACxF,WAAW,UAAa,WAAW,QAClC,oBAAC,eAAY,MAAK,WAAU,MAAK,QAAO,WAAU,QAC/C,kBACH;AAAA,IAEF,oBAAC,SAAI,WAAU,kBAAkB,UAAS;AAAA,IACzC,UAAU,UAAa,MAAM,SAAS,KACrC,oBAAC,cAAW,OAAc,aAA0B,QAAgB,WAAU,8CAA6C;AAAA,KAE/H;AAEJ;","names":[]}
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ComponentProps, Ref } from 'react';
|
|
3
3
|
import { AmountInput } from './amount-input.js';
|
|
4
4
|
import './calculator.js';
|
|
5
|
-
import '../kit-labels-
|
|
5
|
+
import '../kit-labels-D1J2_jyD.js';
|
|
6
6
|
import './data-table-labels.js';
|
|
7
7
|
import './mini-calendar.js';
|
|
8
8
|
import './calendar-heatmap.js';
|
|
@@ -54,6 +54,7 @@ import '../hooks/use-authed-src.js';
|
|
|
54
54
|
import './image-grid.js';
|
|
55
55
|
import './lightbox.js';
|
|
56
56
|
import './write-lock.js';
|
|
57
|
+
import './legal.js';
|
|
57
58
|
import './account-chips.js';
|
|
58
59
|
import '../lib/format.js';
|
|
59
60
|
import '../data-table-3mi2R3so.js';
|
|
@@ -62,6 +63,8 @@ import './swipeable-row.js';
|
|
|
62
63
|
import './reauth-dialog.js';
|
|
63
64
|
import './server-wake.js';
|
|
64
65
|
import '../lib/server-wake.js';
|
|
66
|
+
import './translation-review-labels.js';
|
|
67
|
+
import '../lib/translation-review.js';
|
|
65
68
|
import './numpad-sheet.js';
|
|
66
69
|
|
|
67
70
|
type AmountInputProps = ComponentProps<typeof AmountInput>;
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ComponentProps, ReactNode } from 'react';
|
|
3
3
|
import { NumberInput } from './number-input.js';
|
|
4
4
|
import './calculator.js';
|
|
5
|
-
import '../kit-labels-
|
|
5
|
+
import '../kit-labels-D1J2_jyD.js';
|
|
6
6
|
import './data-table-labels.js';
|
|
7
7
|
import './mini-calendar.js';
|
|
8
8
|
import './calendar-heatmap.js';
|
|
@@ -54,6 +54,7 @@ import '../hooks/use-authed-src.js';
|
|
|
54
54
|
import './image-grid.js';
|
|
55
55
|
import './lightbox.js';
|
|
56
56
|
import './write-lock.js';
|
|
57
|
+
import './legal.js';
|
|
57
58
|
import './account-chips.js';
|
|
58
59
|
import '../lib/format.js';
|
|
59
60
|
import '../data-table-3mi2R3so.js';
|
|
@@ -62,6 +63,8 @@ import './swipeable-row.js';
|
|
|
62
63
|
import './reauth-dialog.js';
|
|
63
64
|
import './server-wake.js';
|
|
64
65
|
import '../lib/server-wake.js';
|
|
66
|
+
import './translation-review-labels.js';
|
|
67
|
+
import '../lib/translation-review.js';
|
|
65
68
|
import './numpad-sheet.js';
|
|
66
69
|
|
|
67
70
|
type NumberInputProps = ComponentProps<typeof NumberInput>;
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { CalculatorButtonLabels } from './calculator.js';
|
|
4
4
|
import { NumberPadSheetLabels } from './numpad-sheet.js';
|
|
5
|
-
import '../kit-labels-
|
|
5
|
+
import '../kit-labels-D1J2_jyD.js';
|
|
6
6
|
import './data-table-labels.js';
|
|
7
7
|
import './mini-calendar.js';
|
|
8
8
|
import './calendar-heatmap.js';
|
|
@@ -54,6 +54,7 @@ import '../hooks/use-authed-src.js';
|
|
|
54
54
|
import './image-grid.js';
|
|
55
55
|
import './lightbox.js';
|
|
56
56
|
import './write-lock.js';
|
|
57
|
+
import './legal.js';
|
|
57
58
|
import './account-chips.js';
|
|
58
59
|
import '../lib/format.js';
|
|
59
60
|
import '../data-table-3mi2R3so.js';
|
|
@@ -62,6 +63,8 @@ import './swipeable-row.js';
|
|
|
62
63
|
import './reauth-dialog.js';
|
|
63
64
|
import './server-wake.js';
|
|
64
65
|
import '../lib/server-wake.js';
|
|
66
|
+
import './translation-review-labels.js';
|
|
67
|
+
import '../lib/translation-review.js';
|
|
65
68
|
|
|
66
69
|
interface NumberInputProps {
|
|
67
70
|
value: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { C as CalculatorLabels } from '../kit-labels-
|
|
3
|
+
import { C as CalculatorLabels } from '../kit-labels-D1J2_jyD.js';
|
|
4
4
|
import './data-table-labels.js';
|
|
5
5
|
import './mini-calendar.js';
|
|
6
6
|
import './calendar-heatmap.js';
|
|
@@ -52,6 +52,7 @@ import '../hooks/use-authed-src.js';
|
|
|
52
52
|
import './image-grid.js';
|
|
53
53
|
import './lightbox.js';
|
|
54
54
|
import './write-lock.js';
|
|
55
|
+
import './legal.js';
|
|
55
56
|
import './account-chips.js';
|
|
56
57
|
import '../lib/format.js';
|
|
57
58
|
import '../data-table-3mi2R3so.js';
|
|
@@ -60,6 +61,8 @@ import './swipeable-row.js';
|
|
|
60
61
|
import './reauth-dialog.js';
|
|
61
62
|
import './server-wake.js';
|
|
62
63
|
import '../lib/server-wake.js';
|
|
64
|
+
import './translation-review-labels.js';
|
|
65
|
+
import '../lib/translation-review.js';
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* A full calculator keypad rendered as a bottom sheet — the mobile counterpart to
|
|
@@ -5,7 +5,7 @@ import { LegendEntry } from './toggle-legend.js';
|
|
|
5
5
|
import { SeriesChartLabels } from './series-chart-labels.js';
|
|
6
6
|
import { TimeTickUnit } from './series-chart-ticks.js';
|
|
7
7
|
import { SeriesChartAxisBudget } from './series-chart-budget.js';
|
|
8
|
-
import { x as ChartTooltipPlacement } from '../kit-labels-
|
|
8
|
+
import { x as ChartTooltipPlacement } from '../kit-labels-D1J2_jyD.js';
|
|
9
9
|
import 'recharts';
|
|
10
10
|
import './data-table-labels.js';
|
|
11
11
|
import './mini-calendar.js';
|
|
@@ -57,6 +57,7 @@ import '../hooks/use-authed-src.js';
|
|
|
57
57
|
import './image-grid.js';
|
|
58
58
|
import './lightbox.js';
|
|
59
59
|
import './write-lock.js';
|
|
60
|
+
import './legal.js';
|
|
60
61
|
import './account-chips.js';
|
|
61
62
|
import '../lib/format.js';
|
|
62
63
|
import '../data-table-3mi2R3so.js';
|
|
@@ -65,6 +66,8 @@ import './swipeable-row.js';
|
|
|
65
66
|
import './reauth-dialog.js';
|
|
66
67
|
import './server-wake.js';
|
|
67
68
|
import '../lib/server-wake.js';
|
|
69
|
+
import './translation-review-labels.js';
|
|
70
|
+
import '../lib/translation-review.js';
|
|
68
71
|
|
|
69
72
|
interface SeriesChartSeries {
|
|
70
73
|
/** The row key this line reads — and the `--color-<key>` it is painted with, so it
|
|
@@ -10,7 +10,8 @@ import { ServerWakeWatcher, ServerWakeStage } from '../lib/server-wake.js';
|
|
|
10
10
|
* Corner-anchored and non-blocking on purpose. The pages underneath already show their
|
|
11
11
|
* own skeletons; what was missing is the explanation, not another overlay. Start
|
|
12
12
|
* corner, above the phone's bottom nav, so it collides with neither the centred toasts
|
|
13
|
-
* nor the nav bar
|
|
13
|
+
* nor the nav bar — and above any FloatingActionButton it would otherwise cover
|
|
14
|
+
* (kastlan's offline pill shares the corner; on a phone the notice spans both).
|
|
14
15
|
*
|
|
15
16
|
* Mount it next to the router, not inside the app layout, so it also covers the login
|
|
16
17
|
* screen and the landing page — a cold start is at its most confusing exactly there,
|
|
@@ -43,7 +44,8 @@ interface ServerWakeNoticeProps {
|
|
|
43
44
|
*/
|
|
44
45
|
navOffset?: string | number;
|
|
45
46
|
/** Extra classes on the fixed anchor — a different width or `start-*`. The bottom
|
|
46
|
-
* offset is an inline style, so move it vertically with {@link navOffset}.
|
|
47
|
+
* offset is an inline style, so move it vertically with {@link navOffset}. Either
|
|
48
|
+
* way it rises above a visible FloatingActionButton under it. */
|
|
47
49
|
className?: string;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { clearanceAbove, subscribeFloating } from "../lib/floating-stack.js";
|
|
6
7
|
import { serverWake } from "../lib/server-wake.js";
|
|
7
8
|
import { AlertBanner } from "./alert-banner.js";
|
|
8
9
|
import { Spinner } from "./ui.js";
|
|
@@ -14,16 +15,36 @@ function useServerWakeStage(watcher = serverWake) {
|
|
|
14
15
|
return useSyncExternalStore(watcher.subscribe, watcher.getStage, () => "idle");
|
|
15
16
|
}
|
|
16
17
|
const px = (v) => typeof v === "number" ? `${v}px` : v;
|
|
18
|
+
const STACK_GAP = "0.5rem";
|
|
19
|
+
function useFloatingClearance(anchor, stage) {
|
|
20
|
+
const [clearance, setClearance] = useState(null);
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
if (stage === "idle") return;
|
|
23
|
+
const measure = () => setClearance(clearanceAbove(anchor.current));
|
|
24
|
+
measure();
|
|
25
|
+
const unsubscribe = subscribeFloating(measure);
|
|
26
|
+
window.addEventListener("resize", measure);
|
|
27
|
+
return () => {
|
|
28
|
+
unsubscribe();
|
|
29
|
+
window.removeEventListener("resize", measure);
|
|
30
|
+
};
|
|
31
|
+
}, [anchor, stage]);
|
|
32
|
+
return stage === "idle" ? null : clearance;
|
|
33
|
+
}
|
|
17
34
|
function ServerWakeNotice({ watcher, appName, labels: labelsProp, navOffset, className }) {
|
|
18
35
|
const labels = useKitLabels("serverWake", DEFAULT_SERVER_WAKE_LABELS, labelsProp);
|
|
19
36
|
const stage = useServerWakeStage(watcher);
|
|
20
37
|
const waking = stage === "waking";
|
|
38
|
+
const anchor = useRef(null);
|
|
39
|
+
const clearance = useFloatingClearance(anchor, stage);
|
|
40
|
+
const base = `calc(max(${navOffset === void 0 ? "var(--app-nav-h, 0px)" : px(navOffset)}, env(safe-area-inset-bottom, 0px)) + 1rem)`;
|
|
21
41
|
const style = {
|
|
22
|
-
bottom:
|
|
42
|
+
bottom: clearance === null ? base : `max(${base}, calc(${clearance}px + ${STACK_GAP}))`
|
|
23
43
|
};
|
|
24
44
|
return /* @__PURE__ */ jsx(
|
|
25
45
|
"div",
|
|
26
46
|
{
|
|
47
|
+
ref: anchor,
|
|
27
48
|
role: "status",
|
|
28
49
|
"aria-live": "polite",
|
|
29
50
|
"data-stage": stage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/server-wake.tsx"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport type { CSSProperties } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { serverWake } from \"../lib/server-wake\";\nimport type { ServerWakeStage, ServerWakeWatcher } from \"../lib/server-wake\";\nimport { AlertBanner } from \"./alert-banner\";\nimport { Spinner } from \"./ui\";\n\n/**\n * The visible half of the cold-start watchdog (`lib/server-wake.ts`), keksdose's\n * `app/server-wake-notice.tsx` lifted into the kit: after a couple of seconds of an\n * unanswered read it says the app is still loading, and after seven it says WHY — the\n * server sleeps when nobody is using it, and the first request has to start it again.\n *\n * Corner-anchored and non-blocking on purpose. The pages underneath already show their\n * own skeletons; what was missing is the explanation, not another overlay. Start\n * corner, above the phone's bottom nav, so it collides with neither the centred toasts\n * nor the nav bar.\n *\n * Mount it next to the router, not inside the app layout, so it also covers the login\n * screen and the landing page — a cold start is at its most confusing exactly there,\n * before anything of the app is on screen.\n */\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\nexport interface ServerWakeLabels {\n /** Past `slowMs` (2 s): a warm server would have answered by now. */\n slow: string;\n /** Past `wakingMs` (7 s): the explanation. `appName` is the notice's prop; without\n * one the sentence says \"the app\". */\n waking: (appName?: string) => string;\n}\n\nexport const DEFAULT_SERVER_WAKE_LABELS: ServerWakeLabels = {\n slow: \"Still loading — this is taking longer than usual.\",\n waking: (appName) =>\n `The server goes to sleep when nobody is using ${appName ?? \"the app\"}, so the first request after a break has to start it again. That can take a moment — nothing is lost, the page fills in by itself.`,\n};\n\n// ── Hook ──────────────────────────────────────────────────────────────────────\n\n/** The watcher's current stage, re-rendering on every change. Default: the shared\n * {@link serverWake} instance. */\nexport function useServerWakeStage(watcher: ServerWakeWatcher = serverWake): ServerWakeStage {\n // The server snapshot is `idle`: nothing is in flight while the HTML is rendered.\n return useSyncExternalStore(watcher.subscribe, watcher.getStage, () => \"idle\");\n}\n\n// ── Notice ────────────────────────────────────────────────────────────────────\n\nexport interface ServerWakeNoticeProps {\n /** Default: the shared {@link serverWake} instance. Pass the app's own when it was\n * made with {@link createServerWake} (e.g. to watch the login POST too). */\n watcher?: ServerWakeWatcher;\n /** Named in the `waking` sentence (\"…when nobody is using Keksdose…\"). Default: the\n * label says \"the app\". */\n appName?: string;\n labels?: Partial<ServerWakeLabels>;\n /**\n * The height to keep clear at the bottom — AppShell's bottom nav. Default: its\n * measured `--app-nav-h` (0px from `md` up and on pages without a shell, such as the\n * login screen), so nothing has to be passed under an AppShell. The safe area is\n * respected either way, and a 1rem gap is added. Numbers are px.\n */\n navOffset?: string | number;\n /** Extra classes on the fixed anchor — a different width or `start-*`. The bottom\n * offset is an inline style, so move it vertically with {@link navOffset}. */\n className?: string;\n}\n\nconst px = (v: string | number) => (typeof v === \"number\" ? `${v}px` : v);\n\n/**\n * Renders nothing visible while idle. The LIVE REGION, though, is always mounted: a\n * `role=\"status\"` element inserted together with its text is announced unreliably\n * (several screen readers only speak changes to a region they already knew about),\n * so the region waits empty and the banner appears inside it. keksdose's notice\n * mounted the region and the words at once.\n */\nexport function ServerWakeNotice({ watcher, appName, labels: labelsProp, navOffset, className }: ServerWakeNoticeProps) {\n const labels = useKitLabels(\"serverWake\", DEFAULT_SERVER_WAKE_LABELS, labelsProp);\n const stage = useServerWakeStage(watcher);\n const waking = stage === \"waking\";\n const
|
|
1
|
+
{"version":3,"sources":["../../src/components/server-wake.tsx"],"sourcesContent":["import { useLayoutEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type { CSSProperties, RefObject } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { clearanceAbove, subscribeFloating } from \"../lib/floating-stack\";\nimport { serverWake } from \"../lib/server-wake\";\nimport type { ServerWakeStage, ServerWakeWatcher } from \"../lib/server-wake\";\nimport { AlertBanner } from \"./alert-banner\";\nimport { Spinner } from \"./ui\";\n\n/**\n * The visible half of the cold-start watchdog (`lib/server-wake.ts`), keksdose's\n * `app/server-wake-notice.tsx` lifted into the kit: after a couple of seconds of an\n * unanswered read it says the app is still loading, and after seven it says WHY — the\n * server sleeps when nobody is using it, and the first request has to start it again.\n *\n * Corner-anchored and non-blocking on purpose. The pages underneath already show their\n * own skeletons; what was missing is the explanation, not another overlay. Start\n * corner, above the phone's bottom nav, so it collides with neither the centred toasts\n * nor the nav bar — and above any FloatingActionButton it would otherwise cover\n * (kastlan's offline pill shares the corner; on a phone the notice spans both).\n *\n * Mount it next to the router, not inside the app layout, so it also covers the login\n * screen and the landing page — a cold start is at its most confusing exactly there,\n * before anything of the app is on screen.\n */\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\nexport interface ServerWakeLabels {\n /** Past `slowMs` (2 s): a warm server would have answered by now. */\n slow: string;\n /** Past `wakingMs` (7 s): the explanation. `appName` is the notice's prop; without\n * one the sentence says \"the app\". */\n waking: (appName?: string) => string;\n}\n\nexport const DEFAULT_SERVER_WAKE_LABELS: ServerWakeLabels = {\n slow: \"Still loading — this is taking longer than usual.\",\n waking: (appName) =>\n `The server goes to sleep when nobody is using ${appName ?? \"the app\"}, so the first request after a break has to start it again. That can take a moment — nothing is lost, the page fills in by itself.`,\n};\n\n// ── Hook ──────────────────────────────────────────────────────────────────────\n\n/** The watcher's current stage, re-rendering on every change. Default: the shared\n * {@link serverWake} instance. */\nexport function useServerWakeStage(watcher: ServerWakeWatcher = serverWake): ServerWakeStage {\n // The server snapshot is `idle`: nothing is in flight while the HTML is rendered.\n return useSyncExternalStore(watcher.subscribe, watcher.getStage, () => \"idle\");\n}\n\n// ── Notice ────────────────────────────────────────────────────────────────────\n\nexport interface ServerWakeNoticeProps {\n /** Default: the shared {@link serverWake} instance. Pass the app's own when it was\n * made with {@link createServerWake} (e.g. to watch the login POST too). */\n watcher?: ServerWakeWatcher;\n /** Named in the `waking` sentence (\"…when nobody is using Keksdose…\"). Default: the\n * label says \"the app\". */\n appName?: string;\n labels?: Partial<ServerWakeLabels>;\n /**\n * The height to keep clear at the bottom — AppShell's bottom nav. Default: its\n * measured `--app-nav-h` (0px from `md` up and on pages without a shell, such as the\n * login screen), so nothing has to be passed under an AppShell. The safe area is\n * respected either way, and a 1rem gap is added. Numbers are px.\n */\n navOffset?: string | number;\n /** Extra classes on the fixed anchor — a different width or `start-*`. The bottom\n * offset is an inline style, so move it vertically with {@link navOffset}. Either\n * way it rises above a visible FloatingActionButton under it. */\n className?: string;\n}\n\nconst px = (v: string | number) => (typeof v === \"number\" ? `${v}px` : v);\n\n/** Gap between the notice and a floating control it stacks above. */\nconst STACK_GAP = \"0.5rem\";\n\n/**\n * How far off the bottom the notice must sit to clear the FloatingActionButtons under\n * it (`lib/floating-stack.ts`), re-measured when one appears, goes, resizes or the\n * viewport does — or `null` while it is idle or nothing is under it. kastlan 0.18.\n */\nfunction useFloatingClearance(anchor: RefObject<HTMLDivElement | null>, stage: ServerWakeStage): number | null {\n const [clearance, setClearance] = useState<number | null>(null);\n useLayoutEffect(() => {\n if (stage === \"idle\") return;\n const measure = () => setClearance(clearanceAbove(anchor.current));\n measure();\n const unsubscribe = subscribeFloating(measure);\n window.addEventListener(\"resize\", measure);\n return () => {\n unsubscribe();\n window.removeEventListener(\"resize\", measure);\n };\n }, [anchor, stage]);\n // Idle, the last measurement is stale and nothing shows anyway.\n return stage === \"idle\" ? null : clearance;\n}\n\n/**\n * Renders nothing visible while idle. The LIVE REGION, though, is always mounted: a\n * `role=\"status\"` element inserted together with its text is announced unreliably\n * (several screen readers only speak changes to a region they already knew about),\n * so the region waits empty and the banner appears inside it. keksdose's notice\n * mounted the region and the words at once.\n */\nexport function ServerWakeNotice({ watcher, appName, labels: labelsProp, navOffset, className }: ServerWakeNoticeProps) {\n const labels = useKitLabels(\"serverWake\", DEFAULT_SERVER_WAKE_LABELS, labelsProp);\n const stage = useServerWakeStage(watcher);\n const waking = stage === \"waking\";\n const anchor = useRef<HTMLDivElement>(null);\n const clearance = useFloatingClearance(anchor, stage);\n const base = `calc(max(${navOffset === undefined ? \"var(--app-nav-h, 0px)\" : px(navOffset)}, env(safe-area-inset-bottom, 0px)) + 1rem)`;\n const style: CSSProperties = {\n bottom: clearance === null ? base : `max(${base}, calc(${clearance}px + ${STACK_GAP}))`,\n };\n\n return (\n <div\n ref={anchor}\n role=\"status\"\n aria-live=\"polite\"\n data-stage={stage}\n style={style}\n // `pointer-events-none` on the empty anchor: a fixed box, even an empty one, is not\n // allowed to swallow a tap on what it sits over.\n className={cn(\n \"fixed start-4 z-50 max-w-[min(24rem,calc(100vw-2rem))]\",\n stage === \"idle\" && \"pointer-events-none\",\n className,\n )}\n >\n {stage !== \"idle\" && (\n // `elevated`: OPAQUE in both themes (keksdose live #209) — the notice that\n // explains a stall is read over whatever stalled, and a translucent dark tint\n // let the page's own text show through it. A box banner is role-less, so the\n // wrapper above stays the one live region.\n <AlertBanner\n tone={waking ? \"warning\" : \"neutral\"}\n size=\"sm\"\n elevated\n // `label={null}`: a Spinner is its own `role=\"status\"` saying \"Loading…\" —\n // nested here, a second live region and a second announcement ahead of the\n // words that actually explain the wait. Here it is decoration.\n icon={<Spinner label={null} className=\"h-3.5 w-3.5\" />}\n >\n {waking ? labels.waking(appName) : labels.slow}\n </AlertBanner>\n )}\n </div>\n );\n}\n"],"mappings":";AAoJgB;AApJhB,SAAS,iBAAiB,QAAQ,UAAU,4BAA4B;AAGxE,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AACnB,SAAS,gBAAgB,yBAAyB;AAClD,SAAS,kBAAkB;AAE3B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AA6BjB,MAAM,6BAA+C;AAAA,EAC1D,MAAM;AAAA,EACN,QAAQ,CAAC,YACP,iDAAiD,WAAW,SAAS;AACzE;AAMO,SAAS,mBAAmB,UAA6B,YAA6B;AAE3F,SAAO,qBAAqB,QAAQ,WAAW,QAAQ,UAAU,MAAM,MAAM;AAC/E;AAyBA,MAAM,KAAK,CAAC,MAAwB,OAAO,MAAM,WAAW,GAAG,CAAC,OAAO;AAGvE,MAAM,YAAY;AAOlB,SAAS,qBAAqB,QAA0C,OAAuC;AAC7G,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAC9D,kBAAgB,MAAM;AACpB,QAAI,UAAU,OAAQ;AACtB,UAAM,UAAU,MAAM,aAAa,eAAe,OAAO,OAAO,CAAC;AACjE,YAAQ;AACR,UAAM,cAAc,kBAAkB,OAAO;AAC7C,WAAO,iBAAiB,UAAU,OAAO;AACzC,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,oBAAoB,UAAU,OAAO;AAAA,IAC9C;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,SAAO,UAAU,SAAS,OAAO;AACnC;AASO,SAAS,iBAAiB,EAAE,SAAS,SAAS,QAAQ,YAAY,WAAW,UAAU,GAA0B;AACtH,QAAM,SAAS,aAAa,cAAc,4BAA4B,UAAU;AAChF,QAAM,QAAQ,mBAAmB,OAAO;AACxC,QAAM,SAAS,UAAU;AACzB,QAAM,SAAS,OAAuB,IAAI;AAC1C,QAAM,YAAY,qBAAqB,QAAQ,KAAK;AACpD,QAAM,OAAO,YAAY,cAAc,SAAY,0BAA0B,GAAG,SAAS,CAAC;AAC1F,QAAM,QAAuB;AAAA,IAC3B,QAAQ,cAAc,OAAO,OAAO,OAAO,IAAI,UAAU,SAAS,QAAQ,SAAS;AAAA,EACrF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,aAAU;AAAA,MACV,cAAY;AAAA,MACZ;AAAA,MAGA,WAAW;AAAA,QACT;AAAA,QACA,UAAU,UAAU;AAAA,QACpB;AAAA,MACF;AAAA,MAEC,oBAAU;AAAA;AAAA;AAAA;AAAA,MAKT;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,SAAS,YAAY;AAAA,UAC3B,MAAK;AAAA,UACL,UAAQ;AAAA,UAIR,MAAM,oBAAC,WAAQ,OAAO,MAAM,WAAU,eAAc;AAAA,UAEnD,mBAAS,OAAO,OAAO,OAAO,IAAI,OAAO;AAAA;AAAA,MAC5C;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { aQ as SelectProps } from '../kit-labels-
|
|
3
|
+
import { aQ as SelectProps } from '../kit-labels-D1J2_jyD.js';
|
|
4
4
|
import { ThemePreference } from '../theme/theme-store.js';
|
|
5
5
|
import './data-table-labels.js';
|
|
6
6
|
import './mini-calendar.js';
|
|
@@ -53,6 +53,7 @@ import '../hooks/use-authed-src.js';
|
|
|
53
53
|
import './image-grid.js';
|
|
54
54
|
import './lightbox.js';
|
|
55
55
|
import './write-lock.js';
|
|
56
|
+
import './legal.js';
|
|
56
57
|
import './account-chips.js';
|
|
57
58
|
import '../lib/format.js';
|
|
58
59
|
import '../data-table-3mi2R3so.js';
|
|
@@ -61,6 +62,8 @@ import './swipeable-row.js';
|
|
|
61
62
|
import './reauth-dialog.js';
|
|
62
63
|
import './server-wake.js';
|
|
63
64
|
import '../lib/server-wake.js';
|
|
65
|
+
import './translation-review-labels.js';
|
|
66
|
+
import '../lib/translation-review.js';
|
|
64
67
|
import 'zustand/middleware';
|
|
65
68
|
import 'zustand';
|
|
66
69
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'react';
|
|
2
2
|
import './account-chips.js';
|
|
3
3
|
import './dialog-frame.js';
|
|
4
|
-
export { U as DEFAULT_SHARE_CARD_LABELS, aR as ShareAddRequest, aS as ShareCandidate, aT as ShareCard, aU as ShareCardLabels, aV as ShareCardProps, aW as ShareDialog, aX as ShareDialogProps, aY as ShareGrantee, aZ as SharePanel, a_ as SharePanelProps, a$ as SharePendingGrant, b0 as ShareRole } from '../kit-labels-
|
|
4
|
+
export { U as DEFAULT_SHARE_CARD_LABELS, aR as ShareAddRequest, aS as ShareCandidate, aT as ShareCard, aU as ShareCardLabels, aV as ShareCardProps, aW as ShareDialog, aX as ShareDialogProps, aY as ShareGrantee, aZ as SharePanel, a_ as SharePanelProps, a$ as SharePendingGrant, b0 as ShareRole } from '../kit-labels-D1J2_jyD.js';
|
|
5
5
|
import 'lucide-react';
|
|
6
6
|
import '../lib/format.js';
|
|
7
7
|
import './chip.js';
|
|
@@ -56,6 +56,9 @@ import '../hooks/use-authed-src.js';
|
|
|
56
56
|
import './image-grid.js';
|
|
57
57
|
import './lightbox.js';
|
|
58
58
|
import './write-lock.js';
|
|
59
|
+
import './legal.js';
|
|
59
60
|
import './reauth-dialog.js';
|
|
60
61
|
import './server-wake.js';
|
|
61
62
|
import '../lib/server-wake.js';
|
|
63
|
+
import './translation-review-labels.js';
|
|
64
|
+
import '../lib/translation-review.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactElement, ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, KeyboardEvent } from 'react';
|
|
3
3
|
import { LucideIcon } from 'lucide-react';
|
|
4
|
-
import { aF as KitLinkComponent } from '../kit-labels-
|
|
4
|
+
import { aF as KitLinkComponent } from '../kit-labels-D1J2_jyD.js';
|
|
5
5
|
import './data-table-labels.js';
|
|
6
6
|
import './mini-calendar.js';
|
|
7
7
|
import './calendar-heatmap.js';
|
|
@@ -52,6 +52,7 @@ import '../hooks/use-authed-src.js';
|
|
|
52
52
|
import './image-grid.js';
|
|
53
53
|
import './lightbox.js';
|
|
54
54
|
import './write-lock.js';
|
|
55
|
+
import './legal.js';
|
|
55
56
|
import './account-chips.js';
|
|
56
57
|
import '../lib/format.js';
|
|
57
58
|
import '../data-table-3mi2R3so.js';
|
|
@@ -60,6 +61,8 @@ import './swipeable-row.js';
|
|
|
60
61
|
import './reauth-dialog.js';
|
|
61
62
|
import './server-wake.js';
|
|
62
63
|
import '../lib/server-wake.js';
|
|
64
|
+
import './translation-review-labels.js';
|
|
65
|
+
import '../lib/translation-review.js';
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* An `href` that leaves the app: a scheme (`https:`, `mailto:`, `tel:`) or a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { aE as InputProps } from '../kit-labels-
|
|
2
|
+
import { aE as InputProps } from '../kit-labels-D1J2_jyD.js';
|
|
3
3
|
import './data-table-labels.js';
|
|
4
4
|
import './mini-calendar.js';
|
|
5
5
|
import './calendar-heatmap.js';
|
|
@@ -51,6 +51,7 @@ import '../hooks/use-authed-src.js';
|
|
|
51
51
|
import './image-grid.js';
|
|
52
52
|
import './lightbox.js';
|
|
53
53
|
import './write-lock.js';
|
|
54
|
+
import './legal.js';
|
|
54
55
|
import './account-chips.js';
|
|
55
56
|
import '../lib/format.js';
|
|
56
57
|
import '../data-table-3mi2R3so.js';
|
|
@@ -59,6 +60,8 @@ import './swipeable-row.js';
|
|
|
59
60
|
import './reauth-dialog.js';
|
|
60
61
|
import './server-wake.js';
|
|
61
62
|
import '../lib/server-wake.js';
|
|
63
|
+
import './translation-review-labels.js';
|
|
64
|
+
import '../lib/translation-review.js';
|
|
62
65
|
|
|
63
66
|
/**
|
|
64
67
|
* A time of day, as a `"HH:mm"` string (`"HH:mm:ss"` once `step` asks for seconds).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { TranslationRow, TranslationReviewWrite, TranslationReviewKey } from '../lib/translation-review.js';
|
|
4
|
+
import { TranslationReviewLabels } from './translation-review-labels.js';
|
|
5
|
+
|
|
6
|
+
/** A callback may return a promise: resolve and the editor closes, reject and it stays
|
|
7
|
+
* open with the error. */
|
|
8
|
+
type MaybePromise = void | Promise<unknown>;
|
|
9
|
+
interface TranslationReviewEditorProps {
|
|
10
|
+
row: TranslationRow;
|
|
11
|
+
/** The reference language's own name ("English", "Deutsch") — the app's choice of
|
|
12
|
+
* reference, so the app's word for it. */
|
|
13
|
+
referenceLabel: ReactNode;
|
|
14
|
+
/** The reviewed language's own name ("Français"). */
|
|
15
|
+
localeLabel: ReactNode;
|
|
16
|
+
/** Store a verdict. Left out (or `readOnly`): the editor shows the row and its last
|
|
17
|
+
* verdict, with no fields and no actions. */
|
|
18
|
+
onSave?: (write: TranslationReviewWrite) => MaybePromise;
|
|
19
|
+
/** Clear the verdict — back to unreviewed. Left out: no reset button. */
|
|
20
|
+
onClear?: (key: TranslationReviewKey) => MaybePromise;
|
|
21
|
+
/** The Cancel button — and called after a save or a reset resolves, so the panel
|
|
22
|
+
* that opened the editor closes it. */
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
/** Look, do not touch: a locale the viewer may read but not review. */
|
|
25
|
+
readOnly?: boolean;
|
|
26
|
+
/** How the verdict's date reads. Default: the kit's `formatDate`, medium, in the
|
|
27
|
+
* provider's locale. */
|
|
28
|
+
formatDate?: (iso: string) => string;
|
|
29
|
+
/** Turns a rejection into words. Default {@link TranslationReviewLabels.failed}. */
|
|
30
|
+
formatError?: (error: unknown) => ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One string, opened: the reference beside the translation, what changed since the last
|
|
36
|
+
* verdict, what a machine can see is wrong (the placeholders), and the two things a
|
|
37
|
+
* reviewer can say about it — keksdose's and kastlan's `translation-review-editor.tsx`.
|
|
38
|
+
*
|
|
39
|
+
* "Better wording" opens on the CURRENT text rather than empty, because the usual
|
|
40
|
+
* correction is a word or an ending, and retyping a sentence to change one word is how
|
|
41
|
+
* new mistakes get in. It is sent only with "Needs a change" — approving says the text is
|
|
42
|
+
* fine as it stands. A "Needs a change" with neither a note nor a different wording tells
|
|
43
|
+
* whoever fixes it nothing, so it takes one of the two.
|
|
44
|
+
*
|
|
45
|
+
* A MISSING string (kastlan: the locale lacks the key) cannot be approved — there is
|
|
46
|
+
* nothing to approve. Its field starts empty and is the translation to add; the verdict
|
|
47
|
+
* is NEEDS_CHANGE on the empty text, which the export hands a developer as the string to
|
|
48
|
+
* add.
|
|
49
|
+
*
|
|
50
|
+
* Every action waits for its callback: the buttons hold while the promise is out, a
|
|
51
|
+
* resolution closes the editor through `onClose`, and a rejection keeps it open with what
|
|
52
|
+
* was typed and the error under the buttons.
|
|
53
|
+
*/
|
|
54
|
+
declare function TranslationReviewEditor({ row, referenceLabel, localeLabel, onSave, onClear, onClose, readOnly, formatDate: formatDateProp, formatError, className, labels: labelsProp, }: TranslationReviewEditorProps): react.JSX.Element;
|
|
55
|
+
|
|
56
|
+
export { TranslationReviewEditor, type TranslationReviewEditorProps };
|