@godxjp/ui 18.15.3 → 19.0.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 +1 -1
- package/dist/app/time-format-labels.d.ts +1 -1
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/card.d.ts +4 -13
- package/dist/components/data-display/card.js +2 -15
- package/dist/components/data-display/data-table.d.ts +3 -8
- package/dist/components/data-display/data-table.js +106 -68
- package/dist/components/data-display/descriptions.js +34 -5
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/permission-matrix.js +7 -7
- package/dist/components/data-display/popover.js +2 -9
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/scroll-area.d.ts +3 -1
- package/dist/components/data-display/scroll-area.js +150 -16
- package/dist/components/data-display/table.js +12 -3
- package/dist/components/data-entry/branch-scope-picker.js +4 -4
- package/dist/components/data-entry/calendar.js +15 -24
- package/dist/components/data-entry/cascader.js +67 -71
- package/dist/components/data-entry/checkbox.js +9 -1
- package/dist/components/data-entry/command.d.ts +6 -6
- package/dist/components/data-entry/date-picker.js +28 -15
- package/dist/components/data-entry/date-range-picker.js +10 -10
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/index.d.ts +2 -1
- package/dist/components/data-entry/input.js +8 -19
- package/dist/components/data-entry/label.js +4 -2
- package/dist/components/data-entry/month-picker.js +16 -15
- package/dist/components/data-entry/month-range-picker.js +17 -16
- package/dist/components/data-entry/radio.js +6 -1
- package/dist/components/data-entry/search-select.js +29 -29
- package/dist/components/data-entry/select.js +15 -18
- package/dist/components/data-entry/switch.js +7 -1
- package/dist/components/data-entry/textarea.d.ts +56 -0
- package/dist/components/data-entry/textarea.js +103 -40
- package/dist/components/data-entry/time-picker.js +52 -52
- package/dist/components/data-entry/toggle-group.d.ts +1 -0
- package/dist/components/data-entry/toggle.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +14 -15
- package/dist/components/data-entry/tree-select.js +16 -22
- package/dist/components/data-entry/upload-crop-dialog.js +4 -4
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +46 -46
- package/dist/components/feedback/alert.js +7 -14
- package/dist/components/feedback/dialog.d.ts +23 -32
- package/dist/components/feedback/dialog.js +16 -13
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +14 -14
- package/dist/components/feedback/skeleton.js +13 -10
- package/dist/components/feedback/sonner.d.ts +1 -1
- package/dist/components/feedback/sonner.js +17 -8
- package/dist/components/feedback/tooltip.js +1 -1
- package/dist/components/general/activity.d.ts +44 -0
- package/dist/components/general/activity.js +65 -0
- package/dist/components/general/button.d.ts +2 -2
- package/dist/components/general/button.js +11 -24
- package/dist/components/general/index.d.ts +2 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/layout/app-shell.js +5 -3
- package/dist/components/layout/auth-divider.d.ts +9 -2
- package/dist/components/layout/auth-divider.js +7 -11
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -20
- package/dist/components/layout/resizable.d.ts +12 -12
- package/dist/components/layout/separator.d.ts +1 -0
- package/dist/components/layout/service-role-panel.js +47 -42
- package/dist/components/layout/sidebar.js +13 -1
- package/dist/components/layout/split-pane.d.ts +12 -1
- package/dist/components/layout/split-pane.js +13 -4
- package/dist/components/navigation/app-setting-picker.js +5 -14
- package/dist/components/navigation/dropdown-menu.js +10 -16
- package/dist/components/navigation/menubar.d.ts +3 -6
- package/dist/components/navigation/pagination-utils.d.ts +15 -2
- package/dist/components/navigation/pagination-utils.js +27 -21
- package/dist/components/navigation/pagination.d.ts +4 -4
- package/dist/components/navigation/pagination.js +2 -2
- package/dist/components/navigation/steps.js +31 -67
- package/dist/components/navigation/tabs.js +17 -5
- package/dist/components/query/infinite-query-state.js +2 -2
- package/dist/components/ui/accordion.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +29 -10
- package/dist/components/ui/input-otp.d.ts +8 -6
- package/dist/components/ui/rating.js +1 -1
- package/dist/components/ui/separator.d.ts +27 -2
- package/dist/components/ui/separator.js +42 -11
- package/dist/components/ui/toggle-group.d.ts +15 -5
- package/dist/components/ui/toggle-group.js +43 -18
- package/dist/components/ui/toggle.d.ts +66 -5
- package/dist/components/ui/toggle.js +64 -9
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +14 -0
- package/dist/lib/control-styles.js +2 -0
- package/dist/props/components/data-display.prop.d.ts +85 -3
- package/dist/props/components/data-entry.prop.d.ts +17 -0
- package/dist/props/components/general.prop.d.ts +52 -1
- package/dist/props/components/layout.prop.d.ts +137 -2
- package/dist/props/registry.d.ts +116 -5
- package/dist/props/registry.js +165 -4
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
- package/dist/props/vocabulary/layout.prop.d.ts +8 -0
- package/dist/styles/alert-layout.css +69 -3
- package/dist/styles/badge-layout.css +2 -2
- package/dist/styles/base.css +6 -0
- package/dist/styles/card-layout.css +27 -7
- package/dist/styles/control.css +1150 -29
- package/dist/styles/data-display-layout.css +185 -11
- package/dist/styles/data-entry-layout.css +352 -1
- package/dist/styles/density.css +2 -2
- package/dist/styles/dialog-layout.css +203 -4
- package/dist/styles/focus-ring.css +155 -0
- package/dist/styles/form-layout.css +50 -5
- package/dist/styles/index.css +1 -0
- package/dist/styles/layout.css +412 -23
- package/dist/styles/motion.css +178 -0
- package/dist/styles/navigation-layout.css +398 -22
- package/dist/styles/shell-layout.css +137 -39
- package/dist/styles/table-layout.css +153 -22
- package/dist/styles/toggle.css +59 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +64 -0
- package/dist/tokens/components/badge.css +7 -0
- package/dist/tokens/components/card.css +14 -10
- package/dist/tokens/components/chart.css +1 -1
- package/dist/tokens/components/control.css +358 -8
- package/dist/tokens/components/data-display.css +75 -0
- package/dist/tokens/components/data-entry.css +25 -0
- package/dist/tokens/components/descriptions.css +22 -0
- package/dist/tokens/components/feedback.css +143 -4
- package/dist/tokens/components/form.css +16 -0
- package/dist/tokens/components/legal-document.css +5 -1
- package/dist/tokens/components/navigation.css +109 -0
- package/dist/tokens/components/scroll-area.css +21 -0
- package/dist/tokens/components/separator.css +52 -0
- package/dist/tokens/components/shell.css +132 -20
- package/dist/tokens/components/table.css +52 -3
- package/dist/tokens/components/toggle.css +82 -0
- package/dist/tokens/components/upload.css +107 -0
- package/dist/tokens/foundation.css +171 -10
- package/dist/tokens/semantic/layout.css +120 -1
- package/package.json +84 -68
- package/scripts/ui-audit.mjs +51 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
|
5
5
|
import { cva } from "class-variance-authority";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
8
|
const toggleVariants = cva("ui-toggle", {
|
|
8
9
|
variants: {
|
|
9
10
|
variant: {
|
|
@@ -21,17 +22,71 @@ const toggleVariants = cva("ui-toggle", {
|
|
|
21
22
|
size: "md"
|
|
22
23
|
}
|
|
23
24
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
function useCounterPill({
|
|
26
|
+
count,
|
|
27
|
+
overflowCount = 99,
|
|
28
|
+
showZero = true,
|
|
29
|
+
countLabel,
|
|
30
|
+
ariaLabel
|
|
31
|
+
}) {
|
|
32
|
+
const { locale } = useTranslation();
|
|
33
|
+
const visible = count != null && (count !== 0 || showZero);
|
|
34
|
+
const formatted = React.useMemo(() => {
|
|
35
|
+
if (count == null) return "";
|
|
36
|
+
const format = new Intl.NumberFormat(locale);
|
|
37
|
+
return count > overflowCount ? `${format.format(overflowCount)}+` : format.format(count);
|
|
38
|
+
}, [count, locale, overflowCount]);
|
|
39
|
+
if (!visible) {
|
|
40
|
+
return { pill: null, resolvedAriaLabel: ariaLabel };
|
|
41
|
+
}
|
|
42
|
+
const spoken = countLabel ? `${formatted} ${countLabel}` : formatted;
|
|
43
|
+
return {
|
|
44
|
+
pill: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
45
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "toggle-count", className: "ui-toggle-count", "aria-hidden": "true", children: formatted }),
|
|
46
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${spoken}` })
|
|
47
|
+
] }),
|
|
48
|
+
resolvedAriaLabel: ariaLabel == null ? ariaLabel : `${ariaLabel}, ${spoken}`
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const Toggle = React.forwardRef(
|
|
52
|
+
({
|
|
53
|
+
className,
|
|
54
|
+
variant,
|
|
55
|
+
size,
|
|
56
|
+
count,
|
|
57
|
+
overflowCount,
|
|
58
|
+
showZero,
|
|
59
|
+
countLabel,
|
|
60
|
+
children,
|
|
61
|
+
"aria-label": ariaLabel,
|
|
30
62
|
...props
|
|
63
|
+
}, ref) => {
|
|
64
|
+
const { pill, resolvedAriaLabel } = useCounterPill({
|
|
65
|
+
count,
|
|
66
|
+
overflowCount,
|
|
67
|
+
showZero,
|
|
68
|
+
countLabel,
|
|
69
|
+
ariaLabel
|
|
70
|
+
});
|
|
71
|
+
return /* @__PURE__ */ jsxs(
|
|
72
|
+
TogglePrimitive.Root,
|
|
73
|
+
{
|
|
74
|
+
ref,
|
|
75
|
+
"data-slot": "toggle",
|
|
76
|
+
"aria-label": resolvedAriaLabel,
|
|
77
|
+
className: cn(toggleVariants({ variant, size }), className),
|
|
78
|
+
...props,
|
|
79
|
+
children: [
|
|
80
|
+
children,
|
|
81
|
+
pill
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
);
|
|
31
85
|
}
|
|
32
|
-
)
|
|
86
|
+
);
|
|
33
87
|
Toggle.displayName = TogglePrimitive.Root.displayName;
|
|
34
88
|
export {
|
|
35
89
|
Toggle,
|
|
36
|
-
toggleVariants
|
|
90
|
+
toggleVariants,
|
|
91
|
+
useCounterPill
|
|
37
92
|
};
|
|
@@ -213,6 +213,14 @@
|
|
|
213
213
|
}
|
|
214
214
|
},
|
|
215
215
|
"dataDisplay": {
|
|
216
|
+
"avatar": {
|
|
217
|
+
"presence": {
|
|
218
|
+
"online": "Online",
|
|
219
|
+
"away": "Away",
|
|
220
|
+
"busy": "Busy",
|
|
221
|
+
"offline": "Offline"
|
|
222
|
+
}
|
|
223
|
+
},
|
|
216
224
|
"carousel": {
|
|
217
225
|
"roleDescription": "carousel",
|
|
218
226
|
"ariaLabel": "Carousel",
|
|
@@ -207,6 +207,14 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dataDisplay": {
|
|
210
|
+
"avatar": {
|
|
211
|
+
"presence": {
|
|
212
|
+
"online": "オンライン",
|
|
213
|
+
"away": "離席中",
|
|
214
|
+
"busy": "取り込み中",
|
|
215
|
+
"offline": "オフライン"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
210
218
|
"carousel": {
|
|
211
219
|
"roleDescription": "カルーセル",
|
|
212
220
|
"ariaLabel": "カルーセル",
|
|
@@ -207,6 +207,14 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dataDisplay": {
|
|
210
|
+
"avatar": {
|
|
211
|
+
"presence": {
|
|
212
|
+
"online": "Trực tuyến",
|
|
213
|
+
"away": "Vắng mặt",
|
|
214
|
+
"busy": "Bận",
|
|
215
|
+
"offline": "Ngoại tuyến"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
210
218
|
"carousel": {
|
|
211
219
|
"roleDescription": "băng chuyền",
|
|
212
220
|
"ariaLabel": "Băng chuyền",
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/** Shared control sizing — reads `--control-height`, `--font-size-*` from density / theme. */
|
|
2
2
|
export declare const controlFieldClass = "ui-control w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
|
|
3
3
|
export declare const controlMultilineClass = "ui-control-multiline w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
|
|
4
|
+
/**
|
|
5
|
+
* Multiline control with its own chrome removed, for a textarea EMBEDDED in a surface that
|
|
6
|
+
* already draws the box — a chat composer inside a Card, an inline edit cell, a comment box in
|
|
7
|
+
* a panel. Two nested rounded borders is the tell that this was missing.
|
|
8
|
+
*
|
|
9
|
+
* These are utilities, not tokens, ON PURPOSE. `.ui-control-multiline` reads
|
|
10
|
+
* `--control-border-width` / `--control-shadow` from `@layer components`, but the default class
|
|
11
|
+
* ALSO carries Tailwind's `border` in `@layer utilities`, which wins whatever the token says —
|
|
12
|
+
* the same structural inertness gh#260 found on Badge's font size. So the variant cannot be a
|
|
13
|
+
* token override by a consumer; it has to drop the utilities here.
|
|
14
|
+
*
|
|
15
|
+
* The focus ring goes with them: the surface owns focus, via `focus-within` on the wrapper.
|
|
16
|
+
*/
|
|
17
|
+
export declare const controlMultilineGhostClass = "ui-control-multiline w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50";
|
|
4
18
|
/**
|
|
5
19
|
* Open-state ring for popup-style triggers (Select / Cascader / TreeSelect / SearchSelect).
|
|
6
20
|
* Radix moves focus INTO the popup on open, so the trigger loses `:focus-visible` and — without
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const controlFieldClass = "ui-control w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
|
|
2
2
|
const controlMultilineClass = "ui-control-multiline w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
|
|
3
|
+
const controlMultilineGhostClass = "ui-control-multiline w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50";
|
|
3
4
|
const controlOpenRingClass = "data-[state=open]:border-ring data-[state=open]:ring-ring/50 data-[state=open]:ring-[3px]";
|
|
4
5
|
const controlTriggerClass = "ui-control flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] border border-input bg-background shadow-sm transition-[color,box-shadow] focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 data-[state=open]:border-ring data-[state=open]:ring-ring/50 data-[state=open]:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1";
|
|
5
6
|
const controlIconClass = "size-[length:var(--control-height)] shrink-0";
|
|
@@ -21,6 +22,7 @@ export {
|
|
|
21
22
|
controlIconLeadingClass,
|
|
22
23
|
controlIconSmClass,
|
|
23
24
|
controlMultilineClass,
|
|
25
|
+
controlMultilineGhostClass,
|
|
24
26
|
controlOpenRingClass,
|
|
25
27
|
controlTriggerClass,
|
|
26
28
|
tableCellPaddingClass,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Data Display component prop types — @see docs/COMPONENTS.md#data-display */
|
|
2
2
|
import type * as React from "react";
|
|
3
|
-
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp } from "../vocabulary/index.js";
|
|
3
|
+
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp } from "../vocabulary/index.js";
|
|
4
4
|
/** @see EmptyState */
|
|
5
5
|
/**
|
|
6
6
|
* Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
|
|
@@ -39,10 +39,19 @@ export type EmptyStateProp = {
|
|
|
39
39
|
titleAs?: "h1" | "h2" | "h3" | "h4" | "p" | "div";
|
|
40
40
|
className?: ClassNameProp;
|
|
41
41
|
};
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* @see Descriptions — composed with `Descriptions.Item` CHILDREN, not an `items` array.
|
|
44
|
+
* This type had drifted from the component (it described a long-gone items-based API and was
|
|
45
|
+
* missing `layout`/`labelAlign` entirely, which the generated manifest already listed).
|
|
46
|
+
*/
|
|
43
47
|
export type DescriptionsProp = {
|
|
44
|
-
|
|
48
|
+
/** `Descriptions.Item` children — one label/value pair each. */
|
|
49
|
+
children: React.ReactNode;
|
|
45
50
|
columns?: 1 | 2 | 3;
|
|
51
|
+
/** Label placement within each item. Default `vertical` (label over value). */
|
|
52
|
+
layout?: DescriptionsLayoutProp;
|
|
53
|
+
/** Label text alignment inside the label column. Applies only to `layout="horizontal"`. */
|
|
54
|
+
labelAlign?: "start" | "end";
|
|
46
55
|
className?: ClassNameProp;
|
|
47
56
|
};
|
|
48
57
|
export type DescriptionsItemProp = {
|
|
@@ -63,9 +72,45 @@ export type DescriptionsItemProp = {
|
|
|
63
72
|
export type AvatarProp = React.ComponentPropsWithoutRef<"span"> & {
|
|
64
73
|
shape?: AvatarShapeProp;
|
|
65
74
|
appearance?: AvatarAppearanceProp;
|
|
75
|
+
/**
|
|
76
|
+
* Presence — WHO is reachable right now, drawn as an indicator at the block-end/inline-end
|
|
77
|
+
* corner of the mark with a localized `sr-only` label folded into the avatar's accessible text.
|
|
78
|
+
* Never colour alone (WCAG 1.4.1): each value also has its own silhouette (filled · half-filled ·
|
|
79
|
+
* barred · hollow).
|
|
80
|
+
*
|
|
81
|
+
* OMIT the prop entirely for an entity that has no presence concept (an organization mark, a
|
|
82
|
+
* capability medallion) — an absent prop emits no node and no attribute, so the DOM stays
|
|
83
|
+
* byte-identical to today's. Pass `"offline"` for a person KNOWN to be unreachable; that is a
|
|
84
|
+
* different statement, exactly as `ListRow`'s omitted vs `false` `unread` is.
|
|
85
|
+
*/
|
|
86
|
+
presence?: AvatarPresenceProp;
|
|
87
|
+
/**
|
|
88
|
+
* Override the localized presence text (`t("dataDisplay.avatar.presence.online")` …) when the
|
|
89
|
+
* product has a more precise phrasing ("In a meeting until 15:00"). Visually hidden either way —
|
|
90
|
+
* a presence dot never carries visible text; that is `Badge status`.
|
|
91
|
+
*/
|
|
92
|
+
presenceLabel?: LabelProp;
|
|
66
93
|
className?: ClassNameProp;
|
|
67
94
|
children?: ChildrenProp;
|
|
68
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* Avatar presence status — a person's realtime reachability.
|
|
98
|
+
*
|
|
99
|
+
* A DELIBERATELY separate vocabulary from the lifecycle `BadgeStatusProp`: presence is volatile,
|
|
100
|
+
* per-person and pushed over a socket, while a lifecycle status is a record's state and renders as
|
|
101
|
+
* a labelled chip. Each value is encoded twice over — a semantic role colour AND a shape — so the
|
|
102
|
+
* four are told apart in greyscale, by a deuteranope and under forced colors:
|
|
103
|
+
*
|
|
104
|
+
* - `"online"` — filled disc (`--success`).
|
|
105
|
+
* - `"away"` — half-filled disc (`--warning`).
|
|
106
|
+
* - `"busy"` — filled disc cut by a horizontal bar, the do-not-disturb mark (`--destructive`).
|
|
107
|
+
* - `"offline"` — hollow ring (`--muted-foreground`).
|
|
108
|
+
*
|
|
109
|
+
* Retune every constant with `--avatar-presence-*`.
|
|
110
|
+
*
|
|
111
|
+
* @see Avatar
|
|
112
|
+
*/
|
|
113
|
+
export type AvatarPresenceProp = "online" | "away" | "busy" | "offline";
|
|
69
114
|
/**
|
|
70
115
|
* Avatar fill treatment.
|
|
71
116
|
*
|
|
@@ -295,3 +340,40 @@ export type PermissionMatrixProp = {
|
|
|
295
340
|
className?: ClassNameProp;
|
|
296
341
|
id?: IdProp;
|
|
297
342
|
};
|
|
343
|
+
/**
|
|
344
|
+
* Edge the ScrollArea viewport sticks to as its content grows (gh#311).
|
|
345
|
+
*
|
|
346
|
+
* - `none` — the scroll offset is left entirely alone. This is the default and is exactly the
|
|
347
|
+
* behaviour a ScrollArea has always had.
|
|
348
|
+
* - `bottom` — a live stream (chat, log tail, streaming response, activity feed). While the reader
|
|
349
|
+
* is within `anchorOffset` of the bottom, arriving content keeps the newest item in view; once
|
|
350
|
+
* they scroll away to read history, growth NEVER moves them, and content inserted ABOVE the
|
|
351
|
+
* read position is compensated so the item under their eyes stays put.
|
|
352
|
+
*/
|
|
353
|
+
export type ScrollAreaAnchorProp = "none" | "bottom";
|
|
354
|
+
/** @see ScrollArea */
|
|
355
|
+
export type ScrollAreaProp = {
|
|
356
|
+
/**
|
|
357
|
+
* Ref to the element that actually SCROLLS — the Radix viewport — not the root. The root is
|
|
358
|
+
* `overflow: hidden` and never scrolls, so the component's own `ref` cannot serve. Use this to
|
|
359
|
+
* read `scrollTop`/`scrollHeight`, call `scrollTo()`, restore a saved position, or drive a
|
|
360
|
+
* "jump to newest" button. It is the typed, supported alternative to querying the Radix-internal
|
|
361
|
+
* `[data-radix-scroll-area-viewport]` attribute, which is not a public contract and is ambiguous
|
|
362
|
+
* the moment two ScrollAreas nest.
|
|
363
|
+
*/
|
|
364
|
+
viewportRef?: React.Ref<HTMLDivElement>;
|
|
365
|
+
/** Edge the viewport sticks to as content grows. Default `none` (inert). */
|
|
366
|
+
anchor?: ScrollAreaAnchorProp;
|
|
367
|
+
/**
|
|
368
|
+
* Distance in px from the bottom edge inside which the reader still counts as "at the bottom"
|
|
369
|
+
* for `anchor="bottom"`. Defaults to the `--scroll-area-anchor-offset` token (3rem), read off
|
|
370
|
+
* the element at mount so a theme — or a `[data-tenant]` scope — moves it globally.
|
|
371
|
+
*/
|
|
372
|
+
anchorOffset?: number;
|
|
373
|
+
/**
|
|
374
|
+
* Fires when the pinned state flips: `false` when the reader scrolls away from the bottom,
|
|
375
|
+
* `true` when they come back inside `anchorOffset`. Render a focusable "jump to newest" button
|
|
376
|
+
* from it — anchoring must never be the only route back to new content.
|
|
377
|
+
*/
|
|
378
|
+
onAnchoredChange?: (anchored: boolean) => void;
|
|
379
|
+
};
|
|
@@ -40,6 +40,14 @@ export type TextareaProp = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
|
40
40
|
allowClear?: boolean;
|
|
41
41
|
/** Called after the field is cleared via the inline ✕. */
|
|
42
42
|
onClear?: () => void;
|
|
43
|
+
/** `ghost` drops the field's own border/background/ring for a textarea inside a surface that already draws the box. */
|
|
44
|
+
variant?: "default" | "ghost";
|
|
45
|
+
/** Grow the box with its content between `minRows` and `maxRows` instead of a fixed `rows` height (default false). */
|
|
46
|
+
autoGrow?: boolean;
|
|
47
|
+
/** Floor in text rows while `autoGrow`; never undercuts the `--control-height` tier. */
|
|
48
|
+
minRows?: number;
|
|
49
|
+
/** Ceiling in text rows while `autoGrow` — past it the control scrolls internally. `0` = unbounded. */
|
|
50
|
+
maxRows?: number;
|
|
43
51
|
};
|
|
44
52
|
/**
|
|
45
53
|
* @see NumberInput — localized numeric spinbutton (composes `Input` + step `Button`s).
|
|
@@ -529,6 +537,15 @@ export type UploadProp = FieldA11yProps & {
|
|
|
529
537
|
* @see Button — same size scale
|
|
530
538
|
*/
|
|
531
539
|
triggerSize?: "default" | "md" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
|
|
540
|
+
/**
|
|
541
|
+
* `variant="button"` only — the visual weight of the visible trigger, forwarded to Button.
|
|
542
|
+
* Defaults to `outline`, which is right for a standalone form field. Pass `ghost` when the
|
|
543
|
+
* trigger sits in a toolbar row beside other icon buttons — inside a chat composer, say —
|
|
544
|
+
* where a bordered square reads as the odd one out.
|
|
545
|
+
*
|
|
546
|
+
* @see Button — same variant scale
|
|
547
|
+
*/
|
|
548
|
+
triggerVariant?: "default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link";
|
|
532
549
|
className?: ClassNameProp;
|
|
533
550
|
children?: React.ReactNode;
|
|
534
551
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
|
|
2
2
|
import type * as React from "react";
|
|
3
|
-
import type { AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
|
|
3
|
+
import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, LabelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
|
|
4
4
|
/** @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`. */
|
|
5
5
|
export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
|
|
6
6
|
/** Render element. Default `span`. Covers inline/block text + description-list + code/caption. */
|
|
@@ -100,3 +100,54 @@ export type RevealProp = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
100
100
|
asChild?: AsChildProp;
|
|
101
101
|
className?: ClassNameProp;
|
|
102
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* @see Activity — the official AMBIENT-motion primitive (a continuous, unbounded "in progress"),
|
|
105
|
+
* the LOOP counterpart to `Reveal`'s one-shot entrance. Reads the DS motion tokens
|
|
106
|
+
* (`--activity-interval`, `--activity-stagger-step`, `--ease-standard`, `--activity-mark-offset`)
|
|
107
|
+
* so a consumer never hand-rolls a looping `@keyframes`. Under `prefers-reduced-motion` the loop is
|
|
108
|
+
* dropped and the mark renders in a static, fully-visible resting state — three solid dots, a solid
|
|
109
|
+
* pulse mark, or a bar segment parked at the reading-start — with no layout shift, the same
|
|
110
|
+
* guarantee `Reveal` gives.
|
|
111
|
+
*
|
|
112
|
+
* NOT `Skeleton` (content is loading — `aria-busy` + an unconditional live region), NOT
|
|
113
|
+
* `Button loading` (this action is in flight). `Activity` means: something is happening,
|
|
114
|
+
* indefinitely, elsewhere — someone typing, a sync running, a response streaming, a recording live.
|
|
115
|
+
*
|
|
116
|
+
* There is deliberately no `asChild`: unlike `Reveal` (which owns no DOM), `Activity` renders its
|
|
117
|
+
* own mark structure, so there is no single child to merge onto.
|
|
118
|
+
*/
|
|
119
|
+
export type ActivityProp = Omit<React.HTMLAttributes<HTMLSpanElement>, "color"> & {
|
|
120
|
+
/**
|
|
121
|
+
* The mark. Default `dots` — three dots rising in sequence, the ellipsis convention.
|
|
122
|
+
* `pulse` is a single breathing mark (live / recording); `bar` an indeterminate sweep (syncing).
|
|
123
|
+
*/
|
|
124
|
+
variant?: ActivityVariantProp;
|
|
125
|
+
/**
|
|
126
|
+
* Size step, the standard ladder. Default `sm` — an ambient mark is never the loudest thing on
|
|
127
|
+
* screen. Scales the mark AND the label together (the mark is `em`-based).
|
|
128
|
+
*/
|
|
129
|
+
size?: SizeProp;
|
|
130
|
+
/** Semantic colour intent. Default `muted` — ambient, not an alert. */
|
|
131
|
+
tone?: TextToneProp;
|
|
132
|
+
/**
|
|
133
|
+
* Localized description of WHAT is happening ("Hưng đang nhập…", "同期中…"). Rendered as visible
|
|
134
|
+
* `Text` beside the mark when `children` are absent; when `children` ARE present it becomes an
|
|
135
|
+
* `sr-only` description instead, so the indicator is never animation-only. Consumer-owned copy —
|
|
136
|
+
* the library never invents it, and the plural form of "N people are typing" must come from the
|
|
137
|
+
* consumer's `Intl.PluralRules`.
|
|
138
|
+
*/
|
|
139
|
+
label?: LabelProp;
|
|
140
|
+
/**
|
|
141
|
+
* Richer visible content in place of `label` (a name in a `<strong>`, a `Badge`, …). The mark
|
|
142
|
+
* stays `aria-hidden`; pass `label` alongside for the sr-only description.
|
|
143
|
+
*/
|
|
144
|
+
children?: ChildrenProp;
|
|
145
|
+
/**
|
|
146
|
+
* Announce the label to assistive technology. Default `false` — the DELIBERATE default, because
|
|
147
|
+
* an ambient indicator that fires a live region on every socket event is a screen-reader flood.
|
|
148
|
+
* With `false` NO live region is emitted at all. `"polite"` wraps ONLY the label in a single
|
|
149
|
+
* `aria-live="polite" aria-atomic="true"` region; the mark stays outside it and `aria-hidden`.
|
|
150
|
+
*/
|
|
151
|
+
announce?: ActivityAnnounceProp;
|
|
152
|
+
className?: ClassNameProp;
|
|
153
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Layout component prop types — @see docs/COMPONENTS.md#layout */
|
|
2
2
|
import type * as React from "react";
|
|
3
3
|
import type { ComponentType, ReactNode, SVGProps } from "react";
|
|
4
|
-
import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp } from "../vocabulary/index.js";
|
|
4
|
+
import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp } from "../vocabulary/index.js";
|
|
5
5
|
import type { EmptyStateToneProp } from "./data-display.prop.js";
|
|
6
6
|
/**
|
|
7
7
|
* Arrangement of the page header's title band and its `extra` slot below the 640px step.
|
|
@@ -24,6 +24,22 @@ export type PageContainerPresetProp = "default" | "admin-collection";
|
|
|
24
24
|
* fluid at the compact gutter.
|
|
25
25
|
*/
|
|
26
26
|
export type PageContainerMeasureProp = "default" | "narrow" | "medium";
|
|
27
|
+
/**
|
|
28
|
+
* What the page's top row IS — the question that decides its type step, not how big you want it.
|
|
29
|
+
*
|
|
30
|
+
* `document` (default) — the row is the page's TITLE: a record, a form, a collection, a report.
|
|
31
|
+
* The `<h1>` takes `--page-title-font-size` and the existing responsive step down at 720px.
|
|
32
|
+
*
|
|
33
|
+
* `chrome` — the row is the surface's own furniture: a chat channel name, a mail subject line, an
|
|
34
|
+
* IDE tab, a conversation header. It names the thing you are already inside rather than announcing
|
|
35
|
+
* a document, so it takes the body type step (`--page-title-font-size-chrome`) and the header band
|
|
36
|
+
* stops competing with the content underneath. Orthogonal to `PageContainerVariantProp`: `ghost`
|
|
37
|
+
* owns the page's chrome WEIGHT (no divider, no header bottom pad), this owns what the title MEANS
|
|
38
|
+
* — a chat page usually wants both, a quiet document feed wants only `ghost`.
|
|
39
|
+
*
|
|
40
|
+
* The `<h1>` stays an `<h1>` either way; only the type step moves, and only via a token.
|
|
41
|
+
*/
|
|
42
|
+
export type PageContainerHeaderScaleProp = "document" | "chrome";
|
|
27
43
|
/** @see PageContainer */
|
|
28
44
|
export type PageContainerProp = {
|
|
29
45
|
title: TitleProp;
|
|
@@ -47,6 +63,18 @@ export type PageContainerProp = {
|
|
|
47
63
|
*/
|
|
48
64
|
headerLoading?: boolean;
|
|
49
65
|
extra?: ExtraProp;
|
|
66
|
+
/**
|
|
67
|
+
* FIXED chrome band between the page header and the scrolling body — a filter strip, a status
|
|
68
|
+
* bar, a "channel workflow" rail. It is a first-class page-chrome slot precisely because the
|
|
69
|
+
* only alternative was hand-laying `position: sticky` at the call site (which the design system
|
|
70
|
+
* forbids) or putting the strip inside the body, where it scrolls away. Under `fill` the body
|
|
71
|
+
* IS the scroll viewport, so this band is a plain `flex: none` sibling OUTSIDE it — content can
|
|
72
|
+
* never travel underneath it the way it does under a sticky box. Shares the page gutters and
|
|
73
|
+
* the `measure` cap with the header and the body, so the three bands line up on both edges; its
|
|
74
|
+
* inset and bottom rule are token-owned (`--page-toolbar-pad-block` / `--page-toolbar-divider`).
|
|
75
|
+
* Omit it and NOTHING is rendered — no wrapper element, no gap.
|
|
76
|
+
*/
|
|
77
|
+
toolbar?: ReactNode;
|
|
50
78
|
footer?: FooterProp;
|
|
51
79
|
breadcrumb?: BreadcrumbProp;
|
|
52
80
|
/**
|
|
@@ -75,6 +103,37 @@ export type PageContainerProp = {
|
|
|
75
103
|
* `--page-header-extra-measure`. At >=640px both arrangements are identical.
|
|
76
104
|
*/
|
|
77
105
|
headerLayout?: PageContainerHeaderLayoutProp;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the page's top row is a DOCUMENT TITLE or the surface's own CHROME. Defaults to
|
|
108
|
+
* `document` — the historical page, byte-identical (no attribute is emitted at all). Pass
|
|
109
|
+
* `chrome` when the row names the thing the user is already inside rather than announcing a
|
|
110
|
+
* document: a chat channel, a mail thread, an IDE tab. The `<h1>` then takes the body type step
|
|
111
|
+
* (`--page-title-font-size-chrome`) at EVERY width — including below 720px, where the
|
|
112
|
+
* document-scale responsive step would otherwise pull it back UP — so the header band stops
|
|
113
|
+
* eating the height the conversation needs (measured in a consumer chat page: a 61px band with a
|
|
114
|
+
* 24px name, against a design that wanted ~40px at the `sm` step).
|
|
115
|
+
*
|
|
116
|
+
* The same answer also puts the band ON the frame's edge: the container's block-start padding
|
|
117
|
+
* becomes `--page-pad-block-start-chrome` (0) instead of `--space-page-active-y`, because a
|
|
118
|
+
* document title needs air above it and a channel head IS the top edge (measured on a consumer
|
|
119
|
+
* chat screen as 24px that pushed the head off y=0 and came off the transcript viewport). The
|
|
120
|
+
* page's bottom edge is untouched, that being `stickyFooter`'s.
|
|
121
|
+
*
|
|
122
|
+
* It carries two more consequences of the same fact. The SUBTITLE drops to
|
|
123
|
+
* `--page-subtitle-font-size-chrome` (`--font-size-2xs`, ≈11px): a caption on chrome, not a
|
|
124
|
+
* document's standfirst, and at the document step it was rendering at the very same size as the
|
|
125
|
+
* chrome title, which is not a hierarchy. And the `extra` cluster CENTRES on the bar
|
|
126
|
+
* (`align-self: center`) wherever the header row is a row (>=640px), because top-packing actions
|
|
127
|
+
* is a document behaviour — they belong on the first line of a tall `<h1>` — and a bar has no
|
|
128
|
+
* tall heading to align to. Measured on a consumer chat screen, that was 8.65px: 28px icon
|
|
129
|
+
* buttons pinned at y=14 inside a 45.3px row whose title block centred at y=22.65.
|
|
130
|
+
*
|
|
131
|
+
* The heading stays an `<h1>` throughout, so the screen-reader outline is unchanged. Compose it
|
|
132
|
+
* with `variant="ghost"` for the full quiet chrome header (ghost drops the divider and the
|
|
133
|
+
* header's bottom pad); the two are separate props because chrome WEIGHT and what the title
|
|
134
|
+
* MEANS are separate questions.
|
|
135
|
+
*/
|
|
136
|
+
headerScale?: PageContainerHeaderScaleProp;
|
|
78
137
|
/**
|
|
79
138
|
* Bounded page measure shared by the header and the body. Defaults to `default` — no cap, the
|
|
80
139
|
* historical fluid page. `narrow` (624px surface) / `medium` (720px surface) cap BOTH bands to
|
|
@@ -314,7 +373,52 @@ export type AuthShellProp = {
|
|
|
314
373
|
density?: "comfortable" | "compact";
|
|
315
374
|
className?: ClassNameProp;
|
|
316
375
|
};
|
|
317
|
-
/**
|
|
376
|
+
/**
|
|
377
|
+
* @see Separator — the tokenized rule, optionally INTERRUPTED by a localized label.
|
|
378
|
+
*
|
|
379
|
+
* With no `label` the DOM, the `data-slot="separator"` and the `.ui-separator` class are exactly
|
|
380
|
+
* what they have always been: an inert Radix rule, `decorative` by default, nothing announced.
|
|
381
|
+
* With a `label` the root becomes the three-cell grid `rule · label · rule` and, because
|
|
382
|
+
* "new messages" is CONTENT rather than decoration, a real `role="separator"` carrying the label
|
|
383
|
+
* as its accessible name (gh#308). Every constant — rule weight, label gap, label inset, label
|
|
384
|
+
* type ramp, rule and label colour per tone — is a `--separator-*` component token (rules #44/#45),
|
|
385
|
+
* so a service retunes a day divider or an unread watermark from its theme and never forks CSS.
|
|
386
|
+
*/
|
|
387
|
+
export type SeparatorProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
388
|
+
/** Rule axis. Default `"horizontal"`. */
|
|
389
|
+
orientation?: OrientationProp;
|
|
390
|
+
/**
|
|
391
|
+
* Localized text that INTERRUPTS the rule — a day divider, a "new messages" watermark, an auth
|
|
392
|
+
* conjunction. The rule splits into two halves around it and the grid keeps the label optically
|
|
393
|
+
* placed regardless of translation length. `horizontal` only: with `orientation="vertical"` the
|
|
394
|
+
* label is ignored and dev builds warn. Omit for a plain rule.
|
|
395
|
+
*
|
|
396
|
+
* A `string`, not the `LabelProp` ReactNode, because this text IS the separator's accessible
|
|
397
|
+
* name — a node cannot be announced. The library never invents the copy: the consumer passes a
|
|
398
|
+
* `t()` string, and a date is formatted with `Intl.DateTimeFormat` on the active locale.
|
|
399
|
+
*/
|
|
400
|
+
label?: string;
|
|
401
|
+
/**
|
|
402
|
+
* Where the label sits on the rule. Default `"center"` (the classic conjunction). `"start"` is
|
|
403
|
+
* the Slack/Mattermost stream convention — the label hugs the inline-start edge with the long
|
|
404
|
+
* half of the rule running to the inline-end. Logical, so it flips under `dir="rtl"`.
|
|
405
|
+
*/
|
|
406
|
+
labelAlign?: TextAlignProp;
|
|
407
|
+
/**
|
|
408
|
+
* Semantic emphasis of the label AND the rule together — `"default"` is the quiet chrome (#44);
|
|
409
|
+
* a semantic role marks an attention rule such as an unread watermark. Never colour-only: the
|
|
410
|
+
* tone re-points both halves, so the distinction survives forced-colors. Default `"default"`.
|
|
411
|
+
*/
|
|
412
|
+
tone?: TextToneProp;
|
|
413
|
+
/**
|
|
414
|
+
* `true` (the default for an UNLABELLED rule) keeps Radix's decorative behaviour — `role="none"`,
|
|
415
|
+
* nothing announced. A `label` flips the default to `false`, so the rule becomes a real
|
|
416
|
+
* `role="separator"` named by the label. Pass it explicitly to override either default.
|
|
417
|
+
*/
|
|
418
|
+
decorative?: boolean;
|
|
419
|
+
className?: ClassNameProp;
|
|
420
|
+
};
|
|
421
|
+
/** @see AuthDivider — the auth-scoped preset over `Separator label` (gh#308). */
|
|
318
422
|
export type AuthDividerProp = {
|
|
319
423
|
/** Short localized conjunction rendered between the two separator rules (for example, "or"). */
|
|
320
424
|
label: string;
|
|
@@ -528,6 +632,22 @@ export type SidebarProductProp = {
|
|
|
528
632
|
role?: string;
|
|
529
633
|
color?: string;
|
|
530
634
|
};
|
|
635
|
+
/**
|
|
636
|
+
* What a nav row's count MEANS — a subset of the shared `ToneProp` vocabulary, not a palette.
|
|
637
|
+
*
|
|
638
|
+
* `neutral` (default) — a plain count: unread items, pending rows, queued jobs. The pill keeps the
|
|
639
|
+
* quiet `--sidebar-badge-background` / `-foreground` pair it always had.
|
|
640
|
+
*
|
|
641
|
+
* `destructive` — the count is ADDRESSED TO THE USER and the rail should pull the eye: an
|
|
642
|
+
* @mention, a direct message, a failing job awaiting them. Reads the
|
|
643
|
+
* `--sidebar-badge-destructive-*` pair.
|
|
644
|
+
*
|
|
645
|
+
* Deliberately TWO values, not the whole `ToneProp` union: a navigation rail answers one question
|
|
646
|
+
* about a count — "does this need me personally?" — and a five-colour rail is decoration, not
|
|
647
|
+
* information. Colour ONLY: the pill's geometry is shared, so a mention row and an unread row stay
|
|
648
|
+
* aligned in the same column.
|
|
649
|
+
*/
|
|
650
|
+
export type SidebarBadgeToneProp = Extract<ToneProp, "neutral" | "destructive">;
|
|
531
651
|
/** @see Sidebar */
|
|
532
652
|
export type SidebarItemProp = {
|
|
533
653
|
id: string;
|
|
@@ -540,7 +660,22 @@ export type SidebarItemProp = {
|
|
|
540
660
|
* `--sidebar-nav-icon-foreground` (see {@link SidebarProp}).
|
|
541
661
|
*/
|
|
542
662
|
icon: ComponentType<SVGProps<SVGSVGElement>>;
|
|
663
|
+
/**
|
|
664
|
+
* Count/status affix rendered in the row's `.sb-badge` pill. Pass the CONTENT ONLY — a number, a
|
|
665
|
+
* string, `"9+"`. Never a `<Badge>`: the row already IS a badge, so nesting one produces two
|
|
666
|
+
* stacked pills (measured: a 37.11×19.14 `.sb-badge` wrapping a 25.11×19.14 `<Badge>` with its
|
|
667
|
+
* own border). To change what the count MEANS, use {@link SidebarItemProp.badgeTone}.
|
|
668
|
+
*/
|
|
543
669
|
badge?: ReactNode;
|
|
670
|
+
/**
|
|
671
|
+
* Emphasis of `badge`. Defaults to `neutral` — the historical pill, byte-identical (no attribute
|
|
672
|
+
* is emitted at all). Pass `destructive` when the count is addressed to the user rather than
|
|
673
|
+
* merely unread: an @mention, a direct message, a failure waiting on them. It moves two colour
|
|
674
|
+
* tokens and nothing else, so mention rows and unread rows still line up.
|
|
675
|
+
*
|
|
676
|
+
* Ignored when `badge` is absent, and on the collapsed rail (which hides `.sb-badge` entirely).
|
|
677
|
+
*/
|
|
678
|
+
badgeTone?: SidebarBadgeToneProp;
|
|
544
679
|
disabled?: boolean;
|
|
545
680
|
/**
|
|
546
681
|
* Destination of the row. It is the SOLE interactive element (no nested `<button>`), so
|