@crisp-ui-kit/crisp 0.23.0 → 0.23.2
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/cjs/components/accordion/accordion.js +8 -2
- package/dist/cjs/components/datepicker/datepicker.js +4 -2
- package/dist/cjs/components/menu/menu.js +12 -1
- package/dist/cjs/components/popover/popover.js +12 -1
- package/dist/cjs/components/tabs/tabs.js +8 -2
- package/dist/cjs/components/tooltip/tooltip.js +20 -2
- package/dist/cjs/product/command/command.js +1 -1
- package/dist/cjs/product/command/command.recipe.js +6 -0
- package/dist/command.css +6 -0
- package/dist/esm/components/accordion/accordion.js +8 -2
- package/dist/esm/components/datepicker/datepicker.js +4 -2
- package/dist/esm/components/menu/menu.js +12 -1
- package/dist/esm/components/popover/popover.js +12 -1
- package/dist/esm/components/tabs/tabs.js +8 -2
- package/dist/esm/components/tooltip/tooltip.js +20 -2
- package/dist/esm/product/command/command.js +1 -1
- package/dist/esm/product/command/command.recipe.js +6 -0
- package/dist/styles.css +6 -0
- package/dist/types/components/tooltip/tooltip.d.ts +6 -1
- package/package.json +1 -1
|
@@ -45,6 +45,7 @@ const accordion_recipe_1 = require("./accordion.recipe");
|
|
|
45
45
|
*/
|
|
46
46
|
const toSet = (v) => new Set(v == null ? [] : Array.isArray(v) ? v : [v]);
|
|
47
47
|
function Accordion({ items, multiple, value, defaultValue, onValueChange, className, ...rest }) {
|
|
48
|
+
const uid = React.useId();
|
|
48
49
|
const [internal, setInternal] = React.useState(() => toSet(defaultValue));
|
|
49
50
|
// `value` present means the caller owns the open set; our own state is then
|
|
50
51
|
// never read, so a click can only change what is shown by going through
|
|
@@ -62,8 +63,13 @@ function Accordion({ items, multiple, value, defaultValue, onValueChange, classN
|
|
|
62
63
|
};
|
|
63
64
|
return ((0, jsx_runtime_1.jsx)("div", { className: (0, accordion_recipe_1.accordionRecipe)({ className }), ...rest, children: items.map((item) => {
|
|
64
65
|
const isOpen = open.has(item.value);
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
// Scoped to this instance. `accordion-panel-${value}` is global, so two
|
|
67
|
+
// accordions with an item of the same value on one page mint the same
|
|
68
|
+
// id twice — and getElementById returns the first, so the second
|
|
69
|
+
// accordion's triggers silently control the first one's panels (#227).
|
|
70
|
+
const panelId = `${uid}-panel-${item.value}`;
|
|
71
|
+
const triggerId = `${uid}-trigger-${item.value}`;
|
|
72
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "accordion-item", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", id: triggerId, className: "accordion-trigger", "aria-expanded": isOpen, "aria-controls": panelId, onClick: () => toggle(item.value), children: [(0, jsx_runtime_1.jsx)("span", { className: "accordion-title", children: item.title }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "accordion-chevron", size: 16, strokeWidth: 1.75, "aria-hidden": "true" })] }), (0, jsx_runtime_1.jsx)("div", { id: panelId, className: "accordion-panel", role: "region", "aria-labelledby": triggerId, "data-open": isOpen ? "" : undefined, hidden: !isOpen, children: (0, jsx_runtime_1.jsx)("div", { className: "accordion-panel-inner", children: (0, jsx_runtime_1.jsx)("div", { className: "accordion-content", children: item.content }) }) })] }, item.value));
|
|
67
73
|
}) }));
|
|
68
74
|
}
|
|
69
75
|
Accordion.displayName = "Accordion";
|
|
@@ -60,7 +60,9 @@ function Header({ api }) {
|
|
|
60
60
|
// in RTL the chevrons swap: "previous" is on the right
|
|
61
61
|
const previousDirection = api.isRtl ? "right" : "left";
|
|
62
62
|
const nextDirection = api.isRtl ? "left" : "right";
|
|
63
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "datepicker-header", children: [(0, jsx_runtime_1.jsxs)("button", { ...api.monthYearButtonProps,
|
|
63
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "datepicker-header", children: [(0, jsx_runtime_1.jsxs)("button", { ...api.monthYearButtonProps, "aria-controls": api.isWheelOpen
|
|
64
|
+
? api.monthYearButtonProps["aria-controls"]
|
|
65
|
+
: undefined, type: "button", id: `${api.rootProps.id}-header-label`, className: "datepicker-header-label", children: [(0, jsx_runtime_1.jsx)("span", { "data-date-picker-header-label": "", children: api.headerLabel }), (0, jsx_runtime_1.jsx)("span", { className: "datepicker-header-chevron", "data-open": api.isWheelOpen ? "" : undefined, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "datepicker-nav", children: [(0, jsx_runtime_1.jsx)(NavigationButton, { ...api.previousButtonProps, direction: previousDirection, className: "datepicker-nav-button" }), (0, jsx_runtime_1.jsx)(NavigationButton, { ...api.nextButtonProps, direction: nextDirection, className: "datepicker-nav-button" })] })] }));
|
|
64
66
|
}
|
|
65
67
|
function TwoMonthHeader({ api, label, labelId, position, }) {
|
|
66
68
|
const isFirst = position === "first";
|
|
@@ -83,7 +85,7 @@ function MonthView({ api, month, renderDateCellContent, renderDateCellSupplement
|
|
|
83
85
|
: labelId, className: "datepicker-grid", children: [showWeekdays && (0, jsx_runtime_1.jsx)(WeekdayRow, { api: api }), month.weeks.map((week) => ((0, jsx_runtime_1.jsx)("div", { role: "row", className: "datepicker-week-row", children: week.cells.map((cell, index) => ((0, jsx_runtime_1.jsx)(DateCellView, { cell: cell, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement }, cell?.key ?? `${week.key}-empty-${index}`))) }, week.key)))] })] }));
|
|
84
86
|
}
|
|
85
87
|
function WheelView({ api }) {
|
|
86
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "datepicker-wheel-container", children: (0, jsx_runtime_1.jsx)(wheelpicker_1.WheelPicker, { className: "datepicker-wheel", "aria-label": api.headerLabel, columns: [
|
|
88
|
+
return ((0, jsx_runtime_1.jsx)("div", { ...api.wheelProps, className: "datepicker-wheel-container", children: (0, jsx_runtime_1.jsx)(wheelpicker_1.WheelPicker, { className: "datepicker-wheel", "aria-label": api.headerLabel, columns: [
|
|
87
89
|
{
|
|
88
90
|
key: "year",
|
|
89
91
|
label: api.ariaLabels.yearWheel,
|
|
@@ -53,6 +53,13 @@ const MenuContext = React.createContext(null);
|
|
|
53
53
|
* closes on outside click. Flips up / right-aligns when space is tight.
|
|
54
54
|
*/
|
|
55
55
|
function Menu({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, className, "aria-label": ariaLabel, }) {
|
|
56
|
+
// The surface and the button that opens it were two unconnected nodes: the
|
|
57
|
+
// menu/dialog had no id, so nothing could point at it, and no accessible
|
|
58
|
+
// name, so a screen reader announced a bare "menu"/"dialog". Labelling the
|
|
59
|
+
// surface by its own trigger is the WAI menu-button recommendation, and it
|
|
60
|
+
// needs no invented copy (#225).
|
|
61
|
+
const surfaceId = React.useId();
|
|
62
|
+
const triggerId = trigger.props.id ?? `${surfaceId}-trigger`;
|
|
56
63
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
|
|
57
64
|
const open = openProp ?? internalOpen;
|
|
58
65
|
const setOpen = React.useCallback((next) => {
|
|
@@ -110,14 +117,18 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
|
|
|
110
117
|
});
|
|
111
118
|
const triggerEl = React.cloneElement(trigger, {
|
|
112
119
|
ref: triggerRef,
|
|
120
|
+
id: triggerId,
|
|
113
121
|
"aria-haspopup": "menu",
|
|
114
122
|
"aria-expanded": open,
|
|
123
|
+
// only while the surface exists — a reference to a node that is not
|
|
124
|
+
// there is not a relationship
|
|
125
|
+
"aria-controls": open ? surfaceId : undefined,
|
|
115
126
|
onClick: (e) => {
|
|
116
127
|
trigger.props.onClick?.(e);
|
|
117
128
|
setOpen(!open);
|
|
118
129
|
},
|
|
119
130
|
});
|
|
120
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className: "menu-content", role: "menu", "aria-label": ariaLabel, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: children }) }))] }));
|
|
131
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className: "menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: children }) }))] }));
|
|
121
132
|
}
|
|
122
133
|
Menu.displayName = "Menu";
|
|
123
134
|
function MenuItem({ children, icon, shortcut, danger, disabled, onSelect, closeOnSelect = true, }) {
|
|
@@ -44,6 +44,13 @@ const popover_recipe_1 = require("./popover.recipe");
|
|
|
44
44
|
* closes on outside pointer-down and Escape. Controlled or self-managed.
|
|
45
45
|
*/
|
|
46
46
|
function Popover({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, "aria-label": ariaLabel, className, }) {
|
|
47
|
+
// The surface and the button that opens it were two unconnected nodes: the
|
|
48
|
+
// menu/dialog had no id, so nothing could point at it, and no accessible
|
|
49
|
+
// name, so a screen reader announced a bare "menu"/"dialog". Labelling the
|
|
50
|
+
// surface by its own trigger is the WAI menu-button recommendation, and it
|
|
51
|
+
// needs no invented copy (#225).
|
|
52
|
+
const surfaceId = React.useId();
|
|
53
|
+
const triggerId = trigger.props.id ?? `${surfaceId}-trigger`;
|
|
47
54
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
|
|
48
55
|
const open = openProp ?? internalOpen;
|
|
49
56
|
const rootRef = React.useRef(null);
|
|
@@ -87,14 +94,18 @@ function Popover({ trigger, children, align = "start", open: openProp, defaultOp
|
|
|
87
94
|
}, [open, setOpen]);
|
|
88
95
|
const triggerEl = React.cloneElement(trigger, {
|
|
89
96
|
ref: triggerRef,
|
|
97
|
+
id: triggerId,
|
|
90
98
|
"aria-haspopup": "dialog",
|
|
91
99
|
"aria-expanded": open,
|
|
100
|
+
// only while the surface exists — a reference to a node that is not
|
|
101
|
+
// there is not a relationship
|
|
102
|
+
"aria-controls": open ? surfaceId : undefined,
|
|
92
103
|
onClick: (e) => {
|
|
93
104
|
trigger.props.onClick?.(e);
|
|
94
105
|
setOpen(!open);
|
|
95
106
|
},
|
|
96
107
|
});
|
|
97
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, popover_recipe_1.popoverRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)("div", { className: "popover-content", role: "dialog", "aria-label": ariaLabel, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: (e) => {
|
|
108
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, popover_recipe_1.popoverRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)("div", { className: "popover-content", role: "dialog", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: (e) => {
|
|
98
109
|
if (e.key === "Escape") {
|
|
99
110
|
e.preventDefault();
|
|
100
111
|
setOpen(false);
|
|
@@ -45,6 +45,12 @@ const tabs_keyboard_1 = require("./tabs-keyboard");
|
|
|
45
45
|
* `value`/`onValueChange`, or uncontrolled from `defaultValue`.
|
|
46
46
|
*/
|
|
47
47
|
function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, className, "aria-label": ariaLabel, ...rest }) {
|
|
48
|
+
// Scoped to this instance: `tab-${value}` and `panel-${value}` are global,
|
|
49
|
+
// so two tab bars sharing a value mint the same id twice and every
|
|
50
|
+
// aria-labelledby resolves to the first one (#227).
|
|
51
|
+
const uid = React.useId();
|
|
52
|
+
const tabId = (v) => `${uid}-tab-${v}`;
|
|
53
|
+
const panelId = (v) => `${uid}-panel-${v}`;
|
|
48
54
|
const [internal, setInternal] = React.useState(defaultValue ?? tabs[0]?.value);
|
|
49
55
|
const active = value ?? internal;
|
|
50
56
|
const listRef = React.useRef(null);
|
|
@@ -79,11 +85,11 @@ function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, cla
|
|
|
79
85
|
});
|
|
80
86
|
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, tabs_recipe_1.tabsRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { ref: listRef, className: "tabs-list", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, children: [leading && (0, jsx_runtime_1.jsx)("span", { className: "tabs-leading", children: leading }), tabs.map((t) => {
|
|
81
87
|
const selected = t.value === active;
|
|
82
|
-
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", id:
|
|
88
|
+
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", id: tabId(t.value), "aria-selected": selected, "aria-controls": children && selected ? panelId(t.value) : undefined, tabIndex: tabIndexFor(t.value), "data-value": t.value, "data-selected": selected ? "" : undefined, disabled: t.disabled, className: "tabs-tab", onClick: () => select(t.value), children: [t.icon, (0, jsx_runtime_1.jsx)("span", { className: "tabs-tab-label", children: t.label }), t.badge != null && ((0, jsx_runtime_1.jsx)("span", { className: "tabs-tab-badge", children: t.badge }))] }, t.value));
|
|
83
89
|
}), indicator && ((0, jsx_runtime_1.jsx)("span", { className: "tabs-indicator", style: {
|
|
84
90
|
width: indicator.width,
|
|
85
91
|
transform: `translateX(${indicator.left}px)`,
|
|
86
|
-
} }))] }), children && ((0, jsx_runtime_1.jsx)("div", { className: "tabs-panel", role: "tabpanel", id:
|
|
92
|
+
} }))] }), children && ((0, jsx_runtime_1.jsx)("div", { className: "tabs-panel", role: "tabpanel", id: panelId(active), "aria-labelledby": tabId(active),
|
|
87
93
|
// biome-ignore lint/a11y/noNoninteractiveTabindex: a tabpanel is focusable per the WAI-ARIA tabs pattern
|
|
88
94
|
tabIndex: 0, children: children(active) }))] }));
|
|
89
95
|
}
|
|
@@ -45,6 +45,7 @@ const tooltip_recipe_1 = require("./tooltip.recipe");
|
|
|
45
45
|
* above the trigger (flipping below near the top edge).
|
|
46
46
|
*/
|
|
47
47
|
function Tooltip({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, }) {
|
|
48
|
+
const tipId = React.useId();
|
|
48
49
|
const triggerRef = React.useRef(null);
|
|
49
50
|
const timer = React.useRef(undefined);
|
|
50
51
|
const [pos, setPos] = React.useState(null);
|
|
@@ -82,9 +83,26 @@ function Tooltip({ content, children, side = "top", delay = 350, open: openProp,
|
|
|
82
83
|
setOpen(false);
|
|
83
84
|
};
|
|
84
85
|
React.useEffect(() => () => clearTimeout(timer.current), []);
|
|
85
|
-
|
|
86
|
+
// The whole point of a tooltip is the description it gives its trigger, and
|
|
87
|
+
// the only way a screen reader learns of one is `aria-describedby` on the
|
|
88
|
+
// element that gets focus. It cannot go on the wrapper: a description on a
|
|
89
|
+
// non-focusable span around a button is never announced. So the trigger is
|
|
90
|
+
// cloned, keeping any aria-describedby it already had, and the reference is
|
|
91
|
+
// only present while the bubble is (a reference to a removed node is dead
|
|
92
|
+
// wiring, #225).
|
|
93
|
+
const described = React.isValidElement(children) && open
|
|
94
|
+
? React.cloneElement(children, {
|
|
95
|
+
"aria-describedby": [
|
|
96
|
+
children.props["aria-describedby"],
|
|
97
|
+
tipId,
|
|
98
|
+
]
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join(" "),
|
|
101
|
+
})
|
|
102
|
+
: children;
|
|
103
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { ref: triggerRef, style: { display: "inline-flex" }, onPointerEnter: openAfterDelay, onPointerLeave: close, onFocusCapture: openAfterDelay, onBlurCapture: close, onKeyDown: (e) => e.key === "Escape" && close(), children: described }), open &&
|
|
86
104
|
pos != null &&
|
|
87
|
-
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { role: "tooltip", className: (0, tooltip_recipe_1.tooltipRecipe)(), style: {
|
|
105
|
+
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { id: tipId, role: "tooltip", className: (0, tooltip_recipe_1.tooltipRecipe)(), style: {
|
|
88
106
|
left: pos.left,
|
|
89
107
|
top: pos.top,
|
|
90
108
|
transform: pos.below
|
|
@@ -231,7 +231,7 @@ function Command({ open, onOpenChange, items = exports.defaultCommandItems, plac
|
|
|
231
231
|
};
|
|
232
232
|
if (!dlg.open)
|
|
233
233
|
return null;
|
|
234
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, command_recipe_1.commandRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...rest, ...(0, core_1.withDescribedBy)(dlg.getContentProps(), describedBy), className: (0, core_1.cx)("command-content", className), children: (0, jsx_runtime_1.jsxs)("div", { className: "command-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "command-control", children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), (0, jsx_runtime_1.jsx)("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
234
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, command_recipe_1.commandRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...rest, ...(0, core_1.withDescribedBy)(dlg.getContentProps(), describedBy), className: (0, core_1.cx)("command-content", className), children: (0, jsx_runtime_1.jsxs)("div", { className: "command-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "command-control", children: [(0, jsx_runtime_1.jsx)("label", { ...combo.getLabelProps(), className: "command-srlabel", children: ariaLabel }), (0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), (0, jsx_runtime_1.jsx)("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
235
235
|
const Icon = item.icon;
|
|
236
236
|
const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
|
|
237
237
|
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroup && ((0, jsx_runtime_1.jsx)("div", { className: "command-group-label", "aria-hidden": "true", children: item.group })), (0, jsx_runtime_1.jsxs)("div", { ...combo.getItemProps({ item }), className: "command-item", children: [(0, jsx_runtime_1.jsx)(Icon, {}), (0, jsx_runtime_1.jsx)("span", { className: "command-item-label", children: item.label }), item.shortcut && ((0, jsx_runtime_1.jsx)("span", { className: "command-item-shortcut", children: item.shortcut.map((key) => ((0, jsx_runtime_1.jsx)(kbd_1.Kbd, { children: key }, key))) }))] })] }, item.id));
|
|
@@ -13,6 +13,12 @@ const config = (0, core_1.defineRecipe)({
|
|
|
13
13
|
& .command-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); overflow: hidden; }
|
|
14
14
|
& .command-control { display: flex; align-items: center; gap: ${tokens_1.contract.space["2.5"]}; height: 52px; flex-shrink: 0; padding: 0 ${tokens_1.contract.space["4"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; color: ${tokens_1.contract.fgMuted}; }
|
|
15
15
|
& .command-control svg { flex-shrink: 0; }
|
|
16
|
+
/* The palette's label element. It exists for the assistive tree only: zag's
|
|
17
|
+
combobox names the input through it and points the listbox's
|
|
18
|
+
aria-labelledby at it, and without it both were unnamed and the reference
|
|
19
|
+
resolved to nothing (#225). Clipped rather than display:none, which would
|
|
20
|
+
take it out of the tree it exists for. */
|
|
21
|
+
& .command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
16
22
|
& .command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.xl}; color: ${tokens_1.contract.fgPrimary}; }
|
|
17
23
|
& .command-input::placeholder { color: ${tokens_1.contract.fgMuted}; }
|
|
18
24
|
& .command-list { flex: 1; min-height: 0; overflow-y: auto; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["2.5"]}; display: flex; flex-direction: column; gap: ${tokens_1.contract.space.px}; }
|
package/dist/command.css
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
& .command-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--radius-xl); background: var(--bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--fg-primary) 8%, transparent); overflow: hidden; }
|
|
8
8
|
& .command-control { display: flex; align-items: center; gap: var(--space-2-5); height: 52px; flex-shrink: 0; padding: 0 var(--space-4); border-bottom: 1px solid var(--border-subtle); color: var(--fg-muted); }
|
|
9
9
|
& .command-control svg { flex-shrink: 0; }
|
|
10
|
+
/* The palette's label element. It exists for the assistive tree only: zag's
|
|
11
|
+
combobox names the input through it and points the listbox's
|
|
12
|
+
aria-labelledby at it, and without it both were unnamed and the reference
|
|
13
|
+
resolved to nothing (#225). Clipped rather than display:none, which would
|
|
14
|
+
take it out of the tree it exists for. */
|
|
15
|
+
& .command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
10
16
|
& .command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: var(--font-sans); font-size: var(--text-xl); color: var(--fg-primary); }
|
|
11
17
|
& .command-input::placeholder { color: var(--fg-muted); }
|
|
12
18
|
& .command-list { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-1) var(--space-2-5); display: flex; flex-direction: column; gap: var(--space-px); }
|
|
@@ -9,6 +9,7 @@ import { accordionRecipe } from "./accordion.recipe.js";
|
|
|
9
9
|
*/
|
|
10
10
|
const toSet = (v) => new Set(v == null ? [] : Array.isArray(v) ? v : [v]);
|
|
11
11
|
export function Accordion({ items, multiple, value, defaultValue, onValueChange, className, ...rest }) {
|
|
12
|
+
const uid = React.useId();
|
|
12
13
|
const [internal, setInternal] = React.useState(() => toSet(defaultValue));
|
|
13
14
|
// `value` present means the caller owns the open set; our own state is then
|
|
14
15
|
// never read, so a click can only change what is shown by going through
|
|
@@ -26,8 +27,13 @@ export function Accordion({ items, multiple, value, defaultValue, onValueChange,
|
|
|
26
27
|
};
|
|
27
28
|
return (_jsx("div", { className: accordionRecipe({ className }), ...rest, children: items.map((item) => {
|
|
28
29
|
const isOpen = open.has(item.value);
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Scoped to this instance. `accordion-panel-${value}` is global, so two
|
|
31
|
+
// accordions with an item of the same value on one page mint the same
|
|
32
|
+
// id twice — and getElementById returns the first, so the second
|
|
33
|
+
// accordion's triggers silently control the first one's panels (#227).
|
|
34
|
+
const panelId = `${uid}-panel-${item.value}`;
|
|
35
|
+
const triggerId = `${uid}-trigger-${item.value}`;
|
|
36
|
+
return (_jsxs("div", { className: "accordion-item", children: [_jsxs("button", { type: "button", id: triggerId, className: "accordion-trigger", "aria-expanded": isOpen, "aria-controls": panelId, onClick: () => toggle(item.value), children: [_jsx("span", { className: "accordion-title", children: item.title }), _jsx(ChevronDown, { className: "accordion-chevron", size: 16, strokeWidth: 1.75, "aria-hidden": "true" })] }), _jsx("div", { id: panelId, className: "accordion-panel", role: "region", "aria-labelledby": triggerId, "data-open": isOpen ? "" : undefined, hidden: !isOpen, children: _jsx("div", { className: "accordion-panel-inner", children: _jsx("div", { className: "accordion-content", children: item.content }) }) })] }, item.value));
|
|
31
37
|
}) }));
|
|
32
38
|
}
|
|
33
39
|
Accordion.displayName = "Accordion";
|
|
@@ -24,7 +24,9 @@ function Header({ api }) {
|
|
|
24
24
|
// in RTL the chevrons swap: "previous" is on the right
|
|
25
25
|
const previousDirection = api.isRtl ? "right" : "left";
|
|
26
26
|
const nextDirection = api.isRtl ? "left" : "right";
|
|
27
|
-
return (_jsxs("div", { className: "datepicker-header", children: [_jsxs("button", { ...api.monthYearButtonProps,
|
|
27
|
+
return (_jsxs("div", { className: "datepicker-header", children: [_jsxs("button", { ...api.monthYearButtonProps, "aria-controls": api.isWheelOpen
|
|
28
|
+
? api.monthYearButtonProps["aria-controls"]
|
|
29
|
+
: undefined, type: "button", id: `${api.rootProps.id}-header-label`, className: "datepicker-header-label", children: [_jsx("span", { "data-date-picker-header-label": "", children: api.headerLabel }), _jsx("span", { className: "datepicker-header-chevron", "data-open": api.isWheelOpen ? "" : undefined, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }), _jsxs("div", { className: "datepicker-nav", children: [_jsx(NavigationButton, { ...api.previousButtonProps, direction: previousDirection, className: "datepicker-nav-button" }), _jsx(NavigationButton, { ...api.nextButtonProps, direction: nextDirection, className: "datepicker-nav-button" })] })] }));
|
|
28
30
|
}
|
|
29
31
|
function TwoMonthHeader({ api, label, labelId, position, }) {
|
|
30
32
|
const isFirst = position === "first";
|
|
@@ -47,7 +49,7 @@ function MonthView({ api, month, renderDateCellContent, renderDateCellSupplement
|
|
|
47
49
|
: labelId, className: "datepicker-grid", children: [showWeekdays && _jsx(WeekdayRow, { api: api }), month.weeks.map((week) => (_jsx("div", { role: "row", className: "datepicker-week-row", children: week.cells.map((cell, index) => (_jsx(DateCellView, { cell: cell, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement }, cell?.key ?? `${week.key}-empty-${index}`))) }, week.key)))] })] }));
|
|
48
50
|
}
|
|
49
51
|
function WheelView({ api }) {
|
|
50
|
-
return (_jsx("div", { className: "datepicker-wheel-container", children: _jsx(WheelPicker, { className: "datepicker-wheel", "aria-label": api.headerLabel, columns: [
|
|
52
|
+
return (_jsx("div", { ...api.wheelProps, className: "datepicker-wheel-container", children: _jsx(WheelPicker, { className: "datepicker-wheel", "aria-label": api.headerLabel, columns: [
|
|
51
53
|
{
|
|
52
54
|
key: "year",
|
|
53
55
|
label: api.ariaLabels.yearWheel,
|
|
@@ -13,6 +13,13 @@ const MenuContext = React.createContext(null);
|
|
|
13
13
|
* closes on outside click. Flips up / right-aligns when space is tight.
|
|
14
14
|
*/
|
|
15
15
|
export function Menu({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, className, "aria-label": ariaLabel, }) {
|
|
16
|
+
// The surface and the button that opens it were two unconnected nodes: the
|
|
17
|
+
// menu/dialog had no id, so nothing could point at it, and no accessible
|
|
18
|
+
// name, so a screen reader announced a bare "menu"/"dialog". Labelling the
|
|
19
|
+
// surface by its own trigger is the WAI menu-button recommendation, and it
|
|
20
|
+
// needs no invented copy (#225).
|
|
21
|
+
const surfaceId = React.useId();
|
|
22
|
+
const triggerId = trigger.props.id ?? `${surfaceId}-trigger`;
|
|
16
23
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
|
|
17
24
|
const open = openProp ?? internalOpen;
|
|
18
25
|
const setOpen = React.useCallback((next) => {
|
|
@@ -70,14 +77,18 @@ export function Menu({ trigger, children, align = "start", open: openProp, defau
|
|
|
70
77
|
});
|
|
71
78
|
const triggerEl = React.cloneElement(trigger, {
|
|
72
79
|
ref: triggerRef,
|
|
80
|
+
id: triggerId,
|
|
73
81
|
"aria-haspopup": "menu",
|
|
74
82
|
"aria-expanded": open,
|
|
83
|
+
// only while the surface exists — a reference to a node that is not
|
|
84
|
+
// there is not a relationship
|
|
85
|
+
"aria-controls": open ? surfaceId : undefined,
|
|
75
86
|
onClick: (e) => {
|
|
76
87
|
trigger.props.onClick?.(e);
|
|
77
88
|
setOpen(!open);
|
|
78
89
|
},
|
|
79
90
|
});
|
|
80
|
-
return (_jsxs("div", { ref: rootRef, className: menuRecipe({ className }), children: [triggerEl, open && (_jsx(MenuContext.Provider, { value: { close }, children: _jsx("div", { ref: contentRef, className: "menu-content", role: "menu", "aria-label": ariaLabel, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: children }) }))] }));
|
|
91
|
+
return (_jsxs("div", { ref: rootRef, className: menuRecipe({ className }), children: [triggerEl, open && (_jsx(MenuContext.Provider, { value: { close }, children: _jsx("div", { ref: contentRef, className: "menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: children }) }))] }));
|
|
81
92
|
}
|
|
82
93
|
Menu.displayName = "Menu";
|
|
83
94
|
export function MenuItem({ children, icon, shortcut, danger, disabled, onSelect, closeOnSelect = true, }) {
|
|
@@ -8,6 +8,13 @@ import { popoverRecipe } from "./popover.recipe.js";
|
|
|
8
8
|
* closes on outside pointer-down and Escape. Controlled or self-managed.
|
|
9
9
|
*/
|
|
10
10
|
export function Popover({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, "aria-label": ariaLabel, className, }) {
|
|
11
|
+
// The surface and the button that opens it were two unconnected nodes: the
|
|
12
|
+
// menu/dialog had no id, so nothing could point at it, and no accessible
|
|
13
|
+
// name, so a screen reader announced a bare "menu"/"dialog". Labelling the
|
|
14
|
+
// surface by its own trigger is the WAI menu-button recommendation, and it
|
|
15
|
+
// needs no invented copy (#225).
|
|
16
|
+
const surfaceId = React.useId();
|
|
17
|
+
const triggerId = trigger.props.id ?? `${surfaceId}-trigger`;
|
|
11
18
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
|
|
12
19
|
const open = openProp ?? internalOpen;
|
|
13
20
|
const rootRef = React.useRef(null);
|
|
@@ -51,14 +58,18 @@ export function Popover({ trigger, children, align = "start", open: openProp, de
|
|
|
51
58
|
}, [open, setOpen]);
|
|
52
59
|
const triggerEl = React.cloneElement(trigger, {
|
|
53
60
|
ref: triggerRef,
|
|
61
|
+
id: triggerId,
|
|
54
62
|
"aria-haspopup": "dialog",
|
|
55
63
|
"aria-expanded": open,
|
|
64
|
+
// only while the surface exists — a reference to a node that is not
|
|
65
|
+
// there is not a relationship
|
|
66
|
+
"aria-controls": open ? surfaceId : undefined,
|
|
56
67
|
onClick: (e) => {
|
|
57
68
|
trigger.props.onClick?.(e);
|
|
58
69
|
setOpen(!open);
|
|
59
70
|
},
|
|
60
71
|
});
|
|
61
|
-
return (_jsxs("div", { ref: rootRef, className: popoverRecipe({ className }), children: [triggerEl, open && (_jsx("div", { className: "popover-content", role: "dialog", "aria-label": ariaLabel, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: (e) => {
|
|
72
|
+
return (_jsxs("div", { ref: rootRef, className: popoverRecipe({ className }), children: [triggerEl, open && (_jsx("div", { className: "popover-content", role: "dialog", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: (e) => {
|
|
62
73
|
if (e.key === "Escape") {
|
|
63
74
|
e.preventDefault();
|
|
64
75
|
setOpen(false);
|
|
@@ -9,6 +9,12 @@ import { useTablistKeyboard } from "./tabs-keyboard.js";
|
|
|
9
9
|
* `value`/`onValueChange`, or uncontrolled from `defaultValue`.
|
|
10
10
|
*/
|
|
11
11
|
export function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, className, "aria-label": ariaLabel, ...rest }) {
|
|
12
|
+
// Scoped to this instance: `tab-${value}` and `panel-${value}` are global,
|
|
13
|
+
// so two tab bars sharing a value mint the same id twice and every
|
|
14
|
+
// aria-labelledby resolves to the first one (#227).
|
|
15
|
+
const uid = React.useId();
|
|
16
|
+
const tabId = (v) => `${uid}-tab-${v}`;
|
|
17
|
+
const panelId = (v) => `${uid}-panel-${v}`;
|
|
12
18
|
const [internal, setInternal] = React.useState(defaultValue ?? tabs[0]?.value);
|
|
13
19
|
const active = value ?? internal;
|
|
14
20
|
const listRef = React.useRef(null);
|
|
@@ -43,11 +49,11 @@ export function Tabs({ tabs, value, defaultValue, onValueChange, leading, childr
|
|
|
43
49
|
});
|
|
44
50
|
return (_jsxs("div", { className: tabsRecipe({ className }), ...rest, children: [_jsxs("div", { ref: listRef, className: "tabs-list", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, children: [leading && _jsx("span", { className: "tabs-leading", children: leading }), tabs.map((t) => {
|
|
45
51
|
const selected = t.value === active;
|
|
46
|
-
return (_jsxs("button", { type: "button", role: "tab", id:
|
|
52
|
+
return (_jsxs("button", { type: "button", role: "tab", id: tabId(t.value), "aria-selected": selected, "aria-controls": children && selected ? panelId(t.value) : undefined, tabIndex: tabIndexFor(t.value), "data-value": t.value, "data-selected": selected ? "" : undefined, disabled: t.disabled, className: "tabs-tab", onClick: () => select(t.value), children: [t.icon, _jsx("span", { className: "tabs-tab-label", children: t.label }), t.badge != null && (_jsx("span", { className: "tabs-tab-badge", children: t.badge }))] }, t.value));
|
|
47
53
|
}), indicator && (_jsx("span", { className: "tabs-indicator", style: {
|
|
48
54
|
width: indicator.width,
|
|
49
55
|
transform: `translateX(${indicator.left}px)`,
|
|
50
|
-
} }))] }), children && (_jsx("div", { className: "tabs-panel", role: "tabpanel", id:
|
|
56
|
+
} }))] }), children && (_jsx("div", { className: "tabs-panel", role: "tabpanel", id: panelId(active), "aria-labelledby": tabId(active),
|
|
51
57
|
// biome-ignore lint/a11y/noNoninteractiveTabindex: a tabpanel is focusable per the WAI-ARIA tabs pattern
|
|
52
58
|
tabIndex: 0, children: children(active) }))] }));
|
|
53
59
|
}
|
|
@@ -9,6 +9,7 @@ import { tooltipRecipe } from "./tooltip.recipe.js";
|
|
|
9
9
|
* above the trigger (flipping below near the top edge).
|
|
10
10
|
*/
|
|
11
11
|
export function Tooltip({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, }) {
|
|
12
|
+
const tipId = React.useId();
|
|
12
13
|
const triggerRef = React.useRef(null);
|
|
13
14
|
const timer = React.useRef(undefined);
|
|
14
15
|
const [pos, setPos] = React.useState(null);
|
|
@@ -46,9 +47,26 @@ export function Tooltip({ content, children, side = "top", delay = 350, open: op
|
|
|
46
47
|
setOpen(false);
|
|
47
48
|
};
|
|
48
49
|
React.useEffect(() => () => clearTimeout(timer.current), []);
|
|
49
|
-
|
|
50
|
+
// The whole point of a tooltip is the description it gives its trigger, and
|
|
51
|
+
// the only way a screen reader learns of one is `aria-describedby` on the
|
|
52
|
+
// element that gets focus. It cannot go on the wrapper: a description on a
|
|
53
|
+
// non-focusable span around a button is never announced. So the trigger is
|
|
54
|
+
// cloned, keeping any aria-describedby it already had, and the reference is
|
|
55
|
+
// only present while the bubble is (a reference to a removed node is dead
|
|
56
|
+
// wiring, #225).
|
|
57
|
+
const described = React.isValidElement(children) && open
|
|
58
|
+
? React.cloneElement(children, {
|
|
59
|
+
"aria-describedby": [
|
|
60
|
+
children.props["aria-describedby"],
|
|
61
|
+
tipId,
|
|
62
|
+
]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join(" "),
|
|
65
|
+
})
|
|
66
|
+
: children;
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { ref: triggerRef, style: { display: "inline-flex" }, onPointerEnter: openAfterDelay, onPointerLeave: close, onFocusCapture: openAfterDelay, onBlurCapture: close, onKeyDown: (e) => e.key === "Escape" && close(), children: described }), open &&
|
|
50
68
|
pos != null &&
|
|
51
|
-
createPortal(_jsx("div", { role: "tooltip", className: tooltipRecipe(), style: {
|
|
69
|
+
createPortal(_jsx("div", { id: tipId, role: "tooltip", className: tooltipRecipe(), style: {
|
|
52
70
|
left: pos.left,
|
|
53
71
|
top: pos.top,
|
|
54
72
|
transform: pos.below
|
|
@@ -194,7 +194,7 @@ export function Command({ open, onOpenChange, items = defaultCommandItems, place
|
|
|
194
194
|
};
|
|
195
195
|
if (!dlg.open)
|
|
196
196
|
return null;
|
|
197
|
-
return (_jsx(Portal, { children: _jsxs("div", { className: commandRecipe(), children: [_jsx("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), _jsx("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(dlg.getContentProps(), describedBy), className: cx("command-content", className), children: _jsxs("div", { className: "command-card", children: [_jsxs("div", { className: "command-control", children: [_jsx(IconSearch, {}), _jsx("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), _jsx("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? (_jsx("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
197
|
+
return (_jsx(Portal, { children: _jsxs("div", { className: commandRecipe(), children: [_jsx("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), _jsx("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(dlg.getContentProps(), describedBy), className: cx("command-content", className), children: _jsxs("div", { className: "command-card", children: [_jsxs("div", { className: "command-control", children: [_jsx("label", { ...combo.getLabelProps(), className: "command-srlabel", children: ariaLabel }), _jsx(IconSearch, {}), _jsx("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), _jsx("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? (_jsx("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
198
198
|
const Icon = item.icon;
|
|
199
199
|
const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
|
|
200
200
|
return (_jsxs(React.Fragment, { children: [showGroup && (_jsx("div", { className: "command-group-label", "aria-hidden": "true", children: item.group })), _jsxs("div", { ...combo.getItemProps({ item }), className: "command-item", children: [_jsx(Icon, {}), _jsx("span", { className: "command-item-label", children: item.label }), item.shortcut && (_jsx("span", { className: "command-item-shortcut", children: item.shortcut.map((key) => (_jsx(Kbd, { children: key }, key))) }))] })] }, item.id));
|
|
@@ -10,6 +10,12 @@ const config = defineRecipe({
|
|
|
10
10
|
& .command-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: ${c.radius.xl}; background: ${c.bgSurface}; box-shadow: 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 8%, transparent); overflow: hidden; }
|
|
11
11
|
& .command-control { display: flex; align-items: center; gap: ${c.space["2.5"]}; height: 52px; flex-shrink: 0; padding: 0 ${c.space["4"]}; border-bottom: 1px solid ${c.borderSubtle}; color: ${c.fgMuted}; }
|
|
12
12
|
& .command-control svg { flex-shrink: 0; }
|
|
13
|
+
/* The palette's label element. It exists for the assistive tree only: zag's
|
|
14
|
+
combobox names the input through it and points the listbox's
|
|
15
|
+
aria-labelledby at it, and without it both were unnamed and the reference
|
|
16
|
+
resolved to nothing (#225). Clipped rather than display:none, which would
|
|
17
|
+
take it out of the tree it exists for. */
|
|
18
|
+
& .command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
13
19
|
& .command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: ${c.font.sans}; font-size: ${c.text.xl}; color: ${c.fgPrimary}; }
|
|
14
20
|
& .command-input::placeholder { color: ${c.fgMuted}; }
|
|
15
21
|
& .command-list { flex: 1; min-height: 0; overflow-y: auto; padding: ${c.space["1"]} ${c.space["2.5"]}; display: flex; flex-direction: column; gap: ${c.space.px}; }
|
package/dist/styles.css
CHANGED
|
@@ -1976,6 +1976,12 @@
|
|
|
1976
1976
|
& .command-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--radius-xl); background: var(--bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--fg-primary) 8%, transparent); overflow: hidden; }
|
|
1977
1977
|
& .command-control { display: flex; align-items: center; gap: var(--space-2-5); height: 52px; flex-shrink: 0; padding: 0 var(--space-4); border-bottom: 1px solid var(--border-subtle); color: var(--fg-muted); }
|
|
1978
1978
|
& .command-control svg { flex-shrink: 0; }
|
|
1979
|
+
/* The palette's label element. It exists for the assistive tree only: zag's
|
|
1980
|
+
combobox names the input through it and points the listbox's
|
|
1981
|
+
aria-labelledby at it, and without it both were unnamed and the reference
|
|
1982
|
+
resolved to nothing (#225). Clipped rather than display:none, which would
|
|
1983
|
+
take it out of the tree it exists for. */
|
|
1984
|
+
& .command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
1979
1985
|
& .command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: var(--font-sans); font-size: var(--text-xl); color: var(--fg-primary); }
|
|
1980
1986
|
& .command-input::placeholder { color: var(--fg-muted); }
|
|
1981
1987
|
& .command-list { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-1) var(--space-2-5); display: flex; flex-direction: column; gap: var(--space-px); }
|
|
@@ -2,7 +2,12 @@ import * as React from "react";
|
|
|
2
2
|
export type TooltipProps = {
|
|
3
3
|
/** The hint text/content shown on hover or focus. */
|
|
4
4
|
content: React.ReactNode;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The trigger element. A SINGLE element: it is cloned to carry the
|
|
7
|
+
* `aria-describedby` that makes the hint reachable, so anything else — a
|
|
8
|
+
* string, a fragment, several children — renders and hovers correctly but
|
|
9
|
+
* cannot be described to a screen reader.
|
|
10
|
+
*/
|
|
6
11
|
children: React.ReactNode;
|
|
7
12
|
/** Preferred side; flips to the other axis end when there's no room. */
|
|
8
13
|
side?: "top" | "bottom";
|