@crisp-ui-kit/crisp 0.50.0 → 0.51.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 (104) 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/cell-date.js +11 -0
  17. package/dist/cjs/core/index.js +2 -1
  18. package/dist/cjs/index.js +1 -0
  19. package/dist/cjs/product/recordpage/recordpage.js +88 -10
  20. package/dist/cjs/product/recordpage/recordpage.recipe.js +30 -0
  21. package/dist/cjs/product/recordpanel/recordpanel.js +54 -4
  22. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +29 -0
  23. package/dist/cjs/product/recordstable/editable-field.js +110 -0
  24. package/dist/cjs/product/recordstable/index.js +4 -1
  25. package/dist/cjs/product/recordstable/recordstable.js +67 -11
  26. package/dist/cjs/product/recordstable/recordstable.recipe.js +9 -1
  27. package/dist/cjs/product/settingsshell/settingsshell.recipe.js +13 -4
  28. package/dist/cjs/product/sidebar/sidebar.js +15 -3
  29. package/dist/cjs/product/sidebar/sidebar.recipe.js +21 -2
  30. package/dist/composewindow.css +24 -0
  31. package/dist/composewindow.d.ts +7 -0
  32. package/dist/composewindow.layered.css +26 -0
  33. package/dist/datatable.css +33 -14
  34. package/dist/datatable.layered.css +33 -14
  35. package/dist/datepickerfield.css +9 -4
  36. package/dist/datepickerfield.layered.css +9 -4
  37. package/dist/dialog.css +2 -1
  38. package/dist/dialog.layered.css +2 -1
  39. package/dist/esm/components/composewindow/composewindow.js +107 -0
  40. package/dist/esm/components/composewindow/composewindow.recipe.js +42 -0
  41. package/dist/esm/components/composewindow/index.js +2 -0
  42. package/dist/esm/components/datatable/datatable.js +226 -45
  43. package/dist/esm/components/datatable/datatable.recipe.js +37 -3
  44. package/dist/esm/components/datatable/index.js +6 -0
  45. package/dist/esm/components/datepickerfield/datepickerfield.js +26 -16
  46. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +25 -8
  47. package/dist/esm/components/dialog/dialog.recipe.js +9 -1
  48. package/dist/esm/components/menu/menu.js +4 -2
  49. package/dist/esm/components/multiselect/multiselect.recipe.js +14 -8
  50. package/dist/esm/components/select/select.js +2 -1
  51. package/dist/esm/components/table/table.recipe.js +12 -3
  52. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
  53. package/dist/esm/components/tabletoolbar/tabletoolbar.js +18 -7
  54. package/dist/esm/core/cell-date.js +10 -0
  55. package/dist/esm/core/index.js +1 -1
  56. package/dist/esm/index.js +1 -0
  57. package/dist/esm/product/recordpage/recordpage.js +89 -11
  58. package/dist/esm/product/recordpage/recordpage.recipe.js +30 -0
  59. package/dist/esm/product/recordpanel/recordpanel.js +55 -5
  60. package/dist/esm/product/recordpanel/recordpanel.recipe.js +29 -0
  61. package/dist/esm/product/recordstable/editable-field.js +73 -0
  62. package/dist/esm/product/recordstable/index.js +1 -0
  63. package/dist/esm/product/recordstable/recordstable.js +69 -13
  64. package/dist/esm/product/recordstable/recordstable.recipe.js +9 -1
  65. package/dist/esm/product/settingsshell/settingsshell.recipe.js +13 -4
  66. package/dist/esm/product/sidebar/sidebar.js +15 -3
  67. package/dist/esm/product/sidebar/sidebar.recipe.js +21 -2
  68. package/dist/multiselect.css +8 -8
  69. package/dist/multiselect.layered.css +8 -8
  70. package/dist/recordpage.css +25 -13
  71. package/dist/recordpage.layered.css +25 -13
  72. package/dist/recordpanel.css +22 -5
  73. package/dist/recordpanel.layered.css +22 -5
  74. package/dist/recordstable.css +9 -2
  75. package/dist/recordstable.layered.css +9 -2
  76. package/dist/settingsshell.css +2 -2
  77. package/dist/settingsshell.layered.css +2 -2
  78. package/dist/sidebar.css +7 -2
  79. package/dist/sidebar.layered.css +7 -2
  80. package/dist/styles.css +147 -54
  81. package/dist/styles.layered.css +147 -54
  82. package/dist/table.css +4 -2
  83. package/dist/table.layered.css +4 -2
  84. package/dist/tabletoolbar.css +1 -1
  85. package/dist/tabletoolbar.layered.css +1 -1
  86. package/dist/types/components/composewindow/composewindow.d.ts +80 -0
  87. package/dist/types/components/composewindow/composewindow.recipe.d.ts +3 -0
  88. package/dist/types/components/composewindow/index.d.ts +2 -0
  89. package/dist/types/components/datatable/datatable.d.ts +58 -4
  90. package/dist/types/components/datatable/index.d.ts +1 -1
  91. package/dist/types/components/datepickerfield/datepickerfield.d.ts +18 -4
  92. package/dist/types/components/menu/menu.d.ts +10 -1
  93. package/dist/types/components/select/select.d.ts +6 -1
  94. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +29 -5
  95. package/dist/types/core/cell-date.d.ts +17 -0
  96. package/dist/types/core/index.d.ts +1 -1
  97. package/dist/types/index.d.ts +1 -0
  98. package/dist/types/product/recordpage/recordpage.d.ts +59 -1
  99. package/dist/types/product/recordpanel/recordpanel.d.ts +11 -1
  100. package/dist/types/product/recordstable/editable-field.d.ts +45 -0
  101. package/dist/types/product/recordstable/index.d.ts +1 -0
  102. package/dist/types/product/recordstable/recordstable.d.ts +29 -0
  103. package/dist/types/product/sidebar/sidebar.d.ts +60 -4
  104. package/package.json +6 -1
