@crisp-ui-kit/crisp 0.50.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 (128) hide show
  1. package/dist/cjs/components/composewindow/composewindow.js +144 -0
  2. package/dist/cjs/components/composewindow/composewindow.recipe.js +45 -0
  3. package/dist/cjs/components/composewindow/index.js +8 -0
  4. package/dist/cjs/components/datatable/datatable.js +224 -43
  5. package/dist/cjs/components/datatable/datatable.recipe.js +37 -3
  6. package/dist/cjs/components/datatable/index.js +6 -0
  7. package/dist/cjs/components/datepickerfield/datepickerfield.js +25 -15
  8. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +25 -8
  9. package/dist/cjs/components/dialog/dialog.recipe.js +9 -1
  10. package/dist/cjs/components/menu/menu.js +4 -2
  11. package/dist/cjs/components/multiselect/multiselect.recipe.js +14 -8
  12. package/dist/cjs/components/select/select.js +2 -1
  13. package/dist/cjs/components/table/table.recipe.js +12 -3
  14. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
  15. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +18 -7
  16. package/dist/cjs/core/caret-rect.js +73 -0
  17. package/dist/cjs/core/cell-date.js +11 -0
  18. package/dist/cjs/core/index.js +8 -1
  19. package/dist/cjs/core/mention.js +32 -0
  20. package/dist/cjs/core/use-mention-trigger.js +124 -0
  21. package/dist/cjs/index.js +1 -0
  22. package/dist/cjs/product/recordpage/recordpage.js +88 -10
  23. package/dist/cjs/product/recordpage/recordpage.recipe.js +30 -0
  24. package/dist/cjs/product/recordpanel/recordpanel.js +11 -29
  25. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +55 -17
  26. package/dist/cjs/product/recordstable/editable-field.js +113 -0
  27. package/dist/cjs/product/recordstable/index.js +4 -1
  28. package/dist/cjs/product/recordstable/recordstable.js +67 -11
  29. package/dist/cjs/product/recordstable/recordstable.recipe.js +9 -1
  30. package/dist/cjs/product/recordstable/recordstable.tag.js +8 -3
  31. package/dist/cjs/product/richtext/index.js +2 -1
  32. package/dist/cjs/product/richtext/richtext.js +440 -23
  33. package/dist/cjs/product/richtext/richtext.recipe.js +59 -9
  34. package/dist/cjs/product/settingsshell/settingsshell.recipe.js +13 -4
  35. package/dist/cjs/product/sidebar/sidebar.js +15 -3
  36. package/dist/cjs/product/sidebar/sidebar.recipe.js +21 -2
  37. package/dist/composewindow.css +24 -0
  38. package/dist/composewindow.d.ts +7 -0
  39. package/dist/composewindow.layered.css +26 -0
  40. package/dist/datatable.css +33 -14
  41. package/dist/datatable.layered.css +33 -14
  42. package/dist/datepickerfield.css +9 -4
  43. package/dist/datepickerfield.layered.css +9 -4
  44. package/dist/dialog.css +2 -1
  45. package/dist/dialog.layered.css +2 -1
  46. package/dist/esm/components/composewindow/composewindow.js +107 -0
  47. package/dist/esm/components/composewindow/composewindow.recipe.js +42 -0
  48. package/dist/esm/components/composewindow/index.js +2 -0
  49. package/dist/esm/components/datatable/datatable.js +226 -45
  50. package/dist/esm/components/datatable/datatable.recipe.js +37 -3
  51. package/dist/esm/components/datatable/index.js +6 -0
  52. package/dist/esm/components/datepickerfield/datepickerfield.js +26 -16
  53. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +25 -8
  54. package/dist/esm/components/dialog/dialog.recipe.js +9 -1
  55. package/dist/esm/components/menu/menu.js +4 -2
  56. package/dist/esm/components/multiselect/multiselect.recipe.js +14 -8
  57. package/dist/esm/components/select/select.js +2 -1
  58. package/dist/esm/components/table/table.recipe.js +12 -3
  59. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
  60. package/dist/esm/components/tabletoolbar/tabletoolbar.js +18 -7
  61. package/dist/esm/core/caret-rect.js +70 -0
  62. package/dist/esm/core/cell-date.js +10 -0
  63. package/dist/esm/core/index.js +4 -1
  64. package/dist/esm/core/mention.js +29 -0
  65. package/dist/esm/core/use-mention-trigger.js +88 -0
  66. package/dist/esm/index.js +1 -0
  67. package/dist/esm/product/recordpage/recordpage.js +89 -11
  68. package/dist/esm/product/recordpage/recordpage.recipe.js +30 -0
  69. package/dist/esm/product/recordpanel/index.js +1 -1
  70. package/dist/esm/product/recordpanel/recordpanel.js +13 -31
  71. package/dist/esm/product/recordpanel/recordpanel.recipe.js +56 -18
  72. package/dist/esm/product/recordstable/editable-field.js +76 -0
  73. package/dist/esm/product/recordstable/index.js +1 -0
  74. package/dist/esm/product/recordstable/recordstable.js +69 -13
  75. package/dist/esm/product/recordstable/recordstable.recipe.js +9 -1
  76. package/dist/esm/product/recordstable/recordstable.tag.js +8 -3
  77. package/dist/esm/product/richtext/index.js +1 -1
  78. package/dist/esm/product/richtext/richtext.js +439 -22
  79. package/dist/esm/product/richtext/richtext.recipe.js +60 -10
  80. package/dist/esm/product/settingsshell/settingsshell.recipe.js +13 -4
  81. package/dist/esm/product/sidebar/sidebar.js +15 -3
  82. package/dist/esm/product/sidebar/sidebar.recipe.js +21 -2
  83. package/dist/multiselect.css +8 -8
  84. package/dist/multiselect.layered.css +8 -8
  85. package/dist/recordpage.css +25 -13
  86. package/dist/recordpage.layered.css +25 -13
  87. package/dist/recordpanel.css +43 -20
  88. package/dist/recordpanel.layered.css +43 -20
  89. package/dist/recordstable.css +9 -2
  90. package/dist/recordstable.layered.css +9 -2
  91. package/dist/richtext.css +31 -5
  92. package/dist/richtext.layered.css +31 -5
  93. package/dist/settingsshell.css +2 -2
  94. package/dist/settingsshell.layered.css +2 -2
  95. package/dist/sidebar.css +7 -2
  96. package/dist/sidebar.layered.css +7 -2
  97. package/dist/styles.css +199 -74
  98. package/dist/styles.layered.css +199 -74
  99. package/dist/table.css +4 -2
  100. package/dist/table.layered.css +4 -2
  101. package/dist/tabletoolbar.css +1 -1
  102. package/dist/tabletoolbar.layered.css +1 -1
  103. package/dist/types/components/composewindow/composewindow.d.ts +80 -0
  104. package/dist/types/components/composewindow/composewindow.recipe.d.ts +3 -0
  105. package/dist/types/components/composewindow/index.d.ts +2 -0
  106. package/dist/types/components/datatable/datatable.d.ts +58 -4
  107. package/dist/types/components/datatable/index.d.ts +1 -1
  108. package/dist/types/components/datepickerfield/datepickerfield.d.ts +18 -4
  109. package/dist/types/components/menu/menu.d.ts +10 -1
  110. package/dist/types/components/select/select.d.ts +6 -1
  111. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +29 -5
  112. package/dist/types/core/caret-rect.d.ts +16 -0
  113. package/dist/types/core/cell-date.d.ts +17 -0
  114. package/dist/types/core/index.d.ts +4 -1
  115. package/dist/types/core/mention.d.ts +27 -0
  116. package/dist/types/core/use-mention-trigger.d.ts +41 -0
  117. package/dist/types/index.d.ts +1 -0
  118. package/dist/types/product/recordpage/recordpage.d.ts +59 -1
  119. package/dist/types/product/recordpanel/index.d.ts +1 -1
  120. package/dist/types/product/recordpanel/recordpanel.d.ts +81 -17
  121. package/dist/types/product/recordstable/editable-field.d.ts +48 -0
  122. package/dist/types/product/recordstable/index.d.ts +1 -0
  123. package/dist/types/product/recordstable/recordstable.d.ts +29 -0
  124. package/dist/types/product/recordstable/recordstable.tag.d.ts +8 -3
  125. package/dist/types/product/richtext/index.d.ts +1 -1
  126. package/dist/types/product/richtext/richtext.d.ts +168 -7
  127. package/dist/types/product/sidebar/sidebar.d.ts +60 -4
  128. package/package.json +6 -1
