@crisp-ui-kit/crisp 0.57.3 → 0.57.4

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 (169) hide show
  1. package/dist/assigneepicker.css +13 -0
  2. package/dist/assigneepicker.d.ts +7 -0
  3. package/dist/assigneepicker.layered.css +15 -0
  4. package/dist/button.css +1 -1
  5. package/dist/button.layered.css +1 -1
  6. package/dist/cjs/components/button/button.recipe.js +15 -10
  7. package/dist/cjs/components/datatable/datatable.js +6 -3
  8. package/dist/cjs/components/datatable/datatable.recipe.js +35 -2
  9. package/dist/cjs/components/menu/menu.js +48 -8
  10. package/dist/cjs/components/multiselect/multiselect.js +3 -0
  11. package/dist/cjs/components/multiselect/multiselect.recipe.js +18 -0
  12. package/dist/cjs/components/select/select.js +2 -2
  13. package/dist/cjs/components/select/select.recipe.js +19 -0
  14. package/dist/cjs/core/index.js +2 -1
  15. package/dist/cjs/core/place-floating.js +38 -0
  16. package/dist/cjs/index.js +8 -0
  17. package/dist/cjs/product/assigneepicker/assigneepicker.js +127 -0
  18. package/dist/cjs/product/assigneepicker/assigneepicker.recipe.js +36 -0
  19. package/dist/cjs/product/assigneepicker/index.js +7 -0
  20. package/dist/cjs/product/notesgallery/index.js +7 -0
  21. package/dist/cjs/product/notesgallery/notesgallery.js +80 -0
  22. package/dist/cjs/product/notesgallery/notesgallery.recipe.js +46 -0
  23. package/dist/cjs/product/objectpagemenu/index.js +17 -0
  24. package/dist/cjs/product/objectpagemenu/objectpagemenu.js +26 -0
  25. package/dist/cjs/product/objectpagemenu/objectpagemenu.recipe.js +30 -0
  26. package/dist/cjs/product/objectsettings/objectsettings.js +107 -14
  27. package/dist/cjs/product/recordpage/recordpage.js +64 -5
  28. package/dist/cjs/product/recordpage/recordpage.recipe.js +38 -0
  29. package/dist/cjs/product/recordpicker/index.js +7 -0
  30. package/dist/cjs/product/recordpicker/recordpicker.js +100 -0
  31. package/dist/cjs/product/recordpicker/recordpicker.recipe.js +36 -0
  32. package/dist/cjs/product/savedviews/index.js +2 -1
  33. package/dist/cjs/product/savedviews/savedviews.js +22 -0
  34. package/dist/cjs/product/savedviews/savedviews.recipe.js +47 -0
  35. package/dist/cjs/product/share/index.js +7 -0
  36. package/dist/cjs/product/share/share.js +179 -0
  37. package/dist/cjs/product/share/share.recipe.js +114 -0
  38. package/dist/cjs/product/shortcutssheet/index.js +17 -0
  39. package/dist/cjs/product/shortcutssheet/shortcutssheet.js +125 -0
  40. package/dist/cjs/product/shortcutssheet/shortcutssheet.recipe.js +50 -0
  41. package/dist/cjs/product/taskeditpopover/index.js +7 -0
  42. package/dist/cjs/product/taskeditpopover/taskeditpopover.js +159 -0
  43. package/dist/cjs/product/taskeditpopover/taskeditpopover.recipe.js +68 -0
  44. package/dist/cjs/product/workspaceswitchermenu/index.js +17 -0
  45. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.js +38 -0
  46. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +34 -0
  47. package/dist/cjs/tokens/palette.js +27 -0
  48. package/dist/cjs/tokens/semantic.js +20 -0
  49. package/dist/datatable.css +40 -7
  50. package/dist/datatable.layered.css +40 -7
  51. package/dist/esm/components/button/button.recipe.js +15 -10
  52. package/dist/esm/components/datatable/datatable.js +6 -3
  53. package/dist/esm/components/datatable/datatable.recipe.js +35 -2
  54. package/dist/esm/components/menu/menu.js +47 -7
  55. package/dist/esm/components/multiselect/multiselect.js +3 -0
  56. package/dist/esm/components/multiselect/multiselect.recipe.js +18 -0
  57. package/dist/esm/components/select/select.js +2 -2
  58. package/dist/esm/components/select/select.recipe.js +19 -0
  59. package/dist/esm/core/index.js +1 -1
  60. package/dist/esm/core/place-floating.js +37 -0
  61. package/dist/esm/index.js +8 -0
  62. package/dist/esm/product/assigneepicker/assigneepicker.js +91 -0
  63. package/dist/esm/product/assigneepicker/assigneepicker.recipe.js +33 -0
  64. package/dist/esm/product/assigneepicker/index.js +2 -0
  65. package/dist/esm/product/notesgallery/index.js +2 -0
  66. package/dist/esm/product/notesgallery/notesgallery.js +44 -0
  67. package/dist/esm/product/notesgallery/notesgallery.recipe.js +43 -0
  68. package/dist/esm/product/objectpagemenu/index.js +1 -0
  69. package/dist/esm/product/objectpagemenu/objectpagemenu.js +23 -0
  70. package/dist/esm/product/objectpagemenu/objectpagemenu.recipe.js +27 -0
  71. package/dist/esm/product/objectsettings/objectsettings.js +106 -13
  72. package/dist/esm/product/recordpage/recordpage.js +64 -5
  73. package/dist/esm/product/recordpage/recordpage.recipe.js +38 -0
  74. package/dist/esm/product/recordpicker/index.js +2 -0
  75. package/dist/esm/product/recordpicker/recordpicker.js +64 -0
  76. package/dist/esm/product/recordpicker/recordpicker.recipe.js +33 -0
  77. package/dist/esm/product/savedviews/index.js +1 -1
  78. package/dist/esm/product/savedviews/savedviews.js +22 -1
  79. package/dist/esm/product/savedviews/savedviews.recipe.js +47 -0
  80. package/dist/esm/product/share/index.js +2 -0
  81. package/dist/esm/product/share/share.js +143 -0
  82. package/dist/esm/product/share/share.recipe.js +111 -0
  83. package/dist/esm/product/shortcutssheet/index.js +1 -0
  84. package/dist/esm/product/shortcutssheet/shortcutssheet.js +88 -0
  85. package/dist/esm/product/shortcutssheet/shortcutssheet.recipe.js +47 -0
  86. package/dist/esm/product/taskeditpopover/index.js +2 -0
  87. package/dist/esm/product/taskeditpopover/taskeditpopover.js +123 -0
  88. package/dist/esm/product/taskeditpopover/taskeditpopover.recipe.js +65 -0
  89. package/dist/esm/product/workspaceswitchermenu/index.js +1 -0
  90. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.js +35 -0
  91. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +31 -0
  92. package/dist/esm/tokens/palette.js +27 -0
  93. package/dist/esm/tokens/semantic.js +20 -0
  94. package/dist/multiselect.css +18 -0
  95. package/dist/multiselect.layered.css +18 -0
  96. package/dist/native/tokens.d.ts +1 -1
  97. package/dist/native/tokens.js +56 -4
  98. package/dist/notesgallery.css +15 -0
  99. package/dist/notesgallery.d.ts +7 -0
  100. package/dist/notesgallery.layered.css +17 -0
  101. package/dist/objectpagemenu.css +3 -0
  102. package/dist/objectpagemenu.d.ts +7 -0
  103. package/dist/objectpagemenu.layered.css +5 -0
  104. package/dist/recordpage.css +36 -0
  105. package/dist/recordpage.layered.css +36 -0
  106. package/dist/recordpicker.css +13 -0
  107. package/dist/recordpicker.d.ts +7 -0
  108. package/dist/recordpicker.layered.css +15 -0
  109. package/dist/savedviews.css +47 -0
  110. package/dist/savedviews.layered.css +47 -0
  111. package/dist/select.css +19 -0
  112. package/dist/select.layered.css +19 -0
  113. package/dist/share.css +84 -0
  114. package/dist/share.d.ts +7 -0
  115. package/dist/share.layered.css +86 -0
  116. package/dist/shortcutssheet.css +24 -0
  117. package/dist/shortcutssheet.d.ts +7 -0
  118. package/dist/shortcutssheet.layered.css +26 -0
  119. package/dist/styles.css +387 -8
  120. package/dist/styles.layered.css +387 -8
  121. package/dist/taskeditpopover.css +36 -0
  122. package/dist/taskeditpopover.d.ts +7 -0
  123. package/dist/taskeditpopover.layered.css +38 -0
  124. package/dist/tokens.css +26 -0
  125. package/dist/tokens.json +56 -4
  126. package/dist/types/components/button/button.recipe.d.ts +2 -3
  127. package/dist/types/components/datatable/datatable.d.ts +8 -0
  128. package/dist/types/components/menu/menu.d.ts +50 -7
  129. package/dist/types/components/multiselect/multiselect.d.ts +9 -0
  130. package/dist/types/components/select/select.d.ts +10 -0
  131. package/dist/types/core/index.d.ts +1 -1
  132. package/dist/types/core/place-floating.d.ts +37 -0
  133. package/dist/types/index.d.ts +8 -0
  134. package/dist/types/product/assigneepicker/assigneepicker.d.ts +36 -0
  135. package/dist/types/product/assigneepicker/assigneepicker.recipe.d.ts +3 -0
  136. package/dist/types/product/assigneepicker/index.d.ts +2 -0
  137. package/dist/types/product/notesgallery/index.d.ts +2 -0
  138. package/dist/types/product/notesgallery/notesgallery.d.ts +54 -0
  139. package/dist/types/product/notesgallery/notesgallery.recipe.d.ts +3 -0
  140. package/dist/types/product/objectpagemenu/index.d.ts +1 -0
  141. package/dist/types/product/objectpagemenu/objectpagemenu.d.ts +40 -0
  142. package/dist/types/product/objectpagemenu/objectpagemenu.recipe.d.ts +17 -0
  143. package/dist/types/product/objectsettings/objectsettings.d.ts +71 -15
  144. package/dist/types/product/recordpage/index.d.ts +1 -1
  145. package/dist/types/product/recordpage/recordpage.d.ts +96 -1
  146. package/dist/types/product/recordpicker/index.d.ts +2 -0
  147. package/dist/types/product/recordpicker/recordpicker.d.ts +38 -0
  148. package/dist/types/product/recordpicker/recordpicker.recipe.d.ts +3 -0
  149. package/dist/types/product/savedviews/index.d.ts +1 -1
  150. package/dist/types/product/savedviews/savedviews.d.ts +36 -0
  151. package/dist/types/product/share/index.d.ts +2 -0
  152. package/dist/types/product/share/share.d.ts +85 -0
  153. package/dist/types/product/share/share.recipe.d.ts +3 -0
  154. package/dist/types/product/shortcutssheet/index.d.ts +1 -0
  155. package/dist/types/product/shortcutssheet/shortcutssheet.d.ts +49 -0
  156. package/dist/types/product/shortcutssheet/shortcutssheet.recipe.d.ts +3 -0
  157. package/dist/types/product/taskeditpopover/index.d.ts +2 -0
  158. package/dist/types/product/taskeditpopover/taskeditpopover.d.ts +67 -0
  159. package/dist/types/product/taskeditpopover/taskeditpopover.recipe.d.ts +3 -0
  160. package/dist/types/product/workspaceswitchermenu/index.d.ts +1 -0
  161. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.d.ts +54 -0
  162. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.recipe.d.ts +19 -0
  163. package/dist/types/tokens/contract.d.ts +13 -0
  164. package/dist/types/tokens/palette.d.ts +52 -0
  165. package/dist/types/tokens/semantic.d.ts +20 -0
  166. package/dist/workspaceswitchermenu.css +4 -0
  167. package/dist/workspaceswitchermenu.d.ts +7 -0
  168. package/dist/workspaceswitchermenu.layered.css +6 -0
  169. package/package.json +41 -1
