@crisp-ui-kit/crisp 0.21.1 → 0.23.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.
Files changed (40) hide show
  1. package/dist/cjs/components/dialog/dialog.js +6 -2
  2. package/dist/cjs/components/drawer/drawer.js +3 -2
  3. package/dist/cjs/core/dialog-description.js +22 -0
  4. package/dist/cjs/core/index.js +3 -1
  5. package/dist/cjs/product/command/command.js +3 -2
  6. package/dist/cjs/product/flowcanvas/flowcanvas.js +6 -2
  7. package/dist/cjs/product/inbox/inbox.js +2 -2
  8. package/dist/cjs/product/listpage/listpage.js +2 -2
  9. package/dist/cjs/product/onboarding/onboarding.js +2 -2
  10. package/dist/cjs/product/recordpage/recordpage.js +2 -2
  11. package/dist/cjs/product/recordstable/recordstable.js +33 -4
  12. package/dist/cjs/product/richtext/richtext.js +2 -2
  13. package/dist/cjs/product/tasklist/tasklist.js +2 -2
  14. package/dist/esm/components/dialog/dialog.js +6 -2
  15. package/dist/esm/components/drawer/drawer.js +3 -2
  16. package/dist/esm/core/dialog-description.js +19 -0
  17. package/dist/esm/core/index.js +1 -0
  18. package/dist/esm/product/command/command.js +3 -2
  19. package/dist/esm/product/flowcanvas/flowcanvas.js +6 -2
  20. package/dist/esm/product/inbox/inbox.js +2 -2
  21. package/dist/esm/product/listpage/listpage.js +2 -2
  22. package/dist/esm/product/onboarding/onboarding.js +2 -2
  23. package/dist/esm/product/recordpage/recordpage.js +2 -2
  24. package/dist/esm/product/recordstable/recordstable.js +31 -4
  25. package/dist/esm/product/richtext/richtext.js +2 -2
  26. package/dist/esm/product/tasklist/tasklist.js +2 -2
  27. package/dist/types/components/dialog/dialog.d.ts +12 -2
  28. package/dist/types/components/drawer/drawer.d.ts +6 -2
  29. package/dist/types/core/dialog-description.d.ts +18 -0
  30. package/dist/types/core/index.d.ts +1 -0
  31. package/dist/types/product/command/command.d.ts +6 -2
  32. package/dist/types/product/flowcanvas/flowcanvas.d.ts +2 -2
  33. package/dist/types/product/inbox/inbox.d.ts +2 -2
  34. package/dist/types/product/listpage/listpage.d.ts +2 -2
  35. package/dist/types/product/onboarding/onboarding.d.ts +2 -2
  36. package/dist/types/product/recordpage/recordpage.d.ts +2 -2
  37. package/dist/types/product/recordstable/recordstable.d.ts +5 -2
  38. package/dist/types/product/richtext/richtext.d.ts +2 -2
  39. package/dist/types/product/tasklist/tasklist.d.ts +2 -2
  40. package/package.json +1 -1
@@ -41,6 +41,7 @@ const dialog = __importStar(require("@zag-js/dialog"));
41
41
  const react_1 = require("@zag-js/react");
42
42
  const lucide_react_1 = require("lucide-react");
43
43
  const React = __importStar(require("react"));
44
+ const core_1 = require("../../core");
44
45
  const dialog_recipe_1 = require("./dialog.recipe");
45
46
  // A globally unique id per mounted instance (see command.tsx for the rationale —
46
47
  // the machine defers its dismissal listeners a frame, so tree-relative useId
@@ -59,7 +60,7 @@ function useDialogInstanceId() {
59
60
  * Escape / outside-click dismissal) with the measured Attio modal surface.
60
61
  * Compose the body with `DialogBody` and actions with `DialogFooter`.
61
62
  */
