@crisp-ui-kit/crisp 0.57.2 → 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 (200) 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 +3 -0
  5. package/dist/button.d.ts +2 -2
  6. package/dist/button.layered.css +3 -0
  7. package/dist/chip.css +1 -1
  8. package/dist/chip.layered.css +1 -1
  9. package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
  10. package/dist/cjs/components/button/button.recipe.js +33 -0
  11. package/dist/cjs/components/chip/chip.recipe.js +3 -1
  12. package/dist/cjs/components/datatable/datatable.js +6 -3
  13. package/dist/cjs/components/datatable/datatable.recipe.js +45 -6
  14. package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
  15. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
  16. package/dist/cjs/components/menu/menu.js +65 -9
  17. package/dist/cjs/components/multiselect/multiselect.js +3 -0
  18. package/dist/cjs/components/multiselect/multiselect.recipe.js +18 -0
  19. package/dist/cjs/components/select/select.js +2 -2
  20. package/dist/cjs/components/select/select.recipe.js +19 -0
  21. package/dist/cjs/components/table/table.recipe.js +7 -1
  22. package/dist/cjs/core/floating-portal.js +7 -4
  23. package/dist/cjs/core/index.js +2 -1
  24. package/dist/cjs/core/place-floating.js +38 -0
  25. package/dist/cjs/index.js +8 -0
  26. package/dist/cjs/product/assigneepicker/assigneepicker.js +127 -0
  27. package/dist/cjs/product/assigneepicker/assigneepicker.recipe.js +36 -0
  28. package/dist/cjs/product/assigneepicker/index.js +7 -0
  29. package/dist/cjs/product/inbox/inbox.js +11 -3
  30. package/dist/cjs/product/notesgallery/index.js +7 -0
  31. package/dist/cjs/product/notesgallery/notesgallery.js +80 -0
  32. package/dist/cjs/product/notesgallery/notesgallery.recipe.js +46 -0
  33. package/dist/cjs/product/objectpagemenu/index.js +17 -0
  34. package/dist/cjs/product/objectpagemenu/objectpagemenu.js +26 -0
  35. package/dist/cjs/product/objectpagemenu/objectpagemenu.recipe.js +30 -0
  36. package/dist/cjs/product/objectsettings/objectsettings.js +107 -14
  37. package/dist/cjs/product/recordpage/index.js +2 -1
  38. package/dist/cjs/product/recordpage/recordpage.js +112 -7
  39. package/dist/cjs/product/recordpage/recordpage.recipe.js +63 -0
  40. package/dist/cjs/product/recordpicker/index.js +7 -0
  41. package/dist/cjs/product/recordpicker/recordpicker.js +100 -0
  42. package/dist/cjs/product/recordpicker/recordpicker.recipe.js +36 -0
  43. package/dist/cjs/product/savedviews/index.js +2 -1
  44. package/dist/cjs/product/savedviews/savedviews.js +22 -0
  45. package/dist/cjs/product/savedviews/savedviews.recipe.js +47 -0
  46. package/dist/cjs/product/share/index.js +7 -0
  47. package/dist/cjs/product/share/share.js +179 -0
  48. package/dist/cjs/product/share/share.recipe.js +114 -0
  49. package/dist/cjs/product/shortcutssheet/index.js +17 -0
  50. package/dist/cjs/product/shortcutssheet/shortcutssheet.js +125 -0
  51. package/dist/cjs/product/shortcutssheet/shortcutssheet.recipe.js +50 -0
  52. package/dist/cjs/product/taskeditpopover/index.js +7 -0
  53. package/dist/cjs/product/taskeditpopover/taskeditpopover.js +159 -0
  54. package/dist/cjs/product/taskeditpopover/taskeditpopover.recipe.js +68 -0
  55. package/dist/cjs/product/workspaceswitchermenu/index.js +17 -0
  56. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.js +38 -0
  57. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +34 -0
  58. package/dist/cjs/tokens/palette.js +27 -0
  59. package/dist/cjs/tokens/semantic.js +20 -0
  60. package/dist/datatable.css +50 -11
  61. package/dist/datatable.layered.css +50 -11
  62. package/dist/datepickerfield.css +8 -2
  63. package/dist/datepickerfield.d.ts +2 -2
  64. package/dist/datepickerfield.layered.css +8 -2
  65. package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
  66. package/dist/esm/components/button/button.recipe.js +33 -0
  67. package/dist/esm/components/chip/chip.recipe.js +3 -1
  68. package/dist/esm/components/datatable/datatable.js +6 -3
  69. package/dist/esm/components/datatable/datatable.recipe.js +45 -6
  70. package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
  71. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
  72. package/dist/esm/components/menu/menu.js +64 -8
  73. package/dist/esm/components/multiselect/multiselect.js +3 -0
  74. package/dist/esm/components/multiselect/multiselect.recipe.js +18 -0
  75. package/dist/esm/components/select/select.js +2 -2
  76. package/dist/esm/components/select/select.recipe.js +19 -0
  77. package/dist/esm/components/table/table.recipe.js +7 -1
  78. package/dist/esm/core/floating-portal.js +7 -4
  79. package/dist/esm/core/index.js +1 -1
  80. package/dist/esm/core/place-floating.js +37 -0
  81. package/dist/esm/index.js +8 -0
  82. package/dist/esm/product/assigneepicker/assigneepicker.js +91 -0
  83. package/dist/esm/product/assigneepicker/assigneepicker.recipe.js +33 -0
  84. package/dist/esm/product/assigneepicker/index.js +2 -0
  85. package/dist/esm/product/inbox/inbox.js +11 -3
  86. package/dist/esm/product/notesgallery/index.js +2 -0
  87. package/dist/esm/product/notesgallery/notesgallery.js +44 -0
  88. package/dist/esm/product/notesgallery/notesgallery.recipe.js +43 -0
  89. package/dist/esm/product/objectpagemenu/index.js +1 -0
  90. package/dist/esm/product/objectpagemenu/objectpagemenu.js +23 -0
  91. package/dist/esm/product/objectpagemenu/objectpagemenu.recipe.js +27 -0
  92. package/dist/esm/product/objectsettings/objectsettings.js +106 -13
  93. package/dist/esm/product/recordpage/index.js +1 -1
  94. package/dist/esm/product/recordpage/recordpage.js +112 -8
  95. package/dist/esm/product/recordpage/recordpage.recipe.js +63 -0
  96. package/dist/esm/product/recordpicker/index.js +2 -0
  97. package/dist/esm/product/recordpicker/recordpicker.js +64 -0
  98. package/dist/esm/product/recordpicker/recordpicker.recipe.js +33 -0
  99. package/dist/esm/product/savedviews/index.js +1 -1
  100. package/dist/esm/product/savedviews/savedviews.js +22 -1
  101. package/dist/esm/product/savedviews/savedviews.recipe.js +47 -0
  102. package/dist/esm/product/share/index.js +2 -0
  103. package/dist/esm/product/share/share.js +143 -0
  104. package/dist/esm/product/share/share.recipe.js +111 -0
  105. package/dist/esm/product/shortcutssheet/index.js +1 -0
  106. package/dist/esm/product/shortcutssheet/shortcutssheet.js +88 -0
  107. package/dist/esm/product/shortcutssheet/shortcutssheet.recipe.js +47 -0
  108. package/dist/esm/product/taskeditpopover/index.js +2 -0
  109. package/dist/esm/product/taskeditpopover/taskeditpopover.js +123 -0
  110. package/dist/esm/product/taskeditpopover/taskeditpopover.recipe.js +65 -0
  111. package/dist/esm/product/workspaceswitchermenu/index.js +1 -0
  112. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.js +35 -0
  113. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +31 -0
  114. package/dist/esm/tokens/palette.js +27 -0
  115. package/dist/esm/tokens/semantic.js +20 -0
  116. package/dist/multiselect.css +18 -0
  117. package/dist/multiselect.layered.css +18 -0
  118. package/dist/native/tokens.d.ts +1 -1
  119. package/dist/native/tokens.js +56 -4
  120. package/dist/notesgallery.css +15 -0
  121. package/dist/notesgallery.d.ts +7 -0
  122. package/dist/notesgallery.layered.css +17 -0
  123. package/dist/objectpagemenu.css +3 -0
  124. package/dist/objectpagemenu.d.ts +7 -0
  125. package/dist/objectpagemenu.layered.css +5 -0
  126. package/dist/recordpage.css +59 -0
  127. package/dist/recordpage.layered.css +59 -0
  128. package/dist/recordpicker.css +13 -0
  129. package/dist/recordpicker.d.ts +7 -0
  130. package/dist/recordpicker.layered.css +15 -0
  131. package/dist/savedviews.css +47 -0
  132. package/dist/savedviews.layered.css +47 -0
  133. package/dist/select.css +19 -0
  134. package/dist/select.layered.css +19 -0
  135. package/dist/share.css +84 -0
  136. package/dist/share.d.ts +7 -0
  137. package/dist/share.layered.css +86 -0
  138. package/dist/shortcutssheet.css +24 -0
  139. package/dist/shortcutssheet.d.ts +7 -0
  140. package/dist/shortcutssheet.layered.css +26 -0
  141. package/dist/styles.css +438 -16
  142. package/dist/styles.layered.css +438 -16
  143. package/dist/table.css +7 -2
  144. package/dist/table.layered.css +7 -2
  145. package/dist/taskeditpopover.css +36 -0
  146. package/dist/taskeditpopover.d.ts +7 -0
  147. package/dist/taskeditpopover.layered.css +38 -0
  148. package/dist/tokens.css +26 -0
  149. package/dist/tokens.json +56 -4
  150. package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
  151. package/dist/types/components/button/button.d.ts +2 -0
  152. package/dist/types/components/button/button.recipe.d.ts +12 -0
  153. package/dist/types/components/chip/chip.recipe.d.ts +1 -1
  154. package/dist/types/components/datatable/datatable.d.ts +8 -0
  155. package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
  156. package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
  157. package/dist/types/components/menu/menu.d.ts +50 -7
  158. package/dist/types/components/multiselect/multiselect.d.ts +9 -0
  159. package/dist/types/components/select/select.d.ts +10 -0
  160. package/dist/types/core/floating-portal.d.ts +6 -5
  161. package/dist/types/core/index.d.ts +1 -1
  162. package/dist/types/core/place-floating.d.ts +37 -0
  163. package/dist/types/index.d.ts +8 -0
  164. package/dist/types/product/assigneepicker/assigneepicker.d.ts +36 -0
  165. package/dist/types/product/assigneepicker/assigneepicker.recipe.d.ts +3 -0
  166. package/dist/types/product/assigneepicker/index.d.ts +2 -0
  167. package/dist/types/product/inbox/inbox.d.ts +13 -1
  168. package/dist/types/product/notesgallery/index.d.ts +2 -0
  169. package/dist/types/product/notesgallery/notesgallery.d.ts +54 -0
  170. package/dist/types/product/notesgallery/notesgallery.recipe.d.ts +3 -0
  171. package/dist/types/product/objectpagemenu/index.d.ts +1 -0
  172. package/dist/types/product/objectpagemenu/objectpagemenu.d.ts +40 -0
  173. package/dist/types/product/objectpagemenu/objectpagemenu.recipe.d.ts +17 -0
  174. package/dist/types/product/objectsettings/objectsettings.d.ts +71 -15
  175. package/dist/types/product/recordpage/index.d.ts +1 -1
  176. package/dist/types/product/recordpage/recordpage.d.ts +142 -1
  177. package/dist/types/product/recordpicker/index.d.ts +2 -0
  178. package/dist/types/product/recordpicker/recordpicker.d.ts +38 -0
  179. package/dist/types/product/recordpicker/recordpicker.recipe.d.ts +3 -0
  180. package/dist/types/product/savedviews/index.d.ts +1 -1
  181. package/dist/types/product/savedviews/savedviews.d.ts +36 -0
  182. package/dist/types/product/share/index.d.ts +2 -0
  183. package/dist/types/product/share/share.d.ts +85 -0
  184. package/dist/types/product/share/share.recipe.d.ts +3 -0
  185. package/dist/types/product/shortcutssheet/index.d.ts +1 -0
  186. package/dist/types/product/shortcutssheet/shortcutssheet.d.ts +49 -0
  187. package/dist/types/product/shortcutssheet/shortcutssheet.recipe.d.ts +3 -0
  188. package/dist/types/product/taskeditpopover/index.d.ts +2 -0
  189. package/dist/types/product/taskeditpopover/taskeditpopover.d.ts +67 -0
  190. package/dist/types/product/taskeditpopover/taskeditpopover.recipe.d.ts +3 -0
  191. package/dist/types/product/workspaceswitchermenu/index.d.ts +1 -0
  192. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.d.ts +54 -0
  193. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.recipe.d.ts +19 -0
  194. package/dist/types/tokens/contract.d.ts +13 -0
  195. package/dist/types/tokens/palette.d.ts +52 -0
  196. package/dist/types/tokens/semantic.d.ts +20 -0
  197. package/dist/workspaceswitchermenu.css +4 -0
  198. package/dist/workspaceswitchermenu.d.ts +7 -0
  199. package/dist/workspaceswitchermenu.layered.css +6 -0
  200. package/package.json +41 -1
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CornerDownLeft } from "lucide-react";
3
+ import * as React from "react";
4
+ import { Drawer, DrawerBody } from "../../components/drawer/index.js";
5
+ import { Kbd } from "../../components/kbd/index.js";
6
+ import { cx, useCrispMessage } from "../../core/index.js";
7
+ /** The physical Enter/Return key's printed glyph — not a reading-direction
8
+ * indicator (a real keyboard prints the same ↵ shape under any locale), so
9
+ * unlike `LogOut` elsewhere it is deliberately left unmirrored (excused in
10
+ * `build/spec-check.mjs`'s own RTL icon-mirroring ALLOW list). */
11
+ const enterKey = () => _jsx(CornerDownLeft, { size: 16, strokeWidth: 1.75 });
12
+ /**
13
+ * Attio's own canonical keyboard-shortcut list (crisp#615), live-measured
14
+ * off app.attio.com/techpartners' "Keyboard shortcuts" panel (Help →
15
+ * "Shortcuts") — 34 rows across 4 sections. The issue's own quoted table
16
+ * collapsed a few of these ("Navigate to the cell above/right/below/left",
17
+ * "Go to next/previous cell") into one row each; the live panel renders
18
+ * them as separate rows, which is what this list follows.
19
+ */
20
+ export const defaultShortcutSections = [
21
+ {
22
+ label: "General",
23
+ rows: [
24
+ { description: "Open quick actions", steps: [["⌘", "K"]] },
25
+ { description: "Open search", steps: [["/"]] },
26
+ { description: "Create a new note", steps: [["N"]] },
27
+ { description: "Create a new entry", steps: [["E"]] },
28
+ { description: "Create a new task", steps: [["T"]] },
29
+ { description: "Create a new email", steps: [["C"]] },
30
+ { description: "Go to home page", steps: [["G"], ["H"]] },
31
+ { description: "Go to tasks page", steps: [["G"], ["T"]] },
32
+ { description: "Go to all notes page", steps: [["G"], ["N"]] },
33
+ { description: "Go to all people page", steps: [["G"], ["P"]] },
34
+ { description: "Go to all companies page", steps: [["G"], ["C"]] },
35
+ { description: "Go to email drafts page", steps: [["G"], ["E"]] },
36
+ { description: "Toggle sidebar", steps: [["⌘", "."]] },
37
+ { description: "Navigate up", steps: [["↑"]] },
38
+ { description: "Navigate down", steps: [["↓"]] },
39
+ ],
40
+ },
41
+ {
42
+ label: "Record page",
43
+ rows: [
44
+ { description: "Go to next tab", steps: [["⌥", "→"]] },
45
+ { description: "Go to previous tab", steps: [["⌥", "←"]] },
46
+ ],
47
+ },
48
+ {
49
+ label: "Table view",
50
+ rows: [
51
+ { description: "Navigate to the cell above", steps: [["↑"]] },
52
+ { description: "Navigate to the cell to the right", steps: [["→"]] },
53
+ { description: "Navigate to the cell below", steps: [["↓"]] },
54
+ { description: "Navigate to the cell to the left", steps: [["←"]] },
55
+ { description: "Jump to bottom", steps: [["⌘", "↓"]] },
56
+ { description: "Jump to top", steps: [["⌘", "↑"]] },
57
+ { description: "Select/deselect current row", steps: [["⇧", "␣"]] },
58
+ { description: "Group select rows up", steps: [["⇧", "↑"]] },
59
+ { description: "Group select rows down", steps: [["⇧", "↓"]] },
60
+ { description: "Group select to top", steps: [["⌘", "⇧", "↑"]] },
61
+ { description: "Group select to bottom", steps: [["⌘", "⇧", "↓"]] },
62
+ {
63
+ description: "Open record page for current entry",
64
+ steps: [["⌘", enterKey()]],
65
+ },
66
+ { description: "Select/deselect all rows", steps: [["⌘", "A"]] },
67
+ { description: "Go to next cell", steps: [["⇥"]] },
68
+ { description: "Go to previous cell", steps: [["⇧", "⇥"]] },
69
+ { description: "Edit cell / go to next cell", steps: [[enterKey()]] },
70
+ ],
71
+ },
72
+ {
73
+ label: "Help",
74
+ rows: [{ description: "Open help drawer", steps: [["?"]] }],
75
+ },
76
+ ];
77
+ /**
78
+ * Attio's "Keyboard shortcuts" panel (crisp#615, parity row P-106) — a
79
+ * `Drawer` narrowed to the measured 360px (Drawer's own default is 420px),
80
+ * listing `sections` grouped with a hairline-free 10px row rhythm and a
81
+ * 20px gap before each new section label.
82
+ */
83
+ export function ShortcutsSheet({ open, onOpenChange, sections = defaultShortcutSections, title: titleProp, thenLabel: thenLabelProp, closeLabel, className, ...rest }) {
84
+ const title = useCrispMessage(titleProp, "Keyboard shortcuts");
85
+ const thenLabel = useCrispMessage(thenLabelProp, "then");
86
+ return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: title, closeLabel: closeLabel, className: cx("crisp-shortcutssheet-panel", className), ...rest, children: _jsx(DrawerBody, { children: _jsx("div", { className: "crisp-shortcutssheet-body", children: sections.map((section) => (_jsxs(React.Fragment, { children: [_jsx("div", { className: "crisp-shortcutssheet-section-label", children: section.label }), section.rows.map((row) => (_jsxs("div", { className: "crisp-shortcutssheet-row", children: [_jsx("div", { className: "crisp-shortcutssheet-desc", children: row.description }), _jsx("div", { className: "crisp-shortcutssheet-keys", children: row.steps.map((step, stepIndex) => (_jsxs(React.Fragment, { children: [stepIndex > 0 && (_jsx("span", { className: "crisp-shortcutssheet-then", children: thenLabel })), step.map((glyph, glyphIndex) => (_jsx(Kbd, { children: glyph }, glyphIndex)))] }, stepIndex))) })] }, String(row.description))))] }, String(section.label)))) }) }) }));
87
+ }
88
+ ShortcutsSheet.displayName = "ShortcutsSheet";
@@ -0,0 +1,47 @@
1
+ import { defineRecipe, resolveRecipe } from "../../core/index.js";
2
+ import { contract as c } from "../../tokens/index.js";
3
+ // Attio's "Keyboard shortcuts" panel (crisp#615), live-measured off
4
+ // app.attio.com/techpartners — a second overlay reached from Help →
5
+ // "Shortcuts": 360px wide (narrower than Drawer's own 420px default, hence
6
+ // the width override below), 16px body padding (already Drawer's own
7
+ // `crisp-drawer-body`, unchanged), rows 10px apart, a 20px gap before a new
8
+ // section (expressed as the flex column's own 10px `gap` plus another 10px
9
+ // on every section label but the first, rather than a second gap value),
10
+ // section labels 12px/500, row descriptions 14px/500 — both the SAME
11
+ // `color-mix(fgPrimary, 63%)` muted-text tint RecordPage's own notes already
12
+ // established (crisp#871), and the "then" connector between a sequential
13
+ // shortcut's two steps at 11px/500 in Kbd's own 50%-fgPrimary tint (the
14
+ // same de-emphasis, on the same kind of supporting glyph).
15
+ const config = defineRecipe({
16
+ name: "shortcutssheet",
17
+ base: "",
18
+ globalCss: `
19
+ /* Overrides Drawer's own 420px default (specificity: .crisp-drawer
20
+ .crisp-drawer-content.crisp-shortcutssheet-panel, higher than
21
+ Drawer's .crisp-drawer .crisp-drawer-content regardless of which
22
+ chunk of dist/styles.css concatenates first). */
23
+ .crisp-drawer .crisp-drawer-content.crisp-shortcutssheet-panel { width: 360px; }
24
+
25
+ .crisp-shortcutssheet-body { display: flex; flex-direction: column; gap: ${c.space["2.5"]}; }
26
+ .crisp-shortcutssheet-section-label {
27
+ font-size: ${c.text.sm}; font-weight: ${c.weight.medium};
28
+ color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent);
29
+ }
30
+ .crisp-shortcutssheet-section-label:not(:first-child) { margin-top: ${c.space["2.5"]}; }
31
+ .crisp-shortcutssheet-row { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["1"]}; }
32
+ .crisp-shortcutssheet-desc {
33
+ font-size: ${c.text.lg}; font-weight: ${c.weight.medium};
34
+ color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent);
35
+ }
36
+ .crisp-shortcutssheet-keys { display: flex; align-items: center; gap: ${c.space["1"]}; flex-shrink: 0; }
37
+ .crisp-shortcutssheet-then {
38
+ font-size: ${c.text.xs}; font-weight: ${c.weight.medium};
39
+ color: color-mix(in srgb, ${c.fgPrimary} 50%, transparent);
40
+ }
41
+ `,
42
+ variants: {},
43
+ defaultVariants: {},
44
+ compoundVariants: [],
45
+ });
46
+ export const shortcutssheetRecipe = resolveRecipe(config);
47
+ export default config;
@@ -0,0 +1,2 @@
1
+ export { TaskEditPopover } from "./taskeditpopover.js";
2
+ export { taskeditpopoverRecipe } from "./taskeditpopover.recipe.js";
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CalendarDays, CircleCheckBig, MoreHorizontal, X } from "lucide-react";
3
+ import * as React from "react";
4
+ import { Popover } from "../../components/popover/index.js";
5
+ import { FloatingPortal, isInsideLaterFloatingLayer, placeFloating, registerFloatingLayer, useCrispMessage, } from "../../core/index.js";
6
+ import { AssigneePicker, } from "../assigneepicker/assigneepicker.js";
7
+ import { RecordPicker, } from "../recordpicker/recordpicker.js";
8
+ import { taskeditpopoverRecipe } from "./taskeditpopover.recipe.js";
9
+ /**
10
+ * A task's "Edit task" popover -- measured live on app.attio.com's Tasks
11
+ * page: a checkbox + editable title, then a chip row (due date / assigned /
12
+ * add record), a "more actions" kebab, and Cancel/Save. Composes
13
+ * `AssigneePicker` and `RecordPicker` (both crisp#626) behind their own
14
+ * chips, using the same core floating primitives `Popover` itself is built
15
+ * on rather than `Popover` directly (its content hardcodes a 360px max
16
+ * width, well under this popover's own measured 800px).
17
+ */
18
+ export 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, }) {
19
+ const headingLabel = useCrispMessage(headingLabelProp, "Edit task");
20
+ const closeLabel = useCrispMessage(closeLabelProp, "Close");
21
+ const dueDatePlaceholder = useCrispMessage(dueDatePlaceholderProp, "Set due date");
22
+ const assignLabel = useCrispMessage(assignLabelProp, "Assign");
23
+ const assignedToLabel = useCrispMessage(assignedToLabelProp, "Assigned to");
24
+ const addRecordLabel = useCrispMessage(addRecordLabelProp, "Add record");
25
+ const linkedRecordLabel = useCrispMessage(linkedRecordLabelProp, linkedRecordCount === 1
26
+ ? "1 linked record"
27
+ : `${linkedRecordCount} linked records`);
28
+ const moreActionsLabel = useCrispMessage(moreActionsLabelProp, "More actions");
29
+ const cancelLabel = useCrispMessage(cancelLabelProp, "Cancel");
30
+ const cancelKeyLabel = useCrispMessage(cancelKeyLabelProp, "Esc");
31
+ const saveLabel = useCrispMessage(saveLabelProp, "Save");
32
+ const surfaceId = React.useId();
33
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
34
+ const open = openProp ?? internalOpen;
35
+ const rootRef = React.useRef(null);
36
+ const triggerRef = React.useRef(null);
37
+ const contentRef = React.useRef(null);
38
+ const [pos, setPos] = React.useState({});
39
+ const setOpen = React.useCallback((next) => {
40
+ if (openProp === undefined)
41
+ setInternalOpen(next);
42
+ onOpenChange?.(next);
43
+ }, [openProp, onOpenChange]);
44
+ const place = () => {
45
+ const el = triggerRef.current;
46
+ if (!el)
47
+ return;
48
+ const { style } = placeFloating({
49
+ rect: el.getBoundingClientRect(),
50
+ align: "start",
51
+ gap: 6,
52
+ flipBelow: 300,
53
+ anchor: el,
54
+ });
55
+ setPos(style);
56
+ };
57
+ // biome-ignore lint/correctness/useExhaustiveDependencies: reposition each time it opens
58
+ React.useLayoutEffect(() => {
59
+ if (open)
60
+ place();
61
+ }, [open]);
62
+ React.useEffect(() => {
63
+ if (!open || !contentRef.current)
64
+ return;
65
+ return registerFloatingLayer(contentRef.current);
66
+ }, [open]);
67
+ React.useEffect(() => {
68
+ if (!open)
69
+ return;
70
+ const onDown = (e) => {
71
+ const target = e.target;
72
+ if (rootRef.current?.contains(target))
73
+ return;
74
+ if (contentRef.current?.contains(target))
75
+ return;
76
+ if (isInsideLaterFloatingLayer(target, contentRef.current))
77
+ return;
78
+ setOpen(false);
79
+ onCancel?.();
80
+ };
81
+ document.addEventListener("pointerdown", onDown);
82
+ return () => document.removeEventListener("pointerdown", onDown);
83
+ }, [open, setOpen, onCancel]);
84
+ const cancel = () => {
85
+ setOpen(false);
86
+ onCancel?.();
87
+ };
88
+ const save = () => {
89
+ setOpen(false);
90
+ onSave?.();
91
+ };
92
+ const triggerEl = React.cloneElement(trigger, {
93
+ ref: triggerRef,
94
+ "aria-haspopup": "dialog",
95
+ "aria-expanded": open,
96
+ "aria-controls": open ? surfaceId : undefined,
97
+ onClick: (e) => {
98
+ trigger.props.onClick?.(e);
99
+ setOpen(!open);
100
+ },
101
+ });
102
+ return (_jsxs("div", { ref: rootRef, style: { display: "contents" }, children: [triggerEl, open && (_jsx(FloatingPortal, { sourceRef: rootRef, children: _jsxs("div", { ref: contentRef, id: surfaceId, role: "dialog", "aria-label": headingLabel, className: taskeditpopoverRecipe({ className }), style: pos, onKeyDown: (e) => {
103
+ if (e.key === "Escape") {
104
+ e.preventDefault();
105
+ cancel();
106
+ }
107
+ if (e.key === "Enter" &&
108
+ e.metaKey === false &&
109
+ e.ctrlKey === false &&
110
+ e.target.tagName !== "TEXTAREA") {
111
+ // Enter saves, same as Attio's own "Save ⏎" -- but never
112
+ // steals it from a nested picker's own Enter-to-commit.
113
+ if (!isInsideLaterFloatingLayer(e.target, contentRef.current)) {
114
+ save();
115
+ }
116
+ }
117
+ }, children: [_jsxs("div", { className: "crisp-taskeditpopover-header", children: [_jsxs("span", { className: "crisp-taskeditpopover-heading", children: [_jsx(CircleCheckBig, { size: 16, strokeWidth: 1.75 }), headingLabel] }), _jsx("button", { type: "button", className: "crisp-taskeditpopover-close", "aria-label": closeLabel, onClick: cancel, children: _jsx(X, { size: 16, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-taskeditpopover-body", children: [_jsx("input", { type: "checkbox", className: "crisp-taskeditpopover-check", checked: done, "aria-label": title, onChange: (e) => onToggleDone?.(e.target.checked) }), _jsx("input", { type: "text", className: "crisp-taskeditpopover-title", value: title, onChange: (e) => onTitleChange?.(e.target.value) })] }), _jsxs("div", { className: "crisp-taskeditpopover-footer", children: [_jsxs("div", { className: "crisp-taskeditpopover-chips", children: [_jsx(Popover, { align: "start", "aria-label": dueDatePlaceholder, trigger: _jsxs("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [_jsx(CalendarDays, { size: 14, strokeWidth: 1.75 }), dueDateLabel ?? dueDatePlaceholder] }), children: dueDatePicker }), _jsx(Popover, { align: "start", "aria-label": assignedToLabel, trigger: _jsxs("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [_jsx("span", { "aria-hidden": "true", children: "@" }), assignee != null
118
+ ? `${assignedToLabel} ${assignee.name}`
119
+ : assignLabel] }), children: _jsx(AssigneePicker, { users: users, assignee: assignee, onAssigneeChange: onAssigneeChange }) }), _jsx(Popover, { align: "start", "aria-label": addRecordLabel, trigger: _jsxs("button", { type: "button", className: "crisp-taskeditpopover-chip", children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), linkedRecordCount > 0
120
+ ? linkedRecordLabel
121
+ : addRecordLabel] }), children: _jsx(RecordPicker, { records: recordCandidates, loading: recordCandidatesLoading, onQueryChange: onRecordQueryChange, onSelect: onSelectRecord }) })] }), _jsxs("div", { className: "crisp-taskeditpopover-actions", children: [menu != null && (_jsx(Popover, { align: "end", "aria-label": moreActionsLabel, trigger: _jsx("button", { type: "button", className: "crisp-taskeditpopover-kebab", "aria-label": moreActionsLabel, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: menu })), _jsxs("button", { type: "button", className: "crisp-taskeditpopover-cancel", onClick: cancel, children: [cancelLabel, _jsx("kbd", { className: "crisp-taskeditpopover-key", children: cancelKeyLabel })] }), _jsxs("button", { type: "button", className: "crisp-taskeditpopover-save", onClick: save, children: [saveLabel, _jsx("kbd", { className: "crisp-taskeditpopover-key", children: "\u23CE" })] })] })] })] }) }))] }));
122
+ }
123
+ TaskEditPopover.displayName = "TaskEditPopover";
@@ -0,0 +1,65 @@
1
+ import { defineRecipe, resolveRecipe } from "../../core/index.js";
2
+ import { contract as c } from "../../tokens/index.js";
3
+ // The "Edit task" popover -- measured live on app.attio.com's Tasks page
4
+ // (click a task row): an 800px-wide floating card, radius 12, a single
5
+ // subtle 1px ring shadow (no blur/offset layer -- lighter than
6
+ // `elevation.floating`, which every other crisp overlay uses; this one
7
+ // really does measure as just the ring, confirmed by walking every
8
+ // ancestor up to the floating root). Same core floating primitives
9
+ // `Popover`/`savedviews` already use directly (this doesn't reuse
10
+ // `Popover` itself: its own `.crisp-popover-content` hardcodes a
11
+ // max-width of min(360px, 92vw), far under this popover's measured width).
12
+ //
13
+ // Three rows: a 44px header (icon+"Edit task", a bottom hairline divider,
14
+ // a close button), a body row (checkbox + editable title), and a 44px
15
+ // footer (the due-date/assigned/add-record chip row on the left, the
16
+ // kebab + Cancel/Save on the right). The chip row's own 10px/400 muted
17
+ // caption size and the footer buttons' matching 10px labels don't fit any
18
+ // existing text-scale token (closest is `text.xs` at 11px) -- raw px per
19
+ // Golden Rule 3's "genuinely intrinsic size" exception, same as
20
+ // `dialog.recipe.ts`'s own submit-key glyphs.
21
+ const config = defineRecipe({
22
+ name: "taskeditpopover",
23
+ // Portaled root: position/z-index/the open transition live here as raw
24
+ // literal CSS (not `base`, which lightningcss nests under `.${prefixed}`
25
+ // at BUILD time -- fine for the plain declarations below, but this exact
26
+ // selector text has to exist as source text for `spec-check.mjs`'s
27
+ // overlay-escape/animation scan, the same reason Popover/Menu/Select
28
+ // declare their own portaled root in `globalCss` rather than `base`).
29
+ 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); } }`,
30
+ base: `display: flex; flex-direction: column; box-sizing: border-box; width: 800px; max-width: 92vw; border-radius: ${c.radius.xl}; background: ${c.bgSurface}; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07); font-family: ${c.font.sans};
31
+ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
32
+
33
+ & .crisp-taskeditpopover-header { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["2"]}; height: 44px; box-sizing: border-box; padding: 0 ${c.space["2"]} 0 ${c.space["3"]}; border-bottom: 1px solid ${c.borderSubtle}; }
34
+ & .crisp-taskeditpopover-heading { display: flex; align-items: center; gap: ${c.space["1"]}; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
35
+ & .crisp-taskeditpopover-heading svg { flex-shrink: 0; color: ${c.fgMuted}; }
36
+ & .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: ${c.radius.md}; background: none; color: ${c.fgMuted}; font-family: inherit; cursor: pointer; }
37
+ & .crisp-taskeditpopover-close:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
38
+
39
+ & .crisp-taskeditpopover-body { display: flex; align-items: center; gap: ${c.space["2.5"]}; min-height: 52px; box-sizing: border-box; padding: ${c.space["3"]}; }
40
+ & .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: ${c.radius.full}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 9%, transparent); cursor: pointer; }
41
+ & .crisp-taskeditpopover-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 30%, transparent); }
42
+ & .crisp-taskeditpopover-check:checked { background: ${c.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"); }
43
+ & .crisp-taskeditpopover-title { flex: 1; min-width: 0; border: none; border-radius: ${c.radius.sm}; outline: none; background: none; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
44
+ & .crisp-taskeditpopover-title:focus-visible { outline: 2px solid ${c.strokeFocusRing}; outline-offset: 2px; }
45
+
46
+ & .crisp-taskeditpopover-footer { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["2"]}; height: 44px; box-sizing: border-box; padding: 0 ${c.space["2"]} 0 ${c.space["3"]}; }
47
+ & .crisp-taskeditpopover-chips { display: flex; align-items: center; gap: ${c.space["1"]}; min-width: 0; }
48
+ & .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: ${c.radius.md}; background: none; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); font-family: inherit; font-size: 10px; white-space: nowrap; cursor: pointer; }
49
+ & .crisp-taskeditpopover-chip svg { flex-shrink: 0; }
50
+ & .crisp-taskeditpopover-chip:hover { background: ${c.bgRaised}; }
51
+
52
+ & .crisp-taskeditpopover-actions { display: flex; align-items: center; gap: ${c.space["2"]}; flex-shrink: 0; }
53
+ & .crisp-taskeditpopover-kebab { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 6px; border: none; border-radius: ${c.radius.md}; background: none; color: ${c.fgPrimary}; font-family: inherit; cursor: pointer; }
54
+ & .crisp-taskeditpopover-kebab:hover { background: ${c.bgRaised}; }
55
+ & .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: ${c.radius.md}; background: none; color: ${c.fgPrimary}; font-family: inherit; font-size: 10px; cursor: pointer; }
56
+ & .crisp-taskeditpopover-cancel:hover { background: ${c.bgRaised}; }
57
+ & .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: ${c.radius.md}; background: ${c.bgBrandSolid}; color: ${c.fgOnBrand}; font-family: inherit; font-size: 10px; cursor: pointer; }
58
+ & .crisp-taskeditpopover-save:hover { background: ${c.bgBrandSolidPressed}; }
59
+ & .crisp-taskeditpopover-key { color: color-mix(in srgb, currentColor 55%, transparent); font-size: 10px; }`,
60
+ variants: {},
61
+ defaultVariants: {},
62
+ compoundVariants: [],
63
+ });
64
+ export const taskeditpopoverRecipe = resolveRecipe(config);
65
+ export default config;
@@ -0,0 +1 @@
1
+ export * from "./workspaceswitchermenu.js";
@@ -0,0 +1,35 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CircleArrowUp, CircleCheck, Grid2x2, HeartHandshake, LogOut, Plus, Settings, User, UserPlus, } from "lucide-react";
3
+ import { Avatar } from "../../components/avatar/index.js";
4
+ import { MenuItem, MenuSeparator } from "../../components/menu/index.js";
5
+ import { useCrispMessage } from "../../core/index.js";
6
+ /**
7
+ * The FIXED content of Attio's own workspace-switcher popover (crisp#632) —
8
+ * live-measured off app.attio.com/techpartners: each workspace as a
9
+ * checked/unchecked row, then New workspace, then Account settings /
10
+ * Workspace settings, then Invite members / Upgrade Attio / Refer another
11
+ * team, then Apps and integrations, then Sign out — each group separated by
12
+ * a hairline, matching a 280px-wide popover.
13
+ *
14
+ * This is CONTENT, not a component with its own trigger/popover: pass it as
15
+ * `Sidebar`'s own `workspaceMenu` prop (crisp#415 already builds the real
16
+ * menu-button trigger/popover machinery around whatever children it's
17
+ * given — the workspace switcher itself has no fixed content of its own by
18
+ * design, since which settings exist is the consumer's; this is that fixed
19
+ * content, ready-made, the same way `defaultCommandItems` is for `Command`).
20
+ */
21
+ export function WorkspaceSwitcherMenu({ workspaces, currentWorkspaceId, onSelectWorkspace, onNewWorkspace, onAccountSettings, onWorkspaceSettings, onInviteMembers, onUpgradeAttio, onReferAnotherTeam, onAppsAndIntegrations, onSignOut, newWorkspaceLabel, accountSettingsLabel, workspaceSettingsLabel, inviteMembersLabel, upgradeAttioLabel, referAnotherTeamLabel, appsAndIntegrationsLabel, signOutLabel, }) {
22
+ const newWorkspace = useCrispMessage(newWorkspaceLabel, "New workspace");
23
+ const accountSettings = useCrispMessage(accountSettingsLabel, "Account settings");
24
+ const workspaceSettings = useCrispMessage(workspaceSettingsLabel, "Workspace settings");
25
+ const inviteMembers = useCrispMessage(inviteMembersLabel, "Invite members");
26
+ const upgradeAttio = useCrispMessage(upgradeAttioLabel, "Upgrade Attio");
27
+ const referAnotherTeam = useCrispMessage(referAnotherTeamLabel, "Refer another team");
28
+ const appsAndIntegrations = useCrispMessage(appsAndIntegrationsLabel, "Apps and integrations");
29
+ const signOut = useCrispMessage(signOutLabel, "Sign out");
30
+ return (_jsxs(_Fragment, { children: [workspaces.map((ws) => {
31
+ const checked = ws.id === currentWorkspaceId;
32
+ return (_jsx(MenuItem, { icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: ws.avatarTone, children: ws.avatarLabel }), trailingIcon: checked ? (_jsx(CircleCheck, { size: 16, strokeWidth: 1.75, className: "crisp-workspaceswitchermenu-check" })) : undefined, "aria-checked": checked, onSelect: () => onSelectWorkspace?.(ws.id), children: ws.name }, ws.id));
33
+ }), _jsx(MenuItem, { icon: _jsx(Plus, { size: 16, strokeWidth: 1.75 }), onSelect: onNewWorkspace, children: newWorkspace }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(User, { size: 16, strokeWidth: 1.75 }), onSelect: onAccountSettings, children: accountSettings }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 16, strokeWidth: 1.75 }), onSelect: onWorkspaceSettings, children: workspaceSettings }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(UserPlus, { size: 16, strokeWidth: 1.75 }), onSelect: onInviteMembers, children: inviteMembers }), _jsx(MenuItem, { icon: _jsx(CircleArrowUp, { size: 16, strokeWidth: 1.75 }), onSelect: onUpgradeAttio, children: upgradeAttio }), _jsx(MenuItem, { icon: _jsx(HeartHandshake, { size: 16, strokeWidth: 1.75 }), onSelect: onReferAnotherTeam, children: referAnotherTeam }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Grid2x2, { size: 16, strokeWidth: 1.75 }), onSelect: onAppsAndIntegrations, children: appsAndIntegrations }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(LogOut, { "data-rtl-flip": "", size: 16, strokeWidth: 1.75 }), onSelect: onSignOut, children: signOut })] }));
34
+ }
35
+ WorkspaceSwitcherMenu.displayName = "WorkspaceSwitcherMenu";
@@ -0,0 +1,31 @@
1
+ import { defineRecipe, resolveRecipe } from "../../core/index.js";
2
+ import { contract as c } from "../../tokens/index.js";
3
+ /**
4
+ * `WorkspaceSwitcherMenu` owns almost no styling of its own — every visible
5
+ * pixel besides the two rules below is `Menu`/`MenuItem`'s own
6
+ * already-measured recipe (crisp#632):
7
+ * - The RTL mirror for its "Sign out" icon: `LogOut` is treated as
8
+ * direction-bearing (`build/spec-check.mjs`'s own DIRECTIONAL set), so
9
+ * it's marked and mirrored the same way ObjectPageMenu's Notifications
10
+ * chevron is (crisp#616) — see that file's identical recipe for why this
11
+ * is a plain absolute selector, not `${rtlFlipCss}`'s `&`-nesting form.
12
+ * - The current-workspace checkmark's colour: `.crisp-menu-item svg` already
13
+ * sets every menu icon to `fgMuted` (menu.recipe.ts), which is deliberate
14
+ * there but wrong for THIS one glyph — Attio's own is brand-coloured, not
15
+ * muted, to read as a status rather than a plain icon. `svg` doubled in
16
+ * the selector (not just the class) so it outranks that rule under equal
17
+ * source order instead of depending on file concatenation order.
18
+ */
19
+ const config = defineRecipe({
20
+ name: "workspaceswitchermenu",
21
+ base: "",
22
+ globalCss: `
23
+ [dir="rtl"] .crisp-menu-item svg[data-rtl-flip] { transform: scaleX(-1); }
24
+ .crisp-menu-item svg.crisp-workspaceswitchermenu-check { color: ${c.fgBrandContrast}; }
25
+ `,
26
+ variants: {},
27
+ defaultVariants: {},
28
+ compoundVariants: [],
29
+ });
30
+ export const workspaceswitchermenuRecipe = resolveRecipe(config);
31
+ export default config;
@@ -274,4 +274,31 @@ export const palette = {
274
274
  // s3 (shadow.yaml) -- the listbox's single-layer floating shadow, in place
275
275
  // of crisp's own multi-layer `elevation.floating` for this component only.
276
276
  selectShadow: { light: "#0000001f", dark: "#000000cc" },
277
+ // your-moon/crisp#998: the 12-colour tag/option-colour palette a workspace
278
+ // admin picks for a Select/MultiSelect option, measured live off
279
+ // app.attio.com's Categories multi-select field (table-cell/column-editor
280
+ // pill, not the larger tinted-text activity-feed variant -- see
281
+ // ~/grc-parity/attio-tag-palette.md). All 12 are near-white pastels with
282
+ // flat black text regardless of which one -- Attio's theme toggle is
283
+ // off-limits (same restriction every other raw entry in this file notes),
284
+ // so no dark variant was reachable; light is reused for dark, same as
285
+ // `cyan600`/`purple600`/`orange600` above already do for the same reason.
286
+ // Numbered 1-12 rather than named: Attio assigns them by an internal hash
287
+ // of the option's id, not by any hue name a consumer would recognise.
288
+ tagSwatch1: { light: "#fff3cc", dark: "#fff3cc" },
289
+ tagSwatch2: { light: "#f4fbcb", dark: "#f4fbcb" },
290
+ tagSwatch3: { light: "#fdf7c4", dark: "#fdf7c4" },
291
+ tagSwatch4: { light: "#feecf3", dark: "#feecf3" },
292
+ tagSwatch5: { light: "#e0fced", dark: "#e0fced" },
293
+ tagSwatch6: { light: "#ddf7ff", dark: "#ddf7ff" },
294
+ tagSwatch7: { light: "#f6f7f7", dark: "#f6f7f7" },
295
+ tagSwatch8: { light: "#ffebeb", dark: "#ffebeb" },
296
+ tagSwatch9: { light: "#feeee1", dark: "#feeee1" },
297
+ tagSwatch10: { light: "#e5eeff", dark: "#e5eeff" },
298
+ tagSwatch11: { light: "#f5f0ff", dark: "#f5f0ff" },
299
+ tagSwatch12: { light: "#fdedff", dark: "#fdedff" },
300
+ // measured alongside the palette above: flat opaque black, not `ink`
301
+ // (#101112) -- a genuinely different, slightly darker hex Attio uses only
302
+ // for this one pill's text, regardless of which of the 12 backgrounds.
303
+ tagSwatchInk: { light: "#000000", dark: "#000000" },
277
304
  };
@@ -126,4 +126,24 @@ export const semantic = {
126
126
  strokeSelectDivider: "selectDivider",
127
127
  fgSelectPlaceholder: "selectPlaceholder",
128
128
  shadowSelect: "selectShadow",
129
+ /** your-moon/crisp#998: a Select/MultiSelect option's fixed tag colour
130
+ * (`swatch`, 1-12) -- see palette.ts's own comment on `tagSwatch1` for
131
+ * the measurement. Distinct from `Select`'s existing `color` (an
132
+ * arbitrary-CSS status dot) and `MultiSelect`'s existing `tone` (crisp's
133
+ * own semantic 7-tone chip family): this is Attio's own enumerable,
134
+ * workspace-admin-assigned palette, reproduced verbatim rather than
135
+ * mapped onto either existing mechanism. */
136
+ bgTagSwatch1: "tagSwatch1",
137
+ bgTagSwatch2: "tagSwatch2",
138
+ bgTagSwatch3: "tagSwatch3",
139
+ bgTagSwatch4: "tagSwatch4",
140
+ bgTagSwatch5: "tagSwatch5",
141
+ bgTagSwatch6: "tagSwatch6",
142
+ bgTagSwatch7: "tagSwatch7",
143
+ bgTagSwatch8: "tagSwatch8",
144
+ bgTagSwatch9: "tagSwatch9",
145
+ bgTagSwatch10: "tagSwatch10",
146
+ bgTagSwatch11: "tagSwatch11",
147
+ bgTagSwatch12: "tagSwatch12",
148
+ fgTagSwatch: "tagSwatchInk",
129
149
  };
@@ -50,6 +50,24 @@
50
50
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
51
51
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
52
52
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
53
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured live,
54
+ see tokens/palette.ts's tagSwatch1 comment) -- flat black text, no
55
+ ring/border, unlike the tone family above. 7px radius/10px text are
56
+ measured literals, no matching step in the shared scale (same as
57
+ Select's own .crisp-select-swatch). */
58
+ .crisp-multiselect-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; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-tag-swatch); }
59
+ .crisp-multiselect-swatch[data-swatch="1"] { background: var(--crisp-bg-tag-swatch1); }
60
+ .crisp-multiselect-swatch[data-swatch="2"] { background: var(--crisp-bg-tag-swatch2); }
61
+ .crisp-multiselect-swatch[data-swatch="3"] { background: var(--crisp-bg-tag-swatch3); }
62
+ .crisp-multiselect-swatch[data-swatch="4"] { background: var(--crisp-bg-tag-swatch4); }
63
+ .crisp-multiselect-swatch[data-swatch="5"] { background: var(--crisp-bg-tag-swatch5); }
64
+ .crisp-multiselect-swatch[data-swatch="6"] { background: var(--crisp-bg-tag-swatch6); }
65
+ .crisp-multiselect-swatch[data-swatch="7"] { background: var(--crisp-bg-tag-swatch7); }
66
+ .crisp-multiselect-swatch[data-swatch="8"] { background: var(--crisp-bg-tag-swatch8); }
67
+ .crisp-multiselect-swatch[data-swatch="9"] { background: var(--crisp-bg-tag-swatch9); }
68
+ .crisp-multiselect-swatch[data-swatch="10"] { background: var(--crisp-bg-tag-swatch10); }
69
+ .crisp-multiselect-swatch[data-swatch="11"] { background: var(--crisp-bg-tag-swatch11); }
70
+ .crisp-multiselect-swatch[data-swatch="12"] { background: var(--crisp-bg-tag-swatch12); }
53
71
 
54
72
  .crisp-multiselect {
55
73
  position: relative; display: inline-flex; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
@@ -51,6 +51,24 @@
51
51
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
52
52
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
53
53
  .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
54
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured live,
55
+ see tokens/palette.ts's tagSwatch1 comment) -- flat black text, no
56
+ ring/border, unlike the tone family above. 7px radius/10px text are
57
+ measured literals, no matching step in the shared scale (same as
58
+ Select's own .crisp-select-swatch). */
59
+ .crisp-multiselect-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; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-tag-swatch); }
60
+ .crisp-multiselect-swatch[data-swatch="1"] { background: var(--crisp-bg-tag-swatch1); }
61
+ .crisp-multiselect-swatch[data-swatch="2"] { background: var(--crisp-bg-tag-swatch2); }
62
+ .crisp-multiselect-swatch[data-swatch="3"] { background: var(--crisp-bg-tag-swatch3); }
63
+ .crisp-multiselect-swatch[data-swatch="4"] { background: var(--crisp-bg-tag-swatch4); }
64
+ .crisp-multiselect-swatch[data-swatch="5"] { background: var(--crisp-bg-tag-swatch5); }
65
+ .crisp-multiselect-swatch[data-swatch="6"] { background: var(--crisp-bg-tag-swatch6); }
66
+ .crisp-multiselect-swatch[data-swatch="7"] { background: var(--crisp-bg-tag-swatch7); }
67
+ .crisp-multiselect-swatch[data-swatch="8"] { background: var(--crisp-bg-tag-swatch8); }
68
+ .crisp-multiselect-swatch[data-swatch="9"] { background: var(--crisp-bg-tag-swatch9); }
69
+ .crisp-multiselect-swatch[data-swatch="10"] { background: var(--crisp-bg-tag-swatch10); }
70
+ .crisp-multiselect-swatch[data-swatch="11"] { background: var(--crisp-bg-tag-swatch11); }
71
+ .crisp-multiselect-swatch[data-swatch="12"] { background: var(--crisp-bg-tag-swatch12); }
54
72
 
55
73
  .crisp-multiselect {
56
74
  position: relative; display: inline-flex; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
@@ -2,7 +2,7 @@
2
2
  // The same tokens the CSS custom properties carry, resolved per theme, with
3
3
  // dimensions as numbers for React Native and Lynx style objects.
4
4
  export type Theme = "light" | "dark";
5
- export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "bgBrandContrast" | "fgBrandContrast" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "bgSurfaceMuted" | "borderDefault" | "borderSubtle" | "borderDropzone" | "borderDivider" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgScrim" | "bgOverlayStrong" | "strokeGroupDivider" | "fgAlertDescription" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgWarningSolid" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft" | "ringBrandSoft" | "fgSelectNeutral" | "bgSelectListbox" | "strokeSelectTrigger" | "bgSelectPressed" | "bgSelectDisabled" | "strokeSelectInvalid" | "strokeSelectDivider" | "fgSelectPlaceholder" | "shadowSelect", string>;
5
+ export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "bgBrandContrast" | "fgBrandContrast" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "bgSurfaceMuted" | "borderDefault" | "borderSubtle" | "borderDropzone" | "borderDivider" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgScrim" | "bgOverlayStrong" | "strokeGroupDivider" | "fgAlertDescription" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgWarningSolid" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft" | "ringBrandSoft" | "fgSelectNeutral" | "bgSelectListbox" | "strokeSelectTrigger" | "bgSelectPressed" | "bgSelectDisabled" | "strokeSelectInvalid" | "strokeSelectDivider" | "fgSelectPlaceholder" | "shadowSelect" | "bgTagSwatch1" | "bgTagSwatch2" | "bgTagSwatch3" | "bgTagSwatch4" | "bgTagSwatch5" | "bgTagSwatch6" | "bgTagSwatch7" | "bgTagSwatch8" | "bgTagSwatch9" | "bgTagSwatch10" | "bgTagSwatch11" | "bgTagSwatch12" | "fgTagSwatch", string>;
6
6
  export declare const tokens: {
7
7
  color: Record<Theme, ColorTokens>;
8
8
  palette: Record<Theme, Record<string, string>>;