@@ -0,0 +1,159 @@
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.TaskEditPopover = TaskEditPopover;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const lucide_react_1 = require("lucide-react");
39
+ const React = __importStar(require("react"));
40
+ const popover_1 = require("../../components/popover");
41
+ const core_1 = require("../../core");
42
+ const assigneepicker_1 = require("../assigneepicker/assigneepicker");
43
+ const recordpicker_1 = require("../recordpicker/recordpicker");
44
+ const taskeditpopover_recipe_1 = require("./taskeditpopover.recipe");
45
+ /**
46
+ * A task's "Edit task" popover -- measured live on app.attio.com's Tasks
47
+ * page: a checkbox + editable title, then a chip row (due date / assigned /
48
+ * add record), a "more actions" kebab, and Cancel/Save. Composes
49
+ * `AssigneePicker` and `RecordPicker` (both crisp#626) behind their own
50
+ * chips, using the same core floating primitives `Popover` itself is built
51
+ * on rather than `Popover` directly (its content hardcodes a 360px max
52
+ * width, well under this popover's own measured 800px).
53
+ */
54
+ function TaskEditPopover({ trigger, open: openProp, defaultOpen, onOpenChange, title, onTitleChange, done = false, onToggleDone, dueDateLabel, dueDatePicker, assignee = null, users = [], onAssigneeChange, linkedRecordCount = 0, recordCandidates = [], recordCandidatesLoading, onRecordQueryChange, onSelectRecord, menu, onCancel, onSave, headingLabel: headingLabelProp, closeLabel: closeLabelProp, dueDatePlaceholder: dueDatePlaceholderProp, assignLabel: assignLabelProp, assignedToLabel: assignedToLabelProp, addRecordLabel: addRecordLabelProp, linkedRecordLabel: linkedRecordLabelProp, moreActionsLabel: moreActionsLabelProp, cancelLabel: cancelLabelProp, cancelKeyLabel: cancelKeyLabelProp, saveLabel: saveLabelProp, className, }) {
55
+ const headingLabel = (0, core_1.useCrispMessage)(headingLabelProp, "Edit task");
56
+ const closeLabel = (0, core_1.useCrispMessage)(closeLabelProp, "Close");
57
+ const dueDatePlaceholder = (0, core_1.useCrispMessage)(dueDatePlaceholderProp, "Set due date");
58
+ const assignLabel = (0, core_1.useCrispMessage)(assignLabelProp, "Assign");
59
+ const assignedToLabel = (0, core_1.useCrispMessage)(assignedToLabelProp, "Assigned to");
60
+ const addRecordLabel = (0, core_1.useCrispMessage)(addRecordLabelProp, "Add record");
61
+ const linkedRecordLabel = (0, core_1.useCrispMessage)(linkedRecordLabelProp, linkedRecordCount === 1
62
+ ? "1 linked record"
63
+ : `${linkedRecordCount} linked records`);
64
+ const moreActionsLabel = (0, core_1.useCrispMessage)(moreActionsLabelProp, "More actions");
65
+ const cancelLabel = (0, core_1.useCrispMessage)(cancelLabelProp, "Cancel");
66
+ const cancelKeyLabel = (0, core_1.useCrispMessage)(cancelKeyLabelProp, "Esc");
67
+ const saveLabel = (0, core_1.useCrispMessage)(saveLabelProp, "Save");
68
+ const surfaceId = React.useId();
69
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
70
+ const open = openProp ?? internalOpen;
71
+ const rootRef = React.useRef(null);
72
+ const triggerRef = React.useRef(null);
73
+ const contentRef = React.useRef(null);
74
+ const [pos, setPos] = React.useState({});
75
+ const setOpen = React.useCallback((next) => {
76
+ if (openProp === undefined)
77
+ setInternalOpen(next);
78
+ onOpenChange?.(next);
79
+ }, [openProp, onOpenChange]);
80
+ const place = () => {
81
+ const el = triggerRef.current;
82
+ if (!el)
83
+ return;
84
+ const { style } = (0, core_1.placeFloating)({
85
+ rect: el.getBoundingClientRect(),
86
+ align: "start",
87
+ gap: 6,
88
+ flipBelow: 300,
89
+ anchor: el,
90
+ });
91
+ setPos(style);
92
+ };
93
+ // biome-ignore lint/correctness/useExhaustiveDependencies: reposition each time it opens
94
+ React.useLayoutEffect(() => {
95
+ if (open)
96
+ place();
97
+ }, [open]);
98
+ React.useEffect(() => {
99
+ if (!open || !contentRef.current)
100
+ return;
101
+ return (0, core_1.registerFloatingLayer)(contentRef.current);
102
+ }, [open]);
103
+ React.useEffect(() => {
104
+ if (!open)
105
+ return;
106
+ const onDown = (e) => {
107
+ const target = e.target;
108
+ if (rootRef.current?.contains(target))
109
+ return;
110
+ if (contentRef.current?.contains(target))
111
+ return;
112
+ if ((0, core_1.isInsideLaterFloatingLayer)(target, contentRef.current))
113
+ return;
114
+ setOpen(false);
115
+ onCancel?.();
116
+ };
117
+ document.addEventListener("pointerdown", onDown);
118
+ return () => document.removeEventListener("pointerdown", onDown);
119
+ }, [open, setOpen, onCancel]);
120
+ const cancel = () => {
121
+ setOpen(false);
122
+ onCancel?.();
123
+ };
124
+ const save = () => {
125
+ setOpen(false);
126
+ onSave?.();
127
+ };
128
+ const triggerEl = React.cloneElement(trigger, {
129
+ ref: triggerRef,
130
+ "aria-haspopup": "dialog",
131
+ "aria-expanded": open,
132
+ "aria-controls": open ? surfaceId : undefined,
133
+ onClick: (e) => {
134
+ trigger.props.onClick?.(e);
135
+ setOpen(!open);
136
+ },
137
+ });
138
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, style: { display: "contents" }, children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: contentRef, id: surfaceId, role: "dialog", "aria-label": headingLabel, className: (0, taskeditpopover_recipe_1.taskeditpopoverRecipe)({ className }), style: pos, onKeyDown: (e) => {
139
+ if (e.key === "Escape") {
140
+ e.preventDefault();
141
+ cancel();
142
+ }
143
+ if (e.key === "Enter" &&
144
+ e.metaKey === false &&
145
+ e.ctrlKey === false &&
146
+ e.target.tagName !== "TEXTAREA") {
147
+ // Enter saves, same as Attio's own "Save ⏎" -- but never
148
+ // steals it from a nested picker's own Enter-to-commit.
149
+ if (!(0, core_1.isInsideLaterFloatingLayer)(e.target, contentRef.current)) {
150
+ save();
151
+ }
152
+ }
153
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskeditpopover-header", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-taskeditpopover-heading", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleCheckBig, { size: 16, strokeWidth: 1.75 }), headingLabel] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-taskeditpopover-close", "aria-label": closeLabel, onClick: cancel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskeditpopover-body", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", className: "crisp-taskeditpopover-check", checked: done, "aria-label": title, onChange: (e) => onToggleDone?.(e.target.checked) }), (0, jsx_runtime_1.jsx)("input", { type: "text", className: "crisp-taskeditpopover-title", value: title, onChange: (e) => onTitleChange?.(e.target.value) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskeditpopover-footer", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskeditpopover-chips", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", "aria-label": dueDatePlaceholder, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 14, strokeWidth: 1.75 }), dueDateLabel ?? dueDatePlaceholder] }), children: dueDatePicker }), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", "aria-label": assignedToLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "@" }), assignee != null
154
+ ? `${assignedToLabel} ${assignee.name}`
155
+ : assignLabel] }), children: (0, jsx_runtime_1.jsx)(assigneepicker_1.AssigneePicker, { users: users, assignee: assignee, onAssigneeChange: onAssigneeChange }) }), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", "aria-label": addRecordLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "\u2197" }), linkedRecordCount > 0
156
+ ? linkedRecordLabel
157
+ : addRecordLabel] }), children: (0, jsx_runtime_1.jsx)(recordpicker_1.RecordPicker, { records: recordCandidates, loading: recordCandidatesLoading, onQueryChange: onRecordQueryChange, onSelect: onSelectRecord }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskeditpopover-actions", children: [menu != null && ((0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "end", "aria-label": moreActionsLabel, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-taskeditpopover-kebab", "aria-label": moreActionsLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: menu })), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-taskeditpopover-cancel", onClick: cancel, children: [cancelLabel, (0, jsx_runtime_1.jsx)("kbd", { className: "crisp-taskeditpopover-key", children: cancelKeyLabel })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-taskeditpopover-save", onClick: save, children: [saveLabel, (0, jsx_runtime_1.jsx)("kbd", { className: "crisp-taskeditpopover-key", children: "\u23CE" })] })] })] })] }) }))] }));
158
+ }
159
+ TaskEditPopover.displayName = "TaskEditPopover";
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.taskeditpopoverRecipe = void 0;
4
+ const core_1 = require("../../core");
5
+ const tokens_1 = require("../../tokens");
6
+ // The "Edit task" popover -- measured live on app.attio.com's Tasks page
7
+ // (click a task row): an 800px-wide floating card, radius 12, a single
8
+ // subtle 1px ring shadow (no blur/offset layer -- lighter than
9
+ // `elevation.floating`, which every other crisp overlay uses; this one
10
+ // really does measure as just the ring, confirmed by walking every
11
+ // ancestor up to the floating root). Same core floating primitives
12
+ // `Popover`/`savedviews` already use directly (this doesn't reuse
13
+ // `Popover` itself: its own `.crisp-popover-content` hardcodes a
14
+ // max-width of min(360px, 92vw), far under this popover's measured width).
15
+ //
16
+ // Three rows: a 44px header (icon+"Edit task", a bottom hairline divider,
17
+ // a close button), a body row (checkbox + editable title), and a 44px
18
+ // footer (the due-date/assigned/add-record chip row on the left, the
19
+ // kebab + Cancel/Save on the right). The chip row's own 10px/400 muted
20
+ // caption size and the footer buttons' matching 10px labels don't fit any
21
+ // existing text-scale token (closest is `text.xs` at 11px) -- raw px per
22
+ // Golden Rule 3's "genuinely intrinsic size" exception, same as
23
+ // `dialog.recipe.ts`'s own submit-key glyphs.
24
+ const config = (0, core_1.defineRecipe)({
25
+ name: "taskeditpopover",
26
+ // Portaled root: position/z-index/the open transition live here as raw
27
+ // literal CSS (not `base`, which lightningcss nests under `.${prefixed}`
28
+ // at BUILD time -- fine for the plain declarations below, but this exact
29
+ // selector text has to exist as source text for `spec-check.mjs`'s
30
+ // overlay-escape/animation scan, the same reason Popover/Menu/Select
31
+ // declare their own portaled root in `globalCss` rather than `base`).
32
+ globalCss: `.crisp-taskeditpopover { position: fixed; z-index: 70; pointer-events: auto; opacity: 1; transform: scale(1); transition: opacity 0.08s ease, transform 0.08s ease; @starting-style { opacity: 0; transform: scale(0.97) translateY(-4px); } }`,
33
+ base: `display: flex; flex-direction: column; box-sizing: border-box; width: 800px; max-width: 92vw; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07); font-family: ${tokens_1.contract.font.sans};
34
+ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
35
+
36
+ & .crisp-taskeditpopover-header { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["2"]}; height: 44px; box-sizing: border-box; padding: 0 ${tokens_1.contract.space["2"]} 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
37
+ & .crisp-taskeditpopover-heading { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
38
+ & .crisp-taskeditpopover-heading svg { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
39
+ & .crisp-taskeditpopover-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; }
40
+ & .crisp-taskeditpopover-close:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
41
+
42
+ & .crisp-taskeditpopover-body { display: flex; align-items: center; gap: ${tokens_1.contract.space["2.5"]}; min-height: 52px; box-sizing: border-box; padding: ${tokens_1.contract.space["3"]}; }
43
+ & .crisp-taskeditpopover-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: ${tokens_1.contract.radius.full}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); cursor: pointer; }
44
+ & .crisp-taskeditpopover-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 30%, transparent); }
45
+ & .crisp-taskeditpopover-check:checked { background: ${tokens_1.contract.bgBrandSolid} center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
46
+ & .crisp-taskeditpopover-title { flex: 1; min-width: 0; border: none; border-radius: ${tokens_1.contract.radius.sm}; outline: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
47
+ & .crisp-taskeditpopover-title:focus-visible { outline: 2px solid ${tokens_1.contract.strokeFocusRing}; outline-offset: 2px; }
48
+
49
+ & .crisp-taskeditpopover-footer { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["2"]}; height: 44px; box-sizing: border-box; padding: 0 ${tokens_1.contract.space["2"]} 0 ${tokens_1.contract.space["3"]}; }
50
+ & .crisp-taskeditpopover-chips { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; min-width: 0; }
51
+ & .crisp-taskeditpopover-chip { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; height: 28px; box-sizing: border-box; padding: 4px 8px 4px 6px; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); font-family: inherit; font-size: 10px; white-space: nowrap; cursor: pointer; }
52
+ & .crisp-taskeditpopover-chip svg { flex-shrink: 0; }
53
+ & .crisp-taskeditpopover-chip:hover { background: ${tokens_1.contract.bgRaised}; }
54
+
55
+ & .crisp-taskeditpopover-actions { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; }
56
+ & .crisp-taskeditpopover-kebab { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 6px; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; cursor: pointer; }
57
+ & .crisp-taskeditpopover-kebab:hover { background: ${tokens_1.contract.bgRaised}; }
58
+ & .crisp-taskeditpopover-cancel { display: inline-flex; align-items: center; gap: 6px; height: 28px; box-sizing: border-box; padding: 4px 6px 4px 8px; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: 10px; cursor: pointer; }
59
+ & .crisp-taskeditpopover-cancel:hover { background: ${tokens_1.contract.bgRaised}; }
60
+ & .crisp-taskeditpopover-save { display: inline-flex; align-items: center; gap: 6px; height: 28px; box-sizing: border-box; padding: 4px 6px 4px 8px; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgBrandSolid}; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-size: 10px; cursor: pointer; }
61
+ & .crisp-taskeditpopover-save:hover { background: ${tokens_1.contract.bgBrandSolidPressed}; }
62
+ & .crisp-taskeditpopover-key { color: color-mix(in srgb, currentColor 55%, transparent); font-size: 10px; }`,
63
+ variants: {},
64
+ defaultVariants: {},
65
+ compoundVariants: [],
66
+ });
67
+ exports.taskeditpopoverRecipe = (0, core_1.resolveRecipe)(config);
68
+ exports.default = config;
@@ -0,0 +1,17 @@
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("./workspaceswitchermenu"), exports);
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkspaceSwitcherMenu = WorkspaceSwitcherMenu;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const lucide_react_1 = require("lucide-react");
6
+ const avatar_1 = require("../../components/avatar");
7
+ const menu_1 = require("../../components/menu");
8
+ const core_1 = require("../../core");
9
+ /**
10
+ * The FIXED content of Attio's own workspace-switcher popover (crisp#632) —
11
+ * live-measured off app.attio.com/techpartners: each workspace as a
12
+ * checked/unchecked row, then New workspace, then Account settings /
13
+ * Workspace settings, then Invite members / Upgrade Attio / Refer another
14
+ * team, then Apps and integrations, then Sign out — each group separated by
15
+ * a hairline, matching a 280px-wide popover.
16
+ *
17
+ * This is CONTENT, not a component with its own trigger/popover: pass it as
18
+ * `Sidebar`'s own `workspaceMenu` prop (crisp#415 already builds the real
19
+ * menu-button trigger/popover machinery around whatever children it's
20
+ * given — the workspace switcher itself has no fixed content of its own by
21
+ * design, since which settings exist is the consumer's; this is that fixed
22
+ * content, ready-made, the same way `defaultCommandItems` is for `Command`).
23
+ */
24
+ function WorkspaceSwitcherMenu({ workspaces, currentWorkspaceId, onSelectWorkspace, onNewWorkspace, onAccountSettings, onWorkspaceSettings, onInviteMembers, onUpgradeAttio, onReferAnotherTeam, onAppsAndIntegrations, onSignOut, newWorkspaceLabel, accountSettingsLabel, workspaceSettingsLabel, inviteMembersLabel, upgradeAttioLabel, referAnotherTeamLabel, appsAndIntegrationsLabel, signOutLabel, }) {
25
+ const newWorkspace = (0, core_1.useCrispMessage)(newWorkspaceLabel, "New workspace");
26
+ const accountSettings = (0, core_1.useCrispMessage)(accountSettingsLabel, "Account settings");
27
+ const workspaceSettings = (0, core_1.useCrispMessage)(workspaceSettingsLabel, "Workspace settings");
28
+ const inviteMembers = (0, core_1.useCrispMessage)(inviteMembersLabel, "Invite members");
29
+ const upgradeAttio = (0, core_1.useCrispMessage)(upgradeAttioLabel, "Upgrade Attio");
30
+ const referAnotherTeam = (0, core_1.useCrispMessage)(referAnotherTeamLabel, "Refer another team");
31
+ const appsAndIntegrations = (0, core_1.useCrispMessage)(appsAndIntegrationsLabel, "Apps and integrations");
32
+ const signOut = (0, core_1.useCrispMessage)(signOutLabel, "Sign out");
33
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [workspaces.map((ws) => {
34
+ const checked = ws.id === currentWorkspaceId;
35
+ return ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: ws.avatarTone, children: ws.avatarLabel }), trailingIcon: checked ? ((0, jsx_runtime_1.jsx)(lucide_react_1.CircleCheck, { size: 16, strokeWidth: 1.75, className: "crisp-workspaceswitchermenu-check" })) : undefined, "aria-checked": checked, onSelect: () => onSelectWorkspace?.(ws.id), children: ws.name }, ws.id));
36
+ }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16, strokeWidth: 1.75 }), onSelect: onNewWorkspace, children: newWorkspace }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.User, { size: 16, strokeWidth: 1.75 }), onSelect: onAccountSettings, children: accountSettings }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { size: 16, strokeWidth: 1.75 }), onSelect: onWorkspaceSettings, children: workspaceSettings }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.UserPlus, { size: 16, strokeWidth: 1.75 }), onSelect: onInviteMembers, children: inviteMembers }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.CircleArrowUp, { size: 16, strokeWidth: 1.75 }), onSelect: onUpgradeAttio, children: upgradeAttio }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.HeartHandshake, { size: 16, strokeWidth: 1.75 }), onSelect: onReferAnotherTeam, children: referAnotherTeam }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Grid2x2, { size: 16, strokeWidth: 1.75 }), onSelect: onAppsAndIntegrations, children: appsAndIntegrations }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { "data-rtl-flip": "", size: 16, strokeWidth: 1.75 }), onSelect: onSignOut, children: signOut })] }));
37
+ }
38
+ WorkspaceSwitcherMenu.displayName = "WorkspaceSwitcherMenu";
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.workspaceswitchermenuRecipe = void 0;
4
+ const core_1 = require("../../core");
5
+ const tokens_1 = require("../../tokens");
6
+ /**
7
+ * `WorkspaceSwitcherMenu` owns almost no styling of its own — every visible
8
+ * pixel besides the two rules below is `Menu`/`MenuItem`'s own
9
+ * already-measured recipe (crisp#632):
10
+ * - The RTL mirror for its "Sign out" icon: `LogOut` is treated as
11
+ * direction-bearing (`build/spec-check.mjs`'s own DIRECTIONAL set), so
12
+ * it's marked and mirrored the same way ObjectPageMenu's Notifications
13
+ * chevron is (crisp#616) — see that file's identical recipe for why this
14
+ * is a plain absolute selector, not `${rtlFlipCss}`'s `&`-nesting form.
15
+ * - The current-workspace checkmark's colour: `.crisp-menu-item svg` already
16
+ * sets every menu icon to `fgMuted` (menu.recipe.ts), which is deliberate
17
+ * there but wrong for THIS one glyph — Attio's own is brand-coloured, not
18
+ * muted, to read as a status rather than a plain icon. `svg` doubled in
19
+ * the selector (not just the class) so it outranks that rule under equal
20
+ * source order instead of depending on file concatenation order.
21
+ */
22
+ const config = (0, core_1.defineRecipe)({
23
+ name: "workspaceswitchermenu",
24
+ base: "",
25
+ globalCss: `
26
+ [dir="rtl"] .crisp-menu-item svg[data-rtl-flip] { transform: scaleX(-1); }
27
+ .crisp-menu-item svg.crisp-workspaceswitchermenu-check { color: ${tokens_1.contract.fgBrandContrast}; }
28
+ `,
29
+ variants: {},
30
+ defaultVariants: {},
31
+ compoundVariants: [],
32
+ });
33
+ exports.workspaceswitchermenuRecipe = (0, core_1.resolveRecipe)(config);
34
+ exports.default = config;
@@ -277,4 +277,31 @@ exports.palette = {
277
277
  // s3 (shadow.yaml) -- the listbox's single-layer floating shadow, in place
278
278
  // of crisp's own multi-layer `elevation.floating` for this component only.
279
279
  selectShadow: { light: "#0000001f", dark: "#000000cc" },
280
+ // your-moon/crisp#998: the 12-colour tag/option-colour palette a workspace
281
+ // admin picks for a Select/MultiSelect option, measured live off
282
+ // app.attio.com's Categories multi-select field (table-cell/column-editor
283
+ // pill, not the larger tinted-text activity-feed variant -- see
284
+ // ~/grc-parity/attio-tag-palette.md). All 12 are near-white pastels with
285
+ // flat black text regardless of which one -- Attio's theme toggle is
286
+ // off-limits (same restriction every other raw entry in this file notes),
287
+ // so no dark variant was reachable; light is reused for dark, same as
288
+ // `cyan600`/`purple600`/`orange600` above already do for the same reason.
289
+ // Numbered 1-12 rather than named: Attio assigns them by an internal hash
290
+ // of the option's id, not by any hue name a consumer would recognise.
291
+ tagSwatch1: { light: "#fff3cc", dark: "#fff3cc" },
292
+ tagSwatch2: { light: "#f4fbcb", dark: "#f4fbcb" },
293
+ tagSwatch3: { light: "#fdf7c4", dark: "#fdf7c4" },
294
+ tagSwatch4: { light: "#feecf3", dark: "#feecf3" },
295
+ tagSwatch5: { light: "#e0fced", dark: "#e0fced" },
296
+ tagSwatch6: { light: "#ddf7ff", dark: "#ddf7ff" },
297
+ tagSwatch7: { light: "#f6f7f7", dark: "#f6f7f7" },
298
+ tagSwatch8: { light: "#ffebeb", dark: "#ffebeb" },
299
+ tagSwatch9: { light: "#feeee1", dark: "#feeee1" },
300
+ tagSwatch10: { light: "#e5eeff", dark: "#e5eeff" },
301
+ tagSwatch11: { light: "#f5f0ff", dark: "#f5f0ff" },
302
+ tagSwatch12: { light: "#fdedff", dark: "#fdedff" },
303
+ // measured alongside the palette above: flat opaque black, not `ink`
304
+ // (#101112) -- a genuinely different, slightly darker hex Attio uses only
305
+ // for this one pill's text, regardless of which of the 12 backgrounds.
306
+ tagSwatchInk: { light: "#000000", dark: "#000000" },
280
307
  };
