@godxjp/ui 27.0.0 → 27.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/badge.d.ts +10 -3
- package/dist/components/data-display/badge.js +14 -3
- package/dist/components/data-display/data-table.d.ts +20 -12
- package/dist/components/data-display/data-table.js +53 -5
- package/dist/components/data-display/range-timeline.d.ts +16 -0
- package/dist/components/data-display/range-timeline.js +115 -80
- package/dist/components/data-display/table.d.ts +16 -0
- package/dist/components/data-display/table.js +2 -0
- package/dist/components/data-entry/checkbox.js +23 -5
- package/dist/components/data-entry/command.d.ts +3 -1
- package/dist/components/data-entry/command.js +10 -2
- package/dist/components/data-entry/form-errors.d.ts +5 -0
- package/dist/components/data-entry/form-errors.js +6 -0
- package/dist/components/data-entry/form-field.js +9 -8
- package/dist/components/data-entry/index.d.ts +1 -0
- package/dist/components/data-entry/search-select.js +145 -136
- package/dist/components/data-entry/select.js +38 -3
- package/dist/components/feedback/dialog.d.ts +4 -3
- package/dist/components/feedback/dialog.js +46 -19
- package/dist/components/feedback/non-modal-layer.d.ts +16 -0
- package/dist/components/feedback/non-modal-layer.js +37 -0
- package/dist/components/feedback/sheet.d.ts +7 -4
- package/dist/components/feedback/sheet.js +63 -40
- package/dist/components/layout/org-switcher.js +1 -1
- package/dist/components/layout/responsive-grid.d.ts +8 -2
- package/dist/components/layout/responsive-grid.js +2 -0
- package/dist/components/query/mutation-feedback.js +2 -4
- package/dist/contracts/measurement.json +1 -1
- package/dist/form/form-field-control.d.ts +1 -1
- package/dist/form/form-field-control.js +32 -23
- package/dist/form/form-root.d.ts +1 -1
- package/dist/form/form-root.js +25 -6
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/components/data-display.prop.d.ts +12 -0
- package/dist/props/components/data-entry.prop.d.ts +10 -0
- package/dist/props/components/feedback.prop.d.ts +7 -1
- package/dist/props/components/form.prop.d.ts +52 -3
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +7 -0
- package/dist/props/registry.d.ts +26 -3
- package/dist/props/registry.js +25 -2
- package/dist/props/vocabulary/data.prop.d.ts +27 -1
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +44 -1
- package/dist/styles/data-display-layout.css +58 -1
- package/dist/styles/form-layout.css +21 -0
- package/dist/styles/layout.css +7 -0
- package/dist/styles/navigation-layout.css +5 -1
- package/dist/styles/shell-layout.css +1 -14
- package/dist/styles/table-layout.css +80 -8
- package/dist/tokens/components/control.css +5 -0
- package/dist/tokens/components/data-display.css +6 -0
- package/dist/tokens/components/navigation.css +1 -1
- package/dist/tokens/components/table.css +2 -0
- package/docs/DESIGN-AUTHORITY.md +32 -1
- package/docs/FORMS.md +11 -10
- package/docs/data-display/data-table/examples/server-paged.tsx +156 -0
- package/docs/data-display/data-table/examples/striped-list.tsx +173 -0
- package/docs/data-display/popover.tsx +43 -1
- package/docs/data-display/timeline.tsx +73 -9
- package/docs/data-entry/command.tsx +80 -1
- package/docs/data-entry/form-root.tsx +12 -2
- package/docs/feedback/dialog.tsx +82 -0
- package/docs/feedback/sheet.tsx +84 -0
- package/docs/layout/responsive-grid.tsx +100 -0
- package/package.json +4 -2
|
@@ -80,14 +80,21 @@ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
80
80
|
* `closable={false}`: no × is rendered.
|
|
81
81
|
*
|
|
82
82
|
* The × accessible name is built from the visible label (`children`, or the resolved `status`
|
|
83
|
-
* label) via `navigation.filterBar.removeFilter
|
|
84
|
-
*
|
|
83
|
+
* label) via `navigation.filterBar.removeFilter`, e.g. `getByRole('button', { name: /期限: 今週/ })`.
|
|
84
|
+
* When `children` is not a string (a link, a formatted node) the name is read from the label's
|
|
85
|
+
* rendered text instead, so several removable chips never share one bare "Delete" (gh#706).
|
|
85
86
|
*/
|
|
86
87
|
onRemove?: () => void;
|
|
88
|
+
/**
|
|
89
|
+
* The × button's full accessible name, verbatim — antd 5.15+ `closable={{ 'aria-label' }}`.
|
|
90
|
+
* Wins over the name derived from the label. Use it when the chip's text alone does not say what
|
|
91
|
+
* removing it does.
|
|
92
|
+
*/
|
|
93
|
+
removeLabel?: string;
|
|
87
94
|
/** Disables only the × when `onRemove` is set (FilterBar bar/chip disabled). Label stays visible. */
|
|
88
95
|
removeDisabled?: boolean;
|
|
89
96
|
}
|
|
90
|
-
export declare function Badge({ as: Element, className, variant, shape, tone, color, icon, status, tabular, style, children, onRemove, removeDisabled, ...props }: BadgeProps): React.JSX.Element;
|
|
97
|
+
export declare function Badge({ as: Element, className, variant, shape, tone, color, icon, status, tabular, style, children, onRemove, removeLabel: removeLabelProp, removeDisabled, ...props }: BadgeProps): React.JSX.Element;
|
|
91
98
|
/**
|
|
92
99
|
* Status-aware badge with the shared domain-status-to-tone mapping. `Badge` remains the
|
|
93
100
|
* general-purpose primitive.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import { cva } from "class-variance-authority";
|
|
4
5
|
import {
|
|
5
6
|
AlertCircle,
|
|
@@ -102,6 +103,7 @@ function Badge({
|
|
|
102
103
|
style,
|
|
103
104
|
children,
|
|
104
105
|
onRemove,
|
|
106
|
+
removeLabel: removeLabelProp,
|
|
105
107
|
removeDisabled,
|
|
106
108
|
...props
|
|
107
109
|
}) {
|
|
@@ -111,7 +113,16 @@ function Badge({
|
|
|
111
113
|
const ResolvedIcon = icon === void 0 ? statusDef?.icon : icon;
|
|
112
114
|
const resolvedChildren = children ?? (status ? status in STATUS_MAP ? t(`status.${status}`) : status : void 0);
|
|
113
115
|
const tinted = color != null && color !== "";
|
|
114
|
-
const
|
|
116
|
+
const stringLabel = typeof children === "string" ? children : typeof resolvedChildren === "string" ? resolvedChildren : void 0;
|
|
117
|
+
const labelRef = React.useRef(null);
|
|
118
|
+
const [renderedLabel, setRenderedLabel] = React.useState(void 0);
|
|
119
|
+
const deriveFromDom = Boolean(onRemove) && removeLabelProp == null && stringLabel == null;
|
|
120
|
+
React.useEffect(() => {
|
|
121
|
+
if (!deriveFromDom) return;
|
|
122
|
+
const text = labelRef.current?.textContent?.replace(/\s+/g, " ").trim();
|
|
123
|
+
setRenderedLabel(text ? text : void 0);
|
|
124
|
+
}, [deriveFromDom, resolvedChildren]);
|
|
125
|
+
const removeLabel = stringLabel ?? (deriveFromDom ? renderedLabel : void 0);
|
|
115
126
|
const named = props["aria-label"] != null || props["aria-labelledby"] != null;
|
|
116
127
|
const nameRole = named && props.role === void 0 && !onRemove ? "img" : void 0;
|
|
117
128
|
return /* @__PURE__ */ jsxs(
|
|
@@ -136,14 +147,14 @@ function Badge({
|
|
|
136
147
|
...props,
|
|
137
148
|
children: [
|
|
138
149
|
ResolvedIcon ? /* @__PURE__ */ jsx(ResolvedIcon, { "data-slot": "badge-icon", "aria-hidden": "true" }) : null,
|
|
139
|
-
resolvedChildren != null ? /* @__PURE__ */ jsx("span", { "data-slot": "badge-label", children: resolvedChildren }) : null,
|
|
150
|
+
resolvedChildren != null ? /* @__PURE__ */ jsx("span", { ref: labelRef, "data-slot": "badge-label", children: resolvedChildren }) : null,
|
|
140
151
|
onRemove ? /* @__PURE__ */ jsx(
|
|
141
152
|
"button",
|
|
142
153
|
{
|
|
143
154
|
type: "button",
|
|
144
155
|
"data-slot": "badge-remove",
|
|
145
156
|
className: "ui-control-inline-affix-action ui-badge-remove",
|
|
146
|
-
"aria-label": removeLabel != null ? t("navigation.filterBar.removeFilter", { label: removeLabel }) : t("common.delete"),
|
|
157
|
+
"aria-label": removeLabelProp ?? (removeLabel != null ? t("navigation.filterBar.removeFilter", { label: removeLabel }) : t("common.delete")),
|
|
147
158
|
onClick: (event) => {
|
|
148
159
|
event.stopPropagation();
|
|
149
160
|
onRemove();
|
|
@@ -38,8 +38,11 @@ interface DataTableProps<T> {
|
|
|
38
38
|
* Numbered-pagination state, surfaced by DataTable.Pagination. THREE shapes, all accepted:
|
|
39
39
|
* the TanStack `{ pageIndex, pageSize }` this prop has always taken; antd's
|
|
40
40
|
* `TablePaginationConfig` object (`{ current, pageSize, total, pageSizeOptions,
|
|
41
|
-
* showSizeChanger, onChange }`, 1-based like antd); and `false`, which
|
|
42
|
-
* entirely (antd `pagination={false}`).
|
|
41
|
+
* showSizeChanger, showTotal, position, onChange }`, 1-based like antd); and `false`, which
|
|
42
|
+
* hides the pager entirely (antd `pagination={false}`). The config object with NO composed
|
|
43
|
+
* `<DataTable.Pagination>` renders the table's own antd footer — the real `Pagination` with
|
|
44
|
+
* total + page numbers at `position` (default `bottomEnd`), `size="sm"` on a compact table.
|
|
45
|
+
* Server paging: `pagination={{ total, current, pageSize, onChange }}` with `data` = that page.
|
|
43
46
|
*/
|
|
44
47
|
pagination?: PaginationState | TablePaginationProp | false;
|
|
45
48
|
onPaginationChange?: OnChangeFn<PaginationState>;
|
|
@@ -72,7 +75,11 @@ interface DataTableProps<T> {
|
|
|
72
75
|
denied?: React.ReactNode;
|
|
73
76
|
/** Retry handler for the built-in `error` state; omit to hide the retry action. */
|
|
74
77
|
onRetry?: () => void;
|
|
75
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Zebra-stripe the body rows by LOGICAL record (an expanded detail row takes its record's stripe;
|
|
80
|
+
* frozen columns wear it too). Omit to inherit the theme default `--table-row-striped-alpha`;
|
|
81
|
+
* `true` / `false` override it for this table.
|
|
82
|
+
*/
|
|
76
83
|
striped?: boolean;
|
|
77
84
|
/** Highlight a row on hover even when it is not clickable (no `onRowClick`). */
|
|
78
85
|
hoverable?: boolean;
|
|
@@ -141,15 +148,15 @@ interface DataTableProps<T> {
|
|
|
141
148
|
}
|
|
142
149
|
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
143
150
|
export declare namespace DataTable {
|
|
144
|
-
var Toolbar: ({ children, className, }: {
|
|
151
|
+
export var Toolbar: ({ children, className, }: {
|
|
145
152
|
children?: React.ReactNode;
|
|
146
153
|
className?: string;
|
|
147
154
|
}) => React.JSX.Element;
|
|
148
|
-
var Search: ({ placeholder, className, }: {
|
|
155
|
+
export var Search: ({ placeholder, className, }: {
|
|
149
156
|
placeholder?: string;
|
|
150
157
|
className?: string;
|
|
151
158
|
}) => React.JSX.Element;
|
|
152
|
-
var ViewOptions: ({ className, label, }: {
|
|
159
|
+
export var ViewOptions: ({ className, label, }: {
|
|
153
160
|
className?: string;
|
|
154
161
|
/**
|
|
155
162
|
* Overrides the button's text. The default ("View") is the shortest thing
|
|
@@ -159,12 +166,13 @@ export declare namespace DataTable {
|
|
|
159
166
|
*/
|
|
160
167
|
label?: React.ReactNode;
|
|
161
168
|
}) => React.JSX.Element | null;
|
|
162
|
-
var SelectAll: () => React.JSX.Element | null;
|
|
163
|
-
var BulkActions: ({ count, children, className, }: BulkActionsProps) => React.JSX.Element | null;
|
|
164
|
-
var DensityToggle: () => React.JSX.Element;
|
|
165
|
-
var Content: () => React.JSX.Element;
|
|
166
|
-
var
|
|
167
|
-
|
|
169
|
+
export var SelectAll: () => React.JSX.Element | null;
|
|
170
|
+
export var BulkActions: ({ count, children, className, }: BulkActionsProps) => React.JSX.Element | null;
|
|
171
|
+
export var DensityToggle: () => React.JSX.Element;
|
|
172
|
+
export var Content: () => React.JSX.Element;
|
|
173
|
+
var _a: (props: PaginationProps) => React.JSX.Element;
|
|
174
|
+
export { _a as Pagination };
|
|
175
|
+
export var RowActions: ({ ariaLabel, children }: RowActionsProps) => React.JSX.Element;
|
|
168
176
|
}
|
|
169
177
|
interface BulkActionsProps {
|
|
170
178
|
count?: number;
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
DropdownMenuSeparator,
|
|
57
57
|
DropdownMenuTrigger
|
|
58
58
|
} from "../navigation/dropdown-menu.js";
|
|
59
|
+
import { Pagination } from "../navigation/pagination.js";
|
|
59
60
|
import { RadioGroupRoot, RadioItem } from "../data-entry/radio.js";
|
|
60
61
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
61
62
|
import {
|
|
@@ -224,7 +225,7 @@ function DataTable({
|
|
|
224
225
|
error,
|
|
225
226
|
denied,
|
|
226
227
|
onRetry,
|
|
227
|
-
striped
|
|
228
|
+
striped,
|
|
228
229
|
hoverable = false,
|
|
229
230
|
stickyHeader = true,
|
|
230
231
|
preset = "default",
|
|
@@ -389,6 +390,13 @@ function DataTable({
|
|
|
389
390
|
(c) => React.isValidElement(c) && c.type.displayName === "DataTable.Pagination" && typeof c.props.onChange !== "function"
|
|
390
391
|
);
|
|
391
392
|
const paginationEngaged = !pagerHidden && (manualPagination || controlledPagination !== void 0 || paginationConfig !== void 0 || onPaginationChange !== void 0 || hasNumberedPager);
|
|
393
|
+
const serverPaged = paginationConfig?.total !== void 0 && data.length < paginationConfig.total && data.length <= pageSize;
|
|
394
|
+
const hasComposedPager = React.Children.toArray(children).some(
|
|
395
|
+
(c) => React.isValidElement(c) && c.type.displayName === "DataTable.Pagination"
|
|
396
|
+
);
|
|
397
|
+
const footerPositions = paginationConfig && !hasComposedPager && !pagerHidden ? (paginationConfig.position ?? ["bottomEnd"]).filter((p) => p !== "none") : [];
|
|
398
|
+
const topFooter = footerPositions.find((p) => p.startsWith("top"));
|
|
399
|
+
const bottomFooter = footerPositions.find((p) => p.startsWith("bottom"));
|
|
392
400
|
const features = React.useMemo(() => dataTableFeatures(), []);
|
|
393
401
|
const selectionEnabled = selectable || rowSelection !== void 0;
|
|
394
402
|
const table = useTable({
|
|
@@ -400,7 +408,7 @@ function DataTable({
|
|
|
400
408
|
manualFiltering,
|
|
401
409
|
// "no pager on this table" is expressed the same way as "the server paginates" — leave the
|
|
402
410
|
// rows unsliced. Page count still derives from `rowCount`/the pre-paginated model either way.
|
|
403
|
-
manualPagination: manualPagination || !paginationEngaged,
|
|
411
|
+
manualPagination: manualPagination || serverPaged || !paginationEngaged,
|
|
404
412
|
rowCount: paginationConfig?.total ?? rowCount,
|
|
405
413
|
enableRowSelection: selectionEnabled,
|
|
406
414
|
state: {
|
|
@@ -467,8 +475,10 @@ function DataTable({
|
|
|
467
475
|
(c) => React.isValidElement(c) && c.type.displayName === "DataTable.Content"
|
|
468
476
|
);
|
|
469
477
|
return /* @__PURE__ */ jsx(DataTableContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cn("ui-data-table-root", densityClass[density], className), children: [
|
|
478
|
+
topFooter ? /* @__PURE__ */ jsx(PaginationFooter, { position: topFooter }) : null,
|
|
470
479
|
children,
|
|
471
|
-
!hasContent && /* @__PURE__ */ jsx(DataTable.Content, {})
|
|
480
|
+
!hasContent && /* @__PURE__ */ jsx(DataTable.Content, {}),
|
|
481
|
+
bottomFooter ? /* @__PURE__ */ jsx(PaginationFooter, { position: bottomFooter }) : null
|
|
472
482
|
] }) });
|
|
473
483
|
}
|
|
474
484
|
DataTable.Toolbar = function DataTableToolbar({
|
|
@@ -929,6 +939,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
929
939
|
preset,
|
|
930
940
|
collapseBelow,
|
|
931
941
|
bordered,
|
|
942
|
+
striped,
|
|
932
943
|
children: [
|
|
933
944
|
/* @__PURE__ */ jsx(
|
|
934
945
|
TableHeader,
|
|
@@ -1145,8 +1156,10 @@ DataTable.Content = function DataTableContent() {
|
|
|
1145
1156
|
} : rowProps.onKeyDown,
|
|
1146
1157
|
className: cn(
|
|
1147
1158
|
rowPadding,
|
|
1148
|
-
// Hover highlight when rows are clickable OR explicitly hoverable…
|
|
1149
|
-
|
|
1159
|
+
// Hover highlight when rows are clickable OR explicitly hoverable… The
|
|
1160
|
+
// --accent step TableRow itself uses, not --muted/50: that sat BELOW the
|
|
1161
|
+
// zebra stripe (--muted/0.8, gh#700), so hovering a striped row lightened it.
|
|
1162
|
+
(onRowClick || hoverable) && "hover:bg-accent/70",
|
|
1150
1163
|
// …but the affordance (cursor + focus mark) only when clickable.
|
|
1151
1164
|
//
|
|
1152
1165
|
// `ui-focus-ring` = the single focus source (styles/focus-ring.css). It
|
|
@@ -1403,6 +1416,41 @@ function NumberedPagination({ pageSizeOptions, className }) {
|
|
|
1403
1416
|
}
|
|
1404
1417
|
);
|
|
1405
1418
|
}
|
|
1419
|
+
const footerAlign = {
|
|
1420
|
+
Start: "start",
|
|
1421
|
+
Center: "center",
|
|
1422
|
+
End: "end"
|
|
1423
|
+
};
|
|
1424
|
+
function PaginationFooter({
|
|
1425
|
+
position
|
|
1426
|
+
}) {
|
|
1427
|
+
const { table, paginationConfig, density } = useDataTableContext();
|
|
1428
|
+
if (!paginationConfig) return null;
|
|
1429
|
+
const { pageIndex, pageSize } = table.state.pagination;
|
|
1430
|
+
const edge = position.replace(/^(top|bottom)/, "");
|
|
1431
|
+
return /* @__PURE__ */ jsx(
|
|
1432
|
+
"div",
|
|
1433
|
+
{
|
|
1434
|
+
className: "ui-data-table-pagination ui-data-table-pagination--footer",
|
|
1435
|
+
"data-position": position,
|
|
1436
|
+
children: /* @__PURE__ */ jsx(
|
|
1437
|
+
Pagination,
|
|
1438
|
+
{
|
|
1439
|
+
value: pageIndex + 1,
|
|
1440
|
+
total: table.getRowCount(),
|
|
1441
|
+
pageSize,
|
|
1442
|
+
pageSizeOptions: paginationConfig.pageSizeOptions,
|
|
1443
|
+
showSizeChanger: paginationConfig.showSizeChanger ?? true,
|
|
1444
|
+
showTotal: paginationConfig.showTotal,
|
|
1445
|
+
hideOnSinglePage: paginationConfig.hideOnSinglePage ?? false,
|
|
1446
|
+
size: density === "compact" ? "sm" : "md",
|
|
1447
|
+
align: footerAlign[edge],
|
|
1448
|
+
onValueChange: (page, size) => table.setPagination({ pageIndex: page - 1, pageSize: size })
|
|
1449
|
+
}
|
|
1450
|
+
)
|
|
1451
|
+
}
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1406
1454
|
DataTable.RowActions = function DataTableRowActions({ ariaLabel, children }) {
|
|
1407
1455
|
const { t } = useTranslation();
|
|
1408
1456
|
return /* @__PURE__ */ jsxs(
|
|
@@ -9,9 +9,11 @@ export type RangeTimelineRow = {
|
|
|
9
9
|
};
|
|
10
10
|
export type RangeTimelineProps = React.HTMLAttributes<HTMLElement> & {
|
|
11
11
|
label: string;
|
|
12
|
+
/** `muted` tints that column down the whole body — a non-working day, a closed period. */
|
|
12
13
|
columns: {
|
|
13
14
|
label: string;
|
|
14
15
|
units: number;
|
|
16
|
+
muted?: boolean;
|
|
15
17
|
}[];
|
|
16
18
|
bands?: {
|
|
17
19
|
label: string;
|
|
@@ -20,13 +22,21 @@ export type RangeTimelineProps = React.HTMLAttributes<HTMLElement> & {
|
|
|
20
22
|
rows: RangeTimelineRow[];
|
|
21
23
|
today?: number | null;
|
|
22
24
|
onRangeChange?: (id: string, edge: "start" | "end", delta: number) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Rule the body as a grid: a line between rows, a line under the header, and a vertical line for
|
|
27
|
+
* every column down the whole body. Default `true`, like `Calendar bordered`; `false` restores
|
|
28
|
+
* the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
|
|
29
|
+
*/
|
|
30
|
+
bordered?: boolean;
|
|
23
31
|
};
|
|
24
32
|
/** A horizontal range axis. Units and labels are data; the design system owns all geometry. */
|
|
25
33
|
export declare const RangeTimeline: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLElement> & {
|
|
26
34
|
label: string;
|
|
35
|
+
/** `muted` tints that column down the whole body — a non-working day, a closed period. */
|
|
27
36
|
columns: {
|
|
28
37
|
label: string;
|
|
29
38
|
units: number;
|
|
39
|
+
muted?: boolean;
|
|
30
40
|
}[];
|
|
31
41
|
bands?: {
|
|
32
42
|
label: string;
|
|
@@ -35,4 +45,10 @@ export declare const RangeTimeline: React.ForwardRefExoticComponent<React.HTMLAt
|
|
|
35
45
|
rows: RangeTimelineRow[];
|
|
36
46
|
today?: number | null;
|
|
37
47
|
onRangeChange?: (id: string, edge: "start" | "end", delta: number) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Rule the body as a grid: a line between rows, a line under the header, and a vertical line for
|
|
50
|
+
* every column down the whole body. Default `true`, like `Calendar bordered`; `false` restores
|
|
51
|
+
* the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
|
|
52
|
+
*/
|
|
53
|
+
bordered?: boolean;
|
|
38
54
|
} & React.RefAttributes<HTMLElement>>;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
-
import { GripVertical } from "lucide-react";
|
|
5
|
+
import { ChevronLeft, ChevronRight, GripVertical } from "lucide-react";
|
|
6
6
|
import { Button } from "../general/button.js";
|
|
7
7
|
import { Text } from "../general/typography.js";
|
|
8
8
|
import { cn } from "../../lib/utils.js";
|
|
9
9
|
const RangeTimeline = React.forwardRef(
|
|
10
|
-
function RangeTimeline2({ label, columns, bands, rows, today, onRangeChange, className, ...props }, ref) {
|
|
10
|
+
function RangeTimeline2({ label, columns, bands, rows, today, onRangeChange, bordered = true, className, ...props }, ref) {
|
|
11
11
|
const { t } = useTranslation();
|
|
12
12
|
const units = Math.max(
|
|
13
13
|
1,
|
|
@@ -37,6 +37,7 @@ const RangeTimeline = React.forwardRef(
|
|
|
37
37
|
...props,
|
|
38
38
|
ref,
|
|
39
39
|
className: cn("ui-range-timeline", className),
|
|
40
|
+
"data-bordered": bordered ? "true" : void 0,
|
|
40
41
|
"aria-label": label,
|
|
41
42
|
tabIndex: 0,
|
|
42
43
|
children: /* @__PURE__ */ jsxs(
|
|
@@ -69,86 +70,120 @@ const RangeTimeline = React.forwardRef(
|
|
|
69
70
|
index
|
|
70
71
|
)) })
|
|
71
72
|
] }),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", children: [
|
|
74
|
+
(bordered || columns.some((column) => column.muted)) && // Decorative: the same flex/units layout as the header columns, laid once behind every
|
|
75
|
+
// row so each vertical rule runs the full body height exactly under its header column.
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-grid", "aria-hidden": "true", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "ui-range-timeline-columns", children: columns.map((column, index) => /* @__PURE__ */ jsx(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
className: "ui-range-timeline-grid-column",
|
|
82
|
+
"data-muted": column.muted ? "true" : void 0,
|
|
83
|
+
style: { flex: column.units }
|
|
84
|
+
},
|
|
85
|
+
index
|
|
86
|
+
)) })
|
|
87
|
+
] }),
|
|
88
|
+
rows.map((row) => {
|
|
89
|
+
const delta = preview?.id === row.id ? preview.delta : 0;
|
|
90
|
+
const start = Math.min(row.end, row.start + (preview?.edge === "start" ? delta : 0));
|
|
91
|
+
const end = Math.max(row.start, row.end + (preview?.edge === "end" ? delta : 0));
|
|
92
|
+
const left = Math.max(0, start), right = Math.min(units, end + 1);
|
|
93
|
+
return /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-row", children: [
|
|
94
|
+
/* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: row.label }),
|
|
95
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-track", children: [
|
|
96
|
+
today != null && today >= 0 && today < units && /* @__PURE__ */ jsx(
|
|
97
|
+
"span",
|
|
98
|
+
{
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
className: "ui-range-timeline-today",
|
|
101
|
+
style: { insetInlineStart: `${(today + 0.5) / units * 100}%` }
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
ChevronLeft,
|
|
101
107
|
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
delta: deltaAt(event.clientX)
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
onPointerUp: complete,
|
|
128
|
-
onPointerCancel: cancel,
|
|
129
|
-
onLostPointerCapture: cancel,
|
|
130
|
-
onKeyDown: (event) => {
|
|
131
|
-
if (event.key === "Escape") {
|
|
132
|
-
cancel();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
136
|
-
if (!step) return;
|
|
137
|
-
event.preventDefault();
|
|
138
|
-
const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
|
|
139
|
-
const delta2 = step * direction;
|
|
140
|
-
if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
|
|
141
|
-
onRangeChange(row.id, edge, delta2);
|
|
142
|
-
},
|
|
143
|
-
children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
|
|
108
|
+
"aria-hidden": "true",
|
|
109
|
+
className: "ui-range-timeline-outside-icon"
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideBefore") })
|
|
113
|
+
] }) : /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "after", children: [
|
|
114
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideAfter") }),
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
ChevronRight,
|
|
117
|
+
{
|
|
118
|
+
"aria-hidden": "true",
|
|
119
|
+
className: "ui-range-timeline-outside-icon"
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] })),
|
|
123
|
+
right > left && /* @__PURE__ */ jsx(
|
|
124
|
+
"div",
|
|
125
|
+
{
|
|
126
|
+
className: "ui-range-timeline-bar",
|
|
127
|
+
style: {
|
|
128
|
+
insetInlineStart: `${left / units * 100}%`,
|
|
129
|
+
inlineSize: `${(right - left) / units * 100}%`
|
|
144
130
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
131
|
+
children: onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
|
|
132
|
+
(edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
|
|
133
|
+
).map((edge) => /* @__PURE__ */ jsx(
|
|
134
|
+
Button,
|
|
135
|
+
{
|
|
136
|
+
variant: "ghost",
|
|
137
|
+
size: "icon-xs",
|
|
138
|
+
className: "ui-range-timeline-handle",
|
|
139
|
+
"data-edge": edge,
|
|
140
|
+
"aria-label": edge === "start" ? row.startLabel : row.endLabel,
|
|
141
|
+
onPointerDown: (event) => {
|
|
142
|
+
const width = track.current?.getBoundingClientRect().width ?? 0;
|
|
143
|
+
if (!width || event.button !== 0) return;
|
|
144
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
145
|
+
drag.current = {
|
|
146
|
+
id: row.id,
|
|
147
|
+
edge,
|
|
148
|
+
x: event.clientX,
|
|
149
|
+
width,
|
|
150
|
+
direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
onPointerMove: (event) => {
|
|
154
|
+
if (drag.current)
|
|
155
|
+
setPreview({
|
|
156
|
+
id: drag.current.id,
|
|
157
|
+
edge: drag.current.edge,
|
|
158
|
+
delta: deltaAt(event.clientX)
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
onPointerUp: complete,
|
|
162
|
+
onPointerCancel: cancel,
|
|
163
|
+
onLostPointerCapture: cancel,
|
|
164
|
+
onKeyDown: (event) => {
|
|
165
|
+
if (event.key === "Escape") {
|
|
166
|
+
cancel();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
170
|
+
if (!step) return;
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
|
|
173
|
+
const delta2 = step * direction;
|
|
174
|
+
if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
|
|
175
|
+
onRangeChange(row.id, edge, delta2);
|
|
176
|
+
},
|
|
177
|
+
children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
|
|
178
|
+
},
|
|
179
|
+
edge
|
|
180
|
+
))
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
] })
|
|
184
|
+
] }, row.id);
|
|
185
|
+
})
|
|
186
|
+
] })
|
|
152
187
|
]
|
|
153
188
|
}
|
|
154
189
|
)
|
|
@@ -13,6 +13,14 @@ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
|
13
13
|
* (default `--border`).
|
|
14
14
|
*/
|
|
15
15
|
bordered?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Zebra rows — every even LOGICAL body row wears `--table-row-striped-background`, so a wide list
|
|
18
|
+
* row is easy to follow across its columns. An expanded detail row (a `<tr data-expanded-row>`
|
|
19
|
+
* under its record) is skipped when counting and takes its record's stripe. Leave it out to
|
|
20
|
+
* inherit the theme default (`--table-row-striped-alpha`, `0%` unless a service turns striping on
|
|
21
|
+
* for every table); `true` / `false` override that default for this table only.
|
|
22
|
+
*/
|
|
23
|
+
striped?: boolean;
|
|
16
24
|
/**
|
|
17
25
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
18
26
|
* table exactly as it is.
|
|
@@ -64,6 +72,14 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
64
72
|
* (default `--border`).
|
|
65
73
|
*/
|
|
66
74
|
bordered?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Zebra rows — every even LOGICAL body row wears `--table-row-striped-background`, so a wide list
|
|
77
|
+
* row is easy to follow across its columns. An expanded detail row (a `<tr data-expanded-row>`
|
|
78
|
+
* under its record) is skipped when counting and takes its record's stripe. Leave it out to
|
|
79
|
+
* inherit the theme default (`--table-row-striped-alpha`, `0%` unless a service turns striping on
|
|
80
|
+
* for every table); `true` / `false` override that default for this table only.
|
|
81
|
+
*/
|
|
82
|
+
striped?: boolean;
|
|
67
83
|
/**
|
|
68
84
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
69
85
|
* table exactly as it is.
|
|
@@ -7,6 +7,7 @@ const Table = React.forwardRef(
|
|
|
7
7
|
className,
|
|
8
8
|
scrollable = true,
|
|
9
9
|
bordered = false,
|
|
10
|
+
striped,
|
|
10
11
|
preset = "default",
|
|
11
12
|
collapseBelow = "sm",
|
|
12
13
|
columnWidths,
|
|
@@ -41,6 +42,7 @@ const Table = React.forwardRef(
|
|
|
41
42
|
{
|
|
42
43
|
ref,
|
|
43
44
|
"data-slot": "table",
|
|
45
|
+
"data-striped": striped === void 0 ? void 0 : striped ? "" : "false",
|
|
44
46
|
className: cn("w-full caption-bottom", bordered && "ui-table-bordered", className),
|
|
45
47
|
...props
|
|
46
48
|
}
|