62
- function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
63
+ function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, className, style, "aria-describedby": describedBy, ...rest }) {
63
64
  const id = useDialogInstanceId();
64
65
  const service = (0, react_1.useMachine)(dialog.machine, {
65
66
  id,
@@ -75,7 +76,10 @@ function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ar
75
76
  const api = dialog.connect(service, react_1.normalizeProps);
76
77
  if (!api.open)
77
78
  return null;
78
- return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, dialog_recipe_1.dialogRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...api.getContentProps(), className: "dialog-content", "data-crisp-dialog": id, style: { maxWidth: `min(${width}px, 90vw)` }, children: (0, jsx_runtime_1.jsxs)("div", { className: "dialog-card", children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "dialog-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
79
+ return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, dialog_recipe_1.dialogRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: (0, core_1.cx)("dialog-content", className), "data-crisp-dialog": id,
80
+ // `width` is the component's own knob, so it wins over a passed
81
+ // maxWidth; everything else a consumer sets survives
82
+ style: { ...style, maxWidth: `min(${width}px, 90vw)` }, children: (0, jsx_runtime_1.jsxs)("div", { className: "dialog-card", children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "dialog-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
79
83
  }
80
84
  Dialog.displayName = "Dialog";
81
85
  function DialogBody({ children, className, }) {
@@ -41,6 +41,7 @@ const dialog = __importStar(require("@zag-js/dialog"));
41
41
  const react_1 = require("@zag-js/react");
42
42
  const lucide_react_1 = require("lucide-react");
43
43
  const React = __importStar(require("react"));
44
+ const core_1 = require("../../core");
44
45
  const drawer_recipe_1 = require("./drawer.recipe");
45
46
  let instanceCounter = 0;
46
47
  function useDrawerInstanceId() {
@@ -56,7 +57,7 @@ function useDrawerInstanceId() {
56
57
  * Escape / outside-click dismissal) with a panel that slides in from the edge.
57
58
  * Compose the body with `DrawerBody` and actions with `DrawerFooter`.
58
59
  */
59
- function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
60
+ function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, className, "aria-describedby": describedBy, ...rest }) {
60
61
  const id = useDrawerInstanceId();
61
62
  const service = (0, react_1.useMachine)(dialog.machine, {
62
63
  id,
@@ -69,7 +70,7 @@ function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label":
69
70
  const api = dialog.connect(service, react_1.normalizeProps);
70
71
  if (!api.open)
71
72
  return null;
72
- return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, drawer_recipe_1.drawerRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: (0, jsx_runtime_1.jsxs)("div", { ...api.getContentProps(), className: "drawer-content", children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "drawer-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
73
+ return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, drawer_recipe_1.drawerRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: (0, jsx_runtime_1.jsxs)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: (0, core_1.cx)("drawer-content", className), children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "drawer-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
73
74
  }
74
75
  Drawer.displayName = "Drawer";
75
76
  function DrawerBody({ children }) {
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withDescribedBy = withDescribedBy;
4
+ /**
5
+ * Zag's dialog machine always advertises a description: `getContentProps()`
6
+ * returns an `aria-describedby` pointing at the id of a description element.
7
+ * crisp renders no such element — Attio's modals have a title and a body, no
8
+ * description line — so the reference dangles, and a dangling id is not a
9
+ * relationship a screen reader can follow.
10
+ *
11
+ * It also silenced the consumer's own: the machine's props are spread after
12
+ * anything passed in, so a real `aria-describedby` was overwritten by the
13
+ * missing one every time (#223).
14
+ *
15
+ * Drop the machine's value, and let the caller's stand when there is one. A
16
+ * consumer who wants a description points at a node inside their own children,
17
+ * which is the only place one exists.
18
+ */
19
+ function withDescribedBy(contentProps, describedBy) {
20
+ const { "aria-describedby": _machine, ...rest } = contentProps;
21
+ return describedBy ? { ...rest, "aria-describedby": describedBy } : rest;
22
+ }
@@ -14,11 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.usePrefersReducedMotion = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.isRtlElement = exports.defineRecipe = exports.cx = void 0;
17
+ exports.usePrefersReducedMotion = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = void 0;
18
18
  var cx_1 = require("./cx");
19
19
  Object.defineProperty(exports, "cx", { enumerable: true, get: function () { return cx_1.cx; } });
20
20
  var define_1 = require("./define");
21
21
  Object.defineProperty(exports, "defineRecipe", { enumerable: true, get: function () { return define_1.defineRecipe; } });
22
+ var dialog_description_1 = require("./dialog-description");
23
+ Object.defineProperty(exports, "withDescribedBy", { enumerable: true, get: function () { return dialog_description_1.withDescribedBy; } });
22
24
  var direction_1 = require("./direction");
23
25
  Object.defineProperty(exports, "isRtlElement", { enumerable: true, get: function () { return direction_1.isRtlElement; } });
24
26
  var place_floating_1 = require("./place-floating");
@@ -42,6 +42,7 @@ const react_1 = require("@zag-js/react");
42
42
  const lucide_react_1 = require("lucide-react");
43
43
  const React = __importStar(require("react"));
44
44
  const kbd_1 = require("../../components/kbd");
45
+ const core_1 = require("../../core");
45
46
  const command_recipe_1 = require("./command.recipe");
46
47
  const IconHome = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.House, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
47
48
  const IconTasks = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
@@ -146,7 +147,7 @@ function useCommandInstanceId() {
146
147
  * option list) so keyboard/focus correctness comes from the machines, not
147
148
  * hand-rolled event handling.
148
149
  */
149
- function Command({ open, onOpenChange, items = exports.defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, }) {
150
+ function Command({ open, onOpenChange, items = exports.defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, className, "aria-describedby": describedBy, ...rest }) {
150
151
  const instanceId = useCommandInstanceId();
151
152
  const dialogId = `${instanceId}-dialog`;
152
153
  const comboId = `${instanceId}-combobox`;
@@ -230,7 +231,7 @@ function Command({ open, onOpenChange, items = exports.defaultCommandItems, plac
230
231
  };
231
232
  if (!dlg.open)
232
233
  return null;
233
- 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", { ...dlg.getContentProps(), className: "command-content", 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)(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
235
  const Icon = item.icon;
235
236
  const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
236
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));
@@ -45,7 +45,7 @@ const clampZoom = (z) => Math.min(1.5, Math.max(0.5, z));
45
45
  * measured from Attio's Workflows editor. Drag a node to move it (fires
46
46
  * `onNodesChange`); drag the background to pan; use the toolbar to zoom.
47
47
  */
48
- function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", }) {
48
+ function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", ...rest }) {
49
49
  const [pan, setPan] = React.useState({ x: 40, y: 40 });
50
50
  const [zoom, setZoom] = React.useState(1);
51
51
  const [panning, setPanning] = React.useState(false);
@@ -93,7 +93,11 @@ function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240,
93
93
  drag.current = null;
94
94
  setPanning(false);
95
95
  };
96
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, flowcanvas_recipe_1.flowcanvasRecipe)({ className }), "data-panning": panning ? "" : undefined, style: {
96
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, flowcanvas_recipe_1.flowcanvasRecipe)({ className }), ...rest, "data-panning": panning ? "" : undefined,
97
+ // the grid follows the pan, so those two keys are the component's own;
98
+ // everything else a consumer sets survives
99
+ style: {
100
+ ...rest.style,
97
101
  backgroundSize: `${GRID * zoom}px ${GRID * zoom}px`,
98
102
  backgroundPosition: `${pan.x}px ${pan.y}px`,
99
103
  }, role: "application", "aria-label": ariaLabel ?? "Flow canvas", onPointerDown: onBackgroundPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flowcanvas-viewport", style: {
@@ -45,7 +45,7 @@ const inbox_recipe_1 = require("./inbox.recipe");
45
45
  * (avatar + message + time + unread dot) or a centred empty state. Put it inside
46
46
  * a Popover for the flyout.
47
47
  */
48
- function Inbox({ notifications = [], title = "Notifications", tabs, activeTab, onTabChange, emptyTitle = "No notifications", emptySubtitle = "Notifications such as mentions and important updates will be displayed here.", emptyAction, tabsLabel = "Notification views", className, }) {
48
+ function Inbox({ notifications = [], title = "Notifications", tabs, activeTab, onTabChange, emptyTitle = "No notifications", emptySubtitle = "Notifications such as mentions and important updates will be displayed here.", emptyAction, tabsLabel = "Notification views", className, ...rest }) {
49
49
  const listRef = React.useRef(null);
50
50
  // crisp's shared tablist model, the one `Tabs` runs (#195). Hand-rolling a
51
51
  // second one is how Inbox ended up with no arrow keys, no roving tabindex
@@ -56,6 +56,6 @@ function Inbox({ notifications = [], title = "Notifications", tabs, activeTab, o
56
56
  select: (id) => onTabChange?.(id),
57
57
  listRef,
58
58
  });
59
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, inbox_recipe_1.inboxRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "inbox-head", children: [(0, jsx_runtime_1.jsx)("div", { className: "inbox-title", children: title }), tabs && tabs.length > 0 && ((0, jsx_runtime_1.jsx)("div", { ref: listRef, className: "inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && ((0, jsx_runtime_1.jsxs)("span", { className: "inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "inbox-empty", children: [(0, jsx_runtime_1.jsx)("span", { className: "inbox-empty-icon", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Bell, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-title", children: emptyTitle }), emptySubtitle != null && ((0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-sub", children: emptySubtitle })), emptyAction != null && ((0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-action", children: emptyAction }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "inbox-list", children: notifications.map((n) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "inbox-row", onClick: n.onClick, children: [n.avatar != null && ((0, jsx_runtime_1.jsx)("span", { className: "inbox-row-avatar", children: n.avatar })), (0, jsx_runtime_1.jsxs)("span", { className: "inbox-row-body", children: [(0, jsx_runtime_1.jsx)("span", { className: "inbox-row-msg", children: n.message }), n.time != null && ((0, jsx_runtime_1.jsx)("span", { className: "inbox-row-time", children: n.time }))] }), n.unread && (0, jsx_runtime_1.jsx)("span", { className: "inbox-row-unread" })] }, n.id))) }))] }));
59
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, inbox_recipe_1.inboxRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "inbox-head", children: [(0, jsx_runtime_1.jsx)("div", { className: "inbox-title", children: title }), tabs && tabs.length > 0 && ((0, jsx_runtime_1.jsx)("div", { ref: listRef, className: "inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && ((0, jsx_runtime_1.jsxs)("span", { className: "inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "inbox-empty", children: [(0, jsx_runtime_1.jsx)("span", { className: "inbox-empty-icon", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Bell, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-title", children: emptyTitle }), emptySubtitle != null && ((0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-sub", children: emptySubtitle })), emptyAction != null && ((0, jsx_runtime_1.jsx)("div", { className: "inbox-empty-action", children: emptyAction }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "inbox-list", children: notifications.map((n) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "inbox-row", onClick: n.onClick, children: [n.avatar != null && ((0, jsx_runtime_1.jsx)("span", { className: "inbox-row-avatar", children: n.avatar })), (0, jsx_runtime_1.jsxs)("span", { className: "inbox-row-body", children: [(0, jsx_runtime_1.jsx)("span", { className: "inbox-row-msg", children: n.message }), n.time != null && ((0, jsx_runtime_1.jsx)("span", { className: "inbox-row-time", children: n.time }))] }), n.unread && (0, jsx_runtime_1.jsx)("span", { className: "inbox-row-unread" })] }, n.id))) }))] }));
60
60
  }
61
61
  Inbox.displayName = "Inbox";
@@ -18,8 +18,8 @@ function ListRow({ children, onClick, }) {
18
18
  * an optional dashed Favorites region, then column headers and time-grouped
19
19
  * rows. Rows are composed by the caller (wrap each in <ListRow>).
20
20
  */
21
- function ListPage({ leading, trailing, count, favorites, favoritesLabel = "Favorites", columns, groups, emptyLabel = "Nothing here yet", className, }) {
21
+ function ListPage({ leading, trailing, count, favorites, favoritesLabel = "Favorites", columns, groups, emptyLabel = "Nothing here yet", className, ...rest }) {
22
22
  const total = groups?.reduce((s, g) => s + g.rows.length, 0) ?? 0;
23
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, listpage_recipe_1.listpageRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "listpage-toolbar", children: [(0, jsx_runtime_1.jsx)("div", { className: "listpage-toolbar-lead", children: leading }), count != null && (0, jsx_runtime_1.jsx)("div", { className: "listpage-count", children: count }), trailing != null && ((0, jsx_runtime_1.jsx)("div", { className: "listpage-toolbar-trail", children: trailing }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "listpage-body", children: [favorites != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "listpage-section-label", children: favoritesLabel }), (0, jsx_runtime_1.jsx)("div", { className: "listpage-favorites", "data-dashed": "", children: favorites })] })), columns && columns.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "listpage-colhead", children: columns.map((col, i) => ((0, jsx_runtime_1.jsxs)("span", { className: "listpage-colhead-cell", children: [col.icon, col.label] }, i))) })), total === 0 && !groups?.length ? ((0, jsx_runtime_1.jsx)("div", { className: "listpage-empty", children: emptyLabel })) : (groups?.map((g) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "listpage-group", children: [g.label, g.count != null && ((0, jsx_runtime_1.jsx)("span", { className: "listpage-group-count", children: g.count }))] }), g.rows] }, g.id))))] })] }));
23
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, listpage_recipe_1.listpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "listpage-toolbar", children: [(0, jsx_runtime_1.jsx)("div", { className: "listpage-toolbar-lead", children: leading }), count != null && (0, jsx_runtime_1.jsx)("div", { className: "listpage-count", children: count }), trailing != null && ((0, jsx_runtime_1.jsx)("div", { className: "listpage-toolbar-trail", children: trailing }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "listpage-body", children: [favorites != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "listpage-section-label", children: favoritesLabel }), (0, jsx_runtime_1.jsx)("div", { className: "listpage-favorites", "data-dashed": "", children: favorites })] })), columns && columns.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "listpage-colhead", children: columns.map((col, i) => ((0, jsx_runtime_1.jsxs)("span", { className: "listpage-colhead-cell", children: [col.icon, col.label] }, i))) })), total === 0 && !groups?.length ? ((0, jsx_runtime_1.jsx)("div", { className: "listpage-empty", children: emptyLabel })) : (groups?.map((g) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "listpage-group", children: [g.label, g.count != null && ((0, jsx_runtime_1.jsx)("span", { className: "listpage-group-count", children: g.count }))] }), g.rows] }, g.id))))] })] }));
24
24
  }
25
25
  ListPage.displayName = "ListPage";
