@crisp-ui-kit/crisp 0.21.0 → 0.22.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 (31) hide show
  1. package/dist/cjs/product/flowcanvas/flowcanvas.js +6 -2
  2. package/dist/cjs/product/inbox/inbox.js +2 -2
  3. package/dist/cjs/product/listpage/listpage.js +2 -2
  4. package/dist/cjs/product/onboarding/onboarding.js +2 -2
  5. package/dist/cjs/product/recordpage/recordpage.js +2 -2
  6. package/dist/cjs/product/recordstable/recordstable.js +33 -4
  7. package/dist/cjs/product/richtext/richtext.js +2 -2
  8. package/dist/cjs/product/sidebar/sidebar.js +1 -1
  9. package/dist/cjs/product/sidebar/sidebar.recipe.js +2 -0
  10. package/dist/cjs/product/tasklist/tasklist.js +2 -2
  11. package/dist/esm/product/flowcanvas/flowcanvas.js +6 -2
  12. package/dist/esm/product/inbox/inbox.js +2 -2
  13. package/dist/esm/product/listpage/listpage.js +2 -2
  14. package/dist/esm/product/onboarding/onboarding.js +2 -2
  15. package/dist/esm/product/recordpage/recordpage.js +2 -2
  16. package/dist/esm/product/recordstable/recordstable.js +31 -4
  17. package/dist/esm/product/richtext/richtext.js +2 -2
  18. package/dist/esm/product/sidebar/sidebar.js +1 -1
  19. package/dist/esm/product/sidebar/sidebar.recipe.js +3 -1
  20. package/dist/esm/product/tasklist/tasklist.js +2 -2
  21. package/dist/sidebar.css +2 -0
  22. package/dist/styles.css +2 -0
  23. package/dist/types/product/flowcanvas/flowcanvas.d.ts +2 -2
  24. package/dist/types/product/inbox/inbox.d.ts +2 -2
  25. package/dist/types/product/listpage/listpage.d.ts +2 -2
  26. package/dist/types/product/onboarding/onboarding.d.ts +2 -2
  27. package/dist/types/product/recordpage/recordpage.d.ts +2 -2
  28. package/dist/types/product/recordstable/recordstable.d.ts +5 -2
  29. package/dist/types/product/richtext/richtext.d.ts +2 -2
  30. package/dist/types/product/tasklist/tasklist.d.ts +2 -2
  31. package/package.json +1 -1
@@ -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: () => {
@@ -57,7 +57,7 @@ const IconSearch = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.Sea
57
57
  const IconInbox = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.Inbox, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
58
58
  const IconInvite = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.UserPlus, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
59
59
  const IconChat = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.MessageSquare, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
60
- const IconCollapsePanel = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeft, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
60
+ const IconCollapsePanel = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeft, { "data-rtl-flip": "", className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
61
61
  const IconChevronMini = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 12, strokeWidth: 1.75, "aria-hidden": "true" }));
62
62
  const GlyphCompanies = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.Building2, { size: 12, color: "white", strokeWidth: 1.75, "aria-hidden": "true" }));
63
63
  const GlyphPeople = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.User, { size: 12, color: "white", strokeWidth: 1.75, "aria-hidden": "true" }));
@@ -30,6 +30,8 @@ const config = (0, core_1.defineRecipe)({
30
30
  & .sidebar-divider { height: 0; border: none; border-top: 1px solid ${tokens_1.contract.borderSubtle}; margin: ${tokens_1.contract.space["2"]} calc(-1 * ${tokens_1.contract.space["2"]}) 0; }
31
31
  & .sidebar-newlist { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: ${tokens_1.contract.size.row}; border-radius: ${tokens_1.contract.radius.md}; border: 1px dashed ${tokens_1.contract.borderDefault}; background: transparent; color: ${tokens_1.contract.fgMuted}; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; }
32
32
  & .sidebar-newlist:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
33
+
34
+ ${core_1.rtlFlipCss}
33
35
  `,
34
36
  variants: {},
35
37
  defaultVariants: {},
@@ -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";
@@ -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: () => {
@@ -21,7 +21,7 @@ const IconSearch = ({ className }) => (_jsx(Search, { className: className, size
21
21
  const IconInbox = ({ className }) => (_jsx(Inbox, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
22
22
  const IconInvite = ({ className }) => (_jsx(UserPlus, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
23
23
  const IconChat = ({ className }) => (_jsx(MessageSquare, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
24
- const IconCollapsePanel = ({ className }) => (_jsx(PanelLeft, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
24
+ const IconCollapsePanel = ({ className }) => (_jsx(PanelLeft, { "data-rtl-flip": "", className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
25
25
  const IconChevronMini = () => (_jsx(ChevronDown, { size: 12, strokeWidth: 1.75, "aria-hidden": "true" }));
26
26
  const GlyphCompanies = () => (_jsx(Building2, { size: 12, color: "white", strokeWidth: 1.75, "aria-hidden": "true" }));
27
27
  const GlyphPeople = () => (_jsx(User, { size: 12, color: "white", strokeWidth: 1.75, "aria-hidden": "true" }));
@@ -1,4 +1,4 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c, elevation as e } from "../../tokens/index.js";
3
3
  const config = defineRecipe({
4
4
  name: "sidebar",
@@ -27,6 +27,8 @@ const config = defineRecipe({
27
27
  & .sidebar-divider { height: 0; border: none; border-top: 1px solid ${c.borderSubtle}; margin: ${c.space["2"]} calc(-1 * ${c.space["2"]}) 0; }
28
28
  & .sidebar-newlist { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: ${c.size.row}; border-radius: ${c.radius.md}; border: 1px dashed ${c.borderDefault}; background: transparent; color: ${c.fgMuted}; font-family: ${c.font.sans}; font-size: ${c.text.md}; font-weight: ${c.weight.medium}; cursor: pointer; }
29
29
  & .sidebar-newlist:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
30
+
31
+ ${rtlFlipCss}
30
32
  `,
31
33
  variants: {},
32
34
  defaultVariants: {},
@@ -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";
package/dist/sidebar.css CHANGED
@@ -24,6 +24,8 @@
24
24
  & .sidebar-divider { height: 0; border: none; border-top: 1px solid var(--border-subtle); margin: var(--space-2) calc(-1 * var(--space-2)) 0; }
25
25
  & .sidebar-newlist { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: var(--size-row); border-radius: var(--radius-md); border: 1px dashed var(--border-default); background: transparent; color: var(--fg-muted); font-family: var(--font-sans); font-size: var(--text-md); font-weight: var(--weight-medium); cursor: pointer; }
26
26
  & .sidebar-newlist:hover { background: var(--bg-raised); color: var(--fg-primary); }
27
+
28
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
27
29
 
28
30
  }
29
31
 
package/dist/styles.css CHANGED
@@ -2718,6 +2718,8 @@
2718
2718
  & .sidebar-divider { height: 0; border: none; border-top: 1px solid var(--border-subtle); margin: var(--space-2) calc(-1 * var(--space-2)) 0; }
2719
2719
  & .sidebar-newlist { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: var(--size-row); border-radius: var(--radius-md); border: 1px dashed var(--border-default); background: transparent; color: var(--fg-muted); font-family: var(--font-sans); font-size: var(--text-md); font-weight: var(--weight-medium); cursor: pointer; }
2720
2720
  & .sidebar-newlist:hover { background: var(--bg-raised); color: var(--fg-primary); }
2721
+
2722
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2721
2723
 
2722
2724
  }
2723
2725
 
@@ -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.0",
3
+ "version": "0.22.0",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",