@eifi1/ui-kit 0.48.2 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -12
- package/dist/admin/admin-action-confirm.js +1 -1
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.js +5 -1
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/user-roster.d.ts +11 -5
- package/dist/admin/user-roster.js +15 -4
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js +2 -2
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/not-found-page.js +1 -1
- package/dist/auth/not-found-page.js.map +1 -1
- package/dist/auth/reset-password-form.js +2 -2
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/verify-email.js +2 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/plan-card.js +2 -2
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/components/breadcrumbs.d.ts +19 -1
- package/dist/components/breadcrumbs.js +11 -4
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-look.d.ts +37 -7
- package/dist/components/button-look.js +33 -6
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +4 -1
- package/dist/components/button.js +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -1
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-edge.d.ts +10 -0
- package/dist/components/chart-edge.js +57 -0
- package/dist/components/chart-edge.js.map +1 -0
- package/dist/components/chart.js +2 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +8 -1
- package/dist/components/chip.js +4 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-context.d.ts +5 -0
- package/dist/components/confirm-context.js +7 -0
- package/dist/components/confirm-context.js.map +1 -0
- package/dist/components/confirm-dialog.js +3 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +2 -2
- package/dist/components/danger-confirm.js +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-fit.js +21 -3
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +48 -2
- package/dist/components/data-table-mobile.js +87 -10
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -1
- package/dist/components/data-table-row-actions.js +7 -0
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table.d.ts +34 -1
- package/dist/components/data-table.js +34 -9
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/floating-panel.d.ts +36 -13
- package/dist/components/floating-panel.js +27 -7
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/form-actions.js +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/icon-button.d.ts +11 -0
- package/dist/components/icon-button.js +7 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/nav-pills.d.ts +8 -1
- package/dist/components/nav-pills.js +61 -38
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/page-header-context.d.ts +8 -0
- package/dist/components/page-header-context.js +7 -0
- package/dist/components/page-header-context.js.map +1 -0
- package/dist/components/page-header.d.ts +14 -2
- package/dist/components/page-header.js +10 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/pie-chart.js +2 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/progress-bar.js +5 -4
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.js +1 -1
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/row-actions-labels.js +3 -1
- package/dist/components/row-actions-labels.js.map +1 -1
- package/dist/components/row-actions.d.ts +43 -9
- package/dist/components/row-actions.js +78 -15
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +37 -0
- package/dist/components/section-nav.js +33 -0
- package/dist/components/section-nav.js.map +1 -0
- package/dist/components/series-chart.js +8 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +1 -1
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/sparkline.js +2 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -7
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +5 -1
- package/dist/components/status-dot.js +4 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +2 -2
- 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/treemap.d.ts +5 -2
- package/dist/components/treemap.js +26 -6
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.js +37 -6
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-banner.js +1 -1
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/demo/demo-ended.js +1 -1
- package/dist/demo/demo-ended.js.map +1 -1
- package/dist/demo/demo-start.js +1 -1
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox.js +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +9 -1
- package/dist/hooks/use-dismiss.js +25 -5
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-document-title.d.ts +14 -0
- package/dist/hooks/use-document-title.js +22 -0
- package/dist/hooks/use-document-title.js.map +1 -0
- package/dist/i18n/german.js +6 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +31 -1
- package/dist/i18n/kit-labels.js +43 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +6 -2
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +6 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +6 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +6 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +6 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +6 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +2 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +1 -0
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.js +2 -2
- package/dist/landing/landing-actions.js.map +1 -1
- package/dist/landing/public-header.js +2 -2
- package/dist/landing/public-header.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +27 -0
- package/dist/lib/clipboard-minus.js +63 -0
- package/dist/lib/clipboard-minus.js.map +1 -0
- package/dist/lib/document-title.d.ts +17 -0
- package/dist/lib/document-title.js +29 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/float-clearance.js +8 -2
- package/dist/lib/float-clearance.js.map +1 -1
- package/dist/lib/format.d.ts +35 -10
- package/dist/lib/format.js +55 -11
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +22 -0
- package/dist/lib/minus.js +14 -0
- package/dist/lib/minus.js.map +1 -0
- package/dist/lib/status-tone.d.ts +30 -0
- package/dist/lib/status-tone.js +17 -0
- package/dist/lib/status-tone.js.map +1 -0
- package/dist/settings/settings-layout.js +3 -2
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +1 -1
- package/dist/shell/account-menu-language.js +9 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/app-shell-labels.js +2 -1
- package/dist/shell/app-shell-labels.js.map +1 -1
- package/dist/shell/app-shell.d.ts +58 -16
- package/dist/shell/app-shell.js +151 -43
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/language-code.d.ts +3 -0
- package/dist/shell/language-code.js +8 -0
- package/dist/shell/language-code.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +14 -6
- package/dist/shell/topbar-controls.js +14 -17
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +1 -1
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +82 -15
- package/dist/theme/chart-palette.js +51 -24
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +9 -0
- package/dist/theme/palette-presets.js +77 -62
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/tour/tour.js +1 -1
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +3 -3
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/admin-action-confirm.tsx +1 -1
- package/src/admin/invitations-panel.tsx +7 -3
- package/src/admin/user-roster.tsx +23 -6
- package/src/auth/accept-invitation.tsx +3 -3
- package/src/auth/not-found-page.tsx +1 -1
- package/src/auth/reset-password-form.tsx +3 -3
- package/src/auth/verify-email.tsx +3 -3
- package/src/billing/plan-card.tsx +2 -2
- package/src/components/breadcrumbs.tsx +32 -4
- package/src/components/button-look.ts +86 -15
- package/src/components/button.tsx +6 -3
- package/src/components/calendar-heatmap.tsx +2 -1
- package/src/components/chart-edge.ts +73 -0
- package/src/components/chart.tsx +3 -1
- package/src/components/chip.tsx +10 -1
- package/src/components/confirm-context.ts +7 -0
- package/src/components/confirm-dialog.tsx +3 -3
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-fit.ts +41 -6
- package/src/components/data-table-mobile.tsx +162 -11
- package/src/components/data-table-row-actions.tsx +9 -1
- package/src/components/data-table-row.tsx +2 -0
- package/src/components/data-table.tsx +82 -18
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/floating-panel.tsx +67 -16
- package/src/components/form-actions.tsx +2 -2
- package/src/components/icon-button.tsx +21 -8
- package/src/components/nav-pills.tsx +29 -2
- package/src/components/page-header-context.ts +8 -0
- package/src/components/page-header.tsx +34 -2
- package/src/components/pie-chart.tsx +2 -1
- package/src/components/progress-bar.tsx +6 -3
- package/src/components/reauth-dialog.tsx +1 -1
- package/src/components/row-actions-labels.ts +2 -0
- package/src/components/row-actions.tsx +154 -33
- package/src/components/section-nav.tsx +65 -0
- package/src/components/series-chart.tsx +12 -3
- package/src/components/share-card.tsx +1 -1
- package/src/components/signature-pad.tsx +1 -1
- package/src/components/sparkline.tsx +2 -1
- package/src/components/stat-tile.tsx +19 -7
- package/src/components/status-dot.tsx +7 -1
- package/src/components/tooltip.tsx +3 -1
- package/src/components/translation-review-editor.tsx +2 -2
- package/src/components/translation-review.tsx +2 -2
- package/src/components/treemap.tsx +34 -8
- package/src/components/use-table-state.ts +63 -8
- package/src/data-table.ts +3 -3
- package/src/demo/demo-banner.tsx +1 -1
- package/src/demo/demo-ended.tsx +1 -1
- package/src/demo/demo-start.tsx +1 -1
- package/src/feedback/feedback-inbox.tsx +1 -1
- package/src/hooks/use-dismiss.ts +43 -4
- package/src/hooks/use-document-title.ts +31 -0
- package/src/i18n/german.ts +4 -0
- package/src/i18n/kit-labels.tsx +76 -1
- 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/i18n/review.ts +2 -0
- package/src/i18n/shared-labels.ts +1 -0
- package/src/index.ts +13 -0
- package/src/landing/landing-actions.tsx +2 -2
- package/src/landing/public-header.tsx +2 -2
- package/src/lib/clipboard-minus.ts +94 -0
- package/src/lib/document-title.ts +41 -0
- package/src/lib/float-clearance.ts +19 -3
- package/src/lib/format.ts +115 -30
- package/src/lib/minus.ts +40 -0
- package/src/lib/status-tone.ts +43 -0
- package/src/settings/settings-layout.tsx +3 -2
- package/src/shell/account-menu-language.tsx +14 -2
- package/src/shell/app-shell-labels.ts +1 -0
- package/src/shell/app-shell.tsx +291 -68
- package/src/shell/language-code.ts +5 -0
- package/src/shell/topbar-controls.tsx +35 -15
- package/src/testing/css-variables.generated.ts +6 -6
- package/src/theme/chart-palette.ts +102 -20
- package/src/theme/palette-presets.ts +121 -84
- package/src/tour/tour.tsx +1 -1
- package/src/wizard/stepper-nav.tsx +4 -4
- package/tokens.css +28 -18
|
@@ -119,7 +119,7 @@ function ResendButton({
|
|
|
119
119
|
}: {
|
|
120
120
|
state: ReturnType<typeof useResend>;
|
|
121
121
|
labels: VerifyEmailLabels;
|
|
122
|
-
variant: "secondary" | "
|
|
122
|
+
variant: "secondary" | "accent";
|
|
123
123
|
}) {
|
|
124
124
|
return (
|
|
125
125
|
<Button
|
|
@@ -269,7 +269,7 @@ export function VerifyEmailStatus({
|
|
|
269
269
|
const href = continueHref ?? (signedIn ? "/" : "/login");
|
|
270
270
|
// `replace`: the token is spent, so Back must not land on this page again (keksdose F1).
|
|
271
271
|
const continueButton = (
|
|
272
|
-
<Button href={href} replace variant={outcome === "verified" ? "
|
|
272
|
+
<Button href={href} replace variant={outcome === "verified" ? "accent" : "secondary"}>
|
|
273
273
|
{labels.continue}
|
|
274
274
|
</Button>
|
|
275
275
|
);
|
|
@@ -304,7 +304,7 @@ export function VerifyEmailStatus({
|
|
|
304
304
|
hint={hint}
|
|
305
305
|
action={
|
|
306
306
|
<>
|
|
307
|
-
{canResend && <ResendButton state={resend} labels={labels} variant="
|
|
307
|
+
{canResend && <ResendButton state={resend} labels={labels} variant="accent" />}
|
|
308
308
|
{continueButton}
|
|
309
309
|
</>
|
|
310
310
|
}
|
|
@@ -471,7 +471,7 @@ export function PlanPicker({
|
|
|
471
471
|
<div className="flex justify-end">
|
|
472
472
|
<Button
|
|
473
473
|
type="button"
|
|
474
|
-
variant="
|
|
474
|
+
variant="default"
|
|
475
475
|
className="max-sm:w-full"
|
|
476
476
|
disabledReason={blocked}
|
|
477
477
|
pending={pending}
|
|
@@ -538,7 +538,7 @@ export function GrantWarningConfirm({ open, onOpenChange, onConfirm, labels: lab
|
|
|
538
538
|
{common.back}
|
|
539
539
|
</Button>
|
|
540
540
|
<Button
|
|
541
|
-
variant="
|
|
541
|
+
variant="default"
|
|
542
542
|
onClick={() => {
|
|
543
543
|
onConfirm();
|
|
544
544
|
onOpenChange(false);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
|
2
2
|
import type { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
|
|
3
3
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
@@ -6,6 +6,7 @@ import { FOCUS_RING } from "./focus-ring";
|
|
|
6
6
|
import { useKitLabels, useKitLink, type LabelOverride } from "../i18n/kit-labels";
|
|
7
7
|
import { pickLinkRenderer, RenderedKitLink } from "./text-link";
|
|
8
8
|
import { DEFAULT_BREADCRUMBS_LABELS } from "./breadcrumbs-labels";
|
|
9
|
+
import { PageHeaderTrailContext } from "./page-header-context";
|
|
9
10
|
|
|
10
11
|
export interface BreadcrumbsLabels {
|
|
11
12
|
/** The `nav` landmark's name — what a screen reader lists it as. */
|
|
@@ -24,6 +25,12 @@ export interface BreadcrumbItem {
|
|
|
24
25
|
href?: string;
|
|
25
26
|
/** A stable React key, when `label` is not a string. */
|
|
26
27
|
key?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The app's home entry (0.49 KIT-9). Default: an entry whose `href` is `"/"`. No trail
|
|
30
|
+
* is drawn when only home entries are left — inside a `PageHeader`, or wherever the
|
|
31
|
+
* current entry is hidden (`hideCurrent`): "Home" above a page's title says nothing.
|
|
32
|
+
*/
|
|
33
|
+
home?: boolean;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/** What {@link BreadcrumbsProps.renderLink} is handed. Spread it onto your router's
|
|
@@ -57,6 +64,18 @@ export interface BreadcrumbsProps extends Omit<ComponentPropsWithRef<"nav">, "ch
|
|
|
57
64
|
keepEnd?: number;
|
|
58
65
|
/** Overrides the {@link UiKitProvider}'s `breadcrumbs` labels. */
|
|
59
66
|
labels?: LabelOverride<BreadcrumbsLabels>;
|
|
67
|
+
/**
|
|
68
|
+
* Leave out the last entry, the current page (0.49 KIT-9). The entry before it stays a
|
|
69
|
+
* link, and no entry carries `aria-current`: the page's title names where you are.
|
|
70
|
+
*
|
|
71
|
+
* Default `true` inside a `PageHeader`'s `breadcrumbs` (the title is right below, so
|
|
72
|
+
* the trail would repeat it), `false` elsewhere. With the current entry hidden — and
|
|
73
|
+
* inside a `PageHeader` either way — the trail is drawn only when at least one entry
|
|
74
|
+
* that is not home ({@link BreadcrumbItem.home}) is left, so a trail drawn by the
|
|
75
|
+
* layout above the page shows no lone "Home" link either. `false` keeps the current
|
|
76
|
+
* entry as text with `aria-current="page"`.
|
|
77
|
+
*/
|
|
78
|
+
hideCurrent?: boolean;
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
/**
|
|
@@ -69,16 +88,18 @@ export interface BreadcrumbsProps extends Omit<ComponentPropsWithRef<"nav">, "ch
|
|
|
69
88
|
* the server's first paint is already right.
|
|
70
89
|
*/
|
|
71
90
|
export function Breadcrumbs({
|
|
72
|
-
items,
|
|
91
|
+
items: allItems,
|
|
73
92
|
renderLink,
|
|
74
93
|
separator,
|
|
75
94
|
collapse = true,
|
|
76
95
|
keepEnd = 2,
|
|
77
96
|
labels: labelsProp,
|
|
97
|
+
hideCurrent,
|
|
78
98
|
className,
|
|
79
99
|
"aria-label": ariaLabel,
|
|
80
100
|
...rest
|
|
81
101
|
}: BreadcrumbsProps) {
|
|
102
|
+
const host = useContext(PageHeaderTrailContext);
|
|
82
103
|
const labels = useKitLabels("breadcrumbs", DEFAULT_BREADCRUMBS_LABELS, labelsProp);
|
|
83
104
|
const kitLink = useKitLink();
|
|
84
105
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -92,7 +113,13 @@ export function Breadcrumbs({
|
|
|
92
113
|
const li = revealedRef.current;
|
|
93
114
|
(li?.querySelector<HTMLElement>("a[href]") ?? li)?.focus();
|
|
94
115
|
}, [expanded]);
|
|
95
|
-
|
|
116
|
+
const hides = hideCurrent ?? host?.titled ?? false;
|
|
117
|
+
const shown = hides ? allItems.slice(0, -1) : allItems;
|
|
118
|
+
if (shown.length === 0) return null;
|
|
119
|
+
// A trail of home entries alone repeats nothing worth a line: in a PageHeader, and
|
|
120
|
+
// wherever the current entry is hidden (a layout's trail above the page's title).
|
|
121
|
+
if ((host || hides) && !shown.some((item) => !(item.home ?? item.href === "/"))) return null;
|
|
122
|
+
const items = shown;
|
|
96
123
|
|
|
97
124
|
const end = Math.max(1, keepEnd);
|
|
98
125
|
// Crumbs [1, firstKept) are the collapsible middle. Collapsing a single crumb into a
|
|
@@ -116,7 +143,8 @@ export function Breadcrumbs({
|
|
|
116
143
|
<nav {...rest} aria-label={ariaLabel ?? labels.label} className={cn("min-w-0 text-sm", className)}>
|
|
117
144
|
<ol className="m-0 flex min-w-0 list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-muted">
|
|
118
145
|
{items.map((item, i) => {
|
|
119
|
-
|
|
146
|
+
// With the current entry hidden, the last one shown is a parent: a link.
|
|
147
|
+
const last = !hides && i === items.length - 1;
|
|
120
148
|
const key = item.key ?? (typeof item.label === "string" ? `${i}-${item.label}` : String(i));
|
|
121
149
|
let content: ReactNode;
|
|
122
150
|
if (last) {
|
|
@@ -13,7 +13,75 @@ import type { KitLinkComponent, KitLinkProps } from "../i18n/kit-labels";
|
|
|
13
13
|
* The other exports are the two buttons' shared parts (`@internal`).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A button's look.
|
|
18
|
+
*
|
|
19
|
+
* - `default`: the quiet action — a warm filled chip with a hairline border. What a
|
|
20
|
+
* Button with no `variant` draws.
|
|
21
|
+
* - `accent`: the strong one, solid `--brand` with `--brand-contrast` text — the one
|
|
22
|
+
* call to action a view leads with. "Accent" is the button's role here, painted in
|
|
23
|
+
* `--brand`; it is not the palette's second anchor colour, also called `accent`
|
|
24
|
+
* (theme/palette-derive.ts), which no button paints.
|
|
25
|
+
* - `secondary`: an outline. `ghost`: text or glyph only. `danger`: solid `--danger`,
|
|
26
|
+
* for an action that destroys something. `link`: a text link that acts (a `<button>`).
|
|
27
|
+
* - `primary` and `brand` are **deprecated** aliases of `default` and `accent` (0.49,
|
|
28
|
+
* same look; a development build warns once per name). They are removed in 0.51, and
|
|
29
|
+
* `primary` will not come back with another meaning.
|
|
30
|
+
*/
|
|
31
|
+
export type ButtonVariant =
|
|
32
|
+
| "default"
|
|
33
|
+
| "accent"
|
|
34
|
+
| "secondary"
|
|
35
|
+
| "ghost"
|
|
36
|
+
| "danger"
|
|
37
|
+
| "link"
|
|
38
|
+
/** @deprecated 0.49 — use `default` (same look); removed in 0.51. */
|
|
39
|
+
| "primary"
|
|
40
|
+
/** @deprecated 0.49 — use `accent` (same look); removed in 0.51. */
|
|
41
|
+
| "brand";
|
|
42
|
+
|
|
43
|
+
/** @internal A {@link ButtonVariant} without the deprecated aliases. */
|
|
44
|
+
export type CanonicalButtonVariant = Exclude<ButtonVariant, "primary" | "brand">;
|
|
45
|
+
|
|
46
|
+
/** The deprecated names and what they stand for — see {@link ButtonVariant}. */
|
|
47
|
+
const DEPRECATED_BUTTON_VARIANTS: Readonly<Record<string, CanonicalButtonVariant>> = {
|
|
48
|
+
primary: "default",
|
|
49
|
+
brand: "accent",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a
|
|
53
|
+
* production one (the consumer's bundler makes `import.meta.env.DEV` false) and never
|
|
54
|
+
* under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */
|
|
55
|
+
function devWarnings(): boolean {
|
|
56
|
+
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const warnedVariants = new Set<string>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @internal A deprecated variant name warns once (per `component` and name) in a
|
|
63
|
+
* development build, then reads as the name it stands for. `component` is the prop's
|
|
64
|
+
* owner in the message ("Button variant", "FormActions submitVariant", …).
|
|
65
|
+
*/
|
|
66
|
+
export function warnDeprecatedVariant(component: string, name: string, replacement: string): void {
|
|
67
|
+
if (!devWarnings()) return;
|
|
68
|
+
const key = `${component}|${name}`;
|
|
69
|
+
if (warnedVariants.has(key)) return;
|
|
70
|
+
warnedVariants.add(key);
|
|
71
|
+
console.warn(
|
|
72
|
+
`[ui-kit] ${component}="${name}" is deprecated: use "${replacement}" (same look). ` +
|
|
73
|
+
`"${name}" is removed in 0.51.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @internal `primary` → `default`, `brand` → `accent` (warning once in development);
|
|
78
|
+
* every other variant as it is. Runs before every lookup keyed by the variant. */
|
|
79
|
+
export function resolveButtonVariant(variant: ButtonVariant, component = "Button variant"): CanonicalButtonVariant {
|
|
80
|
+
const replacement = DEPRECATED_BUTTON_VARIANTS[variant];
|
|
81
|
+
if (replacement === undefined) return variant as CanonicalButtonVariant;
|
|
82
|
+
warnDeprecatedVariant(component, variant, replacement);
|
|
83
|
+
return replacement;
|
|
84
|
+
}
|
|
17
85
|
|
|
18
86
|
// Shared base ring for every button-styled element. Kept as a named const so the
|
|
19
87
|
// <Button> component and the {@link buttonClasses} helper draw from one source and
|
|
@@ -83,13 +151,14 @@ const BUTTON_SIZES_LARGE: Record<ButtonSize, string> = {
|
|
|
83
151
|
export const HOVER_INK = "hover:bg-[color-mix(in_srgb,var(--text-primary)_7%,transparent)]";
|
|
84
152
|
|
|
85
153
|
// Warm, palette-token-driven so buttons blend with the fields + cards in every theme.
|
|
86
|
-
// Actions default to a warm bordered look (
|
|
87
|
-
// outline); `
|
|
154
|
+
// Actions default to a warm bordered look (`default` = filled warm chip, secondary =
|
|
155
|
+
// outline); `accent` is the solid brand fill for the rare strong CTA; `danger` takes the
|
|
88
156
|
// semantic `--danger` family (destructive semantics), so a consumer can re-point the
|
|
89
157
|
// destructive hue instead of inheriting a hard-coded red. Focus rings follow each
|
|
90
|
-
// variant's own accent: brand for the four neutral ones, danger for `danger`.
|
|
91
|
-
|
|
92
|
-
|
|
158
|
+
// variant's own accent: brand for the four neutral ones, danger for `danger`. Keyed by
|
|
159
|
+
// the canonical names: {@link resolveButtonVariant} maps the deprecated ones first.
|
|
160
|
+
export const buttonVariantClasses: Record<CanonicalButtonVariant, string> = {
|
|
161
|
+
default:
|
|
93
162
|
// Hovers onto `--bg-active`, not `--border` (0.33, §12.9): More contrast steps the
|
|
94
163
|
// border until it is a 3:1 LINE, and as a fill under the label that left 2.7:1.
|
|
95
164
|
"border border-subtle bg-surface-2 text-primary hover:bg-active focus-visible:ring-brand",
|
|
@@ -97,7 +166,7 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
|
|
|
97
166
|
ghost: `bg-transparent text-primary ${HOVER_INK} focus-visible:ring-brand`,
|
|
98
167
|
danger:
|
|
99
168
|
"bg-danger text-danger-contrast hover:bg-danger-hover focus-visible:ring-danger-strong",
|
|
100
|
-
|
|
169
|
+
accent:
|
|
101
170
|
"bg-brand text-brand-contrast hover:bg-brand-hover focus-visible:ring-brand",
|
|
102
171
|
// A text link that is still a `<button>` — keksdose's six hand-rolled
|
|
103
172
|
// `<button className="text-brand underline">` sites ("Resend code", "Show all",
|
|
@@ -132,14 +201,14 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
|
|
|
132
201
|
*
|
|
133
202
|
* `danger` also reaches the two NEUTRAL boxed variants, where the box carries it:
|
|
134
203
|
* `secondary` becomes an outlined destructive button (`--danger` text and a
|
|
135
|
-
* `--danger-border` outline, the quiet danger fill on hover) and `
|
|
204
|
+
* `--danger-border` outline, the quiet danger fill on hover) and `default` a soft one
|
|
136
205
|
* (the quiet fill at rest, solid `--danger` on hover). kastlan's meeting-invitations-
|
|
137
206
|
* tab.tsx "Remove all" is `variant="secondary" className="text-destructive"` — a
|
|
138
207
|
* destructive action that must sit in a row of secondary buttons without shouting
|
|
139
208
|
* like the solid `variant="danger"` would, and whose red text alone left the border
|
|
140
209
|
* and hover saying "neutral". `muted` stays a text look and does nothing on a box, and
|
|
141
|
-
* `danger`, `
|
|
142
|
-
* solid danger or
|
|
210
|
+
* `danger`, `accent` and `link`'s own colours are left alone: a tone that recoloured a
|
|
211
|
+
* solid danger or accent button would be a second variant under another name.
|
|
143
212
|
*/
|
|
144
213
|
export type ButtonTone = "default" | "muted" | "danger";
|
|
145
214
|
|
|
@@ -154,19 +223,19 @@ const BUTTON_TONES: Record<Exclude<ButtonTone, "default">, string> = {
|
|
|
154
223
|
};
|
|
155
224
|
|
|
156
225
|
/** Only the two transparent variants take every tone; see {@link ButtonTone}. */
|
|
157
|
-
const TONED_VARIANTS = new Set<
|
|
226
|
+
const TONED_VARIANTS = new Set<CanonicalButtonVariant>(["link", "ghost"]);
|
|
158
227
|
|
|
159
228
|
// `tone="danger"` on the neutral boxed variants — see {@link ButtonTone}. Each string
|
|
160
229
|
// restates every colour the variant sets (border, fill, text, hover, ring) so none of
|
|
161
230
|
// the neutral ones survives the merge.
|
|
162
|
-
const BUTTON_BOXED_DANGER: Partial<Record<
|
|
231
|
+
const BUTTON_BOXED_DANGER: Partial<Record<CanonicalButtonVariant, string>> = {
|
|
163
232
|
// Not boxed, but its hover is a fill: the danger wash under the danger text, as
|
|
164
233
|
// IconButton's danger tone (0.33). On B′'s translucent ink `--danger` fell to 4.2:1
|
|
165
234
|
// over a light page.
|
|
166
235
|
ghost: "hover:bg-danger-soft",
|
|
167
236
|
secondary:
|
|
168
237
|
"border-danger bg-transparent text-danger hover:bg-danger-soft focus-visible:ring-danger-strong",
|
|
169
|
-
|
|
238
|
+
default:
|
|
170
239
|
"border-danger bg-danger-soft text-danger hover:border-[var(--danger)] hover:bg-danger hover:text-danger-contrast focus-visible:ring-danger-strong disabled:hover:border-danger disabled:hover:bg-danger-soft disabled:hover:text-danger",
|
|
171
240
|
};
|
|
172
241
|
|
|
@@ -197,11 +266,12 @@ export interface ButtonClassesOptions {
|
|
|
197
266
|
* compact toolbar links.
|
|
198
267
|
*/
|
|
199
268
|
export function buttonClasses(
|
|
200
|
-
|
|
269
|
+
given: ButtonVariant = "default",
|
|
201
270
|
classNameOrOptions?: string | ButtonClassesOptions,
|
|
202
271
|
): string {
|
|
203
272
|
const { size = "md", className } =
|
|
204
273
|
typeof classNameOrOptions === "object" ? classNameOrOptions : { className: classNameOrOptions };
|
|
274
|
+
const variant = resolveButtonVariant(given, "buttonClasses variant");
|
|
205
275
|
return cn(
|
|
206
276
|
BUTTON_BASE,
|
|
207
277
|
BUTTON_SIZES[size],
|
|
@@ -221,7 +291,8 @@ export const ICON_BUTTON_PRESSED =
|
|
|
221
291
|
"bg-brand-soft text-brand hover:bg-brand-soft-hover hover:text-brand-muted disabled:hover:bg-brand-soft disabled:hover:text-brand";
|
|
222
292
|
|
|
223
293
|
/** @internal The button look shared by the `<button>` and the `<a>` form. */
|
|
224
|
-
export function buttonLook(
|
|
294
|
+
export function buttonLook(given: ButtonVariant, size: ButtonSize, stretch: boolean | undefined, tone: ButtonTone) {
|
|
295
|
+
const variant = resolveButtonVariant(given);
|
|
225
296
|
return cn(
|
|
226
297
|
BUTTON_BASE,
|
|
227
298
|
BUTTON_SIZES[size],
|
|
@@ -24,6 +24,9 @@ import type { ButtonSize, ButtonTone, ButtonVariant } from "./button-look";
|
|
|
24
24
|
* link buttons submitting their form).
|
|
25
25
|
*/
|
|
26
26
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
27
|
+
/** `default` (the quiet action, what a Button without `variant` draws), `accent` (the
|
|
28
|
+
* strong call to action, solid brand), `secondary`, `ghost`, `danger` or `link`.
|
|
29
|
+
* `primary` / `brand` are deprecated aliases of `default` / `accent`. See {@link ButtonVariant}. */
|
|
27
30
|
variant?: ButtonVariant;
|
|
28
31
|
/** `md` (default) is the page's action button. `sm` is the compact one — 12px text
|
|
29
32
|
* and `px-2 py-1` — for the secondary actions in a toolbar, a card header or a
|
|
@@ -39,7 +42,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
39
42
|
ref?: Ref<HTMLButtonElement>;
|
|
40
43
|
/** Text colour for `link` and `ghost`: `muted` (quiet, body colour on hover) or
|
|
41
44
|
* `danger` (quiet, `--danger` on hover). `danger` also makes `secondary` an
|
|
42
|
-
* outlined destructive button and `
|
|
45
|
+
* outlined destructive button and `default` a soft one; ignored by the other
|
|
43
46
|
* variants. See {@link ButtonTone}. */
|
|
44
47
|
tone?: ButtonTone;
|
|
45
48
|
/**
|
|
@@ -181,7 +184,7 @@ function ButtonLink({
|
|
|
181
184
|
replace,
|
|
182
185
|
reloadDocument,
|
|
183
186
|
disabled,
|
|
184
|
-
variant = "
|
|
187
|
+
variant = "default",
|
|
185
188
|
size = "md",
|
|
186
189
|
stretch,
|
|
187
190
|
tone = "default",
|
|
@@ -257,7 +260,7 @@ function ButtonLink({
|
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
function ButtonElement({
|
|
260
|
-
variant = "
|
|
263
|
+
variant = "default",
|
|
261
264
|
size = "md",
|
|
262
265
|
stretch,
|
|
263
266
|
tone = "default",
|
|
@@ -9,6 +9,7 @@ import { parseHex } from "../theme/color";
|
|
|
9
9
|
import { Tooltip } from "./tooltip";
|
|
10
10
|
import type { WeekDay } from "./mini-calendar";
|
|
11
11
|
import { DEFAULT_CALENDAR_HEATMAP_LABELS } from "./calendar-heatmap-labels";
|
|
12
|
+
import { formatWithMinus } from "../lib/minus";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Every string the heatmap speaks — the `calendarHeatmap` namespace of
|
|
@@ -248,7 +249,7 @@ export function CalendarHeatmap({
|
|
|
248
249
|
const providerWeekStart = useKitWeekStart();
|
|
249
250
|
const weekStart: WeekDay = weekStartsOn ?? providerWeekStart ?? localeWeekStart(locale);
|
|
250
251
|
const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
251
|
-
const formatValue = formatValueProp ?? ((v: number) => numberFormat
|
|
252
|
+
const formatValue = formatValueProp ?? ((v: number) => formatWithMinus(numberFormat, v));
|
|
252
253
|
|
|
253
254
|
const values = useMemo(() => {
|
|
254
255
|
const m = new Map<string, number>();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The live side of `chartEdge` (theme/chart-palette.ts): which colours a FILLED mark is
|
|
2
|
+
// painted with and which grounds it sits on, read off <html> as the page resolves them —
|
|
3
|
+
// so the 1px ink edge follows a theme toggle and a palette switch without the chart
|
|
4
|
+
// knowing either store. Shared by SeriesChart (bars, areas) and Treemap (tiles).
|
|
5
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
6
|
+
import { chartEdge } from "../theme/chart-palette";
|
|
7
|
+
import { parseHex } from "../theme/color";
|
|
8
|
+
|
|
9
|
+
/** The tokens a theme or palette change can move, read as ONE string so
|
|
10
|
+
* `useSyncExternalStore` compares a primitive (a fresh array would never be equal). */
|
|
11
|
+
const WATCHED = [
|
|
12
|
+
"--bg-surface",
|
|
13
|
+
"--bg-page",
|
|
14
|
+
...Array.from({ length: 9 }, (_, i) => `--chart-${i + 1}`),
|
|
15
|
+
"--chart-ink",
|
|
16
|
+
"--chart-muted",
|
|
17
|
+
"--money-income",
|
|
18
|
+
"--money-expense",
|
|
19
|
+
"--money-net",
|
|
20
|
+
"--money-neutral",
|
|
21
|
+
"--brand",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function readWatched(): string {
|
|
25
|
+
const style = getComputedStyle(document.documentElement);
|
|
26
|
+
return WATCHED.map((name) => style.getPropertyValue(name).trim()).join(",");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function subscribe(onChange: () => void): () => void {
|
|
30
|
+
const observer = new MutationObserver(onChange);
|
|
31
|
+
observer.observe(document.documentElement, {
|
|
32
|
+
attributes: true,
|
|
33
|
+
attributeFilter: ["class", "style", "data-theme", "data-contrast"],
|
|
34
|
+
});
|
|
35
|
+
return () => observer.disconnect();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Server render: nothing measured, so no edge — the client adds it on hydration. */
|
|
39
|
+
const SERVER = "";
|
|
40
|
+
|
|
41
|
+
const BARE_VAR = /^var\(\s*(--[A-Za-z0-9_-]+)\s*\)$/;
|
|
42
|
+
|
|
43
|
+
/** A colour as the page resolves it: a hex as it is, a bare `var(--token)` read off
|
|
44
|
+
* <html>, anything else (a `color-mix()`, a var with a fallback) null. */
|
|
45
|
+
export function resolveChartColor(color: string, style: Pick<CSSStyleDeclaration, "getPropertyValue">): string | null {
|
|
46
|
+
const trimmed = color.trim();
|
|
47
|
+
if (parseHex(trimmed)) return trimmed;
|
|
48
|
+
const m = BARE_VAR.exec(trimmed);
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
const value = style.getPropertyValue(m[1]).trim();
|
|
51
|
+
return parseHex(value) ? value : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `(fill) => edge`: the ink stroke a filled mark in `fill` needs on this page, or
|
|
56
|
+
* `undefined` — only for a colour under 3:1 against the card or the page (WCAG 1.4.11).
|
|
57
|
+
* `fill` may be a hex or a bare `var(--chart-N)` / `var(--money-…)`; anything it cannot
|
|
58
|
+
* measure gets no edge.
|
|
59
|
+
*/
|
|
60
|
+
export function useChartEdge(): (fill: string | undefined) => string | undefined {
|
|
61
|
+
const snapshot = useSyncExternalStore(subscribe, readWatched, () => SERVER);
|
|
62
|
+
return useCallback(
|
|
63
|
+
(fill: string | undefined) => {
|
|
64
|
+
if (!fill || snapshot === SERVER || typeof document === "undefined") return undefined;
|
|
65
|
+
const style = getComputedStyle(document.documentElement);
|
|
66
|
+
const hex = resolveChartColor(fill, style);
|
|
67
|
+
if (!hex) return undefined;
|
|
68
|
+
const surfaces = ["--bg-surface", "--bg-page"].map((n) => style.getPropertyValue(n).trim());
|
|
69
|
+
return chartEdge(hex, surfaces);
|
|
70
|
+
},
|
|
71
|
+
[snapshot],
|
|
72
|
+
);
|
|
73
|
+
}
|
package/src/components/chart.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "recharts";
|
|
18
18
|
import { cn } from "../lib/cn";
|
|
19
19
|
import { isRtl } from "../lib/direction";
|
|
20
|
+
import { TYPOGRAPHIC_MINUS } from "../lib/minus";
|
|
20
21
|
|
|
21
22
|
export interface ChartSeriesConfig {
|
|
22
23
|
label: ReactNode;
|
|
@@ -376,7 +377,8 @@ export function ChartTooltipContent({
|
|
|
376
377
|
if (inRow && !shown) text = NO_VALUE;
|
|
377
378
|
else if (formatValue) text = formatValue(raw);
|
|
378
379
|
else if (raw == null) text = NO_VALUE;
|
|
379
|
-
|
|
380
|
+
// The bare number's minus is U+2212, as every kit figure's (0.49, KIT-1).
|
|
381
|
+
else text = valueFormatter ? valueFormatter(raw) : String(raw).replace(/-/g, TYPOGRAPHIC_MINUS);
|
|
380
382
|
return (
|
|
381
383
|
<div
|
|
382
384
|
key={`${cfgKey}-${i}`}
|
package/src/components/chip.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import type { KeyboardEvent, MouseEvent, ReactElement, ReactNode, Ref } from "re
|
|
|
3
3
|
import { Check, X } from "lucide-react";
|
|
4
4
|
import type { LucideIcon } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
|
+
import { statusTone, type StatusKind } from "../lib/status-tone";
|
|
6
7
|
import { TOUCH_TARGET_LARGE } from "./button-look";
|
|
7
8
|
import { FOCUS_RING } from "./focus-ring";
|
|
8
9
|
import { assignRef, LockedReason, type DisabledReasonDisplay } from "./field-parts";
|
|
@@ -342,6 +343,12 @@ const REMOVE_HIT_AREA_LARGE =
|
|
|
342
343
|
interface ChipBaseProps {
|
|
343
344
|
children: ReactNode;
|
|
344
345
|
tone?: ChipTone;
|
|
346
|
+
/**
|
|
347
|
+
* What the value means as a status (0.49 KIT-8), instead of a `tone`: `good` (success),
|
|
348
|
+
* `attention` (warning), `problem` (danger), `active` (info), `inactive` (neutral) —
|
|
349
|
+
* {@link statusTone}. An explicit `tone` wins. Never `brand` for a status.
|
|
350
|
+
*/
|
|
351
|
+
status?: StatusKind;
|
|
345
352
|
size?: ChipSize;
|
|
346
353
|
/** See {@link ChipVariant}. Default `soft`. */
|
|
347
354
|
variant?: ChipVariant;
|
|
@@ -537,7 +544,8 @@ export type ChipProps = ChipBaseProps &
|
|
|
537
544
|
export function Chip({
|
|
538
545
|
ref,
|
|
539
546
|
children,
|
|
540
|
-
tone
|
|
547
|
+
tone: toneProp,
|
|
548
|
+
status,
|
|
541
549
|
size = "md",
|
|
542
550
|
variant = "soft",
|
|
543
551
|
shape = "pill",
|
|
@@ -563,6 +571,7 @@ export function Chip({
|
|
|
563
571
|
commit,
|
|
564
572
|
...rest
|
|
565
573
|
}: ChipProps) {
|
|
574
|
+
const tone: ChipTone = toneProp ?? (status ? statusTone(status) : "neutral");
|
|
566
575
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS, { remove: removeLabel });
|
|
567
576
|
const reasonId = useId();
|
|
568
577
|
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import type { ConfirmFn } from "./confirm-dialog";
|
|
3
|
+
|
|
4
|
+
/** The nearest `ConfirmProvider`'s `confirm`, or `null`. Internal: its own module so a
|
|
5
|
+
* part that may sit outside a provider (`RowActions`, 0.49 KIT-12) can ask without
|
|
6
|
+
* `useConfirm()`'s throw, and without adding a name to the barrel. */
|
|
7
|
+
export const ConfirmContext = createContext<ConfirmFn | null>(null);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels";
|
|
@@ -8,6 +8,7 @@ import { DialogFrame } from "./dialog-frame";
|
|
|
8
8
|
import { Button } from "./button";
|
|
9
9
|
import { useWriteLock, writeLockFor, type CommitScope } from "./write-lock";
|
|
10
10
|
import { DEFAULT_CONFIRM_DIALOG_LABELS } from "./confirm-dialog-labels";
|
|
11
|
+
import { ConfirmContext } from "./confirm-context";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback
|
|
@@ -89,7 +90,6 @@ export interface ConfirmOptions {
|
|
|
89
90
|
|
|
90
91
|
export type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
|
|
91
92
|
|
|
92
|
-
const ConfirmContext = createContext<ConfirmFn | null>(null);
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* `const confirm = useConfirm()` → `if (!(await confirm({ title }))) return`.
|
|
@@ -298,7 +298,7 @@ function ConfirmDialogView({
|
|
|
298
298
|
<Button
|
|
299
299
|
ref={confirmRef}
|
|
300
300
|
type="button"
|
|
301
|
-
variant={tone === "danger" ? "danger" : "
|
|
301
|
+
variant={tone === "danger" ? "danger" : "default"}
|
|
302
302
|
disabled={!matches}
|
|
303
303
|
disabledReason={request.lockReason}
|
|
304
304
|
onClick={() => {
|
|
@@ -449,14 +449,14 @@ export interface DangerConfirmProps extends Omit<ComponentPropsWithRef<"div">, "
|
|
|
449
449
|
tone?: "danger" | "warning";
|
|
450
450
|
/**
|
|
451
451
|
* The confirm button's variant, apart from `tone` (0.24, keksdose). Default: what
|
|
452
|
-
* `tone` picks, as before — `"danger"` for `"danger"`, `"
|
|
452
|
+
* `tone` picks, as before — `"danger"` for `"danger"`, `"default"` for `"warning"`.
|
|
453
453
|
*
|
|
454
454
|
* `tone` says how bad the outcome is; this says how loud the last press is, and the two
|
|
455
455
|
* need not agree. keksdose's `UserActionConfirm` (an admin resets somebody else's
|
|
456
456
|
* password) asks an AMBER question — what it guards against is mis-targeting, not data
|
|
457
457
|
* loss, so the danger colour stays its one `severe` consequence's — and ends in a RED
|
|
458
458
|
* Go, as its hand-built panel did before it moved onto this tile in 0.23; with only
|
|
459
|
-
* `tone`, the amber question made Go the neutral
|
|
459
|
+
* `tone`, the amber question made Go the neutral `default` fill. `tone="warning"
|
|
460
460
|
* confirmVariant="danger"` is that panel again.
|
|
461
461
|
*/
|
|
462
462
|
confirmVariant?: ButtonVariant;
|
|
@@ -914,7 +914,7 @@ export function DangerConfirm({
|
|
|
914
914
|
<Button
|
|
915
915
|
id={confirmId}
|
|
916
916
|
type="submit"
|
|
917
|
-
variant={confirmVariant ?? (tone === "warning" ? "
|
|
917
|
+
variant={confirmVariant ?? (tone === "warning" ? "default" : "danger")}
|
|
918
918
|
disabled={!canConfirm}
|
|
919
919
|
// Held by a guard (the caller's or a built-in one), or armed and then locked
|
|
920
920
|
// (or rendered armed under a lock): the confirm says why the way Button
|
|
@@ -15,7 +15,9 @@ import type { DataTableColumn } from "./data-table";
|
|
|
15
15
|
// room for them (its need re-measured, plus the column's own header width from when
|
|
16
16
|
// it was left out), so narrower content (a filter, another page) counts as much as a
|
|
17
17
|
// wider scroller. The scroller's own width, through a ResizeObserver: a sidebar, a
|
|
18
|
-
// narrower window, the Columns drawer and Large text all count the same.
|
|
18
|
+
// narrower window, the Columns drawer and Large text all count the same. A column
|
|
19
|
+
// whose return the very next measure undoes stays out until the table has more room
|
|
20
|
+
// for it than it had then (0.48.3, keksdose B2: see the bring-back in `measure`).
|
|
19
21
|
|
|
20
22
|
/** The least a `grow` column keeps before the table starts leaving columns out: about
|
|
21
23
|
* a sentence of a title. In rem, so Large text asks for more. */
|
|
@@ -94,6 +96,13 @@ export function useColumnFit<T>({
|
|
|
94
96
|
// table's need as it is now, plus this, fits the scroller (keksdose S3, 0.48: not the
|
|
95
97
|
// whole table's need at hide time, which went stale when the content narrowed).
|
|
96
98
|
const ownWidth = useRef(new Map<string, number>());
|
|
99
|
+
// A column whose return the very next measure undid, at the same scroller width: the
|
|
100
|
+
// room the table had for it when it came back (keksdose B2, 0.48.3). It stays out
|
|
101
|
+
// until the table has more. Dropped once the column is no longer left out for width.
|
|
102
|
+
const stuck = useRef(new Map<string, number>());
|
|
103
|
+
// What the last measure brought back, each column with the room it had then, and at
|
|
104
|
+
// which scroller width: read (and cleared) by the measure right after it.
|
|
105
|
+
const lastShown = useRef<{ avail: number; rooms: Map<string, number> } | null>(null);
|
|
97
106
|
|
|
98
107
|
const hasFit = columns.some((c) => c.fitPriority != null);
|
|
99
108
|
const active = enabled && hasFit;
|
|
@@ -118,6 +127,10 @@ export function useColumnFit<T>({
|
|
|
118
127
|
|
|
119
128
|
const measure = useCallback(() => {
|
|
120
129
|
const { order, current, growKey, active, scroller } = latest.current;
|
|
130
|
+
// Only the measure right after a bring-back can undo it.
|
|
131
|
+
const flip = lastShown.current;
|
|
132
|
+
lastShown.current = null;
|
|
133
|
+
for (const k of stuck.current.keys()) if (!current.includes(k)) stuck.current.delete(k);
|
|
121
134
|
const el = scroller();
|
|
122
135
|
if (!active || !el) return;
|
|
123
136
|
const table = el.querySelector("table");
|
|
@@ -150,20 +163,42 @@ export function useColumnFit<T>({
|
|
|
150
163
|
need -= w;
|
|
151
164
|
next.push(col.key);
|
|
152
165
|
}
|
|
153
|
-
if (next.length
|
|
166
|
+
if (next.length === current.length) return;
|
|
167
|
+
// The last measure brought these back at this same width, and the table cannot
|
|
168
|
+
// hold them: their header width fell short of what they cost drawn (see below).
|
|
169
|
+
if (flip && Math.abs(flip.avail - avail) <= 0.5)
|
|
170
|
+
for (const k of next.slice(current.length)) {
|
|
171
|
+
const room = flip.rooms.get(k);
|
|
172
|
+
if (room != null) stuck.current.set(k, room);
|
|
173
|
+
}
|
|
174
|
+
setHidden(next);
|
|
154
175
|
return;
|
|
155
176
|
}
|
|
156
177
|
// Bring back, the last left out first, while the table as it is now has room for
|
|
157
|
-
// them
|
|
158
|
-
//
|
|
178
|
+
// them by their recorded header width. A column that costs more drawn than that is
|
|
179
|
+
// left out again by the next measure, before paint. That alone does not settle it:
|
|
180
|
+
// the re-hide records its header's width again, and the header can measure the same
|
|
181
|
+
// while the column costs more (`virtualize`'s locked widths, keksdose B2, 0.48.3: the
|
|
182
|
+
// drawn column cost the table ~80–110px more than its header), so it came back, went, came back … until React's
|
|
183
|
+
// update limit (#185). So a column whose return the next measure undid, at the same
|
|
184
|
+
// scroller width, waits for more room than it had then: a wider scroller or narrower
|
|
185
|
+
// content. Whatever the error in its width, that is one invisible flip per room it is
|
|
186
|
+
// tried at.
|
|
159
187
|
let n = current.length;
|
|
188
|
+
const rooms = new Map<string, number>();
|
|
160
189
|
while (n > 0) {
|
|
161
|
-
const
|
|
190
|
+
const k = current[n - 1];
|
|
191
|
+
const w = ownWidth.current.get(k);
|
|
162
192
|
if (w == null || need + w > avail + 0.5) break;
|
|
193
|
+
const room = stuck.current.get(k);
|
|
194
|
+
if (room != null && avail - need <= room + 0.5) break;
|
|
195
|
+
rooms.set(k, avail - need);
|
|
163
196
|
need += w;
|
|
164
197
|
n--;
|
|
165
198
|
}
|
|
166
|
-
if (n
|
|
199
|
+
if (n === current.length) return;
|
|
200
|
+
lastShown.current = { avail, rooms };
|
|
201
|
+
setHidden(current.slice(0, n));
|
|
167
202
|
}, [headRefs]);
|
|
168
203
|
|
|
169
204
|
// After a render that changed what is drawn: rows, a filter or a page can widen a
|