@crisp-ui-kit/crisp 0.54.0 → 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.
Files changed (127) hide show
  1. package/dist/activityfeed.css +12 -0
  2. package/dist/activityfeed.d.ts +7 -0
  3. package/dist/activityfeed.layered.css +14 -0
  4. package/dist/badge.css +3 -0
  5. package/dist/badge.d.ts +2 -2
  6. package/dist/badge.layered.css +3 -0
  7. package/dist/cjs/components/badge/badge.recipe.js +10 -0
  8. package/dist/cjs/components/datatable/datatable.js +2 -1
  9. package/dist/cjs/components/datatable/datatable.recipe.js +4 -0
  10. package/dist/cjs/components/mentionchip/index.js +7 -0
  11. package/dist/cjs/components/mentionchip/mentionchip.js +57 -0
  12. package/dist/cjs/components/mentionchip/mentionchip.recipe.js +38 -0
  13. package/dist/cjs/components/table/table.recipe.js +15 -3
  14. package/dist/cjs/core/floating-stack.js +18 -0
  15. package/dist/cjs/core/index.js +2 -1
  16. package/dist/cjs/index.js +7 -0
  17. package/dist/cjs/product/activityfeed/activityfeed.js +38 -0
  18. package/dist/cjs/product/activityfeed/activityfeed.recipe.js +35 -0
  19. package/dist/cjs/product/activityfeed/index.js +10 -0
  20. package/dist/cjs/product/formdialog/formdialog.js +171 -0
  21. package/dist/cjs/product/formdialog/formdialog.recipe.js +26 -0
  22. package/dist/cjs/product/formdialog/index.js +8 -0
  23. package/dist/cjs/product/importdialog/importdialog.js +134 -0
  24. package/dist/cjs/product/importdialog/importdialog.recipe.js +92 -0
  25. package/dist/cjs/product/importdialog/index.js +18 -0
  26. package/dist/cjs/product/richtext/richtext.js +43 -0
  27. package/dist/cjs/product/savedviews/index.js +7 -0
  28. package/dist/cjs/product/savedviews/savedviews.js +207 -0
  29. package/dist/cjs/product/savedviews/savedviews.recipe.js +103 -0
  30. package/dist/cjs/product/taskstable/index.js +7 -0
  31. package/dist/cjs/product/taskstable/taskstable.js +27 -0
  32. package/dist/cjs/product/taskstable/taskstable.recipe.js +47 -0
  33. package/dist/cjs/product/themepicker/index.js +17 -0
  34. package/dist/cjs/product/themepicker/themepicker.js +32 -0
  35. package/dist/cjs/product/themepicker/themepicker.recipe.js +98 -0
  36. package/dist/cjs/tokens/palette.js +31 -0
  37. package/dist/cjs/tokens/semantic.js +11 -0
  38. package/dist/datatable.css +20 -21
  39. package/dist/datatable.layered.css +20 -21
  40. package/dist/esm/components/badge/badge.recipe.js +10 -0
  41. package/dist/esm/components/datatable/datatable.js +2 -1
  42. package/dist/esm/components/datatable/datatable.recipe.js +4 -0
  43. package/dist/esm/components/mentionchip/index.js +2 -0
  44. package/dist/esm/components/mentionchip/mentionchip.js +21 -0
  45. package/dist/esm/components/mentionchip/mentionchip.recipe.js +35 -0
  46. package/dist/esm/components/table/table.recipe.js +15 -3
  47. package/dist/esm/core/floating-stack.js +17 -0
  48. package/dist/esm/core/index.js +1 -1
  49. package/dist/esm/index.js +7 -0
  50. package/dist/esm/product/activityfeed/activityfeed.js +32 -0
  51. package/dist/esm/product/activityfeed/activityfeed.recipe.js +32 -0
  52. package/dist/esm/product/activityfeed/index.js +2 -0
  53. package/dist/esm/product/formdialog/formdialog.js +134 -0
  54. package/dist/esm/product/formdialog/formdialog.recipe.js +23 -0
  55. package/dist/esm/product/formdialog/index.js +2 -0
  56. package/dist/esm/product/importdialog/importdialog.js +97 -0
  57. package/dist/esm/product/importdialog/importdialog.recipe.js +89 -0
  58. package/dist/esm/product/importdialog/index.js +2 -0
  59. package/dist/esm/product/richtext/richtext.js +43 -0
  60. package/dist/esm/product/savedviews/index.js +2 -0
  61. package/dist/esm/product/savedviews/savedviews.js +171 -0
  62. package/dist/esm/product/savedviews/savedviews.recipe.js +100 -0
  63. package/dist/esm/product/taskstable/index.js +2 -0
  64. package/dist/esm/product/taskstable/taskstable.js +24 -0
  65. package/dist/esm/product/taskstable/taskstable.recipe.js +44 -0
  66. package/dist/esm/product/themepicker/index.js +1 -0
  67. package/dist/esm/product/themepicker/themepicker.js +29 -0
  68. package/dist/esm/product/themepicker/themepicker.recipe.js +95 -0
  69. package/dist/esm/tokens/palette.js +31 -0
  70. package/dist/esm/tokens/semantic.js +11 -0
  71. package/dist/formdialog.css +6 -0
  72. package/dist/formdialog.d.ts +7 -0
  73. package/dist/formdialog.layered.css +8 -0
  74. package/dist/importdialog.css +38 -0
  75. package/dist/importdialog.d.ts +7 -0
  76. package/dist/importdialog.layered.css +40 -0
  77. package/dist/mentionchip.css +3 -0
  78. package/dist/mentionchip.d.ts +7 -0
  79. package/dist/mentionchip.layered.css +5 -0
  80. package/dist/native/tokens.d.ts +1 -1
  81. package/dist/native/tokens.js +24 -4
  82. package/dist/savedviews.css +78 -0
  83. package/dist/savedviews.d.ts +7 -0
  84. package/dist/savedviews.layered.css +80 -0
  85. package/dist/styles.css +242 -27
  86. package/dist/styles.layered.css +242 -27
  87. package/dist/table.css +15 -6
  88. package/dist/table.layered.css +15 -6
  89. package/dist/taskstable.css +23 -0
  90. package/dist/taskstable.d.ts +7 -0
  91. package/dist/taskstable.layered.css +25 -0
  92. package/dist/themepicker.css +27 -0
  93. package/dist/themepicker.d.ts +7 -0
  94. package/dist/themepicker.layered.css +29 -0
  95. package/dist/tokens.css +10 -0
  96. package/dist/tokens.json +24 -4
  97. package/dist/types/components/badge/badge.d.ts +2 -0
  98. package/dist/types/components/badge/badge.recipe.d.ts +8 -0
  99. package/dist/types/components/datatable/datatable.d.ts +12 -1
  100. package/dist/types/components/mentionchip/index.d.ts +2 -0
  101. package/dist/types/components/mentionchip/mentionchip.d.ts +20 -0
  102. package/dist/types/components/mentionchip/mentionchip.recipe.d.ts +18 -0
  103. package/dist/types/core/floating-stack.d.ts +14 -0
  104. package/dist/types/core/index.d.ts +1 -1
  105. package/dist/types/index.d.ts +7 -0
  106. package/dist/types/product/activityfeed/activityfeed.d.ts +57 -0
  107. package/dist/types/product/activityfeed/activityfeed.recipe.d.ts +3 -0
  108. package/dist/types/product/activityfeed/index.d.ts +2 -0
  109. package/dist/types/product/formdialog/formdialog.d.ts +57 -0
  110. package/dist/types/product/formdialog/formdialog.recipe.d.ts +3 -0
  111. package/dist/types/product/formdialog/index.d.ts +2 -0
  112. package/dist/types/product/importdialog/importdialog.d.ts +86 -0
  113. package/dist/types/product/importdialog/importdialog.recipe.d.ts +3 -0
  114. package/dist/types/product/importdialog/index.d.ts +2 -0
  115. package/dist/types/product/savedviews/index.d.ts +2 -0
  116. package/dist/types/product/savedviews/savedviews.d.ts +55 -0
  117. package/dist/types/product/savedviews/savedviews.recipe.d.ts +3 -0
  118. package/dist/types/product/taskstable/index.d.ts +2 -0
  119. package/dist/types/product/taskstable/taskstable.d.ts +54 -0
  120. package/dist/types/product/taskstable/taskstable.recipe.d.ts +3 -0
  121. package/dist/types/product/themepicker/index.d.ts +1 -0
  122. package/dist/types/product/themepicker/themepicker.d.ts +21 -0
  123. package/dist/types/product/themepicker/themepicker.recipe.d.ts +3 -0
  124. package/dist/types/tokens/contract.d.ts +5 -0
  125. package/dist/types/tokens/palette.d.ts +20 -0
  126. package/dist/types/tokens/semantic.d.ts +11 -0
  127. package/package.json +36 -1
