@crisp-ui-kit/crisp 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/composewindow/composewindow.js +144 -0
- package/dist/cjs/components/composewindow/composewindow.recipe.js +45 -0
- package/dist/cjs/components/composewindow/index.js +8 -0
- package/dist/cjs/components/datatable/datatable.js +224 -43
- package/dist/cjs/components/datatable/datatable.recipe.js +37 -3
- package/dist/cjs/components/datatable/index.js +6 -0
- package/dist/cjs/components/datepickerfield/datepickerfield.js +25 -15
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +25 -8
- package/dist/cjs/components/dialog/dialog.recipe.js +9 -1
- package/dist/cjs/components/menu/menu.js +4 -2
- package/dist/cjs/components/multiselect/multiselect.recipe.js +14 -8
- package/dist/cjs/components/select/select.js +2 -1
- package/dist/cjs/components/table/table.recipe.js +12 -3
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +18 -7
- package/dist/cjs/core/cell-date.js +11 -0
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/recordpage/recordpage.js +88 -10
- package/dist/cjs/product/recordpage/recordpage.recipe.js +30 -0
- package/dist/cjs/product/recordpanel/recordpanel.js +54 -4
- package/dist/cjs/product/recordpanel/recordpanel.recipe.js +29 -0
- package/dist/cjs/product/recordstable/editable-field.js +110 -0
- package/dist/cjs/product/recordstable/index.js +4 -1
- package/dist/cjs/product/recordstable/recordstable.js +67 -11
- package/dist/cjs/product/recordstable/recordstable.recipe.js +9 -1
- package/dist/cjs/product/settingsshell/settingsshell.recipe.js +13 -4
- package/dist/cjs/product/sidebar/sidebar.js +15 -3
- package/dist/cjs/product/sidebar/sidebar.recipe.js +21 -2
- package/dist/composewindow.css +24 -0
- package/dist/composewindow.d.ts +7 -0
- package/dist/composewindow.layered.css +26 -0
- package/dist/datatable.css +33 -14
- package/dist/datatable.layered.css +33 -14
- package/dist/datepickerfield.css +9 -4
- package/dist/datepickerfield.layered.css +9 -4
- package/dist/dialog.css +2 -1
- package/dist/dialog.layered.css +2 -1
- package/dist/esm/components/composewindow/composewindow.js +107 -0
- package/dist/esm/components/composewindow/composewindow.recipe.js +42 -0
- package/dist/esm/components/composewindow/index.js +2 -0
- package/dist/esm/components/datatable/datatable.js +226 -45
- package/dist/esm/components/datatable/datatable.recipe.js +37 -3
- package/dist/esm/components/datatable/index.js +6 -0
- package/dist/esm/components/datepickerfield/datepickerfield.js +26 -16
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +25 -8
- package/dist/esm/components/dialog/dialog.recipe.js +9 -1
- package/dist/esm/components/menu/menu.js +4 -2
- package/dist/esm/components/multiselect/multiselect.recipe.js +14 -8
- package/dist/esm/components/select/select.js +2 -1
- package/dist/esm/components/table/table.recipe.js +12 -3
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +18 -7
- package/dist/esm/core/cell-date.js +10 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/recordpage/recordpage.js +89 -11
- package/dist/esm/product/recordpage/recordpage.recipe.js +30 -0
- package/dist/esm/product/recordpanel/recordpanel.js +55 -5
- package/dist/esm/product/recordpanel/recordpanel.recipe.js +29 -0
- package/dist/esm/product/recordstable/editable-field.js +73 -0
- package/dist/esm/product/recordstable/index.js +1 -0
- package/dist/esm/product/recordstable/recordstable.js +69 -13
- package/dist/esm/product/recordstable/recordstable.recipe.js +9 -1
- package/dist/esm/product/settingsshell/settingsshell.recipe.js +13 -4
- package/dist/esm/product/sidebar/sidebar.js +15 -3
- package/dist/esm/product/sidebar/sidebar.recipe.js +21 -2
- package/dist/multiselect.css +8 -8
- package/dist/multiselect.layered.css +8 -8
- package/dist/recordpage.css +25 -13
- package/dist/recordpage.layered.css +25 -13
- package/dist/recordpanel.css +22 -5
- package/dist/recordpanel.layered.css +22 -5
- package/dist/recordstable.css +9 -2
- package/dist/recordstable.layered.css +9 -2
- package/dist/settingsshell.css +2 -2
- package/dist/settingsshell.layered.css +2 -2
- package/dist/sidebar.css +7 -2
- package/dist/sidebar.layered.css +7 -2
- package/dist/styles.css +147 -54
- package/dist/styles.layered.css +147 -54
- package/dist/table.css +4 -2
- package/dist/table.layered.css +4 -2
- package/dist/tabletoolbar.css +1 -1
- package/dist/tabletoolbar.layered.css +1 -1
- package/dist/types/components/composewindow/composewindow.d.ts +80 -0
- package/dist/types/components/composewindow/composewindow.recipe.d.ts +3 -0
- package/dist/types/components/composewindow/index.d.ts +2 -0
- package/dist/types/components/datatable/datatable.d.ts +58 -4
- package/dist/types/components/datatable/index.d.ts +1 -1
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +18 -4
- package/dist/types/components/menu/menu.d.ts +10 -1
- package/dist/types/components/select/select.d.ts +6 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +29 -5
- package/dist/types/core/cell-date.d.ts +17 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/recordpage/recordpage.d.ts +59 -1
- package/dist/types/product/recordpanel/recordpanel.d.ts +11 -1
- package/dist/types/product/recordstable/editable-field.d.ts +45 -0
- package/dist/types/product/recordstable/index.d.ts +1 -0
- package/dist/types/product/recordstable/recordstable.d.ts +29 -0
- package/dist/types/product/sidebar/sidebar.d.ts +60 -4
- package/package.json +6 -1
|
@@ -51,10 +51,16 @@ const config = (0, core_1.defineRecipe)({
|
|
|
51
51
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
|
|
52
52
|
& .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
53
53
|
& .crisp-datatable-sort { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 ${tokens_1.contract.space["2"]}; border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
/* A right-aligned column's header keeps its field-icon + label packed at
|
|
55
|
+
the START, matching its own values only in that the VALUES right-align
|
|
56
|
+
-- Attio's own right-aligned header (measured live: "Twitter follower
|
|
57
|
+
count") keeps the icon+label left and only the trailing column-menu
|
|
58
|
+
icon sits pinned to the right edge, independent of the label's width
|
|
59
|
+
(#406, reversing #393's own justify-content: flex-end here). The
|
|
60
|
+
chevron's own margin-inline-start: auto below already pins IT to the
|
|
61
|
+
end of a flex-start row with no override needed -- that's what pins
|
|
62
|
+
the trailing icon on Attio too. */
|
|
56
63
|
& .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; opacity: 0; transition: opacity .12s; }
|
|
57
|
-
& th[data-align="right"] .crisp-datatable-chevron { margin-inline-start: 0; order: -1; }
|
|
58
64
|
& .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
|
|
59
65
|
& .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: ${tokens_1.contract.fgPrimary}; }
|
|
60
66
|
/* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
|
|
@@ -77,6 +83,34 @@ const config = (0, core_1.defineRecipe)({
|
|
|
77
83
|
& .crisp-datatable-editable:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, ${tokens_1.contract.bgSurface}); cursor: text; }
|
|
78
84
|
& .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 ${tokens_1.contract.space["1.5"]}; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; outline: none; vertical-align: middle; }
|
|
79
85
|
& .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
|
|
86
|
+
/* An async onEdit's in-flight state (#385) -- the field/picker dims and
|
|
87
|
+
stops accepting input rather than disappearing, so the just-typed or
|
|
88
|
+
just-chosen value stays visible while its commit is pending. Same
|
|
89
|
+
treatment Input already gives its own disabled state. */
|
|
90
|
+
& .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
91
|
+
& .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: ${tokens_1.contract.space["1"]}; color: ${tokens_1.contract.fgMuted}; vertical-align: middle; }
|
|
92
|
+
/* The rejection's message, beside the cell's own content -- a sibling in
|
|
93
|
+
the TableCell, never nested inside a column's own render output, so a
|
|
94
|
+
hover-only wrapper (a Tooltip, say) around a custom render can't hide
|
|
95
|
+
it. Block-level: the row grows to fit it rather than floating over
|
|
96
|
+
the row below, so it can never end up clipped by the scroll
|
|
97
|
+
container's overflow the way a position:fixed escape would need
|
|
98
|
+
guarding against. */
|
|
99
|
+
& .crisp-datatable-edit-error { display: block; padding: ${tokens_1.contract.space["0.5"]} 0 0; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.bgDanger}; }
|
|
100
|
+
/* editor: "select"/"switch" -- always live, no separate edit mode, so
|
|
101
|
+
the cell IS the picker: same chromeless-fill treatment as
|
|
102
|
+
RecordsTable's own --picker cells (measured there already; DataTable
|
|
103
|
+
shares RecordsTable's grid, Golden Rule 0). */
|
|
104
|
+
& .crisp-datatable-td--picker { padding: 0; }
|
|
105
|
+
& .crisp-datatable-edit-picker { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]}; }
|
|
106
|
+
& .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
|
|
107
|
+
& .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: ${tokens_1.contract.space["2"]}; }
|
|
108
|
+
/* reference picker: a leading 16px avatar per option, plus a trailing
|
|
109
|
+
"Create X" row when the consumer supplies one (#449) -- same measured
|
|
110
|
+
values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
111
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
112
|
+
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
113
|
+
& .crisp-datatable-refoption--create { color: ${tokens_1.contract.fgMuted}; }
|
|
80
114
|
& .crisp-datatable-empty { padding: ${tokens_1.contract.space["8"]} ${tokens_1.contract.space["3"]}; text-align: center; color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.lg}; }
|
|
81
115
|
/* fixed layout so column widths (and resize) are exact — like RecordsTable */
|
|
82
116
|
& table { table-layout: fixed; }
|
|
@@ -3,5 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.datatableRecipe = exports.DataTable = void 0;
|
|
4
4
|
var datatable_1 = require("./datatable");
|
|
5
5
|
Object.defineProperty(exports, "DataTable", { enumerable: true, get: function () { return datatable_1.DataTable; } });
|
|
6
|
+
// `EntityValue` (identical shape, `{ name: string; avatar?: string }`) is
|
|
7
|
+
// deliberately NOT re-exported here: `product/recordstable`'s own barrel
|
|
8
|
+
// already exports a type of that name at the package root, and re-exporting
|
|
9
|
+
// this module's copy too makes `EntityValue` an ambiguous export (TS2308) --
|
|
10
|
+
// verified by trying it. A DataTable consumer needing the type imports it
|
|
11
|
+
// from there; it's the same shape either table would want.
|
|
6
12
|
var datatable_recipe_1 = require("./datatable.recipe");
|
|
7
13
|
Object.defineProperty(exports, "datatableRecipe", { enumerable: true, get: function () { return datatable_recipe_1.datatableRecipe; } });
|
|
@@ -57,7 +57,7 @@ const fromDate = (d) => ({
|
|
|
57
57
|
* popover. This is what `DatePicker` used to be; `DatePicker` is now
|
|
58
58
|
* seed-design's bare calendar, and this composes it.
|
|
59
59
|
*/
|
|
60
|
-
function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder = "Set date…", variant = "default", format = defaultFormat, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
|
|
60
|
+
function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder = "Set date…", variant = "default", format = defaultFormat, clearable = true, clearLabel = "Clear date", presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
|
|
61
61
|
const [internal, setInternal] = React.useState(defaultValue ?? null);
|
|
62
62
|
const selected = value ?? internal;
|
|
63
63
|
const [open, setOpen] = React.useState(false);
|
|
@@ -69,19 +69,29 @@ function DatePickerField({ value, defaultValue, onValueChange, onChange, placeho
|
|
|
69
69
|
onChange?.(d);
|
|
70
70
|
setOpen(false);
|
|
71
71
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
selectionMode
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
const clear = () => {
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
setInternal(null);
|
|
75
|
+
onValueChange?.(null);
|
|
76
|
+
onChange?.(null);
|
|
77
|
+
};
|
|
78
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, datepickerfield_recipe_1.datepickerfieldRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-box", "data-ghost": variant === "ghost" ? "" : undefined, "data-placeholder": selected ? undefined : "", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { open: open, onOpenChange: setOpen, "aria-label": ariaLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datepickerfield-trigger", "aria-label": ariaLabel, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), selected ? format(selected) : placeholder] }), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-panel", children: [(0, jsx_runtime_1.jsx)(datepicker_1.DatePicker
|
|
79
|
+
// DatePickerProps is a union over selectionMode, and TypeScript
|
|
80
|
+
// cannot narrow it through a spread: it tries every branch and
|
|
81
|
+
// reports the range one as missing props. The field is always
|
|
82
|
+
// single-select, so the shape is pinned here rather than inferred.
|
|
83
|
+
, { ...{
|
|
84
|
+
...calendarProps,
|
|
85
|
+
selectionMode: "single",
|
|
86
|
+
value: selected ? fromDate(selected) : undefined,
|
|
87
|
+
onValueChange: (next) => {
|
|
88
|
+
if (next)
|
|
89
|
+
pick(toDate(next));
|
|
90
|
+
},
|
|
91
|
+
} }), presets && presets.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-datepickerfield-presets", children: presets.map((p, i) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datepickerfield-preset", onClick: () => pick(p.date()), children: p.label }, typeof p.label === "string" ? p.label : i))) }))] }) }), clearable && selected && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datepickerfield-clear", "aria-label": clearLabel, onClick: (e) => {
|
|
92
|
+
// not the trigger button's own click -- Popover would open
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
clear();
|
|
95
|
+
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 1.75, "aria-hidden": "true" }) }))] }) }));
|
|
86
96
|
}
|
|
87
97
|
DatePickerField.displayName = "DatePickerField";
|
|
@@ -12,18 +12,35 @@ const config = (0, core_1.defineRecipe)({
|
|
|
12
12
|
base: `
|
|
13
13
|
display: inline-flex; font-family: ${tokens_1.contract.font.sans};
|
|
14
14
|
|
|
15
|
-
& .crisp-datepickerfield-
|
|
16
|
-
display: inline-flex; align-items: center;
|
|
17
|
-
box-sizing: border-box; height: 32px;
|
|
18
|
-
border
|
|
15
|
+
& .crisp-datepickerfield-box {
|
|
16
|
+
display: inline-flex; align-items: center;
|
|
17
|
+
box-sizing: border-box; height: 32px; margin: 0;
|
|
18
|
+
border-radius: ${tokens_1.contract.radius.md}; background: transparent;
|
|
19
19
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent);
|
|
20
20
|
color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.lg};
|
|
21
|
-
white-space: nowrap; cursor: pointer;
|
|
22
21
|
}
|
|
23
|
-
& .crisp-datepickerfield-
|
|
24
|
-
& .crisp-datepickerfield-
|
|
25
|
-
& .crisp-datepickerfield-trigger
|
|
22
|
+
& .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
|
|
23
|
+
& .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); }
|
|
24
|
+
& .crisp-datepickerfield-trigger {
|
|
25
|
+
display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]};
|
|
26
|
+
height: 100%; padding: 0 ${tokens_1.contract.space["2.5"]}; margin: 0;
|
|
27
|
+
border: none; background: none; color: inherit; font-family: inherit;
|
|
28
|
+
font-size: inherit; white-space: nowrap; cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
& .crisp-datepickerfield-box[data-placeholder] .crisp-datepickerfield-trigger { color: ${tokens_1.contract.fgMuted}; }
|
|
26
31
|
& .crisp-datepickerfield-trigger svg { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
|
|
32
|
+
/* Reuses TableToolbar's measured filter-chip remove button
|
|
33
|
+
(tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same
|
|
34
|
+
small inline "clear this value" affordance, not a freshly-measured
|
|
35
|
+
one: no live Attio composition for a DatePickerField-equivalent
|
|
36
|
+
clear was reachable this round (#394). */
|
|
37
|
+
& .crisp-datepickerfield-clear {
|
|
38
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
39
|
+
width: 20px; height: 20px; margin-inline-end: ${tokens_1.contract.space["1"]}; padding: 0;
|
|
40
|
+
border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none;
|
|
41
|
+
color: ${tokens_1.contract.fgMuted}; font-family: inherit; cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
& .crisp-datepickerfield-clear:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 10%, transparent); color: ${tokens_1.contract.fgPrimary}; }
|
|
27
44
|
|
|
28
45
|
& .crisp-datepickerfield-panel { padding: ${tokens_1.contract.space["2"]}; width: 300px; }
|
|
29
46
|
& .crisp-datepickerfield-presets { display: flex; flex-wrap: wrap; gap: ${tokens_1.contract.space["1"]}; margin-top: ${tokens_1.contract.space["2"]}; }
|
|
@@ -8,6 +8,13 @@ const tokens_1 = require("../../tokens");
|
|
|
8
8
|
// per theme — light uses a blue-tinted 3-layer shadow, dark drops to a plain
|
|
9
9
|
// 2-layer black shadow) with a bordered header (title + close), a scrollable
|
|
10
10
|
// body, and a bordered footer for the primary action.
|
|
11
|
+
//
|
|
12
|
+
// #414: the footer's submit-key caps (⌘/Enter) used to be one span drawing a
|
|
13
|
+
// visible pill (18px, 22%-white background). Attio's own are two separate
|
|
14
|
+
// TRANSPARENT 16x16 glyphs, 4px apart. Their 3px/10px/4px measured values sit
|
|
15
|
+
// between crisp's token steps (space has no 3px, radius starts at 6px, text
|
|
16
|
+
// starts at 11px) — raw px per Golden Rule 3's "genuinely intrinsic size"
|
|
17
|
+
// exception, same as a chip's odd 7px corner.
|
|
11
18
|
const config = (0, core_1.defineRecipe)({
|
|
12
19
|
name: "dialog",
|
|
13
20
|
base: `display: contents; font-family: ${tokens_1.contract.font.sans};
|
|
@@ -26,7 +33,8 @@ const config = (0, core_1.defineRecipe)({
|
|
|
26
33
|
& .crisp-dialog-field-label { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent); }
|
|
27
34
|
& .crisp-dialog-more { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
|
|
28
35
|
& .crisp-dialog-submit { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: 28px; padding: 0 ${tokens_1.contract.space["1"]} 0 ${tokens_1.contract.space["2.5"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgBrandSolid}; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; }
|
|
29
|
-
& .crisp-dialog-submit-key { display: inline-flex; align-items: center;
|
|
36
|
+
& .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; }
|
|
37
|
+
& .crisp-dialog-submit-key kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-style: normal; font-size: 10px; }`,
|
|
30
38
|
variants: {},
|
|
31
39
|
defaultVariants: {},
|
|
32
40
|
compoundVariants: [],
|
|
@@ -52,7 +52,7 @@ const MenuContext = React.createContext(null);
|
|
|
52
52
|
* typeahead, Enter/Space select, Esc closes and returns focus to the trigger);
|
|
53
53
|
* closes on outside click. Flips up / right-aligns when space is tight.
|
|
54
54
|
*/
|
|
55
|
-
function Menu({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, className, "aria-label": ariaLabel, }) {
|
|
55
|
+
function Menu({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, className, contentClassName, "aria-label": ariaLabel, }) {
|
|
56
56
|
// The surface and the button that opens it were two unconnected nodes: the
|
|
57
57
|
// menu/dialog had no id, so nothing could point at it, and no accessible
|
|
58
58
|
// name, so a screen reader announced a bare "menu"/"dialog". Labelling the
|
|
@@ -163,7 +163,9 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
|
|
|
163
163
|
setOpen(!open);
|
|
164
164
|
},
|
|
165
165
|
});
|
|
166
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className:
|
|
166
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className: contentClassName
|
|
167
|
+
? `crisp-menu-content ${contentClassName}`
|
|
168
|
+
: "crisp-menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: (0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: children }) }) }))] }));
|
|
167
169
|
}
|
|
168
170
|
Menu.displayName = "Menu";
|
|
169
171
|
function MenuItem({ children, icon, shortcut, danger, disabled, onSelect, closeOnSelect = true, }) {
|
|
@@ -22,6 +22,12 @@ const tokens_1 = require("../../tokens");
|
|
|
22
22
|
// intact WITHIN the portaled subtree, only the link back to the
|
|
23
23
|
// trigger-holding root broke). A flat recordstable-tag selector also works
|
|
24
24
|
// unchanged wherever it's rendered, portaled or not, so it needs no split.
|
|
25
|
+
// This is a third, hand-pasted copy of `recordTagCss`
|
|
26
|
+
// (product/recordstable/recordstable.tag.ts) rather than a splice of it:
|
|
27
|
+
// that helper is written `& .crisp-recordstable-tag { ... }` for nesting
|
|
28
|
+
// under a recipe's `base`, which doesn't compile to anything inside a flat
|
|
29
|
+
// `globalCss` block. Keep the two in sync by hand (#380 added the measured
|
|
30
|
+
// `box-shadow: inset 0 0 0 1px <tone ring colour>` here to match).
|
|
25
31
|
const config = (0, core_1.defineRecipe)({
|
|
26
32
|
name: "multiselect",
|
|
27
33
|
base: `
|
|
@@ -77,14 +83,14 @@ const config = (0, core_1.defineRecipe)({
|
|
|
77
83
|
.crisp-multiselect-option[aria-selected="true"] .crisp-multiselect-check { background: ${tokens_1.contract.bgBrandSolid}; box-shadow: none; color: #fff; }
|
|
78
84
|
.crisp-multiselect-empty { padding: ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["2"]}; text-align: center; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
|
|
79
85
|
|
|
80
|
-
.crisp-recordstable-tag { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.chip}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 82%, transparent); }
|
|
81
|
-
.crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 14%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 82%, ${tokens_1.contract.fgPrimary}); }
|
|
82
|
-
.crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, ${tokens_1.contract.bgSuccess} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgSuccess} 78%, ${tokens_1.contract.fgPrimary}); }
|
|
83
|
-
.crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, ${tokens_1.contract.bgWarning} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgWarning} 82%, ${tokens_1.contract.fgPrimary}); }
|
|
84
|
-
.crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, ${tokens_1.contract.bgDanger} 15%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgDanger} 80%, ${tokens_1.contract.fgPrimary}); }
|
|
85
|
-
.crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, ${tokens_1.contract.bgPurple} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgPurple} 80%, ${tokens_1.contract.fgPrimary}); }
|
|
86
|
-
.crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, ${tokens_1.contract.bgCyan} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgCyan} 82%, ${tokens_1.contract.fgPrimary}); }
|
|
87
|
-
.crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); }
|
|
86
|
+
.crisp-recordstable-tag { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.chip}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 82%, transparent); box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}; }
|
|
87
|
+
.crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 14%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 30%, transparent); }
|
|
88
|
+
.crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, ${tokens_1.contract.bgSuccess} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgSuccess} 78%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgSuccess} 30%, transparent); }
|
|
89
|
+
.crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, ${tokens_1.contract.bgWarning} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgWarning} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgWarning} 30%, transparent); }
|
|
90
|
+
.crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, ${tokens_1.contract.bgDanger} 15%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgDanger} 80%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgDanger} 30%, transparent); }
|
|
91
|
+
.crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, ${tokens_1.contract.bgPurple} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgPurple} 80%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgPurple} 30%, transparent); }
|
|
92
|
+
.crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, ${tokens_1.contract.bgCyan} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgCyan} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgCyan} 30%, transparent); }
|
|
93
|
+
.crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgOrange} 30%, transparent); }
|
|
88
94
|
`,
|
|
89
95
|
variants: {
|
|
90
96
|
// Same ring Select's `invalid` draws — see that recipe's comment.
|
|
@@ -52,7 +52,7 @@ function optionText(o) {
|
|
|
52
52
|
* "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
|
|
53
53
|
* Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
|
|
54
54
|
*/
|
|
55
|
-
function Select({ options, value, defaultValue, onValueChange, name, required, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", emptyLabel = "No results", disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
55
|
+
function Select({ options, value, defaultValue, onValueChange, name, required, placeholder = "Select…", variant = "default", width = "full", searchable, searchPlaceholder = "Search…", onSearchChange, emptyLabel = "No results", disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
56
56
|
// Virtual focus: DOM focus stays on the trigger (or the search field), and the
|
|
57
57
|
// active option is named by `aria-activedescendant`. That is the only thing a
|
|
58
58
|
// screen reader has to go on -- `data-highlighted` is invisible to it -- so the
|
|
@@ -201,6 +201,7 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
|
|
|
201
201
|
onChange: () => { } })), (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, "aria-invalid": invalid === true || undefined, disabled: disabled, "data-open": open ? "" : undefined, className: "crisp-select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-value", children: selectedOption ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: selectedOption.color } })), selectedOption.label] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: listboxRef, id: listboxId, className: "crisp-select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-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) => {
|
|
202
202
|
setQuery(e.target.value);
|
|
203
203
|
setHighlight(0);
|
|
204
|
+
onSearchChange?.(e.target.value);
|
|
204
205
|
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
205
206
|
const isSel = opt.value === selected;
|
|
206
207
|
// #343: a header renders once per run of same-`group`
|
|
@@ -14,11 +14,11 @@ const config = (0, core_1.defineRecipe)({
|
|
|
14
14
|
& .crisp-table-scroll { overflow-x: auto; }
|
|
15
15
|
& table { width: 100%; border-collapse: separate; border-spacing: 0; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; }
|
|
16
16
|
& th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
17
|
-
& th { height: ${tokens_1.contract.size.tableHeader}; background: ${tokens_1.contract.bgSurface}; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; letter-spacing: -0.14px; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); user-select: none; }
|
|
17
|
+
& th { height: ${tokens_1.contract.size.tableHeader}; background: ${tokens_1.contract.bgSurface}; border-top: 1px solid ${tokens_1.contract.borderSubtle}; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; letter-spacing: -0.14px; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); user-select: none; }
|
|
18
18
|
& td { color: ${tokens_1.contract.fgPrimary}; background: ${tokens_1.contract.bgSurface}; }
|
|
19
19
|
& th:last-child, & td:last-child { border-inline-end: none; }
|
|
20
20
|
& tbody tr:last-child td { border-bottom: none; }
|
|
21
|
-
& tfoot td { border-top: 1px solid ${tokens_1.contract.borderSubtle}; border-bottom:
|
|
21
|
+
& tfoot td { border-top: 1px solid ${tokens_1.contract.borderSubtle}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; }
|
|
22
22
|
& caption { padding: ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["2"]}; text-align: start; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; caption-side: bottom; }
|
|
23
23
|
& td[data-align="right"], & th[data-align="right"] { text-align: end; }
|
|
24
24
|
& td[data-align="center"], & th[data-align="center"] { text-align: center; }`,
|
|
@@ -31,8 +31,17 @@ const config = (0, core_1.defineRecipe)({
|
|
|
31
31
|
// card (default): the bordered rounded surface, unchanged for existing
|
|
32
32
|
// consumers. bleed: drops the outer border/radius/inset so the grid runs
|
|
33
33
|
// edge-to-edge, like RecordsTable's real full-page records grid.
|
|
34
|
+
//
|
|
35
|
+
// #439: th/tfoot td now carry their own top/bottom border (below) so a
|
|
36
|
+
// bleed table's header and calc footer close the same way Attio's own
|
|
37
|
+
// do. card's own 4-side border already draws that top/bottom edge, so
|
|
38
|
+
// strip the cells' copies there -- same reasoning as `:last-child`
|
|
39
|
+
// dropping the inline-end border and `tbody tr:last-child` dropping the
|
|
40
|
+
// bottom one: the container's edge wins, cells never double it.
|
|
34
41
|
surface: {
|
|
35
|
-
card: `border: 1px solid ${tokens_1.contract.borderSubtle}; border-radius: ${tokens_1.contract.radius.xl}; overflow: hidden
|
|
42
|
+
card: `border: 1px solid ${tokens_1.contract.borderSubtle}; border-radius: ${tokens_1.contract.radius.xl}; overflow: hidden;
|
|
43
|
+
& th { border-top: none; }
|
|
44
|
+
& tfoot td { border-bottom: none; }`,
|
|
36
45
|
bleed: `border: none; border-radius: 0;`,
|
|
37
46
|
},
|
|
38
47
|
},
|
|
@@ -23,7 +23,7 @@ const toolbarChromeHead = (p) => ` & .${namespace_1.CLASS_PREFIX}${p}-toolbar
|
|
|
23
23
|
& .${namespace_1.CLASS_PREFIX}${p}-search svg { flex-shrink: 0; }
|
|
24
24
|
& .${namespace_1.CLASS_PREFIX}${p}-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgPrimary}; width: 180px; }
|
|
25
25
|
& .${namespace_1.CLASS_PREFIX}${p}-search input::placeholder { color: ${tokens_1.contract.fgMuted}; }
|
|
26
|
-
& .${namespace_1.CLASS_PREFIX}${p}-tool { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.row}; margin: 0; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault}; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; transition: background-color .12s; }
|
|
26
|
+
& .${namespace_1.CLASS_PREFIX}${p}-tool { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.row}; margin: 0; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault}; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
27
27
|
& .${namespace_1.CLASS_PREFIX}${p}-tool:hover { background: ${tokens_1.contract.bgRaised}; }
|
|
28
28
|
& .${namespace_1.CLASS_PREFIX}${p}-tool svg { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
|
|
29
29
|
& .${namespace_1.CLASS_PREFIX}${p}-tool--active { background: ${tokens_1.contract.bgRaised}; }
|
|
@@ -149,8 +149,19 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
149
149
|
* `RecordsTable` renders this same measured chrome internally; this is the
|
|
150
150
|
* composable version, to put above `DataTable` or any table of your own. Pair
|
|
151
151
|
* it with `applyFilters` / `applySorts` to turn its state into rows.
|
|
152
|
+
*
|
|
153
|
+
* Sort and Filter read the same `columns` list by default, but can be given
|
|
154
|
+
* their own (`sortColumns`/`filterColumns`) when a consumer's two menus
|
|
155
|
+
* genuinely offer different fields — Attio's own Tasks page does exactly
|
|
156
|
+
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
157
|
+
* Created by), which one shared list can't reproduce.
|
|
152
158
|
*/
|
|
153
|
-
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, inline, ...rest }) {
|
|
159
|
+
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, 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, inline, ...rest }) {
|
|
160
|
+
// Each side falls back to the shared `columns` independently — a caller
|
|
161
|
+
// that only overrides one (Filter's own Record/Created by, say) still
|
|
162
|
+
// gets `columns` on the other, unchanged from before either prop existed.
|
|
163
|
+
const sortColumns = sortColumnsProp ?? columns ?? [];
|
|
164
|
+
const filterColumns = filterColumnsProp ?? columns ?? [];
|
|
154
165
|
const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
|
|
155
166
|
const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
|
|
156
167
|
const sorts = sortsProp ?? sortsState;
|
|
@@ -197,7 +208,7 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
197
208
|
}, [openMenu]);
|
|
198
209
|
const addSort = () => {
|
|
199
210
|
const used = new Set(sorts.map((s) => s.key));
|
|
200
|
-
const col =
|
|
211
|
+
const col = sortColumns.find((c) => !used.has(c.key));
|
|
201
212
|
if (col)
|
|
202
213
|
putSorts([...sorts, { key: col.key, dir: "asc" }]);
|
|
203
214
|
};
|
|
@@ -236,9 +247,9 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
236
247
|
// The default phrase only works in English word order, so a caller can
|
|
237
248
|
// replace the whole thing rather than fill a template.
|
|
238
249
|
const sortedLabel = sortTriggerLabel
|
|
239
|
-
? sortTriggerLabel(sorts,
|
|
250
|
+
? sortTriggerLabel(sorts, sortColumns)
|
|
240
251
|
: sorts.length
|
|
241
|
-
? `Sorted by ${
|
|
252
|
+
? `Sorted by ${sortColumns.find((c) => c.key === sorts[0].key)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
242
253
|
: "Sort";
|
|
243
254
|
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool${sorts.length ? " crisp-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", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
244
255
|
if (dragSort !== null)
|
|
@@ -247,7 +258,7 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
247
258
|
if (dragSort !== null)
|
|
248
259
|
moveSort(dragSort, i);
|
|
249
260
|
setDragSort(null);
|
|
250
|
-
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)(select_1.Select, { className: "crisp-tabletoolbar-sort-field", searchable: true, value: s.key, onValueChange: (k) => setSortField(i, k), options:
|
|
261
|
+
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)(select_1.Select, { className: "crisp-tabletoolbar-sort-field", searchable: true, value: s.key, onValueChange: (k) => setSortField(i, k), options: sortColumns.map((c) => ({
|
|
251
262
|
value: c.key,
|
|
252
263
|
label: c.icon ? ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(c.icon, { size: 15, strokeWidth: 1.75 }), c.label] })) : (c.label),
|
|
253
264
|
keywords: c.label,
|
|
@@ -268,12 +279,12 @@ function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange,
|
|
|
268
279
|
? `Filter · ${activeFilters}`
|
|
269
280
|
: "Filter"] }), openMenu === "filter" && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterFieldPlaceholder, "aria-label": filterFieldLabel,
|
|
270
281
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
271
|
-
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) }),
|
|
282
|
+
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) }), filterColumns
|
|
272
283
|
.filter((col) => col.label
|
|
273
284
|
.toLowerCase()
|
|
274
285
|
.includes(filterQuery.trim().toLowerCase()))
|
|
275
286
|
.map((col) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => addFilter(col.key), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label] }, col.key)))] }))] }), filters.map((cond) => {
|
|
276
|
-
const col =
|
|
287
|
+
const col = filterColumns.find((c) => c.key === cond.key);
|
|
277
288
|
const open = openMenu === `fcond:${cond.id}`;
|
|
278
289
|
const hasValue = (!opNeedsValue(cond.op) || cond.value.trim()) &&
|
|
279
290
|
(!opNeedsValues(cond.op) || (cond.values?.length ?? 0) > 0);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.parseCellDate = parseCellDate;
|
|
18
18
|
exports.formatCellDate = formatCellDate;
|
|
19
|
+
exports.cellDatePrecision = cellDatePrecision;
|
|
19
20
|
/** `YYYY`, `YYYY-MM`, `YYYY-MM-DD` — the forms ECMA-262 reads as UTC. */
|
|
20
21
|
const DATE_ONLY = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
|
|
21
22
|
function parseCellDate(raw) {
|
|
@@ -35,3 +36,13 @@ function formatCellDate(d) {
|
|
|
35
36
|
const pad = (n) => String(n).padStart(2, "0");
|
|
36
37
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
37
38
|
}
|
|
39
|
+
function cellDatePrecision(raw) {
|
|
40
|
+
const m = DATE_ONLY.exec(raw);
|
|
41
|
+
if (!m)
|
|
42
|
+
return "day";
|
|
43
|
+
if (!m[2])
|
|
44
|
+
return "year";
|
|
45
|
+
if (!m[3])
|
|
46
|
+
return "month";
|
|
47
|
+
return "day";
|
|
48
|
+
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,8 +14,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = void 0;
|
|
18
18
|
var cell_date_1 = require("./cell-date");
|
|
19
|
+
Object.defineProperty(exports, "cellDatePrecision", { enumerable: true, get: function () { return cell_date_1.cellDatePrecision; } });
|
|
19
20
|
Object.defineProperty(exports, "formatCellDate", { enumerable: true, get: function () { return cell_date_1.formatCellDate; } });
|
|
20
21
|
Object.defineProperty(exports, "parseCellDate", { enumerable: true, get: function () { return cell_date_1.parseCellDate; } });
|
|
21
22
|
var cx_1 = require("./cx");
|
package/dist/cjs/index.js
CHANGED
|
@@ -31,6 +31,7 @@ __exportStar(require("./components/card"), exports);
|
|
|
31
31
|
__exportStar(require("./components/checkbox"), exports);
|
|
32
32
|
__exportStar(require("./components/chip"), exports);
|
|
33
33
|
__exportStar(require("./components/columncalc"), exports);
|
|
34
|
+
__exportStar(require("./components/composewindow"), exports);
|
|
34
35
|
__exportStar(require("./components/contextmenu"), exports);
|
|
35
36
|
__exportStar(require("./components/datatable"), exports);
|
|
36
37
|
__exportStar(require("./components/datepicker"), exports);
|