@godxjp/ui 27.6.0 → 27.7.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/range-timeline.d.ts +24 -0
- package/dist/components/data-display/range-timeline.js +191 -92
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +1 -0
- package/dist/i18n/messages/ja.json +1 -0
- package/dist/i18n/messages/vi.json +1 -0
- package/dist/props/registry.d.ts +13 -1
- package/dist/props/registry.js +16 -1
- package/dist/styles/data-display-layout.css +30 -0
- package/dist/tokens/components/data-display.css +2 -0
- package/docs/DESIGN-AUTHORITY.md +17 -0
- package/docs/data-display/timeline.tsx +41 -0
- package/package.json +2 -2
|
@@ -6,6 +6,12 @@ export type RangeTimelineRow = {
|
|
|
6
6
|
end: number;
|
|
7
7
|
startLabel: string;
|
|
8
8
|
endLabel: string;
|
|
9
|
+
/**
|
|
10
|
+
* Nesting level, 0 for a top-level row. Rows arrive flat and depth-first (a parent, then its
|
|
11
|
+
* descendants); a row is a parent when the row after it is deeper. The component draws the
|
|
12
|
+
* indent (`--range-timeline-indent-width` per level) and the disclosure.
|
|
13
|
+
*/
|
|
14
|
+
depth?: number;
|
|
9
15
|
};
|
|
10
16
|
export type RangeTimelineProps = React.HTMLAttributes<HTMLElement> & {
|
|
11
17
|
label: string;
|
|
@@ -28,6 +34,15 @@ export type RangeTimelineProps = React.HTMLAttributes<HTMLElement> & {
|
|
|
28
34
|
* the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
|
|
29
35
|
*/
|
|
30
36
|
bordered?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Controlled ids of the EXPANDED parent rows (same spelling as `Tree expandedValues`). A folded
|
|
39
|
+
* parent hides every descendant row — label and bar.
|
|
40
|
+
*/
|
|
41
|
+
expandedValues?: readonly string[];
|
|
42
|
+
/** Uncontrolled initial expanded parents. Omitted: every parent starts expanded. */
|
|
43
|
+
defaultExpandedValues?: readonly string[];
|
|
44
|
+
/** Fires with the next expanded parent ids, for controlled and uncontrolled timelines alike. */
|
|
45
|
+
onExpandedValuesChange?: (values: string[]) => void;
|
|
31
46
|
};
|
|
32
47
|
/** A horizontal range axis. Units and labels are data; the design system owns all geometry. */
|
|
33
48
|
export declare const RangeTimeline: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLElement> & {
|
|
@@ -51,4 +66,13 @@ export declare const RangeTimeline: React.ForwardRefExoticComponent<React.HTMLAt
|
|
|
51
66
|
* the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
|
|
52
67
|
*/
|
|
53
68
|
bordered?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Controlled ids of the EXPANDED parent rows (same spelling as `Tree expandedValues`). A folded
|
|
71
|
+
* parent hides every descendant row — label and bar.
|
|
72
|
+
*/
|
|
73
|
+
expandedValues?: readonly string[];
|
|
74
|
+
/** Uncontrolled initial expanded parents. Omitted: every parent starts expanded. */
|
|
75
|
+
defaultExpandedValues?: readonly string[];
|
|
76
|
+
/** Fires with the next expanded parent ids, for controlled and uncontrolled timelines alike. */
|
|
77
|
+
onExpandedValuesChange?: (values: string[]) => void;
|
|
54
78
|
} & React.RefAttributes<HTMLElement>>;
|
|
@@ -2,13 +2,67 @@
|
|
|
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 { ChevronLeft, ChevronRight, GripVertical } from "lucide-react";
|
|
5
|
+
import { ChevronDown, 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({
|
|
10
|
+
function RangeTimeline2({
|
|
11
|
+
label,
|
|
12
|
+
columns,
|
|
13
|
+
bands,
|
|
14
|
+
rows,
|
|
15
|
+
today,
|
|
16
|
+
onRangeChange,
|
|
17
|
+
bordered = true,
|
|
18
|
+
expandedValues,
|
|
19
|
+
defaultExpandedValues,
|
|
20
|
+
onExpandedValuesChange,
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}, ref) {
|
|
11
24
|
const { t } = useTranslation();
|
|
25
|
+
const reactId = React.useId();
|
|
26
|
+
const depthOf = (row) => Math.max(0, Math.floor(row.depth ?? 0));
|
|
27
|
+
const nested = rows.some((row) => depthOf(row) > 0);
|
|
28
|
+
const parents = rows.filter((row, index) => index + 1 < rows.length && depthOf(rows[index + 1]) > depthOf(row)).map((row) => row.id);
|
|
29
|
+
const [collapsed, setCollapsed] = React.useState(
|
|
30
|
+
() => new Set(
|
|
31
|
+
defaultExpandedValues ? parents.filter((id) => !defaultExpandedValues.includes(id)) : []
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
const isExpanded = (id) => expandedValues ? expandedValues.includes(id) : !collapsed.has(id);
|
|
35
|
+
const toggleRow = (id) => {
|
|
36
|
+
const open = !isExpanded(id);
|
|
37
|
+
if (!expandedValues) {
|
|
38
|
+
const next = new Set(collapsed);
|
|
39
|
+
if (open) next.delete(id);
|
|
40
|
+
else next.add(id);
|
|
41
|
+
setCollapsed(next);
|
|
42
|
+
}
|
|
43
|
+
onExpandedValuesChange?.(
|
|
44
|
+
parents.filter((parent) => parent === id ? open : isExpanded(parent))
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
let foldedAt = null;
|
|
48
|
+
const visibleRows = rows.filter((row) => {
|
|
49
|
+
const depth = depthOf(row);
|
|
50
|
+
if (foldedAt !== null && depth > foldedAt) return false;
|
|
51
|
+
foldedAt = parents.includes(row.id) && !isExpanded(row.id) ? depth : null;
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
const siblingsOf = (index) => {
|
|
55
|
+
const depth = depthOf(visibleRows[index]);
|
|
56
|
+
let first = index;
|
|
57
|
+
while (first > 0 && depthOf(visibleRows[first - 1]) >= depth) first -= 1;
|
|
58
|
+
let size = 0, position = 0;
|
|
59
|
+
for (let i = first; i < visibleRows.length && depthOf(visibleRows[i]) >= depth; i += 1) {
|
|
60
|
+
if (depthOf(visibleRows[i]) !== depth) continue;
|
|
61
|
+
size += 1;
|
|
62
|
+
if (i === index) position = size;
|
|
63
|
+
}
|
|
64
|
+
return { size, position };
|
|
65
|
+
};
|
|
12
66
|
const units = Math.max(
|
|
13
67
|
1,
|
|
14
68
|
columns.reduce((sum, column) => sum + column.units, 0)
|
|
@@ -73,7 +127,7 @@ const RangeTimeline = React.forwardRef(
|
|
|
73
127
|
index
|
|
74
128
|
)) })
|
|
75
129
|
] }),
|
|
76
|
-
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", children: [
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", role: nested ? "list" : void 0, children: [
|
|
77
131
|
(bordered || columns.some((column) => column.muted)) && // Decorative: the same unit tracks as the header columns, laid once behind every row so
|
|
78
132
|
// each vertical rule runs the full body height exactly under its header column.
|
|
79
133
|
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-grid", "aria-hidden": "true", children: [
|
|
@@ -88,103 +142,148 @@ const RangeTimeline = React.forwardRef(
|
|
|
88
142
|
index
|
|
89
143
|
)) })
|
|
90
144
|
] }),
|
|
91
|
-
|
|
145
|
+
visibleRows.map((row, index) => {
|
|
146
|
+
const idBase = `${reactId}-row-${rows.indexOf(row)}`;
|
|
92
147
|
const delta = preview?.id === row.id ? preview.delta : 0;
|
|
93
148
|
const start = Math.min(row.end, row.start + (preview?.edge === "start" ? delta : 0));
|
|
94
149
|
const end = Math.max(row.start, row.end + (preview?.edge === "end" ? delta : 0));
|
|
95
150
|
const left = Math.max(0, start), right = Math.min(units, end + 1);
|
|
96
|
-
return /* @__PURE__ */ jsxs(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
ChevronLeft,
|
|
110
|
-
{
|
|
111
|
-
"aria-hidden": "true",
|
|
112
|
-
className: "ui-range-timeline-outside-icon"
|
|
113
|
-
}
|
|
114
|
-
),
|
|
115
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideBefore") })
|
|
116
|
-
] }) : /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "after", children: [
|
|
117
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideAfter") }),
|
|
118
|
-
/* @__PURE__ */ jsx(
|
|
119
|
-
ChevronRight,
|
|
151
|
+
return /* @__PURE__ */ jsxs(
|
|
152
|
+
"div",
|
|
153
|
+
{
|
|
154
|
+
className: "ui-range-timeline-row",
|
|
155
|
+
...nested && {
|
|
156
|
+
role: "listitem",
|
|
157
|
+
"aria-level": depthOf(row) + 1,
|
|
158
|
+
"aria-setsize": siblingsOf(index).size,
|
|
159
|
+
"aria-posinset": siblingsOf(index).position
|
|
160
|
+
},
|
|
161
|
+
children: [
|
|
162
|
+
nested ? /* @__PURE__ */ jsxs(
|
|
163
|
+
"div",
|
|
120
164
|
{
|
|
121
|
-
|
|
122
|
-
|
|
165
|
+
className: "ui-range-timeline-label",
|
|
166
|
+
"data-nested": "true",
|
|
167
|
+
style: { "--range-timeline-depth": depthOf(row) },
|
|
168
|
+
children: [
|
|
169
|
+
parents.includes(row.id) ? /* @__PURE__ */ jsxs(
|
|
170
|
+
Button,
|
|
171
|
+
{
|
|
172
|
+
variant: "ghost",
|
|
173
|
+
size: "icon-xs",
|
|
174
|
+
className: "ui-range-timeline-disclosure",
|
|
175
|
+
"aria-expanded": isExpanded(row.id),
|
|
176
|
+
"aria-labelledby": `${idBase}-toggle ${idBase}-label`,
|
|
177
|
+
onClick: () => toggleRow(row.id),
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ jsx("span", { id: `${idBase}-toggle`, hidden: true, children: t("rangeTimeline.childRows") }),
|
|
180
|
+
isExpanded(row.id) ? /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
|
|
181
|
+
ChevronRight,
|
|
182
|
+
{
|
|
183
|
+
"aria-hidden": "true",
|
|
184
|
+
className: "ui-range-timeline-disclosure-collapsed"
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ui-range-timeline-disclosure-spacer" }),
|
|
190
|
+
/* @__PURE__ */ jsx("div", { id: `${idBase}-label`, className: "ui-range-timeline-label-content", children: row.label })
|
|
191
|
+
]
|
|
123
192
|
}
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
className: "ui-range-timeline-bar",
|
|
130
|
-
style: {
|
|
131
|
-
insetInlineStart: `${left / units * 100}%`,
|
|
132
|
-
inlineSize: `${(right - left) / units * 100}%`
|
|
133
|
-
},
|
|
134
|
-
children: onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
|
|
135
|
-
(edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
|
|
136
|
-
).map((edge) => /* @__PURE__ */ jsx(
|
|
137
|
-
Button,
|
|
193
|
+
) : /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: row.label }),
|
|
194
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-track", children: [
|
|
195
|
+
today != null && today >= 0 && today < units && /* @__PURE__ */ jsx(
|
|
196
|
+
"span",
|
|
138
197
|
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
173
|
-
if (!step) return;
|
|
174
|
-
event.preventDefault();
|
|
175
|
-
const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
|
|
176
|
-
const delta2 = step * direction;
|
|
177
|
-
if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
|
|
178
|
-
onRangeChange(row.id, edge, delta2);
|
|
198
|
+
"aria-hidden": "true",
|
|
199
|
+
className: "ui-range-timeline-today",
|
|
200
|
+
style: { insetInlineStart: `${(today + 0.5) / units * 100}%` }
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
ChevronLeft,
|
|
206
|
+
{
|
|
207
|
+
"aria-hidden": "true",
|
|
208
|
+
className: "ui-range-timeline-outside-icon"
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideBefore") })
|
|
212
|
+
] }) : /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "after", children: [
|
|
213
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideAfter") }),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
ChevronRight,
|
|
216
|
+
{
|
|
217
|
+
"aria-hidden": "true",
|
|
218
|
+
className: "ui-range-timeline-outside-icon"
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
] })),
|
|
222
|
+
right > left && /* @__PURE__ */ jsx(
|
|
223
|
+
"div",
|
|
224
|
+
{
|
|
225
|
+
className: "ui-range-timeline-bar",
|
|
226
|
+
style: {
|
|
227
|
+
insetInlineStart: `${left / units * 100}%`,
|
|
228
|
+
inlineSize: `${(right - left) / units * 100}%`
|
|
179
229
|
},
|
|
180
|
-
children:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
230
|
+
children: onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
|
|
231
|
+
(edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
|
|
232
|
+
).map((edge) => /* @__PURE__ */ jsx(
|
|
233
|
+
Button,
|
|
234
|
+
{
|
|
235
|
+
variant: "ghost",
|
|
236
|
+
size: "icon-xs",
|
|
237
|
+
className: "ui-range-timeline-handle",
|
|
238
|
+
"data-edge": edge,
|
|
239
|
+
"aria-label": edge === "start" ? row.startLabel : row.endLabel,
|
|
240
|
+
onPointerDown: (event) => {
|
|
241
|
+
const width = track.current?.getBoundingClientRect().width ?? 0;
|
|
242
|
+
if (!width || event.button !== 0) return;
|
|
243
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
244
|
+
drag.current = {
|
|
245
|
+
id: row.id,
|
|
246
|
+
edge,
|
|
247
|
+
x: event.clientX,
|
|
248
|
+
width,
|
|
249
|
+
direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
onPointerMove: (event) => {
|
|
253
|
+
if (drag.current)
|
|
254
|
+
setPreview({
|
|
255
|
+
id: drag.current.id,
|
|
256
|
+
edge: drag.current.edge,
|
|
257
|
+
delta: deltaAt(event.clientX)
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
onPointerUp: complete,
|
|
261
|
+
onPointerCancel: cancel,
|
|
262
|
+
onLostPointerCapture: cancel,
|
|
263
|
+
onKeyDown: (event) => {
|
|
264
|
+
if (event.key === "Escape") {
|
|
265
|
+
cancel();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
269
|
+
if (!step) return;
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
|
|
272
|
+
const delta2 = step * direction;
|
|
273
|
+
if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
|
|
274
|
+
onRangeChange(row.id, edge, delta2);
|
|
275
|
+
},
|
|
276
|
+
children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
|
|
277
|
+
},
|
|
278
|
+
edge
|
|
279
|
+
))
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
] })
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
row.id
|
|
286
|
+
);
|
|
188
287
|
})
|
|
189
288
|
] })
|
|
190
289
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.7.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -2950,7 +2950,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
2950
2950
|
}, {
|
|
2951
2951
|
readonly field: "rows";
|
|
2952
2952
|
readonly local: true;
|
|
2953
|
-
readonly reason: "Consumer-supplied interval records preserve true inclusive endpoints independently of clipping.";
|
|
2953
|
+
readonly reason: "Consumer-supplied interval records preserve true inclusive endpoints independently of clipping; optional `depth` nests them (flat, depth-first; a row is a parent when the next row is deeper).";
|
|
2954
2954
|
}, {
|
|
2955
2955
|
readonly field: "today";
|
|
2956
2956
|
readonly local: true;
|
|
@@ -2959,6 +2959,18 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
2959
2959
|
readonly field: "onRangeChange";
|
|
2960
2960
|
readonly local: true;
|
|
2961
2961
|
readonly reason: "An endpoint movement command, not an internally owned value; the consumer commits its row data.";
|
|
2962
|
+
}, {
|
|
2963
|
+
readonly field: "expandedValues";
|
|
2964
|
+
readonly local: true;
|
|
2965
|
+
readonly reason: "Controlled expanded parent row ids (gh#724) — the same spelling as `Tree expandedValues` (antd `expandedKeys` / Table `expandedRowKeys`), per docs/DESIGN-AUTHORITY.md.";
|
|
2966
|
+
}, {
|
|
2967
|
+
readonly field: "defaultExpandedValues";
|
|
2968
|
+
readonly local: true;
|
|
2969
|
+
readonly reason: "Uncontrolled initial expanded parents, same spelling as `Tree`; omitted, every parent starts expanded so adding `depth` never hides a row.";
|
|
2970
|
+
}, {
|
|
2971
|
+
readonly field: "onExpandedValuesChange";
|
|
2972
|
+
readonly local: true;
|
|
2973
|
+
readonly reason: "Next expanded parent ids, fired for controlled and uncontrolled timelines alike — the `Tree onExpandedValuesChange` callback.";
|
|
2962
2974
|
}];
|
|
2963
2975
|
};
|
|
2964
2976
|
readonly ResponsiveGridProp: {
|
package/dist/props/registry.js
CHANGED
|
@@ -3460,7 +3460,7 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
3460
3460
|
{
|
|
3461
3461
|
field: "rows",
|
|
3462
3462
|
local: true,
|
|
3463
|
-
reason: "Consumer-supplied interval records preserve true inclusive endpoints independently of clipping."
|
|
3463
|
+
reason: "Consumer-supplied interval records preserve true inclusive endpoints independently of clipping; optional `depth` nests them (flat, depth-first; a row is a parent when the next row is deeper)."
|
|
3464
3464
|
},
|
|
3465
3465
|
{
|
|
3466
3466
|
field: "today",
|
|
@@ -3471,6 +3471,21 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
3471
3471
|
field: "onRangeChange",
|
|
3472
3472
|
local: true,
|
|
3473
3473
|
reason: "An endpoint movement command, not an internally owned value; the consumer commits its row data."
|
|
3474
|
+
},
|
|
3475
|
+
{
|
|
3476
|
+
field: "expandedValues",
|
|
3477
|
+
local: true,
|
|
3478
|
+
reason: "Controlled expanded parent row ids (gh#724) \u2014 the same spelling as `Tree expandedValues` (antd `expandedKeys` / Table `expandedRowKeys`), per docs/DESIGN-AUTHORITY.md."
|
|
3479
|
+
},
|
|
3480
|
+
{
|
|
3481
|
+
field: "defaultExpandedValues",
|
|
3482
|
+
local: true,
|
|
3483
|
+
reason: "Uncontrolled initial expanded parents, same spelling as `Tree`; omitted, every parent starts expanded so adding `depth` never hides a row."
|
|
3484
|
+
},
|
|
3485
|
+
{
|
|
3486
|
+
field: "onExpandedValuesChange",
|
|
3487
|
+
local: true,
|
|
3488
|
+
reason: "Next expanded parent ids, fired for controlled and uncontrolled timelines alike \u2014 the `Tree onExpandedValuesChange` callback."
|
|
3474
3489
|
}
|
|
3475
3490
|
]
|
|
3476
3491
|
},
|
|
@@ -1454,6 +1454,36 @@
|
|
|
1454
1454
|
inset-block: 0;
|
|
1455
1455
|
border-inline-start: 1px solid hsl(var(--primary));
|
|
1456
1456
|
}
|
|
1457
|
+
|
|
1458
|
+
.ui-range-timeline-label[data-nested="true"] {
|
|
1459
|
+
display: flex;
|
|
1460
|
+
align-items: center;
|
|
1461
|
+
gap: var(--space-1);
|
|
1462
|
+
padding-inline-start: calc(
|
|
1463
|
+
var(--space-2) + var(--range-timeline-depth, 0) * var(--range-timeline-indent-width)
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
.ui-range-timeline-label-content {
|
|
1467
|
+
flex: 1;
|
|
1468
|
+
min-inline-size: 0;
|
|
1469
|
+
}
|
|
1470
|
+
.ui-range-timeline-disclosure,
|
|
1471
|
+
.ui-range-timeline-disclosure-spacer {
|
|
1472
|
+
flex-shrink: 0;
|
|
1473
|
+
}
|
|
1474
|
+
.ui-range-timeline-disclosure-spacer {
|
|
1475
|
+
inline-size: var(--control-height-xs);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
@media (pointer: coarse) {
|
|
1479
|
+
.ui-range-timeline-disclosure-spacer {
|
|
1480
|
+
inline-size: var(--control-height-default);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
[dir="rtl"] .ui-range-timeline-disclosure-collapsed {
|
|
1485
|
+
transform: scaleX(-1);
|
|
1486
|
+
}
|
|
1457
1487
|
}
|
|
1458
1488
|
|
|
1459
1489
|
@layer components {
|
package/docs/DESIGN-AUTHORITY.md
CHANGED
|
@@ -298,6 +298,23 @@ bottomEnd | none` (`TablePaginationPositionProp`, default `['bottomEnd']`), the
|
|
|
298
298
|
purpose: omitted, `flow="columns"` keeps `start` and `flow="rows"` keeps the grid's stretch, so
|
|
299
299
|
no existing grid moves; antd's `Row` default is `top` in every case.
|
|
300
300
|
|
|
301
|
+
- **`RangeTimeline` nested rows take `Tree`'s spelling, not antd's (gh#724).** antd has no Gantt;
|
|
302
|
+
the nearest names are `Tree expandedKeys` / `defaultExpandedKeys` / `onExpand` and `Table
|
|
303
|
+
expandable.expandedRowKeys` / `onExpandedRowsChange`. This library already mapped the Tree ones
|
|
304
|
+
onto its controlled vocabulary as `expandedValues` / `defaultExpandedValues` /
|
|
305
|
+
`onExpandedValuesChange`, and a second spelling of the same axis is what
|
|
306
|
+
`check:prop-vocabulary` exists to prevent — so `RangeTimeline` reuses those three names exactly
|
|
307
|
+
(the values are row `id`s). The shape is also `Tree`'s: flat rows plus `depth`, the component
|
|
308
|
+
owns the indent (`--range-timeline-indent-width`) and the disclosure. Two deliberate differences
|
|
309
|
+
from `Tree`: (1) a row is a parent when the NEXT row is deeper — derived from the depth-first list,
|
|
310
|
+
never from a `hasChildren` flag, so a disclosure can never promise children that are not there;
|
|
311
|
+
(2) omitted `defaultExpandedValues` starts every parent EXPANDED (a `Tree` starts closed), so
|
|
312
|
+
adding `depth` to an existing schedule never hides a row. The disclosure is a real `Button`
|
|
313
|
+
(the row is not a focusable tree item, so APG's one-tab-stop rule does not apply), and the rows
|
|
314
|
+
are a `list` of `listitem`s with `aria-level` / `aria-setsize` / `aria-posinset` — the lightest
|
|
315
|
+
role that carries a level without promising the `tree` / `treegrid` arrow-key model. A timeline
|
|
316
|
+
with no `depth > 0` keeps its pre-#724 markup byte for byte.
|
|
317
|
+
|
|
301
318
|
**A knob that only a fork could reach is not parity either.** antd's `components`,
|
|
302
319
|
`filterDropdown`, `classNames`/`styles` semantic maps and `prefixCls` all exist to let a consumer
|
|
303
320
|
replace the rendered markup. This library answers that layer with tokens (cardinal rule #45), so
|
|
@@ -293,6 +293,47 @@ export default function Demo() {
|
|
|
293
293
|
/>
|
|
294
294
|
</CardContent>
|
|
295
295
|
</Card>
|
|
296
|
+
|
|
297
|
+
<Card>
|
|
298
|
+
<CardHeader>
|
|
299
|
+
<CardTitle level={2}>親子のある作業(depth と折りたたみ)</CardTitle>
|
|
300
|
+
<CardDescription>
|
|
301
|
+
行を深さ優先の順に並べ、depth
|
|
302
|
+
を渡すと、ラベルの列の中で段ごとに字下げされます。次の行が
|
|
303
|
+
より深い行は親になり、開閉ボタンが付きます。「実装」は defaultExpandedValues
|
|
304
|
+
に含まれていないため折りたたまれており、子の行もそのバーも表示されません。
|
|
305
|
+
</CardDescription>
|
|
306
|
+
</CardHeader>
|
|
307
|
+
<CardContent>
|
|
308
|
+
<RangeTimeline
|
|
309
|
+
label={t("rangeTimeline.schedule")}
|
|
310
|
+
today={4}
|
|
311
|
+
columns={Array.from({ length: 14 }, (_, index) => {
|
|
312
|
+
const weekday = new Date(Date.UTC(2026, 8, index + 1)).getUTCDay();
|
|
313
|
+
return {
|
|
314
|
+
label: new Intl.NumberFormat(locale).format(index + 1),
|
|
315
|
+
units: 1,
|
|
316
|
+
muted: weekday === 0 || weekday === 6,
|
|
317
|
+
};
|
|
318
|
+
})}
|
|
319
|
+
defaultExpandedValues={["design", "api"]}
|
|
320
|
+
rows={[
|
|
321
|
+
{ id: "design", label: "設計", depth: 0, start: 0, end: 5 },
|
|
322
|
+
{ id: "screens", label: "画面設計", depth: 1, start: 0, end: 2 },
|
|
323
|
+
{ id: "api", label: "API設計", depth: 1, start: 2, end: 5 },
|
|
324
|
+
{ id: "endpoints", label: "エンドポイント定義", depth: 2, start: 2, end: 3 },
|
|
325
|
+
{ id: "build", label: "実装", depth: 0, start: 6, end: 12 },
|
|
326
|
+
{ id: "frontend", label: "フロントエンド", depth: 1, start: 6, end: 10 },
|
|
327
|
+
{ id: "backend", label: "バックエンド", depth: 1, start: 6, end: 12 },
|
|
328
|
+
{ id: "release", label: "リリース", depth: 0, start: 13, end: 13 },
|
|
329
|
+
].map((row) => ({
|
|
330
|
+
...row,
|
|
331
|
+
startLabel: dayLabel(row.start + 1),
|
|
332
|
+
endLabel: dayLabel(row.end + 1),
|
|
333
|
+
}))}
|
|
334
|
+
/>
|
|
335
|
+
</CardContent>
|
|
336
|
+
</Card>
|
|
296
337
|
</Flex>
|
|
297
338
|
</PageContainer>
|
|
298
339
|
);
|