@godxjp/ui 23.0.0 → 23.2.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/app/date-format-labels.d.ts +14 -2
- package/dist/app/date-format-labels.js +1 -1
- package/dist/app/date-formats.d.ts +14 -2
- package/dist/app/date-formats.js +9 -2
- package/dist/components/data-display/card.d.ts +45 -2
- package/dist/components/data-display/card.js +4 -2
- package/dist/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-display/feature-list.d.ts +26 -0
- package/dist/components/data-display/feature-list.js +29 -0
- package/dist/components/data-display/index.d.ts +6 -0
- package/dist/components/data-display/index.js +6 -0
- package/dist/components/data-display/swatch.d.ts +24 -0
- package/dist/components/data-display/swatch.js +19 -0
- package/dist/components/data-display/thumbnail.d.ts +37 -0
- package/dist/components/data-display/thumbnail.js +20 -0
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/form-field.js +2 -2
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/number-input.js +27 -6
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/switch.js +1 -1
- package/dist/components/feedback/dialog.js +2 -2
- package/dist/components/feedback/index.d.ts +2 -2
- package/dist/components/feedback/index.js +19 -1
- package/dist/components/feedback/sheet.js +2 -22
- package/dist/components/feedback/skeleton.d.ts +33 -3
- package/dist/components/feedback/skeleton.js +154 -3
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +17 -5
- package/dist/components/layout/sidebar.js +9 -2
- package/dist/components/navigation/filter-bar.js +2 -1
- package/dist/components/navigation/tabs-scroll.d.ts +27 -0
- package/dist/components/navigation/tabs-scroll.js +36 -1
- package/dist/components/navigation/tabs.d.ts +2 -2
- package/dist/components/navigation/tabs.js +60 -16
- package/dist/i18n/messages/en.json +10 -1
- package/dist/i18n/messages/ja.json +10 -1
- package/dist/i18n/messages/vi.json +10 -1
- package/dist/lib/breakpoint-token.d.ts +8 -0
- package/dist/lib/breakpoint-token.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +101 -1
- package/dist/props/components/feedback.prop.d.ts +85 -1
- package/dist/props/components/layout.prop.d.ts +72 -1
- package/dist/props/components/navigation.prop.d.ts +80 -2
- package/dist/props/registry.d.ts +124 -1
- package/dist/props/registry.js +140 -0
- package/dist/props/vocabulary/data.prop.d.ts +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/alert-layout.css +139 -0
- package/dist/styles/card-layout.css +48 -0
- package/dist/styles/control.css +41 -5
- package/dist/styles/data-display-layout.css +88 -0
- package/dist/styles/focus-ring.css +28 -0
- package/dist/styles/form-layout.css +4 -0
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +89 -5
- package/dist/styles/shell-layout.css +22 -0
- package/dist/styles/table-layout.css +5 -2
- package/dist/tokens/components/card.css +2 -0
- package/dist/tokens/components/control.css +8 -3
- package/dist/tokens/components/data-display.css +23 -0
- package/dist/tokens/components/feedback.css +18 -0
- package/dist/tokens/components/navigation.css +4 -0
- package/dist/tokens/foundation.css +18 -16
- package/docs/CONSUMER-RULES.md +8 -3
- package/docs/CUSTOMER-THEMING.md +18 -8
- package/docs/DATETIME.md +11 -2
- package/docs/DESIGN-AUTHORITY.md +2 -1
- package/docs/FRAME-COVERAGE-REPORT.md +29 -3
- package/docs/SPACING.md +35 -5
- package/docs/TOKENS.md +1 -1
- package/docs/assets/shot-landscape.svg +12 -0
- package/docs/assets/shot-portrait.svg +10 -0
- package/docs/data-display/card/index.tsx +40 -0
- package/docs/data-display/data-table/index.tsx +17 -2
- package/docs/data-display/feature-list.tsx +159 -0
- package/docs/data-display/swatch.tsx +107 -0
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-display/thumbnail.tsx +128 -0
- package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
- package/docs/feedback/skeleton.tsx +57 -0
- package/docs/layout/flex.tsx +29 -0
- package/docs/layout/sidebar.tsx +66 -10
- package/docs/navigation/dropdown-menu.tsx +4 -4
- package/docs/navigation/tabs.tsx +131 -0
- package/docs/roadmap/ai-chat-components.md +1 -2
- package/docs/roadmap/tree-components.md +0 -1
- package/package.json +12 -21
- package/scripts/_agent-setup.mjs +18 -0
- package/scripts/consumer-rule.md +16 -0
- package/scripts/guinea-pig-skill.md +12 -6
- package/scripts/ui-audit.mjs +184 -7
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "react-aria-components";
|
|
10
10
|
import { MoreHorizontal, Plus, X } from "lucide-react";
|
|
11
11
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
12
|
+
import { useMaxWidthBreakpoint } from "../../lib/breakpoint-token.js";
|
|
12
13
|
import { cn } from "../../lib/utils.js";
|
|
13
14
|
import {
|
|
14
15
|
DropdownMenu,
|
|
@@ -16,10 +17,22 @@ import {
|
|
|
16
17
|
DropdownMenuItem,
|
|
17
18
|
DropdownMenuTrigger
|
|
18
19
|
} from "./dropdown-menu.js";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
useKeepActiveTabVisible,
|
|
22
|
+
useTabsOverflowValues,
|
|
23
|
+
useTabsScrollReporter
|
|
24
|
+
} from "./tabs-scroll.js";
|
|
25
|
+
function triggerSizeClassName(size) {
|
|
26
|
+
return cn(
|
|
27
|
+
size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
|
|
28
|
+
size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
|
|
29
|
+
size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
20
32
|
const TabsFrameContext = React.createContext({
|
|
21
33
|
orientation: "horizontal",
|
|
22
|
-
selectionSuppressed: false
|
|
34
|
+
selectionSuppressed: false,
|
|
35
|
+
size: "md"
|
|
23
36
|
});
|
|
24
37
|
function resolveFallbackTabValue(items, requested) {
|
|
25
38
|
if (!items || items.length === 0) return requested;
|
|
@@ -45,6 +58,12 @@ function resolveTabsExtra(extra) {
|
|
|
45
58
|
if (isSlotMap) return extra;
|
|
46
59
|
return { end: extra };
|
|
47
60
|
}
|
|
61
|
+
function resolveTabsAnimated(animated) {
|
|
62
|
+
if (animated === false) return { inkBar: false, tabPane: false };
|
|
63
|
+
if (animated === true) return { inkBar: true, tabPane: true };
|
|
64
|
+
if (animated === void 0) return { inkBar: true, tabPane: false };
|
|
65
|
+
return { inkBar: animated.inkBar ?? true, tabPane: animated.tabPane ?? false };
|
|
66
|
+
}
|
|
48
67
|
function resolveTabsAxis(tabPlacement, orientation) {
|
|
49
68
|
if (tabPlacement) {
|
|
50
69
|
return {
|
|
@@ -57,6 +76,13 @@ function resolveTabsAxis(tabPlacement, orientation) {
|
|
|
57
76
|
orientation: orientation ?? "horizontal"
|
|
58
77
|
};
|
|
59
78
|
}
|
|
79
|
+
const TABS_PLACEMENT_BREAKPOINT_TOKEN = "--tabs-placement-responsive-breakpoint-width";
|
|
80
|
+
const TABS_PLACEMENT_BREAKPOINT_FALLBACK_QUERY = "(max-width: 768px)";
|
|
81
|
+
function foldVerticalPlacement(axis, narrow) {
|
|
82
|
+
if (!narrow || axis.orientation !== "vertical") return axis;
|
|
83
|
+
const trailing = axis.placement === "end" || axis.placement === "bottom";
|
|
84
|
+
return { placement: trailing ? "bottom" : "top", orientation: "horizontal" };
|
|
85
|
+
}
|
|
60
86
|
function Tabs({
|
|
61
87
|
className,
|
|
62
88
|
orientation,
|
|
@@ -78,21 +104,32 @@ function Tabs({
|
|
|
78
104
|
closeIcon,
|
|
79
105
|
onTabClick,
|
|
80
106
|
overflow = "scroll",
|
|
107
|
+
animated,
|
|
108
|
+
indicator,
|
|
109
|
+
moreIcon,
|
|
110
|
+
onTabScroll,
|
|
81
111
|
listClassName,
|
|
82
112
|
contentClassName,
|
|
83
113
|
children,
|
|
84
114
|
...props
|
|
85
115
|
}) {
|
|
86
116
|
const { t } = useTranslation();
|
|
117
|
+
const motion = resolveTabsAnimated(animated);
|
|
118
|
+
const indicatorSize = indicator?.size ?? "full";
|
|
119
|
+
const indicatorAlign = indicator?.align ?? "center";
|
|
87
120
|
const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
const narrow = useMaxWidthBreakpoint(
|
|
122
|
+
TABS_PLACEMENT_BREAKPOINT_TOKEN,
|
|
123
|
+
TABS_PLACEMENT_BREAKPOINT_FALLBACK_QUERY
|
|
124
|
+
);
|
|
125
|
+
const { placement, orientation: resolvedOrientation } = foldVerticalPlacement(
|
|
126
|
+
resolveTabsAxis(tabPlacement, orientation),
|
|
127
|
+
narrow
|
|
91
128
|
);
|
|
92
129
|
const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
|
|
93
130
|
const frame = React.useMemo(
|
|
94
|
-
() => ({ orientation: resolvedOrientation, selectionSuppressed }),
|
|
95
|
-
[resolvedOrientation, selectionSuppressed]
|
|
131
|
+
() => ({ orientation: resolvedOrientation, selectionSuppressed, size }),
|
|
132
|
+
[resolvedOrientation, selectionSuppressed, size]
|
|
96
133
|
);
|
|
97
134
|
const editable = variant === "editable-card";
|
|
98
135
|
const collapsible = items != null && overflow === "menu";
|
|
@@ -108,6 +145,7 @@ function Tabs({
|
|
|
108
145
|
);
|
|
109
146
|
}, []);
|
|
110
147
|
useTabsOverflowValues(listRef, itemValues, handleHiddenChange);
|
|
148
|
+
useTabsScrollReporter(listRef, resolvedOrientation === "vertical", onTabScroll);
|
|
111
149
|
const [mirroredValue, setMirroredValue] = React.useState(
|
|
112
150
|
value ?? resolvedDefault
|
|
113
151
|
);
|
|
@@ -127,11 +165,7 @@ function Tabs({
|
|
|
127
165
|
const showAdd = editable && !hideAdd && Boolean(onEdit);
|
|
128
166
|
const needsBar = Boolean(extraStart || extraEnd || showAdd || collapsible);
|
|
129
167
|
const card = variant === "card" || variant === "editable-card";
|
|
130
|
-
const sizeTriggerClassName =
|
|
131
|
-
size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
|
|
132
|
-
size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
|
|
133
|
-
size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
|
|
134
|
-
);
|
|
168
|
+
const sizeTriggerClassName = triggerSizeClassName(size);
|
|
135
169
|
const list = items ? /* @__PURE__ */ jsx(
|
|
136
170
|
TabsList,
|
|
137
171
|
{
|
|
@@ -271,6 +305,10 @@ function Tabs({
|
|
|
271
305
|
"data-placement": placement,
|
|
272
306
|
"data-size": size,
|
|
273
307
|
"data-centered": centered ? "true" : void 0,
|
|
308
|
+
"data-animated-ink-bar": motion.inkBar ? "true" : "false",
|
|
309
|
+
"data-animated-tab-pane": motion.tabPane ? "true" : "false",
|
|
310
|
+
"data-indicator-size": indicatorSize,
|
|
311
|
+
"data-indicator-align": indicatorAlign,
|
|
274
312
|
orientation: resolvedOrientation,
|
|
275
313
|
keyboardActivation: activationMode,
|
|
276
314
|
selectedKey: value ?? (collapsible ? mirroredValue : void 0),
|
|
@@ -295,7 +333,11 @@ function Tabs({
|
|
|
295
333
|
"data-variant": variant,
|
|
296
334
|
"data-placement": placement,
|
|
297
335
|
"data-size": size,
|
|
298
|
-
"data-centered": centered ? "true" : void 0
|
|
336
|
+
"data-centered": centered ? "true" : void 0,
|
|
337
|
+
"data-animated-ink-bar": motion.inkBar ? "true" : "false",
|
|
338
|
+
"data-animated-tab-pane": motion.tabPane ? "true" : "false",
|
|
339
|
+
"data-indicator-size": indicatorSize,
|
|
340
|
+
"data-indicator-align": indicatorAlign
|
|
299
341
|
}),
|
|
300
342
|
children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
301
343
|
needsBar ? /* @__PURE__ */ jsxs("div", { "data-slot": "tabs-bar", className: "ui-tabs-bar", children: [
|
|
@@ -318,7 +360,7 @@ function Tabs({
|
|
|
318
360
|
{
|
|
319
361
|
className: "ui-tabs-overflow",
|
|
320
362
|
"aria-label": t("navigation.tabs.moreTabs"),
|
|
321
|
-
children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "ui-tabs-overflow-icon", "aria-hidden": "true" })
|
|
363
|
+
children: moreIcon ?? /* @__PURE__ */ jsx(MoreHorizontal, { className: "ui-tabs-overflow-icon", "aria-hidden": "true" })
|
|
322
364
|
}
|
|
323
365
|
),
|
|
324
366
|
/* @__PURE__ */ jsx(DropdownMenuContent, { placement: "bottomEnd", children: overflowItems.map((item) => /* @__PURE__ */ jsx(
|
|
@@ -395,7 +437,7 @@ const TabsList = React.forwardRef(
|
|
|
395
437
|
TabsList.displayName = "TabsList";
|
|
396
438
|
const TabsTrigger = React.forwardRef(
|
|
397
439
|
({ className, value, disabled, children, onKeyDown, onClick, ...props }, ref) => {
|
|
398
|
-
const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
|
|
440
|
+
const { orientation, selectionSuppressed, size } = React.useContext(TabsFrameContext);
|
|
399
441
|
return /* @__PURE__ */ jsx(
|
|
400
442
|
AriaTab,
|
|
401
443
|
{
|
|
@@ -422,6 +464,8 @@ const TabsTrigger = React.forwardRef(
|
|
|
422
464
|
// Selected and focused stay visually distinct (WCAG 2.4.7): selected is a 1px hairline in the
|
|
423
465
|
// border, focused is the 2px ring plus its halo outside it.
|
|
424
466
|
"text-muted-foreground ring-offset-background hover:text-foreground ui-focus-ring data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:border-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
|
|
467
|
+
// The tier the root was given. Without this the compound form ignored `size` outright.
|
|
468
|
+
triggerSizeClassName(size),
|
|
425
469
|
className
|
|
426
470
|
),
|
|
427
471
|
render: (domProps, renderProps) => withDomProps("button", "tabs-trigger", { type: "button", ...props }, domProps, {
|
|
@@ -470,7 +514,7 @@ const TabsContent = React.forwardRef(
|
|
|
470
514
|
ref,
|
|
471
515
|
id: value,
|
|
472
516
|
shouldForceMount: forceMount,
|
|
473
|
-
className: cn("ui-focus-ring flex-1
|
|
517
|
+
className: cn("ui-focus-ring flex-1", className),
|
|
474
518
|
render: (domProps, renderProps) => withDomProps("div", "tabs-content", props, domProps, {
|
|
475
519
|
// A force-mounted panel whose tab is not selected: Radix marked it `data-state="inactive"`
|
|
476
520
|
// and `hidden`; RAC marks it inert. The state hook is re-emitted so CSS keys the same way.
|
|
@@ -283,6 +283,11 @@
|
|
|
283
283
|
"dotsLabel": "Choose slide",
|
|
284
284
|
"goToSlide": "Go to slide {index}"
|
|
285
285
|
},
|
|
286
|
+
"featureList": {
|
|
287
|
+
"included": "Included",
|
|
288
|
+
"excluded": "Not included",
|
|
289
|
+
"limited": "Limited"
|
|
290
|
+
},
|
|
286
291
|
"listRow": {
|
|
287
292
|
"unread": "Unread",
|
|
288
293
|
"read": "Read"
|
|
@@ -341,7 +346,9 @@
|
|
|
341
346
|
},
|
|
342
347
|
"numberInput": {
|
|
343
348
|
"increment": "Increase",
|
|
344
|
-
"decrement": "Decrease"
|
|
349
|
+
"decrement": "Decrease",
|
|
350
|
+
"incrementField": "Increase {label}",
|
|
351
|
+
"decrementField": "Decrease {label}"
|
|
345
352
|
}
|
|
346
353
|
},
|
|
347
354
|
"navigation": {
|
|
@@ -368,6 +375,7 @@
|
|
|
368
375
|
},
|
|
369
376
|
"filterBar": {
|
|
370
377
|
"appliedFilters": "Applied filters",
|
|
378
|
+
"clearFilter": "Clear {label} selection",
|
|
371
379
|
"removeFilter": "Remove filter: {label}",
|
|
372
380
|
"resultCount": {
|
|
373
381
|
"one": "{count} result",
|
|
@@ -442,6 +450,7 @@
|
|
|
442
450
|
},
|
|
443
451
|
"dateFormat": {
|
|
444
452
|
"iso": "ISO (yyyy-MM-dd)",
|
|
453
|
+
"ymd": "Year / Month / Day (yyyy/MM/dd)",
|
|
445
454
|
"dmy": "Day / Month / Year",
|
|
446
455
|
"mdy": "Month / Day / Year"
|
|
447
456
|
},
|
|
@@ -276,6 +276,11 @@
|
|
|
276
276
|
"dotsLabel": "スライドを選択",
|
|
277
277
|
"goToSlide": "{index} 番目のスライドへ"
|
|
278
278
|
},
|
|
279
|
+
"featureList": {
|
|
280
|
+
"included": "含む",
|
|
281
|
+
"excluded": "含まない",
|
|
282
|
+
"limited": "制限あり"
|
|
283
|
+
},
|
|
279
284
|
"listRow": {
|
|
280
285
|
"unread": "未読",
|
|
281
286
|
"read": "既読"
|
|
@@ -334,7 +339,9 @@
|
|
|
334
339
|
},
|
|
335
340
|
"numberInput": {
|
|
336
341
|
"increment": "増やす",
|
|
337
|
-
"decrement": "減らす"
|
|
342
|
+
"decrement": "減らす",
|
|
343
|
+
"incrementField": "{label}を増やす",
|
|
344
|
+
"decrementField": "{label}を減らす"
|
|
338
345
|
}
|
|
339
346
|
},
|
|
340
347
|
"navigation": {
|
|
@@ -361,6 +368,7 @@
|
|
|
361
368
|
},
|
|
362
369
|
"filterBar": {
|
|
363
370
|
"appliedFilters": "適用中のフィルター",
|
|
371
|
+
"clearFilter": "{label}の選択をクリア",
|
|
364
372
|
"removeFilter": "フィルターを解除: {label}",
|
|
365
373
|
"resultCount": "{count} 件の結果"
|
|
366
374
|
},
|
|
@@ -429,6 +437,7 @@
|
|
|
429
437
|
},
|
|
430
438
|
"dateFormat": {
|
|
431
439
|
"iso": "YYYY-MM-DD(年-月-日)",
|
|
440
|
+
"ymd": "YYYY/MM/DD(年/月/日)",
|
|
432
441
|
"dmy": "日/月/年",
|
|
433
442
|
"mdy": "月/日/年"
|
|
434
443
|
},
|
|
@@ -277,6 +277,11 @@
|
|
|
277
277
|
"dotsLabel": "Chọn slide",
|
|
278
278
|
"goToSlide": "Đến slide {index}"
|
|
279
279
|
},
|
|
280
|
+
"featureList": {
|
|
281
|
+
"included": "Có",
|
|
282
|
+
"excluded": "Không có",
|
|
283
|
+
"limited": "Giới hạn"
|
|
284
|
+
},
|
|
280
285
|
"listRow": {
|
|
281
286
|
"unread": "Chưa đọc",
|
|
282
287
|
"read": "Đã đọc"
|
|
@@ -335,7 +340,9 @@
|
|
|
335
340
|
},
|
|
336
341
|
"numberInput": {
|
|
337
342
|
"increment": "Tăng",
|
|
338
|
-
"decrement": "Giảm"
|
|
343
|
+
"decrement": "Giảm",
|
|
344
|
+
"incrementField": "Tăng {label}",
|
|
345
|
+
"decrementField": "Giảm {label}"
|
|
339
346
|
}
|
|
340
347
|
},
|
|
341
348
|
"navigation": {
|
|
@@ -362,6 +369,7 @@
|
|
|
362
369
|
},
|
|
363
370
|
"filterBar": {
|
|
364
371
|
"appliedFilters": "Bộ lọc đang áp dụng",
|
|
372
|
+
"clearFilter": "Xóa lựa chọn {label}",
|
|
365
373
|
"removeFilter": "Bỏ bộ lọc: {label}",
|
|
366
374
|
"resultCount": "{count} kết quả"
|
|
367
375
|
},
|
|
@@ -430,6 +438,7 @@
|
|
|
430
438
|
},
|
|
431
439
|
"dateFormat": {
|
|
432
440
|
"iso": "ISO (yyyy-MM-dd)",
|
|
441
|
+
"ymd": "Năm / Tháng / Ngày (yyyy/MM/dd)",
|
|
433
442
|
"dmy": "Ngày / Tháng / Năm",
|
|
434
443
|
"mdy": "Tháng / Ngày / Năm"
|
|
435
444
|
},
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True while the viewport is at or below the width the theme token `token` names.
|
|
3
|
+
*
|
|
4
|
+
* The token is read once per mount rather than at module scope: the fallback is what SSR and the
|
|
5
|
+
* first client render agree on, and the themed value arrives in the effect. Same-value updates
|
|
6
|
+
* bail out inside React, so a theme that left the default alone re-renders nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useMaxWidthBreakpoint(token: string, fallbackQuery: string): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useMediaQuery } from "./hooks.js";
|
|
4
|
+
function cssLengthToPx(value, rootFontSize) {
|
|
5
|
+
const match = /^(-?\d*\.?\d+)(px|rem|em)?$/.exec(value.trim());
|
|
6
|
+
if (match == null) return void 0;
|
|
7
|
+
const amount = Number(match[1]);
|
|
8
|
+
if (!Number.isFinite(amount)) return void 0;
|
|
9
|
+
return match[2] === "rem" || match[2] === "em" ? amount * rootFontSize : amount;
|
|
10
|
+
}
|
|
11
|
+
function readMaxWidthQuery(token, fallbackQuery) {
|
|
12
|
+
if (typeof document === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
13
|
+
return fallbackQuery;
|
|
14
|
+
}
|
|
15
|
+
const rootStyle = window.getComputedStyle(document.documentElement);
|
|
16
|
+
const rootFontSize = cssLengthToPx(rootStyle.fontSize || "16px", 16) ?? 16;
|
|
17
|
+
const px = cssLengthToPx(rootStyle.getPropertyValue(token), rootFontSize);
|
|
18
|
+
return px == null ? fallbackQuery : `(max-width: ${String(px)}px)`;
|
|
19
|
+
}
|
|
20
|
+
function useMaxWidthBreakpoint(token, fallbackQuery) {
|
|
21
|
+
const [query, setQuery] = React.useState(fallbackQuery);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
setQuery(readMaxWidthQuery(token, fallbackQuery));
|
|
24
|
+
}, [token, fallbackQuery]);
|
|
25
|
+
return useMediaQuery(query);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
useMaxWidthBreakpoint
|
|
29
|
+
};
|
|
@@ -28,7 +28,7 @@ export type ProseProp = {
|
|
|
28
28
|
className?: ClassNameProp;
|
|
29
29
|
children?: ChildrenProp;
|
|
30
30
|
};
|
|
31
|
-
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
|
|
31
|
+
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, GetRowLabelProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
|
|
32
32
|
import type { TreeFieldNamesProp, TreeOptionProp } from "./data-entry.prop.js";
|
|
33
33
|
/**
|
|
34
34
|
* One key in a `Legend`: a tone, and the words that tone stands for.
|
|
@@ -47,6 +47,99 @@ export type LegendProp = Omit<React.HTMLAttributes<HTMLUListElement>, "children"
|
|
|
47
47
|
items: LegendItemProp[];
|
|
48
48
|
className?: ClassNameProp;
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* @see Swatch — a READ-ONLY sample of ONE colour a person chose.
|
|
52
|
+
*
|
|
53
|
+
* ## Why it is not a Legend, a ColorPicker or a Badge
|
|
54
|
+
*
|
|
55
|
+
* `Legend` is a KEY: a closed set of semantic TONES, each with required words beside it, because
|
|
56
|
+
* there the colour stands FOR something. Here the colour IS the content — a brand's
|
|
57
|
+
* `primary_color`, a calendar category, a label a user tinted — so there is no tone it maps to and
|
|
58
|
+
* no second thing for a word to name. `ColorPicker` is the INPUT for the same value, and rendering
|
|
59
|
+
* a disabled input to display one reads as a control that broke. `Badge` is a chip: tinted fill,
|
|
60
|
+
* border, clickable affordance — not a sample of the exact colour.
|
|
61
|
+
*
|
|
62
|
+
* Its mark is the same square Legend draws, one type step larger, because it stands beside a name
|
|
63
|
+
* rather than inside an 11px key.
|
|
64
|
+
*/
|
|
65
|
+
export type SwatchProp = Omit<React.HTMLAttributes<HTMLSpanElement>, "children" | "color"> & {
|
|
66
|
+
/**
|
|
67
|
+
* The colour to show, as a CSS colour VALUE — `#7C3AED`, `rgb(…)`, `oklch(…)`. It is DATA the
|
|
68
|
+
* same way `Badge`'s `color` is: a value a person picked in a settings screen, so it arrives as
|
|
69
|
+
* a prop and is never written into a stylesheet. Passing a semantic token's own colour here is
|
|
70
|
+
* the one thing to avoid — a tone that MEANS something belongs on a component that names the
|
|
71
|
+
* meaning (`Badge tone`, `Legend`), not on a sample.
|
|
72
|
+
*/
|
|
73
|
+
color: string;
|
|
74
|
+
/**
|
|
75
|
+
* The accessible NAME of the sample, and the reason this component can exist without a visible
|
|
76
|
+
* label. Say what the colour is FOR and what it is: `aria-label={`${t.primaryColor}: ${hex}`}`.
|
|
77
|
+
*
|
|
78
|
+
* With it, the swatch is a `role="img"` that announces that sentence. Without it the swatch is
|
|
79
|
+
* `aria-hidden` — correct, and the only correct option, when a visible line beside it already
|
|
80
|
+
* states the colour. Either way colour is never the sole carrier of the meaning (WCAG 1.4.1);
|
|
81
|
+
* what is NOT offered is a third path where it is.
|
|
82
|
+
*/
|
|
83
|
+
"aria-label"?: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Whether a `FeatureList` line is in, out, or in with a limit.
|
|
87
|
+
*
|
|
88
|
+
* Three members and no fourth: the axis is INCLUSION, and a list that also carried "coming soon",
|
|
89
|
+
* "beta" or "deprecated" would be encoding a roadmap on the same glyph column as a fact about
|
|
90
|
+
* today. Those are a `Badge` beside the label, which is content the screen already owns.
|
|
91
|
+
*/
|
|
92
|
+
export type FeatureStateProp = "included" | "excluded" | "limited";
|
|
93
|
+
/**
|
|
94
|
+
* One line of a `FeatureList`: what state it is in, what it is called, and — optionally — a
|
|
95
|
+
* sentence about it that WRAPS.
|
|
96
|
+
*
|
|
97
|
+
* There is deliberately no field for a quantity ("10,000 req/mo"). Composing it into `label` as
|
|
98
|
+
* `<>API calls <Text tone="muted" tabular>10,000 req/mo</Text></>` is already legal and already
|
|
99
|
+
* audit-clean, so a prop for it would fail question 1 of docs/WHAT-BELONGS-HERE.md — the consumer
|
|
100
|
+
* has a move. What the consumer did NOT have a move for is the glyph column and its alignment,
|
|
101
|
+
* which is what this component owns.
|
|
102
|
+
*/
|
|
103
|
+
export type FeatureItemProp = {
|
|
104
|
+
/** In, out, or in with a limit. Drives the glyph, its mark colour and the `sr-only` prefix. */
|
|
105
|
+
state: FeatureStateProp;
|
|
106
|
+
/** What the line is about. Wraps. */
|
|
107
|
+
label: LabelProp;
|
|
108
|
+
/** An optional muted sentence under the label. Wraps; long unbroken tokens break. */
|
|
109
|
+
description?: DescriptionProp;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* @see FeatureList — a list of statements, each with a leading state glyph: what a plan includes,
|
|
113
|
+
* what a tier supports, which requirements a submission met.
|
|
114
|
+
*/
|
|
115
|
+
export type FeatureListProp = Omit<React.HTMLAttributes<HTMLUListElement>, "children"> & {
|
|
116
|
+
items: FeatureItemProp[];
|
|
117
|
+
className?: ClassNameProp;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Fixed BLOCK size of a `Thumbnail`; the inline size stays intrinsic.
|
|
121
|
+
*
|
|
122
|
+
* A subset of the shared `SizeProp` ladder — `xs` is off the bottom because a 48px frame with a
|
|
123
|
+
* 1px hairline is a favicon, not a thumbnail, and nothing in the reported cases wanted one.
|
|
124
|
+
*/
|
|
125
|
+
export type ThumbnailSizeProp = Extract<SizeProp, "sm" | "md" | "lg">;
|
|
126
|
+
/**
|
|
127
|
+
* @see Thumbnail — a framed image at a FIXED HEIGHT and its own intrinsic width, for a wrapping
|
|
128
|
+
* row of pictures whose aspect ratios differ.
|
|
129
|
+
*/
|
|
130
|
+
export type ThumbnailProp = Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt"> & {
|
|
131
|
+
/** Image URL. */
|
|
132
|
+
src: string;
|
|
133
|
+
/**
|
|
134
|
+
* Required, with no way to omit it. Pass `""` for a picture that carries no information the
|
|
135
|
+
* page does not already say — the empty string is a DECISION the author has to make, where a
|
|
136
|
+
* missing attribute is an omission nobody notices (WCAG 1.1.1).
|
|
137
|
+
*/
|
|
138
|
+
alt: string;
|
|
139
|
+
/** Frame height. Default `md`. The width follows the picture's own ratio. */
|
|
140
|
+
size?: ThumbnailSizeProp;
|
|
141
|
+
className?: ClassNameProp;
|
|
142
|
+
};
|
|
50
143
|
/** @see EmptyState */
|
|
51
144
|
/**
|
|
52
145
|
* Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
|
|
@@ -255,6 +348,13 @@ export type DataTableProp<T> = {
|
|
|
255
348
|
data: T[];
|
|
256
349
|
columns: ColumnDefProp<T>[];
|
|
257
350
|
getRowId?: GetRowIdProp<T>;
|
|
351
|
+
/**
|
|
352
|
+
* Human name of a row, announced by its selection checkbox or radio as "Select row {label}".
|
|
353
|
+
* Default: the text of the `priority: "primary"` column, else of the first column, when that
|
|
354
|
+
* value is a string or number; the row id only as a last resort. `rowSelection.getCheckboxProps`
|
|
355
|
+
* `aria-label` still overrides a single row.
|
|
356
|
+
*/
|
|
357
|
+
getRowLabel?: GetRowLabelProp<T>;
|
|
258
358
|
selectable?: boolean;
|
|
259
359
|
selected?: SelectedIdsProp;
|
|
260
360
|
onSelectChange?: OnSelectChangeProp;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Feedback component prop types — @see docs/COMPONENTS.md#feedback */
|
|
2
2
|
import type * as React from "react";
|
|
3
3
|
import type { QueryErrorCategory } from "../../lib/query-error.js";
|
|
4
|
-
import type { AlertVariantProp, CancelLabelProp, ChildrenProp, ClassNameProp, ConfirmLabelProp, ConfirmVariantProp, DescriptionProp, HandlerProp, IconProp, OpenProp, OnOpenChangeProp, PendingProp, ToneProp, TitleProp } from "../vocabulary/index.js";
|
|
4
|
+
import type { AlertVariantProp, AvatarShapeProp, CancelLabelProp, ChildrenProp, ClassNameProp, ConfirmLabelProp, ConfirmVariantProp, DescriptionProp, HandlerProp, IconProp, OpenProp, OnOpenChangeProp, PendingProp, ShapeProp, SizeProp, ToneProp, TitleProp } from "../vocabulary/index.js";
|
|
5
5
|
/** @see AlertDialog */
|
|
6
6
|
export type AlertDialogProp = {
|
|
7
7
|
open: OpenProp;
|
|
@@ -83,3 +83,87 @@ export type SkeletonRowsProp = {
|
|
|
83
83
|
rows?: number;
|
|
84
84
|
columns?: number;
|
|
85
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* A skeleton line's MEASURE. `number` is read as pixels, matching antd's
|
|
88
|
+
* `SkeletonParagraphProps["width"]` (components/skeleton/Paragraph.tsx); a string is any CSS length
|
|
89
|
+
* or percentage. It reaches the DOM as the `--skeleton-line-width` custom property rather than a
|
|
90
|
+
* raw `width`, so the line still resolves its own block size and radius from the token tier.
|
|
91
|
+
*/
|
|
92
|
+
export type SkeletonWidth = number | string;
|
|
93
|
+
/**
|
|
94
|
+
* @see Skeleton — the placeholder BLOCK. Both fields are antd's, ported onto the block this
|
|
95
|
+
* library already shipped rather than onto a second component beside it.
|
|
96
|
+
*/
|
|
97
|
+
export type SkeletonProp = React.HTMLAttributes<HTMLDivElement> & {
|
|
98
|
+
/**
|
|
99
|
+
* Swap the resting pulse for the travelling SHEEN (antd's `active`). antd's non-active skeleton
|
|
100
|
+
* is fully static; this library's block has always pulsed, so the default is left alone and
|
|
101
|
+
* `active` selects the louder of the two motions instead of turning motion on.
|
|
102
|
+
*/
|
|
103
|
+
active?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* `false` renders `children` in place of the placeholder. An OMITTED `loading` still renders the
|
|
106
|
+
* placeholder — antd's `loading || !("loading" in props)`.
|
|
107
|
+
*/
|
|
108
|
+
loading?: boolean;
|
|
109
|
+
};
|
|
110
|
+
/** @see SkeletonAvatar */
|
|
111
|
+
export type SkeletonAvatarProp = {
|
|
112
|
+
/** Box, from the `--control-height` tier — the same tier the real `Avatar` sizes from. */
|
|
113
|
+
size?: SizeProp;
|
|
114
|
+
shape?: AvatarShapeProp;
|
|
115
|
+
active?: boolean;
|
|
116
|
+
className?: ClassNameProp;
|
|
117
|
+
};
|
|
118
|
+
/** @see SkeletonButton */
|
|
119
|
+
export type SkeletonButtonProp = {
|
|
120
|
+
size?: SizeProp;
|
|
121
|
+
/** Corner, in `Button`'s own vocabulary: `pill` is antd's `shape="round"`. */
|
|
122
|
+
shape?: ShapeProp;
|
|
123
|
+
/** Fill the inline axis, for a button that spans its column (antd's `block`). */
|
|
124
|
+
block?: boolean;
|
|
125
|
+
active?: boolean;
|
|
126
|
+
className?: ClassNameProp;
|
|
127
|
+
};
|
|
128
|
+
/** @see SkeletonInput */
|
|
129
|
+
export type SkeletonInputProp = {
|
|
130
|
+
size?: SizeProp;
|
|
131
|
+
block?: boolean;
|
|
132
|
+
active?: boolean;
|
|
133
|
+
className?: ClassNameProp;
|
|
134
|
+
};
|
|
135
|
+
/** @see SkeletonNode — a square standing in for a media/custom slot; `children` centres in it. */
|
|
136
|
+
export type SkeletonNodeProp = {
|
|
137
|
+
active?: boolean;
|
|
138
|
+
children?: ChildrenProp;
|
|
139
|
+
className?: ClassNameProp;
|
|
140
|
+
};
|
|
141
|
+
/** @see SkeletonImage */
|
|
142
|
+
export type SkeletonImageProp = {
|
|
143
|
+
active?: boolean;
|
|
144
|
+
className?: ClassNameProp;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* @see SkeletonArticle — antd's own `<Skeleton>` shape (avatar + title + paragraph). It is a
|
|
148
|
+
* SIBLING of `Skeleton`, not a replacement: `Skeleton` here is the block antd spells
|
|
149
|
+
* `Skeleton.Node`, and re-pointing it at the article would change what every existing call site
|
|
150
|
+
* renders.
|
|
151
|
+
*/
|
|
152
|
+
export type SkeletonArticleProp = {
|
|
153
|
+
avatar?: boolean | Pick<SkeletonAvatarProp, "size" | "shape">;
|
|
154
|
+
/** The heading line. NOT a string title — `false` drops the line, `{ width }` re-measures it. */
|
|
155
|
+
title?: boolean | {
|
|
156
|
+
width?: SkeletonWidth;
|
|
157
|
+
};
|
|
158
|
+
/** `width` as an array measures each row; as a single value it measures the LAST row. */
|
|
159
|
+
paragraph?: boolean | {
|
|
160
|
+
rows?: number;
|
|
161
|
+
width?: SkeletonWidth | SkeletonWidth[];
|
|
162
|
+
};
|
|
163
|
+
/** Pill corners on every line (antd's `round`, whose capsule radius is `--radius-pill` here). */
|
|
164
|
+
round?: boolean;
|
|
165
|
+
active?: boolean;
|
|
166
|
+
loading?: boolean;
|
|
167
|
+
children?: ChildrenProp;
|
|
168
|
+
className?: ClassNameProp;
|
|
169
|
+
};
|
|
@@ -185,6 +185,47 @@ export type FlexProp = React.HTMLAttributes<HTMLElement> & {
|
|
|
185
185
|
* only on the narrow side (a compact-only affordance). Omit for no attribute and no rule.
|
|
186
186
|
*/
|
|
187
187
|
hideFrom?: BreakpointProp;
|
|
188
|
+
/**
|
|
189
|
+
* ESCAPE HATCH: the same drop, at a width in PIXELS that is off every step of the scale — the
|
|
190
|
+
* `gapRaw`/`padRaw` contract applied to the breakpoint axis (gh#528).
|
|
191
|
+
*
|
|
192
|
+
* ## Why the token form stays the default
|
|
193
|
+
*
|
|
194
|
+
* An off-scale breakpoint is a LOCAL EXCEPTION, not a new tier. `sm`/`md`/`lg`/`xl` are the
|
|
195
|
+
* package's canonical steps, shared with `--master-detail-collapse-below` and with every
|
|
196
|
+
* `collapseBelow`, so two regions asked to fold "at the same place" actually fold together.
|
|
197
|
+
* A raw width folds one region and nothing else; spend the four named steps FIRST, and reach
|
|
198
|
+
* here only when the canonical design specifies a width the scale does not have (a nav that
|
|
199
|
+
* becomes a hamburger at 900px).
|
|
200
|
+
*
|
|
201
|
+
* ## The price, and why it is the right one
|
|
202
|
+
*
|
|
203
|
+
* It leaves `data-hide-below-raw` on the DOM, exactly like `data-gap-raw`/`data-pad-raw`, so
|
|
204
|
+
* every escape is COUNTABLE — grep the source or scan the DOM and a repo drifting off the scale
|
|
205
|
+
* shows up as a number instead of a feeling. Before this prop the only move left was
|
|
206
|
+
* `className="hidden min-[901px]:flex"`, which `ui-audit` blocks and which counts as nothing.
|
|
207
|
+
*
|
|
208
|
+
* It also prints ONE media rule per distinct width (a media query cannot read a `var()`, so an
|
|
209
|
+
* off-scale width can only reach CSS as a literal).
|
|
210
|
+
*
|
|
211
|
+
* ## The seam
|
|
212
|
+
*
|
|
213
|
+
* `hideBelowRaw` hides while `width < N`, `hideFromRaw` hides while `width >= N` — the SAME pair
|
|
214
|
+
* of comparisons as the token steps, so the two are exact complements and at exactly N the
|
|
215
|
+
* `hideBelowRaw` region is the visible one. Do NOT reach for an inclusive `<= N` here: pairing
|
|
216
|
+
* `<= N` with `>= N` leaves BOTH regions hidden at exactly N, which is the one-pixel hole a
|
|
217
|
+
* consumer measured on a hand-rolled `max-[900px]:` pair (gh#528).
|
|
218
|
+
*
|
|
219
|
+
* When present it WINS over `hideBelow`, which then emits no attribute, so the two cannot both
|
|
220
|
+
* match at one width.
|
|
221
|
+
*/
|
|
222
|
+
hideBelowRaw?: number;
|
|
223
|
+
/**
|
|
224
|
+
* The inverse of `hideBelowRaw` — drop the region FROM a raw pixel width upwards. Same contract,
|
|
225
|
+
* same price, same seam (`width >= N`); it wins over `hideFrom` and leaves `data-hide-from-raw`
|
|
226
|
+
* on the DOM.
|
|
227
|
+
*/
|
|
228
|
+
hideFromRaw?: number;
|
|
188
229
|
/**
|
|
189
230
|
* Take the space the siblings leave — the Flex becomes the row's ELASTIC column.
|
|
190
231
|
*
|
|
@@ -802,6 +843,21 @@ export type SidebarItemProp = {
|
|
|
802
843
|
* merely unread: an @mention, a direct message, a failure waiting on them.
|
|
803
844
|
*/
|
|
804
845
|
badgeTone?: SidebarBadgeToneProp;
|
|
846
|
+
/**
|
|
847
|
+
* TRAILING 16px glyph — the disclosure mark of a row that opens something (the `⌃⌄` of a
|
|
848
|
+
* workspace switcher, a `→` on a row that leaves the app). Pinned to the same box as `icon`,
|
|
849
|
+
* and hidden on the collapsed rail exactly like `badge`.
|
|
850
|
+
*
|
|
851
|
+
* It is NOT `badge`, and the difference is the pill: `badge` wraps whatever it is given in
|
|
852
|
+
* `.sb-badge` — a 9999px-radius `hsl(var(--secondary))` capsule sized for a COUNT — so a
|
|
853
|
+
* chevron passed there renders as a grey lozenge with a 24px SVG inside it (measured 36×24 with
|
|
854
|
+
* a 24×24 glyph, beside a 16×16 leading icon in the same 32px row). A glyph draws no surface,
|
|
855
|
+
* so it gets its own slot rather than a size tier on the count pill.
|
|
856
|
+
*
|
|
857
|
+
* Takes the COMPONENT, like `icon` — not an element. That is what lets the rail pin the size;
|
|
858
|
+
* a `ReactNode` hole is how `badge` ended up carrying an unsized 24px SVG.
|
|
859
|
+
*/
|
|
860
|
+
trailingIcon?: ComponentType<SVGProps<SVGSVGElement>>;
|
|
805
861
|
disabled?: boolean;
|
|
806
862
|
/**
|
|
807
863
|
* Destination of the row. It is the SOLE interactive element (no nested `<button>`), so
|
|
@@ -1164,7 +1220,22 @@ export type SidebarProp = {
|
|
|
1164
1220
|
* canonical row.
|
|
1165
1221
|
*/
|
|
1166
1222
|
renderItem?: (item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode;
|
|
1167
|
-
|
|
1223
|
+
/**
|
|
1224
|
+
* Footer slot pinned BELOW the scroll area — identity, status, a mode switch.
|
|
1225
|
+
*
|
|
1226
|
+
* Takes a FUNCTION for the same reason `brand` does, and it is the same reason twice because
|
|
1227
|
+
* both slots live INSIDE the collapsible rail: a plain node is built outside this component and
|
|
1228
|
+
* cannot see the EFFECTIVE collapsed state, so the consumer has no correct move. Reading their
|
|
1229
|
+
* own `collapsed` boolean renders a glyph-only footer inside the full-width drawer (`AppShell`
|
|
1230
|
+
* hands the same Sidebar to both surfaces and the drawer un-collapses); building a second
|
|
1231
|
+
* Sidebar for `AppShell.mobileNav` is the override that switches `railInDrawer` off; doing
|
|
1232
|
+
* neither leaves the expanded footer to reflow inside a 64px rail (measured: a two-line identity
|
|
1233
|
+
* block went 255×66 docked → 63×111 collapsed, wrapping the name across three lines).
|
|
1234
|
+
*
|
|
1235
|
+
* The function is called with the surface-effective value. A plain `ReactNode` still works
|
|
1236
|
+
* unchanged.
|
|
1237
|
+
*/
|
|
1238
|
+
footer?: ReactNode | ((collapsed: boolean) => ReactNode);
|
|
1168
1239
|
/** Override the nav landmark's accessible name. Defaults to a localized "Main navigation". */
|
|
1169
1240
|
"aria-label"?: string;
|
|
1170
1241
|
};
|