@@ -1,35 +1,99 @@
1
1
  import * as React from "react";
2
- import { type Column, type Row } from "../recordstable";
2
+ import { type AvatarProps } from "../../components/avatar";
3
+ import type { Row } from "../recordstable";
4
+ type IconType = React.ComponentType<{
5
+ size?: number;
6
+ strokeWidth?: number;
7
+ }>;
8
+ /** One Highlights card — label, value and an optional icon, laid out the
9
+ * way Attio's peek panel lays its own out (measured live, #448): a 2-column
10
+ * grid, no card chrome (no border/background/shadow), a muted label row
11
+ * with a trailing icon, a filled-colour value below it. */
12
+ export type RecordPanelHighlight = {
13
+ key: string;
14
+ label: string;
15
+ value: React.ReactNode;
16
+ icon?: IconType;
17
+ };
3
18
  export type RecordPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "title"> & {
4
- /** The record to show; render nothing when null. */
5
19
  /** Label for the close control. */
6
20
  closeLabel?: string;
21
+ /**
22
+ * A toolbar row above the panel's own identity header — measured live on
23
+ * app.attio.com's peek (hover a Companies row, click "Open preview"):
24
+ * Close, then Previous/Next record on the left, an "Open record" icon on
25
+ * the right, a hairline border beneath separating it from the identity
26
+ * block below. Close always renders (`onClose` is required); Previous/
27
+ * Next/Open record each render only when given a handler — a consumer
28
+ * with no adjacent-row concept, or nowhere to open the record's own full
29
+ * page, simply omits them, leaving just Close.
30
+ */
31
+ onOpenRecord?: () => void;
32
+ openRecordLabel?: string;
33
+ onPrevious?: () => void;
34
+ previousRecordLabel?: string;
35
+ onNext?: () => void;
36
+ nextRecordLabel?: string;
7
37
  /**
8
38
  * The panel's heading, and so its accessible name (via `aria-labelledby`,
9
39
  * not a stringified `aria-label` — it also drives the visible title, which
10
- * can be any node). Defaults to the entity column's name, same as before
11
- * this prop existed; pass it to title the panel by something else, e.g.
12
- * "{Collection} record".
40
+ * can be any node). Defaults to `record.name`.
13
41
  */
14
42
  title?: React.ReactNode;
15
- /** Heading above the field list. */
16
- detailsLabel?: React.ReactNode;
17
- /** Shown in place of a field that has no value. */
18
- emptyLabel?: React.ReactNode;
19
- /** Rendered below the field list, e.g. an Edit button. Omit for no footer. */
43
+ /** Avatar image URL beside the title. Omit for an initials tile built
44
+ * from `record.name`'s first character. */
45
+ avatar?: string;
46
+ /** Tone for the initials tile when no `avatar` image is given (default
47
+ * "brand", matching Attio's own peek). */
48
+ avatarTone?: NonNullable<AvatarProps["tone"]>;
49
+ /** The record to show; render nothing when null. Only `id`/`name` are
50
+ * read directly here — everything else the panel shows comes from
51
+ * `highlights`/`activity`, the consumer's own data. */
52
+ record: (Row & {
53
+ name?: string;
54
+ }) | null;
55
+ /** Heading above the highlight cards (default "Highlights"). Omit the
56
+ * whole section by omitting `highlights`. */
57
+ highlightsLabel?: React.ReactNode;
58
+ /**
59
+ * Highlight cards — label, value, optional icon (#448). Attio's own peek
60
+ * shows these as a fixed set of business-pipeline stats (Connection
61
+ * strength, Estimated ARR, …); crisp has no such fixed set, so this is
62
+ * fully consumer-supplied, laid out in Attio's own measured 2-column
63
+ * grid. Omit for no Highlights section at all.
64
+ */
65
+ highlights?: RecordPanelHighlight[];
66
+ /** Heading above the activity feed (default "Activity"). */
67
+ activityLabel?: React.ReactNode;
68
+ /** Label for the trailing "View all" action beside the Activity heading.
69
+ * Only rendered when `onViewAllActivity` is given. */
70
+ viewAllActivityLabel?: React.ReactNode;
71
+ onViewAllActivity?: () => void;
72
+ /**
73
+ * Activity feed content (#448) — a consumer-supplied slot. crisp has no
74
+ * activity-feed model of its own (no fixed shape for "who did what,
75
+ * when" — that's app data); this only wraps whatever's given with the
76
+ * measured "Activity" heading Attio's peek shows above its own feed. Omit
77
+ * for no Activity section at all.
78
+ */
79
+ activity?: React.ReactNode;
80
+ /** Rendered below the panel's content, e.g. an Edit button. Omit for no footer. */
20
81
  footer?: React.ReactNode;
21
- record: Row | null;
22
- columns: Column[];
23
82
  onClose: () => void;
24
83
  className?: string;
25
84
  };
