@crisp-ui-kit/crisp 0.54.1 → 0.54.2
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/datatable/datatable.js +2 -1
- package/dist/cjs/components/datatable/datatable.recipe.js +4 -0
- package/dist/cjs/components/mentionchip/index.js +7 -0
- package/dist/cjs/components/mentionchip/mentionchip.js +57 -0
- package/dist/cjs/components/mentionchip/mentionchip.recipe.js +38 -0
- package/dist/cjs/components/table/table.recipe.js +15 -3
- package/dist/cjs/core/floating-stack.js +18 -0
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/product/formdialog/formdialog.js +171 -0
- package/dist/cjs/product/formdialog/formdialog.recipe.js +26 -0
- package/dist/cjs/product/formdialog/index.js +8 -0
- package/dist/cjs/product/importdialog/importdialog.js +134 -0
- package/dist/cjs/product/importdialog/importdialog.recipe.js +92 -0
- package/dist/cjs/product/importdialog/index.js +18 -0
- package/dist/cjs/product/savedviews/index.js +7 -0
- package/dist/cjs/product/savedviews/savedviews.js +207 -0
- package/dist/cjs/product/savedviews/savedviews.recipe.js +103 -0
- package/dist/cjs/product/taskstable/index.js +7 -0
- package/dist/cjs/product/taskstable/taskstable.js +27 -0
- package/dist/cjs/product/taskstable/taskstable.recipe.js +47 -0
- package/dist/cjs/product/themepicker/index.js +17 -0
- package/dist/cjs/product/themepicker/themepicker.js +32 -0
- package/dist/cjs/product/themepicker/themepicker.recipe.js +98 -0
- package/dist/cjs/tokens/palette.js +30 -13
- package/dist/cjs/tokens/semantic.js +6 -0
- package/dist/datatable.css +20 -21
- package/dist/datatable.layered.css +20 -21
- package/dist/esm/components/datatable/datatable.js +2 -1
- package/dist/esm/components/datatable/datatable.recipe.js +4 -0
- package/dist/esm/components/mentionchip/index.js +2 -0
- package/dist/esm/components/mentionchip/mentionchip.js +21 -0
- package/dist/esm/components/mentionchip/mentionchip.recipe.js +35 -0
- package/dist/esm/components/table/table.recipe.js +15 -3
- package/dist/esm/core/floating-stack.js +17 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/product/formdialog/formdialog.js +134 -0
- package/dist/esm/product/formdialog/formdialog.recipe.js +23 -0
- package/dist/esm/product/formdialog/index.js +2 -0
- package/dist/esm/product/importdialog/importdialog.js +97 -0
- package/dist/esm/product/importdialog/importdialog.recipe.js +89 -0
- package/dist/esm/product/importdialog/index.js +2 -0
- package/dist/esm/product/savedviews/index.js +2 -0
- package/dist/esm/product/savedviews/savedviews.js +171 -0
- package/dist/esm/product/savedviews/savedviews.recipe.js +100 -0
- package/dist/esm/product/taskstable/index.js +2 -0
- package/dist/esm/product/taskstable/taskstable.js +24 -0
- package/dist/esm/product/taskstable/taskstable.recipe.js +44 -0
- package/dist/esm/product/themepicker/index.js +1 -0
- package/dist/esm/product/themepicker/themepicker.js +29 -0
- package/dist/esm/product/themepicker/themepicker.recipe.js +95 -0
- package/dist/esm/tokens/palette.js +30 -13
- package/dist/esm/tokens/semantic.js +6 -0
- package/dist/formdialog.css +6 -0
- package/dist/formdialog.d.ts +7 -0
- package/dist/formdialog.layered.css +8 -0
- package/dist/importdialog.css +38 -0
- package/dist/importdialog.d.ts +7 -0
- package/dist/importdialog.layered.css +40 -0
- package/dist/mentionchip.css +3 -0
- package/dist/mentionchip.d.ts +7 -0
- package/dist/mentionchip.layered.css +5 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +14 -6
- package/dist/savedviews.css +78 -0
- package/dist/savedviews.d.ts +7 -0
- package/dist/savedviews.layered.css +80 -0
- package/dist/styles.css +223 -30
- package/dist/styles.layered.css +223 -30
- package/dist/table.css +15 -6
- package/dist/table.layered.css +15 -6
- package/dist/taskstable.css +23 -0
- package/dist/taskstable.d.ts +7 -0
- package/dist/taskstable.layered.css +25 -0
- package/dist/themepicker.css +27 -0
- package/dist/themepicker.d.ts +7 -0
- package/dist/themepicker.layered.css +29 -0
- package/dist/tokens.css +7 -3
- package/dist/tokens.json +14 -6
- package/dist/types/components/datatable/datatable.d.ts +12 -1
- package/dist/types/components/mentionchip/index.d.ts +2 -0
- package/dist/types/components/mentionchip/mentionchip.d.ts +20 -0
- package/dist/types/components/mentionchip/mentionchip.recipe.d.ts +18 -0
- package/dist/types/core/floating-stack.d.ts +14 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/product/formdialog/formdialog.d.ts +57 -0
- package/dist/types/product/formdialog/formdialog.recipe.d.ts +3 -0
- package/dist/types/product/formdialog/index.d.ts +2 -0
- package/dist/types/product/importdialog/importdialog.d.ts +86 -0
- package/dist/types/product/importdialog/importdialog.recipe.d.ts +3 -0
- package/dist/types/product/importdialog/index.d.ts +2 -0
- package/dist/types/product/savedviews/index.d.ts +2 -0
- package/dist/types/product/savedviews/savedviews.d.ts +55 -0
- package/dist/types/product/savedviews/savedviews.recipe.d.ts +3 -0
- package/dist/types/product/taskstable/index.d.ts +2 -0
- package/dist/types/product/taskstable/taskstable.d.ts +54 -0
- package/dist/types/product/taskstable/taskstable.recipe.d.ts +3 -0
- package/dist/types/product/themepicker/index.d.ts +1 -0
- package/dist/types/product/themepicker/themepicker.d.ts +21 -0
- package/dist/types/product/themepicker/themepicker.recipe.d.ts +3 -0
- package/dist/types/tokens/contract.d.ts +2 -0
- package/dist/types/tokens/palette.d.ts +11 -3
- package/dist/types/tokens/semantic.d.ts +6 -0
- package/package.json +31 -1
|
@@ -337,7 +337,7 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
337
337
|
onCommit(v);
|
|
338
338
|
} }) }));
|
|
339
339
|
}
|
|
340
|
-
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
340
|
+
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, scrollProps, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
341
341
|
// crisp/#471: same resolution order as TableToolbar's own — an explicit
|
|
342
342
|
// prop wins, then the provider's translation of the built-in English
|
|
343
343
|
// default, then that default itself with no provider in the tree.
|
|
@@ -818,6 +818,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
818
818
|
return ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
819
819
|
}
|
|
820
820
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), viewSettingsLabel] }), children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-group-label", children: columnsGroupLabel }), allColumns.map((c) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-count", children: resolvedCountLabel(rows.length, data.length) })), toolbar] })), (0, jsx_runtime_1.jsxs)(table_1.Table, { interactive: interactive, surface: surface, style: { minWidth }, scrollProps: {
|
|
821
|
+
...scrollProps,
|
|
821
822
|
ref: scrollRef,
|
|
822
823
|
onScroll: (e) => {
|
|
823
824
|
const el = e.currentTarget;
|
|
@@ -69,6 +69,10 @@ const config = (0, core_1.defineRecipe)({
|
|
|
69
69
|
/* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
|
|
70
70
|
& th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: ${tokens_1.contract.bgSurface}; }
|
|
71
71
|
& th[data-sticky] { z-index: 3; }
|
|
72
|
+
/* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
|
|
73
|
+
\`tfoot td { z-index: 2 }\`), which \`td[data-sticky]\`'s own higher-specificity
|
|
74
|
+
\`z-index: 1\` would otherwise undercut -- keep it level with the row it's in. */
|
|
75
|
+
& tfoot td[data-sticky] { z-index: 2; }
|
|
72
76
|
& tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 10%, ${tokens_1.contract.bgSurface}); }
|
|
73
77
|
& tbody tr:hover td[data-sticky] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, ${tokens_1.contract.bgSurface}); }
|
|
74
78
|
& .crisp-table-scroll[data-scrolled-x] [data-sticky-edge] { clip-path: inset(0 -38px 0 0); box-shadow: 6px 0 16px 4px rgba(0,0,0,0.12); }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mentionChipRecipe = exports.MentionChip = void 0;
|
|
4
|
+
var mentionchip_1 = require("./mentionchip");
|
|
5
|
+
Object.defineProperty(exports, "MentionChip", { enumerable: true, get: function () { return mentionchip_1.MentionChip; } });
|
|
6
|
+
var mentionchip_recipe_1 = require("./mentionchip.recipe");
|
|
7
|
+
Object.defineProperty(exports, "mentionChipRecipe", { enumerable: true, get: function () { return mentionchip_recipe_1.mentionChipRecipe; } });
|
|
@@ -0,0 +1,57 @@
|
|
|
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.MentionChip = void 0;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const React = __importStar(require("react"));
|
|
39
|
+
const core_1 = require("../../core");
|
|
40
|
+
const mentionchip_recipe_1 = require("./mentionchip.recipe");
|
|
41
|
+
/**
|
|
42
|
+
* A standalone "@mention" chip — the same look `RichTextEditor` draws
|
|
43
|
+
* inline while an editor is live (`.crisp-rte-mention`), exported on its
|
|
44
|
+
* own for a consumer rendering a mention OUTSIDE a mounted editor: a
|
|
45
|
+
* comment thread, a note's collapsed preview, anywhere a stored
|
|
46
|
+
* `@[Name](actor:id)`-shaped token needs to read as a person, not literal
|
|
47
|
+
* markup. Renders whatever `children` is given (the trigger character
|
|
48
|
+
* included, if the caller wants one, matching how `RichTextEditor`'s own
|
|
49
|
+
* chip includes it) rather than assuming an `@` prefix itself — plain text,
|
|
50
|
+
* no built-in click/remove behaviour (none was asked for; add one once a
|
|
51
|
+
* real interaction is measured, the same reasoning `Chip`'s own doc gives
|
|
52
|
+
* for not guessing a remove affordance).
|
|
53
|
+
*/
|
|
54
|
+
exports.MentionChip = React.forwardRef(({ className, asChild, ...props }, ref) => {
|
|
55
|
+
return ((0, jsx_runtime_1.jsx)(core_1.Primitive.span, { ref: ref, asChild: asChild, className: (0, mentionchip_recipe_1.mentionChipRecipe)({ className }), ...props }));
|
|
56
|
+
});
|
|
57
|
+
exports.MentionChip.displayName = "MentionChip";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spec = exports.mentionChipRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// The exact same values `product/richtext`'s own inline "@mention" span
|
|
7
|
+
// uses (`.crisp-rte-mention`, richtext.recipe.ts) — not re-measured against
|
|
8
|
+
// Attio independently (#428's own doc: no live @mention was reachable to
|
|
9
|
+
// read computed styles from, and typing one to create one is off-limits),
|
|
10
|
+
// reused verbatim from the one measured "brand" tag/chip this repo already
|
|
11
|
+
// has (Chip's own `tone="brand"`). Pulled out to its own top-level recipe,
|
|
12
|
+
// rather than importing richtext's, because `.crisp-rte-mention`'s CSS is
|
|
13
|
+
// nested under richtext's own root selector (`&`, i.e. native CSS
|
|
14
|
+
// nesting) — it only resolves inside a mounted `RichTextEditor`. A
|
|
15
|
+
// standalone consumer rendering a mention OUTSIDE an editor (a comment
|
|
16
|
+
// thread, a note preview list) needs the same look with no such ancestor,
|
|
17
|
+
// which is what this recipe is for. Kept deliberately un-pill-shaped (no
|
|
18
|
+
// fixed height, no ring) unlike Chip's own `brand` tone: this reads as
|
|
19
|
+
// tinted text flowing inside a sentence, the same as a live mention does
|
|
20
|
+
// mid-edit, not a standalone tag sitting in its own table cell.
|
|
21
|
+
const spec = (0, core_1.defineSpec)({
|
|
22
|
+
name: "mention-chip",
|
|
23
|
+
base: {
|
|
24
|
+
display: "inline",
|
|
25
|
+
borderRadius: tokens_1.contract.radius.chip,
|
|
26
|
+
paddingInline: tokens_1.contract.space["1"],
|
|
27
|
+
background: `color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 14%, transparent)`,
|
|
28
|
+
color: `color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 82%, ${tokens_1.contract.fgPrimary})`,
|
|
29
|
+
fontWeight: tokens_1.contract.weight.medium,
|
|
30
|
+
cursor: "default",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
exports.spec = spec;
|
|
34
|
+
const config = (0, core_1.specToRecipe)(spec);
|
|
35
|
+
exports.mentionChipRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
36
|
+
config,
|
|
37
|
+
});
|
|
38
|
+
exports.default = config;
|
|
@@ -11,18 +11,30 @@ const tokens_1 = require("../../tokens");
|
|
|
11
11
|
const config = (0, core_1.defineRecipe)({
|
|
12
12
|
name: "table",
|
|
13
13
|
base: `display: block; box-sizing: border-box; background: ${tokens_1.contract.bgSurface};
|
|
14
|
-
|
|
14
|
+
/* #554: both axes -- a consumer bounds the height via \`scrollProps\`
|
|
15
|
+
(style/className on this element), and the header/footer below pin
|
|
16
|
+
inside it exactly the way Attio's own grid does ("Both the header and
|
|
17
|
+
the summary row are sticky, on the scroll container rather than the
|
|
18
|
+
cells" -- docs/study/attio-table.md). Unbounded, \`overflow: auto\`
|
|
19
|
+
shows no vertical scrollbar and nothing sticks, so this is a no-op
|
|
20
|
+
for every existing consumer that never set a height. */
|
|
21
|
+
& .crisp-table-scroll { overflow: auto; }
|
|
15
22
|
& 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
23
|
/* #465: 12px, not crisp's old 8px -- measured on a live header cell
|
|
17
24
|
(padding: 0 12px) and independently on a body cell's text inset
|
|
18
25
|
(getBoundingClientRect delta from the cell edge). Matches
|
|
19
26
|
ColumnCalc's own already-correct 12px trigger inset. */
|
|
20
27
|
& th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["3"]}; 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; }
|
|
21
|
-
|
|
28
|
+
/* #554: sticky on the vertical axis, mirroring RecordsTable's own
|
|
29
|
+
\`.crisp-recordstable-th\`. z-index 2 sits above a plain body cell (auto)
|
|
30
|
+
and below a sticky-column corner cell (DataTable's \`th[data-sticky]\`,
|
|
31
|
+
z-index 3), so the corner always wins. */
|
|
32
|
+
& th { position: sticky; top: 0; z-index: 2; 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; }
|
|
22
33
|
& td { color: ${tokens_1.contract.fgPrimary}; background: ${tokens_1.contract.bgSurface}; }
|
|
23
34
|
& th:last-child, & td:last-child { border-inline-end: none; }
|
|
24
35
|
& tbody tr:last-child td { border-bottom: none; }
|
|
25
|
-
|
|
36
|
+
/* #554: sticky to the bottom, mirroring \`.crisp-recordstable-sum\`. */
|
|
37
|
+
& tfoot td { position: sticky; bottom: 0; z-index: 2; 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}; }
|
|
26
38
|
& 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; }
|
|
27
39
|
& td[data-align="right"], & th[data-align="right"] { text-align: end; }
|
|
28
40
|
& td[data-align="center"], & th[data-align="center"] { text-align: center; }`,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFloatingLayer = registerFloatingLayer;
|
|
4
4
|
exports.isInsideLaterFloatingLayer = isInsideLaterFloatingLayer;
|
|
5
|
+
exports.hasNestedFloatingLayer = hasNestedFloatingLayer;
|
|
5
6
|
exports.hasOpenFloatingLayer = hasOpenFloatingLayer;
|
|
6
7
|
/**
|
|
7
8
|
* Tracks every currently-open `FloatingPortal` layer, in open order, so a
|
|
@@ -53,6 +54,23 @@ function isInsideLaterFloatingLayer(target, self) {
|
|
|
53
54
|
}
|
|
54
55
|
return false;
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* True when a layer is registered strictly after `self` — i.e. `self` has
|
|
59
|
+
* spawned a still-open nested layer of its own (a row's kebab `Menu` inside
|
|
60
|
+
* `SavedViews`' own panel, e.g.).
|
|
61
|
+
*
|
|
62
|
+
* This is `hasOpenFloatingLayer`'s sibling for a caller that DOES register
|
|
63
|
+
* itself on this stack: `hasOpenFloatingLayer` reads "is the stack
|
|
64
|
+
* non-empty", which is always true for such a caller once its own layer is
|
|
65
|
+
* open — checking that before closing on Escape would defer to itself
|
|
66
|
+
* forever and Escape would never close it. `self`'s own position on the
|
|
67
|
+
* stack has to be excluded from the count, which `hasOpenFloatingLayer`
|
|
68
|
+
* cannot do since it takes no argument.
|
|
69
|
+
*/
|
|
70
|
+
function hasNestedFloatingLayer(self) {
|
|
71
|
+
const selfIndex = self ? stack.indexOf(self) : -1;
|
|
72
|
+
return selfIndex + 1 < stack.length;
|
|
73
|
+
}
|
|
56
74
|
/**
|
|
57
75
|
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
58
76
|
* `ContextMenu` is currently open, anywhere (#361).
|
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.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.matchMentionTrigger = exports.useCrispMessages = exports.useCrispMessage = exports.useCrispComponentDefaults = exports.CrispMessagesProvider = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.matchMentionTrigger = exports.useCrispMessages = exports.useCrispMessage = exports.useCrispComponentDefaults = exports.CrispMessagesProvider = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.hasNestedFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
|
|
18
18
|
var caret_rect_1 = require("./caret-rect");
|
|
19
19
|
Object.defineProperty(exports, "caretRectInField", { enumerable: true, get: function () { return caret_rect_1.caretRectInField; } });
|
|
20
20
|
var cell_date_1 = require("./cell-date");
|
|
@@ -32,6 +32,7 @@ Object.defineProperty(exports, "isRtlElement", { enumerable: true, get: function
|
|
|
32
32
|
var floating_portal_1 = require("./floating-portal");
|
|
33
33
|
Object.defineProperty(exports, "FloatingPortal", { enumerable: true, get: function () { return floating_portal_1.FloatingPortal; } });
|
|
34
34
|
var floating_stack_1 = require("./floating-stack");
|
|
35
|
+
Object.defineProperty(exports, "hasNestedFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.hasNestedFloatingLayer; } });
|
|
35
36
|
Object.defineProperty(exports, "hasOpenFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.hasOpenFloatingLayer; } });
|
|
36
37
|
Object.defineProperty(exports, "isInsideLaterFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.isInsideLaterFloatingLayer; } });
|
|
37
38
|
Object.defineProperty(exports, "registerFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.registerFloatingLayer; } });
|
package/dist/cjs/index.js
CHANGED
|
@@ -49,6 +49,7 @@ __exportStar(require("./components/heatmapchart"), exports);
|
|
|
49
49
|
__exportStar(require("./components/input"), exports);
|
|
50
50
|
__exportStar(require("./components/kbd"), exports);
|
|
51
51
|
__exportStar(require("./components/linechart"), exports);
|
|
52
|
+
__exportStar(require("./components/mentionchip"), exports);
|
|
52
53
|
__exportStar(require("./components/menu"), exports);
|
|
53
54
|
__exportStar(require("./components/middletruncate"), exports);
|
|
54
55
|
__exportStar(require("./components/multiselect"), exports);
|
|
@@ -88,7 +89,9 @@ __exportStar(require("./product/board"), exports);
|
|
|
88
89
|
__exportStar(require("./product/command"), exports);
|
|
89
90
|
__exportStar(require("./product/entitycard"), exports);
|
|
90
91
|
__exportStar(require("./product/flowcanvas"), exports);
|
|
92
|
+
__exportStar(require("./product/formdialog"), exports);
|
|
91
93
|
__exportStar(require("./product/headerbar"), exports);
|
|
94
|
+
__exportStar(require("./product/importdialog"), exports);
|
|
92
95
|
__exportStar(require("./product/inbox"), exports);
|
|
93
96
|
__exportStar(require("./product/listpage"), exports);
|
|
94
97
|
__exportStar(require("./product/navitem"), exports);
|
|
@@ -97,11 +100,14 @@ __exportStar(require("./product/recordpage"), exports);
|
|
|
97
100
|
__exportStar(require("./product/recordpanel"), exports);
|
|
98
101
|
__exportStar(require("./product/recordstable"), exports);
|
|
99
102
|
__exportStar(require("./product/richtext"), exports);
|
|
103
|
+
__exportStar(require("./product/savedviews"), exports);
|
|
100
104
|
__exportStar(require("./product/settingssection"), exports);
|
|
101
105
|
__exportStar(require("./product/settingsshell"), exports);
|
|
102
106
|
__exportStar(require("./product/sidebar"), exports);
|
|
103
107
|
__exportStar(require("./product/statlist"), exports);
|
|
104
108
|
__exportStar(require("./product/tasklist"), exports);
|
|
109
|
+
__exportStar(require("./product/taskstable"), exports);
|
|
110
|
+
__exportStar(require("./product/themepicker"), exports);
|
|
105
111
|
__exportStar(require("./product/trialbanner"), exports);
|
|
106
112
|
__exportStar(require("./product/viewtoggle"), exports);
|
|
107
113
|
// design tokens + recipe core (for building custom components)
|
|
@@ -0,0 +1,171 @@
|
|
|
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.useFormDialogOptional = useFormDialogOptional;
|
|
37
|
+
exports.FormDialog = FormDialog;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const React = __importStar(require("react"));
|
|
40
|
+
const button_1 = require("../../components/button");
|
|
41
|
+
const dialog_1 = require("../../components/dialog");
|
|
42
|
+
const switch_1 = require("../../components/switch");
|
|
43
|
+
const core_1 = require("../../core");
|
|
44
|
+
const formdialog_recipe_1 = require("./formdialog.recipe");
|
|
45
|
+
/**
|
|
46
|
+
* Whether a non-required field inside a `createMode` `FormDialog` gets
|
|
47
|
+
* `FormField`'s "(optional)" marker automatically — Attio suffixes every
|
|
48
|
+
* optional label this way on its own "New Company" dialog (crisp#548). A
|
|
49
|
+
* consumer's own field wrapper reads this once (`useFormDialogOptional`);
|
|
50
|
+
* `false` outside any `FormDialog`, or on an edit dialog.
|
|
51
|
+
*/
|
|
52
|
+
const OptionalLabelsContext = React.createContext(false);
|
|
53
|
+
/** See `OptionalLabelsContext`. Call from a field wrapper as
|
|
54
|
+
* `optional={useFormDialogOptional(required)}`. */
|
|
55
|
+
function useFormDialogOptional(required) {
|
|
56
|
+
return React.useContext(OptionalLabelsContext) && !required;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A modal for a create or edit form — crisp's own `Dialog`/`DialogBody`/
|
|
60
|
+
* `DialogFooter`, plus everything a record-creating form needs beyond plain
|
|
61
|
+
* chrome (crisp#548, moved out of internal/grc's own `forms.tsx`, called
|
|
62
|
+
* from nine places there): dirty tracking with a discard-changes confirm
|
|
63
|
+
* (any click/change/input inside the form marks it dirty; Escape/backdrop/
|
|
64
|
+
* header-×/Cancel all route through one `requestClose`, which only asks
|
|
65
|
+
* when something actually changed — a stacked crisp `Dialog`), a
|
|
66
|
+
* `beforeunload` guard for a reload or external navigation, and
|
|
67
|
+
* scroll-to-first-invalid-field once a submit attempt finishes (crisp's own
|
|
68
|
+
* `FormField` already marks every control `aria-invalid`, so this needs no
|
|
69
|
+
* per-field wiring). A record needs several fields at once to be valid,
|
|
70
|
+
* which is why records are not created by inline editing.
|
|
71
|
+
*/
|
|
72
|
+
function FormDialog({ title, submitLabel, onSubmit, onClose, busy, error, children, wide = false, size, createMode = false, createMoreLabel: createMoreLabelProp, cancelLabel: cancelLabelProp, discardTitle: discardTitleProp, discardConfirmLabel: discardConfirmLabelProp, discardCancelLabel: discardCancelLabelProp, className, ...rest }) {
|
|
73
|
+
const createMoreLabel = (0, core_1.useCrispMessage)(createMoreLabelProp, "Create more");
|
|
74
|
+
const createRecordLabel = (0, core_1.useCrispMessage)(undefined, "Create record");
|
|
75
|
+
const cancelLabel = (0, core_1.useCrispMessage)(cancelLabelProp, "Cancel");
|
|
76
|
+
const discardTitle = (0, core_1.useCrispMessage)(discardTitleProp, "Discard changes? Your changes will be lost.");
|
|
77
|
+
const discardConfirmLabel = (0, core_1.useCrispMessage)(discardConfirmLabelProp, "Discard");
|
|
78
|
+
const discardCancelLabel = (0, core_1.useCrispMessage)(discardCancelLabelProp, "Keep editing");
|
|
79
|
+
const formId = React.useId();
|
|
80
|
+
const formRef = React.useRef(null);
|
|
81
|
+
const switchId = React.useId();
|
|
82
|
+
// Read at submit time (click, Enter, or Cmd/Ctrl+Enter alike — all three
|
|
83
|
+
// go through the one <form onSubmit>), so `onSubmit` never has to reach
|
|
84
|
+
// back into the footer to know which way the switch was left.
|
|
85
|
+
const [createMore, setCreateMore] = React.useState(false);
|
|
86
|
+
const [confirmingDiscard, setConfirmingDiscard] = React.useState(false);
|
|
87
|
+
// Dirtiness is detected generically (any click, change or text input
|
|
88
|
+
// anywhere inside the form), not wired per field: React delivers these as
|
|
89
|
+
// synthetic events through the REACT tree, so a Select's portaled listbox
|
|
90
|
+
// or a DatePicker's portaled calendar — neither a DOM descendant of
|
|
91
|
+
// <form> — still reaches this handler, the same way a click inside either
|
|
92
|
+
// already reaches a submit handler on the form itself. A stray click
|
|
93
|
+
// (opening a picker without choosing anything) can mark this dirty when
|
|
94
|
+
// nothing actually changed; an occasional unneeded confirmation is a far
|
|
95
|
+
// smaller cost than silently discarding real, typed work.
|
|
96
|
+
const dirtyRef = React.useRef(false);
|
|
97
|
+
const markDirty = () => {
|
|
98
|
+
dirtyRef.current = true;
|
|
99
|
+
};
|
|
100
|
+
const requestClose = () => {
|
|
101
|
+
if (!dirtyRef.current) {
|
|
102
|
+
onClose();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
setConfirmingDiscard(true);
|
|
106
|
+
};
|
|
107
|
+
// A reload or an external navigation never reaches `requestClose` above —
|
|
108
|
+
// needs its own listener, live only while the dialog is mounted.
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
const onBeforeUnload = (e) => {
|
|
111
|
+
if (!dirtyRef.current)
|
|
112
|
+
return;
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.returnValue = "";
|
|
115
|
+
};
|
|
116
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
117
|
+
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
118
|
+
}, []);
|
|
119
|
+
// A refused save that leaves a field invalid is the one thing on screen
|
|
120
|
+
// that matters, and on a long form the field can be several screens above
|
|
121
|
+
// the submit button — a failed submit that never moves the viewport reads
|
|
122
|
+
// as nothing happened. `FormField` already marks every field it was given
|
|
123
|
+
// an error for with `aria-invalid`, on every control kind, so this needs
|
|
124
|
+
// no per-field wiring of its own — it just goes looking for the first one
|
|
125
|
+
// once a submit attempt finishes.
|
|
126
|
+
// `usePrefersReducedMotion`, not CSS `scroll-behavior: smooth`: this is JS
|
|
127
|
+
// motion driven by an explicit `behavior` argument, which CSS cannot reach.
|
|
128
|
+
const reduceMotion = (0, core_1.usePrefersReducedMotion)();
|
|
129
|
+
const wasBusy = React.useRef(false);
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
if (wasBusy.current && !busy) {
|
|
132
|
+
const invalid = formRef.current?.querySelector('[aria-invalid="true"]');
|
|
133
|
+
if (invalid) {
|
|
134
|
+
invalid.scrollIntoView({
|
|
135
|
+
block: "center",
|
|
136
|
+
...(reduceMotion
|
|
137
|
+
? { behavior: "auto" }
|
|
138
|
+
: { behavior: "smooth" }),
|
|
139
|
+
});
|
|
140
|
+
invalid.focus({ preventScroll: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
wasBusy.current = Boolean(busy);
|
|
144
|
+
}, [busy, reduceMotion]);
|
|
145
|
+
// Attached to the document, not the form: a portaled Popover (a
|
|
146
|
+
// DatePicker's calendar, a Select's listbox) renders outside the <form>
|
|
147
|
+
// element in the DOM, and a person pressing the shortcut while one of
|
|
148
|
+
// those is open should still submit the dialog it belongs to.
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
if (!createMode)
|
|
151
|
+
return;
|
|
152
|
+
const onKeyDown = (e) => {
|
|
153
|
+
if (busy)
|
|
154
|
+
return;
|
|
155
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
formRef.current?.requestSubmit();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
document.addEventListener("keydown", onKeyDown);
|
|
161
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
162
|
+
}, [createMode, busy]);
|
|
163
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: true, onOpenChange: (open) => !open && requestClose(), title: title, width: size ?? (wide ? 760 : 640), className: (0, formdialog_recipe_1.formdialogRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)(dialog_1.DialogBody, { children: [(0, jsx_runtime_1.jsx)(OptionalLabelsContext.Provider, { value: createMode, children: (0, jsx_runtime_1.jsx)("form", { ref: formRef, id: formId, className: "crisp-formdialog-form", onSubmit: (e) => {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
onSubmit(createMore);
|
|
166
|
+
}, onChange: markDirty, onInput: markDirty, onClick: markDirty, children: children }) }), error != null && (0, jsx_runtime_1.jsx)("p", { className: "crisp-formdialog-error", children: error })] }), (0, jsx_runtime_1.jsx)(dialog_1.DialogFooter, { children: createMode ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-formdialog-createfoot", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-more", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { id: switchId, checked: createMore, onCheckedChange: setCreateMore, "aria-label": createMoreLabel }), (0, jsx_runtime_1.jsx)("label", { className: "crisp-formdialog-more-label", htmlFor: switchId, children: createMoreLabel })] }), (0, jsx_runtime_1.jsxs)("button", { type: "submit", form: formId, className: "crisp-dialog-submit", disabled: busy, children: [createRecordLabel, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-submit-key", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("kbd", { children: "\u2318" }), (0, jsx_runtime_1.jsx)("kbd", { children: "\u21B5" })] })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: requestClose, children: cancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "submit", form: formId, intent: "primary", loading: busy, children: submitLabel })] })) })] }), (0, jsx_runtime_1.jsx)(dialog_1.Dialog, { open: confirmingDiscard, onOpenChange: (open) => !open && setConfirmingDiscard(false), title: discardTitle, width: 400, children: (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: () => setConfirmingDiscard(false), children: discardCancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "danger", onClick: () => {
|
|
167
|
+
setConfirmingDiscard(false);
|
|
168
|
+
onClose();
|
|
169
|
+
}, children: discardConfirmLabel })] }) })] }));
|
|
170
|
+
}
|
|
171
|
+
FormDialog.displayName = "FormDialog";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formdialogRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// The createMode footer's switch+button cluster — measured live off Attio's
|
|
7
|
+
// own "New Company" create dialog (crisp#548): a 16px gap between the
|
|
8
|
+
// "Create more" switch group and the submit button, different from
|
|
9
|
+
// `.crisp-dialog-footer`'s own 12px (the gap every OTHER dialog's
|
|
10
|
+
// Cancel/Save pair uses). One wrapper carries the 16px itself rather than
|
|
11
|
+
// widening the shared token, so it doesn't stretch to the footer's full
|
|
12
|
+
// width — `.crisp-dialog-footer`'s `justify-content: flex-end` still pushes
|
|
13
|
+
// the whole cluster to the right edge as one snug group.
|
|
14
|
+
const config = (0, core_1.defineRecipe)({
|
|
15
|
+
name: "formdialog",
|
|
16
|
+
base: `& .crisp-formdialog-createfoot { display: flex; align-items: center; gap: ${tokens_1.contract.space["4"]}; }
|
|
17
|
+
& .crisp-formdialog-more-label { font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
|
|
18
|
+
& .crisp-formdialog-error { font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgDanger}; margin: 0; }
|
|
19
|
+
& .crisp-formdialog-form { display: grid; gap: ${tokens_1.contract.space["3"]}; }
|
|
20
|
+
`,
|
|
21
|
+
variants: {},
|
|
22
|
+
defaultVariants: {},
|
|
23
|
+
compoundVariants: [],
|
|
24
|
+
});
|
|
25
|
+
exports.formdialogRecipe = (0, core_1.resolveRecipe)(config);
|
|
26
|
+
exports.default = config;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formdialogRecipe = exports.useFormDialogOptional = exports.FormDialog = void 0;
|
|
4
|
+
var formdialog_1 = require("./formdialog");
|
|
5
|
+
Object.defineProperty(exports, "FormDialog", { enumerable: true, get: function () { return formdialog_1.FormDialog; } });
|
|
6
|
+
Object.defineProperty(exports, "useFormDialogOptional", { enumerable: true, get: function () { return formdialog_1.useFormDialogOptional; } });
|
|
7
|
+
var formdialog_recipe_1 = require("./formdialog.recipe");
|
|
8
|
+
Object.defineProperty(exports, "formdialogRecipe", { enumerable: true, get: function () { return formdialog_recipe_1.formdialogRecipe; } });
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ImportDialog = ImportDialog;
|
|
38
|
+
exports.ImportDialogDropzone = ImportDialogDropzone;
|
|
39
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
|
+
const react_file_upload_1 = require("@seed-design/react-file-upload");
|
|
41
|
+
const dialog = __importStar(require("@zag-js/dialog"));
|
|
42
|
+
const react_1 = require("@zag-js/react");
|
|
43
|
+
const lucide_react_1 = require("lucide-react");
|
|
44
|
+
const React = __importStar(require("react"));
|
|
45
|
+
const button_1 = require("../../components/button");
|
|
46
|
+
const core_1 = require("../../core");
|
|
47
|
+
const importdialog_recipe_1 = require("./importdialog.recipe");
|
|
48
|
+
let instanceCounter = 0;
|
|
49
|
+
function useImportDialogInstanceId() {
|
|
50
|
+
const idRef = React.useRef(null);
|
|
51
|
+
if (idRef.current == null) {
|
|
52
|
+
instanceCounter += 1;
|
|
53
|
+
idRef.current = `crisp-importdialog-${instanceCounter}`;
|
|
54
|
+
}
|
|
55
|
+
return idRef.current;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A full-viewport step wizard — Attio's own CSV import flow (measured live:
|
|
59
|
+
* `1584x871` filling a ~1600x900 viewport, `border-radius: 0`, no shadow,
|
|
60
|
+
* opaque white). Composes Zag's dialog machine for focus-trap/scroll-lock/
|
|
61
|
+
* Escape exactly like `Dialog`/`Drawer`, with its own recipe for the
|
|
62
|
+
* full-bleed chrome neither of those two share (see importdialog.recipe.ts).
|
|
63
|
+
*
|
|
64
|
+
* The step header and footer are generic — the wizard doesn't know about
|
|
65
|
+
* CSV. Step 1's body is typically `ImportDialogDropzone`; steps 2-4
|
|
66
|
+
* (map columns / review values / preview import) are consumer-supplied
|
|
67
|
+
* `children`, since Attio's own column-mapping UI wasn't part of this
|
|
68
|
+
* measurement pass.
|
|
69
|
+
*/
|
|
70
|
+
function ImportDialog({ open, onOpenChange, title = "Import", steps, activeStep, onStepClick, children, primaryLabel = "Next", onPrimary, primaryDisabled, "aria-label": ariaLabel, closeLabel: closeLabelProp, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
71
|
+
const closeLabel = (0, core_1.useCrispMessage)(closeLabelProp, "Close");
|
|
72
|
+
const id = useImportDialogInstanceId();
|
|
73
|
+
const service = (0, react_1.useMachine)(dialog.machine, {
|
|
74
|
+
id,
|
|
75
|
+
open,
|
|
76
|
+
onOpenChange(details) {
|
|
77
|
+
onOpenChange(details.open);
|
|
78
|
+
},
|
|
79
|
+
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
80
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
81
|
+
// handling (#361) -- see Dialog's identical comment; this composes the
|
|
82
|
+
// same Zag machine.
|
|
83
|
+
onEscapeKeyDown(event) {
|
|
84
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const api = dialog.connect(service, react_1.normalizeProps);
|
|
89
|
+
if (!api.open)
|
|
90
|
+
return null;
|
|
91
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, importdialog_recipe_1.importdialogRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "crisp-importdialog-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "crisp-importdialog-positioner", children: (0, jsx_runtime_1.jsxs)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: (0, core_1.cx)("crisp-importdialog-content", className), style: style, "data-crisp-importdialog": id, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-importdialog-header", children: [(0, jsx_runtime_1.jsx)("h2", { ...api.getTitleProps(), className: "crisp-importdialog-title", children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", ...api.getCloseTriggerProps(), className: "crisp-importdialog-close", "aria-label": closeLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 2, "aria-hidden": "true" }) })] }), (0, jsx_runtime_1.jsx)("nav", { className: "crisp-importdialog-steps", "aria-label": (typeof title === "string" ? title : ariaLabel) ?? "Steps", children: steps.map((step) => {
|
|
92
|
+
const isActive = step.id === activeStep;
|
|
93
|
+
const commonProps = {
|
|
94
|
+
className: "crisp-importdialog-step",
|
|
95
|
+
"data-active": isActive ? "" : undefined,
|
|
96
|
+
"aria-current": isActive ? "step" : undefined,
|
|
97
|
+
};
|
|
98
|
+
return onStepClick ? ((0, jsx_runtime_1.jsx)("button", { type: "button", ...commonProps, onClick: () => onStepClick(step.id), children: step.label }, step.id)) : ((0, jsx_runtime_1.jsx)("span", { ...commonProps, children: step.label }, step.id));
|
|
99
|
+
}) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-importdialog-body", children: children }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-importdialog-footer", children: (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "primary", size: "md", disabled: primaryDisabled, onClick: onPrimary, children: primaryLabel }) })] }) })] }) }));
|
|
100
|
+
}
|
|
101
|
+
ImportDialog.displayName = "ImportDialog";
|
|
102
|
+
/**
|
|
103
|
+
* The step-1 drop target — measured chrome (`border: 1px dashed
|
|
104
|
+
* rgb(230,231,234)`, `border-radius: 20px`, `background: rgb(251,251,251)`)
|
|
105
|
+
* over the SAME headless drag/drop + file-picker engine `FileUpload` uses
|
|
106
|
+
* (`@seed-design/react-file-upload`'s `useFileUpload`), rather than a fork
|
|
107
|
+
* of it. `FileUpload`'s own rendered markup (a single button carrying one
|
|
108
|
+
* title + one hint) can't host this dropzone's four separate pieces of copy
|
|
109
|
+
* — a title, an "or" divider, a "Choose a .CSV file" trigger, and a "Learn
|
|
110
|
+
* more" link that needs its own `<a href>` — so this composes the hook
|
|
111
|
+
* directly instead of wrapping `<FileUpload>`, and the engine (not this
|
|
112
|
+
* file) still owns every bit of drag-state/accept/reject behaviour.
|
|
113
|
+
*/
|
|
114
|
+
function ImportDialogDropzone({ accept = ".csv", disabled, maxFileSize, onFileAccept, onFileReject, dropLabel = "Drop your .CSV file onto this area to upload", orLabel = "or", chooseLabel = "Choose a .CSV file", learnMoreHref, learnMoreLabel = "Learn more", className, id, ...rest }) {
|
|
115
|
+
const [rejections, setRejections] = React.useState([]);
|
|
116
|
+
const { dropzoneProps, triggerProps, hiddenInputProps, inputRef } = (0, react_file_upload_1.useFileUpload)({
|
|
117
|
+
accept,
|
|
118
|
+
disabled,
|
|
119
|
+
maxFileSize,
|
|
120
|
+
maxFiles: 1,
|
|
121
|
+
onFileReject: (r) => {
|
|
122
|
+
setRejections(r);
|
|
123
|
+
onFileReject?.(r);
|
|
124
|
+
},
|
|
125
|
+
onFileAccept: (entries) => {
|
|
126
|
+
setRejections([]);
|
|
127
|
+
const file = entries[entries.length - 1]?.file;
|
|
128
|
+
if (file)
|
|
129
|
+
onFileAccept?.(file);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ...rest, ...dropzoneProps, className: (0, core_1.cx)("crisp-importdialog-dropzone", className), children: [(0, jsx_runtime_1.jsx)("input", { ...hiddenInputProps, id: id ?? hiddenInputProps.id, ref: inputRef, className: "crisp-importdialog-dropzone-input" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-importdialog-dropzone-title", children: dropLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-importdialog-dropzone-or", children: orLabel }), (0, jsx_runtime_1.jsx)("button", { ...triggerProps, className: "crisp-importdialog-dropzone-choose", children: chooseLabel }), learnMoreHref && ((0, jsx_runtime_1.jsx)("a", { className: "crisp-importdialog-dropzone-learnmore", href: learnMoreHref, target: "_blank", rel: "noreferrer", children: learnMoreLabel })), (0, jsx_runtime_1.jsx)("ul", { className: "crisp-importdialog-dropzone-errors", "aria-live": "polite", children: rejections.map((r) => ((0, jsx_runtime_1.jsx)("li", { className: "crisp-importdialog-dropzone-error", children: r.file.name }, r.file.name))) })] }));
|
|
133
|
+
}
|
|
134
|
+
ImportDialogDropzone.displayName = "ImportDialogDropzone";
|