@@ -19,9 +19,9 @@ exports.onboardingLabels = {
19
19
  * started" widget. Title + optional menu/minimize, a segmented progress bar,
20
20
  * and step rows (completed rows strike through with a brand check).
21
21
  */
22
- function OnboardingChecklist({ title = "Getting started", steps, encouragement = "Keep it up!", onStepClick, onMenu, onMinimize, className, optionsLabel = "Checklist options", minimizeLabel = "Minimize", progressLabel = "Onboarding progress", progressText = exports.onboardingLabels.progressText, completedStepLabel = exports.onboardingLabels.completedStepLabel, }) {
22
+ function OnboardingChecklist({ title = "Getting started", steps, encouragement = "Keep it up!", onStepClick, onMenu, onMinimize, className, optionsLabel = "Checklist options", minimizeLabel = "Minimize", progressLabel = "Onboarding progress", progressText = exports.onboardingLabels.progressText, completedStepLabel = exports.onboardingLabels.completedStepLabel, ...rest }) {
23
23
  const total = steps.length;
24
24
  const done = steps.filter((s) => s.done).length;
25
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, onboarding_recipe_1.onboardingRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "onboarding-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "onboarding-title", children: title }), onMenu && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "onboarding-iconbtn", "aria-label": optionsLabel, onClick: onMenu, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), onMinimize && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "onboarding-iconbtn", "aria-label": minimizeLabel, onClick: onMinimize, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 16, strokeWidth: 1.75 }) }))] }), (0, jsx_runtime_1.jsxs)("span", { className: "onboarding-sub", children: [progressText(done, total), encouragement ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [" \u00B7 ", encouragement] }) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "onboarding-progress", role: "progressbar", "aria-valuenow": done, "aria-valuemin": 0, "aria-valuemax": total, "aria-label": progressLabel, children: Array.from({ length: total }, (_, i) => ((0, jsx_runtime_1.jsx)("span", { className: "onboarding-seg", "data-done": i < done ? "" : undefined }, i))) }), (0, jsx_runtime_1.jsx)("div", { className: "onboarding-steps", children: steps.map((s) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "onboarding-step", "data-done": s.done ? "" : undefined, "aria-label": s.done ? completedStepLabel(s.label) : undefined, disabled: s.done, onClick: s.done ? undefined : () => onStepClick?.(s.id), children: [(0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-icon", children: s.icon }), (0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-label", children: s.label }), (0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-check", children: s.done && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] }, s.id))) })] }));
25
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, onboarding_recipe_1.onboardingRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "onboarding-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "onboarding-title", children: title }), onMenu && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "onboarding-iconbtn", "aria-label": optionsLabel, onClick: onMenu, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), onMinimize && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "onboarding-iconbtn", "aria-label": minimizeLabel, onClick: onMinimize, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 16, strokeWidth: 1.75 }) }))] }), (0, jsx_runtime_1.jsxs)("span", { className: "onboarding-sub", children: [progressText(done, total), encouragement ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [" \u00B7 ", encouragement] }) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "onboarding-progress", role: "progressbar", "aria-valuenow": done, "aria-valuemin": 0, "aria-valuemax": total, "aria-label": progressLabel, children: Array.from({ length: total }, (_, i) => ((0, jsx_runtime_1.jsx)("span", { className: "onboarding-seg", "data-done": i < done ? "" : undefined }, i))) }), (0, jsx_runtime_1.jsx)("div", { className: "onboarding-steps", children: steps.map((s) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "onboarding-step", "data-done": s.done ? "" : undefined, "aria-label": s.done ? completedStepLabel(s.label) : undefined, disabled: s.done, onClick: s.done ? undefined : () => onStepClick?.(s.id), children: [(0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-icon", children: s.icon }), (0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-label", children: s.label }), (0, jsx_runtime_1.jsx)("span", { className: "onboarding-step-check", children: s.done && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] }, s.id))) })] }));
26
26
  }
27
27
  OnboardingChecklist.displayName = "OnboardingChecklist";
@@ -190,7 +190,7 @@ function EmptyCard({ icon, children, }) {
190
190
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
191
191
  * into the app's main content area.
192
192
  */
193
- function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, }) {
193
+ function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, ...rest }) {
194
194
  const t = React.useMemo(() => ({
195
195
  ...exports.recordPageLabels,
196
196
  ...labels,
@@ -392,7 +392,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
392
392
  return ((0, jsx_runtime_1.jsx)("div", { className: "recordpage-panel", children: (0, jsx_runtime_1.jsx)(EmptyCard, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Folder, { size: 22, strokeWidth: 1.5 }), children: t.nothingHereYet }) }));
393
393
  }
394
394
  };
395
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-action recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-action recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-action recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
395
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-action recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-action recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-action recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
396
396
  // biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
397
397
  autoFocus: true, type: "text", className: "recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
398
398
  if (e.key === "Escape") {
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.defaultRows = exports.defaultColumns = exports.recordsTableLabels = void 0;
37
37
  exports.tagTone = tagTone;
38
+ exports.parseCellDate = parseCellDate;
39
+ exports.formatCellDate = formatCellDate;
38
40
  exports.RecordsTable = RecordsTable;
39
41
  const jsx_runtime_1 = require("react/jsx-runtime");
40
42
  const lucide_react_1 = require("lucide-react");
@@ -322,10 +324,37 @@ function TagsCell({ column, row, options, onChange, labels, }) {
322
324
  const value = row[column.key] ?? [];
323
325
  return ((0, jsx_runtime_1.jsx)(multiselect_1.MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder: labels.searchOptionsPlaceholder, "aria-label": column.label }));
324
326
  }
327
+ /**
328
+ * A date-only cell is a CALENDAR DATE, not an instant. ECMA-262 parses the
329
+ * date-only ISO forms — `1998`, `1998-09`, `1998-09-04` — as UTC midnight, and
330
+ * the field then renders that instant in the reader's own timezone. Attio's
331
+ * own `founded: "1998"` therefore read **Dec 31, 1997** for every reader west
332
+ * of UTC, at every hour of every day, and a date picked east of UTC
333
+ * round-tripped back a day short through `toISOString()` (#220).
334
+ *
335
+ * A string that carries a time is left alone: that one really is an instant,
336
+ * and shifting it would move the moment it names.
337
+ */
338
+ const DATE_ONLY = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
339
+ function parseCellDate(raw) {
340
+ if (!raw)
341
+ return null;
342
+ const m = DATE_ONLY.exec(raw);
343
+ // the local-midnight constructor, so the calendar day survives the render
344
+ const d = m
345
+ ? new Date(Number(m[1]), Number(m[2] ?? "1") - 1, Number(m[3] ?? "1"))
346
+ : new Date(raw);
347
+ return Number.isNaN(d.getTime()) ? null : d;
348
+ }
349
+ /** The inverse: the picked date's own calendar day, not the UTC one. */
350
+ function formatCellDate(d) {
351
+ const pad = (n) => String(n).padStart(2, "0");
352
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
353
+ }
325
354
  function DateCell({ column, row, onChange, }) {
326
355
  const raw = row[column.key];
327
- const value = raw ? new Date(raw) : null;
328
- return ((0, jsx_runtime_1.jsx)(datepickerfield_1.DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(d.toISOString().slice(0, 10)) }));
356
+ const value = parseCellDate(raw);
357
+ return ((0, jsx_runtime_1.jsx)(datepickerfield_1.DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(formatCellDate(d)) }));
329
358
  }
330
359
  /**
331
360
  * The "New <object>" create modal — measured from Attio's create-record dialog:
@@ -395,7 +424,7 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
395
424
  * and a floating bulk-action bar (delete / deselect) when rows are selected.
396
425
  * Selection can be controlled via `selectedIds`/`onSelectionChange`.
397
426
  */
398
- function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, }) {
427
+ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, ...rest }) {
399
428
  const [cols, setCols] = React.useState(columnsProp);
400
429
  const [data, setData] = React.useState(rowsProp);
401
430
  // The table owns `cols`/`data` so it can rename a column, edit a cell, or
@@ -922,7 +951,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
922
951
  window.addEventListener("pointermove", onMove);
923
952
  window.addEventListener("pointerup", onUp);
924
953
  };
925
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordstable_recipe_1.recordstableRecipe)({ className }), ref: rootRef, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordstable-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `recordstable-tool${sorts.length ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sorts.length
954
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordstable_recipe_1.recordstableRecipe)({ className }), ref: rootRef, ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordstable-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `recordstable-tool${sorts.length ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sorts.length
926
955
  ? t.sortedBy(cols.find((c) => c.key === sorts[0].key)?.label ?? "", sorts.length - 1)
927
956
  : t.sort] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-menu recordstable-sortpop", role: "dialog", "aria-label": t.sort, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "recordstable-sort-empty", children: t.noSorts })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-sort-row", onDragOver: (e) => {
928
957
  if (dragSort !== null)
@@ -45,7 +45,7 @@ const exec = (cmd, value) => document.execCommand(cmd, false, value);
45
45
  * that appears over a text selection. Built on contentEditable, dependency-free.
46
46
  * Uncontrolled: seed with `defaultTitle`/`defaultHtml`, read via `onChange`.
47
47
  */
48
- function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", }) {
48
+ function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", ...rest }) {
49
49
  const titleRef = React.useRef(null);
50
50
  const bodyRef = React.useRef(null);
51
51
  const [bar, setBar] = React.useState(null);
@@ -142,7 +142,7 @@ function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder
142
142
  */
143
143
  const tool = (key, label, icon, onClick, toggles = true) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "rte-tool", "aria-label": label, "aria-pressed": toggles ? !!active[key] : undefined, "data-active": active[key] ? "" : undefined, onMouseDown: keepSel, onClick: onClick, children: icon }));
144
144
  const ic = { size: 15, strokeWidth: 2 };
