@eifi1/ui-kit 0.27.0 → 0.27.1
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.
|
@@ -32,8 +32,11 @@ interface LegalLinksProps {
|
|
|
32
32
|
/** The page being shown; its link is marked `aria-current="page"`. Usually the
|
|
33
33
|
* router's pathname. */
|
|
34
34
|
currentHref?: string;
|
|
35
|
-
/** `false` renders the links without the `<nav>` — inside `AuthLayout`'s
|
|
36
|
-
*
|
|
35
|
+
/** `false` renders the links without the `<nav>` landmark — inside `AuthLayout`'s
|
|
36
|
+
* `footer` or `TopBarActionMenu`'s `footer`, which are named `<nav>`s already. The row
|
|
37
|
+
* keeps its spacing and `className` either way (0.27.1: it used to return the bare
|
|
38
|
+
* links, so in a plain container — Kurvenschmiede's account-menu `<li>` — they ran
|
|
39
|
+
* together as "ImpressumDatenschutzerklärungNutz…" and overflowed). Default true. */
|
|
37
40
|
nav?: boolean;
|
|
38
41
|
labels?: Partial<LegalLabels>;
|
|
39
42
|
className?: string;
|
package/dist/components/legal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
5
|
import { AlertBanner } from "./alert-banner.js";
|
|
@@ -10,15 +10,9 @@ const DEFAULT_LEGAL_LABELS = {
|
|
|
10
10
|
function LegalLinks({ links, currentHref, nav = true, labels: labelsProp, className }) {
|
|
11
11
|
const labels = useKitLabels("legal", DEFAULT_LEGAL_LABELS, labelsProp);
|
|
12
12
|
const items = links.map((link) => /* @__PURE__ */ jsx(TextLink, { href: link.href, tone: "muted", current: link.href === currentHref, children: link.label }, link.href));
|
|
13
|
-
|
|
14
|
-
return /* @__PURE__ */ jsx(
|
|
15
|
-
|
|
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
|
-
);
|
|
13
|
+
const rowClass = cn("flex flex-wrap gap-x-4 gap-y-1 text-xs", className);
|
|
14
|
+
if (!nav) return /* @__PURE__ */ jsx("div", { className: rowClass, children: items });
|
|
15
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": labels.navLabel, className: rowClass, children: items });
|
|
22
16
|
}
|
|
23
17
|
function LegalSection({ heading, body, children, id, headingAs: Heading = "h2" }) {
|
|
24
18
|
return /* @__PURE__ */ jsxs("section", { id, className: "scroll-mt-20", children: [
|
|
@@ -1 +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
|
|
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>` landmark — inside `AuthLayout`'s\n * `footer` or `TopBarActionMenu`'s `footer`, which are named `<nav>`s already. The row\n * keeps its spacing and `className` either way (0.27.1: it used to return the bare\n * links, so in a plain container — Kurvenschmiede's account-menu `<li>` — they ran\n * together as \"ImpressumDatenschutzerklärungNutz…\" and overflowed). 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 const rowClass = cn(\"flex flex-wrap gap-x-4 gap-y-1 text-xs\", className);\n if (!nav) return <div className={rowClass}>{items}</div>;\n return (\n <nav aria-label={labels.navLabel} className={rowClass}>\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":";AA+DI,cA8BA,YA9BA;AA7DJ,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AA0BlB,MAAM,uBAAoC;AAAA,EAC/C,UAAU;AACZ;AA2BO,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,QAAM,WAAW,GAAG,0CAA0C,SAAS;AACvE,MAAI,CAAC,IAAK,QAAO,oBAAC,SAAI,WAAW,UAAW,iBAAM;AAClD,SACE,oBAAC,SAAI,cAAY,OAAO,UAAU,WAAW,UAC1C,iBACH;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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
package/src/components/legal.tsx
CHANGED
|
@@ -46,8 +46,11 @@ export interface LegalLinksProps {
|
|
|
46
46
|
/** The page being shown; its link is marked `aria-current="page"`. Usually the
|
|
47
47
|
* router's pathname. */
|
|
48
48
|
currentHref?: string;
|
|
49
|
-
/** `false` renders the links without the `<nav>` — inside `AuthLayout`'s
|
|
50
|
-
*
|
|
49
|
+
/** `false` renders the links without the `<nav>` landmark — inside `AuthLayout`'s
|
|
50
|
+
* `footer` or `TopBarActionMenu`'s `footer`, which are named `<nav>`s already. The row
|
|
51
|
+
* keeps its spacing and `className` either way (0.27.1: it used to return the bare
|
|
52
|
+
* links, so in a plain container — Kurvenschmiede's account-menu `<li>` — they ran
|
|
53
|
+
* together as "ImpressumDatenschutzerklärungNutz…" and overflowed). Default true. */
|
|
51
54
|
nav?: boolean;
|
|
52
55
|
labels?: Partial<LegalLabels>;
|
|
53
56
|
className?: string;
|
|
@@ -62,12 +65,10 @@ export function LegalLinks({ links, currentHref, nav = true, labels: labelsProp,
|
|
|
62
65
|
{link.label}
|
|
63
66
|
</TextLink>
|
|
64
67
|
));
|
|
65
|
-
|
|
68
|
+
const rowClass = cn("flex flex-wrap gap-x-4 gap-y-1 text-xs", className);
|
|
69
|
+
if (!nav) return <div className={rowClass}>{items}</div>;
|
|
66
70
|
return (
|
|
67
|
-
<nav
|
|
68
|
-
aria-label={labels.navLabel}
|
|
69
|
-
className={cn("flex flex-wrap gap-x-4 gap-y-1 text-xs", className)}
|
|
70
|
-
>
|
|
71
|
+
<nav aria-label={labels.navLabel} className={rowClass}>
|
|
71
72
|
{items}
|
|
72
73
|
</nav>
|
|
73
74
|
);
|