@@ -7,7 +7,18 @@ export type EntityValue = {
7
7
  name: string;
8
8
  avatar?: string;
9
9
  };
10
- export type DataCellType = "text" | "entity" | "tags" | "status" | "date" | "number" | "currency";
10
+ /** An `editor: "select"` column's choices. Same shape as RecordsTable's
11
+ * `StatusOption`. */
12
+ export type DataEditableOption = {
13
+ value: string;
14
+ label: string;
15
+ color?: string;
16
+ /** `editor: "reference"` only (#449) — a photo URL for the option's
17
+ * leading avatar; a generic icon renders when omitted. Unused by
18
+ * `editor: "select"`. */
19
+ avatar?: string;
20
+ };
21
+ export type DataCellType = "text" | "entity" | "tags" | "status" | "date" | "datetime" | "number" | "currency";
11
22
  export type DataTableColumn<T> = {
12
23
  key: string;
13
24
  /** Truncates with an ellipsis when it's wider than the
@@ -29,8 +40,44 @@ export type DataTableColumn<T> = {
29
40
  sortable?: boolean;
30
41
  /** Exclude this column from the search filter (default: included). */
31
42
  filterable?: boolean;
32
- /** Allow inline editing (click the cell → text input → Enter/blur commits). */
43
+ /** Allow inline editing. A plain `editable` column opens a text/number/
44
+ * date/datetime field on double-click (Enter/blur/choosing a value
45
+ * commits) — see `editor` for `select`/`switch`/`reference`. */
33
46
  editable?: boolean;
47
+ /** The inline editor an `editable` column opens. Omitted: a plain field
48
+ * inferred from `type` (number/date/datetime/text), gated behind the
49
+ * double-click `editable` opens today. `"select"`/`"switch"`/`"reference"`
50
+ * instead render crisp's own `Select`/`Switch` as the cell's live content,
51
+ * ALWAYS interactive — no double-click gate, no separate edit/view mode —
52
+ * the same convention RecordsTable's `status`/`reference` pickers already
53
+ * use, since choosing an option or flipping a toggle IS the commit
54
+ * gesture; there is nothing for a second click to open. `select` reads
55
+ * `options`; `reference` reads `refOptions` and is search-enabled, the
56
+ * same searchable-`Select` shape as `RecordsTable`'s own
57
+ * `ReferenceCell`. */
58
+ editor?: "select" | "switch" | "reference";
59
+ /** Options for `editor: "select"` (value/label, an optional `color` a
60
+ * caller's own `render` can read) — same shape as RecordsTable's
61
+ * `Column.options` for its `status` picker. */
62
+ options?: DataEditableOption[];
63
+ /** Options for `editor: "reference"` — same shape as `options`, plus
64
+ * `avatar` (#449): a 16px avatar renders before each option's label, a
65
+ * photo when given, a generic fallback icon otherwise — matching
66
+ * RecordsTable's own `ReferenceCell`, which this picker mirrors. */
67
+ refOptions?: DataEditableOption[];
68
+ /** Placeholder for `editor: "reference"`'s search input. Defaults to
69
+ * `Select`'s own generic "Search…" — a generic primitive doesn't hardcode
70
+ * Attio's own copy ("Search records..."), which is workspace/object
71
+ * text, not a crisp-measured value. */
72
+ refSearchPlaceholder?: string;
73
+ /** Adds a trailing "Create {createLabel}" row to `editor: "reference"`'s
74
+ * picker (Attio's own always has one, #449) — rendered only when this is
75
+ * provided. Picking it hands back the picker's current search text, e.g.
76
+ * to open a create flow pre-seeded with it. */
77
+ onCreateReference?: (query: string) => void;
78
+ /** The object noun in "Create {createLabel}" (Attio: "Create Person").
79
+ * Only used with `onCreateReference`; defaults to "record". */
80
+ createLabel?: string;
34
81
  align?: "left" | "right" | "center";
35
82
  width?: number;
36
83
  /** Custom cell renderer. */
@@ -125,8 +172,15 @@ export type DataTableProps<T extends {
125
172
  * of `searchPlaceholder`, which is visible text — this is the
126
173
  * screen-reader-only label. */
127
174
  searchLabel?: string;
128
- /** Commit handler for inline edits. */
129
- onEdit?: (id: string, key: string, value: string) => void;
175
+ /** Commit handler for inline edits. Returning (or resolving) nothing
176
+ * commits synchronously, same as before -- `Promise<void>` is additive.
177
+ * Returning a `Promise` that rejects shows a pending state on the cell
178
+ * while it's in flight, then reverts the cell to its last-known-good value
179
+ * (the cell only ever shows `data`, which this never mutates itself) and
180
+ * renders the rejection's message in an error slot beside the cell,
181
+ * outside `render`'s own output so a hover-only wrapper (a `Tooltip`, say)
182
+ * can't swallow it. */
183
+ onEdit?: (id: string, key: string, value: string) => Promise<void> | void;
130
184
  /** Sticky the first column(s) with a horizontal scroll shadow (default true). */
131
185
  stickyFirst?: boolean;
132
186
  /** Sticky the LAST column with a horizontal scroll shadow, mirroring
@@ -1,2 +1,2 @@
1
- export { DataTable, type DataTableColumn, type DataTableProps, type SortState, } from "./datatable";
1
+ export { type DataCellType, type DataEditableOption, DataTable, type DataTableColumn, type DataTableProps, type SortState, } from "./datatable";
2
2
  export { datatableRecipe } from "./datatable.recipe";
@@ -11,20 +11,34 @@ export type DatePickerFieldProps = Omit<React.ComponentPropsWithoutRef<"div">, "
11
11
  /** Controlled value. Omit to let the field manage it (see defaultValue). */
12
12
  value?: Date | null;
13
13
  defaultValue?: Date | null;
14
- /** Fires with the picked date, controlled or not. */
15
- onValueChange?: (date: Date) => void;
14
+ /**
15
+ * Fires with the picked date, controlled or not — or `null`, when the
16
+ * field's own Clear button is pressed (#394). A field whose value is
17
+ * meaningfully optional (a range's From/To, e.g. — blank legitimately
18
+ * means "unconstrained") needs a way back to that blank state without
19
+ * leaving the field; there was none before this.
20
+ */
21
+ onValueChange?: (date: Date | null) => void;
16
22
  /**
17
23
  * The original name for `onValueChange`, kept working so no consumer breaks.
18
24
  * Prefer `onValueChange`: it is what every other crisp component calls this,
19
25
  * and passing the house name to a component that only knew `onChange` was
20
26
  * not a type error — the handler simply never fired (#202).
21
27
  */
22
- onChange?: (date: Date) => void;
28
+ onChange?: (date: Date | null) => void;
23
29
  placeholder?: string;
24
30
  /** `default` = outlined trigger; `ghost` = transparent (for table cells). */
25
31
  variant?: "default" | "ghost";
26
32
  /** Formats the trigger label (default: e.g. "Jun 3, 2026"). */
27
33
  format?: (date: Date) => string;
34
+ /**
35
+ * Shows a Clear (×) button next to the trigger once a value is set
36
+ * (default `true`). Set `false` for a field whose value is never
37
+ * meaningfully optional.
38
+ */
39
+ clearable?: boolean;
40
+ /** aria-label on the Clear button (default "Clear date"). */
41
+ clearLabel?: string;
28
42
  /** Quick-pick chips under the calendar. */
29
43
  presets?: DatePreset[];
30
44
  /** Forwarded to the calendar — constraints, locale, ariaLabels and the rest. */
@@ -37,7 +51,7 @@ export type DatePickerFieldProps = Omit<React.ComponentPropsWithoutRef<"div">, "
37
51
  * popover. This is what `DatePicker` used to be; `DatePicker` is now
38
52
  * seed-design's bare calendar, and this composes it.
39
53
  */
40
- export declare function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder, variant, format, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }: DatePickerFieldProps): React.JSX.Element;
54
+ export declare function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder, variant, format, clearable, clearLabel, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }: DatePickerFieldProps): React.JSX.Element;
41
55
  export declare namespace DatePickerField {
42
56
  var displayName: string;
43
57
  }
