@crisp-ui-kit/crisp 0.51.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/core/caret-rect.js +73 -0
  2. package/dist/cjs/core/index.js +7 -1
  3. package/dist/cjs/core/mention.js +32 -0
  4. package/dist/cjs/core/use-mention-trigger.js +124 -0
  5. package/dist/cjs/product/recordpanel/recordpanel.js +13 -81
  6. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +53 -44
  7. package/dist/cjs/product/recordstable/editable-field.js +11 -8
  8. package/dist/cjs/product/recordstable/recordstable.tag.js +8 -3
  9. package/dist/cjs/product/richtext/index.js +2 -1
  10. package/dist/cjs/product/richtext/richtext.js +440 -23
  11. package/dist/cjs/product/richtext/richtext.recipe.js +59 -9
  12. package/dist/esm/core/caret-rect.js +70 -0
  13. package/dist/esm/core/index.js +3 -0
  14. package/dist/esm/core/mention.js +29 -0
  15. package/dist/esm/core/use-mention-trigger.js +88 -0
  16. package/dist/esm/product/recordpanel/index.js +1 -1
  17. package/dist/esm/product/recordpanel/recordpanel.js +15 -83
  18. package/dist/esm/product/recordpanel/recordpanel.recipe.js +54 -45
  19. package/dist/esm/product/recordstable/editable-field.js +11 -8
  20. package/dist/esm/product/recordstable/recordstable.tag.js +8 -3
  21. package/dist/esm/product/richtext/index.js +1 -1
  22. package/dist/esm/product/richtext/richtext.js +439 -22
  23. package/dist/esm/product/richtext/richtext.recipe.js +60 -10
  24. package/dist/recordpanel.css +47 -41
  25. package/dist/recordpanel.layered.css +47 -41
  26. package/dist/richtext.css +31 -5
  27. package/dist/richtext.layered.css +31 -5
  28. package/dist/styles.css +79 -47
  29. package/dist/styles.layered.css +79 -47
  30. package/dist/types/core/caret-rect.d.ts +16 -0
  31. package/dist/types/core/index.d.ts +3 -0
  32. package/dist/types/core/mention.d.ts +27 -0
  33. package/dist/types/core/use-mention-trigger.d.ts +41 -0
  34. package/dist/types/product/recordpanel/index.d.ts +1 -1
  35. package/dist/types/product/recordpanel/recordpanel.d.ts +81 -27
  36. package/dist/types/product/recordstable/editable-field.d.ts +11 -8
  37. package/dist/types/product/recordstable/recordstable.tag.d.ts +8 -3
  38. package/dist/types/product/richtext/index.d.ts +1 -1
  39. package/dist/types/product/richtext/richtext.d.ts +168 -7
  40. package/package.json +1 -1
@@ -1,8 +1,49 @@
1
1
  import * as React from "react";
2
+ import { type MentionAnchor } from "../../core";
2
3
  export type RichTextValue = {
3
4
  title: string;
4
5
  html: string;
5
6
  };
7
+ /**
8
+ * A named text-color / highlight-color pair — the 12 tones Attio's own note
9
+ * editor offers as inline marks (measured live, read-only, 2026-09-24;
10
+ * re-verified against the same note before shipping — see HANDOFF). Exported
11
+ * so a consumer wanting the same palette elsewhere (a tag picker, a legend)
12
+ * doesn't have to re-type it.
13
+ */
14
+ export type RichTextColorTone = {
15
+ id: string;
16
+ label: string;
17
+ /** Value passed to `execCommand("foreColor", …)`. */
18
+ text: string;
19
+ /** Value passed to `execCommand("hiliteColor", …)`. */
20
+ highlight: string;
21
+ };
22
+ export declare const RICHTEXT_COLOR_TONES: RichTextColorTone[];
23
+ /** A reference to insert as an atomic mention chip (#428). */
24
+ export type RichTextMention = {
25
+ id: string;
26
+ label: string;
27
+ };
28
+ /** State handed to `renderMentionPicker` each time it's asked to show. */
29
+ export type RichTextMentionTriggerState = {
30
+ /** Text typed after the trigger character so far. */
31
+ query: string;
32
+ /** Where to anchor the picker, in viewport coordinates. */
33
+ anchor: MentionAnchor;
34
+ /** Insert `mention` as a chip in place of the "@query" text, and dismiss. */
35
+ select: (mention: RichTextMention) => void;
36
+ /** Dismiss without inserting anything — e.g. on Escape or an outside click. */
37
+ close: () => void;
38
+ };
39
+ export type RichTextEditorHandle = {
40
+ /** Insert `mention` as an atomic, non-editable chip — in place of the
41
+ * active "@query" trigger text when one is open, otherwise at the current
42
+ * caret (or the end of the body, with no caret in it at all). */
43
+ insertMention: (mention: RichTextMention) => void;
44
+ /** Focus the body editor. */
45
+ focus: () => void;
46
+ };
6
47
  export type RichTextEditorProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onChange" | "placeholder"> & {