@@ -129,4 +129,24 @@ exports.semantic = {
129
129
  strokeSelectDivider: "selectDivider",
130
130
  fgSelectPlaceholder: "selectPlaceholder",
131
131
  shadowSelect: "selectShadow",
132
+ /** your-moon/crisp#998: a Select/MultiSelect option's fixed tag colour
133
+ * (`swatch`, 1-12) -- see palette.ts's own comment on `tagSwatch1` for
134
+ * the measurement. Distinct from `Select`'s existing `color` (an
135
+ * arbitrary-CSS status dot) and `MultiSelect`'s existing `tone` (crisp's
136
+ * own semantic 7-tone chip family): this is Attio's own enumerable,
137
+ * workspace-admin-assigned palette, reproduced verbatim rather than
138
+ * mapped onto either existing mechanism. */
139
+ bgTagSwatch1: "tagSwatch1",
140
+ bgTagSwatch2: "tagSwatch2",
141
+ bgTagSwatch3: "tagSwatch3",
142
+ bgTagSwatch4: "tagSwatch4",
143
+ bgTagSwatch5: "tagSwatch5",
144
+ bgTagSwatch6: "tagSwatch6",
145
+ bgTagSwatch7: "tagSwatch7",
146
+ bgTagSwatch8: "tagSwatch8",
147
+ bgTagSwatch9: "tagSwatch9",
148
+ bgTagSwatch10: "tagSwatch10",
149
+ bgTagSwatch11: "tagSwatch11",
150
+ bgTagSwatch12: "tagSwatch12",
151
+ fgTagSwatch: "tagSwatchInk",
132
152
  };
