@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { Fragment, isValidElement, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode } from "react";
|
|
3
3
|
import { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from "lucide-react";
|
|
4
4
|
import { FOCUS_RING } from "./focus-ring";
|
|
@@ -13,6 +13,9 @@ import { FilterPopover } from "./data-table-filter-popover";
|
|
|
13
13
|
import { SwipeableRow, type SwipeAction } from "./swipeable-row";
|
|
14
14
|
import { DialogFrame } from "./dialog-frame";
|
|
15
15
|
import { Checkbox } from "./checkbox";
|
|
16
|
+
import { Chip } from "./chip";
|
|
17
|
+
import { BooleanMark } from "./data-table-cells";
|
|
18
|
+
import { EMPTY_FORMATTED_VALUE } from "../lib/format";
|
|
16
19
|
import { useLongPress } from "../hooks/use-long-press";
|
|
17
20
|
import type { DataTableLabels } from "./data-table-labels";
|
|
18
21
|
import { RowLink, cleanAttrs, columnLabel, fromOutside, fromOwnControl } from "./data-table-shared";
|
|
@@ -70,19 +73,21 @@ const CARD_NO_TRUNCATE =
|
|
|
70
73
|
* header's `sr-only` text (an actions column's), as every kit container must.
|
|
71
74
|
*/
|
|
72
75
|
export function StackedFields<T>({ columns, row, compact }: { columns: DataTableColumn<T>[]; row: T; compact: boolean }) {
|
|
76
|
+
const fields = filledFields(columns, row);
|
|
77
|
+
if (fields.length === 0) return null;
|
|
73
78
|
return (
|
|
74
79
|
<dl
|
|
75
80
|
data-card-fields="stacked"
|
|
76
81
|
className={cn("relative flex min-w-0 flex-col", compact ? "gap-1.5 text-xs" : "gap-2.5 text-sm", CARD_NO_TRUNCATE)}
|
|
77
82
|
>
|
|
78
|
-
{
|
|
83
|
+
{fields.map(({ col, value }) => (
|
|
79
84
|
<div key={col.key} className="min-w-0">
|
|
80
85
|
<dt className="text-xs uppercase tracking-wide text-muted">{col.header}</dt>
|
|
81
86
|
<dd
|
|
82
87
|
{...dataAttrsOf(col.cellProps?.(row))}
|
|
83
88
|
className="mt-0.5 min-w-0 break-words text-secondary"
|
|
84
89
|
>
|
|
85
|
-
{
|
|
90
|
+
{value}
|
|
86
91
|
</dd>
|
|
87
92
|
</div>
|
|
88
93
|
))}
|
|
@@ -90,6 +95,112 @@ export function StackedFields<T>({ columns, row, compact }: { columns: DataTable
|
|
|
90
95
|
);
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
/** The most fields the default phone card shows under its title (0.49 KIT-7). */
|
|
99
|
+
export const MOBILE_CARD_FIELDS = 3;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The default phone card's columns (0.49 KIT-7), out of the phone's columns (visible,
|
|
103
|
+
* not `mobileHidden`): the title — `mobile: "primary"` or `mobilePrimary`, else the
|
|
104
|
+
* first that is not a status — the `role: "status"` columns for beside it, at most
|
|
105
|
+
* {@link MOBILE_CARD_FIELDS} fields: those marked `mobile: "secondary"` when any is,
|
|
106
|
+
* else the first after the title, and the `role: "actions"` columns, drawn after the
|
|
107
|
+
* fields. An actions column is never the title and never one of the fields, so the
|
|
108
|
+
* fields and the marks leave it on the card; only `mobileHidden` / `mobile: "hidden"`
|
|
109
|
+
* (which keep it out of `columns`) remove it.
|
|
110
|
+
*/
|
|
111
|
+
export function mobileCardColumns<T>(columns: DataTableColumn<T>[]): {
|
|
112
|
+
primary: DataTableColumn<T> | null;
|
|
113
|
+
status: DataTableColumn<T>[];
|
|
114
|
+
fields: DataTableColumn<T>[];
|
|
115
|
+
actions: DataTableColumn<T>[];
|
|
116
|
+
} {
|
|
117
|
+
const titles = columns.filter((c) => c.role !== "actions");
|
|
118
|
+
const primary =
|
|
119
|
+
titles.find((c) => c.mobile === "primary") ??
|
|
120
|
+
titles.find((c) => c.mobilePrimary) ??
|
|
121
|
+
titles.find((c) => c.role !== "status") ??
|
|
122
|
+
titles[0] ??
|
|
123
|
+
null;
|
|
124
|
+
const status = columns.filter((c) => c !== primary && c.role === "status");
|
|
125
|
+
const actions = columns.filter((c) => c.role === "actions");
|
|
126
|
+
const rest = titles.filter((c) => c !== primary && c.role !== "status");
|
|
127
|
+
const marked = rest.filter((c) => c.mobile === "secondary");
|
|
128
|
+
return { primary, status, fields: (marked.length > 0 ? marked : rest).slice(0, MOBILE_CARD_FIELDS), actions };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A column whose cell keeps its own clicks — `noRowLink`, or `role: "actions"`: no row
|
|
132
|
+
* link may wrap it, and a card that draws it is not a link itself. */
|
|
133
|
+
export function keepsOwnClicks<T>(col: DataTableColumn<T>): boolean {
|
|
134
|
+
return !!col.noRowLink || col.role === "actions";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a
|
|
138
|
+
* production one, never under vitest. Read per call so a test can switch it on. */
|
|
139
|
+
function devWarnings(): boolean {
|
|
140
|
+
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A development build's note on the default phone card (0.49 KIT-7): a `noRowLink`
|
|
145
|
+
* column past its three fields. Such a column holds the cell's own controls — a per-row
|
|
146
|
+
* delete, a link — and the card leaves it out with them, so on a phone the action is
|
|
147
|
+
* gone. Never for a `role: "actions"` column, which the card draws after its fields.
|
|
148
|
+
* Read off the declared `columns` (not the ones a person or the width hid), and only
|
|
149
|
+
* while the table lets the card choose: `enabled` is false with a `mobileCard` or
|
|
150
|
+
* `mobileDetailsInRow` (the column is then in the opened row), and a column marked
|
|
151
|
+
* `mobile: "secondary"` means the app chose. Once per table and column key; `name`
|
|
152
|
+
* (the table's `storageKey` or `urlSync` prefix) says which table, else its title
|
|
153
|
+
* column does.
|
|
154
|
+
*/
|
|
155
|
+
export function useCardColumnWarnings<T>(columns: DataTableColumn<T>[], enabled: boolean, name?: string): void {
|
|
156
|
+
const warned = useRef<Set<string> | null>(null);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!enabled || !devWarnings()) return;
|
|
159
|
+
const phone = columns.filter((c) => !c.mobileHidden && c.mobile !== "hidden");
|
|
160
|
+
if (phone.some((c) => c.mobile === "secondary")) return;
|
|
161
|
+
const { primary, status, fields, actions } = mobileCardColumns(phone);
|
|
162
|
+
const onCard = new Set<DataTableColumn<T> | null>([primary, ...status, ...fields, ...actions]);
|
|
163
|
+
const table = name ? ` "${name}"` : primary ? ` (title column "${primary.key}")` : "";
|
|
164
|
+
warned.current ??= new Set();
|
|
165
|
+
for (const col of phone) {
|
|
166
|
+
if (onCard.has(col) || !col.noRowLink || warned.current.has(col.key)) continue;
|
|
167
|
+
warned.current.add(col.key);
|
|
168
|
+
console.warn(
|
|
169
|
+
`[ui-kit] DataTable${table}: column "${col.key}" (noRowLink) is past the phone card's three fields and ` +
|
|
170
|
+
'leaves the card — give it role: "actions" (drawn after the fields), or move its action to rowActions.',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}, [columns, enabled, name]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Whether a rendered cell shows nothing worth a line (0.49 KIT-7): `null`, `undefined`,
|
|
178
|
+
* a boolean (React draws neither), `""` or blank text, and the formatters' empty value
|
|
179
|
+
* "—" ({@link EMPTY_FORMATTED_VALUE}), and `booleanColumn`'s mark for an unset value.
|
|
180
|
+
* Any other element is never empty here — its output is the component's — so a column
|
|
181
|
+
* whose empty value should drop off the card returns `null` (or "—") from `cell` for it.
|
|
182
|
+
*/
|
|
183
|
+
export function isEmptyCell(value: ReactNode): boolean {
|
|
184
|
+
if (value === null || value === undefined || typeof value === "boolean") return true;
|
|
185
|
+
if (isValidElement<{ value?: unknown }>(value) && value.type === BooleanMark) return value.props.value == null;
|
|
186
|
+
if (typeof value === "string") {
|
|
187
|
+
const text = value.trim();
|
|
188
|
+
return text === "" || text === EMPTY_FORMATTED_VALUE;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Each column's rendered cell, without the empty ones. */
|
|
194
|
+
function filledFields<T>(columns: DataTableColumn<T>[], row: T): { col: DataTableColumn<T>; value: ReactNode }[] {
|
|
195
|
+
return columns.map((col) => ({ col, value: col.cell(row) })).filter(({ value }) => !isEmptyCell(value));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A status column's value beside the card's title: text in a neutral chip, an element
|
|
199
|
+
* (the app's own Chip or StatusDot) as it is. */
|
|
200
|
+
function StatusChip({ value }: { value: ReactNode }) {
|
|
201
|
+
return typeof value === "string" || typeof value === "number" ? <Chip size="sm">{value}</Chip> : <>{value}</>;
|
|
202
|
+
}
|
|
203
|
+
|
|
93
204
|
// ---------- Phone sort control (keksdose K10) ----------
|
|
94
205
|
|
|
95
206
|
/**
|
|
@@ -199,7 +310,11 @@ export interface MobileRowProps<T> {
|
|
|
199
310
|
mobileCardLinkable: boolean;
|
|
200
311
|
mobileCard: DataTableProps<T>["mobileCard"];
|
|
201
312
|
mobilePrimaryCol: DataTableColumn<T> | null;
|
|
313
|
+
/** The `role: "status"` columns, drawn as chips beside the title (0.49 KIT-7). */
|
|
314
|
+
mobileStatusColumns: DataTableColumn<T>[];
|
|
202
315
|
mobileSecondaryColumns: DataTableColumn<T>[];
|
|
316
|
+
/** The `role: "actions"` columns, drawn after the fields at the card's end (0.49 KIT-7). */
|
|
317
|
+
mobileActionColumns: DataTableColumn<T>[];
|
|
203
318
|
mobileExpandAsDialog: boolean;
|
|
204
319
|
compact: boolean;
|
|
205
320
|
/** The text size is Large or Extra large (§4). */
|
|
@@ -236,7 +351,9 @@ export function MobileRow<T>({
|
|
|
236
351
|
mobileCardLinkable,
|
|
237
352
|
mobileCard,
|
|
238
353
|
mobilePrimaryCol,
|
|
354
|
+
mobileStatusColumns,
|
|
239
355
|
mobileSecondaryColumns,
|
|
356
|
+
mobileActionColumns,
|
|
240
357
|
mobileExpandAsDialog,
|
|
241
358
|
compact,
|
|
242
359
|
large,
|
|
@@ -305,6 +422,11 @@ export function MobileRow<T>({
|
|
|
305
422
|
// itself is the anchor, and routing it through `linkColumn` too would nest an
|
|
306
423
|
// <a> inside an <a>. Desktop and mobile are mutually exclusive (`isMdUp`), so
|
|
307
424
|
// exactly one anchor exists per row.
|
|
425
|
+
// The default card's filled fields and status values (0.49 KIT-7): an empty one
|
|
426
|
+
// gets no line. Not computed for a `mobileCard`, which draws its own.
|
|
427
|
+
const fields = mobileCard ? [] : filledFields(mobileSecondaryColumns, row);
|
|
428
|
+
const statuses = mobileCard ? [] : filledFields(mobileStatusColumns, row);
|
|
429
|
+
const actionCells = mobileCard ? [] : filledFields(mobileActionColumns, row);
|
|
308
430
|
const cardInner = (
|
|
309
431
|
<>
|
|
310
432
|
{/* The card's own content keeps the column it always had; the chevron sits
|
|
@@ -323,24 +445,42 @@ export function MobileRow<T>({
|
|
|
323
445
|
mobileCard(row)
|
|
324
446
|
) : (
|
|
325
447
|
<>
|
|
326
|
-
{/* Only the `data-*` half of `cellProps` — see its note.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
448
|
+
{/* Only the `data-*` half of `cellProps` — see its note. The title row
|
|
449
|
+
carries the status chips at its end (0.49 KIT-7). */}
|
|
450
|
+
{(mobilePrimaryCol || statuses.length > 0) && (
|
|
451
|
+
<div data-card-head="" className="flex min-w-0 items-start justify-between gap-2">
|
|
452
|
+
{mobilePrimaryCol && (
|
|
453
|
+
<div
|
|
454
|
+
{...dataAttrsOf(mobilePrimaryCol.cellProps?.(row))}
|
|
455
|
+
data-card-title=""
|
|
456
|
+
className="min-w-0 flex-1 break-words font-medium"
|
|
457
|
+
>
|
|
458
|
+
{mobilePrimaryCol.cell(row)}
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
{statuses.length > 0 && (
|
|
462
|
+
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
|
|
463
|
+
{statuses.map(({ col, value }) => (
|
|
464
|
+
<span key={col.key} {...dataAttrsOf(col.cellProps?.(row))} data-card-status="" className="inline-flex">
|
|
465
|
+
<StatusChip value={value} />
|
|
466
|
+
</span>
|
|
467
|
+
))}
|
|
468
|
+
</div>
|
|
469
|
+
)}
|
|
330
470
|
</div>
|
|
331
471
|
)}
|
|
332
|
-
{
|
|
472
|
+
{fields.length > 0 && large ? (
|
|
333
473
|
// §4: each label ABOVE its value at Large. Side by side, a label column
|
|
334
474
|
// sized to the widest label leaves a value a sliver of a 240 px card.
|
|
335
475
|
<StackedFields columns={mobileSecondaryColumns} row={row} compact={compact} />
|
|
336
|
-
) :
|
|
476
|
+
) : fields.length > 0 && (
|
|
337
477
|
<dl
|
|
338
478
|
className={cn(
|
|
339
479
|
"grid grid-cols-[auto_1fr] gap-x-3",
|
|
340
480
|
compact ? "gap-y-0.5 text-xs" : "gap-y-1 text-sm",
|
|
341
481
|
)}
|
|
342
482
|
>
|
|
343
|
-
{
|
|
483
|
+
{fields.map(({ col, value }) => (
|
|
344
484
|
<Fragment key={col.key}>
|
|
345
485
|
<dt className="text-xs uppercase tracking-wide text-muted self-center">
|
|
346
486
|
{col.header}
|
|
@@ -349,12 +489,23 @@ export function MobileRow<T>({
|
|
|
349
489
|
{...dataAttrsOf(col.cellProps?.(row))}
|
|
350
490
|
className="min-w-0 text-secondary self-center"
|
|
351
491
|
>
|
|
352
|
-
{
|
|
492
|
+
{value}
|
|
353
493
|
</dd>
|
|
354
494
|
</Fragment>
|
|
355
495
|
))}
|
|
356
496
|
</dl>
|
|
357
497
|
)}
|
|
498
|
+
{/* The row's actions after the fields, at the card's end (0.49 KIT-7): its
|
|
499
|
+
buttons keep their own clicks, and RowActions folds into "⋯" at Large. */}
|
|
500
|
+
{actionCells.length > 0 && (
|
|
501
|
+
<div data-card-actions="" className="flex min-w-0 flex-wrap items-center justify-end gap-2">
|
|
502
|
+
{actionCells.map(({ col, value }) => (
|
|
503
|
+
<div key={col.key} {...dataAttrsOf(col.cellProps?.(row))} className="flex min-w-0 justify-end">
|
|
504
|
+
{value}
|
|
505
|
+
</div>
|
|
506
|
+
))}
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
358
509
|
</>
|
|
359
510
|
)}
|
|
360
511
|
</div>
|
|
@@ -64,7 +64,8 @@ function swipeTone(action: SwipeAction): RowActionTone {
|
|
|
64
64
|
* `end` then `start` — the physical `right` / `left` only where the logical side is
|
|
65
65
|
* missing, as the card reads them — each action once, told apart by its `id`, else by its
|
|
66
66
|
* label (two stages with one label would be one button twice). Each button runs the
|
|
67
|
-
* swipe's own `onCommit
|
|
67
|
+
* swipe's own `onCommit` — with no confirmation of the kit's (`confirm: false`), as the
|
|
68
|
+
* swipe has none; a swipe's `danger` / `warning` / `info` tone carries over, any
|
|
68
69
|
* other reads `muted`. `options.omit` leaves an action out, `options.commit` gives it a
|
|
69
70
|
* `CommitScope` (see {@link DataTableDesktopSwipeActions}). A `null` plan is `[]`.
|
|
70
71
|
*
|
|
@@ -94,6 +95,10 @@ export function swipePlanRowActions<T>(
|
|
|
94
95
|
: undefined,
|
|
95
96
|
tone: swipeTone(action),
|
|
96
97
|
commit,
|
|
98
|
+
// A swipe commits without the kit asking (the plan's `onCommit` asks, as keksdose's
|
|
99
|
+
// does), so its button does not ask twice (0.49 KIT-12); a danger one still sits in
|
|
100
|
+
// the row's menu.
|
|
101
|
+
confirm: false,
|
|
97
102
|
onSelect: action.onCommit,
|
|
98
103
|
});
|
|
99
104
|
}
|
|
@@ -142,6 +147,9 @@ function toRowAction<T>(a: ResolvedRowAction<T>, row: T): RowAction {
|
|
|
142
147
|
icon: isValidElement(a.icon) ? a.icon : a.icon != null && a.icon !== false ? <>{a.icon}</> : undefined,
|
|
143
148
|
tone: a.tone,
|
|
144
149
|
disabledReason: a.action.disabledReason?.(row),
|
|
150
|
+
// The table's own `confirm` asks in `runRowAction`; RowActions' default question
|
|
151
|
+
// (0.49 KIT-12) is for a delete that brought none.
|
|
152
|
+
confirm: a.action.confirm ? false : undefined,
|
|
145
153
|
onSelect: () => void runRowAction(a.action, row),
|
|
146
154
|
};
|
|
147
155
|
}
|
|
@@ -406,7 +406,9 @@ export interface DataTableMobileRowProps<T>
|
|
|
406
406
|
| "mobileCardLinkable"
|
|
407
407
|
| "mobileCard"
|
|
408
408
|
| "mobilePrimaryCol"
|
|
409
|
+
| "mobileStatusColumns"
|
|
409
410
|
| "mobileSecondaryColumns"
|
|
411
|
+
| "mobileActionColumns"
|
|
410
412
|
| "mobileExpandAsDialog"
|
|
411
413
|
| "compact"
|
|
412
414
|
| "large"
|
|
@@ -6,7 +6,15 @@ import { isTableOwnColumn, useRowActionColumn } from "./data-table-row-actions";
|
|
|
6
6
|
import { useColumnResize } from "./data-table-resize";
|
|
7
7
|
import { useRangeSelection } from "./data-table-selection";
|
|
8
8
|
import { columnLabel, logicalAlign, resolveDataTableLabels } from "./data-table-shared";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
MobileFilters,
|
|
11
|
+
MobileSelectBar,
|
|
12
|
+
MobileSort,
|
|
13
|
+
StackedFields,
|
|
14
|
+
keepsOwnClicks,
|
|
15
|
+
mobileCardColumns,
|
|
16
|
+
useCardColumnWarnings,
|
|
17
|
+
} from "./data-table-mobile";
|
|
10
18
|
import { DataTableMobileRow, DataTableRow, type DataTableRowCtx } from "./data-table-row";
|
|
11
19
|
import { DataTableHead } from "./data-table-head";
|
|
12
20
|
import { DataTableLeadingRow } from "./data-table-leading-row";
|
|
@@ -61,6 +69,36 @@ export interface DataTableColumn<T> {
|
|
|
61
69
|
// bookkeeping) that would crowd a card without aiding scanning.
|
|
62
70
|
mobilePrimary?: boolean;
|
|
63
71
|
mobileHidden?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* This column on the phone's default card (0.49 KIT-7; ignored with `mobileCard`):
|
|
74
|
+
* `"primary"` — the card's title, as `mobilePrimary`; `"secondary"` — one of its
|
|
75
|
+
* fields; `"hidden"` — not on the card, as `mobileHidden`. The card shows at most
|
|
76
|
+
* three fields: the columns marked `"secondary"` when any
|
|
77
|
+
* is, else the first three visible columns after the title. A field whose value is
|
|
78
|
+
* empty — the cell returns `null`, `undefined`, `""` or the formatters' empty "—" —
|
|
79
|
+
* is left out of that card. The columns the card does not show go into the opened
|
|
80
|
+
* row with `mobileDetailsInRow`. A `noRowLink` column (the cell's own buttons or link)
|
|
81
|
+
* counts as a field: past the third it leaves the card with its action, and a
|
|
82
|
+
* development build says so once per table and column key. Give a column of row
|
|
83
|
+
* actions `role: "actions"` instead: it is then no field, and the card draws it after
|
|
84
|
+
* them.
|
|
85
|
+
*/
|
|
86
|
+
mobile?: "primary" | "secondary" | "hidden";
|
|
87
|
+
/**
|
|
88
|
+
* What the column is (0.49 KIT-7), for the phone's default card; the desktop table is
|
|
89
|
+
* unchanged.
|
|
90
|
+
*
|
|
91
|
+
* - `"status"`: its value sits as a chip beside the title instead of as a label/value
|
|
92
|
+
* line, and takes none of the card's three fields. A cell that returns text is drawn
|
|
93
|
+
* in a neutral `Chip`; one that returns an element (a `Chip` with its own tone, a
|
|
94
|
+
* `StatusDot`) is drawn as it is.
|
|
95
|
+
* - `"actions"`: the row's buttons (`rowActionsColumn` sets it). Never the card's title
|
|
96
|
+
* and never one of its three fields: the card draws it after them, at its end,
|
|
97
|
+
* whether or not other columns are marked `mobile: "secondary"`; only `mobileHidden`
|
|
98
|
+
* / `mobile: "hidden"` take it off. Its cell keeps its own clicks, as `noRowLink`:
|
|
99
|
+
* no row link wraps it, and a card that draws it is not a link.
|
|
100
|
+
*/
|
|
101
|
+
role?: "status" | "actions";
|
|
64
102
|
/**
|
|
65
103
|
* Set on a column whose cell renders its own `<a>` (or anything else that must
|
|
66
104
|
* keep its own click): the row anchor of `rowHref` then skips it and lands on
|
|
@@ -275,7 +313,10 @@ export interface DataTableProps<T> {
|
|
|
275
313
|
* When true, mirror filter/sort/page state into URL search params so views are
|
|
276
314
|
* shareable and bookmarkable. URL params used: `f.<column>`, `sort`, `p`, `ps`.
|
|
277
315
|
* URL state takes precedence over localStorage on initial load. Requires the
|
|
278
|
-
* consuming app to render inside a react-router Router.
|
|
316
|
+
* consuming app to render inside a react-router Router (0.49: only with `urlSync` — a
|
|
317
|
+
* table without it needs none). With `urlSync` and no Router, a development build
|
|
318
|
+
* throws "DataTable urlSync requires a Router"; a production build keeps the view in
|
|
319
|
+
* local state instead.
|
|
279
320
|
*
|
|
280
321
|
* `{ prefix }` namespaces them, for two URL-synced tables on one page:
|
|
281
322
|
* `urlSync={{ prefix: "inv." }}` uses `inv.f.<column>`, `inv.sort`, `inv.p`, `inv.ps`.
|
|
@@ -1309,14 +1350,14 @@ export function DataTable<T>({
|
|
|
1309
1350
|
// column panel. Resolved from `visibleColumns` for exactly that reason — off a
|
|
1310
1351
|
// raw `columns` lookup a hidden headline would leave the row with no link at all.
|
|
1311
1352
|
//
|
|
1312
|
-
// `noRowLink` columns are skipped in BOTH steps: wrapping a
|
|
1313
|
-
// anchor nests two links and disables the inner one (see the prop's
|
|
1314
|
-
// nothing visible is left to wrap, the row gets NO anchor — an anchor
|
|
1315
|
-
// one cell there is would cancel that cell's own click and hand back
|
|
1316
|
-
// and a row without a link still behaves exactly as it did before #451.
|
|
1353
|
+
// `noRowLink` (and `role: "actions"`) columns are skipped in BOTH steps: wrapping a
|
|
1354
|
+
// cell that owns an anchor nests two links and disables the inner one (see the prop's
|
|
1355
|
+
// note). If nothing visible is left to wrap, the row gets NO anchor — an anchor
|
|
1356
|
+
// over the one cell there is would cancel that cell's own click and hand back
|
|
1357
|
+
// nothing, and a row without a link still behaves exactly as it did before #451.
|
|
1317
1358
|
const linkColumn = rowHref
|
|
1318
|
-
? (visibleColumns.find((c) => c.mobilePrimary && !c
|
|
1319
|
-
visibleColumns.find((c) => !c
|
|
1359
|
+
? (visibleColumns.find((c) => c.mobilePrimary && !keepsOwnClicks(c)) ??
|
|
1360
|
+
visibleColumns.find((c) => !keepsOwnClicks(c)))
|
|
1320
1361
|
: undefined;
|
|
1321
1362
|
|
|
1322
1363
|
// ---- Column resize ---- (data-table-resize.ts: pointer capture, one `setWidths` per
|
|
@@ -1387,15 +1428,36 @@ export function DataTable<T>({
|
|
|
1387
1428
|
// filters come through a bottom sheet (feedback #299) and the sort through the
|
|
1388
1429
|
// opt-in `mobileSort` row (keksdose K10), both on the table's own state.
|
|
1389
1430
|
// Memoised on `visibleColumns`: the cards compare their columns by identity (0.48.2).
|
|
1390
|
-
const mobileColumns = useMemo(
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1431
|
+
const mobileColumns = useMemo(
|
|
1432
|
+
() => visibleColumns.filter((c) => !c.mobileHidden && c.mobile !== "hidden"),
|
|
1433
|
+
[visibleColumns],
|
|
1434
|
+
);
|
|
1435
|
+
// The default card (0.49 KIT-7): the title, the status chips beside it, at most three
|
|
1436
|
+
// fields — the ones marked `mobile: "secondary"`, else the first after the title — and
|
|
1437
|
+
// the `role: "actions"` columns after them.
|
|
1438
|
+
const {
|
|
1439
|
+
primary: mobilePrimaryCol,
|
|
1440
|
+
status: mobileStatusColumns,
|
|
1441
|
+
fields: mobileSecondaryColumns,
|
|
1442
|
+
actions: mobileActionColumns,
|
|
1443
|
+
} = useMemo(() => mobileCardColumns(mobileColumns), [mobileColumns]);
|
|
1444
|
+
// A development build names a `noRowLink` column (a per-row delete) the card drops.
|
|
1445
|
+
useCardColumnWarnings(
|
|
1446
|
+
columns,
|
|
1447
|
+
!mobileCard && !mobileDetailsInRow,
|
|
1448
|
+
storageKey ?? (typeof urlSync === "object" ? urlSync.prefix : undefined),
|
|
1396
1449
|
);
|
|
1397
|
-
// The columns the card leaves out, for the opened row (`mobileDetailsInRow`)
|
|
1398
|
-
|
|
1450
|
+
// The columns the card leaves out, for the opened row (`mobileDetailsInRow`): the
|
|
1451
|
+
// hidden ones and, without a `mobileCard`, the ones past the card's three fields.
|
|
1452
|
+
const onCard = new Set<DataTableColumn<T>>([
|
|
1453
|
+
...(mobilePrimaryCol ? [mobilePrimaryCol] : []),
|
|
1454
|
+
...mobileStatusColumns,
|
|
1455
|
+
...mobileSecondaryColumns,
|
|
1456
|
+
...mobileActionColumns,
|
|
1457
|
+
]);
|
|
1458
|
+
const mobileDetailColumns = mobileDetailsInRow
|
|
1459
|
+
? visibleColumns.filter((c) => (mobileCard ? c.mobileHidden || c.mobile === "hidden" : !onCard.has(c)))
|
|
1460
|
+
: [];
|
|
1399
1461
|
// Large and Extra large (§4 "DataTable phone card"): the card's labels go ABOVE their
|
|
1400
1462
|
// values, nothing in it truncates, and the rows get more room. Most of it is a
|
|
1401
1463
|
// `large:` class; the label/value grid needs a different STRUCTURE (each pair in a
|
|
@@ -1420,7 +1482,7 @@ export function DataTable<T>({
|
|
|
1420
1482
|
// opens the row and leaves the cell's own link working. A caller's `mobileCard`
|
|
1421
1483
|
// renderer is opaque to us; a link inside one is the caller's to declare by
|
|
1422
1484
|
// marking the column it comes from.
|
|
1423
|
-
const mobileCardLinkable = mobileColumns.every((c) => !c
|
|
1485
|
+
const mobileCardLinkable = (mobileCard ? mobileColumns : [...onCard]).every((c) => !keepsOwnClicks(c));
|
|
1424
1486
|
// The card list's reading direction, for the one thing on it CSS cannot flip: which
|
|
1425
1487
|
// physical drag a logical `start`/`end` swipe action means. Read off the DOM through
|
|
1426
1488
|
// a ref callback (it runs at commit, like a layout effect) because `dir` is an
|
|
@@ -1590,7 +1652,9 @@ export function DataTable<T>({
|
|
|
1590
1652
|
mobileCardLinkable={mobileCardLinkable}
|
|
1591
1653
|
mobileCard={mobileCard}
|
|
1592
1654
|
mobilePrimaryCol={mobilePrimaryCol}
|
|
1655
|
+
mobileStatusColumns={mobileStatusColumns}
|
|
1593
1656
|
mobileSecondaryColumns={mobileSecondaryColumns}
|
|
1657
|
+
mobileActionColumns={mobileActionColumns}
|
|
1594
1658
|
mobileExpandAsDialog={mobileExpandAsDialog}
|
|
1595
1659
|
compact={compact}
|
|
1596
1660
|
large={large}
|
|
@@ -604,14 +604,14 @@ function ErrorFallback({
|
|
|
604
604
|
const errorLine = details.message ? `${details.name}: ${details.message}` : details.name;
|
|
605
605
|
|
|
606
606
|
const reloadButton = (
|
|
607
|
-
<Button key="reload" variant={mode === "error" ? "secondary" : "
|
|
607
|
+
<Button key="reload" variant={mode === "error" ? "secondary" : "default"} onClick={reloadPage}>
|
|
608
608
|
{text.reload}
|
|
609
609
|
</Button>
|
|
610
610
|
);
|
|
611
611
|
// Not after a chunk failure: `React.lazy` keeps the rejected import, so Try again
|
|
612
612
|
// could only throw the same error again.
|
|
613
613
|
const retryButton = chunk ? null : (
|
|
614
|
-
<Button key="retry" variant={mode === "error" ? "
|
|
614
|
+
<Button key="retry" variant={mode === "error" ? "default" : "secondary"} onClick={reset}>
|
|
615
615
|
{text.retry}
|
|
616
616
|
</Button>
|
|
617
617
|
);
|