26
85
  /**
27
- * Right-side record peek panel — shows one record's fields, opened from a
28
- * RecordsTable row (via `onOpenRecord`). Render it inside a `position:
29
- * relative` container (e.g. the app main area); it slides in from the right and
30
- * overlays the content.
86
+ * Right-side record peek panel — Attio's own peek shows a Highlights card
87
+ * area and an Activity feed (measured live: hover a Companies row, click its
88
+ * "Open preview" trigger), never an editable field list (#448 — a "Details"
89
+ * list was this component's own invention before, and #431's editable API
90
+ * had nothing real in this panel to attach to; that API stays on
91
+ * `RecordPage`'s own Details column, where Attio does let you edit in
92
+ * place). Render it inside a `position: relative` container (e.g. the app
93
+ * main area); it slides in from the right and overlays the content.
31
94
  */
32
- export declare function RecordPanel({ record, columns, onClose, className, closeLabel, title, detailsLabel, emptyLabel, footer, ...rest }: RecordPanelProps): React.JSX.Element | null;
95
+ export declare function RecordPanel({ record, onClose, className, closeLabel, onOpenRecord, openRecordLabel, onPrevious, previousRecordLabel, onNext, nextRecordLabel, title, avatar, avatarTone, highlightsLabel, highlights, activityLabel, viewAllActivityLabel, onViewAllActivity, activity, footer, ...rest }: RecordPanelProps): React.JSX.Element | null;
33
96
  export declare namespace RecordPanel {
34
97
  var displayName: string;
35
98
  }
