@eifi1/ui-kit 0.9.0 → 0.11.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 +37 -23
- package/dist/components/alert-banner.d.ts +57 -5
- package/dist/components/alert-banner.js +63 -17
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +6 -0
- package/dist/components/breadcrumbs.d.ts +60 -0
- package/dist/components/breadcrumbs.js +88 -0
- package/dist/components/breadcrumbs.js.map +1 -0
- package/dist/components/bulk-action-bar.d.ts +112 -0
- package/dist/components/bulk-action-bar.js +196 -0
- package/dist/components/bulk-action-bar.js.map +1 -0
- package/dist/components/button-group.d.ts +58 -3
- package/dist/components/button-group.js +56 -5
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +6 -0
- package/dist/components/calendar-heatmap.d.ts +124 -0
- package/dist/components/calendar-heatmap.js +295 -0
- package/dist/components/calendar-heatmap.js.map +1 -0
- package/dist/components/chip.d.ts +31 -5
- package/dist/components/chip.js +85 -5
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +34 -2
- package/dist/components/choice-card.js +49 -0
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/copy-button.d.ts +14 -4
- package/dist/components/copy-button.js +17 -4
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table.js +2 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +22 -2
- package/dist/components/date-picker.js +11 -3
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +43 -6
- package/dist/components/description-list.js +91 -11
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +7 -0
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/disclosure.d.ts +57 -2
- package/dist/components/disclosure.js +24 -12
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/field.d.ts +43 -0
- package/dist/components/field.js +52 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/file-button.d.ts +1 -0
- package/dist/components/file-dropzone.d.ts +4 -4
- package/dist/components/file-dropzone.js +2 -1
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.d.ts +147 -4
- package/dist/components/floating-panel.js +188 -28
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +23 -4
- package/dist/components/full-bleed-dialog.js +11 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/list.d.ts +187 -0
- package/dist/components/list.js +221 -0
- package/dist/components/list.js.map +1 -0
- package/dist/components/menu-item.d.ts +109 -0
- package/dist/components/menu-item.js +88 -0
- package/dist/components/menu-item.js.map +1 -0
- package/dist/components/mini-calendar.d.ts +70 -5
- package/dist/components/mini-calendar.js +166 -62
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +23 -1
- package/dist/components/modal.js +36 -10
- package/dist/components/modal.js.map +1 -1
- package/dist/components/nav-pills.d.ts +66 -0
- package/dist/components/nav-pills.js +65 -0
- package/dist/components/nav-pills.js.map +1 -0
- package/dist/components/number-field.d.ts +6 -0
- package/dist/components/number-input.d.ts +6 -0
- package/dist/components/numpad-sheet.d.ts +6 -0
- package/dist/components/page-contents.js +1 -2
- package/dist/components/page-contents.js.map +1 -1
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +36 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/progress-bar.d.ts +31 -2
- package/dist/components/progress-bar.js +73 -7
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +3 -0
- package/dist/components/series-chart-labels.js +2 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +32 -0
- package/dist/components/series-chart.js +141 -4
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.d.ts +1 -0
- package/dist/components/status-dot.d.ts +53 -0
- package/dist/components/status-dot.js +57 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/table.d.ts +78 -13
- package/dist/components/table.js +84 -11
- package/dist/components/table.js.map +1 -1
- package/dist/components/text.d.ts +70 -0
- package/dist/components/text.js +24 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/time-input.d.ts +1 -0
- package/dist/components/toast.d.ts +167 -0
- package/dist/components/toast.js +226 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toggle-group.d.ts +44 -1
- package/dist/components/toggle-group.js +73 -4
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/tooltip.d.ts +46 -15
- package/dist/components/tooltip.js +87 -47
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +158 -7
- package/dist/components/ui.js +191 -10
- package/dist/components/ui.js.map +1 -1
- package/dist/components/user-avatar.d.ts +25 -3
- package/dist/components/user-avatar.js +32 -3
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/hooks/use-close-transition.d.ts +7 -1
- package/dist/hooks/use-close-transition.js +5 -2
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.js +1 -1
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +1 -0
- package/dist/i18n/defaults.d.ts +6 -0
- package/dist/i18n/defaults.js +11 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +11 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +6 -0
- package/dist/i18n/locales/de-CH.d.ts +6 -0
- package/dist/i18n/locales/de-informal.d.ts +6 -0
- package/dist/i18n/locales/de.d.ts +6 -0
- package/dist/i18n/locales/de.js +30 -2
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +6 -0
- package/dist/i18n/locales/es.js +30 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +6 -0
- package/dist/i18n/locales/fr.js +30 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +6 -0
- package/dist/i18n/locales/hu.js +30 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +6 -0
- package/dist/i18n/locales/it.js +30 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +6 -0
- package/dist/i18n/locales/zh.js +30 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +27 -15
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/clipping.d.ts +25 -0
- package/dist/lib/clipping.js +17 -0
- package/dist/lib/clipping.js.map +1 -0
- package/dist/lib/dates.d.ts +17 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/rhf/form.d.ts +1 -0
- package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
- package/dist/rhf/use-rhf-wizard-step.js +38 -0
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
- package/dist/rhf.d.ts +2 -0
- package/dist/rhf.js +1 -0
- package/dist/rhf.js.map +1 -1
- package/dist/search/command-palette.d.ts +19 -2
- package/dist/search/command-palette.js +16 -5
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.d.ts +26 -1
- package/dist/search/global-search.js +29 -13
- package/dist/search/global-search.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/shell/topbar-action-menu.d.ts +73 -3
- package/dist/shell/topbar-action-menu.js +100 -27
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +2 -1
- package/dist/wizard/stepper-nav.d.ts +1 -0
- package/dist/wizard/stepper-nav.js +1 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +2 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +1 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-context.d.ts +7 -1
- package/dist/wizard/wizard-context.js +4 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-summary.js +3 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +2 -1
- package/package.json +16 -12
- package/src/components/alert-banner.tsx +149 -21
- package/src/components/breadcrumbs.tsx +168 -0
- package/src/components/bulk-action-bar.tsx +362 -0
- package/src/components/button-group.tsx +124 -4
- package/src/components/calendar-heatmap.tsx +504 -0
- package/src/components/chip.tsx +128 -7
- package/src/components/choice-card.tsx +97 -1
- package/src/components/copy-button.tsx +29 -7
- package/src/components/data-table.tsx +7 -0
- package/src/components/date-picker.tsx +49 -1
- package/src/components/description-list.tsx +168 -15
- package/src/components/dialog-frame.tsx +7 -0
- package/src/components/disclosure.tsx +93 -21
- package/src/components/field.tsx +137 -0
- package/src/components/file-dropzone.tsx +9 -10
- package/src/components/floating-panel.tsx +366 -15
- package/src/components/full-bleed-dialog.tsx +42 -5
- package/src/components/list.tsx +443 -0
- package/src/components/menu-item.tsx +235 -0
- package/src/components/mini-calendar.tsx +245 -54
- package/src/components/modal.tsx +80 -17
- package/src/components/nav-pills.tsx +145 -0
- package/src/components/page-contents.tsx +4 -4
- package/src/components/page-header.tsx +68 -0
- package/src/components/progress-bar.tsx +129 -11
- package/src/components/series-chart-labels.ts +4 -0
- package/src/components/series-chart.tsx +274 -3
- package/src/components/status-dot.tsx +107 -0
- package/src/components/table.tsx +176 -17
- package/src/components/text.tsx +97 -0
- package/src/components/toast.tsx +441 -0
- package/src/components/toggle-group.tsx +128 -4
- package/src/components/tooltip.tsx +200 -100
- package/src/components/ui.tsx +381 -14
- package/src/components/user-avatar.tsx +57 -3
- package/src/hooks/use-close-transition.ts +14 -5
- package/src/hooks/use-copy-to-clipboard.ts +1 -1
- package/src/i18n/defaults.ts +10 -0
- package/src/i18n/kit-labels.tsx +10 -0
- package/src/i18n/locales/de.ts +29 -0
- package/src/i18n/locales/es.ts +29 -0
- package/src/i18n/locales/fr.ts +29 -0
- package/src/i18n/locales/hu.ts +29 -0
- package/src/i18n/locales/it.ts +29 -0
- package/src/i18n/locales/zh.ts +29 -0
- package/src/index.ts +31 -0
- package/src/lib/clipping.ts +34 -0
- package/src/lib/dates.ts +37 -0
- package/src/rhf/use-rhf-wizard-step.ts +113 -0
- package/src/rhf.ts +2 -0
- package/src/search/command-palette.tsx +42 -5
- package/src/search/global-search.tsx +59 -18
- package/src/shell/topbar-action-menu.tsx +239 -49
- package/src/wizard/stepper-nav.tsx +2 -1
- package/src/wizard/types.ts +2 -2
- package/src/wizard/use-wizard.ts +3 -3
- package/src/wizard/wizard-context.tsx +9 -0
- package/src/wizard/wizard-summary.tsx +14 -10
- package/tokens.css +97 -0
package/src/components/table.tsx
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
import { createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import type { ComponentPropsWithoutRef } from "react";
|
|
1
|
+
import { Children, createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { THIN_SCROLLBAR_CLASS, useScrollOverflow } from "./scroll-area";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/** `none` (0.10.0): no cell padding at all — keksdose's VAT summary, a table set
|
|
7
|
+
* inside a card's own padding whose columns are spaced by hand (`pl-2` on exactly the
|
|
8
|
+
* columns that need it). Type stays at the body size, as `comfortable`. */
|
|
9
|
+
export type TableDensity = "comfortable" | "compact" | "none";
|
|
10
|
+
/** CSS `table-layout`. `auto` (the browser default) sizes columns to their content;
|
|
11
|
+
* `fixed` takes widths from the first row and ignores the rest — keksdose's VAT
|
|
12
|
+
* summary switches to it while editing so two inputs stop bidding against the
|
|
13
|
+
* figures for room. */
|
|
14
|
+
export type TableLayout = "auto" | "fixed";
|
|
7
15
|
export type TableAlign = "start" | "center" | "end";
|
|
16
|
+
/**
|
|
17
|
+
* Vertical alignment of a cell's content (0.11.0). The defaults stay as they were — a
|
|
18
|
+
* head cell sits on the `bottom` (a wrapped heading ends on the line above the
|
|
19
|
+
* figures), a body cell at the `top` (a wrapped note starts level with its row). A
|
|
20
|
+
* table whose cells are INPUTS wants `middle`: keksdose's VAT summary
|
|
21
|
+
* (invoices/vat-summary.tsx) sets fields beside derived read-out figures in one row,
|
|
22
|
+
* and a figure at the top of its cell sat above the middle of the field next to it —
|
|
23
|
+
* so it writes `<tr className="align-middle">` over raw `<th>`/`<td>`, because the
|
|
24
|
+
* kit's cells each stated `align-top` and a class on the row could not reach them.
|
|
25
|
+
*/
|
|
26
|
+
export type TableVAlign = "top" | "middle" | "bottom";
|
|
27
|
+
/** The head cell's type size: `xs` (the head's default) or `sm` (the body's). */
|
|
28
|
+
export type TableHeaderCellSize = "xs" | "sm";
|
|
29
|
+
/** The header cell's weight. Default `medium`. */
|
|
30
|
+
export type TableHeaderCellWeight = "normal" | "medium" | "semibold";
|
|
8
31
|
|
|
9
32
|
interface TableContextValue {
|
|
10
33
|
density: TableDensity;
|
|
@@ -25,9 +48,32 @@ const TableContext = createContext<TableContextValue>({
|
|
|
25
48
|
/** Which section a row sits in — zebra and hover apply to body rows only. */
|
|
26
49
|
const SectionContext = createContext<"head" | "body" | "foot">("body");
|
|
27
50
|
|
|
51
|
+
/** A {@link TableRow}'s `valign`, which its cells take unless they set their own. It
|
|
52
|
+
* cannot be left to CSS inheritance: every cell states its own default alignment,
|
|
53
|
+
* and a class on the cell beats one on the row. */
|
|
54
|
+
const RowVAlignContext = createContext<TableVAlign | undefined>(undefined);
|
|
55
|
+
|
|
56
|
+
const VALIGN: Record<TableVAlign, string> = {
|
|
57
|
+
top: "align-top",
|
|
58
|
+
middle: "align-middle",
|
|
59
|
+
bottom: "align-bottom",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const HEADER_SIZE: Record<TableHeaderCellSize, string> = {
|
|
63
|
+
xs: "text-xs",
|
|
64
|
+
sm: "text-sm",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const HEADER_WEIGHT: Record<TableHeaderCellWeight, string> = {
|
|
68
|
+
normal: "font-normal",
|
|
69
|
+
medium: "font-medium",
|
|
70
|
+
semibold: "font-semibold",
|
|
71
|
+
};
|
|
72
|
+
|
|
28
73
|
const CELL_PAD: Record<TableDensity, string> = {
|
|
29
74
|
comfortable: "px-3 py-2",
|
|
30
75
|
compact: "px-2 py-1",
|
|
76
|
+
none: "p-0",
|
|
31
77
|
};
|
|
32
78
|
|
|
33
79
|
const ALIGN: Record<TableAlign, string> = {
|
|
@@ -54,6 +100,8 @@ export interface TableProps extends ComponentPropsWithoutRef<"table"> {
|
|
|
54
100
|
hover?: boolean;
|
|
55
101
|
/** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */
|
|
56
102
|
wrapperClassName?: string;
|
|
103
|
+
/** See {@link TableLayout}. Unset leaves the browser's `auto` and adds no class. */
|
|
104
|
+
layout?: TableLayout;
|
|
57
105
|
}
|
|
58
106
|
|
|
59
107
|
/**
|
|
@@ -78,6 +126,7 @@ export function Table({
|
|
|
78
126
|
zebra = false,
|
|
79
127
|
hover = false,
|
|
80
128
|
wrapperClassName,
|
|
129
|
+
layout,
|
|
81
130
|
className,
|
|
82
131
|
"aria-label": ariaLabel,
|
|
83
132
|
...rest
|
|
@@ -113,6 +162,7 @@ export function Table({
|
|
|
113
162
|
{...(overflowing ? regionName : null)}
|
|
114
163
|
tabIndex={overflowing ? 0 : undefined}
|
|
115
164
|
data-overflowing={overflowing || undefined}
|
|
165
|
+
data-clips=""
|
|
116
166
|
className={cn(
|
|
117
167
|
// `relative`: the containing block for an `sr-only` caption.
|
|
118
168
|
"relative w-full overflow-x-auto",
|
|
@@ -127,6 +177,8 @@ export function Table({
|
|
|
127
177
|
className={cn(
|
|
128
178
|
"w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]",
|
|
129
179
|
density === "compact" && "text-xs",
|
|
180
|
+
layout === "fixed" && "table-fixed",
|
|
181
|
+
layout === "auto" && "table-auto",
|
|
130
182
|
className,
|
|
131
183
|
)}
|
|
132
184
|
/>
|
|
@@ -145,16 +197,87 @@ export function TableHead({ className, ...rest }: TableHeadProps) {
|
|
|
145
197
|
);
|
|
146
198
|
}
|
|
147
199
|
|
|
148
|
-
export
|
|
200
|
+
export interface TableBodyProps extends ComponentPropsWithoutRef<"tbody"> {
|
|
201
|
+
/**
|
|
202
|
+
* Shown as ONE full-width row (a {@link TableEmpty}) when the body has no rows —
|
|
203
|
+
* `children` that render nothing: an empty `.map`, a `false`, `null`. kastlan hand-
|
|
204
|
+
* writes that row with a counted `colSpan` in unit-values-editor, the meeting agenda
|
|
205
|
+
* and invitations tabs, the journal-entry and invoice detail pages, and a column
|
|
206
|
+
* added later leaves every one of those counts one short.
|
|
207
|
+
*/
|
|
208
|
+
empty?: ReactNode;
|
|
209
|
+
}
|
|
149
210
|
|
|
150
|
-
export function TableBody({ className, ...rest }: TableBodyProps) {
|
|
211
|
+
export function TableBody({ className, empty, children, ...rest }: TableBodyProps) {
|
|
212
|
+
// `toArray` drops null/undefined/booleans and flattens arrays, so `[[], false]` —
|
|
213
|
+
// an empty map beside a conditional row that is off — is no rows.
|
|
214
|
+
const hasRows = Children.toArray(children).length > 0;
|
|
151
215
|
return (
|
|
152
216
|
<SectionContext.Provider value="body">
|
|
153
|
-
<tbody {...rest} className={cn("[&>tr:last-child]:border-b-0", className)}
|
|
217
|
+
<tbody {...rest} className={cn("[&>tr:last-child]:border-b-0", className)}>
|
|
218
|
+
{hasRows || empty === undefined || empty === null || empty === false ? (
|
|
219
|
+
children
|
|
220
|
+
) : (
|
|
221
|
+
<TableEmpty>{empty}</TableEmpty>
|
|
222
|
+
)}
|
|
223
|
+
</tbody>
|
|
154
224
|
</SectionContext.Provider>
|
|
155
225
|
);
|
|
156
226
|
}
|
|
157
227
|
|
|
228
|
+
export interface TableEmptyProps extends Omit<ComponentPropsWithoutRef<"tr">, "children"> {
|
|
229
|
+
children: ReactNode;
|
|
230
|
+
/** Columns to span. Default: MEASURED — the widest row of the table it renders in,
|
|
231
|
+
* counted after mount, so the row stays full width when a column is added. */
|
|
232
|
+
colSpan?: number;
|
|
233
|
+
/** Classes for the `<td>`. */
|
|
234
|
+
cellClassName?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The "nothing here" row: one cell across every column, centred, muted. What
|
|
239
|
+
* {@link TableBodyProps.empty} renders, and usable on its own for a body whose rows
|
|
240
|
+
* are not its direct children.
|
|
241
|
+
*/
|
|
242
|
+
export function TableEmpty({ children, colSpan, cellClassName, className, ...rest }: TableEmptyProps) {
|
|
243
|
+
const { density } = useContext(TableContext);
|
|
244
|
+
const cell = useRef<HTMLTableCellElement | null>(null);
|
|
245
|
+
const [measured, setMeasured] = useState(1);
|
|
246
|
+
// Every render, deliberately: a column can appear in the head without anything this
|
|
247
|
+
// row depends on changing. It cannot loop — setting the same count is a bail-out.
|
|
248
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
249
|
+
useLayoutEffect(() => {
|
|
250
|
+
if (colSpan !== undefined) return;
|
|
251
|
+
const row = cell.current?.parentElement;
|
|
252
|
+
const table = cell.current?.closest("table");
|
|
253
|
+
if (!row || !table) return;
|
|
254
|
+
let widest = 1;
|
|
255
|
+
for (const r of Array.from(table.rows)) {
|
|
256
|
+
if (r === row) continue;
|
|
257
|
+
let n = 0;
|
|
258
|
+
for (const c of Array.from(r.cells)) n += c.colSpan || 1;
|
|
259
|
+
widest = Math.max(widest, n);
|
|
260
|
+
}
|
|
261
|
+
setMeasured(widest);
|
|
262
|
+
});
|
|
263
|
+
return (
|
|
264
|
+
<tr {...rest} data-table-empty="" className={className}>
|
|
265
|
+
<td
|
|
266
|
+
ref={cell}
|
|
267
|
+
colSpan={colSpan ?? measured}
|
|
268
|
+
className={cn(
|
|
269
|
+
// `none` keeps the message off the edges regardless: it is text, not a figure.
|
|
270
|
+
density === "none" ? "py-2" : CELL_PAD[density],
|
|
271
|
+
"text-center text-[var(--text-muted)]",
|
|
272
|
+
cellClassName,
|
|
273
|
+
)}
|
|
274
|
+
>
|
|
275
|
+
{children}
|
|
276
|
+
</td>
|
|
277
|
+
</tr>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
158
281
|
export type TableFootProps = ComponentPropsWithoutRef<"tfoot">;
|
|
159
282
|
|
|
160
283
|
/** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`
|
|
@@ -167,23 +290,31 @@ export function TableFoot({ className, ...rest }: TableFootProps) {
|
|
|
167
290
|
);
|
|
168
291
|
}
|
|
169
292
|
|
|
170
|
-
|
|
293
|
+
/** The deprecated HTML `valign` attribute is replaced by a class-backed one. */
|
|
294
|
+
export interface TableRowProps extends ComponentPropsWithoutRef<"tr"> {
|
|
295
|
+
/** Vertical alignment for every cell of the row that does not set its own. See
|
|
296
|
+
* {@link TableVAlign}. Left out, each cell keeps its default. */
|
|
297
|
+
valign?: TableVAlign;
|
|
298
|
+
}
|
|
171
299
|
|
|
172
|
-
export function TableRow({ className, ...rest }: TableRowProps) {
|
|
300
|
+
export function TableRow({ valign, className, ...rest }: TableRowProps) {
|
|
173
301
|
const { zebra, hover } = useContext(TableContext);
|
|
174
302
|
const section = useContext(SectionContext);
|
|
175
303
|
const body = section === "body";
|
|
176
|
-
|
|
304
|
+
const row = (
|
|
177
305
|
<tr
|
|
178
306
|
{...rest}
|
|
179
307
|
className={cn(
|
|
180
308
|
body && "border-b border-[var(--border)]",
|
|
181
309
|
body && zebra && "even:bg-[var(--bg-surface-2)]",
|
|
182
310
|
body && hover && "transition-colors hover:bg-[var(--bg-hover)]",
|
|
311
|
+
// On the row as well, for a raw `<td>` of the caller's, which inherits it.
|
|
312
|
+
valign && VALIGN[valign],
|
|
183
313
|
className,
|
|
184
314
|
)}
|
|
185
315
|
/>
|
|
186
316
|
);
|
|
317
|
+
return valign ? <RowVAlignContext.Provider value={valign}>{row}</RowVAlignContext.Provider> : row;
|
|
187
318
|
}
|
|
188
319
|
|
|
189
320
|
interface CellAlignProps {
|
|
@@ -191,19 +322,44 @@ interface CellAlignProps {
|
|
|
191
322
|
numeric?: boolean;
|
|
192
323
|
/** Logical alignment. Default `start` (`end` when `numeric`). */
|
|
193
324
|
align?: TableAlign;
|
|
325
|
+
/** Vertical alignment. Default: the row's `valign`, else `bottom` for a head cell and
|
|
326
|
+
* `top` for a body cell. See {@link TableVAlign}. */
|
|
327
|
+
valign?: TableVAlign;
|
|
194
328
|
}
|
|
195
329
|
|
|
196
|
-
/** The deprecated HTML `align`
|
|
197
|
-
export interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<"th">, "align">, CellAlignProps {
|
|
330
|
+
/** The deprecated HTML `align` and `valign` attributes are replaced by class-backed ones. */
|
|
331
|
+
export interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<"th">, "align" | "valign">, CellAlignProps {
|
|
332
|
+
/**
|
|
333
|
+
* Type size. Left out: `xs` in the head, the table's own size in the body — as
|
|
334
|
+
* before. keksdose's VAT summary heads its columns at the body size and normal weight
|
|
335
|
+
* (vat-summary.tsx:158–168, `font-normal` on each `<th>`), a quiet header over a
|
|
336
|
+
* small table of figures, and could not say so without overriding classes.
|
|
337
|
+
*/
|
|
338
|
+
size?: TableHeaderCellSize;
|
|
339
|
+
/** Default `medium`. `normal` for the quiet header above; see {@link size}. */
|
|
340
|
+
weight?: TableHeaderCellWeight;
|
|
341
|
+
}
|
|
198
342
|
|
|
199
343
|
/**
|
|
200
344
|
* A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header
|
|
201
345
|
* cell in a body row is the row's label ("Net rent" in a key-value table), and a
|
|
202
346
|
* reader needs the scope to read it with each cell beside it.
|
|
203
347
|
*/
|
|
204
|
-
export function TableHeaderCell({
|
|
348
|
+
export function TableHeaderCell({
|
|
349
|
+
numeric = false,
|
|
350
|
+
align,
|
|
351
|
+
valign,
|
|
352
|
+
size,
|
|
353
|
+
weight = "medium",
|
|
354
|
+
scope,
|
|
355
|
+
className,
|
|
356
|
+
...rest
|
|
357
|
+
}: TableHeaderCellProps) {
|
|
205
358
|
const { density } = useContext(TableContext);
|
|
206
359
|
const section = useContext(SectionContext);
|
|
360
|
+
const rowVAlign = useContext(RowVAlignContext);
|
|
361
|
+
const head = section === "head";
|
|
362
|
+
const textSize = size ?? (head ? "xs" : undefined);
|
|
207
363
|
return (
|
|
208
364
|
<th
|
|
209
365
|
{...rest}
|
|
@@ -212,24 +368,27 @@ export function TableHeaderCell({ numeric = false, align, scope, className, ...r
|
|
|
212
368
|
CELL_PAD[density],
|
|
213
369
|
ALIGN[align ?? (numeric ? "end" : "start")],
|
|
214
370
|
numeric && "tabular-nums",
|
|
215
|
-
"
|
|
216
|
-
|
|
371
|
+
VALIGN[valign ?? rowVAlign ?? (head ? "bottom" : "top")],
|
|
372
|
+
HEADER_WEIGHT[weight],
|
|
373
|
+
head ? "text-[var(--text-muted)]" : "text-[var(--text-secondary)]",
|
|
374
|
+
textSize && HEADER_SIZE[textSize],
|
|
217
375
|
className,
|
|
218
376
|
)}
|
|
219
377
|
/>
|
|
220
378
|
);
|
|
221
379
|
}
|
|
222
380
|
|
|
223
|
-
export interface TableCellProps extends Omit<ComponentPropsWithoutRef<"td">, "align">, CellAlignProps {}
|
|
381
|
+
export interface TableCellProps extends Omit<ComponentPropsWithoutRef<"td">, "align" | "valign">, CellAlignProps {}
|
|
224
382
|
|
|
225
|
-
export function TableCell({ numeric = false, align, className, ...rest }: TableCellProps) {
|
|
383
|
+
export function TableCell({ numeric = false, align, valign, className, ...rest }: TableCellProps) {
|
|
226
384
|
const { density } = useContext(TableContext);
|
|
385
|
+
const rowVAlign = useContext(RowVAlignContext);
|
|
227
386
|
return (
|
|
228
387
|
<td
|
|
229
388
|
{...rest}
|
|
230
389
|
className={cn(
|
|
231
390
|
CELL_PAD[density],
|
|
232
|
-
"
|
|
391
|
+
VALIGN[valign ?? rowVAlign ?? "top"],
|
|
233
392
|
ALIGN[align ?? (numeric ? "end" : "start")],
|
|
234
393
|
numeric && "tabular-nums whitespace-nowrap",
|
|
235
394
|
className,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
/* ── SectionLabel ─────────────────────────────────────────────────────────── */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `xs` is 10px — the size `LegendGroup`'s title and the top-bar menus' heading row draw;
|
|
8
|
+
* `sm` (default) is 12px — the size `StatTile`'s label and lenkbank's section headings
|
|
9
|
+
* (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a
|
|
10
|
+
* dense panel (a legend, a menu) or above a block of page content, and one size looked
|
|
11
|
+
* wrong in the other place.
|
|
12
|
+
*
|
|
13
|
+
* `md` (0.11.0) is 11px, the rung between them: keksdose's budget-summary card labels
|
|
14
|
+
* its figures ("Assigned", "Activity", "Upcoming", "Available" —
|
|
15
|
+
* budget-summary-card:258/264/286/297) and its support thread its day divider and a message's meta line
|
|
16
|
+
* (support-thread:153/223) at `text-[11px]` by hand — 10px read as a footnote under a
|
|
17
|
+
* figure that size, 12px competed with it. Named `md` rather than slotted in order
|
|
18
|
+
* because `sm` was already the default and renaming it would move every caller.
|
|
19
|
+
*/
|
|
20
|
+
export type SectionLabelSize = "xs" | "md" | "sm";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The small, uppercase, tracked, muted label — as a class string, per size.
|
|
24
|
+
*
|
|
25
|
+
* Exported because `LegendGroup` and `StatTile` already draw it inline, and a class a
|
|
26
|
+
* caller has to position itself (a `<th>`, a `<legend>`, a menu `<li>`) is better
|
|
27
|
+
* reached as a string than wrapped in a component that only takes a `className`. The
|
|
28
|
+
* uppercase is CSS, so a screen reader reads the text as written: write it in normal
|
|
29
|
+
* case.
|
|
30
|
+
*/
|
|
31
|
+
export const SECTION_LABEL_CLASS: Record<SectionLabelSize, string> = {
|
|
32
|
+
xs: "text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
|
|
33
|
+
md: "text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
|
|
34
|
+
sm: "text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type SectionLabelElement = "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div" | "legend";
|
|
38
|
+
|
|
39
|
+
export interface SectionLabelProps extends ComponentPropsWithoutRef<"h3"> {
|
|
40
|
+
/**
|
|
41
|
+
* The element — which is the heading LEVEL, and so is the caller's to pick: the kit
|
|
42
|
+
* cannot know where in the outline the label sits. Default `h3`, which is what
|
|
43
|
+
* lenkbank's control page writes under its `h2` card title. Pass `span` or `p` for a
|
|
44
|
+
* label that is not a heading at all (a legend group's title).
|
|
45
|
+
*/
|
|
46
|
+
as?: SectionLabelElement;
|
|
47
|
+
size?: SectionLabelSize;
|
|
48
|
+
children: ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A section's small uppercase label. lenkbank spells
|
|
53
|
+
* `text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400`
|
|
54
|
+
* on every `<h3>` of its control page and more; keksdose's budget switcher writes the
|
|
55
|
+
* 10px version on a menu row. One component, the token colour, and the level chosen
|
|
56
|
+
* where the outline is known.
|
|
57
|
+
*/
|
|
58
|
+
export function SectionLabel({ as = "h3", size = "sm", className, children, ...rest }: SectionLabelProps) {
|
|
59
|
+
const Tag = as as ElementType;
|
|
60
|
+
return (
|
|
61
|
+
<Tag {...rest} className={cn(SECTION_LABEL_CLASS[size], className)}>
|
|
62
|
+
{children}
|
|
63
|
+
</Tag>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ── Caption ──────────────────────────────────────────────────────────────── */
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The caption type as a class string: 11px, snug, muted. lenkbank's `CAPTION`
|
|
71
|
+
* (shared/ui/caption.ts:23) — written out 31 times before it was a constant — as a
|
|
72
|
+
* token colour rather than `slate-500 dark:slate-400`, so it follows the palette.
|
|
73
|
+
* Exported for the same reason lenkbank's is a string: every caller positions it
|
|
74
|
+
* differently (`mt-1`, `self-end pb-2`, `border-t`), and what must be one thing is the
|
|
75
|
+
* type, not the box.
|
|
76
|
+
*/
|
|
77
|
+
export const CAPTION_CLASS = "text-[11px] leading-snug text-[var(--text-muted)]";
|
|
78
|
+
|
|
79
|
+
export interface CaptionProps extends ComponentPropsWithoutRef<"p"> {
|
|
80
|
+
/** Default `p`; `span` for a caption inside a line of other content. */
|
|
81
|
+
as?: "p" | "span" | "div";
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The small grey sentence under a field, a table or a button — prose ABOUT the thing
|
|
87
|
+
* above it. Not `FieldHint`, which hides behind a field label's "?" so a paragraph does
|
|
88
|
+
* not make one field taller than its neighbour (lenkbank feedback #27).
|
|
89
|
+
*/
|
|
90
|
+
export function Caption({ as = "p", className, children, ...rest }: CaptionProps) {
|
|
91
|
+
const Tag = as as ElementType;
|
|
92
|
+
return (
|
|
93
|
+
<Tag {...rest} className={cn(CAPTION_CLASS, className)}>
|
|
94
|
+
{children}
|
|
95
|
+
</Tag>
|
|
96
|
+
);
|
|
97
|
+
}
|