@crisp-ui-kit/crisp 0.18.0 → 0.19.1
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/columncalc/columncalc.js +2 -2
- package/dist/cjs/components/multiselect/multiselect.js +2 -2
- package/dist/cjs/components/select/select.js +2 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +4 -4
- package/dist/cjs/components/wheelpicker/wheelpicker.js +8 -2
- package/dist/cjs/core/index.js +3 -1
- package/dist/cjs/core/use-prefers-reduced-motion.js +67 -0
- package/dist/cjs/product/recordpage/recordpage.js +8 -3
- package/dist/cjs/product/recordstable/recordstable.js +6 -3
- package/dist/cjs/product/sidebar/sidebar.js +2 -2
- package/dist/esm/components/columncalc/columncalc.js +2 -2
- package/dist/esm/components/multiselect/multiselect.js +2 -2
- package/dist/esm/components/select/select.js +2 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +4 -4
- package/dist/esm/components/wheelpicker/wheelpicker.js +8 -2
- package/dist/esm/core/index.js +1 -0
- package/dist/esm/core/use-prefers-reduced-motion.js +31 -0
- package/dist/esm/product/recordpage/recordpage.js +8 -3
- package/dist/esm/product/recordstable/recordstable.js +6 -3
- package/dist/esm/product/sidebar/sidebar.js +2 -2
- package/dist/types/components/columncalc/columncalc.d.ts +5 -1
- package/dist/types/components/multiselect/multiselect.d.ts +3 -1
- package/dist/types/components/select/select.d.ts +3 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +9 -1
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/core/use-prefers-reduced-motion.d.ts +17 -0
- package/dist/types/product/recordpage/recordpage.d.ts +10 -0
- package/dist/types/product/recordstable/recordstable.d.ts +6 -0
- package/dist/types/product/sidebar/sidebar.d.ts +4 -0
- package/package.json +1 -1
|
@@ -117,7 +117,7 @@ function computeCalc(values, op) {
|
|
|
117
117
|
const defaultFormat = (n, op) => op === "percentEmpty" || op === "percentFilled"
|
|
118
118
|
? `${Math.round(n)}%`
|
|
119
119
|
: n.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
120
|
-
function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
120
|
+
function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel = "Add calculation", sectionLabel = "Aggregations", format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
121
121
|
// a partial override, so a consumer can translate one op without
|
|
122
122
|
// restating all nine
|
|
123
123
|
const labelFor = (o) => opLabels?.[o] ?? exports.CALC_LABEL[o];
|
|
@@ -135,5 +135,5 @@ function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opPr
|
|
|
135
135
|
...(numeric ? exports.NUMERIC_CALCS : []),
|
|
136
136
|
"none",
|
|
137
137
|
];
|
|
138
|
-
return ((0, jsx_runtime_1.jsx)("span", { className: (0, columncalc_recipe_1.columncalcRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)(menu_1.Menu, { trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "columncalc-trigger", "data-align": align === "right" ? "right" : undefined, children: op === "none" || result === null ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 12, strokeWidth: 2 }), (0, jsx_runtime_1.jsx)("span", { className: "columncalc-placeholder", children:
|
|
138
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: (0, columncalc_recipe_1.columncalcRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)(menu_1.Menu, { trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "columncalc-trigger", "data-align": align === "right" ? "right" : undefined, children: op === "none" || result === null ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 12, strokeWidth: 2 }), (0, jsx_runtime_1.jsx)("span", { className: "columncalc-placeholder", children: addLabel })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "columncalc-value", children: format(result, op) }), (0, jsx_runtime_1.jsx)("span", { className: "columncalc-op", children: labelFor(op) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 12, strokeWidth: 2 })] })) }), children: [(0, jsx_runtime_1.jsx)("div", { className: "columncalc-section", children: sectionLabel }), ops.map((o) => ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setOp(o), children: labelFor(o) }, o)))] }) }));
|
|
139
139
|
}
|
|
@@ -64,7 +64,7 @@ function Chip({ opt }) {
|
|
|
64
64
|
* popover with a search field over rows of "[chip] … [checkbox]" that toggle on
|
|
65
65
|
* click (the menu stays open). Keyboard: ↑/↓ move, Enter toggles, Esc closes.
|
|
66
66
|
*/
|
|
67
|
-
function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…", disabled, className, "aria-label": ariaLabel, ...rest }) {
|
|
67
|
+
function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…", emptyLabel = "No results", disabled, className, "aria-label": ariaLabel, ...rest }) {
|
|
68
68
|
// Virtual focus: DOM focus stays on the trigger (or the search field) and the
|
|
69
69
|
// active option is named by `aria-activedescendant`. `data-highlighted` is a
|
|
70
70
|
// styling hook only -- a screen reader cannot see it.
|
|
@@ -161,7 +161,7 @@ function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, p
|
|
|
161
161
|
}, children: [(0, jsx_runtime_1.jsx)("span", { className: "multiselect-chips", children: selectedOptions.length === 0 ? ((0, jsx_runtime_1.jsx)("span", { className: "multiselect-placeholder", children: placeholder })) : (selectedOptions.map((o) => (0, jsx_runtime_1.jsx)(Chip, { opt: o }, o.value))) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "multiselect-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsxs)("div", { id: listboxId, className: "multiselect-listbox", role: "listbox", "aria-multiselectable": "true", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "multiselect-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
|
|
162
162
|
setQuery(e.target.value);
|
|
163
163
|
setHighlight(0);
|
|
164
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "multiselect-empty", children:
|
|
164
|
+
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
165
165
|
const sel = selectedSet.has(opt.value);
|
|
166
166
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [(0, jsx_runtime_1.jsx)("span", { className: "multiselect-option-chip", children: (0, jsx_runtime_1.jsx)(Chip, { opt: opt }) }), (0, jsx_runtime_1.jsx)("span", { className: "multiselect-check", children: sel && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] }, opt.value));
|
|
167
167
|
}))] }))] }));
|
|
@@ -51,7 +51,7 @@ function optionText(o) {
|
|
|
51
51
|
* "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
|
|
52
52
|
* Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
|
|
53
53
|
*/
|
|
54
|
-
function Select({ options, value, defaultValue, onValueChange, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", disabled, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
54
|
+
function Select({ options, value, defaultValue, onValueChange, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", emptyLabel = "No results", disabled, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
55
55
|
// Virtual focus: DOM focus stays on the trigger (or the search field), and the
|
|
56
56
|
// active option is named by `aria-activedescendant`. That is the only thing a
|
|
57
57
|
// screen reader has to go on -- `data-highlighted` is invisible to it -- so the
|
|
@@ -150,7 +150,7 @@ function Select({ options, value, defaultValue, onValueChange, placeholder = "Se
|
|
|
150
150
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, select_recipe_1.selectRecipe)({ variant, width, className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, disabled: disabled, "data-open": open ? "" : undefined, className: "select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "select-value", children: selectedOption ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "select-option-dot", style: { background: selectedOption.color } })), selectedOption.label] })) : ((0, jsx_runtime_1.jsx)("span", { className: "select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsxs)("div", { id: listboxId, className: "select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "select-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
|
|
151
151
|
setQuery(e.target.value);
|
|
152
152
|
setHighlight(0);
|
|
153
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "select-empty", children:
|
|
153
|
+
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
154
154
|
const isSel = opt.value === selected;
|
|
155
155
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && ((0, jsx_runtime_1.jsx)("span", { className: "select-option-dot", style: { background: opt.color } })), (0, jsx_runtime_1.jsx)("span", { className: "select-option-label", children: opt.label }), isSel && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "select-option-check", size: 15, strokeWidth: 2 }))] }, opt.value));
|
|
156
156
|
}))] }))] }));
|
|
@@ -134,7 +134,7 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
134
134
|
* composable version, to put above `DataTable` or any table of your own. Pair
|
|
135
135
|
* it with `applyFilters` / `applySorts` to turn its state into rows.
|
|
136
136
|
*/
|
|
137
|
-
function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel = "Sort", reorderSortLabel = "Reorder sort", sortFieldLabel = "Sort field", sortDirectionLabel = "Sort direction", removeSortLabel = "Remove sort", filterFieldLabel = "Filter by field", filterFieldPlaceholder = "Filter by…", filterValuePlaceholder = "Value…", ascendingLabel = "Ascending", descendingLabel = "Descending", sortTriggerLabel, filterTriggerLabel, ...rest }) {
|
|
137
|
+
function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel = "Sort", reorderSortLabel = "Reorder sort", noSortsLabel = "No sorts applied to this view.", addSortLabel = "Add sort", learnSortingLabel = "Learn about sorting", selectConditionLabel = "Select condition", sortFieldLabel = "Sort field", sortDirectionLabel = "Sort direction", removeSortLabel = "Remove sort", filterFieldLabel = "Filter by field", filterFieldPlaceholder = "Filter by…", filterValuePlaceholder = "Value…", ascendingLabel = "Ascending", descendingLabel = "Descending", sortTriggerLabel, filterTriggerLabel, ...rest }) {
|
|
138
138
|
const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
|
|
139
139
|
const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
|
|
140
140
|
const sorts = sortsProp ?? sortsState;
|
|
@@ -206,7 +206,7 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
206
206
|
: sorts.length
|
|
207
207
|
? `Sorted by ${columns.find((c) => c.key === sorts[0].key)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
208
208
|
: "Sort";
|
|
209
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `tabletoolbar-tool${sorts.length ? " tabletoolbar-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" }), sortedLabel] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-menu tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "tabletoolbar-sort-empty", children:
|
|
209
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `tabletoolbar-tool${sorts.length ? " tabletoolbar-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" }), sortedLabel] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-menu tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-sort-row", onDragOver: (e) => {
|
|
210
210
|
if (dragSort !== null)
|
|
211
211
|
e.preventDefault();
|
|
212
212
|
}, onDrop: () => {
|
|
@@ -228,7 +228,7 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
228
228
|
label: ((0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), descendingLabel] })),
|
|
229
229
|
keywords: descendingLabel,
|
|
230
230
|
},
|
|
231
|
-
], "aria-label": sortDirectionLabel }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "tabletoolbar-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }),
|
|
231
|
+
], "aria-label": sortDirectionLabel }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "tabletoolbar-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && ((0, jsx_runtime_1.jsx)("div", { className: "tabletoolbar-sort-foot", children: (0, jsx_runtime_1.jsxs)("a", { className: "tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] }), (0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `tabletoolbar-tool tabletoolbar-tool--ghost${activeFilters ? " tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "filter" ? null : "filter"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
232
232
|
? filterTriggerLabel(activeFilters)
|
|
233
233
|
: activeFilters
|
|
234
234
|
? `Filter · ${activeFilters}`
|
|
@@ -242,7 +242,7 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
242
242
|
const col = columns.find((c) => c.key === cond.key);
|
|
243
243
|
const open = openMenu === `fcond:${cond.id}`;
|
|
244
244
|
const hasValue = !opNeedsValue(cond.op) || cond.value.trim();
|
|
245
|
-
return ((0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-op", children: OP_LABELS[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-val", children: cond.value }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-op", children:
|
|
245
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "tabletoolbar-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-op", children: OP_LABELS[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-val", children: cond.value }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-fchip-op", children: selectConditionLabel }))] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tabletoolbar-fchip-x", "aria-label": `Remove ${col?.label} filter`, onClick: () => removeFilter(cond.id), children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) })] }), open && ((0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-menu", role: "dialog", "aria-label": `${col?.label} condition`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "tabletoolbar-menu-head", children: [col?.label, " condition"] }), OP_ORDER.map((op) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [OP_LABELS[op], cond.op === op ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("input", { className: "tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
246
246
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
247
247
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) }))] }))] }, cond.id));
|
|
248
248
|
}), (0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-spacer" }), count != null && (0, jsx_runtime_1.jsx)("span", { className: "tabletoolbar-count", children: count }), children] }) }));
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.WheelPicker = WheelPicker;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
|
+
const core_1 = require("../../core");
|
|
39
40
|
const wheelpicker_recipe_1 = require("./wheelpicker.recipe");
|
|
40
41
|
const ITEM_HEIGHT = { small: 36, medium: 44 };
|
|
41
42
|
/**
|
|
@@ -70,6 +71,11 @@ function WheelPicker({ columns, value: valueProp, defaultValue, onValueChange, s
|
|
|
70
71
|
setInternal(merged);
|
|
71
72
|
onValueChange?.(merged);
|
|
72
73
|
};
|
|
74
|
+
// CSS cannot reach this: `scroll-behavior` in a stylesheet does not override
|
|
75
|
+
// an explicit `behavior` passed to scrollTo, so the preference is read here
|
|
76
|
+
// and the two "smooth" call sites go through `glide` (#208).
|
|
77
|
+
const reduceMotion = (0, core_1.usePrefersReducedMotion)();
|
|
78
|
+
const glide = reduceMotion ? "auto" : "smooth";
|
|
73
79
|
const scrollTo = React.useCallback((key, index, behavior) => {
|
|
74
80
|
const el = refs.current.get(key);
|
|
75
81
|
if (!el || index < 0)
|
|
@@ -107,7 +113,7 @@ function WheelPicker({ columns, value: valueProp, defaultValue, onValueChange, s
|
|
|
107
113
|
if (!opt)
|
|
108
114
|
return;
|
|
109
115
|
commit(col.key, opt.value);
|
|
110
|
-
scrollTo(col.key, clamped,
|
|
116
|
+
scrollTo(col.key, clamped, glide);
|
|
111
117
|
};
|
|
112
118
|
if (e.key === "ArrowDown")
|
|
113
119
|
go(i + 1);
|
|
@@ -127,7 +133,7 @@ function WheelPicker({ columns, value: valueProp, defaultValue, onValueChange, s
|
|
|
127
133
|
const selected = opt.value === value[col.key];
|
|
128
134
|
return ((0, jsx_runtime_1.jsx)("div", { id: optionId(col.key, opt.value), role: "option", "aria-selected": selected, "data-selected": selected ? "" : undefined, className: "wheelpicker-item", onClick: () => {
|
|
129
135
|
commit(col.key, opt.value);
|
|
130
|
-
scrollTo(col.key, col.options.indexOf(opt),
|
|
136
|
+
scrollTo(col.key, col.options.indexOf(opt), glide);
|
|
131
137
|
}, children: opt.label }, opt.value));
|
|
132
138
|
}) }, col.key))) }) })] }));
|
|
133
139
|
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,7 +14,7 @@ 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.resolveRecipe = exports.Primitive = exports.defineRecipe = exports.cx = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.resolveRecipe = exports.Primitive = 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");
|
|
@@ -24,3 +24,5 @@ Object.defineProperty(exports, "Primitive", { enumerable: true, get: function ()
|
|
|
24
24
|
var resolve_1 = require("./resolve");
|
|
25
25
|
Object.defineProperty(exports, "resolveRecipe", { enumerable: true, get: function () { return resolve_1.resolveRecipe; } });
|
|
26
26
|
__exportStar(require("./rtl"), exports);
|
|
27
|
+
var use_prefers_reduced_motion_1 = require("./use-prefers-reduced-motion");
|
|
28
|
+
Object.defineProperty(exports, "usePrefersReducedMotion", { enumerable: true, get: function () { return use_prefers_reduced_motion_1.usePrefersReducedMotion; } });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.usePrefersReducedMotion = usePrefersReducedMotion;
|
|
37
|
+
const React = __importStar(require("react"));
|
|
38
|
+
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
39
|
+
const subscribe = (onChange) => {
|
|
40
|
+
if (typeof window === "undefined" || !window.matchMedia)
|
|
41
|
+
return () => { };
|
|
42
|
+
const mq = window.matchMedia(QUERY);
|
|
43
|
+
mq.addEventListener("change", onChange);
|
|
44
|
+
return () => mq.removeEventListener("change", onChange);
|
|
45
|
+
};
|
|
46
|
+
const getSnapshot = () => typeof window !== "undefined" && window.matchMedia
|
|
47
|
+
? window.matchMedia(QUERY).matches
|
|
48
|
+
: false;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the user has asked for reduced motion, live.
|
|
51
|
+
*
|
|
52
|
+
* crisp's CSS carries a `@media (prefers-reduced-motion: reduce)` override in
|
|
53
|
+
* every animating recipe, and `bun run spec` checks it. That covers CSS.
|
|
54
|
+
* Motion driven from JavaScript — `scrollTo({ behavior: "smooth" })`,
|
|
55
|
+
* `element.animate()` — is invisible to a stylesheet: `scroll-behavior` in CSS
|
|
56
|
+
* does NOT override an explicit `behavior` argument. Those call sites have to
|
|
57
|
+
* read the preference here (#208).
|
|
58
|
+
*
|
|
59
|
+
* The server snapshot is `false` rather than "unknown" on purpose. It is the
|
|
60
|
+
* only value that keeps the markup identical on both sides, and a component
|
|
61
|
+
* using this must not put the result in its output for the same reason — the
|
|
62
|
+
* preference belongs in a handler or an effect, where it changes behaviour
|
|
63
|
+
* rather than HTML.
|
|
64
|
+
*/
|
|
65
|
+
function usePrefersReducedMotion() {
|
|
66
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
67
|
+
}
|
|
@@ -64,6 +64,7 @@ exports.recordPageLabels = {
|
|
|
64
64
|
composeEmail: "Compose email",
|
|
65
65
|
askAttio: "Ask Attio",
|
|
66
66
|
addToList: "Add to list",
|
|
67
|
+
noLists: "This record has not been added to any lists.",
|
|
67
68
|
highlights: "Highlights",
|
|
68
69
|
recordDetails: "Record Details",
|
|
69
70
|
searchFields: "Search fields",
|
|
@@ -76,6 +77,10 @@ exports.recordPageLabels = {
|
|
|
76
77
|
viewLess: "View less",
|
|
77
78
|
newNote: "New note",
|
|
78
79
|
newTask: "New task",
|
|
80
|
+
notePlaceholder: "Write a note… (⌘↵ to save)",
|
|
81
|
+
saveNote: "Save note",
|
|
82
|
+
taskPlaceholder: "Task name… (↵ to add)",
|
|
83
|
+
addTask: "Add task",
|
|
79
84
|
cancel: "Cancel",
|
|
80
85
|
noNotesYet: "No notes yet.",
|
|
81
86
|
noTasksYet: "No tasks yet.",
|
|
@@ -159,14 +164,14 @@ function Composer({ placeholder, submitLabel, multiline, onSubmit, onCancel, lab
|
|
|
159
164
|
}
|
|
160
165
|
function NotesSection({ notes, onAdd, labels, }) {
|
|
161
166
|
const [open, setOpen] = React.useState(false);
|
|
162
|
-
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: labels.tabs.notes, count: notes.length, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && ((0, jsx_runtime_1.jsx)(Composer, { labels: labels, multiline: true, placeholder:
|
|
167
|
+
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: labels.tabs.notes, count: notes.length, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && ((0, jsx_runtime_1.jsx)(Composer, { labels: labels, multiline: true, placeholder: labels.notePlaceholder, submitLabel: labels.saveNote, onCancel: () => setOpen(false), onSubmit: (t) => {
|
|
163
168
|
onAdd(t);
|
|
164
169
|
setOpen(false);
|
|
165
170
|
} })), notes.length === 0 && !open ? ((0, jsx_runtime_1.jsx)("div", { className: "recordpage-emptystate", children: labels.noNotesYet })) : (notes.map((n) => ((0, jsx_runtime_1.jsxs)("div", { className: "recordpage-note", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-note-body", children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordpage-note-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "recordpage-note-author", children: labels.you }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-note-time", children: n.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "recordpage-note-text", children: n.text })] })] }, n.id))))] }));
|
|
166
171
|
}
|
|
167
172
|
function TasksSection({ tasks, onAdd, onToggle, labels, }) {
|
|
168
173
|
const [open, setOpen] = React.useState(false);
|
|
169
|
-
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: labels.tabs.tasks, count: tasks.length, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), labels.newTask] }) }), open && ((0, jsx_runtime_1.jsx)(Composer, { labels: labels, placeholder:
|
|
174
|
+
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: labels.tabs.tasks, count: tasks.length, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), labels.newTask] }) }), open && ((0, jsx_runtime_1.jsx)(Composer, { labels: labels, placeholder: labels.taskPlaceholder, submitLabel: labels.addTask, onCancel: () => setOpen(false), onSubmit: (t) => {
|
|
170
175
|
onAdd(t);
|
|
171
176
|
setOpen(false);
|
|
172
177
|
} })), tasks.length === 0 && !open ? ((0, jsx_runtime_1.jsx)("div", { className: "recordpage-emptystate", children: labels.noTasksYet })) : (tasks.map((t) => ((0, jsx_runtime_1.jsxs)("div", { className: "recordpage-task", "data-done": t.done ? "" : undefined, children: [(0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { checked: t.done, onChange: () => onToggle(t.id), "aria-label": t.text }), (0, jsx_runtime_1.jsx)("span", { className: "recordpage-task-text", children: t.text })] }, t.id))))] }));
|
|
@@ -399,7 +404,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
399
404
|
setSearchOpen(next);
|
|
400
405
|
if (!next)
|
|
401
406
|
setFieldQuery("");
|
|
402
|
-
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "recordpage-noresults", children: ["No fields match \u201C", fieldQuery, "\u201D."] })))] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "recordpage-emptystate", children:
|
|
407
|
+
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "recordpage-noresults", children: ["No fields match \u201C", fieldQuery, "\u201D."] })))] }), (0, jsx_runtime_1.jsxs)("div", { className: "recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": 300, "aria-valuemax": 560, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
403
408
|
if (e.key === "ArrowLeft")
|
|
404
409
|
setDetailsW((w) => Math.max(300, w - 16));
|
|
405
410
|
else if (e.key === "ArrowRight")
|
|
@@ -124,6 +124,9 @@ exports.recordsTableLabels = {
|
|
|
124
124
|
duplicate: "Duplicate",
|
|
125
125
|
addToList: "Add to list",
|
|
126
126
|
inAList: "In a list",
|
|
127
|
+
searchRecordsPlaceholder: "Search records…",
|
|
128
|
+
searchOptionsPlaceholder: "Search options…",
|
|
129
|
+
selectedCount: "Selected",
|
|
127
130
|
delete: "Delete",
|
|
128
131
|
sendEmail: "Send email",
|
|
129
132
|
exportCsv: "Export CSV",
|
|
@@ -309,14 +312,14 @@ function StatusCell({ column, row, onChange, }) {
|
|
|
309
312
|
return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", "aria-label": column.label, placeholder: "\u2014", value: row[column.key] ?? undefined, onValueChange: onChange, options: column.options ?? [] }));
|
|
310
313
|
}
|
|
311
314
|
function ReferenceCell({ column, row, onChange, labels, }) {
|
|
312
|
-
return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder:
|
|
315
|
+
return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder: labels.searchRecordsPlaceholder, "aria-label": column.label, placeholder: labels.setRecordPlaceholder, value: row[column.key] ?? undefined, onValueChange: onChange, options: (column.refOptions ?? []).map((o) => ({
|
|
313
316
|
value: o.value,
|
|
314
317
|
label: o.label,
|
|
315
318
|
})) }));
|
|
316
319
|
}
|
|
317
320
|
function TagsCell({ column, row, options, onChange, labels, }) {
|
|
318
321
|
const value = row[column.key] ?? [];
|
|
319
|
-
return ((0, jsx_runtime_1.jsx)(multiselect_1.MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder:
|
|
322
|
+
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 }));
|
|
320
323
|
}
|
|
321
324
|
function DateCell({ column, row, onChange, }) {
|
|
322
325
|
const raw = row[column.key];
|
|
@@ -1047,7 +1050,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
|
|
|
1047
1050
|
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--up", role: "menu", "aria-label": `${col.label} summary`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: aggOptions(col).map((opt) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitemradio", tabIndex: -1, "aria-checked": a === opt, "data-on": a === opt ? "" : undefined, onClick: () => setAggValue(col.key, opt), children: [t.agg[opt], a === opt ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "recordstable-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, opt))) }), rootRef.current)] }) }, col.key));
|
|
1048
1051
|
}), (0, jsx_runtime_1.jsx)("td", { className: "recordstable-sum recordstable-addcol" })] }) })] }), visibleRows.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "recordstable-empty", children: (0, jsx_runtime_1.jsx)(emptystate_1.EmptyState, { variant: "page", icon: activeFilters ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Inbox, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })), title: activeFilters ? "No records match that filter" : t.noRecords, description: activeFilters
|
|
1049
1052
|
? "None of your records match the filters that are set."
|
|
1050
|
-
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-addrow", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), selected.size > 0 && ((0, jsx_runtime_1.jsxs)(bulkbar_1.BulkBar, { count: selected.size, countLabel:
|
|
1053
|
+
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-addrow", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), selected.size > 0 && ((0, jsx_runtime_1.jsxs)(bulkbar_1.BulkBar, { count: selected.size, countLabel: t.selectedCount, onClear: () => setSelected(new Set()), children: [(0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ListPlus, { size: 15, strokeWidth: 1.75 }), onClick: addToList, children: t.addToList }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 15, strokeWidth: 1.75 }), onClick: sendEmail, children: t.sendEmail }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Workflow, { size: 15, strokeWidth: 1.75 }), onClick: runWorkflow, children: t.runWorkflow }), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.moreActions, trigger: (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 15, strokeWidth: 1.75 }), children: t.more }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), onSelect: exportSelected, children: t.exportCsv }), (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, onSelect: deleteSelected, children: t.delete })] })] })), (0, jsx_runtime_1.jsx)(CreateRecordDialog, { labels: t, open: creating, onOpenChange: setCreating, columns: cols, entityLabel: cols.find((c) => c.type === "entity")?.label ?? "record", onCreate: (row) => setData((prev) => [...prev, row]) }), ctxMenu &&
|
|
1051
1054
|
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: ctxRef, className: "recordstable-menu recordstable-ctx", role: "menu", "aria-label": t.recordActions, style: { position: "fixed", top: ctxMenu.y, left: ctxMenu.x }, onKeyDown: onCtxKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => copyRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), t.copy] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => duplicateRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CopyPlus, { size: 15, strokeWidth: 1.75 }), t.duplicate] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => pinRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), t.addToList] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item recordstable-menu-item--danger", role: "menuitem", tabIndex: -1, onClick: () => deleteRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), t.delete] })] }), rootRef.current ?? document.body)] }));
|
|
1052
1055
|
}
|
|
1053
1056
|
exports.defaultColumns = [
|
|
@@ -117,7 +117,7 @@ exports.ATTIO_SIDEBAR_FOOTER = {
|
|
|
117
117
|
function SectionHeader({ label, open, onToggle, }) {
|
|
118
118
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [(0, jsx_runtime_1.jsx)(IconChevronMini, {}), (0, jsx_runtime_1.jsx)("span", { children: label })] }));
|
|
119
119
|
}
|
|
120
|
-
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, footer = exports.ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
120
|
+
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, footer = exports.ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
121
121
|
const [collapsedSections, setCollapsedSections] = React.useState(() => new Set());
|
|
122
122
|
const [commandOpen, setCommandOpen] = React.useState(false);
|
|
123
123
|
React.useEffect(() => {
|
|
@@ -143,6 +143,6 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
143
143
|
return next;
|
|
144
144
|
});
|
|
145
145
|
}, []);
|
|
146
|
-
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.div, { ref: ref, asChild: asChild, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "sidebar-ws", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), (0, jsx_runtime_1.jsx)("span", { className: "sidebar-ws-name", children: workspace.name }), (0, jsx_runtime_1.jsx)(IconChevronDown, {}), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "sidebar-search-label", children:
|
|
146
|
+
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.div, { ref: ref, asChild: asChild, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "sidebar-ws", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), (0, jsx_runtime_1.jsx)("span", { className: "sidebar-ws-name", children: workspace.name }), (0, jsx_runtime_1.jsx)(IconChevronDown, {}), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "sidebar-search-label", children: searchLabel }), (0, jsx_runtime_1.jsx)("span", { className: "sidebar-search-kbd", children: "\u2318K" })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: (0, jsx_runtime_1.jsx)(IconInbox, {}) })] }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-divider" }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), children: label }, label))) }), (0, jsx_runtime_1.jsxs)("div", { className: "sidebar-footer", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", className: "sidebar-pill", children: footer.gettingStartedLabel }), (0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(IconInvite, {}), children: footer.inviteLabel }), (0, jsx_runtime_1.jsx)("div", { className: "sidebar-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "sidebar-trial", children: [(0, jsx_runtime_1.jsx)("span", { children: footer.trialText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), (0, jsx_runtime_1.jsx)(command_1.Command, { open: commandOpen, onOpenChange: setCommandOpen })] }));
|
|
147
147
|
});
|
|
148
148
|
exports.Sidebar.displayName = "Sidebar";
|
|
@@ -78,7 +78,7 @@ export function computeCalc(values, op) {
|
|
|
78
78
|
const defaultFormat = (n, op) => op === "percentEmpty" || op === "percentFilled"
|
|
79
79
|
? `${Math.round(n)}%`
|
|
80
80
|
: n.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
81
|
-
export function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
81
|
+
export function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel = "Add calculation", sectionLabel = "Aggregations", format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
82
82
|
// a partial override, so a consumer can translate one op without
|
|
83
83
|
// restating all nine
|
|
84
84
|
const labelFor = (o) => opLabels?.[o] ?? CALC_LABEL[o];
|
|
@@ -96,5 +96,5 @@ export function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", o
|
|
|
96
96
|
...(numeric ? NUMERIC_CALCS : []),
|
|
97
97
|
"none",
|
|
98
98
|
];
|
|
99
|
-
return (_jsx("span", { className: columncalcRecipe({ className }), ...rest, children: _jsxs(Menu, { trigger: _jsx("button", { type: "button", className: "columncalc-trigger", "data-align": align === "right" ? "right" : undefined, children: op === "none" || result === null ? (_jsxs(_Fragment, { children: [_jsx(Plus, { size: 12, strokeWidth: 2 }), _jsx("span", { className: "columncalc-placeholder", children:
|
|
99
|
+
return (_jsx("span", { className: columncalcRecipe({ className }), ...rest, children: _jsxs(Menu, { trigger: _jsx("button", { type: "button", className: "columncalc-trigger", "data-align": align === "right" ? "right" : undefined, children: op === "none" || result === null ? (_jsxs(_Fragment, { children: [_jsx(Plus, { size: 12, strokeWidth: 2 }), _jsx("span", { className: "columncalc-placeholder", children: addLabel })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "columncalc-value", children: format(result, op) }), _jsx("span", { className: "columncalc-op", children: labelFor(op) }), _jsx(ChevronDown, { size: 12, strokeWidth: 2 })] })) }), children: [_jsx("div", { className: "columncalc-section", children: sectionLabel }), ops.map((o) => (_jsx(MenuItem, { onSelect: () => setOp(o), children: labelFor(o) }, o)))] }) }));
|
|
100
100
|
}
|
|
@@ -28,7 +28,7 @@ function Chip({ opt }) {
|
|
|
28
28
|
* popover with a search field over rows of "[chip] … [checkbox]" that toggle on
|
|
29
29
|
* click (the menu stays open). Keyboard: ↑/↓ move, Enter toggles, Esc closes.
|
|
30
30
|
*/
|
|
31
|
-
export function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…", disabled, className, "aria-label": ariaLabel, ...rest }) {
|
|
31
|
+
export function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…", emptyLabel = "No results", disabled, className, "aria-label": ariaLabel, ...rest }) {
|
|
32
32
|
// Virtual focus: DOM focus stays on the trigger (or the search field) and the
|
|
33
33
|
// active option is named by `aria-activedescendant`. `data-highlighted` is a
|
|
34
34
|
// styling hook only -- a screen reader cannot see it.
|
|
@@ -125,7 +125,7 @@ export function MultiSelect({ options, value: valueProp, defaultValue, onValueCh
|
|
|
125
125
|
}, children: [_jsx("span", { className: "multiselect-chips", children: selectedOptions.length === 0 ? (_jsx("span", { className: "multiselect-placeholder", children: placeholder })) : (selectedOptions.map((o) => _jsx(Chip, { opt: o }, o.value))) }), _jsx(ChevronDown, { className: "multiselect-chevron", size: 16, strokeWidth: 1.75 })] }), open && (_jsxs("div", { id: listboxId, className: "multiselect-listbox", role: "listbox", "aria-multiselectable": "true", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && (_jsxs("div", { className: "multiselect-search", children: [_jsx(Search, { size: 15, strokeWidth: 1.75 }), _jsx("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
|
|
126
126
|
setQuery(e.target.value);
|
|
127
127
|
setHighlight(0);
|
|
128
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "multiselect-empty", children:
|
|
128
|
+
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
129
129
|
const sel = selectedSet.has(opt.value);
|
|
130
130
|
return (_jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [_jsx("span", { className: "multiselect-option-chip", children: _jsx(Chip, { opt: opt }) }), _jsx("span", { className: "multiselect-check", children: sel && _jsx(Check, { size: 12, strokeWidth: 3 }) })] }, opt.value));
|
|
131
131
|
}))] }))] }));
|
|
@@ -15,7 +15,7 @@ function optionText(o) {
|
|
|
15
15
|
* "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
|
|
16
16
|
* Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
|
|
17
17
|
*/
|
|
18
|
-
export function Select({ options, value, defaultValue, onValueChange, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", disabled, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
18
|
+
export function Select({ options, value, defaultValue, onValueChange, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", emptyLabel = "No results", disabled, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
19
19
|
// Virtual focus: DOM focus stays on the trigger (or the search field), and the
|
|
20
20
|
// active option is named by `aria-activedescendant`. That is the only thing a
|
|
21
21
|
// screen reader has to go on -- `data-highlighted` is invisible to it -- so the
|
|
@@ -114,7 +114,7 @@ export function Select({ options, value, defaultValue, onValueChange, placeholde
|
|
|
114
114
|
return (_jsxs("div", { ref: rootRef, className: selectRecipe({ variant, width, className }), ...rest, children: [_jsxs("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, disabled: disabled, "data-open": open ? "" : undefined, className: "select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [_jsx("span", { className: "select-value", children: selectedOption ? (_jsxs(_Fragment, { children: [selectedOption.color && (_jsx("span", { className: "select-option-dot", style: { background: selectedOption.color } })), selectedOption.label] })) : (_jsx("span", { className: "select-placeholder", children: placeholder })) }), _jsx(ChevronDown, { className: "select-chevron", size: 16, strokeWidth: 1.75 })] }), open && (_jsxs("div", { id: listboxId, className: "select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && (_jsxs("div", { className: "select-search", children: [_jsx(Search, { size: 15, strokeWidth: 1.75 }), _jsx("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
|
|
115
115
|
setQuery(e.target.value);
|
|
116
116
|
setHighlight(0);
|
|
117
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "select-empty", children:
|
|
117
|
+
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
118
118
|
const isSel = opt.value === selected;
|
|
119
119
|
return (_jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && (_jsx("span", { className: "select-option-dot", style: { background: opt.color } })), _jsx("span", { className: "select-option-label", children: opt.label }), isSel && (_jsx(Check, { className: "select-option-check", size: 15, strokeWidth: 2 }))] }, opt.value));
|
|
120
120
|
}))] }))] }));
|
|
@@ -96,7 +96,7 @@ export function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
96
96
|
* composable version, to put above `DataTable` or any table of your own. Pair
|
|
97
97
|
* it with `applyFilters` / `applySorts` to turn its state into rows.
|
|
98
98
|
*/
|
|
99
|
-
export function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel = "Sort", reorderSortLabel = "Reorder sort", sortFieldLabel = "Sort field", sortDirectionLabel = "Sort direction", removeSortLabel = "Remove sort", filterFieldLabel = "Filter by field", filterFieldPlaceholder = "Filter by…", filterValuePlaceholder = "Value…", ascendingLabel = "Ascending", descendingLabel = "Descending", sortTriggerLabel, filterTriggerLabel, ...rest }) {
|
|
99
|
+
export function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel = "Sort", reorderSortLabel = "Reorder sort", noSortsLabel = "No sorts applied to this view.", addSortLabel = "Add sort", learnSortingLabel = "Learn about sorting", selectConditionLabel = "Select condition", sortFieldLabel = "Sort field", sortDirectionLabel = "Sort direction", removeSortLabel = "Remove sort", filterFieldLabel = "Filter by field", filterFieldPlaceholder = "Filter by…", filterValuePlaceholder = "Value…", ascendingLabel = "Ascending", descendingLabel = "Descending", sortTriggerLabel, filterTriggerLabel, ...rest }) {
|
|
100
100
|
const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
|
|
101
101
|
const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
|
|
102
102
|
const sorts = sortsProp ?? sortsState;
|
|
@@ -168,7 +168,7 @@ export function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsC
|
|
|
168
168
|
: sorts.length
|
|
169
169
|
? `Sorted by ${columns.find((c) => c.key === sorts[0].key)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
170
170
|
: "Sort";
|
|
171
|
-
return (_jsx("div", { className: tabletoolbarRecipe({ className }), ...rest, children: _jsxs("div", { className: "tabletoolbar-toolbar", children: [_jsxs("span", { className: "tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `tabletoolbar-tool${sorts.length ? " tabletoolbar-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" }), sortedLabel] }), openMenu === "sort" && (_jsxs("div", { className: "tabletoolbar-menu tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? (_jsx("div", { className: "tabletoolbar-sort-empty", children:
|
|
171
|
+
return (_jsx("div", { className: tabletoolbarRecipe({ className }), ...rest, children: _jsxs("div", { className: "tabletoolbar-toolbar", children: [_jsxs("span", { className: "tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `tabletoolbar-tool${sorts.length ? " tabletoolbar-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" }), sortedLabel] }), openMenu === "sort" && (_jsxs("div", { className: "tabletoolbar-menu tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? (_jsx("div", { className: "tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => (_jsxs("div", { className: "tabletoolbar-sort-row", onDragOver: (e) => {
|
|
172
172
|
if (dragSort !== null)
|
|
173
173
|
e.preventDefault();
|
|
174
174
|
}, onDrop: () => {
|
|
@@ -190,7 +190,7 @@ export function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsC
|
|
|
190
190
|
label: (_jsxs("span", { className: "tabletoolbar-sort-optlabel", children: [_jsx(ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), descendingLabel] })),
|
|
191
191
|
keywords: descendingLabel,
|
|
192
192
|
},
|
|
193
|
-
], "aria-label": sortDirectionLabel }), _jsx("button", { type: "button", className: "tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), _jsx("div", { className: "tabletoolbar-sort-addrow", children: _jsxs("button", { type: "button", className: "tabletoolbar-sort-add", onClick: addSort, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }),
|
|
193
|
+
], "aria-label": sortDirectionLabel }), _jsx("button", { type: "button", className: "tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), _jsx("div", { className: "tabletoolbar-sort-addrow", children: _jsxs("button", { type: "button", className: "tabletoolbar-sort-add", onClick: addSort, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && (_jsx("div", { className: "tabletoolbar-sort-foot", children: _jsxs("a", { className: "tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [_jsx(CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] }), _jsxs("span", { className: "tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `tabletoolbar-tool tabletoolbar-tool--ghost${activeFilters ? " tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "filter" ? null : "filter"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
194
194
|
? filterTriggerLabel(activeFilters)
|
|
195
195
|
: activeFilters
|
|
196
196
|
? `Filter · ${activeFilters}`
|
|
@@ -204,7 +204,7 @@ export function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsC
|
|
|
204
204
|
const col = columns.find((c) => c.key === cond.key);
|
|
205
205
|
const open = openMenu === `fcond:${cond.id}`;
|
|
206
206
|
const hasValue = !opNeedsValue(cond.op) || cond.value.trim();
|
|
207
|
-
return (_jsxs("span", { className: "tabletoolbar-toolgroup", children: [_jsxs("span", { className: "tabletoolbar-fchip", children: [_jsxs("button", { type: "button", className: "tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [_jsx("span", { className: "tabletoolbar-fchip-field", children: col?.label }), hasValue ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "tabletoolbar-fchip-op", children: OP_LABELS[cond.op] }), opNeedsValue(cond.op) && (_jsx("span", { className: "tabletoolbar-fchip-val", children: cond.value }))] })) : (_jsx("span", { className: "tabletoolbar-fchip-op", children:
|
|
207
|
+
return (_jsxs("span", { className: "tabletoolbar-toolgroup", children: [_jsxs("span", { className: "tabletoolbar-fchip", children: [_jsxs("button", { type: "button", className: "tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [_jsx("span", { className: "tabletoolbar-fchip-field", children: col?.label }), hasValue ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "tabletoolbar-fchip-op", children: OP_LABELS[cond.op] }), opNeedsValue(cond.op) && (_jsx("span", { className: "tabletoolbar-fchip-val", children: cond.value }))] })) : (_jsx("span", { className: "tabletoolbar-fchip-op", children: selectConditionLabel }))] }), _jsx("button", { type: "button", className: "tabletoolbar-fchip-x", "aria-label": `Remove ${col?.label} filter`, onClick: () => removeFilter(cond.id), children: _jsx(X, { size: 13, strokeWidth: 2 }) })] }), open && (_jsxs("div", { className: "tabletoolbar-menu", role: "dialog", "aria-label": `${col?.label} condition`, children: [_jsxs("div", { className: "tabletoolbar-menu-head", children: [col?.label, " condition"] }), OP_ORDER.map((op) => (_jsxs("button", { type: "button", className: "tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [OP_LABELS[op], cond.op === op ? (_jsx(Check, { className: "tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && (_jsx("input", { className: "tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
208
208
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
209
209
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) }))] }))] }, cond.id));
|
|
210
210
|
}), _jsx("span", { className: "tabletoolbar-spacer" }), count != null && _jsx("span", { className: "tabletoolbar-count", children: count }), children] }) }));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { usePrefersReducedMotion } from "../../core/index.js";
|
|
3
4
|
import { wheelpickerRecipe } from "./wheelpicker.recipe.js";
|
|
4
5
|
const ITEM_HEIGHT = { small: 36, medium: 44 };
|
|
5
6
|
/**
|
|
@@ -34,6 +35,11 @@ export function WheelPicker({ columns, value: valueProp, defaultValue, onValueCh
|
|
|
34
35
|
setInternal(merged);
|
|
35
36
|
onValueChange?.(merged);
|
|
36
37
|
};
|
|
38
|
+
// CSS cannot reach this: `scroll-behavior` in a stylesheet does not override
|
|
39
|
+
// an explicit `behavior` passed to scrollTo, so the preference is read here
|
|
40
|
+
// and the two "smooth" call sites go through `glide` (#208).
|
|
41
|
+
const reduceMotion = usePrefersReducedMotion();
|
|
42
|
+
const glide = reduceMotion ? "auto" : "smooth";
|
|
37
43
|
const scrollTo = React.useCallback((key, index, behavior) => {
|
|
38
44
|
const el = refs.current.get(key);
|
|
39
45
|
if (!el || index < 0)
|
|
@@ -71,7 +77,7 @@ export function WheelPicker({ columns, value: valueProp, defaultValue, onValueCh
|
|
|
71
77
|
if (!opt)
|
|
72
78
|
return;
|
|
73
79
|
commit(col.key, opt.value);
|
|
74
|
-
scrollTo(col.key, clamped,
|
|
80
|
+
scrollTo(col.key, clamped, glide);
|
|
75
81
|
};
|
|
76
82
|
if (e.key === "ArrowDown")
|
|
77
83
|
go(i + 1);
|
|
@@ -91,7 +97,7 @@ export function WheelPicker({ columns, value: valueProp, defaultValue, onValueCh
|
|
|
91
97
|
const selected = opt.value === value[col.key];
|
|
92
98
|
return (_jsx("div", { id: optionId(col.key, opt.value), role: "option", "aria-selected": selected, "data-selected": selected ? "" : undefined, className: "wheelpicker-item", onClick: () => {
|
|
93
99
|
commit(col.key, opt.value);
|
|
94
|
-
scrollTo(col.key, col.options.indexOf(opt),
|
|
100
|
+
scrollTo(col.key, col.options.indexOf(opt), glide);
|
|
95
101
|
}, children: opt.label }, opt.value));
|
|
96
102
|
}) }, col.key))) }) })] }));
|
|
97
103
|
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
3
|
+
const subscribe = (onChange) => {
|
|
4
|
+
if (typeof window === "undefined" || !window.matchMedia)
|
|
5
|
+
return () => { };
|
|
6
|
+
const mq = window.matchMedia(QUERY);
|
|
7
|
+
mq.addEventListener("change", onChange);
|
|
8
|
+
return () => mq.removeEventListener("change", onChange);
|
|
9
|
+
};
|
|
10
|
+
const getSnapshot = () => typeof window !== "undefined" && window.matchMedia
|
|
11
|
+
? window.matchMedia(QUERY).matches
|
|
12
|
+
: false;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the user has asked for reduced motion, live.
|
|
15
|
+
*
|
|
16
|
+
* crisp's CSS carries a `@media (prefers-reduced-motion: reduce)` override in
|
|
17
|
+
* every animating recipe, and `bun run spec` checks it. That covers CSS.
|
|
18
|
+
* Motion driven from JavaScript — `scrollTo({ behavior: "smooth" })`,
|
|
19
|
+
* `element.animate()` — is invisible to a stylesheet: `scroll-behavior` in CSS
|
|
20
|
+
* does NOT override an explicit `behavior` argument. Those call sites have to
|
|
21
|
+
* read the preference here (#208).
|
|
22
|
+
*
|
|
23
|
+
* The server snapshot is `false` rather than "unknown" on purpose. It is the
|
|
24
|
+
* only value that keeps the markup identical on both sides, and a component
|
|
25
|
+
* using this must not put the result in its output for the same reason — the
|
|
26
|
+
* preference belongs in a handler or an effect, where it changes behaviour
|
|
27
|
+
* rather than HTML.
|
|
28
|
+
*/
|
|
29
|
+
export function usePrefersReducedMotion() {
|
|
30
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
31
|
+
}
|
|
@@ -27,6 +27,7 @@ export const recordPageLabels = {
|
|
|
27
27
|
composeEmail: "Compose email",
|
|
28
28
|
askAttio: "Ask Attio",
|
|
29
29
|
addToList: "Add to list",
|
|
30
|
+
noLists: "This record has not been added to any lists.",
|
|
30
31
|
highlights: "Highlights",
|
|
31
32
|
recordDetails: "Record Details",
|
|
32
33
|
searchFields: "Search fields",
|
|
@@ -39,6 +40,10 @@ export const recordPageLabels = {
|
|
|
39
40
|
viewLess: "View less",
|
|
40
41
|
newNote: "New note",
|
|
41
42
|
newTask: "New task",
|
|
43
|
+
notePlaceholder: "Write a note… (⌘↵ to save)",
|
|
44
|
+
saveNote: "Save note",
|
|
45
|
+
taskPlaceholder: "Task name… (↵ to add)",
|
|
46
|
+
addTask: "Add task",
|
|
42
47
|
cancel: "Cancel",
|
|
43
48
|
noNotesYet: "No notes yet.",
|
|
44
49
|
noTasksYet: "No tasks yet.",
|
|
@@ -122,14 +127,14 @@ function Composer({ placeholder, submitLabel, multiline, onSubmit, onCancel, lab
|
|
|
122
127
|
}
|
|
123
128
|
function NotesSection({ notes, onAdd, labels, }) {
|
|
124
129
|
const [open, setOpen] = React.useState(false);
|
|
125
|
-
return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.notes, count: notes.length, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && (_jsx(Composer, { labels: labels, multiline: true, placeholder:
|
|
130
|
+
return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.notes, count: notes.length, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && (_jsx(Composer, { labels: labels, multiline: true, placeholder: labels.notePlaceholder, submitLabel: labels.saveNote, onCancel: () => setOpen(false), onSubmit: (t) => {
|
|
126
131
|
onAdd(t);
|
|
127
132
|
setOpen(false);
|
|
128
133
|
} })), notes.length === 0 && !open ? (_jsx("div", { className: "recordpage-emptystate", children: labels.noNotesYet })) : (notes.map((n) => (_jsxs("div", { className: "recordpage-note", children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), _jsxs("div", { className: "recordpage-note-body", children: [_jsxs("div", { className: "recordpage-note-head", children: [_jsx("span", { className: "recordpage-note-author", children: labels.you }), _jsx("span", { className: "recordpage-note-time", children: n.time })] }), _jsx("div", { className: "recordpage-note-text", children: n.text })] })] }, n.id))))] }));
|
|
129
134
|
}
|
|
130
135
|
function TasksSection({ tasks, onAdd, onToggle, labels, }) {
|
|
131
136
|
const [open, setOpen] = React.useState(false);
|
|
132
|
-
return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.tasks, count: tasks.length, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newTask] }) }), open && (_jsx(Composer, { labels: labels, placeholder:
|
|
137
|
+
return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.tasks, count: tasks.length, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newTask] }) }), open && (_jsx(Composer, { labels: labels, placeholder: labels.taskPlaceholder, submitLabel: labels.addTask, onCancel: () => setOpen(false), onSubmit: (t) => {
|
|
133
138
|
onAdd(t);
|
|
134
139
|
setOpen(false);
|
|
135
140
|
} })), tasks.length === 0 && !open ? (_jsx("div", { className: "recordpage-emptystate", children: labels.noTasksYet })) : (tasks.map((t) => (_jsxs("div", { className: "recordpage-task", "data-done": t.done ? "" : undefined, children: [_jsx(Checkbox, { checked: t.done, onChange: () => onToggle(t.id), "aria-label": t.text }), _jsx("span", { className: "recordpage-task-text", children: t.text })] }, t.id))))] }));
|
|
@@ -362,7 +367,7 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
362
367
|
setSearchOpen(next);
|
|
363
368
|
if (!next)
|
|
364
369
|
setFieldQuery("");
|
|
365
|
-
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsxs("div", { className: "recordpage-noresults", children: ["No fields match \u201C", fieldQuery, "\u201D."] })))] }), _jsxs("div", { className: "recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "recordpage-emptystate", children:
|
|
370
|
+
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsxs("div", { className: "recordpage-noresults", children: ["No fields match \u201C", fieldQuery, "\u201D."] })))] }), _jsxs("div", { className: "recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": 300, "aria-valuemax": 560, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
366
371
|
if (e.key === "ArrowLeft")
|
|
367
372
|
setDetailsW((w) => Math.max(300, w - 16));
|
|
368
373
|
else if (e.key === "ArrowRight")
|
|
@@ -86,6 +86,9 @@ export const recordsTableLabels = {
|
|
|
86
86
|
duplicate: "Duplicate",
|
|
87
87
|
addToList: "Add to list",
|
|
88
88
|
inAList: "In a list",
|
|
89
|
+
searchRecordsPlaceholder: "Search records…",
|
|
90
|
+
searchOptionsPlaceholder: "Search options…",
|
|
91
|
+
selectedCount: "Selected",
|
|
89
92
|
delete: "Delete",
|
|
90
93
|
sendEmail: "Send email",
|
|
91
94
|
exportCsv: "Export CSV",
|
|
@@ -271,14 +274,14 @@ function StatusCell({ column, row, onChange, }) {
|
|
|
271
274
|
return (_jsx(Select, { variant: "ghost", "aria-label": column.label, placeholder: "\u2014", value: row[column.key] ?? undefined, onValueChange: onChange, options: column.options ?? [] }));
|
|
272
275
|
}
|
|
273
276
|
function ReferenceCell({ column, row, onChange, labels, }) {
|
|
274
|
-
return (_jsx(Select, { variant: "ghost", searchable: true, searchPlaceholder:
|
|
277
|
+
return (_jsx(Select, { variant: "ghost", searchable: true, searchPlaceholder: labels.searchRecordsPlaceholder, "aria-label": column.label, placeholder: labels.setRecordPlaceholder, value: row[column.key] ?? undefined, onValueChange: onChange, options: (column.refOptions ?? []).map((o) => ({
|
|
275
278
|
value: o.value,
|
|
276
279
|
label: o.label,
|
|
277
280
|
})) }));
|
|
278
281
|
}
|
|
279
282
|
function TagsCell({ column, row, options, onChange, labels, }) {
|
|
280
283
|
const value = row[column.key] ?? [];
|
|
281
|
-
return (_jsx(MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder:
|
|
284
|
+
return (_jsx(MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder: labels.searchOptionsPlaceholder, "aria-label": column.label }));
|
|
282
285
|
}
|
|
283
286
|
function DateCell({ column, row, onChange, }) {
|
|
284
287
|
const raw = row[column.key];
|
|
@@ -1009,7 +1012,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
|
|
|
1009
1012
|
createPortal(_jsx("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--up", role: "menu", "aria-label": `${col.label} summary`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: aggOptions(col).map((opt) => (_jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitemradio", tabIndex: -1, "aria-checked": a === opt, "data-on": a === opt ? "" : undefined, onClick: () => setAggValue(col.key, opt), children: [t.agg[opt], a === opt ? (_jsx(Check, { className: "recordstable-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, opt))) }), rootRef.current)] }) }, col.key));
|
|
1010
1013
|
}), _jsx("td", { className: "recordstable-sum recordstable-addcol" })] }) })] }), visibleRows.length === 0 ? (_jsx("div", { className: "recordstable-empty", children: _jsx(EmptyState, { variant: "page", icon: activeFilters ? (_jsx(ListFilter, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })) : (_jsx(Inbox, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })), title: activeFilters ? "No records match that filter" : t.noRecords, description: activeFilters
|
|
1011
1014
|
? "None of your records match the filters that are set."
|
|
1012
|
-
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? (_jsxs(Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [_jsx(ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : (_jsxs(Button, { intent: "primary", size: "sm", onClick: addRow, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : (_jsxs("button", { type: "button", className: "recordstable-addrow", onClick: addRow, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), selected.size > 0 && (_jsxs(BulkBar, { count: selected.size, countLabel:
|
|
1015
|
+
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? (_jsxs(Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [_jsx(ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : (_jsxs(Button, { intent: "primary", size: "sm", onClick: addRow, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : (_jsxs("button", { type: "button", className: "recordstable-addrow", onClick: addRow, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), selected.size > 0 && (_jsxs(BulkBar, { count: selected.size, countLabel: t.selectedCount, onClear: () => setSelected(new Set()), children: [_jsx(BulkBarButton, { icon: _jsx(ListPlus, { size: 15, strokeWidth: 1.75 }), onClick: addToList, children: t.addToList }), _jsx(BulkBarButton, { icon: _jsx(Mail, { size: 15, strokeWidth: 1.75 }), onClick: sendEmail, children: t.sendEmail }), _jsx(BulkBarButton, { icon: _jsx(Workflow, { size: 15, strokeWidth: 1.75 }), onClick: runWorkflow, children: t.runWorkflow }), _jsxs(Menu, { align: "end", "aria-label": t.moreActions, trigger: _jsx(BulkBarButton, { icon: _jsx(MoreHorizontal, { size: 15, strokeWidth: 1.75 }), children: t.more }), children: [_jsx(MenuItem, { icon: _jsx(Download, { size: 15, strokeWidth: 1.75 }), onSelect: exportSelected, children: t.exportCsv }), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, onSelect: deleteSelected, children: t.delete })] })] })), _jsx(CreateRecordDialog, { labels: t, open: creating, onOpenChange: setCreating, columns: cols, entityLabel: cols.find((c) => c.type === "entity")?.label ?? "record", onCreate: (row) => setData((prev) => [...prev, row]) }), ctxMenu &&
|
|
1013
1016
|
createPortal(_jsxs("div", { ref: ctxRef, className: "recordstable-menu recordstable-ctx", role: "menu", "aria-label": t.recordActions, style: { position: "fixed", top: ctxMenu.y, left: ctxMenu.x }, onKeyDown: onCtxKeyDown, children: [_jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => copyRow(ctxMenu.id), children: [_jsx(Copy, { size: 15, strokeWidth: 1.75 }), t.copy] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => duplicateRow(ctxMenu.id), children: [_jsx(CopyPlus, { size: 15, strokeWidth: 1.75 }), t.duplicate] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => pinRow(ctxMenu.id), children: [_jsx(Star, { size: 15, strokeWidth: 1.75 }), t.addToList] }), _jsxs("button", { type: "button", className: "recordstable-menu-item recordstable-menu-item--danger", role: "menuitem", tabIndex: -1, onClick: () => deleteRow(ctxMenu.id), children: [_jsx(Trash2, { size: 15, strokeWidth: 1.75 }), t.delete] })] }), rootRef.current ?? document.body)] }));
|
|
1014
1017
|
}
|
|
1015
1018
|
export const defaultColumns = [
|
|
@@ -81,7 +81,7 @@ export const ATTIO_SIDEBAR_FOOTER = {
|
|
|
81
81
|
function SectionHeader({ label, open, onToggle, }) {
|
|
82
82
|
return (_jsxs("button", { type: "button", className: "sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [_jsx(IconChevronMini, {}), _jsx("span", { children: label })] }));
|
|
83
83
|
}
|
|
84
|
-
export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", workspace = ATTIO_SIDEBAR_WORKSPACE, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, footer = ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
84
|
+
export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, footer = ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
85
85
|
const [collapsedSections, setCollapsedSections] = React.useState(() => new Set());
|
|
86
86
|
const [commandOpen, setCommandOpen] = React.useState(false);
|
|
87
87
|
React.useEffect(() => {
|
|
@@ -107,6 +107,6 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
|
|
|
107
107
|
return next;
|
|
108
108
|
});
|
|
109
109
|
}, []);
|
|
110
|
-
return (_jsxs(Primitive.div, { ref: ref, asChild: asChild, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {}), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "sidebar-search-label", children:
|
|
110
|
+
return (_jsxs(Primitive.div, { ref: ref, asChild: asChild, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {}), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "sidebar-search-label", children: searchLabel }), _jsx("span", { className: "sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "sidebar-divider" }), _jsx("div", { className: "sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing }) => (_jsx(NavItem, { href: "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, children: label }, label))) }), _jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => (_jsx(NavItem, { href: "#", icon: _jsx(ItemIcon, {}), children: label }, label))) }), _jsxs("div", { className: "sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "sidebar-divider" }), _jsxs("div", { className: "sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen })] }));
|
|
111
111
|
});
|
|
112
112
|
Sidebar.displayName = "Sidebar";
|
|
@@ -37,6 +37,10 @@ export type ColumnCalcProps<T> = Omit<React.ComponentPropsWithoutRef<"span">, "v
|
|
|
37
37
|
/** Controlled operation. */
|
|
38
38
|
op?: CalcOp;
|
|
39
39
|
onOpChange?: (op: CalcOp) => void;
|
|
40
|
+
/** Placeholder in the cell when no calculation is chosen. */
|
|
41
|
+
addLabel?: string;
|
|
42
|
+
/** Heading above the operation list in the menu. */
|
|
43
|
+
sectionLabel?: string;
|
|
40
44
|
/** Overrides the operation names shown in the menu and the cell. Partial —
|
|
41
45
|
* anything omitted keeps its English default from `CALC_LABEL`. */
|
|
42
46
|
opLabels?: Partial<Record<CalcOp, string>>;
|
|
@@ -46,4 +50,4 @@ export type ColumnCalcProps<T> = Omit<React.ComponentPropsWithoutRef<"span">, "v
|
|
|
46
50
|
align?: "left" | "right";
|
|
47
51
|
className?: string;
|
|
48
52
|
};
|
|
49
|
-
export declare function ColumnCalc<T>({ rows, value, numeric, defaultOp, op: opProp, onOpChange, format, align, opLabels, className, ...rest }: ColumnCalcProps<T>): React.JSX.Element;
|
|
53
|
+
export declare function ColumnCalc<T>({ rows, value, numeric, defaultOp, op: opProp, onOpChange, addLabel, sectionLabel, format, align, opLabels, className, ...rest }: ColumnCalcProps<T>): React.JSX.Element;
|
|
@@ -17,6 +17,8 @@ export type MultiSelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "valu
|
|
|
17
17
|
/** Show a filter field at the top of the list (default true). */
|
|
18
18
|
searchable?: boolean;
|
|
19
19
|
searchPlaceholder?: string;
|
|
20
|
+
/** Shown when the search matches nothing. */
|
|
21
|
+
emptyLabel?: string;
|
|
20
22
|
disabled?: boolean;
|
|
21
23
|
className?: string;
|
|
22
24
|
"aria-label"?: string;
|
|
@@ -27,7 +29,7 @@ export type MultiSelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "valu
|
|
|
27
29
|
* popover with a search field over rows of "[chip] … [checkbox]" that toggle on
|
|
28
30
|
* click (the menu stays open). Keyboard: ↑/↓ move, Enter toggles, Esc closes.
|
|
29
31
|
*/
|
|
30
|
-
export declare function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder, searchable, searchPlaceholder, disabled, className, "aria-label": ariaLabel, ...rest }: MultiSelectProps): React.JSX.Element;
|
|
32
|
+
export declare function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, placeholder, searchable, searchPlaceholder, emptyLabel, disabled, className, "aria-label": ariaLabel, ...rest }: MultiSelectProps): React.JSX.Element;
|
|
31
33
|
export declare namespace MultiSelect {
|
|
32
34
|
var displayName: string;
|
|
33
35
|
}
|
|
@@ -22,6 +22,8 @@ export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" |
|
|
|
22
22
|
/** Show a search field at the top of the list that filters options (Linear/cmdk style). */
|
|
23
23
|
searchable?: boolean;
|
|
24
24
|
searchPlaceholder?: string;
|
|
25
|
+
/** Shown when the search matches nothing. */
|
|
26
|
+
emptyLabel?: string;
|
|
25
27
|
disabled?: boolean;
|
|
26
28
|
className?: string;
|
|
27
29
|
"aria-label"?: string;
|
|
@@ -34,7 +36,7 @@ export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" |
|
|
|
34
36
|
* "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
|
|
35
37
|
* Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
|
|
36
38
|
*/
|
|
37
|
-
export declare function Select({ options, value, defaultValue, onValueChange, placeholder, variant, width, searchable, searchPlaceholder, disabled, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
|
|
39
|
+
export declare function Select({ options, value, defaultValue, onValueChange, placeholder, variant, width, searchable, searchPlaceholder, emptyLabel, disabled, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
|
|
38
40
|
export declare namespace Select {
|
|
39
41
|
var displayName: string;
|
|
40
42
|
}
|
|
@@ -36,6 +36,14 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
|
|
|
36
36
|
count?: React.ReactNode;
|
|
37
37
|
/** Right-aligned actions after the count (a "New" button, say). */
|
|
38
38
|
children?: React.ReactNode;
|
|
39
|
+
/** Empty state in the sort popover. */
|
|
40
|
+
noSortsLabel?: string;
|
|
41
|
+
/** Label on the button that appends a sort condition. */
|
|
42
|
+
addSortLabel?: string;
|
|
43
|
+
/** Text of the help link, when `helpHref` is set. */
|
|
44
|
+
learnSortingLabel?: string;
|
|
45
|
+
/** Placeholder on a filter condition's operator picker. */
|
|
46
|
+
selectConditionLabel?: string;
|
|
39
47
|
/** Shows a "Learn about sorting" link in the sort popover when set. */
|
|
40
48
|
helpHref?: string;
|
|
41
49
|
/** aria-label on the Sort trigger (default "Sort"). */
|
|
@@ -85,5 +93,5 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
|
|
|
85
93
|
* composable version, to put above `DataTable` or any table of your own. Pair
|
|
86
94
|
* it with `applyFilters` / `applySorts` to turn its state into rows.
|
|
87
95
|
*/
|
|
88
|
-
export declare function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel, reorderSortLabel, sortFieldLabel, sortDirectionLabel, removeSortLabel, filterFieldLabel, filterFieldPlaceholder, filterValuePlaceholder, ascendingLabel, descendingLabel, sortTriggerLabel, filterTriggerLabel, ...rest }: TableToolbarProps): React.JSX.Element;
|
|
96
|
+
export declare function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel, reorderSortLabel, noSortsLabel, addSortLabel, learnSortingLabel, selectConditionLabel, sortFieldLabel, sortDirectionLabel, removeSortLabel, filterFieldLabel, filterFieldPlaceholder, filterValuePlaceholder, ascendingLabel, descendingLabel, sortTriggerLabel, filterTriggerLabel, ...rest }: TableToolbarProps): React.JSX.Element;
|
|
89
97
|
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the user has asked for reduced motion, live.
|
|
3
|
+
*
|
|
4
|
+
* crisp's CSS carries a `@media (prefers-reduced-motion: reduce)` override in
|
|
5
|
+
* every animating recipe, and `bun run spec` checks it. That covers CSS.
|
|
6
|
+
* Motion driven from JavaScript — `scrollTo({ behavior: "smooth" })`,
|
|
7
|
+
* `element.animate()` — is invisible to a stylesheet: `scroll-behavior` in CSS
|
|
8
|
+
* does NOT override an explicit `behavior` argument. Those call sites have to
|
|
9
|
+
* read the preference here (#208).
|
|
10
|
+
*
|
|
11
|
+
* The server snapshot is `false` rather than "unknown" on purpose. It is the
|
|
12
|
+
* only value that keeps the markup identical on both sides, and a component
|
|
13
|
+
* using this must not put the result in its output for the same reason — the
|
|
14
|
+
* preference belongs in a handler or an effect, where it changes behaviour
|
|
15
|
+
* rather than HTML.
|
|
16
|
+
*/
|
|
17
|
+
export declare function usePrefersReducedMotion(): boolean;
|
|
@@ -40,6 +40,8 @@ export type RecordPageLabels = {
|
|
|
40
40
|
composeEmail: string;
|
|
41
41
|
askAttio: string;
|
|
42
42
|
addToList: string;
|
|
43
|
+
/** Empty state under the Lists panel. */
|
|
44
|
+
noLists: string;
|
|
43
45
|
highlights: string;
|
|
44
46
|
recordDetails: string;
|
|
45
47
|
searchFields: string;
|
|
@@ -52,6 +54,14 @@ export type RecordPageLabels = {
|
|
|
52
54
|
viewLess: string;
|
|
53
55
|
newNote: string;
|
|
54
56
|
newTask: string;
|
|
57
|
+
/** Placeholder in the note composer. */
|
|
58
|
+
notePlaceholder: string;
|
|
59
|
+
/** Submit button in the note composer. */
|
|
60
|
+
saveNote: string;
|
|
61
|
+
/** Placeholder in the task composer. */
|
|
62
|
+
taskPlaceholder: string;
|
|
63
|
+
/** Submit button in the task composer. */
|
|
64
|
+
addTask: string;
|
|
55
65
|
cancel: string;
|
|
56
66
|
noNotesYet: string;
|
|
57
67
|
noTasksYet: string;
|
|
@@ -146,6 +146,12 @@ export type RecordsTableLabels = {
|
|
|
146
146
|
duplicate: string;
|
|
147
147
|
addToList: string;
|
|
148
148
|
inAList: string;
|
|
149
|
+
/** Placeholder in the record search field. */
|
|
150
|
+
searchRecordsPlaceholder: string;
|
|
151
|
+
/** Placeholder in a select cell's option search. */
|
|
152
|
+
searchOptionsPlaceholder: string;
|
|
153
|
+
/** Prefix on the bulk bar's selected count. */
|
|
154
|
+
selectedCount: string;
|
|
149
155
|
delete: string;
|
|
150
156
|
sendEmail: string;
|
|
151
157
|
exportCsv: string;
|
|
@@ -52,6 +52,8 @@ export type SidebarProps = React.ComponentPropsWithoutRef<"nav"> & {
|
|
|
52
52
|
collapseLabel?: string;
|
|
53
53
|
/** Label for the inbox quick-action. */
|
|
54
54
|
inboxLabel?: string;
|
|
55
|
+
/** Label for the search / command-palette button. */
|
|
56
|
+
searchLabel?: string;
|
|
55
57
|
asChild?: boolean;
|
|
56
58
|
/** Called when the collapse-panel button in the header is pressed. */
|
|
57
59
|
onToggleCollapse?: () => void;
|
|
@@ -85,6 +87,8 @@ export declare const Sidebar: React.ForwardRefExoticComponent<Omit<React.Detaile
|
|
|
85
87
|
collapseLabel?: string;
|
|
86
88
|
/** Label for the inbox quick-action. */
|
|
87
89
|
inboxLabel?: string;
|
|
90
|
+
/** Label for the search / command-palette button. */
|
|
91
|
+
searchLabel?: string;
|
|
88
92
|
asChild?: boolean;
|
|
89
93
|
/** Called when the collapse-panel button in the header is pressed. */
|
|
90
94
|
onToggleCollapse?: () => void;
|