@@ -13,6 +13,15 @@ export type MenuProps = {
13
13
  /** Fires with the next open state, controlled or not. */
14
14
  onOpenChange?: (open: boolean) => void;
15
15
  className?: string;
16
+ /**
17
+ * Extra class(es) for the popover surface itself (`.crisp-menu-content`).
18
+ * `className` alone can't reach it: the surface is portaled to
19
+ * `document.body` (#339) so it's no longer a DOM descendant of the root
20
+ * `className` lands on, and a descendant selector scoped to the root
21
+ * silently matches nothing there — found wiring the workspace switcher's
22
+ * wider, Attio-measured popover (#415).
23
+ */
24
+ contentClassName?: string;
16
25
  "aria-label"?: string;
17
26
  };
18
27
  /**
@@ -22,7 +31,7 @@ export type MenuProps = {
22
31
  * typeahead, Enter/Space select, Esc closes and returns focus to the trigger);
23
32
  * closes on outside click. Flips up / right-aligns when space is tight.
24
33
  */
25
- export declare function Menu({ trigger, children, align, open: openProp, defaultOpen, onOpenChange, className, "aria-label": ariaLabel, }: MenuProps): React.JSX.Element;
34
+ export declare function Menu({ trigger, children, align, open: openProp, defaultOpen, onOpenChange, className, contentClassName, "aria-label": ariaLabel, }: MenuProps): React.JSX.Element;
26
35
  export declare namespace Menu {
27
36
  var displayName: string;
28
37
  }
