@crisp-ui-kit/crisp 0.21.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/dialog/dialog.js +6 -2
- package/dist/cjs/components/drawer/drawer.js +3 -2
- package/dist/cjs/core/dialog-description.js +22 -0
- package/dist/cjs/core/index.js +3 -1
- package/dist/cjs/product/command/command.js +3 -2
- 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/components/dialog/dialog.js +6 -2
- package/dist/esm/components/drawer/drawer.js +3 -2
- package/dist/esm/core/dialog-description.js +19 -0
- package/dist/esm/core/index.js +1 -0
- package/dist/esm/product/command/command.js +3 -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/components/dialog/dialog.d.ts +12 -2
- package/dist/types/components/drawer/drawer.d.ts +6 -2
- package/dist/types/core/dialog-description.d.ts +18 -0
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/product/command/command.d.ts +6 -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
|
@@ -41,6 +41,7 @@ const dialog = __importStar(require("@zag-js/dialog"));
|
|
|
41
41
|
const react_1 = require("@zag-js/react");
|
|
42
42
|
const lucide_react_1 = require("lucide-react");
|
|
43
43
|
const React = __importStar(require("react"));
|
|
44
|
+
const core_1 = require("../../core");
|
|
44
45
|
const dialog_recipe_1 = require("./dialog.recipe");
|
|
45
46
|
// A globally unique id per mounted instance (see command.tsx for the rationale —
|
|
46
47
|
// the machine defers its dismissal listeners a frame, so tree-relative useId
|
|
@@ -59,7 +60,7 @@ function useDialogInstanceId() {
|
|
|
59
60
|
* Escape / outside-click dismissal) with the measured Attio modal surface.
|
|
60
61
|
* Compose the body with `DialogBody` and actions with `DialogFooter`.
|
|
61
62
|
*/
|
|
62
|
-
function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
|
|
63
|
+
function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
63
64
|
const id = useDialogInstanceId();
|
|
64
65
|
const service = (0, react_1.useMachine)(dialog.machine, {
|
|
65
66
|
id,
|
|
@@ -75,7 +76,10 @@ function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ar
|
|
|
75
76
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
76
77
|
if (!api.open)
|
|
77
78
|
return null;
|
|
78
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, dialog_recipe_1.dialogRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...
|
|
79
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, dialog_recipe_1.dialogRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: (0, core_1.cx)("dialog-content", className), "data-crisp-dialog": id,
|
|
80
|
+
// `width` is the component's own knob, so it wins over a passed
|
|
81
|
+
// maxWidth; everything else a consumer sets survives
|
|
82
|
+
style: { ...style, maxWidth: `min(${width}px, 90vw)` }, children: (0, jsx_runtime_1.jsxs)("div", { className: "dialog-card", children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "dialog-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
|
|
79
83
|
}
|
|
80
84
|
Dialog.displayName = "Dialog";
|
|
81
85
|
function DialogBody({ children, className, }) {
|
|
@@ -41,6 +41,7 @@ const dialog = __importStar(require("@zag-js/dialog"));
|
|
|
41
41
|
const react_1 = require("@zag-js/react");
|
|
42
42
|
const lucide_react_1 = require("lucide-react");
|
|
43
43
|
const React = __importStar(require("react"));
|
|
44
|
+
const core_1 = require("../../core");
|
|
44
45
|
const drawer_recipe_1 = require("./drawer.recipe");
|
|
45
46
|
let instanceCounter = 0;
|
|
46
47
|
function useDrawerInstanceId() {
|
|
@@ -56,7 +57,7 @@ function useDrawerInstanceId() {
|
|
|
56
57
|
* Escape / outside-click dismissal) with a panel that slides in from the edge.
|
|
57
58
|
* Compose the body with `DrawerBody` and actions with `DrawerFooter`.
|
|
58
59
|
*/
|
|
59
|
-
function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
|
|
60
|
+
function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, className, "aria-describedby": describedBy, ...rest }) {
|
|
60
61
|
const id = useDrawerInstanceId();
|
|
61
62
|
const service = (0, react_1.useMachine)(dialog.machine, {
|
|
62
63
|
id,
|
|
@@ -69,7 +70,7 @@ function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label":
|
|
|
69
70
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
70
71
|
if (!api.open)
|
|
71
72
|
return null;
|
|
72
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, drawer_recipe_1.drawerRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: (0, jsx_runtime_1.jsxs)("div", { ...api.getContentProps(), className: "drawer-content", children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "drawer-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
|
|
73
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, drawer_recipe_1.drawerRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: (0, jsx_runtime_1.jsxs)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: (0, core_1.cx)("drawer-content", className), children: [title != null && ((0, jsx_runtime_1.jsxs)("div", { className: "drawer-header", children: [icon, (0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
|
|
73
74
|
}
|
|
74
75
|
Drawer.displayName = "Drawer";
|
|
75
76
|
function DrawerBody({ children }) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withDescribedBy = withDescribedBy;
|
|
4
|
+
/**
|
|
5
|
+
* Zag's dialog machine always advertises a description: `getContentProps()`
|
|
6
|
+
* returns an `aria-describedby` pointing at the id of a description element.
|
|
7
|
+
* crisp renders no such element — Attio's modals have a title and a body, no
|
|
8
|
+
* description line — so the reference dangles, and a dangling id is not a
|
|
9
|
+
* relationship a screen reader can follow.
|
|
10
|
+
*
|
|
11
|
+
* It also silenced the consumer's own: the machine's props are spread after
|
|
12
|
+
* anything passed in, so a real `aria-describedby` was overwritten by the
|
|
13
|
+
* missing one every time (#223).
|
|
14
|
+
*
|
|
15
|
+
* Drop the machine's value, and let the caller's stand when there is one. A
|
|
16
|
+
* consumer who wants a description points at a node inside their own children,
|
|
17
|
+
* which is the only place one exists.
|
|
18
|
+
*/
|
|
19
|
+
function withDescribedBy(contentProps, describedBy) {
|
|
20
|
+
const { "aria-describedby": _machine, ...rest } = contentProps;
|
|
21
|
+
return describedBy ? { ...rest, "aria-describedby": describedBy } : rest;
|
|
22
|
+
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,11 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.usePrefersReducedMotion = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.isRtlElement = exports.defineRecipe = exports.cx = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = void 0;
|
|
18
18
|
var cx_1 = require("./cx");
|
|
19
19
|
Object.defineProperty(exports, "cx", { enumerable: true, get: function () { return cx_1.cx; } });
|
|
20
20
|
var define_1 = require("./define");
|
|
21
21
|
Object.defineProperty(exports, "defineRecipe", { enumerable: true, get: function () { return define_1.defineRecipe; } });
|
|
22
|
+
var dialog_description_1 = require("./dialog-description");
|
|
23
|
+
Object.defineProperty(exports, "withDescribedBy", { enumerable: true, get: function () { return dialog_description_1.withDescribedBy; } });
|
|
22
24
|
var direction_1 = require("./direction");
|
|
23
25
|
Object.defineProperty(exports, "isRtlElement", { enumerable: true, get: function () { return direction_1.isRtlElement; } });
|
|
24
26
|
var place_floating_1 = require("./place-floating");
|
|
@@ -42,6 +42,7 @@ const react_1 = require("@zag-js/react");
|
|
|
42
42
|
const lucide_react_1 = require("lucide-react");
|
|
43
43
|
const React = __importStar(require("react"));
|
|
44
44
|
const kbd_1 = require("../../components/kbd");
|
|
45
|
+
const core_1 = require("../../core");
|
|
45
46
|
const command_recipe_1 = require("./command.recipe");
|
|
46
47
|
const IconHome = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.House, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
47
48
|
const IconTasks = () => ((0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
@@ -146,7 +147,7 @@ function useCommandInstanceId() {
|
|
|
146
147
|
* option list) so keyboard/focus correctness comes from the machines, not
|
|
147
148
|
* hand-rolled event handling.
|
|
148
149
|
*/
|
|
149
|
-
function Command({ open, onOpenChange, items = exports.defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, }) {
|
|
150
|
+
function Command({ open, onOpenChange, items = exports.defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, className, "aria-describedby": describedBy, ...rest }) {
|
|
150
151
|
const instanceId = useCommandInstanceId();
|
|
151
152
|
const dialogId = `${instanceId}-dialog`;
|
|
152
153
|
const comboId = `${instanceId}-combobox`;
|
|
@@ -230,7 +231,7 @@ function Command({ open, onOpenChange, items = exports.defaultCommandItems, plac
|
|
|
230
231
|
};
|
|
231
232
|
if (!dlg.open)
|
|
232
233
|
return null;
|
|
233
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, command_recipe_1.commandRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...dlg.getContentProps(), className: "command-content", children: (0, jsx_runtime_1.jsxs)("div", { className: "command-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "command-control", children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), (0, jsx_runtime_1.jsx)("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
234
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, command_recipe_1.commandRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: (0, jsx_runtime_1.jsx)("div", { ...rest, ...(0, core_1.withDescribedBy)(dlg.getContentProps(), describedBy), className: (0, core_1.cx)("command-content", className), children: (0, jsx_runtime_1.jsxs)("div", { className: "command-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "command-control", children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), (0, jsx_runtime_1.jsx)("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
234
235
|
const Icon = item.icon;
|
|
235
236
|
const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
|
|
236
237
|
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroup && ((0, jsx_runtime_1.jsx)("div", { className: "command-group-label", "aria-hidden": "true", children: item.group })), (0, jsx_runtime_1.jsxs)("div", { ...combo.getItemProps({ item }), className: "command-item", children: [(0, jsx_runtime_1.jsx)(Icon, {}), (0, jsx_runtime_1.jsx)("span", { className: "command-item-label", children: item.label }), item.shortcut && ((0, jsx_runtime_1.jsx)("span", { className: "command-item-shortcut", children: item.shortcut.map((key) => ((0, jsx_runtime_1.jsx)(kbd_1.Kbd, { children: key }, key))) }))] })] }, item.id));
|
|
@@ -45,7 +45,7 @@ const clampZoom = (z) => Math.min(1.5, Math.max(0.5, z));
|
|
|
45
45
|
* measured from Attio's Workflows editor. Drag a node to move it (fires
|
|
46
46
|
* `onNodesChange`); drag the background to pan; use the toolbar to zoom.
|
|
47
47
|
*/
|
|
48
|
-
function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", }) {
|
|
48
|
+
function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", ...rest }) {
|
|
49
49
|
const [pan, setPan] = React.useState({ x: 40, y: 40 });
|
|
50
50
|
const [zoom, setZoom] = React.useState(1);
|
|
51
51
|
const [panning, setPanning] = React.useState(false);
|
|
@@ -93,7 +93,11 @@ function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240,
|
|
|
93
93
|
drag.current = null;
|
|
94
94
|
setPanning(false);
|
|
95
95
|
};
|
|
96
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, flowcanvas_recipe_1.flowcanvasRecipe)({ className }), "data-panning": panning ? "" : undefined,
|
|
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";
|
|
@@ -3,6 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
7
|
import { dialogRecipe } from "./dialog.recipe.js";
|
|
7
8
|
// A globally unique id per mounted instance (see command.tsx for the rationale —
|
|
8
9
|
// the machine defers its dismissal listeners a frame, so tree-relative useId
|
|
@@ -21,7 +22,7 @@ function useDialogInstanceId() {
|
|
|
21
22
|
* Escape / outside-click dismissal) with the measured Attio modal surface.
|
|
22
23
|
* Compose the body with `DialogBody` and actions with `DialogFooter`.
|
|
23
24
|
*/
|
|
24
|
-
export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
|
|
25
|
+
export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ariaLabel, closeLabel = "Close", children, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
25
26
|
const id = useDialogInstanceId();
|
|
26
27
|
const service = useMachine(dialog.machine, {
|
|
27
28
|
id,
|
|
@@ -37,7 +38,10 @@ export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-lab
|
|
|
37
38
|
const api = dialog.connect(service, normalizeProps);
|
|
38
39
|
if (!api.open)
|
|
39
40
|
return null;
|
|
40
|
-
return (_jsx(Portal, { children: _jsxs("div", { className: dialogRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: _jsx("div", { ...api.getContentProps(),
|
|
41
|
+
return (_jsx(Portal, { children: _jsxs("div", { className: dialogRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "dialog-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "dialog-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(api.getContentProps(), describedBy), className: cx("dialog-content", className), "data-crisp-dialog": id,
|
|
42
|
+
// `width` is the component's own knob, so it wins over a passed
|
|
43
|
+
// maxWidth; everything else a consumer sets survives
|
|
44
|
+
style: { ...style, maxWidth: `min(${width}px, 90vw)` }, children: _jsxs("div", { className: "dialog-card", children: [title != null && (_jsxs("div", { className: "dialog-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "dialog-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "dialog-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) }) })] }) }));
|
|
41
45
|
}
|
|
42
46
|
Dialog.displayName = "Dialog";
|
|
43
47
|
export function DialogBody({ children, className, }) {
|
|
@@ -3,6 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
7
|
import { drawerRecipe } from "./drawer.recipe.js";
|
|
7
8
|
let instanceCounter = 0;
|
|
8
9
|
function useDrawerInstanceId() {
|
|
@@ -18,7 +19,7 @@ function useDrawerInstanceId() {
|
|
|
18
19
|
* Escape / outside-click dismissal) with a panel that slides in from the edge.
|
|
19
20
|
* Compose the body with `DrawerBody` and actions with `DrawerFooter`.
|
|
20
21
|
*/
|
|
21
|
-
export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, }) {
|
|
22
|
+
export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label": ariaLabel, closeLabel = "Close", children, className, "aria-describedby": describedBy, ...rest }) {
|
|
22
23
|
const id = useDrawerInstanceId();
|
|
23
24
|
const service = useMachine(dialog.machine, {
|
|
24
25
|
id,
|
|
@@ -31,7 +32,7 @@ export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-
|
|
|
31
32
|
const api = dialog.connect(service, normalizeProps);
|
|
32
33
|
if (!api.open)
|
|
33
34
|
return null;
|
|
34
|
-
return (_jsx(Portal, { children: _jsxs("div", { className: drawerRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: _jsxs("div", { ...api.getContentProps(), className: "drawer-content", children: [title != null && (_jsxs("div", { className: "drawer-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
|
|
35
|
+
return (_jsx(Portal, { children: _jsxs("div", { className: drawerRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "drawer-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "drawer-positioner", "data-side": side, children: _jsxs("div", { ...rest, ...withDescribedBy(api.getContentProps(), describedBy), className: cx("drawer-content", className), children: [title != null && (_jsxs("div", { className: "drawer-header", children: [icon, _jsx("span", { ...api.getTitleProps(), className: "drawer-title", children: title }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "drawer-close", "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] })), children] }) })] }) }));
|
|
35
36
|
}
|
|
36
37
|
Drawer.displayName = "Drawer";
|
|
37
38
|
export function DrawerBody({ children }) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zag's dialog machine always advertises a description: `getContentProps()`
|
|
3
|
+
* returns an `aria-describedby` pointing at the id of a description element.
|
|
4
|
+
* crisp renders no such element — Attio's modals have a title and a body, no
|
|
5
|
+
* description line — so the reference dangles, and a dangling id is not a
|
|
6
|
+
* relationship a screen reader can follow.
|
|
7
|
+
*
|
|
8
|
+
* It also silenced the consumer's own: the machine's props are spread after
|
|
9
|
+
* anything passed in, so a real `aria-describedby` was overwritten by the
|
|
10
|
+
* missing one every time (#223).
|
|
11
|
+
*
|
|
12
|
+
* Drop the machine's value, and let the caller's stand when there is one. A
|
|
13
|
+
* consumer who wants a description points at a node inside their own children,
|
|
14
|
+
* which is the only place one exists.
|
|
15
|
+
*/
|
|
16
|
+
export function withDescribedBy(contentProps, describedBy) {
|
|
17
|
+
const { "aria-describedby": _machine, ...rest } = contentProps;
|
|
18
|
+
return describedBy ? { ...rest, "aria-describedby": describedBy } : rest;
|
|
19
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { cx } from "./cx.js";
|
|
2
2
|
export { defineRecipe } from "./define.js";
|
|
3
|
+
export { withDescribedBy } from "./dialog-description.js";
|
|
3
4
|
export { isRtlElement } from "./direction.js";
|
|
4
5
|
export { placeFloating, } from "./place-floating.js";
|
|
5
6
|
export { Primitive } from "./primitive.js";
|
|
@@ -5,6 +5,7 @@ import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
|
5
5
|
import { Building2, CircleDollarSign, House, ListTodo, Search, Settings, SquareCheck, User, UserPlus, } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { Kbd } from "../../components/kbd/index.js";
|
|
8
|
+
import { cx, withDescribedBy } from "../../core/index.js";
|
|
8
9
|
import { commandRecipe } from "./command.recipe.js";
|
|
9
10
|
const IconHome = () => (_jsx(House, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
10
11
|
const IconTasks = () => (_jsx(SquareCheck, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
@@ -109,7 +110,7 @@ function useCommandInstanceId() {
|
|
|
109
110
|
* option list) so keyboard/focus correctness comes from the machines, not
|
|
110
111
|
* hand-rolled event handling.
|
|
111
112
|
*/
|
|
112
|
-
export function Command({ open, onOpenChange, items = defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, }) {
|
|
113
|
+
export function Command({ open, onOpenChange, items = defaultCommandItems, placeholder = "Search actions and records…", "aria-label": ariaLabel = "Command palette", navigateLabel = "Navigate", selectLabel = "Select", emptyLabel = "No matching commands", query: queryProp, onQueryChange, filterItems = true, className, "aria-describedby": describedBy, ...rest }) {
|
|
113
114
|
const instanceId = useCommandInstanceId();
|
|
114
115
|
const dialogId = `${instanceId}-dialog`;
|
|
115
116
|
const comboId = `${instanceId}-combobox`;
|
|
@@ -193,7 +194,7 @@ export function Command({ open, onOpenChange, items = defaultCommandItems, place
|
|
|
193
194
|
};
|
|
194
195
|
if (!dlg.open)
|
|
195
196
|
return null;
|
|
196
|
-
return (_jsx(Portal, { children: _jsxs("div", { className: commandRecipe(), children: [_jsx("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), _jsx("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: _jsx("div", { ...dlg.getContentProps(), className: "command-content", children: _jsxs("div", { className: "command-card", children: [_jsxs("div", { className: "command-control", children: [_jsx(IconSearch, {}), _jsx("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), _jsx("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? (_jsx("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
197
|
+
return (_jsx(Portal, { children: _jsxs("div", { className: commandRecipe(), children: [_jsx("div", { ...dlg.getBackdropProps(), className: "command-backdrop" }), _jsx("div", { ...dlg.getPositionerProps(), className: "command-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(dlg.getContentProps(), describedBy), className: cx("command-content", className), children: _jsxs("div", { className: "command-card", children: [_jsxs("div", { className: "command-control", children: [_jsx(IconSearch, {}), _jsx("input", { ref: inputRef, ...combo.getInputProps(), className: "command-input", placeholder: placeholder })] }), _jsx("div", { ...combo.getContentProps(), className: "command-list", children: filteredItems.length === 0 ? (_jsx("div", { className: "command-empty", children: emptyLabel })) : (filteredItems.map((item, i) => {
|
|
197
198
|
const Icon = item.icon;
|
|
198
199
|
const showGroup = item.group && item.group !== filteredItems[i - 1]?.group;
|
|
199
200
|
return (_jsxs(React.Fragment, { children: [showGroup && (_jsx("div", { className: "command-group-label", "aria-hidden": "true", children: item.group })), _jsxs("div", { ...combo.getItemProps({ item }), className: "command-item", children: [_jsx(Icon, {}), _jsx("span", { className: "command-item-label", children: item.label }), item.shortcut && (_jsx("span", { className: "command-item-shortcut", children: item.shortcut.map((key) => (_jsx(Kbd, { children: key }, key))) }))] })] }, item.id));
|
|
@@ -9,7 +9,7 @@ const clampZoom = (z) => Math.min(1.5, Math.max(0.5, z));
|
|
|
9
9
|
* measured from Attio's Workflows editor. Drag a node to move it (fires
|
|
10
10
|
* `onNodesChange`); drag the background to pan; use the toolbar to zoom.
|
|
11
11
|
*/
|
|
12
|
-
export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", }) {
|
|
12
|
+
export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth = 240, nodeHeight = 64, "aria-label": ariaLabel, className, zoomOutLabel = "Zoom out", zoomInLabel = "Zoom in", resetLabel = "Reset", edgesLabel = "Edges", ...rest }) {
|
|
13
13
|
const [pan, setPan] = React.useState({ x: 40, y: 40 });
|
|
14
14
|
const [zoom, setZoom] = React.useState(1);
|
|
15
15
|
const [panning, setPanning] = React.useState(false);
|
|
@@ -57,7 +57,11 @@ export function FlowCanvas({ nodes, edges, onNodesChange, renderNode, nodeWidth
|
|
|
57
57
|
drag.current = null;
|
|
58
58
|
setPanning(false);
|
|
59
59
|
};
|
|
60
|
-
return (_jsxs("div", { className: flowcanvasRecipe({ className }), "data-panning": panning ? "" : undefined,
|
|
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";
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Root props land on the DIALOG SURFACE — the element that carries
|
|
4
|
+
* `role="dialog"` — not on the portal wrapper. That is what a consumer needs
|
|
5
|
+
* to reach: `aria-describedby` has to sit beside `aria-modal` to associate the
|
|
6
|
+
* description, and a `data-testid` or a `style` is wanted on the panel, not on
|
|
7
|
+
* the full-viewport layer behind it (#223).
|
|
8
|
+
*
|
|
9
|
+
* `id` and the aria wiring are the machine's: it mints the ids that link the
|
|
10
|
+
* title and the description, so those are spread after anything passed in.
|
|
11
|
+
*/
|
|
12
|
+
export type DialogProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" | "children"> & {
|
|
3
13
|
/** Controlled open state. */
|
|
4
14
|
open: boolean;
|
|
5
15
|
onOpenChange: (open: boolean) => void;
|
|
@@ -19,7 +29,7 @@ export type DialogProps = {
|
|
|
19
29
|
* Escape / outside-click dismissal) with the measured Attio modal surface.
|
|
20
30
|
* Compose the body with `DialogBody` and actions with `DialogFooter`.
|
|
21
31
|
*/
|
|
22
|
-
export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel, children, }: DialogProps): React.JSX.Element | null;
|
|
32
|
+
export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel, children, className, style, "aria-describedby": describedBy, ...rest }: DialogProps): React.JSX.Element | null;
|
|
23
33
|
export declare namespace Dialog {
|
|
24
34
|
var displayName: string;
|
|
25
35
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Root props land on the DRAWER SURFACE — the element carrying `role="dialog"`
|
|
4
|
+
* — not on the portal wrapper, for the same reason as Dialog (#223).
|
|
5
|
+
*/
|
|
6
|
+
export type DrawerProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" | "children"> & {
|
|
3
7
|
open: boolean;
|
|
4
8
|
onOpenChange: (open: boolean) => void;
|
|
5
9
|
/** Which edge the sheet slides in from (default right). */
|
|
@@ -16,7 +20,7 @@ export type DrawerProps = {
|
|
|
16
20
|
* Escape / outside-click dismissal) with a panel that slides in from the edge.
|
|
17
21
|
* Compose the body with `DrawerBody` and actions with `DrawerFooter`.
|
|
18
22
|
*/
|
|
19
|
-
export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel, children, }: DrawerProps): React.JSX.Element | null;
|
|
23
|
+
export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel, children, className, "aria-describedby": describedBy, ...rest }: DrawerProps): React.JSX.Element | null;
|
|
20
24
|
export declare namespace Drawer {
|
|
21
25
|
var displayName: string;
|
|
22
26
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zag's dialog machine always advertises a description: `getContentProps()`
|
|
3
|
+
* returns an `aria-describedby` pointing at the id of a description element.
|
|
4
|
+
* crisp renders no such element — Attio's modals have a title and a body, no
|
|
5
|
+
* description line — so the reference dangles, and a dangling id is not a
|
|
6
|
+
* relationship a screen reader can follow.
|
|
7
|
+
*
|
|
8
|
+
* It also silenced the consumer's own: the machine's props are spread after
|
|
9
|
+
* anything passed in, so a real `aria-describedby` was overwritten by the
|
|
10
|
+
* missing one every time (#223).
|
|
11
|
+
*
|
|
12
|
+
* Drop the machine's value, and let the caller's stand when there is one. A
|
|
13
|
+
* consumer who wants a description points at a node inside their own children,
|
|
14
|
+
* which is the only place one exists.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withDescribedBy<T extends Record<string, unknown>>(contentProps: T, describedBy: string | undefined): Omit<T, "aria-describedby"> & {
|
|
17
|
+
"aria-describedby"?: string;
|
|
18
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { cx } from "./cx";
|
|
2
2
|
export { defineRecipe } from "./define";
|
|
3
|
+
export { withDescribedBy } from "./dialog-description";
|
|
3
4
|
export { isRtlElement } from "./direction";
|
|
4
5
|
export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
|
|
5
6
|
export { Primitive } from "./primitive";
|
|
@@ -13,7 +13,11 @@ export type CommandItem = {
|
|
|
13
13
|
};
|
|
14
14
|
/** crisp's own generic command set (not copied from any product). */
|
|
15
15
|
export declare const defaultCommandItems: CommandItem[];
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Root props land on the PALETTE SURFACE — the element carrying
|
|
18
|
+
* `role="dialog"` — not on the portal wrapper (#223).
|
|
19
|
+
*/
|
|
20
|
+
export type CommandProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onSelect"> & {
|
|
17
21
|
/** Controlled open state — the caller owns when the palette is shown. */
|
|
18
22
|
open: boolean;
|
|
19
23
|
onOpenChange: (open: boolean) => void;
|
|
@@ -50,4 +54,4 @@ export type CommandProps = {
|
|
|
50
54
|
* option list) so keyboard/focus correctness comes from the machines, not
|
|
51
55
|
* hand-rolled event handling.
|
|
52
56
|
*/
|
|
53
|
-
export declare function Command({ open, onOpenChange, items, placeholder, "aria-label": ariaLabel, navigateLabel, selectLabel, emptyLabel, query: queryProp, onQueryChange, filterItems, }: CommandProps): React.JSX.Element | null;
|
|
57
|
+
export declare function Command({ open, onOpenChange, items, placeholder, "aria-label": ariaLabel, navigateLabel, selectLabel, emptyLabel, query: queryProp, onQueryChange, filterItems, className, "aria-describedby": describedBy, ...rest }: CommandProps): React.JSX.Element | null;
|
|
@@ -8,7 +8,7 @@ export type FlowEdge = {
|
|
|
8
8
|
from: string;
|
|
9
9
|
to: string;
|
|
10
10
|
};
|
|
11
|
-
export type FlowCanvasProps<T extends FlowNode> = {
|
|
11
|
+
export type FlowCanvasProps<T extends FlowNode> = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onChange"> & {
|
|
12
12
|
/** Zoom-out control label. */
|
|
13
13
|
zoomOutLabel?: string;
|
|
14
14
|
/** Zoom-in control label. */
|
|
@@ -34,7 +34,7 @@ export type FlowCanvasProps<T extends FlowNode> = {
|
|
|
34
34
|
* measured from Attio's Workflows editor. Drag a node to move it (fires
|
|
35
35
|
* `onNodesChange`); drag the background to pan; use the toolbar to zoom.
|
|
36
36
|
*/
|
|
37
|
-
export declare function FlowCanvas<T extends FlowNode>({ nodes, edges, onNodesChange, renderNode, nodeWidth, nodeHeight, "aria-label": ariaLabel, className, zoomOutLabel, zoomInLabel, resetLabel, edgesLabel, }: FlowCanvasProps<T>): React.JSX.Element;
|
|
37
|
+
export declare function FlowCanvas<T extends FlowNode>({ nodes, edges, onNodesChange, renderNode, nodeWidth, nodeHeight, "aria-label": ariaLabel, className, zoomOutLabel, zoomInLabel, resetLabel, edgesLabel, ...rest }: FlowCanvasProps<T>): React.JSX.Element;
|
|
38
38
|
export declare namespace FlowCanvas {
|
|
39
39
|
var displayName: string;
|
|
40
40
|
}
|
|
@@ -16,7 +16,7 @@ export type InboxTab = {
|
|
|
16
16
|
count?: number;
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
};
|
|
19
|
-
export type InboxProps = {
|
|
19
|
+
export type InboxProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "title"> & {
|
|
20
20
|
notifications?: InboxNotification[];
|
|
21
21
|
title?: React.ReactNode;
|
|
22
22
|
/** Tabs (e.g. Notifications / Requests). */
|
|
@@ -38,7 +38,7 @@ export type InboxProps = {
|
|
|
38
38
|
* (avatar + message + time + unread dot) or a centred empty state. Put it inside
|
|
39
39
|
* a Popover for the flyout.
|
|
40
40
|
*/
|
|
41
|
-
export declare function Inbox({ notifications, title, tabs, activeTab, onTabChange, emptyTitle, emptySubtitle, emptyAction, tabsLabel, className, }: InboxProps): React.JSX.Element;
|
|
41
|
+
export declare function Inbox({ notifications, title, tabs, activeTab, onTabChange, emptyTitle, emptySubtitle, emptyAction, tabsLabel, className, ...rest }: InboxProps): React.JSX.Element;
|
|
42
42
|
export declare namespace Inbox {
|
|
43
43
|
var displayName: string;
|
|
44
44
|
}
|
|
@@ -10,7 +10,7 @@ export type ListGroup = {
|
|
|
10
10
|
/** Pre-composed row nodes (wrap each in <ListRow>). */
|
|
11
11
|
rows: React.ReactNode[];
|
|
12
12
|
};
|
|
13
|
-
export type ListPageProps = {
|
|
13
|
+
export type ListPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
|
|
14
14
|
/** Left toolbar slot — e.g. Sort + Filter buttons. */
|
|
15
15
|
leading?: React.ReactNode;
|
|
16
16
|
/** Right toolbar slot — e.g. view toggle, View settings, a New button. */
|
|
@@ -40,7 +40,7 @@ export declare function ListRow({ children, onClick, }: {
|
|
|
40
40
|
* an optional dashed Favorites region, then column headers and time-grouped
|
|
41
41
|
* rows. Rows are composed by the caller (wrap each in <ListRow>).
|
|
42
42
|
*/
|
|
43
|
-
export declare function ListPage({ leading, trailing, count, favorites, favoritesLabel, columns, groups, emptyLabel, className, }: ListPageProps): React.JSX.Element;
|
|
43
|
+
export declare function ListPage({ leading, trailing, count, favorites, favoritesLabel, columns, groups, emptyLabel, className, ...rest }: ListPageProps): React.JSX.Element;
|
|
44
44
|
export declare namespace ListPage {
|
|
45
45
|
var displayName: string;
|
|
46
46
|
}
|
|
@@ -7,7 +7,7 @@ export type OnboardingStep = {
|
|
|
7
7
|
icon?: React.ReactNode;
|
|
8
8
|
done?: boolean;
|
|
9
9
|
};
|
|
10
|
-
export type OnboardingChecklistProps = {
|
|
10
|
+
export type OnboardingChecklistProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "title"> & {
|
|
11
11
|
/** Label for the overflow-menu trigger. */
|
|
12
12
|
optionsLabel?: string;
|
|
13
13
|
/** Label for the minimise control. */
|
|
@@ -52,7 +52,7 @@ export declare const onboardingLabels: {
|
|
|
52
52
|
* started" widget. Title + optional menu/minimize, a segmented progress bar,
|
|
53
53
|
* and step rows (completed rows strike through with a brand check).
|
|
54
54
|
*/
|
|
55
|
-
export declare function OnboardingChecklist({ title, steps, encouragement, onStepClick, onMenu, onMinimize, className, optionsLabel, minimizeLabel, progressLabel, progressText, completedStepLabel, }: OnboardingChecklistProps): React.JSX.Element;
|
|
55
|
+
export declare function OnboardingChecklist({ title, steps, encouragement, onStepClick, onMenu, onMinimize, className, optionsLabel, minimizeLabel, progressLabel, progressText, completedStepLabel, ...rest }: OnboardingChecklistProps): React.JSX.Element;
|
|
56
56
|
export declare namespace OnboardingChecklist {
|
|
57
57
|
var displayName: string;
|
|
58
58
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type Column, type Row } from "../recordstable";
|
|
3
|
-
export type RecordPageProps = {
|
|
3
|
+
export type RecordPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
|
|
4
4
|
record: Row;
|
|
5
5
|
columns: Column[];
|
|
6
6
|
/** Breadcrumb label + back button target (e.g. "Companies"). */
|
|
@@ -97,7 +97,7 @@ export type RecordPageLabelsInput = Partial<Omit<RecordPageLabels, "tabs" | "hig
|
|
|
97
97
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
98
98
|
* into the app's main content area.
|
|
99
99
|
*/
|
|
100
|
-
export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, }: RecordPageProps): React.JSX.Element;
|
|
100
|
+
export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, ...rest }: RecordPageProps): React.JSX.Element;
|
|
101
101
|
export declare namespace RecordPage {
|
|
102
102
|
var displayName: string;
|
|
103
103
|
}
|
|
@@ -73,7 +73,7 @@ export type EntityValue = {
|
|
|
73
73
|
export type Row = {
|
|
74
74
|
id: string;
|
|
75
75
|
} & Record<string, any>;
|
|
76
|
-
export type RecordsTableProps = {
|
|
76
|
+
export type RecordsTableProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
|
|
77
77
|
/** Partial override of the chrome strings; see `recordsTableLabels`. */
|
|
78
78
|
labels?: RecordsTableLabelsInput;
|
|
79
79
|
columns?: Column[];
|
|
@@ -187,6 +187,9 @@ export type RecordsTableLabelsInput = Partial<Omit<RecordsTableLabels, "agg" | "
|
|
|
187
187
|
op?: Partial<Record<FilterOp, string>>;
|
|
188
188
|
};
|
|
189
189
|
type FilterOp = "contains" | "is" | "isnot" | "empty" | "notempty";
|
|
190
|
+
export declare function parseCellDate(raw: string | undefined): Date | null;
|
|
191
|
+
/** The inverse: the picked date's own calendar day, not the UTC one. */
|
|
192
|
+
export declare function formatCellDate(d: Date): string;
|
|
190
193
|
/**
|
|
191
194
|
* Attio-style record grid — fully featured. Toolbar (search / sort status /
|
|
192
195
|
* count / New), sticky checkbox + entity columns, 36px rows, typed cells,
|
|
@@ -195,7 +198,7 @@ type FilterOp = "contains" | "is" | "isnot" | "empty" | "notempty";
|
|
|
195
198
|
* and a floating bulk-action bar (delete / deselect) when rows are selected.
|
|
196
199
|
* Selection can be controlled via `selectedIds`/`onSelectionChange`.
|
|
197
200
|
*/
|
|
198
|
-
export declare function RecordsTable({ labels, columns: columnsProp, rows: rowsProp, selectedIds, onSelectionChange, showNew, allowAddColumn, onOpenRecord, onOpenRecordPage, avatarTone, className, }: RecordsTableProps): React.JSX.Element;
|
|
201
|
+
export declare function RecordsTable({ labels, columns: columnsProp, rows: rowsProp, selectedIds, onSelectionChange, showNew, allowAddColumn, onOpenRecord, onOpenRecordPage, avatarTone, className, ...rest }: RecordsTableProps): React.JSX.Element;
|
|
199
202
|
export declare const defaultColumns: Column[];
|
|
200
203
|
export declare const defaultRows: Row[];
|
|
201
204
|
export {};
|
|
@@ -3,7 +3,7 @@ export type RichTextValue = {
|
|
|
3
3
|
title: string;
|
|
4
4
|
html: string;
|
|
5
5
|
};
|
|
6
|
-
export type RichTextEditorProps = {
|
|
6
|
+
export type RichTextEditorProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onChange" | "placeholder"> & {
|
|
7
7
|
/** Accessible name for the title field. */
|
|
8
8
|
titleLabel?: string;
|
|
9
9
|
/** Accessible name for the body editor. */
|
|
@@ -24,7 +24,7 @@ export type RichTextEditorProps = {
|
|
|
24
24
|
* that appears over a text selection. Built on contentEditable, dependency-free.
|
|
25
25
|
* Uncontrolled: seed with `defaultTitle`/`defaultHtml`, read via `onChange`.
|
|
26
26
|
*/
|
|
27
|
-
export declare function RichTextEditor({ defaultTitle, defaultHtml, titlePlaceholder, placeholder, onChange, className, titleLabel, bodyLabel, linkPromptLabel, }: RichTextEditorProps): React.JSX.Element;
|
|
27
|
+
export declare function RichTextEditor({ defaultTitle, defaultHtml, titlePlaceholder, placeholder, onChange, className, titleLabel, bodyLabel, linkPromptLabel, ...rest }: RichTextEditorProps): React.JSX.Element;
|
|
28
28
|
export declare namespace RichTextEditor {
|
|
29
29
|
var displayName: string;
|
|
30
30
|
}
|
|
@@ -14,7 +14,7 @@ export type TaskGroup = {
|
|
|
14
14
|
label: React.ReactNode;
|
|
15
15
|
tasks: Task[];
|
|
16
16
|
};
|
|
17
|
-
export type TaskListProps = {
|
|
17
|
+
export type TaskListProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onToggle"> & {
|
|
18
18
|
/** Label for a task's checkbox while the task is open. */
|
|
19
19
|
completeLabel?: string;
|
|
20
20
|
/** Label for a task's checkbox once it is done. */
|
|
@@ -34,7 +34,7 @@ export type TaskListProps = {
|
|
|
34
34
|
* count, then rows of a round complete-checkbox + title + trailing meta.
|
|
35
35
|
* Completed rows strike through; overdue dates go red.
|
|
36
36
|
*/
|
|
37
|
-
export declare function TaskList({ tasks, groups, onToggle, emptyLabel, className, completeLabel, incompleteLabel, }: TaskListProps): React.JSX.Element;
|
|
37
|
+
export declare function TaskList({ tasks, groups, onToggle, emptyLabel, className, completeLabel, incompleteLabel, ...rest }: TaskListProps): React.JSX.Element;
|
|
38
38
|
export declare namespace TaskList {
|
|
39
39
|
var displayName: string;
|
|
40
40
|
}
|