@@ -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";
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importdialogRecipe = void 0;
4
+ const core_1 = require("../../core");
5
+ const tokens_1 = require("../../tokens");
6
+ // A full-viewport wizard takeover — measured live from Attio's own Import CSV
7
+ // flow (app.attio.com/techpartners/companies -> toolbar "Import / Export" ->
8
+ // "Import CSV"): panel `1584x871` in a ~1600x900 viewport (i.e. it fills the
9
+ // content area, not a small centred modal like `Dialog`), `border-radius: 0`,
10
+ // `box-shadow: none`, opaque `rgb(255,255,255)` background. Unlike
11
+ // Dialog/Drawer's frosted, edge-gutter surfaces (AUTHORING Lesson 30), a
12
+ // full-viewport takeover has no edge to float off of, so it gets its own
13
+ // recipe rather than composing either.
14
+ //
15
+ // The 4-step header renders each step as a 28px pill (`size.row`), 8px
16
+ // corner (`radius.md`), transparent at rest. Attio's own active-step
17
+ // treatment wasn't captured in the measurement pass, so the current step
18
+ // reuses crisp's existing brand-tint pattern (14%/82% `color-mix` — the same
19
+ // ratio `RecordsTable`'s brand-tone tag and `MultiSelect`'s brand chip
20
+ // already use) rather than inventing a new colour — a documented scope
21
+ // decision, not a guess.
22
+ //
23
+ // The dropzone is `border: 1px dashed rgb(230,231,234)` (`borderDropzone`,
24
+ // see tokens/palette.ts), `border-radius: 20px`, `background: rgb(251,251,251)`
25
+ // (`bgSubtle`). 20px sits between the scale's `2xl` (16px) and `full` (999px)
26
+ // with no other measured use yet — left as a raw px per AUTHORING 4b's
27
+ // "genuine one-off" exception rather than added to the shared scale for a
28
+ // single consumer.
29
+ //
30
+ // The primary "Next" button is composed from crisp's own `Button` (`intent`
31
+ // `primary`, `size` `md` — 28px tall, 8px corner, brand solid background —
32
+ // matches the measurement exactly), not a hand-rolled button.
33
+ const config = (0, core_1.defineRecipe)({
34
+ name: "importdialog",
35
+ base: `display: contents; font-family: ${tokens_1.contract.font.sans};
36
+ & .crisp-importdialog-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; }
37
+ & .crisp-importdialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; }
38
+ & .crisp-importdialog-content {
39
+ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
40
+ position: relative; display: flex; flex-direction: column; box-sizing: border-box;
41
+ width: 100%; height: 100%; border-radius: 0; background: ${tokens_1.contract.bgSurface}; box-shadow: none;
42
+ transform: none; opacity: 1; transition: opacity 0.2s ease;
43
+ @starting-style { opacity: 0; }
44
+ }
45
+ & .crisp-importdialog-header { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; height: 52px; padding: 0 ${tokens_1.contract.space["2"]} 0 ${tokens_1.contract.space["6"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
46
+ & .crisp-importdialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${tokens_1.contract.text.xl}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; margin: 0; }
47
+ & .crisp-importdialog-close { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; color: ${tokens_1.contract.fgMuted}; font-family: inherit; cursor: pointer; }
48
+ & .crisp-importdialog-close:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: ${tokens_1.contract.fgPrimary}; }
49
+
50
+ & .crisp-importdialog-steps { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; padding: ${tokens_1.contract.space["4"]} ${tokens_1.contract.space["6"]} 0; }
51
+ & .crisp-importdialog-step {
52
+ display: inline-flex; align-items: center; height: ${tokens_1.contract.size.row}; padding: 0 ${tokens_1.contract.space["2.5"]};
53
+ border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; color: ${tokens_1.contract.fgMuted};
54
+ font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: default;
55
+ transition: background-color .12s, color .12s;
56
+ }
57
+ & button.crisp-importdialog-step { cursor: pointer; }
58
+ & .crisp-importdialog-step[data-active] { 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}); }
59
+ & .crisp-importdialog-step:focus-visible { outline: 2px solid ${tokens_1.contract.strokeFocusRing}; outline-offset: 1px; }
60
+
61
+ & .crisp-importdialog-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; padding: ${tokens_1.contract.space["6"]}; }
62
+
63
+ & .crisp-importdialog-footer { display: flex; align-items: center; justify-content: flex-end; gap: ${tokens_1.contract.space["3"]}; flex-shrink: 0; min-height: 56px; padding: 0 ${tokens_1.contract.space["6"]}; border-top: 1px solid ${tokens_1.contract.borderSubtle}; }
64
+
65
+ & .crisp-importdialog-dropzone {
66
+ box-sizing: border-box; position: relative; flex: 1; min-height: 0;
67
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
68
+ gap: ${tokens_1.contract.space["1"]}; width: 100%;
69
+ border: 1px dashed ${tokens_1.contract.borderDropzone}; border-radius: 20px; background: ${tokens_1.contract.bgSubtle};
70
+ text-align: center; transition: background-color .12s, border-color .12s;
71
+ }
72
+ & .crisp-importdialog-dropzone[data-dragging-over] { border-color: ${tokens_1.contract.bgBrandSolid}; background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 6%, transparent); }
73
+ & .crisp-importdialog-dropzone[data-invalid] { border-color: ${tokens_1.contract.bgDanger}; }
74
+ & .crisp-importdialog-dropzone-title { color: ${tokens_1.contract.fgPrimary}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; }
75
+ & .crisp-importdialog-dropzone-or { color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.md}; line-height: 18px; }
76
+ & .crisp-importdialog-dropzone-choose {
77
+ border: none; background: none; padding: 0; margin: 0; font-family: inherit;
78
+ font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.bgBrandSolid}; cursor: pointer;
79
+ }
80
+ & .crisp-importdialog-dropzone-choose:hover { color: ${tokens_1.contract.bgBrandSolidPressed}; }
81
+ & .crisp-importdialog-dropzone-choose:focus-visible { outline: 2px solid ${tokens_1.contract.strokeFocusRing}; outline-offset: 2px; border-radius: ${tokens_1.contract.radius.sm}; }
82
+ & .crisp-importdialog-dropzone-learnmore { color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.md}; text-decoration: underline; }
83
+ & .crisp-importdialog-dropzone-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
84
+ & .crisp-importdialog-dropzone-errors { margin: 0; padding: 0; list-style: none; }
85
+ & .crisp-importdialog-dropzone-errors:empty { position: absolute; }
86
+ & .crisp-importdialog-dropzone-error { color: ${tokens_1.contract.bgDanger}; font-size: ${tokens_1.contract.text.md}; line-height: 18px; }`,
87
+ variants: {},
88
+ defaultVariants: {},
89
+ compoundVariants: [],
90
+ });
91
+ exports.importdialogRecipe = (0, core_1.resolveRecipe)(config);
92
+ exports.default = config;
@@ -0,0 +1,18 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./importdialog"), exports);
18
+ __exportStar(require("./importdialog.recipe"), exports);
@@ -183,6 +183,14 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
183
183
  // (documented React behaviour), so a plain onFocus/onBlur pair on this