7
48
  /** Accessible name for the title field. */
8
49
  titleLabel?: string;
@@ -10,21 +51,141 @@ export type RichTextEditorProps = Omit<React.ComponentPropsWithoutRef<"div">, "c
10
51
  bodyLabel?: string;
11
52
  /** Message shown when asking for a link URL. */
12
53
  linkPromptLabel?: string;
54
+ /** Message shown when asking for an image URL. Attio uploads a file
55
+ * through its own storage; crisp is dependency-free, so this follows the
56
+ * same `window.prompt` pattern as `linkPromptLabel` rather than inventing
57
+ * an upload pipeline. */
58
+ imagePromptLabel?: string;
59
+ /** The text-color picker's trigger button — its visible label and
60
+ * accessible name (default "Text", matching Attio's own copy). */
61
+ textColorLabel?: string;
62
+ /** The highlight-color picker's trigger button (default "Highlight"). */
63
+ highlightColorLabel?: string;
64
+ /** aria-label for each picker's "clear" swatch. */
65
+ clearColorLabel?: string;
66
+ /** The block-type toolbar button — visible label and accessible name
67
+ * (default "Body", matching Attio's own copy for the current-block
68
+ * indicator, even though the seven choices behind it include far more
69
+ * than "Body"). */
70
+ blockMenuLabel?: string;
71
+ /** Placeholder for the block menu's search field, shown when it's opened
72
+ * from the toolbar (default "Search items…", matching Attio's own copy). */
73
+ blockMenuSearchPlaceholder?: string;
74
+ /** Shown when the search field matches nothing. */
75
+ blockMenuEmptyLabel?: string;
76
+ /** Character that opens the mention picker. Default `"@"`. */
77
+ mentionTrigger?: string;
78
+ /** Renders a consumer-supplied picker anchored at the caret when
79
+ * `mentionTrigger` is typed (#372, #428). crisp detects the trigger,
80
+ * measures where it sits, and inserts whatever is picked — the picker
81
+ * itself (avatars, grouping, a "create new" row) is the caller's, since
82
+ * that varies too much by app to have one measured shape. */
83
+ renderMentionPicker?: (state: RichTextMentionTriggerState) => React.ReactNode;
13
84
  defaultTitle?: string;
14
85
  /** Initial body HTML. */
15
86
  defaultHtml?: string;
16
87
  titlePlaceholder?: string;
17
88
  placeholder?: string;
18
89
  onChange?: (value: RichTextValue) => void;
90
+ /** Renders both fields as read-only — Attio's own note preview (the
91
+ * sidebar list, a record's Notes tab) is the SAME editor with
92
+ * `contentEditable` toggled off, not a separate stripped-text summary, so
93
+ * headings/lists/marks all survive into it. No toolbar renders in this
94
+ * mode. */
95
+ readOnly?: boolean;
19
96
  className?: string;
20
97
  };
21
98
  /**
22
- * A block rich-text editor — measured from a notes editor. A large title,
23
- * a prose body (headings, lists, quote, marks, links), and a floating toolbar
24
- * that appears over a text selection. Built on contentEditable, dependency-free.
99
+ * A block rich-text editor — measured from Attio's own notes editor. A large
100
+ * title, a prose body (h1/h2/h3, bulleted/numbered lists, an image block,
101
+ * marks), a floating selection toolbar, a searchable block-type menu, and a
102
+ * caret-anchored mention trigger. Built on contentEditable, dependency-free.
25
103
  * Uncontrolled: seed with `defaultTitle`/`defaultHtml`, read via `onChange`.
104
+ * Imperative actions (`insertMention`, `focus`) go through a ref handle
105
+ * rather than a new prop each — the rest of the surface stays uncontrolled.
106
+ *
107
+ * **No underline, no inline code, no blockquote** — all three confirmed
108
+ * absent from Attio's live toolbar (7 controls) and its exhaustive 7-item
109
+ * block-type menu; the generic baseline this component used to ship
110
+ * overshot Attio on all three. `.crisp-rte-body blockquote` still renders
111
+ * correctly for content pasted in from elsewhere — only the control for it
112
+ * is gone.
113
+ *
114
+ * **The block menu's own chrome (356px, radius 12px, `elevation.floating`,
115
+ * a "Search items…" input, 7 rows at 52px with a 16px icon) was measured
116
+ * through the in-editor "Insert" button, not the toolbar's "Body ▾".**
117
+ * Clicking "Body ▾" itself was tried repeatedly, with both synthetic and
118
+ * real CDP-dispatched trusted clicks, single- and multi-step sessions, and
119
+ * consistently closed the ENTIRE note a fraction of a second after opening
120
+ * something (confirmed via a 0/50/150/400ms readback: the toolbar portal was
121
+ * still there at +50ms, everything was gone by +150ms) — a nested-popover
122
+ * dismissable-layer interaction synthetic input in this environment could
123
+ * not reliably drive. The Insert button opens the identical shape ("The
124
+ * same-shaped popover... is also what opens from the 'Body' button" — the
125
+ * source measurement's own finding, and Insert's own DOM confirms the exact
126
+ * same `role="dialog"`/search-input/`role="option"` structure), so the
127
+ * chrome below is that measurement, reused for both entry points. What's
128
+ * NOT independently confirmed is "Body ▾"'s own anchor side — Insert opens
129
+ * `data-side="right"` (it sits at the end of a line, with no room below);
130
+ * the toolbar sits at the TOP of the selection, so this component opens the
131
+ * menu below it, a reasoned default rather than a reading. The `/`-in-body
132
+ * trigger, and the "@" mention trigger, both anchor at the caret and do not
133
+ * steal focus from the body — the toolbar's own "Body ▾" click DOES focus a
134
+ * real search input, since there's no in-document query to read a filter
135
+ * from until one exists.
136
+ *
137
+ * Not built: Attio's own separate "Insert" trigger button (a third way to
138
+ * reach the identical menu, sitting at the end of the body's last line) —
139
+ * only the two entry points #426 asked for.
26
140
  */
