@crisp-ui-kit/crisp 0.23.1 → 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.
@@ -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
- const panelId = `accordion-panel-${item.value}`;
66
- return ((0, jsx_runtime_1.jsxs)("div", { className: "accordion-item", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", 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", "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));
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, 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" })] })] }));
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,
@@ -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: `tab-${t.value}`, "aria-selected": selected, "aria-controls": `panel-${t.value}`, 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));
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: `panel-${active}`, "aria-labelledby": `tab-${active}`,
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
  }
@@ -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
- const panelId = `accordion-panel-${item.value}`;
30
- return (_jsxs("div", { className: "accordion-item", children: [_jsxs("button", { type: "button", 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", "data-open": isOpen ? "" : undefined, hidden: !isOpen, children: _jsx("div", { className: "accordion-panel-inner", children: _jsx("div", { className: "accordion-content", children: item.content }) }) })] }, item.value));
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, 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" })] })] }));
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,
@@ -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: `tab-${t.value}`, "aria-selected": selected, "aria-controls": `panel-${t.value}`, 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));
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: `panel-${active}`, "aria-labelledby": `tab-${active}`,
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.23.1",
3
+ "version": "0.23.2",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",