184
184
  // same wrapper sees the portal's contents correctly.
185
185
  const mentionPickerHasFocusRef = React.useRef(false);
186
+ // A caret position `insertMention` just established, to be re-asserted
187
+ // the next time `body` regains focus (#514 fixed the picker stealing
188
+ // focus on OPEN; this is the same "caret resets to offset 0" mechanism
189
+ // on SELECT — see insertMention's own comment for why one synchronous
190
+ // `sel.addRange` isn't enough). One-shot: consumed and cleared by the
191
+ // very next focus or input, whichever comes first, so a stale pointer
192
+ // can never later hijack an unrelated focus.
193
+ const pendingCaretRef = React.useRef(null);
186
194
  const syncEmpty = (el) => {
187
195
  if (el)
188
196
  el.toggleAttribute("data-empty", (el.textContent ?? "") === "");
@@ -494,6 +502,20 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
494
502
  after.collapse(true);
495
503
  sel.removeAllRanges();
496
504
  sel.addRange(after);
505
+ // `select`ing a mention is usually the LAST thing the consumer's
506
+ // picker does before it unmounts (ActorPicker-shaped consumers call
507
+ // `close` right after `select`, same as this file's own doc example).
508
+ // A picker built on a dialog primitive (crisp's own `Command`
509
+ // included) commonly restores focus to whatever had it before the
510
+ // dialog opened as part of ITS OWN unmount cleanup — a plain
511
+ // `.focus()` with no Range of its own, which collapses a
512
+ // contentEditable's caret to offset 0. That cleanup runs after this
513
+ // function returns (the picker only unmounts once `closeMention`
514
+ // below flips `mention` to null and React commits it), so the
515
+ // Selection set two lines up can already be correct here and still
516
+ // get clobbered a tick later. Re-assert it the next time `body`
517
+ // actually regains focus, whenever that turns out to be.
518
+ pendingCaretRef.current = { node: space, offset: 1 };
497
519
  closeMention();
498
520
  syncEmpty(bodyRef.current);
499
521
  emit();
@@ -558,10 +580,31 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
558
580
  } })), (0, jsx_runtime_1.jsx)("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
559
581
  ? undefined
560
582
  : () => {
583
+ // A real keystroke landing here means `body` already has
584
+ // focus on its own terms — any caret a prior mention
585
+ // selection was waiting to reassert (see insertMention) is
586
+ // moot now and must not later jump the caret backward on
587
+ // some unrelated future focus.
588
+ pendingCaretRef.current = null;
561
589
  syncEmpty(bodyRef.current);
562
590
  updateSlashTrigger();
563
591
  updateMention();
564
592
  emit();
593
+ }, onFocus: readOnly
594
+ ? undefined
595
+ : () => {
596
+ const pending = pendingCaretRef.current;
597
+ pendingCaretRef.current = null;
598
+ if (!pending?.node.isConnected)
599
+ return;
600
+ const sel = window.getSelection();
601
+ if (!sel)
602
+ return;
603
+ const range = document.createRange();
604
+ range.setStart(pending.node, pending.offset);
605
+ range.collapse(true);
606
+ sel.removeAllRanges();
607
+ sel.addRange(range);
565
608
  }, onMouseUp: readOnly
566
609
  ? undefined
567
610
  : () => {
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.savedviewsRecipe = exports.SavedViews = void 0;
4
+ var savedviews_1 = require("./savedviews");
5
+ Object.defineProperty(exports, "SavedViews", { enumerable: true, get: function () { return savedviews_1.SavedViews; } });
6
+ var savedviews_recipe_1 = require("./savedviews.recipe");
7
+ Object.defineProperty(exports, "savedviewsRecipe", { enumerable: true, get: function () { return savedviews_recipe_1.savedviewsRecipe; } });