27
- export declare function RichTextEditor({ defaultTitle, defaultHtml, titlePlaceholder, placeholder, onChange, className, titleLabel, bodyLabel, linkPromptLabel, ...rest }: RichTextEditorProps): React.JSX.Element;
28
- export declare namespace RichTextEditor {
29
- var displayName: string;
30
- }
141
+ export declare const RichTextEditor: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "children" | "onChange" | "placeholder"> & {
142
+ /** Accessible name for the title field. */
143
+ titleLabel?: string;
144
+ /** Accessible name for the body editor. */
145
+ bodyLabel?: string;
146
+ /** Message shown when asking for a link URL. */
147
+ linkPromptLabel?: string;
148
+ /** Message shown when asking for an image URL. Attio uploads a file
149
+ * through its own storage; crisp is dependency-free, so this follows the
150
+ * same `window.prompt` pattern as `linkPromptLabel` rather than inventing
151
+ * an upload pipeline. */
152
+ imagePromptLabel?: string;
153
+ /** The text-color picker's trigger button — its visible label and
154
+ * accessible name (default "Text", matching Attio's own copy). */
155
+ textColorLabel?: string;
156
+ /** The highlight-color picker's trigger button (default "Highlight"). */
157
+ highlightColorLabel?: string;
158
+ /** aria-label for each picker's "clear" swatch. */
159
+ clearColorLabel?: string;
160
+ /** The block-type toolbar button — visible label and accessible name
161
+ * (default "Body", matching Attio's own copy for the current-block
162
+ * indicator, even though the seven choices behind it include far more
163
+ * than "Body"). */
164
+ blockMenuLabel?: string;
165
+ /** Placeholder for the block menu's search field, shown when it's opened
166
+ * from the toolbar (default "Search items…", matching Attio's own copy). */
167
+ blockMenuSearchPlaceholder?: string;
168
+ /** Shown when the search field matches nothing. */
169
+ blockMenuEmptyLabel?: string;
170
+ /** Character that opens the mention picker. Default `"@"`. */
171
+ mentionTrigger?: string;
172
+ /** Renders a consumer-supplied picker anchored at the caret when
173
+ * `mentionTrigger` is typed (#372, #428). crisp detects the trigger,
174
+ * measures where it sits, and inserts whatever is picked — the picker
175
+ * itself (avatars, grouping, a "create new" row) is the caller's, since
176
+ * that varies too much by app to have one measured shape. */
177
+ renderMentionPicker?: (state: RichTextMentionTriggerState) => React.ReactNode;
178
+ defaultTitle?: string;
179
+ /** Initial body HTML. */
180
+ defaultHtml?: string;
181
+ titlePlaceholder?: string;
182
+ placeholder?: string;
183
+ onChange?: (value: RichTextValue) => void;
184
+ /** Renders both fields as read-only — Attio's own note preview (the
185
+ * sidebar list, a record's Notes tab) is the SAME editor with
186
+ * `contentEditable` toggled off, not a separate stripped-text summary, so
187
+ * headings/lists/marks all survive into it. No toolbar renders in this
188
+ * mode. */
189
+ readOnly?: boolean;
190
+ className?: string;
191
+ } & React.RefAttributes<RichTextEditorHandle>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",