@@ -290,14 +290,29 @@
290
290
  Avatar's own already-shipped shape="rounded" value, not a new number.
291
291
  shape="circle" reuses Avatar's own shape="circle" value (radius.full)
292
292
  the same way, measured live on a real Attio person avatar. */
293
- & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
293
+ & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-tone); background: var(--crisp-bg-brand-solid); }
294
294
  & .crisp-datatable-avatar[data-shape="circle"] { border-radius: var(--crisp-radius-full); }
295
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
296
- & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
297
- & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
298
- & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
299
- & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
300
- & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
295
+ /* your-moon/crisp#891: every data-tone override painted ONLY a
296
+ background, inheriting the base rule's white fgOnBrand -- white
297
+ clears AA against none of these seven fills (brand 3.23:1, success
298
+ 3.96, warning 4.12, danger 3.76, purple 3.58, cyan 2.30, orange
299
+ 2.77). fgOnTone (ink, #101112 -- semantic.ts's own comment: "dark ink
300
+ clears AA on every tone in both themes") clears 4.5:1+ on every fill
301
+ here with a comfortable margin (brand 5.85, success 4.77/6.29,
302
+ danger 5.03/6.19, purple 5.27, cyan 8.23, orange 6.83, light/dark),
303
+ so the base rule's colour moves there and every override keeps it --
304
+ EXCEPT warning, whose raw fill (bgWarning) only clears 4.59:1 in
305
+ light, too thin a margin to rely on: it takes bgWarningSolid instead,
306
+ the same solid-warning-plus-ink pair Badge's own solid variant
307
+ already established (13.98/7.83), for the same reason (see
308
+ tokens/palette.ts's amberSolid comment). No background changes
309
+ otherwise -- the fills themselves are unaffected, only the text. */
310
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); color: var(--crisp-fg-on-tone); }
311
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning-solid); color: var(--crisp-fg-on-tone); }
312
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); color: var(--crisp-fg-on-tone); }
313
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); color: var(--crisp-fg-on-tone); }
314
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); color: var(--crisp-fg-on-tone); }
315
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); color: var(--crisp-fg-on-tone); }
301
316
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
302
317
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
303
318
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
@@ -308,6 +323,24 @@
308
323
  & .crisp-datatable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); }
