@crisp-ui-kit/crisp 0.21.1 → 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.
- package/dist/cjs/product/flowcanvas/flowcanvas.js +6 -2
- package/dist/cjs/product/inbox/inbox.js +2 -2
- package/dist/cjs/product/listpage/listpage.js +2 -2
- package/dist/cjs/product/onboarding/onboarding.js +2 -2
- package/dist/cjs/product/recordpage/recordpage.js +2 -2
- package/dist/cjs/product/recordstable/recordstable.js +33 -4
- package/dist/cjs/product/richtext/richtext.js +2 -2
- package/dist/cjs/product/tasklist/tasklist.js +2 -2
- package/dist/esm/product/flowcanvas/flowcanvas.js +6 -2
- package/dist/esm/product/inbox/inbox.js +2 -2
- package/dist/esm/product/listpage/listpage.js +2 -2
- package/dist/esm/product/onboarding/onboarding.js +2 -2
- package/dist/esm/product/recordpage/recordpage.js +2 -2
- package/dist/esm/product/recordstable/recordstable.js +31 -4
- package/dist/esm/product/richtext/richtext.js +2 -2
- package/dist/esm/product/tasklist/tasklist.js +2 -2
- package/dist/types/product/flowcanvas/flowcanvas.d.ts +2 -2
- package/dist/types/product/inbox/inbox.d.ts +2 -2
- package/dist/types/product/listpage/listpage.d.ts +2 -2
- package/dist/types/product/onboarding/onboarding.d.ts +2 -2
- package/dist/types/product/recordpage/recordpage.d.ts +2 -2
- package/dist/types/product/recordstable/recordstable.d.ts +5 -2
- package/dist/types/product/richtext/richtext.d.ts +2 -2
- package/dist/types/product/tasklist/tasklist.d.ts +2 -2
- 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,
|
|
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 =
|
|
328
|
-
return ((0, jsx_runtime_1.jsx)(datepickerfield_1.DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(d
|
|
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";
|
|
@@ -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,
|
|
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 =
|
|
290
|
-
return (_jsx(DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(d
|
|
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";
|
|
@@ -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
|
}
|