145
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, richtext_recipe_1.richtextRecipe)({ className }), children: [(0, jsx_runtime_1.jsx)("div", { ref: titleRef, className: "rte-title", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-label": titleLabel, "data-placeholder": titlePlaceholder, onInput: () => {
145
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, richtext_recipe_1.richtextRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsx)("div", { ref: titleRef, className: "rte-title", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-label": titleLabel, "data-placeholder": titlePlaceholder, onInput: () => {
146
146
  syncEmpty(titleRef.current);
147
147
  emit();
148
148
  } }), (0, jsx_runtime_1.jsx)("div", { ref: bodyRef, className: "rte-body", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-multiline": "true", "aria-label": bodyLabel, "data-placeholder": placeholder, onInput: () => {
@@ -11,9 +11,9 @@ function Row({ task, onToggle, completeLabel, incompleteLabel, }) {
11
11
  * count, then rows of a round complete-checkbox + title + trailing meta.
12
12
  * Completed rows strike through; overdue dates go red.
13
13
  */
14
- function TaskList({ tasks, groups, onToggle, emptyLabel = "No tasks", className, completeLabel = "Mark complete", incompleteLabel = "Mark incomplete", }) {
14
+ function TaskList({ tasks, groups, onToggle, emptyLabel = "No tasks", className, completeLabel = "Mark complete", incompleteLabel = "Mark incomplete", ...rest }) {
15
15
  const resolved = groups ?? (tasks?.length ? [{ label: null, tasks }] : []);
16
16
  const total = resolved.reduce((s, g) => s + g.tasks.length, 0);
17
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, tasklist_recipe_1.tasklistRecipe)({ className }), children: total === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "tasklist-empty", children: emptyLabel })) : (resolved.map((g, gi) => ((0, jsx_runtime_1.jsxs)("div", { children: [g.label != null && ((0, jsx_runtime_1.jsxs)("div", { className: "tasklist-group", children: [g.label, (0, jsx_runtime_1.jsx)("span", { className: "tasklist-group-count", children: g.tasks.length })] })), g.tasks.map((t) => ((0, jsx_runtime_1.jsx)(Row, { task: t, onToggle: onToggle, completeLabel: completeLabel, incompleteLabel: incompleteLabel }, t.id)))] }, gi)))) }));
17
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, tasklist_recipe_1.tasklistRecipe)({ className }), ...rest, children: total === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "tasklist-empty", children: emptyLabel })) : (resolved.map((g, gi) => ((0, jsx_runtime_1.jsxs)("div", { children: [g.label != null && ((0, jsx_runtime_1.jsxs)("div", { className: "tasklist-group", children: [g.label, (0, jsx_runtime_1.jsx)("span", { className: "tasklist-group-count", children: g.tasks.length })] })), g.tasks.map((t) => ((0, jsx_runtime_1.jsx)(Row, { task: t, onToggle: onToggle, completeLabel: completeLabel, incompleteLabel: incompleteLabel }, t.id)))] }, gi)))) }));
18
18
  }
19
19
  TaskList.displayName = "TaskList";
@@ -3,6 +3,7 @@ import * as dialog from "@zag-js/dialog";
3
3
  import { normalizeProps, Portal, useMachine } from "@zag-js/react";
4
4
  import { X } from "lucide-react";
5
5
  import * as React from "react";
6
+ import { cx, withDescribedBy } from "../../core/index.js";
6
7
  import { dialogRecipe } from "./dialog.recipe.js";
7
8
  // A globally unique id per mounted instance (see command.tsx for the rationale —
8
9
  // the machine defers its dismissal listeners a frame, so tree-relative useId
@@ -21,7 +22,7 @@ function useDialogInstanceId() {
21
22
  * Escape / outside-click dismissal) with the measured Attio modal surface.
22
23
  * Compose the body with `DialogBody` and actions with `DialogFooter`.
23
24
  */