309
324
  & .crisp-datatable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); }
310
325
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
326
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured
327
+ live, see tokens/palette.ts's tagSwatch1 comment) -- flat black
328
+ text, no ring, unlike the tone family above. 7px radius/10px text
329
+ are measured literals (no matching step in the shared scale, same
330
+ as Select's own .crisp-select-swatch). */
331
+ & .crisp-datatable-swatch { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: 7px; font-size: 10px; font-weight: var(--crisp-weight-regular); white-space: nowrap; color: var(--crisp-fg-tag-swatch); }
332
+ & .crisp-datatable-swatch[data-swatch="1"] { background: var(--crisp-bg-tag-swatch1); }
333
+ & .crisp-datatable-swatch[data-swatch="2"] { background: var(--crisp-bg-tag-swatch2); }
334
+ & .crisp-datatable-swatch[data-swatch="3"] { background: var(--crisp-bg-tag-swatch3); }
335
+ & .crisp-datatable-swatch[data-swatch="4"] { background: var(--crisp-bg-tag-swatch4); }
336
+ & .crisp-datatable-swatch[data-swatch="5"] { background: var(--crisp-bg-tag-swatch5); }
337
+ & .crisp-datatable-swatch[data-swatch="6"] { background: var(--crisp-bg-tag-swatch6); }
338
+ & .crisp-datatable-swatch[data-swatch="7"] { background: var(--crisp-bg-tag-swatch7); }
339
+ & .crisp-datatable-swatch[data-swatch="8"] { background: var(--crisp-bg-tag-swatch8); }
340
+ & .crisp-datatable-swatch[data-swatch="9"] { background: var(--crisp-bg-tag-swatch9); }
341
+ & .crisp-datatable-swatch[data-swatch="10"] { background: var(--crisp-bg-tag-swatch10); }
342
+ & .crisp-datatable-swatch[data-swatch="11"] { background: var(--crisp-bg-tag-swatch11); }
343
+ & .crisp-datatable-swatch[data-swatch="12"] { background: var(--crisp-bg-tag-swatch12); }
311
344
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
312
345
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
313
346
  /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
@@ -291,14 +291,29 @@
291
291
  Avatar's own already-shipped shape="rounded" value, not a new number.
292
292
  shape="circle" reuses Avatar's own shape="circle" value (radius.full)
293
293
  the same way, measured live on a real Attio person avatar. */
294
- & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
294
+ & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-tone); background: var(--crisp-bg-brand-solid); }
295
295
  & .crisp-datatable-avatar[data-shape="circle"] { border-radius: var(--crisp-radius-full); }