99
+ export {};
@@ -0,0 +1,48 @@
1
+ /** A `select`-editor field's choices — same shape DataTable's own
2
+ * `DataEditableOption` uses (#385/#430), kept as its own type (not
3
+ * `Column.options`/`StatusOption`, whose `color` is required for the
4
+ * `status` picker) so this stays purely additive. */
5
+ export type EditableOption = {
6
+ value: string;
7
+ label: string;
8
+ color?: string;
9
+ };
10
+ /**
11
+ * The async-commit state machine behind `RecordPage`'s editable Details
12
+ * column (#431) — the same shape `DataTable`'s own `commitEdit` uses
13
+ * (#385/#430): a sync `onEdit` (returns `undefined`) commits and closes
14
+ * immediately; a Promise-returning `onEdit` keeps the field open and pending
15
+ * while it settles, then either closes it (resolve) or closes it AND
16
+ * reverts while showing the rejection's message (reject). The revert needs
17
+ * no extra code here either: `RecordPage` never mutates `record` itself, so
18
+ * once `editingKey` clears, the field simply renders `record[key]` again —
19
+ * whatever it still says.
20
+ *
21
+ * `RecordPanel` (the peek panel) used to share this hook too, before #448
22
+ * found that Attio's own peek has no editable field at all to attach it to
23
+ * — only `RecordPage`'s Details column does. Kept as its own module rather
24
+ * than folded back into `recordpage.tsx`: a second consumer turning up
25
+ * again is a smaller change than re-extracting it would be.
26
+ */
27
+ export declare function useEditableField(onEdit?: (id: string, key: string, value: string) => Promise<void> | void): {
28
+ editingKey: string | null;
29
+ pendingKey: string | null;
30
+ error: {
31
+ key: string;
32
+ message: string;
33
+ } | null;
34
+ openEditing: (key: string) => void;
35
+ closeEditing: () => void;
36
+ commit: (id: string, key: string, value: string) => void;
37
+ };
38
+ /**
39
+ * Which plain editor a column gets when it's `editable` without an
40
+ * `editor: "select" | "switch"` — identical to `DataTable`'s own
41
+ * `editorFor` (#385): number/currency get a numeric field, date a native
42
+ * date field, everything else (text, entity, tags, url, status, reference)
43
+ * a plain text field. Kept as one function so the two never drift apart.
44
+ */
45
+ export declare function editableInputType(colType: string | undefined): {
46
+ type: string;
47
+ inputMode?: "decimal" | "text";
48
+ };
@@ -1,2 +1,3 @@
1
+ export { type EditableOption, editableInputType, useEditableField, } from "./editable-field";
1
2
  export { type Column, type ColumnType, defaultColumns, defaultRows, type EntityValue, RecordsTable, type RecordsTableProps, type Row, tagTone, } from "./recordstable";