24
- export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
25
+ export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, className, style, "aria-describedby": describedBy, ...rest }) {
25
26
  const id = useDialogInstanceId();
26
27
  const service = useMachine(dialog.machine, {
27
28
  id,
@@ -37,7 +38,10 @@ export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-lab
37
38
  const api = dialog.connect(service, normalizeProps);
38
39
  if (!api.open)
39
40
  return null;
40
- return (_jsx(Portal, { children: _jsxs("div", { className: dialogRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: _jsx("div", { ...api.getContentProps(), className: "dialog-content", "data-crisp-dialog": id, style: { maxWidth: `min(${width}px, 90vw)` }, children: _jsxs("div", { className: "dialog-card", children: [title != null && (_jsxs("div", { className: "dialog-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
41
+ return (_jsx(Portal, { children: _jsxs("div", { className: dialogRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(api.getContentProps(), describedBy), className: cx("dialog-content", className), "data-crisp-dialog": id,
42
+ // `width` is the component's own knob, so it wins over a passed
43
+ // maxWidth; everything else a consumer sets survives
44
+ style: { ...style, maxWidth: `min(${width}px, 90vw)` }, children: _jsxs("div", { className: "dialog-card", children: [title != null && (_jsxs("div", { className: "dialog-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
41
45
  }
42
46
  Dialog.displayName = "Dialog";
43
47
  export function DialogBody({ children, className, }) {
@@ -3,6 +3,7 @@ import * as dialog from "@zag-js/dialog";
3
3
  import { normalizeProps, Portal, useMachine } from "@zag-js/react";
4
4
  import { X } from "lucide-react";
5
5
  import * as React from "react";
6
+ import { cx, withDescribedBy } from "../../core/index.js";
6
7
  import { drawerRecipe } from "./drawer.recipe.js";
7
8
  let instanceCounter = 0;
8
9
  function useDrawerInstanceId() {
@@ -18,7 +19,7 @@ function useDrawerInstanceId() {
18
19
  * Escape / outside-click dismissal) with a panel that slides in from the edge.
19
20
  * Compose the body with `DrawerBody` and actions with `DrawerFooter`.
20
21
  */
21
- export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
22
+ export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, className, "aria-describedby": describedBy, ...rest }) {
22
23
  const id = useDrawerInstanceId();
23
24
  const service = useMachine(dialog.machine, {
24
25
  id,
@@ -31,7 +32,7 @@ export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-
31
32
  const api = dialog.connect(service, normalizeProps);
32
33
  if (!api.open)
33
34
  return null;
34
- return (_jsx(Portal, { children: _jsxs("div", { className: drawerRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: _jsxs("div", { ...api.getContentProps(), className: "drawer-content", children: [title != null && (_jsxs("div", { className: "drawer-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
35
+ return (_jsx(Portal, { children: _jsxs("div", { className: drawerRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: _jsxs("div", { ...rest, ...withDescribedBy(api.getContentProps(), describedBy), className: cx("drawer-content", className), children: [title != null && (_jsxs("div", { className: "drawer-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
35
36
  }
36
37
  Drawer.displayName = "Drawer";
37
38
  export function DrawerBody({ children }) {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Zag's dialog machine always advertises a description: `getContentProps()`
3
+ * returns an `aria-describedby` pointing at the id of a description element.
4
+ * crisp renders no such element — Attio's modals have a title and a body, no
5
+ * description line — so the reference dangles, and a dangling id is not a
6
+ * relationship a screen reader can follow.
7
+ *
8
+ * It also silenced the consumer's own: the machine's props are spread after
9
+ * anything passed in, so a real `aria-describedby` was overwritten by the
10
+ * missing one every time (#223).
11
+ *
12
+ * Drop the machine's value, and let the caller's stand when there is one. A
13
+ * consumer who wants a description points at a node inside their own children,
14
+ * which is the only place one exists.
15
+ */
16
+ export function withDescribedBy(contentProps, describedBy) {
17
+ const { "aria-describedby": _machine, ...rest } = contentProps;
18
+ return describedBy ? { ...rest, "aria-describedby": describedBy } : rest;
19
+ }
@@ -1,5 +1,6 @@
1
1
  export { cx } from "./cx.js";
2
2
  export { defineRecipe } from "./define.js";
3
+ export { withDescribedBy } from "./dialog-description.js";
3
4
  export { isRtlElement } from "./direction.js";
4
5
  export { placeFloating, } from "./place-floating.js";
5
6
  export { Primitive } from "./primitive.js";
@@ -5,6 +5,7 @@ import { normalizeProps, Portal, useMachine } from "@zag-js/react";
5
5
  import { Building2, CircleDollarSign, House, ListTodo, Search, Settings, SquareCheck, User, UserPlus, } from "lucide-react";
6
6
  import * as React from "react";
7
7
  import { Kbd } from "../../components/kbd/index.js";
8
+ import { cx, withDescribedBy } from "../../core/index.js";
8
9
  import { commandRecipe } from "./command.recipe.js";
9
10
  const IconHome = () => (_jsx(House, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
10
11
  const IconTasks = () => (_jsx(SquareCheck, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
@@ -109,7 +110,7 @@ function useCommandInstanceId() {
109
110
  * option list) so keyboard/focus correctness comes from the machines, not
110
111
  * hand-rolled event handling.
111
112
  */
112
- export function Command({ open, onOpenChange, items = defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, }) {
113
+ export function Command({ open, onOpenChange, items = defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, className, "aria-describedby": describedBy, ...rest }) {
113
114
  const instanceId = useCommandInstanceId();
114
115
  const dialogId = `${instanceId}-dialog`;
115
116
  const comboId = `${instanceId}-combobox`;
@@ -193,7 +194,7 @@ export function Command({ open, onOpenChange, items = defaultCommandItems, place
193
194
  };
194
195
  if (!dlg.open)
195
196
  return null;
196
- 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", { ...dlg.getContentProps(), className: "command-content", 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(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
198
  const Icon = item.icon;
198
199
  const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
199
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));
@@ -9,7 +9,7 @@ const clampZoom = (z) => Math.min(1.5, Math.max(0.5, z));
9
9
  * measured from Attio's Workflows editor. Drag a node to move it (fires
10
10
  * `onNodesChange`); drag the background to pan; use the toolbar to zoom.
11
11
  */
12
- export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", }) {
12
+ export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", ...rest }) {
13
13
  const [pan, setPan] = React.useState({ x: 40, y: 40 });
14
14
  const [zoom, setZoom] = React.useState(1);
15
15
  const [panning, setPanning] = React.useState(false);
@@ -57,7 +57,11 @@ export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth
57
57
  drag.current = null;
58
58
  setPanning(false);
59
59
  };
60
- return (_jsxs("div", { className: flowcanvasRecipe({ className }), "data-panning": panning ? "" : undefined, style: {
60
+ return (_jsxs("div", { className: flowcanvasRecipe({ className }), ...rest, "data-panning": panning ? "" : undefined,
61
+ // the grid follows the pan, so those two keys are the component's own;
62
+ // everything else a consumer sets survives
63
+ style: {
64
+ ...rest.style,
61
65
  backgroundSize: `${GRID * zoom}px ${GRID * zoom}px`,
62
66
  backgroundPosition: `${pan.x}px ${pan.y}px`,
63
67
  }, role: "application", "aria-label": ariaLabel ?? "Flow canvas", onPointerDown: onBackgroundPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, children: [_jsxs("div", { className: "flowcanvas-viewport", style: {
@@ -9,7 +9,7 @@ import { inboxRecipe } from "./inbox.recipe.js";
9
9
  * (avatar + message + time + unread dot) or a centred empty state. Put it inside
10
10
  * a Popover for the flyout.
11
11
  */
12
- export function Inbox({ notifications = [], title = "Notifications", tabs, activeTab, onTabChange, emptyTitle = "No notifications", emptySubtitle = "Notifications such as mentions and important updates will be displayed here.", emptyAction, tabsLabel = "Notification views", className, }) {
12
+ export function Inbox({ notifications = [], title = "Notifications", tabs, activeTab, onTabChange, emptyTitle = "No notifications", emptySubtitle = "Notifications such as mentions and important updates will be displayed here.", emptyAction, tabsLabel = "Notification views", className, ...rest }) {
13
13
  const listRef = React.useRef(null);
14
14
  // crisp's shared tablist model, the one `Tabs` runs (#195). Hand-rolling a
15
15
  // second one is how Inbox ended up with no arrow keys, no roving tabindex
@@ -20,6 +20,6 @@ export function Inbox({ notifications = [], title = "Notifications", tabs, activ
20
20
  select: (id) => onTabChange?.(id),
21
21
  listRef,
22
22
  });
23
- return (_jsxs("div", { className: inboxRecipe({ className }), children: [_jsxs("div", { className: "inbox-head", children: [_jsx("div", { className: "inbox-title", children: title }), tabs && tabs.length > 0 && (_jsx("div", { ref: listRef, className: "inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && (_jsxs("span", { className: "inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? (_jsxs("div", { className: "inbox-empty", children: [_jsx("span", { className: "inbox-empty-icon", children: _jsx(Bell, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "inbox-empty-title", children: emptyTitle }), emptySubtitle != null && (_jsx("div", { className: "inbox-empty-sub", children: emptySubtitle })), emptyAction != null && (_jsx("div", { className: "inbox-empty-action", children: emptyAction }))] })) : (_jsx("div", { className: "inbox-list", children: notifications.map((n) => (_jsxs("button", { type: "button", className: "inbox-row", onClick: n.onClick, children: [n.avatar != null && (_jsx("span", { className: "inbox-row-avatar", children: n.avatar })), _jsxs("span", { className: "inbox-row-body", children: [_jsx("span", { className: "inbox-row-msg", children: n.message }), n.time != null && (_jsx("span", { className: "inbox-row-time", children: n.time }))] }), n.unread && _jsx("span", { className: "inbox-row-unread" })] }, n.id))) }))] }));
23
+ return (_jsxs("div", { className: inboxRecipe({ className }), ...rest, children: [_jsxs("div", { className: "inbox-head", children: [_jsx("div", { className: "inbox-title", children: title }), tabs && tabs.length > 0 && (_jsx("div", { ref: listRef, className: "inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && (_jsxs("span", { className: "inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? (_jsxs("div", { className: "inbox-empty", children: [_jsx("span", { className: "inbox-empty-icon", children: _jsx(Bell, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "inbox-empty-title", children: emptyTitle }), emptySubtitle != null && (_jsx("div", { className: "inbox-empty-sub", children: emptySubtitle })), emptyAction != null && (_jsx("div", { className: "inbox-empty-action", children: emptyAction }))] })) : (_jsx("div", { className: "inbox-list", children: notifications.map((n) => (_jsxs("button", { type: "button", className: "inbox-row", onClick: n.onClick, children: [n.avatar != null && (_jsx("span", { className: "inbox-row-avatar", children: n.avatar })), _jsxs("span", { className: "inbox-row-body", children: [_jsx("span", { className: "inbox-row-msg", children: n.message }), n.time != null && (_jsx("span", { className: "inbox-row-time", children: n.time }))] }), n.unread && _jsx("span", { className: "inbox-row-unread" })] }, n.id))) }))] }));
24
24
  }
25
25
  Inbox.displayName = "Inbox";
@@ -14,8 +14,8 @@ export function ListRow({ children, onClick, }) {
14
14
  * an optional dashed Favorites region, then column headers and time-grouped
15
15
  * rows. Rows are composed by the caller (wrap each in <ListRow>).
16
16
  */
17
- export function ListPage({ leading, trailing, count, favorites, favoritesLabel = "Favorites", columns, groups, emptyLabel = "Nothing here yet", className, }) {
17
+ export function ListPage({ leading, trailing, count, favorites, favoritesLabel = "Favorites", columns, groups, emptyLabel = "Nothing here yet", className, ...rest }) {
18
18
  const total = groups?.reduce((s, g) => s + g.rows.length, 0) ?? 0;
19
- return (_jsxs("div", { className: listpageRecipe({ className }), children: [_jsxs("div", { className: "listpage-toolbar", children: [_jsx("div", { className: "listpage-toolbar-lead", children: leading }), count != null && _jsx("div", { className: "listpage-count", children: count }), trailing != null && (_jsx("div", { className: "listpage-toolbar-trail", children: trailing }))] }), _jsxs("div", { className: "listpage-body", children: [favorites != null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "listpage-section-label", children: favoritesLabel }), _jsx("div", { className: "listpage-favorites", "data-dashed": "", children: favorites })] })), columns && columns.length > 0 && (_jsx("div", { className: "listpage-colhead", children: columns.map((col, i) => (_jsxs("span", { className: "listpage-colhead-cell", children: [col.icon, col.label] }, i))) })), total === 0 && !groups?.length ? (_jsx("div", { className: "listpage-empty", children: emptyLabel })) : (groups?.map((g) => (_jsxs("div", { children: [_jsxs("div", { className: "listpage-group", children: [g.label, g.count != null && (_jsx("span", { className: "listpage-group-count", children: g.count }))] }), g.rows] }, g.id))))] })] }));
19
+ return (_jsxs("div", { className: listpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "listpage-toolbar", children: [_jsx("div", { className: "listpage-toolbar-lead", children: leading }), count != null && _jsx("div", { className: "listpage-count", children: count }), trailing != null && (_jsx("div", { className: "listpage-toolbar-trail", children: trailing }))] }), _jsxs("div", { className: "listpage-body", children: [favorites != null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "listpage-section-label", children: favoritesLabel }), _jsx("div", { className: "listpage-favorites", "data-dashed": "", children: favorites })] })), columns && columns.length > 0 && (_jsx("div", { className: "listpage-colhead", children: columns.map((col, i) => (_jsxs("span", { className: "listpage-colhead-cell", children: [col.icon, col.label] }, i))) })), total === 0 && !groups?.length ? (_jsx("div", { className: "listpage-empty", children: emptyLabel })) : (groups?.map((g) => (_jsxs("div", { children: [_jsxs("div", { className: "listpage-group", children: [g.label, g.count != null && (_jsx("span", { className: "listpage-group-count", children: g.count }))] }), g.rows] }, g.id))))] })] }));
20
20
  }
21
21
  ListPage.displayName = "ListPage";
@@ -15,9 +15,9 @@ export const onboardingLabels = {
15
15
  * started" widget. Title + optional menu/minimize, a segmented progress bar,
16
16
  * and step rows (completed rows strike through with a brand check).
17
17
  */
18
- export function OnboardingChecklist({ title = "Getting started", steps, encouragement = "Keep it up!", onStepClick, onMenu, onMinimize, className, optionsLabel = "Checklist options", minimizeLabel = "Minimize", progressLabel = "Onboarding progress", progressText = onboardingLabels.progressText, completedStepLabel = onboardingLabels.completedStepLabel, }) {
18
+ export function OnboardingChecklist({ title = "Getting started", steps, encouragement = "Keep it up!", onStepClick, onMenu, onMinimize, className, optionsLabel = "Checklist options", minimizeLabel = "Minimize", progressLabel = "Onboarding progress", progressText = onboardingLabels.progressText, completedStepLabel = onboardingLabels.completedStepLabel, ...rest }) {
19
19
  const total = steps.length;
20
20
  const done = steps.filter((s) => s.done).length;
21
- return (_jsxs("div", { className: onboardingRecipe({ className }), children: [_jsxs("div", { className: "onboarding-head", children: [_jsx("span", { className: "onboarding-title", children: title }), onMenu && (_jsx("button", { type: "button", className: "onboarding-iconbtn", "aria-label": optionsLabel, onClick: onMenu, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), onMinimize && (_jsx("button", { type: "button", className: "onboarding-iconbtn", "aria-label": minimizeLabel, onClick: onMinimize, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) }))] }), _jsxs("span", { className: "onboarding-sub", children: [progressText(done, total), encouragement ? _jsxs(_Fragment, { children: [" \u00B7 ", encouragement] }) : null] }), _jsx("div", { className: "onboarding-progress", role: "progressbar", "aria-valuenow": done, "aria-valuemin": 0, "aria-valuemax": total, "aria-label": progressLabel, children: Array.from({ length: total }, (_, i) => (_jsx("span", { className: "onboarding-seg", "data-done": i < done ? "" : undefined }, i))) }), _jsx("div", { className: "onboarding-steps", children: steps.map((s) => (_jsxs("button", { type: "button", className: "onboarding-step", "data-done": s.done ? "" : undefined, "aria-label": s.done ? completedStepLabel(s.label) : undefined, disabled: s.done, onClick: s.done ? undefined : () => onStepClick?.(s.id), children: [_jsx("span", { className: "onboarding-step-icon", children: s.icon }), _jsx("span", { className: "onboarding-step-label", children: s.label }), _jsx("span", { className: "onboarding-step-check", children: s.done && _jsx(Check, { size: 12, strokeWidth: 3 }) })] }, s.id))) })] }));
21
+ return (_jsxs("div", { className: onboardingRecipe({ className }), ...rest, children: [_jsxs("div", { className: "onboarding-head", children: [_jsx("span", { className: "onboarding-title", children: title }), onMenu && (_jsx("button", { type: "button", className: "onboarding-iconbtn", "aria-label": optionsLabel, onClick: onMenu, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), onMinimize && (_jsx("button", { type: "button", className: "onboarding-iconbtn", "aria-label": minimizeLabel, onClick: onMinimize, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) }))] }), _jsxs("span", { className: "onboarding-sub", children: [progressText(done, total), encouragement ? _jsxs(_Fragment, { children: [" \u00B7 ", encouragement] }) : null] }), _jsx("div", { className: "onboarding-progress", role: "progressbar", "aria-valuenow": done, "aria-valuemin": 0, "aria-valuemax": total, "aria-label": progressLabel, children: Array.from({ length: total }, (_, i) => (_jsx("span", { className: "onboarding-seg", "data-done": i < done ? "" : undefined }, i))) }), _jsx("div", { className: "onboarding-steps", children: steps.map((s) => (_jsxs("button", { type: "button", className: "onboarding-step", "data-done": s.done ? "" : undefined, "aria-label": s.done ? completedStepLabel(s.label) : undefined, disabled: s.done, onClick: s.done ? undefined : () => onStepClick?.(s.id), children: [_jsx("span", { className: "onboarding-step-icon", children: s.icon }), _jsx("span", { className: "onboarding-step-label", children: s.label }), _jsx("span", { className: "onboarding-step-check", children: s.done && _jsx(Check, { size: 12, strokeWidth: 3 }) })] }, s.id))) })] }));
22
22
  }
23
23
  OnboardingChecklist.displayName = "OnboardingChecklist";
@@ -153,7 +153,7 @@ function EmptyCard({ icon, children, }) {
153
153
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
154
154
  * into the app's main content area.
155
155
  */
156
- export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, }) {
156
+ export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, ...rest }) {
157
157
  const t = React.useMemo(() => ({
158
158
  ...recordPageLabels,
159
159
  ...labels,
@@ -355,7 +355,7 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
355
355
  return (_jsx("div", { className: "recordpage-panel", children: _jsx(EmptyCard, { icon: _jsx(Folder, { size: 22, strokeWidth: 1.5 }), children: t.nothingHereYet }) }));
356
356
  }
357
357
  };
358
- return (_jsxs("div", { className: recordpageRecipe({ className }), children: [_jsxs("div", { className: "recordpage-topbar", children: [_jsxs("nav", { className: "recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "recordpage-crumb-name", children: name })] }), _jsxs("div", { className: "recordpage-actions", children: [_jsxs("button", { type: "button", className: "recordpage-action recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "recordpage-action recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "recordpage-action recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), _jsx(MenuItem, { icon: _jsx(Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "recordpage-body", children: [_jsxs("aside", { className: "recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.copyRecordLink, children: _jsx(Copy, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.logANote, children: _jsx(FileText, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.linkRecords, children: _jsx(Share2, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.createATask, children: _jsx(SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "recordpage-section", children: [_jsxs("div", { className: "recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
358
+ return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "recordpage-topbar", children: [_jsxs("nav", { className: "recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "recordpage-crumb-name", children: name })] }), _jsxs("div", { className: "recordpage-actions", children: [_jsxs("button", { type: "button", className: "recordpage-action recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "recordpage-action recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "recordpage-action recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), _jsx(MenuItem, { icon: _jsx(Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "recordpage-body", children: [_jsxs("aside", { className: "recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.copyRecordLink, children: _jsx(Copy, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.logANote, children: _jsx(FileText, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.linkRecords, children: _jsx(Share2, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "recordpage-quickbtn", "aria-label": t.createATask, children: _jsx(SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "recordpage-section", children: [_jsxs("div", { className: "recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
359
359
  // biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
360
360
  autoFocus: true, type: "text", className: "recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
361
361
  if (e.key === "Escape") {
@@ -284,10 +284,37 @@ function TagsCell({ column, row, options, onChange, labels, }) {
284
284
  const value = row[column.key] ?? [];
285
285
  return (_jsx(MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder: labels.searchOptionsPlaceholder, "aria-label": column.label }));
286
286
  }
287
+ /**
288
+ * A date-only cell is a CALENDAR DATE, not an instant. ECMA-262 parses the
289
+ * date-only ISO forms — `1998`, `1998-09`, `1998-09-04` — as UTC midnight, and
290
+ * the field then renders that instant in the reader's own timezone. Attio's
291
+ * own `founded: "1998"` therefore read **Dec 31, 1997** for every reader west
292
+ * of UTC, at every hour of every day, and a date picked east of UTC
293
+ * round-tripped back a day short through `toISOString()` (#220).
294
+ *
295
+ * A string that carries a time is left alone: that one really is an instant,
296
+ * and shifting it would move the moment it names.
297
+ */
298
+ const DATE_ONLY = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
299
+ export function parseCellDate(raw) {
300
+ if (!raw)
301
+ return null;
302
+ const m = DATE_ONLY.exec(raw);
303
+ // the local-midnight constructor, so the calendar day survives the render
304
+ const d = m
305
+ ? new Date(Number(m[1]), Number(m[2] ?? "1") - 1, Number(m[3] ?? "1"))
306
+ : new Date(raw);
307
+ return Number.isNaN(d.getTime()) ? null : d;
308
+ }
309
+ /** The inverse: the picked date's own calendar day, not the UTC one. */
310
+ export function formatCellDate(d) {
311
+ const pad = (n) => String(n).padStart(2, "0");
312
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
313
+ }
287
314
  function DateCell({ column, row, onChange, }) {
288
315
  const raw = row[column.key];
289
- const value = raw ? new Date(raw) : null;
290
- return (_jsx(DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(d.toISOString().slice(0, 10)) }));
316
+ const value = parseCellDate(raw);
317
+ return (_jsx(DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(formatCellDate(d)) }));
291
318
  }
292
319
  /**
293
320
  * The "New <object>" create modal — measured from Attio's create-record dialog:
@@ -357,7 +384,7 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
357
384
  * and a floating bulk-action bar (delete / deselect) when rows are selected.
358
385
  * Selection can be controlled via `selectedIds`/`onSelectionChange`.
359
386
  */
360
- export function RecordsTable({ labels, columns: columnsProp = defaultColumns, rows: rowsProp = defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, }) {
387
+ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, rows: rowsProp = defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, ...rest }) {
361
388
  const [cols, setCols] = React.useState(columnsProp);
362
389
  const [data, setData] = React.useState(rowsProp);
363
390
  // The table owns `cols`/`data` so it can rename a column, edit a cell, or
@@ -884,7 +911,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
884
911
  window.addEventListener("pointermove", onMove);
885
912
  window.addEventListener("pointerup", onUp);
886
913
  };
887
- return (_jsxs("div", { className: recordstableRecipe({ className }), ref: rootRef, children: [_jsxs("div", { className: "recordstable-toolbar", children: [_jsxs("span", { className: "recordstable-toolgroup", children: [_jsxs("button", { type: "button", className: `recordstable-tool${sorts.length ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [_jsx(ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sorts.length
914
+ return (_jsxs("div", { className: recordstableRecipe({ className }), ref: rootRef, ...rest, children: [_jsxs("div", { className: "recordstable-toolbar", children: [_jsxs("span", { className: "recordstable-toolgroup", children: [_jsxs("button", { type: "button", className: `recordstable-tool${sorts.length ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [_jsx(ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sorts.length
888
915
  ? t.sortedBy(cols.find((c) => c.key === sorts[0].key)?.label ?? "", sorts.length - 1)
889
916
  : t.sort] }), openMenu === "sort" && (_jsxs("div", { className: "recordstable-menu recordstable-sortpop", role: "dialog", "aria-label": t.sort, children: [sorts.length === 0 ? (_jsx("div", { className: "recordstable-sort-empty", children: t.noSorts })) : (sorts.map((s, i) => (_jsxs("div", { className: "recordstable-sort-row", onDragOver: (e) => {
890
917
  if (dragSort !== null)
@@ -9,7 +9,7 @@ const exec = (cmd, value) => document.execCommand(cmd, false, value);
9
9
  * that appears over a text selection. Built on contentEditable, dependency-free.
10
10
  * Uncontrolled: seed with `defaultTitle`/`defaultHtml`, read via `onChange`.
11
11
  */
12
- export function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", }) {
12
+ export function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", ...rest }) {
13
13
  const titleRef = React.useRef(null);
14
14
  const bodyRef = React.useRef(null);
15
15
  const [bar, setBar] = React.useState(null);
@@ -106,7 +106,7 @@ export function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlace
106
106
  */
107
107
  const tool = (key, label, icon, onClick, toggles = true) => (_jsx("button", { type: "button", className: "rte-tool", "aria-label": label, "aria-pressed": toggles ? !!active[key] : undefined, "data-active": active[key] ? "" : undefined, onMouseDown: keepSel, onClick: onClick, children: icon }));
108
108
  const ic = { size: 15, strokeWidth: 2 };
109
- return (_jsxs("div", { className: richtextRecipe({ className }), children: [_jsx("div", { ref: titleRef, className: "rte-title", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-label": titleLabel, "data-placeholder": titlePlaceholder, onInput: () => {
109
+ return (_jsxs("div", { className: richtextRecipe({ className }), ...rest, children: [_jsx("div", { ref: titleRef, className: "rte-title", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-label": titleLabel, "data-placeholder": titlePlaceholder, onInput: () => {
110
110
  syncEmpty(titleRef.current);
111
111
  emit();
112
112
  } }), _jsx("div", { ref: bodyRef, className: "rte-body", contentEditable: true, suppressContentEditableWarning: true, role: "textbox", tabIndex: 0, "aria-multiline": "true", "aria-label": bodyLabel, "data-placeholder": placeholder, onInput: () => {
@@ -8,9 +8,9 @@ function Row({ task, onToggle, completeLabel, incompleteLabel, }) {
8
8
  * count, then rows of a round complete-checkbox + title + trailing meta.
9
9
  * Completed rows strike through; overdue dates go red.
10
10
  */
11
- export function TaskList({ tasks, groups, onToggle, emptyLabel = "No tasks", className, completeLabel = "Mark complete", incompleteLabel = "Mark incomplete", }) {
11
+ export function TaskList({ tasks, groups, onToggle, emptyLabel = "No tasks", className, completeLabel = "Mark complete", incompleteLabel = "Mark incomplete", ...rest }) {
12
12
  const resolved = groups ?? (tasks?.length ? [{ label: null, tasks }] : []);
13
13
  const total = resolved.reduce((s, g) => s + g.tasks.length, 0);
14
- return (_jsx("div", { className: tasklistRecipe({ className }), children: total === 0 ? (_jsx("div", { className: "tasklist-empty", children: emptyLabel })) : (resolved.map((g, gi) => (_jsxs("div", { children: [g.label != null && (_jsxs("div", { className: "tasklist-group", children: [g.label, _jsx("span", { className: "tasklist-group-count", children: g.tasks.length })] })), g.tasks.map((t) => (_jsx(Row, { task: t, onToggle: onToggle, completeLabel: completeLabel, incompleteLabel: incompleteLabel }, t.id)))] }, gi)))) }));
14
+ return (_jsx("div", { className: tasklistRecipe({ className }), ...rest, children: total === 0 ? (_jsx("div", { className: "tasklist-empty", children: emptyLabel })) : (resolved.map((g, gi) => (_jsxs("div", { children: [g.label != null && (_jsxs("div", { className: "tasklist-group", children: [g.label, _jsx("span", { className: "tasklist-group-count", children: g.tasks.length })] })), g.tasks.map((t) => (_jsx(Row, { task: t, onToggle: onToggle, completeLabel: completeLabel, incompleteLabel: incompleteLabel }, t.id)))] }, gi)))) }));
15
15
  }
16
16
  TaskList.displayName = "TaskList";
@@ -1,5 +1,15 @@
1
1
  import * as React from "react";
2
- export type DialogProps = {
2
+ /**
3
+ * Root props land on the DIALOG SURFACE — the element that carries
4
+ * `role="dialog"` — not on the portal wrapper. That is what a consumer needs
5
+ * to reach: `aria-describedby` has to sit beside `aria-modal` to associate the
6
+ * description, and a `data-testid` or a `style` is wanted on the panel, not on
7
+ * the full-viewport layer behind it (#223).
8
+ *
9
+ * `id` and the aria wiring are the machine's: it mints the ids that link the
10
+ * title and the description, so those are spread after anything passed in.
11
+ */
12
+ export type DialogProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" | "children"> & {
3
13
  /** Controlled open state. */
4
14
  open: boolean;
5
15
  onOpenChange: (open: boolean) => void;
@@ -19,7 +29,7 @@ export type DialogProps = {
19
29
  * Escape / outside-click dismissal) with the measured Attio modal surface.
20
30
  * Compose the body with `DialogBody` and actions with `DialogFooter`.
21
31
  */
22
- export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel, children, }: DialogProps): React.JSX.Element | null;
32
+ export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel, children, className, style, "aria-describedby": describedBy, ...rest }: DialogProps): React.JSX.Element | null;
23
33
  export declare namespace Dialog {
24
34
  var displayName: string;
25
35
  }
@@ -1,5 +1,9 @@
1
1
  import * as React from "react";
2
- export type DrawerProps = {
2
+ /**
3
+ * Root props land on the DRAWER SURFACE — the element carrying `role="dialog"`
4
+ * — not on the portal wrapper, for the same reason as Dialog (#223).
5
+ */
6
+ export type DrawerProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" | "children"> & {
3
7
  open: boolean;
4
8
  onOpenChange: (open: boolean) => void;
5
9
  /** Which edge the sheet slides in from (default right). */
@@ -16,7 +20,7 @@ export type DrawerProps = {
16
20
  * Escape / outside-click dismissal) with a panel that slides in from the edge.
17
21
  * Compose the body with `DrawerBody` and actions with `DrawerFooter`.
18
22
  */
19
- export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel, children, }: DrawerProps): React.JSX.Element | null;
23
+ export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel, children, className, "aria-describedby": describedBy, ...rest }: DrawerProps): React.JSX.Element | null;
20
24
  export declare namespace Drawer {
21
25
  var displayName: string;
22
26
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Zag's dialog machine always advertises a description: `getContentProps()`
3
+ * returns an `aria-describedby` pointing at the id of a description element.
4
+ * crisp renders no such element — Attio's modals have a title and a body, no
5
+ * description line — so the reference dangles, and a dangling id is not a
6
+ * relationship a screen reader can follow.
7
+ *
8
+ * It also silenced the consumer's own: the machine's props are spread after
9
+ * anything passed in, so a real `aria-describedby` was overwritten by the
10
+ * missing one every time (#223).
11
+ *
12
+ * Drop the machine's value, and let the caller's stand when there is one. A
13
+ * consumer who wants a description points at a node inside their own children,
14
+ * which is the only place one exists.
15
+ */
16
+ export declare function withDescribedBy<T extends Record<string, unknown>>(contentProps: T, describedBy: string | undefined): Omit<T, "aria-describedby"> & {
17
+ "aria-describedby"?: string;
18
+ };
@@ -1,5 +1,6 @@
1
1
  export { cx } from "./cx";
2
2
  export { defineRecipe } from "./define";
3
+ export { withDescribedBy } from "./dialog-description";
3
4
  export { isRtlElement } from "./direction";
4
5
  export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
5
6
  export { Primitive } from "./primitive";
@@ -13,7 +13,11 @@ export type CommandItem = {
13
13
  };
14
14
  /** crisp's own generic command set (not copied from any product). */
15
15
  export declare const defaultCommandItems: CommandItem[];
16
- export type CommandProps = {
16
+ /**
17
+ * Root props land on the PALETTE SURFACE — the element carrying
18
+ * `role="dialog"` — not on the portal wrapper (#223).
19
+ */
20
+ export type CommandProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onSelect"> & {
17
21
  /** Controlled open state — the caller owns when the palette is shown. */
18
22
  open: boolean;
19
23
  onOpenChange: (open: boolean) => void;
@@ -50,4 +54,4 @@ export type CommandProps = {
50
54
  * option list) so keyboard/focus correctness comes from the machines, not
51
55
  * hand-rolled event handling.
52
56
  */
53
- export declare function Command({ open, onOpenChange, items, placeholder, "aria-label": ariaLabel, navigateLabel, selectLabel, emptyLabel, query: queryProp, onQueryChange, filterItems, }: CommandProps): React.JSX.Element | null;
57
+ export declare function Command({ open, onOpenChange, items, placeholder, "aria-label": ariaLabel, navigateLabel, selectLabel, emptyLabel, query: queryProp, onQueryChange, filterItems, className, "aria-describedby": describedBy, ...rest }: CommandProps): React.JSX.Element | null;
@@ -8,7 +8,7 @@ export type FlowEdge = {
8
8
  from: string;
9
9
  to: string;
10
10
  };
11
- export type FlowCanvasProps<T extends FlowNode> = {
11
+ export type FlowCanvasProps<T extends FlowNode> = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onChange"> & {
12
12
  /** Zoom-out control label. */
13
13
  zoomOutLabel?: string;
14
14
  /** Zoom-in control label. */
@@ -34,7 +34,7 @@ export type FlowCanvasProps<T extends FlowNode> = {
34
34
  * measured from Attio's Workflows editor. Drag a node to move it (fires
35
35
  * `onNodesChange`); drag the background to pan; use the toolbar to zoom.
36
36
  */
37
- export declare function FlowCanvas<T extends FlowNode>({ nodes, edges, onNodesChange, renderNode, nodeWidth, nodeHeight, "aria-label": ariaLabel, className, zoomOutLabel, zoomInLabel, resetLabel, edgesLabel, }: FlowCanvasProps<T>): React.JSX.Element;
37
+ export declare function FlowCanvas<T extends FlowNode>({ nodes, edges, onNodesChange, renderNode, nodeWidth, nodeHeight, "aria-label": ariaLabel, className, zoomOutLabel, zoomInLabel, resetLabel, edgesLabel, ...rest }: FlowCanvasProps<T>): React.JSX.Element;
38
38
  export declare namespace FlowCanvas {
39
39
  var displayName: string;
40
40
  }
@@ -16,7 +16,7 @@ export type InboxTab = {
16
16
  count?: number;
17
17
  disabled?: boolean;
18
18
  };
19
- export type InboxProps = {
19
+ export type InboxProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "title"> & {
20
20
  notifications?: InboxNotification[];
21
21
  title?: React.ReactNode;
22
22
  /** Tabs (e.g. Notifications / Requests). */
@@ -38,7 +38,7 @@ export type InboxProps = {
38
38
  * (avatar + message + time + unread dot) or a centred empty state. Put it inside
39
39
  * a Popover for the flyout.
40
40
  */
41
- export declare function Inbox({ notifications, title, tabs, activeTab, onTabChange, emptyTitle, emptySubtitle, emptyAction, tabsLabel, className, }: InboxProps): React.JSX.Element;
41
+ export declare function Inbox({ notifications, title, tabs, activeTab, onTabChange, emptyTitle, emptySubtitle, emptyAction, tabsLabel, className, ...rest }: InboxProps): React.JSX.Element;
42
42
  export declare namespace Inbox {
43
43
  var displayName: string;
44
44
  }
@@ -10,7 +10,7 @@ export type ListGroup = {
10
10
  /** Pre-composed row nodes (wrap each in <ListRow>). */
11
11
  rows: React.ReactNode[];
12
12
  };
13
- export type ListPageProps = {
13
+ export type ListPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
14
14
  /** Left toolbar slot — e.g. Sort + Filter buttons. */
15
15
  leading?: React.ReactNode;
16
16
  /** Right toolbar slot — e.g. view toggle, View settings, a New button. */
@@ -40,7 +40,7 @@ export declare function ListRow({ children, onClick, }: {
40
40
  * an optional dashed Favorites region, then column headers and time-grouped
41
41
  * rows. Rows are composed by the caller (wrap each in <ListRow>).
42
42
  */
43
- export declare function ListPage({ leading, trailing, count, favorites, favoritesLabel, columns, groups, emptyLabel, className, }: ListPageProps): React.JSX.Element;
43
+ export declare function ListPage({ leading, trailing, count, favorites, favoritesLabel, columns, groups, emptyLabel, className, ...rest }: ListPageProps): React.JSX.Element;
44
44
  export declare namespace ListPage {
45
45
  var displayName: string;
46
46
  }
@@ -7,7 +7,7 @@ export type OnboardingStep = {
7
7
  icon?: React.ReactNode;
8
8
  done?: boolean;
9
9
  };
10
- export type OnboardingChecklistProps = {
10
+ export type OnboardingChecklistProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "title"> & {
11
11
  /** Label for the overflow-menu trigger. */
12
12
  optionsLabel?: string;
13
13
  /** Label for the minimise control. */
@@ -52,7 +52,7 @@ export declare const onboardingLabels: {
52
52
  * started" widget. Title + optional menu/minimize, a segmented progress bar,
53
53
  * and step rows (completed rows strike through with a brand check).
54
54
  */
55
- export declare function OnboardingChecklist({ title, steps, encouragement, onStepClick, onMenu, onMinimize, className, optionsLabel, minimizeLabel, progressLabel, progressText, completedStepLabel, }: OnboardingChecklistProps): React.JSX.Element;
55
+ export declare function OnboardingChecklist({ title, steps, encouragement, onStepClick, onMenu, onMinimize, className, optionsLabel, minimizeLabel, progressLabel, progressText, completedStepLabel, ...rest }: OnboardingChecklistProps): React.JSX.Element;
56
56
  export declare namespace OnboardingChecklist {
57
57
  var displayName: string;
58
58
  }
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { type Column, type Row } from "../recordstable";
3
- export type RecordPageProps = {
3
+ export type RecordPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
4
4
  record: Row;
5
5
  columns: Column[];
6
6
  /** Breadcrumb label + back button target (e.g. "Companies"). */
@@ -97,7 +97,7 @@ export type RecordPageLabelsInput = Partial<Omit<RecordPageLabels, "tabs" | "hig
97
97
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
98
98
  * into the app's main content area.
99
99
  */
100
- export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, }: RecordPageProps): React.JSX.Element;
100
+ export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, ...rest }: RecordPageProps): React.JSX.Element;
101
101
  export declare namespace RecordPage {
102
102
  var displayName: string;
103
103
  }
@@ -73,7 +73,7 @@ export type EntityValue = {
73
73
  export type Row = {
74
74
  id: string;
75
75
  } & Record<string, any>;
76
- export type RecordsTableProps = {
76
+ export type RecordsTableProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
77
77
  /** Partial override of the chrome strings; see `recordsTableLabels`. */
78
78
  labels?: RecordsTableLabelsInput;
79
79
  columns?: Column[];
@@ -187,6 +187,9 @@ export type RecordsTableLabelsInput = Partial<Omit<RecordsTableLabels, "agg" | "
187
187
  op?: Partial<Record<FilterOp, string>>;
188
188
  };
189
189
  type FilterOp = "contains" | "is" | "isnot" | "empty" | "notempty";
190
+ export declare function parseCellDate(raw: string | undefined): Date | null;
191
+ /** The inverse: the picked date's own calendar day, not the UTC one. */
192
+ export declare function formatCellDate(d: Date): string;
190
193
  /**
191
194
  * Attio-style record grid — fully featured. Toolbar (search / sort status /
192
195
  * count / New), sticky checkbox + entity columns, 36px rows, typed cells,
@@ -195,7 +198,7 @@ type FilterOp = "contains" | "is" | "isnot" | "empty" | "notempty";
195
198
  * and a floating bulk-action bar (delete / deselect) when rows are selected.
196
199
  * Selection can be controlled via `selectedIds`/`onSelectionChange`.
197
200
  */
198
- export declare function RecordsTable({ labels, columns: columnsProp, rows: rowsProp, selectedIds, onSelectionChange, showNew, allowAddColumn, onOpenRecord, onOpenRecordPage, avatarTone, className, }: RecordsTableProps): React.JSX.Element;
201
+ export declare function RecordsTable({ labels, columns: columnsProp, rows: rowsProp, selectedIds, onSelectionChange, showNew, allowAddColumn, onOpenRecord, onOpenRecordPage, avatarTone, className, ...rest }: RecordsTableProps): React.JSX.Element;
199
202
  export declare const defaultColumns: Column[];
200
203
  export declare const defaultRows: Row[];
201
204
  export {};
@@ -3,7 +3,7 @@ export type RichTextValue = {
3
3
  title: string;
4
4
  html: string;
5
5
  };
6
- export type RichTextEditorProps = {
6
+ export type RichTextEditorProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onChange" | "placeholder"> & {
7
7
  /** Accessible name for the title field. */
8
8
  titleLabel?: string;
9
9
  /** Accessible name for the body editor. */
@@ -24,7 +24,7 @@ export type RichTextEditorProps = {
24
24
  * that appears over a text selection. Built on contentEditable, dependency-free.
25
25
  * Uncontrolled: seed with `defaultTitle`/`defaultHtml`, read via `onChange`.
26
26
  */
27
- export declare function RichTextEditor({ defaultTitle, defaultHtml, titlePlaceholder, placeholder, onChange, className, titleLabel, bodyLabel, linkPromptLabel, }: RichTextEditorProps): React.JSX.Element;
27
+ export declare function RichTextEditor({ defaultTitle, defaultHtml, titlePlaceholder, placeholder, onChange, className, titleLabel, bodyLabel, linkPromptLabel, ...rest }: RichTextEditorProps): React.JSX.Element;
28
28
  export declare namespace RichTextEditor {
29
29
  var displayName: string;
30
30
  }
@@ -14,7 +14,7 @@ export type TaskGroup = {
14
14
  label: React.ReactNode;
15
15
  tasks: Task[];
16
16
  };
17
- export type TaskListProps = {
17
+ export type TaskListProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onToggle"> & {
18
18
  /** Label for a task's checkbox while the task is open. */
19
19
  completeLabel?: string;
20
20
  /** Label for a task's checkbox once it is done. */
@@ -34,7 +34,7 @@ export type TaskListProps = {
34
34
  * count, then rows of a round complete-checkbox + title + trailing meta.
35
35
  * Completed rows strike through; overdue dates go red.
36
36
  */
37
- export declare function TaskList({ tasks, groups, onToggle, emptyLabel, className, completeLabel, incompleteLabel, }: TaskListProps): React.JSX.Element;
37
+ export declare function TaskList({ tasks, groups, onToggle, emptyLabel, className, completeLabel, incompleteLabel, ...rest }: TaskListProps): React.JSX.Element;
38
38
  export declare namespace TaskList {
39
39
  var displayName: string;
40
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",