296
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
297
- & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
298
- & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
299
- & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
300
- & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
301
- & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
296
+ /* your-moon/crisp#891: every data-tone override painted ONLY a
297
+ background, inheriting the base rule's white fgOnBrand -- white
298
+ clears AA against none of these seven fills (brand 3.23:1, success
299
+ 3.96, warning 4.12, danger 3.76, purple 3.58, cyan 2.30, orange
300
+ 2.77). fgOnTone (ink, #101112 -- semantic.ts's own comment: "dark ink
301
+ clears AA on every tone in both themes") clears 4.5:1+ on every fill
302
+ here with a comfortable margin (brand 5.85, success 4.77/6.29,
303
+ danger 5.03/6.19, purple 5.27, cyan 8.23, orange 6.83, light/dark),
304
+ so the base rule's colour moves there and every override keeps it --
305
+ EXCEPT warning, whose raw fill (bgWarning) only clears 4.59:1 in
306
+ light, too thin a margin to rely on: it takes bgWarningSolid instead,
307
+ the same solid-warning-plus-ink pair Badge's own solid variant
308
+ already established (13.98/7.83), for the same reason (see
309
+ tokens/palette.ts's amberSolid comment). No background changes
310
+ otherwise -- the fills themselves are unaffected, only the text. */
311
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); color: var(--crisp-fg-on-tone); }
312
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning-solid); color: var(--crisp-fg-on-tone); }
313
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); color: var(--crisp-fg-on-tone); }
314
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); color: var(--crisp-fg-on-tone); }
315
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); color: var(--crisp-fg-on-tone); }
316
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); color: var(--crisp-fg-on-tone); }
302
317
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
303
318
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
304
319
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
@@ -309,6 +324,24 @@
309
324
  & .crisp-datatable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); }
