@godxjp/ui 18.11.0 → 18.12.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/dist/components/data-display/data-table.d.ts +4 -1
- package/dist/components/data-display/data-table.js +31 -2
- package/dist/components/data-display/table.d.ts +16 -0
- package/dist/components/data-display/table.js +9 -2
- package/dist/styles/alert-layout.css +1 -1
- package/dist/styles/shell-layout.css +13 -0
- package/dist/styles/table-layout.css +24 -0
- package/dist/tokens/components/feedback.css +4 -0
- package/dist/tokens/components/table.css +6 -0
- package/dist/tokens/foundation.css +11 -0
- package/package.json +5 -4
|
@@ -41,7 +41,10 @@ interface DataTableProps<T> {
|
|
|
41
41
|
onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
|
|
42
42
|
/**
|
|
43
43
|
* Manual (server) flags. Default `false` so the simple `data`+`columns` case
|
|
44
|
-
* sorts / filters
|
|
44
|
+
* sorts / filters in-browser with no extra wiring. Client pagination slices
|
|
45
|
+
* rows ONLY when engaged — a numbered `<DataTable.Pagination>` child is
|
|
46
|
+
* composed, or `pagination`/`onPaginationChange` is supplied; a plain table
|
|
47
|
+
* with neither renders every row (no silent 10-row cap, gh#270). Set the
|
|
45
48
|
* relevant flag `true` and drive the matching state from your query for
|
|
46
49
|
* server-side sort / filter / pagination.
|
|
47
50
|
*/
|
|
@@ -178,6 +178,10 @@ function DataTable({
|
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
const tanstackColumns = React.useMemo(() => toTanstackColumns(columns), [columns]);
|
|
181
|
+
const hasNumberedPager = React.Children.toArray(children).some(
|
|
182
|
+
(c) => React.isValidElement(c) && c.type.displayName === "DataTable.Pagination" && typeof c.props.onChange !== "function"
|
|
183
|
+
);
|
|
184
|
+
const paginationEngaged = manualPagination || controlledPagination !== void 0 || onPaginationChange !== void 0 || hasNumberedPager;
|
|
181
185
|
const table = useReactTable({
|
|
182
186
|
data,
|
|
183
187
|
columns: tanstackColumns,
|
|
@@ -185,7 +189,7 @@ function DataTable({
|
|
|
185
189
|
getCoreRowModel: getCoreRowModel(),
|
|
186
190
|
...manualSorting ? {} : { getSortedRowModel: getSortedRowModel() },
|
|
187
191
|
...manualFiltering ? {} : { getFilteredRowModel: getFilteredRowModel() },
|
|
188
|
-
...manualPagination ? {} : { getPaginationRowModel: getPaginationRowModel() },
|
|
192
|
+
...manualPagination || !paginationEngaged ? {} : { getPaginationRowModel: getPaginationRowModel() },
|
|
189
193
|
manualSorting,
|
|
190
194
|
manualFiltering,
|
|
191
195
|
manualPagination,
|
|
@@ -419,6 +423,26 @@ DataTable.Content = function DataTableContent() {
|
|
|
419
423
|
);
|
|
420
424
|
}
|
|
421
425
|
}, [missingHeaderNames]);
|
|
426
|
+
const scrollRef = React.useRef(null);
|
|
427
|
+
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
428
|
+
React.useEffect(() => {
|
|
429
|
+
const el = scrollRef.current;
|
|
430
|
+
if (!el) return;
|
|
431
|
+
const update = () => {
|
|
432
|
+
const overflowing = el.scrollWidth - el.clientWidth > 1;
|
|
433
|
+
const atEnd = Math.abs(el.scrollLeft) + el.clientWidth >= el.scrollWidth - 1;
|
|
434
|
+
setHasOverflowEnd(overflowing && !atEnd);
|
|
435
|
+
};
|
|
436
|
+
update();
|
|
437
|
+
el.addEventListener("scroll", update, { passive: true });
|
|
438
|
+
const observer = new ResizeObserver(update);
|
|
439
|
+
observer.observe(el);
|
|
440
|
+
if (el.firstElementChild) observer.observe(el.firstElementChild);
|
|
441
|
+
return () => {
|
|
442
|
+
el.removeEventListener("scroll", update);
|
|
443
|
+
observer.disconnect();
|
|
444
|
+
};
|
|
445
|
+
}, []);
|
|
422
446
|
const activeSort = sort ?? sortingStateToSort(table.getState().sorting);
|
|
423
447
|
const isControlledSort = sort !== void 0 || !!onSortChange;
|
|
424
448
|
const onHeaderClick = (col) => {
|
|
@@ -440,7 +464,12 @@ DataTable.Content = function DataTableContent() {
|
|
|
440
464
|
return /* @__PURE__ */ jsx(
|
|
441
465
|
"div",
|
|
442
466
|
{
|
|
443
|
-
|
|
467
|
+
ref: scrollRef,
|
|
468
|
+
className: cn(
|
|
469
|
+
"ui-data-table-scroll",
|
|
470
|
+
hasPinEnd && "ui-data-table-has-pin-end",
|
|
471
|
+
hasOverflowEnd && "ui-data-table-has-overflow-end"
|
|
472
|
+
),
|
|
444
473
|
"aria-busy": loading,
|
|
445
474
|
tabIndex: presetAttr ? void 0 : 0,
|
|
446
475
|
children: /* @__PURE__ */ jsx(
|
|
@@ -9,6 +9,14 @@ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
|
9
9
|
* avoids a redundant NESTED scroll container and a duplicate keyboard focus stop for one table.
|
|
10
10
|
*/
|
|
11
11
|
scrollable?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Draw the full cell grid (gh#274): a 1px outer frame plus vertical rules between columns
|
|
14
|
+
* (horizontal row rules already come from TableRow). Reach for this whenever the table
|
|
15
|
+
* carries rowSpan/colSpan merged cells — without column rules the merge relationships are
|
|
16
|
+
* unreadable. Colour comes from the `--table-border-color` token (default `--border`).
|
|
17
|
+
* Default `false` emits nothing, keeping the plain table byte-identical.
|
|
18
|
+
*/
|
|
19
|
+
bordered?: boolean;
|
|
12
20
|
/**
|
|
13
21
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
14
22
|
* table exactly as it is. `"action-collection"` is the canonical dense approval / action queue
|
|
@@ -42,6 +50,14 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
42
50
|
* avoids a redundant NESTED scroll container and a duplicate keyboard focus stop for one table.
|
|
43
51
|
*/
|
|
44
52
|
scrollable?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Draw the full cell grid (gh#274): a 1px outer frame plus vertical rules between columns
|
|
55
|
+
* (horizontal row rules already come from TableRow). Reach for this whenever the table
|
|
56
|
+
* carries rowSpan/colSpan merged cells — without column rules the merge relationships are
|
|
57
|
+
* unreadable. Colour comes from the `--table-border-color` token (default `--border`).
|
|
58
|
+
* Default `false` emits nothing, keeping the plain table byte-identical.
|
|
59
|
+
*/
|
|
60
|
+
bordered?: boolean;
|
|
45
61
|
/**
|
|
46
62
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
47
63
|
* table exactly as it is. `"action-collection"` is the canonical dense approval / action queue
|
|
@@ -3,7 +3,14 @@ import * as React from "react";
|
|
|
3
3
|
import { tableHeadHeightClass } from "../../lib/control-styles.js";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
const Table = React.forwardRef(
|
|
6
|
-
({
|
|
6
|
+
({
|
|
7
|
+
className,
|
|
8
|
+
scrollable = true,
|
|
9
|
+
bordered = false,
|
|
10
|
+
preset = "default",
|
|
11
|
+
collapseBelow = "sm",
|
|
12
|
+
...props
|
|
13
|
+
}, ref) => (
|
|
7
14
|
// A table wider than its container scrolls horizontally in this wrapper; keep it
|
|
8
15
|
// keyboard-reachable so it can be scrolled without a pointer (WCAG 2.1.1 / axe
|
|
9
16
|
// scrollable-region-focusable). No landmark role — avoids landmark-unique collisions.
|
|
@@ -26,7 +33,7 @@ const Table = React.forwardRef(
|
|
|
26
33
|
{
|
|
27
34
|
ref,
|
|
28
35
|
"data-slot": "table",
|
|
29
|
-
className: cn("w-full caption-bottom text-sm", className),
|
|
36
|
+
className: cn("w-full caption-bottom text-sm", bordered && "ui-table-bordered", className),
|
|
30
37
|
...props
|
|
31
38
|
}
|
|
32
39
|
)
|
|
@@ -77,6 +77,19 @@
|
|
|
77
77
|
background-color: var(--app-shell-main-background);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/* `.app-main` carries tabindex="0" (axe scrollable-region-focusable, dbad118), so keyboard
|
|
81
|
+
* focus can land on the region. The UA default outline is suppressed and the indicator is
|
|
82
|
+
* token-gated (gh#276): `--region-focus-ring-width` defaults to 0 — OFF — because a
|
|
83
|
+
* control-strength frame around the whole content area reads as a glitch (`:focus-visible`
|
|
84
|
+
* promotes on any keypress after a click-focus, so mouse users see it constantly). A service
|
|
85
|
+
* that needs the visible keyboard-scroll affordance (WCAG 2.4.7) opts in via the token;
|
|
86
|
+
* INSET because `.app-main` is the shell's clip boundary (`contain: paint`). */
|
|
87
|
+
.app-main:focus-visible {
|
|
88
|
+
outline: none;
|
|
89
|
+
box-shadow: inset 0 0 0 var(--region-focus-ring-width)
|
|
90
|
+
var(--region-focus-ring-color, hsl(var(--focus-ring-color, var(--ring))));
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
.app-footer {
|
|
81
94
|
grid-area: footer;
|
|
82
95
|
border-top: 1px solid hsl(var(--border));
|
|
@@ -169,6 +169,11 @@
|
|
|
169
169
|
padding-inline: var(--space-page-active-x);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/* Scroll-hint fade — a SCROLL affordance, so it only shows while the region
|
|
173
|
+
* actually overflows and is not scrolled to the inline-end. The component
|
|
174
|
+
* measures the scroll box and stamps `.ui-data-table-has-overflow-end`
|
|
175
|
+
* (gh#267 — the fade used to render unconditionally, washing out the last
|
|
176
|
+
* column of tables that never scroll). */
|
|
172
177
|
.ui-data-table-scroll::after {
|
|
173
178
|
content: "";
|
|
174
179
|
position: absolute;
|
|
@@ -178,9 +183,15 @@
|
|
|
178
183
|
z-index: 20;
|
|
179
184
|
width: 1.75rem;
|
|
180
185
|
pointer-events: none;
|
|
186
|
+
opacity: 0;
|
|
187
|
+
transition: opacity 150ms ease;
|
|
181
188
|
background: linear-gradient(to left, hsl(var(--background) / 0.88), transparent);
|
|
182
189
|
}
|
|
183
190
|
|
|
191
|
+
.ui-data-table-has-overflow-end::after {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
[data-slot="card-content"][data-flush] .ui-data-table-scroll {
|
|
185
196
|
margin-inline: 0;
|
|
186
197
|
padding-inline: 0;
|
|
@@ -270,6 +281,19 @@
|
|
|
270
281
|
var(--table-row-selected-background, hsl(var(--muted) / 0.3))
|
|
271
282
|
);
|
|
272
283
|
}
|
|
284
|
+
|
|
285
|
+
/* `<Table bordered>` — the full cell grid (gh#274). Horizontal row rules already come from
|
|
286
|
+
* TableRow's own border; here we add the outer frame and the vertical rules between columns,
|
|
287
|
+
* so rowSpan/colSpan merged cells read as merged (a merged group cell floating in whitespace
|
|
288
|
+
* is unreadable). Colour resolves the --table-border-color token at the CALL SITE so a scoped
|
|
289
|
+
* theme override of --border reaches it. */
|
|
290
|
+
.ui-table-bordered {
|
|
291
|
+
border: 1px solid var(--table-border-color, hsl(var(--border)));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.ui-table-bordered :is([data-slot="table-head"], [data-slot="table-cell"]):not(:last-child) {
|
|
295
|
+
border-inline-end: 1px solid var(--table-border-color, hsl(var(--border)));
|
|
296
|
+
}
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
@layer components {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
--dialog-space-inset: var(--dialog-space-y) var(--dialog-space-x);
|
|
22
22
|
--dialog-space-gap: var(--space-stack-md);
|
|
23
23
|
--dialog-close-space-offset: var(--space-4);
|
|
24
|
+
/* Alert corner radius (gh#268 — rule #45): a full-width Alert often sits in the
|
|
25
|
+
* same page column as a Card (--card-radius); a service aligns them by
|
|
26
|
+
* overriding this once. Default keeps the historical --radius-md. */
|
|
27
|
+
--alert-radius: var(--radius-md);
|
|
24
28
|
--alert-space-inset: var(--space-section-active);
|
|
25
29
|
--alert-space-gap: var(--space-inline-md);
|
|
26
30
|
--alert-inner-space-gap: var(--space-stack-sm);
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
* --table-cell-space-x are density-scaled and re-declared inside a `.ui-density-*` subtree, and a
|
|
19
19
|
* :root binding would freeze the footer at the :root density (docs/TOKENS.md — the :root freeze
|
|
20
20
|
* rule). Defaults = var(--space-stack-sm) block · var(--table-cell-space-x) inline. */
|
|
21
|
+
/* Cell-grid rule colour for `<Table bordered>` (gh#274) — the outer frame and the vertical
|
|
22
|
+
* rules between columns. Declared `initial` so the default re-resolves to the LIVE --border
|
|
23
|
+
* role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and
|
|
24
|
+
* a scoped [data-tenant] override of --border would never reach it — docs/TOKENS.md).
|
|
25
|
+
* Default = hsl(var(--border)). */
|
|
26
|
+
--table-border-color: initial;
|
|
21
27
|
--table-pagination-padding-y: initial;
|
|
22
28
|
--table-pagination-padding-x: initial;
|
|
23
29
|
/* DataTable surface — narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a
|
|
@@ -153,6 +153,17 @@
|
|
|
153
153
|
* default re-resolves at the element that paints the ring while an explicit override still wins. */
|
|
154
154
|
--focus-ring-color: initial; /* default = the live --ring role */
|
|
155
155
|
--focus-ring-width: 2px;
|
|
156
|
+
/* REGION focus ring — the keyboard-scroll affordance on large scroll REGIONS (`.app-main`,
|
|
157
|
+
* gh#271/gh#276), separate from the control ring above. A control-strength 2px brand frame
|
|
158
|
+
* around the entire content area reads as a glitch to mouse-first users (`:focus-visible`
|
|
159
|
+
* promotes on any keypress after a click-focus), so the DEFAULT IS OFF — a deliberate,
|
|
160
|
+
* product-owner tradeoff against WCAG 2.4.7 for keyboard-scroll visibility. A service that
|
|
161
|
+
* needs the visible affordance opts in with ONE token:
|
|
162
|
+
* --region-focus-ring-width: var(--focus-ring-width);
|
|
163
|
+
* `--region-focus-ring-color` is declared `initial` (NOT a :root var binding — the freeze
|
|
164
|
+
* rule) and resolves to the live focus-ring hue at the element that paints it. */
|
|
165
|
+
--region-focus-ring-width: 0;
|
|
166
|
+
--region-focus-ring-color: initial; /* default = hsl(var(--focus-ring-color, var(--ring))) */
|
|
156
167
|
|
|
157
168
|
/* GRADIENTS — opt-in decorative fills exposed at the token layer so a service can paint a hero
|
|
158
169
|
* banner, a brand wash, or a radial brand glow purely by configuring tokens (cardinal rule #45).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "18.
|
|
4
|
-
"godxUiMcp": "18.
|
|
3
|
+
"version": "18.12.0",
|
|
4
|
+
"godxUiMcp": "18.12.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -331,14 +331,15 @@
|
|
|
331
331
|
"check:final-touch-rtl": "node scripts/check-final-touch-rtl.mjs",
|
|
332
332
|
"check:frame-runtime": "pnpm check:data-entry-frame-runtime && pnpm check:data-entry-touch-aria && pnpm check:layout-nav-frames && pnpm check:provider-feedback-query-runtime && pnpm check:layout-nav-closure && pnpm check:final-touch-rtl && pnpm check:data-table-pagination-wrap && pnpm check:button-icon-xs",
|
|
333
333
|
"pretest": "pnpm check:frame-coverage",
|
|
334
|
-
"verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
|
|
335
|
-
"verify:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm check:visual-audit && pnpm test",
|
|
334
|
+
"verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
|
|
335
|
+
"verify:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm check:visual-audit && pnpm test",
|
|
336
336
|
"verify:release": "pnpm verify:static && pnpm check:frame-contracts && pnpm check:frame-coverage && pnpm check:frame-axe",
|
|
337
337
|
"check:mcp-lockstep": "node scripts/check-release-lockstep.mjs",
|
|
338
338
|
"check:release-plan": "node scripts/check-release-command-plan.mjs",
|
|
339
339
|
"check:packed-public-contract": "node scripts/check-packed-public-contract.mjs",
|
|
340
340
|
"check:mcp-pattern-imports": "node scripts/check-mcp-pattern-imports.mjs",
|
|
341
341
|
"check:mcp-sync": "node scripts/check-mcp-sync.mjs",
|
|
342
|
+
"check:mcp-catalog-coverage": "node scripts/check-mcp-catalog-coverage.mjs",
|
|
342
343
|
"check:mcp-orphans": "node scripts/check-mcp-orphans.mjs",
|
|
343
344
|
"check:mcp-prop-sync": "node scripts/check-mcp-prop-sync.mjs",
|
|
344
345
|
"gen:component-api-manifest": "node scripts/gen-component-api-manifest.mjs",
|