@eifi1/ui-kit 0.7.1 → 0.8.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.
- package/README.md +32 -17
- package/dist/chart.d.ts +3 -2
- package/dist/components/alert-banner.d.ts +59 -10
- package/dist/components/alert-banner.js +114 -8
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +3 -0
- package/dist/components/button-group.d.ts +26 -0
- package/dist/components/button-group.js +49 -0
- package/dist/components/button-group.js.map +1 -0
- package/dist/components/calculator.d.ts +3 -0
- package/dist/components/chart-zoom.d.ts +63 -14
- package/dist/components/chart-zoom.js +82 -16
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip.d.ts +67 -2
- package/dist/components/chip.js +73 -8
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +120 -0
- package/dist/components/confirm-dialog.js +96 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/copy-button.d.ts +62 -0
- package/dist/components/copy-button.js +95 -0
- package/dist/components/copy-button.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +5 -1
- package/dist/components/data-table-pagination.js +71 -58
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +28 -5
- package/dist/components/data-table-sort.js +11 -7
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +544 -423
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +51 -5
- package/dist/components/date-picker.js +152 -40
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +60 -0
- package/dist/components/description-list.js +112 -0
- package/dist/components/description-list.js.map +1 -0
- package/dist/components/disclosure.d.ts +37 -2
- package/dist/components/disclosure.js +14 -5
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/facing-pair.d.ts +2 -0
- package/dist/components/file-button.d.ts +17 -1
- package/dist/components/file-button.js +8 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +54 -9
- package/dist/components/file-dropzone.js +100 -70
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.d.ts +107 -0
- package/dist/components/floating-panel.js +206 -0
- package/dist/components/floating-panel.js.map +1 -0
- package/dist/components/full-bleed-dialog.d.ts +12 -1
- package/dist/components/full-bleed-dialog.js +28 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/modal.d.ts +6 -2
- package/dist/components/modal.js +2 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-field.d.ts +3 -0
- package/dist/components/number-input.d.ts +3 -0
- package/dist/components/numpad-sheet.d.ts +3 -0
- package/dist/components/progress-bar.d.ts +54 -0
- package/dist/components/progress-bar.js +94 -0
- package/dist/components/progress-bar.js.map +1 -0
- package/dist/components/scroll-area.d.ts +53 -0
- package/dist/components/scroll-area.js +86 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/separator.d.ts +23 -0
- package/dist/components/separator.js +24 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/series-chart-ticks.d.ts +34 -1
- package/dist/components/series-chart-ticks.js +83 -1
- package/dist/components/series-chart-ticks.js.map +1 -1
- package/dist/components/series-chart.d.ts +314 -19
- package/dist/components/series-chart.js +510 -120
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/skeleton.d.ts +36 -0
- package/dist/components/skeleton.js +35 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/stat-tile.d.ts +60 -6
- package/dist/components/stat-tile.js +60 -26
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +83 -0
- package/dist/components/table.js +166 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/toggle-legend.d.ts +3 -2
- package/dist/components/toggle-legend.js +1 -1
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tree-view.d.ts +129 -0
- package/dist/components/tree-view.js +376 -0
- package/dist/components/tree-view.js.map +1 -0
- package/dist/components/treemap.d.ts +9 -2
- package/dist/components/treemap.js +6 -2
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/ui.d.ts +94 -13
- package/dist/components/ui.js +111 -29
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
- package/dist/data-table.d.ts +2 -2
- package/dist/data-table.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
- package/dist/hooks/use-copy-to-clipboard.js +81 -0
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
- package/dist/hooks/use-debounce.d.ts +43 -0
- package/dist/hooks/use-debounce.js +56 -0
- package/dist/hooks/use-debounce.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +72 -0
- package/dist/hooks/use-file-drop.js +57 -0
- package/dist/hooks/use-file-drop.js.map +1 -0
- package/dist/i18n/defaults.d.ts +3 -0
- package/dist/i18n/defaults.js +7 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -0
- package/dist/i18n/kit-labels.js +6 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
- package/dist/i18n/locales/de-CH-informal.js +8 -0
- package/dist/i18n/locales/de-CH-informal.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +3 -0
- package/dist/i18n/locales/de-CH.js +1 -14
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/de-informal.d.ts +64 -0
- package/dist/i18n/locales/de-informal.js +37 -0
- package/dist/i18n/locales/de-informal.js.map +1 -0
- package/dist/i18n/locales/de.d.ts +3 -0
- package/dist/i18n/locales/de.js +25 -0
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +3 -0
- package/dist/i18n/locales/es.js +25 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +3 -0
- package/dist/i18n/locales/fr.js +25 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +3 -0
- package/dist/i18n/locales/hu.js +25 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +3 -0
- package/dist/i18n/locales/it.js +25 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +3 -0
- package/dist/i18n/locales/zh.js +25 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/swiss.d.ts +13 -0
- package/dist/i18n/swiss.js +19 -0
- package/dist/i18n/swiss.js.map +1 -0
- package/dist/index.d.ts +25 -10
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/dates.d.ts +37 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/search/command-palette.d.ts +15 -1
- package/dist/search/command-palette.js +10 -2
- package/dist/search/command-palette.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +35 -2
- package/dist/wizard/stepper-nav.js +54 -22
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +107 -5
- package/dist/wizard/types.js +2 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +141 -21
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-summary.d.ts +10 -2
- package/dist/wizard/wizard-summary.js +3 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +4 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-banner.tsx +211 -20
- package/src/components/button-group.tsx +75 -0
- package/src/components/chart-zoom.tsx +158 -20
- package/src/components/chip.tsx +160 -9
- package/src/components/confirm-dialog.tsx +242 -0
- package/src/components/copy-button.tsx +158 -0
- package/src/components/data-table-pagination.tsx +19 -6
- package/src/components/data-table-sort.ts +49 -10
- package/src/components/data-table.tsx +293 -24
- package/src/components/date-picker.tsx +262 -57
- package/src/components/description-list.tsx +174 -0
- package/src/components/disclosure.tsx +58 -5
- package/src/components/file-button.tsx +22 -2
- package/src/components/file-dropzone.tsx +198 -100
- package/src/components/floating-panel.tsx +341 -0
- package/src/components/full-bleed-dialog.tsx +53 -5
- package/src/components/modal.tsx +9 -2
- package/src/components/progress-bar.tsx +163 -0
- package/src/components/scroll-area.tsx +129 -0
- package/src/components/separator.tsx +37 -0
- package/src/components/series-chart-ticks.ts +135 -0
- package/src/components/series-chart.tsx +934 -67
- package/src/components/skeleton.tsx +65 -0
- package/src/components/stat-tile.tsx +160 -27
- package/src/components/table.tsx +263 -0
- package/src/components/toggle-legend.tsx +4 -3
- package/src/components/tree-view.tsx +589 -0
- package/src/components/treemap.tsx +13 -2
- package/src/components/ui.tsx +270 -39
- package/src/data-table.ts +2 -0
- package/src/hooks/use-copy-to-clipboard.ts +122 -0
- package/src/hooks/use-debounce.ts +105 -0
- package/src/hooks/use-file-drop.ts +123 -0
- package/src/i18n/defaults.ts +6 -0
- package/src/i18n/kit-labels.tsx +19 -0
- package/src/i18n/locales/de-CH-informal.ts +10 -0
- package/src/i18n/locales/de-CH.ts +1 -16
- package/src/i18n/locales/de-informal.ts +61 -0
- package/src/i18n/locales/de.ts +25 -0
- package/src/i18n/locales/es.ts +25 -0
- package/src/i18n/locales/fr.ts +25 -0
- package/src/i18n/locales/hu.ts +25 -0
- package/src/i18n/locales/it.ts +25 -0
- package/src/i18n/locales/zh.ts +25 -0
- package/src/i18n/swiss.ts +25 -0
- package/src/index.ts +29 -0
- package/src/lib/dates.ts +48 -0
- package/src/search/command-palette.tsx +29 -3
- package/src/wizard/stepper-nav.tsx +102 -28
- package/src/wizard/types.ts +106 -4
- package/src/wizard/use-wizard.ts +211 -31
- package/src/wizard/wizard-summary.tsx +24 -12
- package/src/wizard.ts +3 -2
|
@@ -8,10 +8,18 @@ import { SummarySection, WizardLabels } from './types.js';
|
|
|
8
8
|
* Sections are data, not children, because "jump back to step N" is the whole
|
|
9
9
|
* point of the component and a caller assembling its own cards invariably drops
|
|
10
10
|
* it. Labels and values are `ReactNode`, so a value can be a badge or a link.
|
|
11
|
+
*
|
|
12
|
+
* A section with no `stepIndex` (file-level counts, a total) is a card without an
|
|
13
|
+
* edit button. `disabled` greys every edit button at once — while the commit runs,
|
|
14
|
+
* when jumping back would show a step whose edits can no longer reach the request.
|
|
11
15
|
*/
|
|
12
|
-
declare function WizardSummary({ sections, onEditStep, labels, }: {
|
|
16
|
+
declare function WizardSummary({ sections, onEditStep, disabled, labels, }: {
|
|
13
17
|
sections: SummarySection[];
|
|
14
|
-
|
|
18
|
+
/** Called with the section's `stepIndex`. Optional only for a summary whose
|
|
19
|
+
* sections name no step at all. */
|
|
20
|
+
onEditStep?: (stepIndex: number) => void;
|
|
21
|
+
/** Disable every edit button (e.g. `wizard.isSubmitting`). */
|
|
22
|
+
disabled?: boolean;
|
|
15
23
|
labels?: Partial<WizardLabels>;
|
|
16
24
|
}): react.JSX.Element;
|
|
17
25
|
|
|
@@ -7,6 +7,7 @@ import { useKitLabels } from "../i18n/kit-labels.js";
|
|
|
7
7
|
function WizardSummary({
|
|
8
8
|
sections,
|
|
9
9
|
onEditStep,
|
|
10
|
+
disabled = false,
|
|
10
11
|
labels
|
|
11
12
|
}) {
|
|
12
13
|
const l = useKitLabels("wizard", DEFAULT_WIZARD_LABELS, labels);
|
|
@@ -15,13 +16,14 @@ function WizardSummary({
|
|
|
15
16
|
sections.map((section, sIdx) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
16
17
|
/* @__PURE__ */ jsxs(CardHeader, { className: "pb-2", children: [
|
|
17
18
|
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: section.label }),
|
|
18
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(
|
|
19
|
+
section.stepIndex !== void 0 && onEditStep && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(
|
|
19
20
|
IconButton,
|
|
20
21
|
{
|
|
21
22
|
size: "sm",
|
|
22
23
|
variant: "ghost",
|
|
23
24
|
"aria-label": l.edit,
|
|
24
25
|
title: l.edit,
|
|
26
|
+
disabled,
|
|
25
27
|
onClick: () => onEditStep(section.stepIndex),
|
|
26
28
|
children: /* @__PURE__ */ jsx(Pencil, {})
|
|
27
29
|
}
|
|
@@ -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 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 */\nexport function WizardSummary({\n sections,\n onEditStep,\n labels,\n}: {\n sections: SummarySection[];\n onEditStep
|
|
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 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 <IconButton\n size=\"sm\"\n variant=\"ghost\"\n aria-label={l.edit}\n title={l.edit}\n disabled={disabled}\n onClick={() => onEditStep(section.stepIndex as number)}\n >\n <Pencil />\n </IconButton>\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":";AAoCM,cAMI,YANJ;AApCN,SAAS,cAAc;AACvB,SAAS,MAAM,YAAY,aAAa,YAAY,WAAW,kBAAkB;AAEjF,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,QAIC;AAAA,2BAAC,cAAW,WAAU,QACpB;AAAA,4BAAC,aAAU,WAAU,aAAa,kBAAQ,OAAM;AAAA,QAC/C,QAAQ,cAAc,UAAa,cAClC,oBAAC,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,cAAY,EAAE;AAAA,YACd,OAAO,EAAE;AAAA,YACT;AAAA,YACA,SAAS,MAAM,WAAW,QAAQ,SAAmB;AAAA,YAErD,8BAAC,UAAO;AAAA;AAAA,QACV,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,SArCS,IAsCX,CACD;AAAA,KACH;AAEJ;","names":[]}
|
package/dist/wizard.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_WIZARD_LABELS, FieldErrors, StepStatus, SummaryItem, SummarySection, UseWizardOptions, UseWizardReturn, ValidateResult, WizardLabels, WizardStepConfig, resolveWizardLabels } from './wizard/types.js';
|
|
1
|
+
export { DEFAULT_WIZARD_LABELS, FieldErrors, StepStatus, SummaryItem, SummarySection, UseWizardOptions, UseWizardReturn, ValidateResult, WizardLabels, WizardStepConfig, WizardUrlSync, resolveWizardLabels } from './wizard/types.js';
|
|
2
2
|
export { useWizard } from './wizard/use-wizard.js';
|
|
3
3
|
export { WizardContextProvider, WizardContextValue, useWizardContext } from './wizard/wizard-context.js';
|
|
4
4
|
export { RequiredFieldSpec, requiredFieldsValidator } from './wizard/validation.js';
|
|
@@ -6,3 +6,6 @@ export { WizardStep } 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 './components/ui.js';
|
|
10
|
+
import './components/tooltip.js';
|
|
11
|
+
import './hooks/use-anchored-rect.js';
|
package/dist/wizard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wizard.ts"],"sourcesContent":["// `@eifi1/ui-kit/wizard` — the multi-step engine, its chrome and its review step.\n//\n// A re-slicing of the main barrel, not a new API.
|
|
1
|
+
{"version":3,"sources":["../src/wizard.ts"],"sourcesContent":["// `@eifi1/ui-kit/wizard` — the multi-step engine, its chrome and its review step.\n//\n// A re-slicing of the main barrel, not a new API. By default useWizard syncs the\n// active step to `?step=` via react-router's useSearchParams, so it needs a router;\n// `urlSync: false` keeps the step in memory and needs none.\n//\n// NOT the same thing as `WizardStepper` in components/wizard-stepper, which is a bare\n// two-step indicator with no engine and stays on the main barrel.\nexport * from \"./wizard/types\";\nexport * from \"./wizard/use-wizard\";\nexport * from \"./wizard/wizard-context\";\nexport * from \"./wizard/validation\";\nexport * from \"./wizard/wizard-step\";\nexport * from \"./wizard/wizard-summary\";\nexport * from \"./wizard/stepper-nav\";\n"],"mappings":";AAQA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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.",
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
-
import { AlertTriangle } from "lucide-react";
|
|
1
|
+
import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from "react";
|
|
2
|
+
import { AlertTriangle, ChevronRight, Info, X } from "lucide-react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
|
+
import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/** `info` (0.8.0) is the sky family, for news that is neither good nor bad: keksdose's
|
|
7
|
+
* preview and beta-performance banners, which borrowed `warning` for want of it and
|
|
8
|
+
* so told every user of the preview build that something was wrong. */
|
|
9
|
+
export type AlertTone = "danger" | "warning" | "info" | "neutral";
|
|
6
10
|
|
|
7
11
|
/** Border + surface per tone, WITHOUT a radius or padding, so a caller that owns
|
|
8
12
|
* its own box (a Card, say) can take the tone alone. Single source for both
|
|
@@ -20,6 +24,7 @@ export type AlertTone = "danger" | "warning" | "neutral";
|
|
|
20
24
|
const TONE_FRAME: Record<AlertTone, string> = {
|
|
21
25
|
danger: "border-2 border-[var(--danger-border)] bg-[var(--danger-bg)]",
|
|
22
26
|
warning: "border-2 border-[var(--warning-border)] bg-[var(--warning-bg)]",
|
|
27
|
+
info: "border-2 border-[var(--info-border)] bg-[var(--info-bg)]",
|
|
23
28
|
neutral: "border border-[var(--border)]",
|
|
24
29
|
};
|
|
25
30
|
|
|
@@ -38,38 +43,224 @@ export function alertFrameClass(tone: AlertTone): string {
|
|
|
38
43
|
return `${box} ${TONE_FRAME[tone]}`;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
const TONE_TEXT: Record<
|
|
46
|
+
const TONE_TEXT: Record<AlertTone, string> = {
|
|
42
47
|
danger: "text-[var(--danger)]",
|
|
43
48
|
warning: "text-[var(--warning)]",
|
|
49
|
+
info: "text-[var(--info)]",
|
|
50
|
+
neutral: "text-[var(--text-secondary)]",
|
|
44
51
|
};
|
|
45
52
|
|
|
46
|
-
const TONE_ICON: Record<
|
|
53
|
+
const TONE_ICON: Record<AlertTone, string> = {
|
|
47
54
|
danger: "text-[var(--danger)]",
|
|
48
55
|
warning: "text-[var(--warning)]",
|
|
56
|
+
info: "text-[var(--info)]",
|
|
57
|
+
neutral: "text-[var(--text-muted)]",
|
|
49
58
|
};
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
/** Which glyph a tone draws when the caller names none. The triangle belongs to the
|
|
61
|
+
* two tones that mean "something is wrong"; `info` and `neutral` get the ⓘ. That is
|
|
62
|
+
* what makes `neutral` safe to offer at all: until 0.8.0 it was withheld because a
|
|
63
|
+
* triangle with no colour is a warning that looks like a note — and a neutral note
|
|
64
|
+
* that draws no triangle is simply a note (lenkbank's "values are rounded"). */
|
|
65
|
+
const TONE_GLYPH: Record<AlertTone, typeof AlertTriangle> = {
|
|
66
|
+
danger: AlertTriangle,
|
|
67
|
+
warning: AlertTriangle,
|
|
68
|
+
info: Info,
|
|
69
|
+
neutral: Info,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
interface AlertBannerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "onClick"> {
|
|
73
|
+
/** Default `danger`. `info` for news, `neutral` for a plain note — see
|
|
74
|
+
* {@link TONE_GLYPH} for why a neutral banner is now allowed. */
|
|
75
|
+
tone?: AlertTone;
|
|
76
|
+
/**
|
|
77
|
+
* `box` (default): the framed callout. `inline`: the tone's colour, glyph and role
|
|
78
|
+
* and NOTHING else — no border, no surface, no padding — so it fits inside a
|
|
79
|
+
* toolbar row beside the controls it is about. Lenkbank's corner-motion toolbar
|
|
80
|
+
* shows "Radius below the minimum" and "Solving…" there; a box would have doubled
|
|
81
|
+
* the row's height every time either appeared.
|
|
82
|
+
*
|
|
83
|
+
* The inline variant is also a LIVE REGION by default — `role="alert"` for
|
|
84
|
+
* `danger`, `role="status"` for the rest — because a message that appears in a
|
|
85
|
+
* toolbar in answer to what the user just did is exactly the kind a screen reader
|
|
86
|
+
* otherwise never hears. The box stays role-less as it always was: it is usually
|
|
87
|
+
* page furniture present at load, and an alert that fires on every navigation is
|
|
88
|
+
* noise. Either way a `role` you pass wins.
|
|
89
|
+
*/
|
|
90
|
+
variant?: "box" | "inline";
|
|
91
|
+
/** Replaces the tone's glyph — a `<Spinner label={null} />` for "Solving…", say.
|
|
92
|
+
* `null` draws none. Always decorative; the text carries the message. */
|
|
93
|
+
icon?: ReactNode;
|
|
94
|
+
/** Renders an × at the end that calls this. The banner does not hide itself — the
|
|
95
|
+
* caller removes it (and remembers that it did), as with every other kit control
|
|
96
|
+
* whose state is app state. */
|
|
97
|
+
onDismiss?: () => void;
|
|
98
|
+
/** Accessible name of the ×. Default: `common.dismiss` from the
|
|
99
|
+
* {@link UiKitProvider}, else "Dismiss". */
|
|
100
|
+
dismissLabel?: string;
|
|
57
101
|
children: ReactNode;
|
|
58
102
|
}
|
|
59
103
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The whole-row modes. keksdose's budget-summary card is a banner you tap to open
|
|
106
|
+
* the budget; built from a `<div onClick>` it was a mouse-only target, and built as a
|
|
107
|
+
* banner with a link inside it was a small target inside a big one that looked like
|
|
108
|
+
* one. So `onClick` makes the banner itself ONE `<button>`, and `href` makes it one
|
|
109
|
+
* `<a>`, with a trailing chevron that says so. Mutually exclusive, as on `Chip`.
|
|
110
|
+
*
|
|
111
|
+
* With `onDismiss` as well, the × cannot go inside the button (interactive content
|
|
112
|
+
* may not nest), so the frame becomes a wrapper holding two siblings: the row's
|
|
113
|
+
* button, stretched over the whole frame, and the × on top of it. It still looks and
|
|
114
|
+
* clicks like one banner with an × in its corner, and the two are separate tab stops.
|
|
115
|
+
*/
|
|
116
|
+
export type AlertBannerProps = AlertBannerBaseProps &
|
|
117
|
+
(
|
|
118
|
+
| {
|
|
119
|
+
/** Makes the whole banner one link. Mutually exclusive with `onClick`. */
|
|
120
|
+
href?: string;
|
|
121
|
+
onClick?: never;
|
|
122
|
+
}
|
|
123
|
+
| {
|
|
124
|
+
href?: never;
|
|
125
|
+
/** Makes the whole banner one button. Mutually exclusive with `href`. The
|
|
126
|
+
* event's element is the `<button>`, not a `<div>`. */
|
|
127
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
/** Warning/danger/info callout box with the shared frame and an icon — or, with
|
|
132
|
+
* `variant="inline"`, the same message as a frameless line of toned text. */
|
|
133
|
+
export function AlertBanner({
|
|
134
|
+
tone = "danger",
|
|
135
|
+
variant = "box",
|
|
136
|
+
icon,
|
|
137
|
+
onDismiss,
|
|
138
|
+
dismissLabel,
|
|
139
|
+
href,
|
|
140
|
+
onClick,
|
|
141
|
+
className,
|
|
142
|
+
children,
|
|
143
|
+
role,
|
|
144
|
+
...rest
|
|
145
|
+
}: AlertBannerProps) {
|
|
146
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS, { dismiss: dismissLabel });
|
|
147
|
+
const inline = variant === "inline";
|
|
148
|
+
const interactive = href !== undefined || onClick !== undefined;
|
|
149
|
+
const resolvedRole = role ?? (inline ? (tone === "danger" ? "alert" : "status") : undefined);
|
|
150
|
+
|
|
151
|
+
const Glyph = TONE_GLYPH[tone];
|
|
152
|
+
const glyph =
|
|
153
|
+
icon === null ? null : icon !== undefined ? (
|
|
154
|
+
<span aria-hidden className={cn("flex shrink-0 [&_svg]:size-4", !inline && "mt-0.5", TONE_ICON[tone])}>
|
|
155
|
+
{icon}
|
|
156
|
+
</span>
|
|
157
|
+
) : (
|
|
158
|
+
<Glyph aria-hidden className={cn("size-4 shrink-0", !inline && "mt-0.5", TONE_ICON[tone])} />
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// The row layout, shared by every shape below.
|
|
162
|
+
const row = inline ? "inline-flex items-center gap-1.5 text-sm" : "flex items-start gap-2 text-sm";
|
|
163
|
+
const frame = inline ? "" : alertFrameClass(tone);
|
|
164
|
+
const focusRing =
|
|
165
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-surface)]";
|
|
166
|
+
|
|
167
|
+
const content = (
|
|
168
|
+
<>
|
|
169
|
+
{glyph}
|
|
170
|
+
<span className={cn(!inline && "flex-1", "min-w-0 text-start")}>{children}</span>
|
|
171
|
+
{interactive && (
|
|
172
|
+
<ChevronRight
|
|
173
|
+
aria-hidden
|
|
174
|
+
className={cn("size-4 shrink-0 opacity-70 rtl:-scale-x-100", !inline && "mt-0.5")}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const dismiss = onDismiss ? (
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
aria-label={common.dismiss}
|
|
184
|
+
onClick={(e) => {
|
|
185
|
+
// A dismiss inside a whole-row banner must not also follow the row.
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
e.stopPropagation();
|
|
188
|
+
onDismiss();
|
|
189
|
+
}}
|
|
190
|
+
className={cn(
|
|
191
|
+
// Negative margins so the × does not make the box taller than its text; the
|
|
192
|
+
// hover wash takes the tone's own hue, so it works on every tone.
|
|
193
|
+
"relative z-10 -my-0.5 -me-0.5 flex size-6 shrink-0 items-center justify-center rounded transition-colors hover:bg-current/10",
|
|
194
|
+
focusRing,
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
<X aria-hidden className="size-4" />
|
|
198
|
+
</button>
|
|
199
|
+
) : null;
|
|
200
|
+
|
|
201
|
+
if (!interactive) {
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
// Arbitrary attributes first — a `data-tour` anchor, a test id, an
|
|
205
|
+
// `aria-describedby` — then the layout and tone classes, which are merged
|
|
206
|
+
// through `cn` rather than spread so a caller's `className` refines the box
|
|
207
|
+
// instead of replacing it.
|
|
208
|
+
{...rest}
|
|
209
|
+
role={resolvedRole}
|
|
210
|
+
className={cn(row, frame, TONE_TEXT[tone], className)}
|
|
211
|
+
>
|
|
212
|
+
{content}
|
|
213
|
+
{dismiss}
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hover = "cursor-pointer transition-[filter] hover:brightness-[0.97] dark:hover:brightness-110";
|
|
219
|
+
// With a dismiss the frame is a wrapper and the row element is stretched over it;
|
|
220
|
+
// without one the row element IS the frame.
|
|
221
|
+
const split = dismiss !== null;
|
|
222
|
+
const actionClass = split
|
|
223
|
+
? cn(
|
|
224
|
+
"flex min-w-0 flex-1 gap-2 text-start",
|
|
225
|
+
inline ? "items-center" : "items-start",
|
|
226
|
+
"outline-none after:absolute after:inset-0 after:rounded-md after:content-['']",
|
|
227
|
+
"focus-visible:after:ring-2 focus-visible:after:ring-[var(--brand)]",
|
|
228
|
+
)
|
|
229
|
+
: cn(row, !inline && "w-full", frame, TONE_TEXT[tone], hover, focusRing, className);
|
|
230
|
+
// The div's attributes are the row element's: an `id`, a test id or an
|
|
231
|
+
// `aria-describedby` belongs on the thing that takes focus. The live-region role is
|
|
232
|
+
// NOT carried over — a button or a link that is also a `status` is neither.
|
|
233
|
+
const actionRest = split ? {} : rest;
|
|
234
|
+
|
|
235
|
+
const action =
|
|
236
|
+
href !== undefined ? (
|
|
237
|
+
<a
|
|
238
|
+
{...(actionRest as ComponentPropsWithoutRef<"a">)}
|
|
239
|
+
href={href}
|
|
240
|
+
className={actionClass}
|
|
241
|
+
>
|
|
242
|
+
{content}
|
|
243
|
+
</a>
|
|
244
|
+
) : (
|
|
245
|
+
<button
|
|
246
|
+
{...(actionRest as ComponentPropsWithoutRef<"button">)}
|
|
247
|
+
type="button"
|
|
248
|
+
onClick={onClick}
|
|
249
|
+
className={actionClass}
|
|
250
|
+
>
|
|
251
|
+
{content}
|
|
252
|
+
</button>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (!split) return action;
|
|
62
256
|
return (
|
|
63
257
|
<div
|
|
64
|
-
// Arbitrary attributes first — a `data-tour` anchor, a test id, an
|
|
65
|
-
// `aria-describedby` — then the layout and tone classes, which are merged
|
|
66
|
-
// through `cn` rather than spread so a caller's `className` refines the box
|
|
67
|
-
// instead of replacing it.
|
|
68
258
|
{...rest}
|
|
69
|
-
|
|
259
|
+
role={resolvedRole}
|
|
260
|
+
className={cn("relative", row, frame, TONE_TEXT[tone], hover, className)}
|
|
70
261
|
>
|
|
71
|
-
|
|
72
|
-
|
|
262
|
+
{action}
|
|
263
|
+
{dismiss}
|
|
73
264
|
</div>
|
|
74
265
|
);
|
|
75
266
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
// Every class below is spelled out in full rather than assembled from parts: the
|
|
5
|
+
// consumer's Tailwind build finds classes by scanning this package's source as text,
|
|
6
|
+
// and a class that exists only after a template literal runs is never generated.
|
|
7
|
+
//
|
|
8
|
+
// Each rule is written twice, once for a direct child and once for a `<button>` one
|
|
9
|
+
// level down. The second is for a Tooltip-wrapped IconButton — the map zoom's + and −
|
|
10
|
+
// both carry a tooltip — where the direct child is the tooltip's wrapper span and the
|
|
11
|
+
// button that draws the border and the corners sits inside it.
|
|
12
|
+
const JOINED_COMMON = cn(
|
|
13
|
+
"inline-flex rounded-md border border-[var(--border)]",
|
|
14
|
+
// The group draws the frame; each member gives up its own border, radius and shadow.
|
|
15
|
+
"[&>*]:rounded-none [&>*]:border-0 [&>*]:shadow-none",
|
|
16
|
+
"[&>*>button]:rounded-none [&>*>button]:border-0 [&>*>button]:shadow-none",
|
|
17
|
+
"[&>*]:border-[var(--border)]",
|
|
18
|
+
// A focused member rises above its neighbours so its ring is not painted over by
|
|
19
|
+
// the next one's background.
|
|
20
|
+
"[&>*]:relative [&>*:focus-within]:z-10",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const JOINED: Record<"horizontal" | "vertical", string> = {
|
|
24
|
+
// Logical edges and corners throughout (`border-s`, `rounded-s-*`), so in RTL the
|
|
25
|
+
// first member sits on the right with the right-hand corners rounded — the physical
|
|
26
|
+
// pair would round the outside of the last button and square off the first.
|
|
27
|
+
horizontal: cn(
|
|
28
|
+
JOINED_COMMON,
|
|
29
|
+
"flex-row items-stretch",
|
|
30
|
+
"[&>*:not(:first-child)]:border-s",
|
|
31
|
+
"[&>:first-child]:rounded-s-md [&>:last-child]:rounded-e-md",
|
|
32
|
+
"[&>:first-child>button]:rounded-s-md [&>:last-child>button]:rounded-e-md",
|
|
33
|
+
),
|
|
34
|
+
// The block axis is top-to-bottom in both reading directions, so top/bottom are
|
|
35
|
+
// already the logical edges here.
|
|
36
|
+
vertical: cn(
|
|
37
|
+
JOINED_COMMON,
|
|
38
|
+
"flex-col items-stretch",
|
|
39
|
+
"[&>*:not(:first-child)]:border-t",
|
|
40
|
+
"[&>:first-child]:rounded-t-md [&>:last-child]:rounded-b-md",
|
|
41
|
+
"[&>:first-child>button]:rounded-t-md [&>:last-child>button]:rounded-b-md",
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface ButtonGroupProps extends Omit<ComponentPropsWithoutRef<"div">, "role"> {
|
|
46
|
+
/** `horizontal` (default): side by side, in reading order. `vertical`: stacked —
|
|
47
|
+
* the map's zoom + over −. */
|
|
48
|
+
orientation?: "horizontal" | "vertical";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adjacent `Button`s or `IconButton`s drawn as one joined control: a single frame,
|
|
53
|
+
* hairline dividers between the members, rounded only at the outer corners.
|
|
54
|
+
*
|
|
55
|
+
* keksdose asked for it twice — "collapse all / expand all" above its category
|
|
56
|
+
* tree, and the + and − of its map zoom — both hand-built with negative margins and
|
|
57
|
+
* physical `rounded-l` / `rounded-r`, which came out inside-out under `dir="rtl"`.
|
|
58
|
+
*
|
|
59
|
+
* `role="group"` with a name: a reader then introduces the pair as "Zoom, group"
|
|
60
|
+
* before reading "Zoom in", which is what tells a listener that the next button
|
|
61
|
+
* belongs with this one. Pass `aria-label` (or `aria-labelledby`); a group with no
|
|
62
|
+
* name is one more unlabelled wrapper. It is NOT a toolbar — it adds no arrow-key
|
|
63
|
+
* roving, and every member keeps its own tab stop, as plain adjacent buttons do.
|
|
64
|
+
* For one-of-many selection use `ToggleGroup`, which has the radio semantics.
|
|
65
|
+
*/
|
|
66
|
+
export function ButtonGroup({ orientation = "horizontal", className, ...rest }: ButtonGroupProps) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
{...rest}
|
|
70
|
+
role="group"
|
|
71
|
+
data-orientation={orientation}
|
|
72
|
+
className={cn(JOINED[orientation], className)}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|