310
325
  & .crisp-datatable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); }
311
326
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
327
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured
328
+ live, see tokens/palette.ts's tagSwatch1 comment) -- flat black
329
+ text, no ring, unlike the tone family above. 7px radius/10px text
330
+ are measured literals (no matching step in the shared scale, same
331
+ as Select's own .crisp-select-swatch). */
332
+ & .crisp-datatable-swatch { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: 7px; font-size: 10px; font-weight: var(--crisp-weight-regular); white-space: nowrap; color: var(--crisp-fg-tag-swatch); }
333
+ & .crisp-datatable-swatch[data-swatch="1"] { background: var(--crisp-bg-tag-swatch1); }
334
+ & .crisp-datatable-swatch[data-swatch="2"] { background: var(--crisp-bg-tag-swatch2); }
335
+ & .crisp-datatable-swatch[data-swatch="3"] { background: var(--crisp-bg-tag-swatch3); }
336
+ & .crisp-datatable-swatch[data-swatch="4"] { background: var(--crisp-bg-tag-swatch4); }
337
+ & .crisp-datatable-swatch[data-swatch="5"] { background: var(--crisp-bg-tag-swatch5); }
338
+ & .crisp-datatable-swatch[data-swatch="6"] { background: var(--crisp-bg-tag-swatch6); }
339
+ & .crisp-datatable-swatch[data-swatch="7"] { background: var(--crisp-bg-tag-swatch7); }
340
+ & .crisp-datatable-swatch[data-swatch="8"] { background: var(--crisp-bg-tag-swatch8); }
341
+ & .crisp-datatable-swatch[data-swatch="9"] { background: var(--crisp-bg-tag-swatch9); }
342
+ & .crisp-datatable-swatch[data-swatch="10"] { background: var(--crisp-bg-tag-swatch10); }
343
+ & .crisp-datatable-swatch[data-swatch="11"] { background: var(--crisp-bg-tag-swatch11); }
344
+ & .crisp-datatable-swatch[data-swatch="12"] { background: var(--crisp-bg-tag-swatch12); }
312
345
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
313
346
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
314
347
  /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
@@ -132,19 +132,24 @@ const spec = defineSpec({
132
132
  // shape="icon" already does for every other size, so it needs no
133
133
  // extra rule), 4px gap between multiple children.
134
134
  //
135
- // The label itself is NOT 14px/#682's tracking -- getComputedStyle on
136
- // the live button (fonts loaded, DPR 2, zero zoom/transform anywhere
137
- // in the ancestor chain) reads 10px/regular/15px line-height/normal
138
- // tracking, independently matching `recordpanel.recipe.ts`'s own
139
- // "View all" measurement (10px/400) elsewhere in this codebase. 10px
140
- // sits below the text scale's 11px floor, so it's a literal here, the
141
- // same as recordpanel's.
135
+ // Follow-up correction (still #688): the label is 12px/500(medium)/
136
+ // 16px line-height/normal tracking -- NOT #682's 14px button tracking,
137
+ // and NOT the 10px/regular/15px this file shipped with first. That
138
+ // first pass read getComputedStyle on the `<button>` element itself;
139
+ // Attio's actual label lives in an inner `<div>` that overrides
140
+ // font-size/weight/line-height on top of it (confirmed by walking the
141
+ // full child tree, on "View all" AND "Today, <date>", both Home and
142
+ // the savedviews toolbar's own "Save for everyone") -- the outer
143
+ // button's own 10px/400/15px is real but never what's on screen.
144
+ // 12px is already `c.text.sm` and 500 is already the base's own
145
+ // default `fontWeight` (no override needed for either); only
146
+ // line-height and tracking still diverge from the base's 20px/
147
+ // -0.07px and need their own values here.
142
148
  xs: {
143
149
  height: "20px",
144
150
  paddingInline: c.space["1.5"],
145
- fontSize: "10px",
146
- fontWeight: c.weight.regular,
147
- lineHeight: "15px",
151
+ fontSize: c.text.sm,
152
+ lineHeight: "16px",
148
153
  letterSpacing: "normal",
149
154
  borderRadius: c.radius.sm,
150
155
  gap: c.space["1"],
@@ -390,14 +390,17 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
390
390
  const tags = Array.isArray(value) ? value : [];
391
391
  if (!tags.length)
392
392
  return empty;
393
- return (_jsx("span", { className: "crisp-datatable-tags", children: tags.map((t) => (_jsx("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[t] ?? toneFor(t), children: t }, t))) }));
393
+ return (_jsx("span", { className: "crisp-datatable-tags", children: tags.map((t) => col.swatches?.[t] ? (_jsx("span", { className: "crisp-datatable-swatch", "data-swatch": col.swatches[t], children: t }, t)) : (_jsx("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[t] ?? toneFor(t), children: t }, t))) }));
394
394
  }
395
395
  case "status": {
396
396
  const s = asText(value);
397
397
  if (!s)
398
398
  return empty;
399
- // tone is looked up by the RAW status, so formatting the label (casing,
400
- // translation) can never silently repaint the chip
399
+ // tone/swatch is looked up by the RAW status, so formatting the label
400
+ // (casing, translation) can never silently repaint the chip
401
+ if (col.swatches?.[s]) {
402
+ return (_jsx("span", { className: "crisp-datatable-swatch", "data-swatch": col.swatches[s], children: display || s }));
403
+ }
401
404
  return (_jsx("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[s] ?? toneFor(s), children: display || s }));
402
405
  }
403
406
  case "rating": {