@eifi1/ui-kit 0.32.0 → 0.32.2
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/dist/account/delete-account-setting.d.ts +1 -1
- package/dist/admin/admin-action-confirm.d.ts +1 -1
- package/dist/admin/admin-action-log.d.ts +1 -1
- package/dist/admin/admin-action-log.js +4 -2
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +1 -1
- package/dist/admin/reviewer-scope-editor.d.ts +1 -1
- package/dist/admin/role-select.d.ts +1 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +1 -1
- package/dist/admin/user-roster.d.ts +1 -1
- package/dist/auth/register-form.d.ts +1 -1
- package/dist/billing/billing-banner.d.ts +1 -1
- package/dist/billing/billing-labels.d.ts +1 -1
- package/dist/billing/billing-labels.js +3 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/billing-lock.d.ts +1 -1
- package/dist/billing/billing-standing.d.ts +1 -1
- package/dist/billing/plan-card.d.ts +1 -1
- package/dist/billing/plan-card.js +1 -1
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +1 -1
- package/dist/billing/subscription-actions.d.ts +1 -1
- package/dist/billing/subscription-status-chip.d.ts +1 -1
- package/dist/chart.d.ts +2 -2
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +1 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +1 -1
- package/dist/components/appearance-labels.d.ts +6 -0
- package/dist/components/appearance-labels.js +2 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/button-group.d.ts +1 -1
- package/dist/components/button-group.js +7 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -1
- package/dist/components/chip.js +7 -2
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.js +1 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +1 -1
- package/dist/components/copy-button.d.ts +1 -1
- package/dist/components/danger-confirm.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +2 -1
- package/dist/components/data-table-pagination.js +15 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table.js +2 -2
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +11 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/facing-pair.d.ts +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-dropzone.d.ts +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/iban-input.d.ts +1 -1
- package/dist/components/inline-edit-field.js +4 -1
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +1 -1
- package/dist/components/legal-page.d.ts +1 -1
- package/dist/components/legal.d.ts +1 -1
- package/dist/components/money-field.d.ts +1 -1
- package/dist/components/month-picker.js +41 -34
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/number-field.d.ts +1 -1
- package/dist/components/number-input.d.ts +1 -1
- package/dist/components/numpad-sheet.d.ts +1 -1
- package/dist/components/page-header.js +6 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +2 -2
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.js +2 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +1 -1
- package/dist/components/row-actions.js +97 -37
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart.d.ts +1 -1
- package/dist/components/settings-fields.d.ts +7 -3
- package/dist/components/settings-fields.js +2 -1
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +1 -1
- package/dist/components/share-card.js +6 -2
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/status-dot.js +9 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swipeable-row.js +8 -1
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/text-link.d.ts +1 -1
- package/dist/components/time-input.d.ts +1 -1
- package/dist/components/toggle-group.d.ts +9 -2
- package/dist/components/toggle-group.js +3 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -1
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +1 -1
- package/dist/components/ui.js +6 -6
- package/dist/components/ui.js.map +1 -1
- package/dist/components/wizard-stepper.js +20 -14
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{delete-account-setting-BvdQdbv3.d.ts → delete-account-setting-Dy4kJgUm.d.ts} +53 -14
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-context.d.ts +1 -1
- package/dist/feedback/feedback-crash.d.ts +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-labels.d.ts +1 -1
- package/dist/feedback/feedback-menu.d.ts +1 -1
- package/dist/feedback/feedback-record.d.ts +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +1 -1
- package/dist/feedback/feedback-submit.d.ts +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-table.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +1 -1
- package/dist/feedback.d.ts +1 -1
- package/dist/hooks/use-breakpoint.d.ts +19 -8
- package/dist/hooks/use-breakpoint.js +1 -1
- package/dist/hooks/use-breakpoint.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +1 -1
- package/dist/i18n/defaults.d.ts +1 -1
- package/dist/i18n/german.d.ts +1 -1
- package/dist/i18n/german.js +7 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +1 -1
- package/dist/i18n/languages.d.ts +1 -1
- package/dist/i18n/locales/de-CH.d.ts +1 -1
- package/dist/i18n/locales/en.d.ts +1 -1
- package/dist/i18n/locales/es.d.ts +1 -1
- package/dist/i18n/locales/es.js +7 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +1 -1
- package/dist/i18n/locales/fr.js +7 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +1 -1
- package/dist/i18n/locales/hu.js +7 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +1 -1
- package/dist/i18n/locales/it.js +7 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +1 -1
- package/dist/i18n/locales/zh.js +7 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +1 -1
- package/dist/landing/landing-sections.d.ts +1 -1
- package/dist/landing/landing-sections.js +7 -3
- package/dist/landing/landing-sections.js.map +1 -1
- package/dist/landing/public-header.d.ts +1 -1
- package/dist/landing/routing.d.ts +1 -1
- package/dist/rhf/fields.d.ts +1 -1
- package/dist/rhf/form.d.ts +1 -1
- package/dist/rhf.d.ts +1 -1
- package/dist/settings/settings-section.d.ts +1 -1
- package/dist/settings/use-account-appearance.d.ts +1 -1
- package/dist/settings/use-account-appearance.js.map +1 -1
- package/dist/shell/app-shell.d.ts +1 -1
- package/dist/shell/app-shell.js +2 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/top-bar-brand.d.ts +1 -1
- package/dist/shell.d.ts +1 -1
- package/dist/theme/contrast.d.ts +3 -2
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/text-size.d.ts +19 -3
- package/dist/theme/text-size.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/wizard-summary.js +1 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +1 -1
- package/package.json +1 -1
- package/src/admin/admin-action-log.tsx +6 -1
- package/src/billing/billing-labels.ts +16 -0
- package/src/billing/plan-card.tsx +1 -1
- package/src/chart.ts +2 -0
- package/src/components/alert-banner.tsx +5 -1
- package/src/components/appearance-labels.ts +7 -0
- package/src/components/button-group.tsx +21 -0
- package/src/components/chip.tsx +34 -6
- package/src/components/choice-card.tsx +1 -1
- package/src/components/data-table-pagination.tsx +60 -31
- package/src/components/data-table.tsx +8 -4
- package/src/components/date-picker.tsx +32 -1
- package/src/components/inline-edit-field.tsx +20 -1
- package/src/components/month-picker.tsx +6 -1
- package/src/components/page-header.tsx +15 -1
- package/src/components/phone-input.tsx +7 -2
- package/src/components/popover.tsx +4 -0
- package/src/components/row-actions.tsx +156 -49
- package/src/components/settings-fields.tsx +8 -3
- package/src/components/share-card.tsx +21 -2
- package/src/components/status-dot.tsx +25 -2
- package/src/components/swipeable-row.tsx +8 -1
- package/src/components/toggle-group.tsx +26 -12
- package/src/components/translation-review-editor.tsx +1 -1
- package/src/components/translation-review.tsx +5 -4
- package/src/components/ui.tsx +28 -9
- package/src/components/wizard-stepper.tsx +5 -1
- package/src/hooks/use-breakpoint.ts +20 -9
- package/src/i18n/german.ts +4 -0
- package/src/i18n/locales/es.ts +4 -0
- package/src/i18n/locales/fr.ts +4 -0
- package/src/i18n/locales/hu.ts +4 -0
- package/src/i18n/locales/it.ts +4 -0
- package/src/i18n/locales/zh.ts +4 -0
- package/src/index.ts +4 -2
- package/src/landing/landing-sections.tsx +7 -3
- package/src/settings/use-account-appearance.ts +1 -1
- package/src/shell/app-shell.tsx +11 -6
- package/src/theme/contrast.ts +3 -2
- package/src/theme/text-size.ts +19 -3
- package/src/wizard/wizard-summary.tsx +4 -2
- package/tokens.css +15 -7
|
@@ -15,7 +15,7 @@ function WizardSummary({
|
|
|
15
15
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
16
16
|
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-[var(--text-primary)]", children: l.reviewTitle }),
|
|
17
17
|
sections.map((section, sIdx) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
18
|
-
/* @__PURE__ */ jsxs(CardHeader, { className: "pb-2", children: [
|
|
18
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "pb-2", stackAction: false, children: [
|
|
19
19
|
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: section.label }),
|
|
20
20
|
section.stepIndex !== void 0 && onEditStep && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Tooltip, { label: l.edit, children: /* @__PURE__ */ jsx(
|
|
21
21
|
IconButton,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport { Tooltip } from \"../components/tooltip\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The review step: one card per section, each row a label/value pair, each card\n * carrying an edit button back to the step it came from.\n *\n * Sections are data, not children, because \"jump back to step N\" is the whole\n * point of the component and a caller assembling its own cards invariably drops\n * it. Labels and values are `ReactNode`, so a value can be a badge or a link.\n *\n * A section with no `stepIndex` (file-level counts, a total) is a card without an\n * edit button. `disabled` greys every edit button at once — while the commit runs,\n * when jumping back would show a step whose edits can no longer reach the request.\n */\nexport function WizardSummary({\n sections,\n onEditStep,\n disabled = false,\n labels,\n}: {\n sections: SummarySection[];\n /** Called with the section's `stepIndex`. Optional only for a summary whose\n * sections name no step at all. */\n onEditStep?: (stepIndex: number) => void;\n /** Disable every edit button (e.g. `wizard.isSubmitting`). */\n disabled?: boolean;\n labels?: Partial<WizardLabels>;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{l.reviewTitle}</h2>\n {sections.map((section, sIdx) => (\n <Card key={sIdx}>\n {/* CardHeader is a grid, and CardAction is the top-right column it\n reserves — the edit button has to go through it or it stacks under\n the title instead of sitting beside it. */}\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base\">{section.label}</CardTitle>\n {section.stepIndex !== undefined && onEditStep && (\n <CardAction>\n {/* The kit Tooltip, not a native `title`: it shows on keyboard focus too,\n and an app with a one-tooltip rule (keksdose dev#523) sees one. */}\n <Tooltip label={l.edit}>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n aria-label={l.edit}\n disabled={disabled}\n onClick={() => onEditStep(section.stepIndex as number)}\n >\n <Pencil />\n </IconButton>\n </Tooltip>\n </CardAction>\n )}\n </CardHeader>\n <CardContent>\n <dl className=\"grid gap-2 text-sm\">\n {section.items.map((item, iIdx) => (\n <div key={iIdx}>\n {iIdx > 0 && (\n <div className=\"mb-2 border-t border-[var(--border)]\" role=\"presentation\" />\n )}\n <div className=\"flex items-start justify-between gap-4\">\n <dt className=\"text-[var(--text-muted)]\">{item.label}</dt>\n <dd className=\"text-end font-medium text-[var(--text-primary)]\">\n {item.value}\n </dd>\n </div>\n </div>\n ))}\n </dl>\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n"],"mappings":";AAqCM,
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/wizard-summary.tsx"],"sourcesContent":["import { Pencil } from \"lucide-react\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from \"../components/ui\";\nimport { Tooltip } from \"../components/tooltip\";\nimport type { SummarySection, WizardLabels } from \"./types\";\nimport { DEFAULT_WIZARD_LABELS } from \"./types\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The review step: one card per section, each row a label/value pair, each card\n * carrying an edit button back to the step it came from.\n *\n * Sections are data, not children, because \"jump back to step N\" is the whole\n * point of the component and a caller assembling its own cards invariably drops\n * it. Labels and values are `ReactNode`, so a value can be a badge or a link.\n *\n * A section with no `stepIndex` (file-level counts, a total) is a card without an\n * edit button. `disabled` greys every edit button at once — while the commit runs,\n * when jumping back would show a step whose edits can no longer reach the request.\n */\nexport function WizardSummary({\n sections,\n onEditStep,\n disabled = false,\n labels,\n}: {\n sections: SummarySection[];\n /** Called with the section's `stepIndex`. Optional only for a summary whose\n * sections name no step at all. */\n onEditStep?: (stepIndex: number) => void;\n /** Disable every edit button (e.g. `wizard.isSubmitting`). */\n disabled?: boolean;\n labels?: Partial<WizardLabels>;\n}) {\n const l = useKitLabels(\"wizard\", DEFAULT_WIZARD_LABELS, labels);\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{l.reviewTitle}</h2>\n {sections.map((section, sIdx) => (\n <Card key={sIdx}>\n {/* CardHeader is a grid, and CardAction is the top-right column it\n reserves — the edit button has to go through it or it stacks under\n the title instead of sitting beside it. `stackAction={false}`: the pencil\n is named by `aria-label` and stays an icon at Large, so it keeps its\n place beside the title there too (0.32.1). */}\n <CardHeader className=\"pb-2\" stackAction={false}>\n <CardTitle className=\"text-base\">{section.label}</CardTitle>\n {section.stepIndex !== undefined && onEditStep && (\n <CardAction>\n {/* The kit Tooltip, not a native `title`: it shows on keyboard focus too,\n and an app with a one-tooltip rule (keksdose dev#523) sees one. */}\n <Tooltip label={l.edit}>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n aria-label={l.edit}\n disabled={disabled}\n onClick={() => onEditStep(section.stepIndex as number)}\n >\n <Pencil />\n </IconButton>\n </Tooltip>\n </CardAction>\n )}\n </CardHeader>\n <CardContent>\n <dl className=\"grid gap-2 text-sm\">\n {section.items.map((item, iIdx) => (\n <div key={iIdx}>\n {iIdx > 0 && (\n <div className=\"mb-2 border-t border-[var(--border)]\" role=\"presentation\" />\n )}\n <div className=\"flex items-start justify-between gap-4\">\n <dt className=\"text-[var(--text-muted)]\">{item.label}</dt>\n <dd className=\"text-end font-medium text-[var(--text-primary)]\">\n {item.value}\n </dd>\n </div>\n </div>\n ))}\n </dl>\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n"],"mappings":";AAqCM,cAQI,YARJ;AArCN,SAAS,cAAc;AACvB,SAAS,MAAM,YAAY,aAAa,YAAY,WAAW,kBAAkB;AACjF,SAAS,eAAe;AAExB,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AActB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,GAQG;AACD,QAAM,IAAI,aAAa,UAAU,uBAAuB,MAAM;AAE9D,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,QAAG,WAAU,oDAAoD,YAAE,aAAY;AAAA,IAC/E,SAAS,IAAI,CAAC,SAAS,SACtB,qBAAC,QAMC;AAAA,2BAAC,cAAW,WAAU,QAAO,aAAa,OACxC;AAAA,4BAAC,aAAU,WAAU,aAAa,kBAAQ,OAAM;AAAA,QAC/C,QAAQ,cAAc,UAAa,cAClC,oBAAC,cAGC,8BAAC,WAAQ,OAAO,EAAE,MAChB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,cAAY,EAAE;AAAA,YACd;AAAA,YACA,SAAS,MAAM,WAAW,QAAQ,SAAmB;AAAA,YAErD,8BAAC,UAAO;AAAA;AAAA,QACV,GACF,GACF;AAAA,SAEJ;AAAA,MACA,oBAAC,eACC,8BAAC,QAAG,WAAU,sBACX,kBAAQ,MAAM,IAAI,CAAC,MAAM,SACxB,qBAAC,SACE;AAAA,eAAO,KACN,oBAAC,SAAI,WAAU,wCAAuC,MAAK,gBAAe;AAAA,QAE5E,qBAAC,SAAI,WAAU,0CACb;AAAA,8BAAC,QAAG,WAAU,4BAA4B,eAAK,OAAM;AAAA,UACrD,oBAAC,QAAG,WAAU,mDACX,eAAK,OACR;AAAA,WACF;AAAA,WATQ,IAUV,CACD,GACH,GACF;AAAA,SA1CS,IA2CX,CACD;AAAA,KACH;AAEJ;","names":[]}
|
package/dist/wizard.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
|
|
|
6
6
|
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
7
|
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
8
|
import 'react';
|
|
9
|
-
import './delete-account-setting-
|
|
9
|
+
import './delete-account-setting-Dy4kJgUm.js';
|
|
10
10
|
import './components/tooltip.js';
|
|
11
11
|
import './hooks/use-anchored-rect.js';
|
|
12
12
|
import './lib/clipping.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
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.",
|
|
@@ -47,7 +47,10 @@ export type AdminActionKind =
|
|
|
47
47
|
| "transfer"
|
|
48
48
|
| "deletion_request"
|
|
49
49
|
| "deletion_cancel"
|
|
50
|
-
| "erase"
|
|
50
|
+
| "erase"
|
|
51
|
+
// 0.32.1 (keksdose's 0.32 report): server-kit 0.6's `AdminAction.PLAN`, an operator's
|
|
52
|
+
// plan change or grant (docs/billing-harmonization.md §6).
|
|
53
|
+
| "plan";
|
|
51
54
|
|
|
52
55
|
/** Every {@link AdminActionKind}, in the contract's order. */
|
|
53
56
|
export const ADMIN_ACTION_KINDS: readonly AdminActionKind[] = [
|
|
@@ -67,6 +70,7 @@ export const ADMIN_ACTION_KINDS: readonly AdminActionKind[] = [
|
|
|
67
70
|
"deletion_request",
|
|
68
71
|
"deletion_cancel",
|
|
69
72
|
"erase",
|
|
73
|
+
"plan",
|
|
70
74
|
];
|
|
71
75
|
|
|
72
76
|
/** One logged action — the app's `admin_actions` row with the people joined in. */
|
|
@@ -142,6 +146,7 @@ export const DEFAULT_ADMIN_ACTION_LOG_LABELS: AdminActionLogLabels = {
|
|
|
142
146
|
deletion_request: "Deletion requested",
|
|
143
147
|
deletion_cancel: "Deletion cancelled",
|
|
144
148
|
erase: "Account erased",
|
|
149
|
+
plan: "Plan changed",
|
|
145
150
|
},
|
|
146
151
|
};
|
|
147
152
|
|
|
@@ -146,6 +146,19 @@ export interface BillingLabels {
|
|
|
146
146
|
waitingChanges: (count: number) => string;
|
|
147
147
|
/** The sign-out guard's way out for someone who will not pay. */
|
|
148
148
|
discardWaiting: string;
|
|
149
|
+
|
|
150
|
+
/* ── Refusals with no part of their own (0.32.1, kastlan's 0.32 report) ── */
|
|
151
|
+
/**
|
|
152
|
+
* `billing_not_configured`: billing is on but the provider's settings are missing — the
|
|
153
|
+
* operator's mistake, so the words ask the payer to wait, not to act. For the toast or
|
|
154
|
+
* alert after "Choose a plan" or "Payment and invoices" (`isBillingError(err,
|
|
155
|
+
* "billing_not_configured")`). `billing_read_only` and `plan_limit` need none: the
|
|
156
|
+
* write lock, the banner and `PlanLimitNotice` say them.
|
|
157
|
+
*/
|
|
158
|
+
notConfigured: string;
|
|
159
|
+
/** `billing_disabled`: billing is switched off on this server (§2.9), for a page or a
|
|
160
|
+
* link that reached it anyway. */
|
|
161
|
+
disabled: string;
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
/** "today", "tomorrow", "in 5 days". */
|
|
@@ -215,6 +228,9 @@ export const DEFAULT_BILLING_LABELS: BillingLabels = {
|
|
|
215
228
|
waitingChanges: (count) =>
|
|
216
229
|
count === 1 ? "1 change waiting for a plan" : `${count} changes waiting for a plan`,
|
|
217
230
|
discardWaiting: "Discard waiting changes",
|
|
231
|
+
|
|
232
|
+
notConfigured: "Payments aren't set up yet. Please try again later.",
|
|
233
|
+
disabled: "Subscriptions aren't available here.",
|
|
218
234
|
};
|
|
219
235
|
|
|
220
236
|
/** The `billing` namespace, resolved: English, then the provider, then `labels` — which
|
|
@@ -420,7 +420,7 @@ export function PlanPicker({
|
|
|
420
420
|
{legend}
|
|
421
421
|
</legend>
|
|
422
422
|
)}
|
|
423
|
-
<div className={cn("grid gap-2 sm:grid-cols-2 lg:grid-cols-3", gridClassName)}>
|
|
423
|
+
<div className={cn("grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3", gridClassName)}>
|
|
424
424
|
{plans.map((plan) => (
|
|
425
425
|
<PlanCard
|
|
426
426
|
key={plan.code}
|
package/src/chart.ts
CHANGED
|
@@ -16,6 +16,8 @@ export * from "./components/treemap";
|
|
|
16
16
|
// lenkbank's zoomable series chart, its synced x-zoom, the toggle legend and the
|
|
17
17
|
// mirrored-pair axis geometry — moved here so the apps derive them from the package.
|
|
18
18
|
export * from "./components/series-chart";
|
|
19
|
+
// 0.32.1 (keksdose's 0.32 report): the height every chart takes, for an app's wrappers.
|
|
20
|
+
export type { ChartHeight } from "./components/chart-height";
|
|
19
21
|
export * from "./components/chart-zoom";
|
|
20
22
|
export * from "./components/toggle-legend";
|
|
21
23
|
export * from "./components/facing-pair";
|
|
@@ -336,11 +336,15 @@ export function AlertBanner({
|
|
|
336
336
|
) : null;
|
|
337
337
|
|
|
338
338
|
// `relative z-10` so it stays clickable over a whole-row banner's stretched target.
|
|
339
|
+
// `max-w-full flex-wrap`: on a line of its own, two actions side by side ran 57 px
|
|
340
|
+
// past a 360 px phone at Extra large (the demo strip's "Request access" and "Sign in",
|
|
341
|
+
// 0.32.1); held to the line, they wrap there instead. Where they fit in one row,
|
|
342
|
+
// nothing changes.
|
|
339
343
|
const trailing =
|
|
340
344
|
action !== undefined && action !== null ? (
|
|
341
345
|
<div
|
|
342
346
|
className={cn(
|
|
343
|
-
"relative z-10 flex shrink-0 items-center gap-2",
|
|
347
|
+
"relative z-10 flex max-w-full shrink-0 flex-wrap items-center gap-2",
|
|
344
348
|
!strip && !inlineRow && "self-center",
|
|
345
349
|
strip && "ms-auto",
|
|
346
350
|
// Last, on a line of its own, lined up with the message (past the glyph).
|
|
@@ -22,6 +22,12 @@ export interface AppearanceLabels {
|
|
|
22
22
|
contrastHelp: string;
|
|
23
23
|
/** The three choices, in {@link CONTRAST_MODES} order. */
|
|
24
24
|
contrastModes: Record<ContrastMode, string>;
|
|
25
|
+
/**
|
|
26
|
+
* The account could not keep the pick (`useAccountAppearance`'s `pickTextSize` /
|
|
27
|
+
* `pickContrast` rejected): this device keeps it, the account doesn't. For the app's
|
|
28
|
+
* toast or the line under the setting (0.32.1, keksdose's 0.32 report).
|
|
29
|
+
*/
|
|
30
|
+
saveFailed: string;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export const DEFAULT_APPEARANCE_LABELS: AppearanceLabels = {
|
|
@@ -31,6 +37,7 @@ export const DEFAULT_APPEARANCE_LABELS: AppearanceLabels = {
|
|
|
31
37
|
contrast: "Contrast",
|
|
32
38
|
contrastHelp: "More contrast darkens quiet text and lines, and thickens focus frames. System follows this device.",
|
|
33
39
|
contrastModes: { system: "System", standard: "Standard", more: "More" },
|
|
40
|
+
saveFailed: "Couldn't save this to your account. It still applies on this device.",
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
/** The `appearance` namespace, resolved — English, then the provider, then `labels`. For
|
|
@@ -26,6 +26,26 @@ const JOINED_COMMON = cn(
|
|
|
26
26
|
"[&>*]:relative [&>*:focus-within]:z-10",
|
|
27
27
|
);
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A joined row at Large and Extra large wraps rather than run past its container (0.32.1,
|
|
31
|
+
* the 360 px Extra-large sweep; docs/text-size-harmonization.md §4 "nothing
|
|
32
|
+
* overflows"): "Buttons | Chips | Feedback" was 329 px at 150 %, 67 px past a 360 px
|
|
33
|
+
* phone's card. A wrapped row cannot keep one frame with dividers between members — the
|
|
34
|
+
* second line's first member would carry a divider against the frame and no line would
|
|
35
|
+
* part the rows — so at Large the frame is drawn by the members: each its own 1px
|
|
36
|
+
* border, overlapping its neighbours by a pixel on both axes (the container's `ps-px
|
|
37
|
+
* pt-px` takes the overlap back at the edges). On one row that draws the same frame and
|
|
38
|
+
* dividers as Normal; wrapped, every line and every member is parted by one hairline,
|
|
39
|
+
* and the members share out each line (`grow`), so the lines make one block. A group
|
|
40
|
+
* that fits is only as wide as its members, so nothing grows there. A `large:` class,
|
|
41
|
+
* so Normal keeps the frame it has, and it costs no render.
|
|
42
|
+
*/
|
|
43
|
+
const JOINED_WRAP_AT_LARGE = cn(
|
|
44
|
+
"large:max-w-full large:flex-wrap large:border-0 large:ps-px large:pt-px",
|
|
45
|
+
"large:[&>*]:border large:[&>*]:-ms-px large:[&>*]:-mt-px",
|
|
46
|
+
"large:[&>*]:grow large:[&>*>button]:grow large:[&>*>a]:grow",
|
|
47
|
+
);
|
|
48
|
+
|
|
29
49
|
const JOINED: Record<"horizontal" | "vertical", string> = {
|
|
30
50
|
// Logical edges and corners throughout (`border-s`, `rounded-s-*`), so in RTL the
|
|
31
51
|
// first member sits on the right with the right-hand corners rounded — the physical
|
|
@@ -37,6 +57,7 @@ const JOINED: Record<"horizontal" | "vertical", string> = {
|
|
|
37
57
|
"[&>:first-child]:rounded-s-md [&>:last-child]:rounded-e-md",
|
|
38
58
|
"[&>:first-child>button]:rounded-s-md [&>:last-child>button]:rounded-e-md",
|
|
39
59
|
"[&>:first-child>a]:rounded-s-md [&>:last-child>a]:rounded-e-md",
|
|
60
|
+
JOINED_WRAP_AT_LARGE,
|
|
40
61
|
),
|
|
41
62
|
// The block axis is top-to-bottom in both reading directions, so top/bottom are
|
|
42
63
|
// already the logical edges here.
|
package/src/components/chip.tsx
CHANGED
|
@@ -382,8 +382,10 @@ export function refreshChipEdges(): void {
|
|
|
382
382
|
const r = el.getBoundingClientRect();
|
|
383
383
|
const label = el.querySelector<HTMLElement>(".truncate");
|
|
384
384
|
// Not laid out (a hidden ancestor, jsdom), or truncated at its container's edge,
|
|
385
|
-
// where a wider chip would overflow: leave it alone.
|
|
386
|
-
|
|
385
|
+
// where a wider chip would overflow: leave it alone. So too a label wrapped onto
|
|
386
|
+
// a second line at Large (0.32.1): the chip is already as wide as its row lets it
|
|
387
|
+
// be, and a min-width past that would push it over the edge just the same.
|
|
388
|
+
if (!r.width || (label && (label.scrollWidth > label.clientWidth || labelWraps(label)))) return null;
|
|
387
389
|
const width = Math.round(r.width * 64) / 64;
|
|
388
390
|
const target = snappedChipEdgeWidth(width, r.left + window.scrollX, dpr);
|
|
389
391
|
return target === width ? null : target;
|
|
@@ -406,6 +408,13 @@ export function refreshChipEdges(): void {
|
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
410
|
|
|
411
|
+
/** Whether a chip's label runs to more than one line — taller than one and a half of
|
|
412
|
+
* its line height. A line height of `normal` (no number to compare) counts as one line. */
|
|
413
|
+
function labelWraps(label: HTMLElement): boolean {
|
|
414
|
+
const line = Number.parseFloat(getComputedStyle(label).lineHeight);
|
|
415
|
+
return line > 0 && label.clientHeight > 1.5 * line;
|
|
416
|
+
}
|
|
417
|
+
|
|
409
418
|
function scheduleChipEdges() {
|
|
410
419
|
if (snapFrame || typeof requestAnimationFrame === "undefined") return;
|
|
411
420
|
snapFrame = requestAnimationFrame(() => {
|
|
@@ -489,12 +498,30 @@ function assignRef<T>(target: Ref<T> | undefined, value: T | null) {
|
|
|
489
498
|
}
|
|
490
499
|
|
|
491
500
|
const CHIP_PILL = "inline-flex max-w-full items-center rounded-full border transition-colors";
|
|
501
|
+
|
|
492
502
|
// `focus-visible`, not `focus`: a chip commonly receives focus programmatically (the
|
|
493
503
|
// ChipInput moves focus onto one after a removal) and a ring that appears on a
|
|
494
504
|
// pointer click reads as a stuck selection.
|
|
495
505
|
const CHIP_RING = `${FOCUS_RING} focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-surface)]`;
|
|
496
506
|
const CHIP_BASE = `${CHIP_PILL} ${CHIP_RING}`;
|
|
497
507
|
|
|
508
|
+
/**
|
|
509
|
+
* A text chip's label at Large and Extra large (0.32.1, keksdose's 0.32 report;
|
|
510
|
+
* docs/text-size-harmonization.md §4 "nothing truncates"): it wraps instead of ending in
|
|
511
|
+
* "…". keksdose's "Zur Prüfung zurücklegen" read "Zur Prüfung zu…" on a phone at Extra
|
|
512
|
+
* large, which is a different instruction. No size has a fixed height (padding and
|
|
513
|
+
* `min-h-*` only), so the pill grows with its lines; the mark before the text stays
|
|
514
|
+
* centred on them, as in a pill. A `large:` class, so Normal keeps its one-line chip.
|
|
515
|
+
*
|
|
516
|
+
* `break-words`, not `[overflow-wrap:anywhere]`: a status chip often sits in a table
|
|
517
|
+
* cell, and `anywhere` lowers the label's min-content width to one letter, so an
|
|
518
|
+
* auto-sized column can squeeze it to a letter per line (StatusDot's label did, in the
|
|
519
|
+
* showcase's tone table at 360 px, Extra large). `break-word` keeps each word whole
|
|
520
|
+
* wherever the width is the content's to choose, and still breaks one that cannot fit
|
|
521
|
+
* the row at all.
|
|
522
|
+
*/
|
|
523
|
+
const CHIP_LABEL_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
524
|
+
|
|
498
525
|
/**
|
|
499
526
|
* An interactive chip's touch target at Large and Extra large (docs/text-size-harmonization.md
|
|
500
527
|
* §4: "touch targets at least 48 px … for buttons, inputs, chips"): a link or toggle chip
|
|
@@ -814,14 +841,15 @@ export const Chip = forwardRef<HTMLElement, ChipProps>(function Chip(
|
|
|
814
841
|
) : (
|
|
815
842
|
<Icon className={cn(s.icon, "shrink-0")} aria-hidden />
|
|
816
843
|
))}
|
|
817
|
-
{/* Text truncates
|
|
818
|
-
|
|
819
|
-
text (keksdose live
|
|
844
|
+
{/* Text truncates at Normal and wraps at Large (CHIP_LABEL_WRAP_AT_LARGE). Mixed
|
|
845
|
+
children (an icon and a word) sit in a row instead: as a plain span, the svg —
|
|
846
|
+
a block under Tailwind's preflight — stacked above the text (keksdose live
|
|
847
|
+
#358). */}
|
|
820
848
|
<span
|
|
821
849
|
className={cn(
|
|
822
850
|
"min-w-0",
|
|
823
851
|
typeof children === "string" || typeof children === "number"
|
|
824
|
-
? "truncate"
|
|
852
|
+
? cn("truncate", CHIP_LABEL_WRAP_AT_LARGE)
|
|
825
853
|
: "inline-flex items-center gap-1 [&>svg]:size-[1em] [&>svg]:shrink-0",
|
|
826
854
|
)}
|
|
827
855
|
>
|
|
@@ -528,7 +528,7 @@ export function ChoiceCardGroup<T extends string>(props: ChoiceCardGroupProps<T>
|
|
|
528
528
|
{required && <RequiredMark />}
|
|
529
529
|
</legend>
|
|
530
530
|
)}
|
|
531
|
-
<div className={cn("grid gap-2 sm:grid-cols-2", className)}>
|
|
531
|
+
<div className={cn("grid grid-cols-1 gap-2 sm:grid-cols-2", className)}>
|
|
532
532
|
{options.map((o) => (
|
|
533
533
|
<ChoiceCard
|
|
534
534
|
key={o.value}
|
|
@@ -3,6 +3,8 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
|
|
5
5
|
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
6
|
+
import { usePhoneLayout } from "../hooks/use-breakpoint";
|
|
7
|
+
import { useLargeText } from "../hooks/use-large-text";
|
|
6
8
|
|
|
7
9
|
export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;
|
|
8
10
|
|
|
@@ -32,8 +34,28 @@ interface PaginationProps {
|
|
|
32
34
|
density?: "comfortable" | "compact";
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* How many page numbers flank the current one on the strip: ±2, and ±1 on a phone at
|
|
39
|
+
* Large and Extra large (0.32.1, the 360 px Extra-large sweep; docs/text-size-
|
|
40
|
+
* harmonization.md §4). At 150 % a number button is 42 px, and "‹ 1 2 3 … 494 ›" was
|
|
41
|
+
* 312 px on a 360 px phone's 262; one neighbour fewer on each side saves two buttons.
|
|
42
|
+
*/
|
|
43
|
+
const PAGE_WINDOW = 2;
|
|
44
|
+
const PAGE_WINDOW_LARGE_PHONE = 1;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The strip's two parts (0.32.1, same sweep, §4 "nothing overflows"). The arrows keep
|
|
48
|
+
* the ends; the numbers between them wrap, centred, when even the narrower window does
|
|
49
|
+
* not fit — a middle page of a long list ("1 … 249 250 251 … 494") at Extra large, or at
|
|
50
|
+
* Normal on a narrow phone, where the one-row strip ran past the screen. Where the
|
|
51
|
+
* strip fits it is one row, as before.
|
|
52
|
+
*/
|
|
53
|
+
const PAGE_STRIP = "flex min-w-0 max-w-full items-center gap-1";
|
|
54
|
+
const PAGE_NUMBERS = "flex min-w-0 flex-wrap items-center justify-center gap-1";
|
|
55
|
+
|
|
35
56
|
/** Footer for {@link DataTable}: range summary, page-size select, and a
|
|
36
|
-
* windowed page-number strip (first/last + ±2 around the current page
|
|
57
|
+
* windowed page-number strip (first/last + ±2 around the current page, ±1 on a phone
|
|
58
|
+
* at Large — see {@link PAGE_WINDOW}). */
|
|
37
59
|
export function Pagination({
|
|
38
60
|
page,
|
|
39
61
|
totalPages,
|
|
@@ -57,15 +79,20 @@ export function Pagination({
|
|
|
57
79
|
// summary is not formatted here — `pageRange` / `rowCount` take NUMBERS so the
|
|
58
80
|
// translation can format them itself, alongside the words around them.
|
|
59
81
|
const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
82
|
+
const large = useLargeText();
|
|
83
|
+
const phone = usePhoneLayout();
|
|
84
|
+
const radius = large && phone ? PAGE_WINDOW_LARGE_PHONE : PAGE_WINDOW;
|
|
60
85
|
const visible = useMemo(() => {
|
|
61
|
-
|
|
86
|
+
// Every page when that is no longer than the window would be (first, last, the
|
|
87
|
+
// current one, its neighbours and two ellipses).
|
|
88
|
+
if (totalPages <= 2 * radius + 3) return Array.from({ length: totalPages }, (_, i) => i);
|
|
62
89
|
const set = new Set<number>([0, totalPages - 1, page]);
|
|
63
|
-
for (let d = 1; d <=
|
|
90
|
+
for (let d = 1; d <= radius; d++) {
|
|
64
91
|
if (page - d >= 0) set.add(page - d);
|
|
65
92
|
if (page + d <= totalPages - 1) set.add(page + d);
|
|
66
93
|
}
|
|
67
94
|
return Array.from(set).sort((a, b) => a - b);
|
|
68
|
-
}, [page, totalPages]);
|
|
95
|
+
}, [page, totalPages, radius]);
|
|
69
96
|
|
|
70
97
|
const items: (number | "ellipsis")[] = [];
|
|
71
98
|
visible.forEach((p, idx) => {
|
|
@@ -108,7 +135,7 @@ export function Pagination({
|
|
|
108
135
|
)}
|
|
109
136
|
</div>
|
|
110
137
|
{pageSize !== Infinity && totalPages > 1 && (
|
|
111
|
-
<div
|
|
138
|
+
<div data-slot="page-strip" className={PAGE_STRIP}>
|
|
112
139
|
<button
|
|
113
140
|
type="button"
|
|
114
141
|
onClick={() => onPage(Math.max(0, page - 1))}
|
|
@@ -119,32 +146,34 @@ export function Pagination({
|
|
|
119
146
|
{/* "Previous" points back along the line — left in LTR, right in RTL. */}
|
|
120
147
|
<ChevronLeft className={cn("rtl:-scale-x-100", compact ? "size-3.5" : "size-4")} />
|
|
121
148
|
</button>
|
|
122
|
-
|
|
123
|
-
it
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
<div data-slot="page-numbers" className={PAGE_NUMBERS}>
|
|
150
|
+
{items.map((it, i) =>
|
|
151
|
+
it === "ellipsis" ? (
|
|
152
|
+
<span key={`e${i}`} className="px-1 text-[var(--text-placeholder)]">
|
|
153
|
+
…
|
|
154
|
+
</span>
|
|
155
|
+
) : (
|
|
156
|
+
<button
|
|
157
|
+
key={it}
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={() => onPage(it)}
|
|
160
|
+
// Which page you are on is carried only by a background colour
|
|
161
|
+
// otherwise, and the strip is a row of bare numbers with nothing
|
|
162
|
+
// else to tell them apart.
|
|
163
|
+
aria-current={it === page ? "page" : undefined}
|
|
164
|
+
className={cn(
|
|
165
|
+
"rounded",
|
|
166
|
+
compact ? "min-w-6 px-1.5 py-0" : "min-w-7 px-2 py-0.5",
|
|
167
|
+
it === page
|
|
168
|
+
? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]"
|
|
169
|
+
: "hover:bg-[var(--bg-hover)]",
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{num.format(it + 1)}
|
|
173
|
+
</button>
|
|
174
|
+
),
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
148
177
|
<button
|
|
149
178
|
type="button"
|
|
150
179
|
onClick={() => onPage(Math.min(totalPages - 1, page + 1))}
|
|
@@ -35,7 +35,7 @@ import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
|
|
|
35
35
|
import { useOverlayHistory } from "../hooks/use-overlay-history";
|
|
36
36
|
import { FullBleedDialog } from "./full-bleed-dialog";
|
|
37
37
|
import { Popover } from "./popover";
|
|
38
|
-
import {
|
|
38
|
+
import { usePhoneLayout } from "../hooks/use-breakpoint";
|
|
39
39
|
import { useAnnounce } from "../hooks/use-announce";
|
|
40
40
|
import { DEFAULT_DATA_TABLE_LABELS, resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
|
|
41
41
|
import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels";
|
|
@@ -1655,9 +1655,13 @@ export function DataTable<T>({
|
|
|
1655
1655
|
: labels.columnsCount(visibleCount, totalCount);
|
|
1656
1656
|
// Match the `md:` breakpoint at the text size in force: we render either the table or
|
|
1657
1657
|
// the card list — never both — so we don't double up DOM nodes that screen readers and
|
|
1658
|
-
// integration tests would have to disambiguate.
|
|
1659
|
-
// (
|
|
1660
|
-
|
|
1658
|
+
// integration tests would have to disambiguate. `usePhoneLayout()` itself, not its twin
|
|
1659
|
+
// `useBreakpoint("md")`: the same query an app's own phone checks ask (keksdose's
|
|
1660
|
+
// sticky editor footer), so the card list and the full-screen row dialog switch with
|
|
1661
|
+
// them and never a frame apart (§10.4 "usePhoneLayout() is the single answer"; 0.32.1,
|
|
1662
|
+
// keksdose's 0.32 report, where `md` and `max-md` both missed at 767.5 px). Without
|
|
1663
|
+
// `matchMedia` (SSR, jsdom) it is the table, as before.
|
|
1664
|
+
const isMdUp = !usePhoneLayout();
|
|
1661
1665
|
// `edgeFade` (0.25) on the desktop scroller; the phone list never scrolls sideways.
|
|
1662
1666
|
const desktopScroller = useRef<HTMLDivElement | null>(null);
|
|
1663
1667
|
const edgeFadeProps = useEdgeFade(desktopScroller, edgeFade && isMdUp);
|
|
@@ -206,6 +206,16 @@ function CustomTrigger({
|
|
|
206
206
|
return render(attrs, valueId);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The trigger's value wraps instead of truncating at Large and Extra large (0.32.1, the
|
|
211
|
+
* 360 px Extra-large sweep; docs/text-size-harmonization.md §4 "nothing truncates"). A
|
|
212
|
+
* range ("01/09/2026 – 30/09/2026") is 312 px at 150 %, and on a 360 px phone it read
|
|
213
|
+
* "01/09/2026 – 30/…" — its end, the half a reader needs, was the half cut off. It breaks
|
|
214
|
+
* at its spaces; a single date breaks only where it cannot fit at all. A `large:` class,
|
|
215
|
+
* so Normal keeps its one-line field.
|
|
216
|
+
*/
|
|
217
|
+
const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
218
|
+
|
|
209
219
|
/**
|
|
210
220
|
* The field-shaped button that opens the calendar.
|
|
211
221
|
*
|
|
@@ -320,7 +330,7 @@ function DateFieldTrigger({
|
|
|
320
330
|
|
|
321
331
|
return (
|
|
322
332
|
<button {...attrs}>
|
|
323
|
-
<span id={valueId} className={cn("truncate", !hasValue && "text-[var(--text-placeholder)]")}>
|
|
333
|
+
<span id={valueId} className={cn("truncate", VALUE_WRAP_AT_LARGE, !hasValue && "text-[var(--text-placeholder)]")}>
|
|
324
334
|
{/* `|| " "` (a NON-BREAKING space) — triggerText is "" when there is no value and no
|
|
325
335
|
placeholder was passed. An empty span has no line box, so the
|
|
326
336
|
trigger collapses to its padding and sits shorter than every
|
|
@@ -558,6 +568,23 @@ function DateField({
|
|
|
558
568
|
);
|
|
559
569
|
}
|
|
560
570
|
|
|
571
|
+
/**
|
|
572
|
+
* The ‹ date › (today) row on a phone at Large and Extra large (0.32.1, the 360 px
|
|
573
|
+
* Extra-large sweep; docs/text-size-harmonization.md §4): the field takes the first line
|
|
574
|
+
* on its own, whole and with all four corners, and the buttons share the line under it
|
|
575
|
+
* as one joined strip. Three 2.5rem buttons are 180 px at 150 %, and on a 360 px phone
|
|
576
|
+
* they left the field 84 px — "Book… 0" for "Booking date 07/09/2026" — where a 360 px
|
|
577
|
+
* row would hold the date only by cutting it. `large:max-sm:`, the scaled phone width,
|
|
578
|
+
* so a tablet or a desktop at Large keeps the one row, and Normal is unchanged.
|
|
579
|
+
*/
|
|
580
|
+
const STEP_ROW_STACK_AT_LARGE = cn(
|
|
581
|
+
"large:max-sm:flex-wrap large:max-sm:gap-y-2",
|
|
582
|
+
"large:max-sm:[&>[data-slot=date-step-field]]:order-first large:max-sm:[&>[data-slot=date-step-field]]:basis-full",
|
|
583
|
+
"large:max-sm:[&>[data-slot=date-step-field]]:ms-0 large:max-sm:[&_[role=combobox]]:rounded-md",
|
|
584
|
+
// The buttons (each in its tooltip's wrapper) share the line under the field.
|
|
585
|
+
"large:max-sm:[&>span]:flex-1 large:max-sm:[&>span>button]:w-full",
|
|
586
|
+
);
|
|
587
|
+
|
|
561
588
|
/** One ‹ / › day-step button, sized to sit flush beside the field. */
|
|
562
589
|
function StepButton({
|
|
563
590
|
icon: Icon,
|
|
@@ -1105,6 +1132,9 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
1105
1132
|
disabled={disabled}
|
|
1106
1133
|
invalid={messages.isInvalid}
|
|
1107
1134
|
className={step ? "min-w-0 flex-1" : className}
|
|
1135
|
+
// Named for STEP_ROW_STACK_AT_LARGE; only in the row, so a caller's own
|
|
1136
|
+
// `data-slot` on a bare field is left alone.
|
|
1137
|
+
{...(wrapped ? { "data-slot": "date-step-field" } : {})}
|
|
1108
1138
|
hasValue={Boolean(value)}
|
|
1109
1139
|
triggerText={value ? render(value) : (placeholder ?? "")}
|
|
1110
1140
|
onClear={() => onChange("")}
|
|
@@ -1173,6 +1203,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
1173
1203
|
// the trigger, which raising the trigger alone covered. Hover raises nothing: it
|
|
1174
1204
|
// changes no border, and would let a hovered › cover a focused field's edge.
|
|
1175
1205
|
"[&>*:focus-within]:z-20 [&>*:active]:z-20 [&>*:has([aria-invalid=true])]:z-10",
|
|
1206
|
+
STEP_ROW_STACK_AT_LARGE,
|
|
1176
1207
|
className,
|
|
1177
1208
|
)}
|
|
1178
1209
|
>
|
|
@@ -155,6 +155,23 @@ function RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render:
|
|
|
155
155
|
return render(props);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The value at Large and Extra large (0.32.1, keksdose's 0.32 report;
|
|
160
|
+
* docs/text-size-harmonization.md §4 "nothing truncates"): the display button, the
|
|
161
|
+
* locked one and the read-only text wrap instead of ending in "…", and break a word too
|
|
162
|
+
* long for the cell, so an IBAN or an e-mail address shows to its last character. The
|
|
163
|
+
* value IS the button's accessible name and the thing being edited: a reader who needs
|
|
164
|
+
* big type cannot recover a cut-off end by squinting. A `large:` class, so a table of
|
|
165
|
+
* these keeps its one-line cells at Normal.
|
|
166
|
+
*
|
|
167
|
+
* `break-words`, not `[overflow-wrap:anywhere]`: these live in table cells (keksdose's
|
|
168
|
+
* budget and invoice lines), and `anywhere` lowers the value's min-content width to one
|
|
169
|
+
* letter, so an auto-sized column could squeeze an amount to a digit per line. With
|
|
170
|
+
* `break-word` a column keeps each word whole; the button is `w-full`, so a word wider
|
|
171
|
+
* than a sized cell still breaks.
|
|
172
|
+
*/
|
|
173
|
+
const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
174
|
+
|
|
158
175
|
/** `onCommit`'s result, if it is a promise. */
|
|
159
176
|
function asPromise(result: unknown): Promise<unknown> | null {
|
|
160
177
|
return result && typeof (result as Promise<unknown>).then === "function" ? (result as Promise<unknown>) : null;
|
|
@@ -415,7 +432,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
415
432
|
let body: ReactNode;
|
|
416
433
|
if (readOnly) {
|
|
417
434
|
body = (
|
|
418
|
-
<span {...private_} className={cn("block min-w-0 truncate", alignClass)}>
|
|
435
|
+
<span {...private_} className={cn("block min-w-0 truncate", VALUE_WRAP_AT_LARGE, alignClass)}>
|
|
419
436
|
{content}
|
|
420
437
|
</span>
|
|
421
438
|
);
|
|
@@ -492,6 +509,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
492
509
|
onClick={(e) => e.preventDefault()}
|
|
493
510
|
className={cn(
|
|
494
511
|
"block w-full min-w-0 cursor-not-allowed truncate rounded-sm focus:outline-none",
|
|
512
|
+
VALUE_WRAP_AT_LARGE,
|
|
495
513
|
FOCUS_RING,
|
|
496
514
|
alignClass,
|
|
497
515
|
)}
|
|
@@ -516,6 +534,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
516
534
|
onClick={() => setOpen(true)}
|
|
517
535
|
className={cn(
|
|
518
536
|
"block w-full min-w-0 truncate rounded-sm underline-offset-2 hover:underline focus:outline-none",
|
|
537
|
+
VALUE_WRAP_AT_LARGE,
|
|
519
538
|
FOCUS_RING,
|
|
520
539
|
alignClass,
|
|
521
540
|
)}
|
|
@@ -962,7 +962,12 @@ export function MonthPicker(props: MonthPickerProps) {
|
|
|
962
962
|
</Popover>
|
|
963
963
|
);
|
|
964
964
|
return (
|
|
965
|
-
|
|
965
|
+
// `flex-wrap`: where the heading and the buttons do not fit one row, the buttons
|
|
966
|
+
// take the next line, at the end (0.32.1, the 360 px Extra-large sweep;
|
|
967
|
+
// docs/text-size-harmonization.md §4). In one row at 150 % "Today ‹ ›" left the
|
|
968
|
+
// heading 30 px on a 360 px phone, and "October 2026" was drawn under "Today".
|
|
969
|
+
// Where they fit, nothing changes.
|
|
970
|
+
<div {...wrapperRest} ref={rootRef} className={cn("flex min-w-0 flex-wrap items-center gap-2", className)}>
|
|
966
971
|
{Heading ? <Heading className="m-0 min-w-0 text-lg font-semibold">{trigger}</Heading> : trigger}
|
|
967
972
|
<div className="ms-auto flex shrink-0 items-center gap-1">
|
|
968
973
|
{showToday && (
|
|
@@ -30,6 +30,16 @@ const TITLE: Record<PageHeaderSize, string> = {
|
|
|
30
30
|
*/
|
|
31
31
|
export type PageHeaderMobileLayout = "stacked" | "inline";
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* `stacked` at Large and Extra large on a phone (0.32.2, kastlan's 0.32.1 report;
|
|
35
|
+
* docs/text-size-harmonization.md §4, as FormActions does): the actions take the full
|
|
36
|
+
* width, one under the other. Side by side in 240 px of layout, two buttons each wrapped
|
|
37
|
+
* their label onto three lines. `large:max-sm:`, the scaled phone width, so Normal and
|
|
38
|
+
* every wider screen keep their row; `inline` is for actions known to fit, and stays.
|
|
39
|
+
*/
|
|
40
|
+
const STACK_ACTIONS_AT_LARGE =
|
|
41
|
+
"large:max-sm:flex-col large:max-sm:items-stretch large:max-sm:[&>*]:w-full";
|
|
42
|
+
|
|
33
43
|
const ROW: Record<PageHeaderMobileLayout, string> = {
|
|
34
44
|
stacked: "flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4",
|
|
35
45
|
inline: "flex-row items-center justify-between gap-2 sm:gap-4",
|
|
@@ -232,7 +242,11 @@ export function PageHeader({
|
|
|
232
242
|
{actions != null && (
|
|
233
243
|
<div
|
|
234
244
|
ref={actionsRef}
|
|
235
|
-
className={cn(
|
|
245
|
+
className={cn(
|
|
246
|
+
"flex shrink-0 flex-wrap items-center gap-2",
|
|
247
|
+
hasSecondary && "sm:order-2",
|
|
248
|
+
mobileLayout === "stacked" && STACK_ACTIONS_AT_LARGE,
|
|
249
|
+
)}
|
|
236
250
|
>
|
|
237
251
|
{actions}
|
|
238
252
|
</div>
|