2
3
  export { recordstableRecipe } from "./recordstable.recipe";
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import type { EditableOption } from "./editable-field";
2
3
  type IconType = React.ComponentType<{
3
4
  size?: number;
4
5
  strokeWidth?: number;
@@ -29,6 +30,14 @@ export type Column = {
29
30
  options?: StatusOption[];
30
31
  /** Choices for a `reference` column (a searchable record picker). */
31
32
  refOptions?: ReferenceOption[];
33
+ /** Adds a trailing "Create {createLabel}" row to the picker (Attio's own
34
+ * always has one) — rendered only when this is provided. Picking it hands
35
+ * back the picker's current search text, e.g. to open a create flow
36
+ * pre-seeded with it. */
37
+ onCreateReference?: (query: string) => void;
38
+ /** The object noun in "Create {createLabel}" (Attio: "Create Person").
39
+ * Only used with `onCreateReference`; defaults to "record". */
40
+ createLabel?: string;
32
41
  /**
33
42
  * Full custom cell content — an escape hatch for a display crisp can't
34
43
  * express via `type` (sign-coloured currency, a maskable amount, a custom
@@ -65,6 +74,26 @@ export type Column = {
65
74
  * currency string still sums and sorts numerically.
66
75
  */
67
76
  format?: (value: unknown, row: Row) => string;
77
+ /**
78
+ * Opts this column into `RecordPanel`/`RecordPage`'s editable-field API
79
+ * (#431) — click-to-edit there (not this table's own type-driven
80
+ * mechanism, which those two components don't use). Ignored by
81
+ * `RecordsTable` itself. A plain `editable` column, with no `editor`,
82
+ * opens a text/number/date field (inferred from `type`, same inference
83
+ * `DataTable`'s own `editable` columns use — #385); `editor` below picks
84
+ * a `select`/`switch` picker instead. Both consumers require `onEdit` to
85
+ * actually be given for a field to open at all.
86
+ */
87
+ editable?: boolean;
88
+ /** `select`/`switch` render as the field's own live, always-interactive
89
+ * content instead of opening on click — see `DataTableColumn.editor`
90
+ * (#385) for the identical convention and reasoning. `select` reads
91
+ * `editOptions`. */
92
+ editor?: "select" | "switch";
93
+ /** Options for `editor: "select"` — same shape `DataTable`'s own
94
+ * `DataEditableOption` uses. Not `options` (`StatusOption` requires
95
+ * `color`, for this type's own `status` picker) so this stays additive. */
96
+ editOptions?: EditableOption[];
68
97
  };
69
98
  export type EntityValue = {
70
99
  name: string;
@@ -3,8 +3,13 @@
3
3
  * `border-radius: ${c.radius.chip}`, 14px/20px at weight 500, and a 1px inset ring in the
4
4
  * tone's colour.
5
5
  *
6
- * Shared because RecordsTable and RecordPanel render the *same* chip. It used
7
- * to be pasted into both recipes, and the two copies had already started to
8
- * drift — adding the measured inset ring meant patching the same rules twice.
6
+ * Its own module (not pasted into `recordstable.recipe.ts`) because it used
7
+ * to be shared with `RecordPanel`'s own Details field list too, and the two
8
+ * copies had already started to drift before that sharing existed — adding
9
+ * the measured inset ring meant patching the same rules twice. `RecordPanel`
10
+ * no longer renders a Details list (#448, Attio's own peek doesn't have
11
+ * one), so `RecordsTable` is this module's only consumer now; kept as its
12
+ * own file rather than folded back in, since a second consumer showing up
13
+ * again is exactly how the drift happened the first time.
9
14
  */
10
15
  export declare const recordTagCss: string;
@@ -1,2 +1,2 @@
1
- export { RichTextEditor, type RichTextEditorProps, type RichTextValue, } from "./richtext";
1
+ export { RICHTEXT_COLOR_TONES, type RichTextColorTone, RichTextEditor, type RichTextEditorHandle, type RichTextEditorProps, type RichTextMention, type RichTextMentionTriggerState, type RichTextValue, } from "./richtext";
2
2
  export { richtextRecipe } from "./richtext.recipe";
@@ -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>>;
@@ -11,6 +11,14 @@ export type SidebarNavItem = {
11
11
  icon: React.ComponentType<IconProps>;
12
12
  active?: boolean;
13
13
  trailing?: React.ReactNode;
14
+ /** Either this or `onSelect` — a plain link, or a handler for anything
15
+ * that isn't real navigation. Both may be set; neither is required to
16
+ * make the row *something* clickable (it still renders as a link,
17
+ * defaulting `href` to "#"). Same shape as a `groups` row's
18
+ * `SidebarNavGroupItem` (#384) — `items` is the only place that used to
19
+ * be missing this wiring. */
20
+ href?: string;
21
+ onSelect?: () => void;
14
22
  };
15
23
  /** A row in the Records section — rendered as a tinted 20px glyph avatar. */
16
24
  export type SidebarRecordItem = {
@@ -96,6 +104,20 @@ export type SidebarProps = React.ComponentPropsWithoutRef<"nav"> & {
96
104
  onToggleCollapse?: () => void;
97
105
  /** Workspace switcher content. Defaults to the measured workspace. */
98
106
  workspace?: SidebarWorkspace;
107
+ /**
108
+ * Menu content for the workspace switcher — `MenuItem`/`MenuSeparator`/
109
+ * `MenuLabel` children, the same as a consumer would give `Menu` directly.
110
+ * Omit to leave the switcher a plain (still real, focusable) button with
111
+ * no popup, as before. When given, the switcher becomes a real menu
112
+ * trigger (`aria-haspopup="menu"`, `aria-expanded`, keyboard-openable —
113
+ * rents `Menu`'s own machine, no new one) that opens it — router-agnostic
114
+ * and consistent with the nav API (#384): a `MenuItem`'s own `onSelect` is
115
+ * how a consumer reaches its own navigation, the same as a nav row's
116
+ * (#415: Attio's switcher opens "Account settings" / "Workspace settings",
117
+ * among others — crisp ships no fixed items of its own, since which
118
+ * settings exist is entirely the consumer's).
119
+ */
120
+ workspaceMenu?: React.ReactNode;
99
121
  /** Main nav rows (Home, Tasks, …). Defaults to the measured nav. */
100
122
  items?: SidebarNavItem[];
101
123
  /** The Records section (label + glyph-avatar rows). */
@@ -114,8 +136,18 @@ export type SidebarProps = React.ComponentPropsWithoutRef<"nav"> & {
114
136
  * renders exactly as before this prop existed.
115
137
  */
116
138
  groups?: SidebarNavGroup[];
117
- /** Footer copy (onboarding pill, invite row, trial banner). */
118
- footer?: SidebarFooter;
139
+ /**
140
+ * The footer slot. Defaults to Attio's own onboarding pill + invite row +
141
+ * trial banner (`ATTIO_SIDEBAR_FOOTER`). Pass a `SidebarFooter` object to
142
+ * override just the strings in that fixed shape (as before), any other
143
+ * `ReactNode` — an account/user menu, say — to replace the slot's content
144
+ * entirely (still pinned to the bottom of the nav), or `false` to render
145
+ * no footer at all so a consumer can compose their own element directly
146
+ * below `<Sidebar>` instead (#408: GRC has no onboarding checklist, no
147
+ * invite flow and no trial banner, so none of the fixed copy had anything
148
+ * real to say, and there was no way to put a real account menu here).
149
+ */
150
+ footer?: SidebarFooter | React.ReactNode;
119
151
  /**
120
152
  * Command-palette items, forwarded to the internal `Command`'s own
121
153
  * `items` prop. Omit for Command's built-in Attio set (#364: GRC's
@@ -162,6 +194,20 @@ export declare const Sidebar: React.ForwardRefExoticComponent<Omit<React.Detaile
162
194
  onToggleCollapse?: () => void;
163
195
  /** Workspace switcher content. Defaults to the measured workspace. */
164
196
  workspace?: SidebarWorkspace;
197
+ /**
198
+ * Menu content for the workspace switcher — `MenuItem`/`MenuSeparator`/
199
+ * `MenuLabel` children, the same as a consumer would give `Menu` directly.
200
+ * Omit to leave the switcher a plain (still real, focusable) button with
201
+ * no popup, as before. When given, the switcher becomes a real menu
202
+ * trigger (`aria-haspopup="menu"`, `aria-expanded`, keyboard-openable —
203
+ * rents `Menu`'s own machine, no new one) that opens it — router-agnostic
204
+ * and consistent with the nav API (#384): a `MenuItem`'s own `onSelect` is
205
+ * how a consumer reaches its own navigation, the same as a nav row's
206
+ * (#415: Attio's switcher opens "Account settings" / "Workspace settings",
207
+ * among others — crisp ships no fixed items of its own, since which
208
+ * settings exist is entirely the consumer's).
209
+ */
210
+ workspaceMenu?: React.ReactNode;
165
211
  /** Main nav rows (Home, Tasks, …). Defaults to the measured nav. */
166
212
  items?: SidebarNavItem[];
167
213
  /** The Records section (label + glyph-avatar rows). */
@@ -180,8 +226,18 @@ export declare const Sidebar: React.ForwardRefExoticComponent<Omit<React.Detaile
180
226
  * renders exactly as before this prop existed.
181
227
  */
182
228
  groups?: SidebarNavGroup[];
183
- /** Footer copy (onboarding pill, invite row, trial banner). */
184
- footer?: SidebarFooter;
229
+ /**
230
+ * The footer slot. Defaults to Attio's own onboarding pill + invite row +
231
+ * trial banner (`ATTIO_SIDEBAR_FOOTER`). Pass a `SidebarFooter` object to
232
+ * override just the strings in that fixed shape (as before), any other
233
+ * `ReactNode` — an account/user menu, say — to replace the slot's content
234
+ * entirely (still pinned to the bottom of the nav), or `false` to render
235
+ * no footer at all so a consumer can compose their own element directly
236
+ * below `<Sidebar>` instead (#408: GRC has no onboarding checklist, no
237
+ * invite flow and no trial banner, so none of the fixed copy had anything
238
+ * real to say, and there was no way to put a real account menu here).
239
+ */
240
+ footer?: SidebarFooter | React.ReactNode;
185
241
  /**
186
242
  * Command-palette items, forwarded to the internal `Command`'s own
187
243
  * `items` prop. Omit for Command's built-in Attio set (#364: GRC's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.50.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",
@@ -98,6 +98,11 @@
98
98
  "import": "./dist/esm/components/columncalc/index.js",
99
99
  "require": "./dist/cjs/components/columncalc/index.js"
100
100
  },
101
+ "./composewindow": {
102
+ "types": "./dist/types/components/composewindow/index.d.ts",
103
+ "import": "./dist/esm/components/composewindow/index.js",
104
+ "require": "./dist/cjs/components/composewindow/index.js"
105
+ },
101
106
  "./contextmenu": {
102
107
  "types": "./dist/types/components/contextmenu/index.d.ts",
103
108
  "import": "./dist/esm/components/contextmenu/index.js",