@@ -47,6 +47,11 @@ export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" |
47
47
  /** Show a search field at the top of the list that filters options (Linear/cmdk style). */
48
48
  searchable?: boolean;
49
49
  searchPlaceholder?: string;
50
+ /** Fires on every keystroke in the search field. `Select` filters `options`
51
+ * itself, so this exists for a consumer who needs the raw text too — a
52
+ * trailing "create" row seeded with it, a server-side search, and so on.
53
+ * Only meaningful with `searchable`. */
54
+ onSearchChange?: (query: string) => void;
50
55
  /** Shown when the search matches nothing. */
51
56
  emptyLabel?: string;
52
57
  disabled?: boolean;
@@ -64,7 +69,7 @@ export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" |
64
69
  * "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
65
70
  * Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
66
71
  */
67
- export declare function Select({ options, value, defaultValue, onValueChange, name, required, placeholder, variant, width, searchable, searchPlaceholder, emptyLabel, disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
72
+ export declare function Select({ options, value, defaultValue, onValueChange, name, required, placeholder, variant, width, searchable, searchPlaceholder, onSearchChange, emptyLabel, disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
68
73
  export declare namespace Select {
69
74
  var displayName: string;
70
75
  }
@@ -31,8 +31,25 @@ export type TableToolbarColumn = {
31
31
  values?: string[];
32
32
  };
33
33
  export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
34
- /** The fields that can be sorted and filtered on. */
35
- columns: TableToolbarColumn[];
34
+ /**
35
+ * The fields Sort and Filter both offer, when neither overrides it below.
36
+ * Optional only when `sortColumns` AND `filterColumns` are both supplied —
37
+ * TableToolbar has nothing left to fall back to otherwise, and a side left
38
+ * with neither offers no fields at all (its trigger still renders; its
39
+ * popover is just empty).
40
+ */
41
+ columns?: TableToolbarColumn[];
42
+ /**
43
+ * The fields Sort offers, when they differ from Filter's — Attio's own
44
+ * Tasks page is the reference (measured live, `app.attio.com/<ws>/tasks`):
45
+ * Sort offers Due date/Assignee/Creation date, Filter offers Assignee/
46
+ * Record/Created by, and neither is the other's full set. Defaults to
47
+ * `columns`.
48
+ */
49
+ sortColumns?: TableToolbarColumn[];
50
+ /** The fields Filter offers, when they differ from Sort's. Defaults to
51
+ * `columns`. */
52
+ filterColumns?: TableToolbarColumn[];
36
53
  /** Controlled sorts. Omit to let the toolbar manage them internally. */
37
54
  sorts?: ToolbarSort[];
38
55
  defaultSorts?: ToolbarSort[];
@@ -75,8 +92,9 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
75
92
  ascendingLabel?: string;
76
93
  /** Label on the "descending" sort-direction option (default "Descending"). */
77
94
  descendingLabel?: string;
78
- /** Text on the sort trigger. Receives the active sorts and the columns, so
79
- * the sorted-by phrase can be built in the caller's own word order —
95
+ /** Text on the sort trigger. Receives the active sorts and the *sort*
96
+ * fields (`sortColumns`, or `columns` when that's not set), so the
97
+ * sorted-by phrase can be built in the caller's own word order —
80
98
  * "Sorted by Name +2" does not survive a template with holes. */
81
99
  sortTriggerLabel?: (sorts: ToolbarSort[], columns: TableToolbarColumn[]) => React.ReactNode;
82
100
  /** Text on the filter trigger. Receives the number of active filters. */
@@ -110,8 +128,14 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
110
128
  * `RecordsTable` renders this same measured chrome internally; this is the
111
129
  * composable version, to put above `DataTable` or any table of your own. Pair
112
130
  * it with `applyFilters` / `applySorts` to turn its state into rows.
131
+ *
132
+ * Sort and Filter read the same `columns` list by default, but can be given
133
+ * their own (`sortColumns`/`filterColumns`) when a consumer's two menus
134
+ * genuinely offer different fields — Attio's own Tasks page does exactly
135
+ * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
136
+ * Created by), which one shared list can't reproduce.
113
137
  */
114
- export declare function TableToolbar({ columns, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel, reorderSortLabel, noSortsLabel, addSortLabel, learnSortingLabel, selectConditionLabel, sortFieldLabel, sortDirectionLabel, removeSortLabel, filterFieldLabel, filterFieldPlaceholder, filterValuePlaceholder, ascendingLabel, descendingLabel, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
138
+ export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel, reorderSortLabel, noSortsLabel, addSortLabel, learnSortingLabel, selectConditionLabel, sortFieldLabel, sortDirectionLabel, removeSortLabel, filterFieldLabel, filterFieldPlaceholder, filterValuePlaceholder, ascendingLabel, descendingLabel, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
115
139
  export type ToolbarToolProps = React.ComponentPropsWithoutRef<"button"> & {
116
140
  /** Drawn the way Sort/Filter are once a condition is set (#399). */
117
141
  active?: boolean;
@@ -15,3 +15,20 @@
15
15
  export declare function parseCellDate(raw: string | undefined): Date | null;
16
16
  /** The inverse: the picked date's own calendar day, not the UTC one. */
17
17
  export declare function formatCellDate(d: Date): string;
18
+ /**
19
+ * How much of a date-only value a raw cell string actually carried.
20
+ *
21
+ * `parseCellDate` fills a missing month/day with January/the 1st so a
22
+ * year-only or year-month value can become a `Date` — right for parsing,
23
+ * which must produce an instant, but wrong for display: a *display*
24
+ * formatter that always prints `month day, year` renders a value that named
25
+ * one field as though it named three (#312 — `"1998"` rendered "Jan 1,
26
+ * 1998"). A caller that formats for display reads this first and only
27
+ * requests the precision the raw string actually had.
28
+ *
29
+ * A value the `DATE_ONLY` forms don't match (it carries a time, or isn't a
30
+ * recognised date at all) names a specific instant rather than a calendar
31
+ * field, so it's always `"day"` — full precision, same as today.
32
+ */
33
+ export type CellDatePrecision = "year" | "month" | "day";
34
+ export declare function cellDatePrecision(raw: string): CellDatePrecision;
@@ -1,4 +1,4 @@
1
- export { formatCellDate, parseCellDate } from "./cell-date";
1
+ export { type CellDatePrecision, cellDatePrecision, formatCellDate, parseCellDate, } from "./cell-date";
2
2
  export { cx } from "./cx";
3
3
  export { defineRecipe } from "./define";
4
4
  export { withDescribedBy } from "./dialog-description";
@@ -11,6 +11,7 @@ export * from "./components/card";
11
11
  export * from "./components/checkbox";
12
12
  export * from "./components/chip";
13
13
  export * from "./components/columncalc";
14
+ export * from "./components/composewindow";
14
15
  export * from "./components/contextmenu";
15
16
  export * from "./components/datatable";
16
17
  export * from "./components/datepicker";
@@ -1,8 +1,42 @@
1
1
  import * as React from "react";
2
2
  import { type Column, type Row } from "../recordstable";
3
+ export type RecordPageNote = {
4
+ id: string;
5
+ text: string;
6
+ time: string;
7
+ };
8
+ export type RecordPageTask = {
9
+ id: string;
10
+ text: string;
11
+ done: boolean;
12
+ };
13
+ /**
14
+ * A consumer-supplied tab, replacing one of the built-in CRM tabs
15
+ * (Overview/Activity/Emails/Calls/Team/Notes/Tasks/Files/Deals). `render`
16
+ * is called only while its tab is active, the same lazy-body contract the
17
+ * built-in tabs already follow.
18
+ */
19
+ export type RecordPageTab = {
20
+ id: string;
21
+ label: string;
22
+ /** Optional count/badge shown after the label (e.g. "Notes · 3"). */
23
+ count?: number;
24
+ render: () => React.ReactNode;
25
+ };
3
26
  export type RecordPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
4
27
  record: Row;
5
28
  columns: Column[];
29
+ /**
30
+ * Commit handler for a `column.editable` Details field (#431) — same
31
+ * shape `DataTable`'s own `onEdit` uses (#385/#430): returning (or
32
+ * resolving) nothing commits synchronously; returning a `Promise` that
33
+ * rejects shows a pending state on the field while it's in flight, then
34
+ * reverts to the last-known-good value (this component never mutates
35
+ * `record` itself) and renders the rejection's message beside the field.
36
+ * A field with `editable` but no `onEdit` never opens — there's nothing
37
+ * to commit to.
38
+ */
39
+ onEdit?: (id: string, key: string, value: string) => Promise<void> | void;
6
40
  /** Breadcrumb label + back button target (e.g. "Companies"). */
7
41
  collection?: string;
8
42
  /** Partial override of the chrome strings; see `recordPageLabels`. */
@@ -12,6 +46,30 @@ export type RecordPageProps = Omit<React.ComponentPropsWithoutRef<"div">, "child
12
46
  onToggleFull?: () => void;
13
47
  full?: boolean;
14
48
  className?: string;
49
+ /**
50
+ * Replaces the built-in CRM tab set. An app with a non-CRM domain (a
51
+ * custom register's own sections, a Related/backlinks tab) supplies its
52
+ * own list instead of rebuilding RecordPage's chrome by hand. Omit to
53
+ * keep the default Overview/Activity/Emails/Calls/Team/Notes/Tasks/
54
+ * Files/Deals set.
55
+ */
56
+ tabs?: RecordPageTab[];
57
+ /** Active tab id on first render, when `tabs` is supplied. Defaults to the first tab. Ignored for the built-in tab set (Overview is always first there). */
58
+ defaultTab?: string;
59
+ /**
60
+ * Controlled Notes: when supplied (even as `[]`), RecordPage renders
61
+ * this list instead of managing its own, and the composer calls
62
+ * `onAddNote` instead of appending to internal state — the same
63
+ * "caller owns the data, crisp owns the chrome" shape `Board`'s
64
+ * `cards`/`onCardsChange` uses. Omit both to keep the self-contained
65
+ * Notes section (crisp owns the data too, as today).
66
+ */
67
+ notes?: RecordPageNote[];
68
+ onAddNote?: (text: string) => void;
69
+ /** Controlled Tasks — same shape as `notes`/`onAddNote`. */
70
+ tasks?: RecordPageTask[];
71
+ onAddTask?: (text: string) => void;
72
+ onToggleTask?: (id: string) => void;
15
73
  };
16
74
  /**
17
75
  * Every chrome string RecordPage renders. Supplied as a PARTIAL map, so
@@ -97,7 +155,7 @@ export type RecordPageLabelsInput = Partial<Omit<RecordPageLabels, "tabs" | "hig
97
155
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
98
156
  * into the app's main content area.
99
157
  */
100
- export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, ...rest }: RecordPageProps): React.JSX.Element;
158
+ export declare function RecordPage({ record, columns, collection, labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, ...rest }: RecordPageProps): React.JSX.Element;
101
159
  export declare namespace RecordPage {
102
160
  var displayName: string;
103
161
  }
@@ -21,6 +21,16 @@ export type RecordPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "chil
21
21
  record: Row | null;
22
22
  columns: Column[];
23
23
  onClose: () => void;
24
+ /**
25
+ * Commit handler for a `column.editable` field (#431) — same shape
26
+ * `DataTable`'s own `onEdit` uses (#385/#430): returning (or resolving)
27
+ * nothing commits synchronously; returning a `Promise` that rejects shows
28
+ * a pending state on the field while it's in flight, then reverts to the
29
+ * last-known-good value (this component never mutates `record` itself)
30
+ * and renders the rejection's message beside the field. A field with
31
+ * `editable` but no `onEdit` never opens — there's nothing to commit to.
32
+ */
33
+ onEdit?: (id: string, key: string, value: string) => Promise<void> | void;
24
34
  className?: string;
25
35
  };
26
36
  /**
@@ -29,7 +39,7 @@ export type RecordPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "chil
29
39
  * relative` container (e.g. the app main area); it slides in from the right and
30
40
  * overlays the content.
31
41
  */
32
- export declare function RecordPanel({ record, columns, onClose, className, closeLabel, title, detailsLabel, emptyLabel, footer, ...rest }: RecordPanelProps): React.JSX.Element | null;
42
+ export declare function RecordPanel({ record, columns, onClose, className, closeLabel, title, detailsLabel, emptyLabel, footer, onEdit, ...rest }: RecordPanelProps): React.JSX.Element | null;
33
43
  export declare namespace RecordPanel {
34
44
  var displayName: string;
35
45
  }
@@ -0,0 +1,45 @@
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 `RecordPanel`/`RecordPage`'s
12
+ * editable fields (#431) — the same shape `DataTable`'s own `commitEdit`
13
+ * uses (#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: neither `RecordPanel` nor `RecordPage` mutates
18
+ * `record` itself, so once `editingKey` clears, the field simply renders
19
+ * `record[key]` again — whatever it still says.
20
+ *
21
+ * One hook shared by both consumers rather than two copies of the same
22
+ * three `useState`s and the same `.then` branching.
23
+ */
24
+ export declare function useEditableField(onEdit?: (id: string, key: string, value: string) => Promise<void> | void): {
25
+ editingKey: string | null;
26
+ pendingKey: string | null;
27
+ error: {
28
+ key: string;
29
+ message: string;
30
+ } | null;
31
+ openEditing: (key: string) => void;
32
+ closeEditing: () => void;
33
+ commit: (id: string, key: string, value: string) => void;
34
+ };
35
+ /**
36
+ * Which plain editor a column gets when it's `editable` without an
37
+ * `editor: "select" | "switch"` — identical to `DataTable`'s own
38
+ * `editorFor` (#385): number/currency get a numeric field, date a native
39
+ * date field, everything else (text, entity, tags, url, status, reference)
40
+ * a plain text field. Kept as one function so the two never drift apart.
41
+ */
42
+ export declare function editableInputType(colType: string | undefined): {
43
+ type: string;
44
+ inputMode?: "decimal" | "text";
45
+ };
@@ -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;
@@ -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.51.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",