@crisp-ui-kit/crisp 0.53.4 → 0.53.5

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.
@@ -149,7 +149,7 @@ const exec = (cmd, value) => document.execCommand(cmd, false, value);
149
149
  * reach the identical menu, sitting at the end of the body's last line) —
150
150
  * only the two entry points #426 asked for.
151
151
  */
152
- exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", imagePromptLabel = "Image URL", textColorLabel = "Text", highlightColorLabel = "Highlight", clearColorLabel = "Default", blockMenuLabel = "Body", blockMenuSearchPlaceholder = "Search items…", blockMenuEmptyLabel = "No matching blocks", mentionTrigger = "@", renderMentionPicker, readOnly = false, ...rest }, ref) {
152
+ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", imagePromptLabel = "Image URL", textColorLabel = "Text", highlightColorLabel = "Highlight", clearColorLabel = "Default", blockMenuLabel = "Body", blockMenuSearchPlaceholder = "Search items…", blockMenuEmptyLabel = "No matching blocks", mentionTrigger = "@", renderMentionPicker, readOnly = false, showTitle = true, ...rest }, ref) {
153
153
  const titleRef = React.useRef(null);
154
154
  const bodyRef = React.useRef(null);
155
155
  const [bar, setBar] = React.useState(null);
@@ -550,12 +550,12 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
550
550
  ol: (0, jsx_runtime_1.jsx)(lucide_react_1.ListOrdered, { ...menuIc }),
551
551
  image: (0, jsx_runtime_1.jsx)(lucide_react_1.Image, { ...menuIc }),
552
552
  };
553
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, richtext_recipe_1.richtextRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsx)("div", { ref: titleRef, className: "crisp-rte-title", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-label": titleLabel, "aria-readonly": readOnly || undefined, "data-placeholder": titlePlaceholder, onInput: readOnly
553
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, richtext_recipe_1.richtextRecipe)({ className }), ...rest, children: [showTitle && ((0, jsx_runtime_1.jsx)("div", { ref: titleRef, className: "crisp-rte-title", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-label": titleLabel, "aria-readonly": readOnly || undefined, "data-placeholder": titlePlaceholder, onInput: readOnly
554
554
  ? undefined
555
555
  : () => {
556
556
  syncEmpty(titleRef.current);
557
557
  emit();
558
- } }), (0, jsx_runtime_1.jsx)("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
558
+ } })), (0, jsx_runtime_1.jsx)("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
559
559
  ? undefined
560
560
  : () => {
561
561
  syncEmpty(bodyRef.current);
@@ -6,13 +6,22 @@ const tokens_1 = require("../../tokens");
6
6
  // A settings section — measured from Settings pages: a 16px/600 title
7
7
  // with an optional subtitle, then a stack of rows (form fields, toggles…). Rows
8
8
  // gap evenly; an optional trailing action sits on the title row.
9
+ //
10
+ // Title line-height/letter-spacing and subtitle font-size/line-height
11
+ // re-measured live at app.attio.com/<workspace>/settings/account against a
12
+ // subsection heading ("Time preferences") — crisp#522. The subtitle's own
13
+ // colour (measured `rgba(0,0,0,0.63)`, a black-alpha value) is left as
14
+ // `fgMuted` pending a dark-mode measurement: a raw alpha-black has no
15
+ // existing token match and would need its own light/dark pair to stay
16
+ // legible in dark mode (Lesson: theme-constant vs theme-flipping colours
17
+ // must each be independently verified).
9
18
  const config = (0, core_1.defineRecipe)({
10
19
  name: "settingssection",
11
20
  base: `display: flex; flex-direction: column; box-sizing: border-box; font-family: ${tokens_1.contract.font.sans};
12
21
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: ${tokens_1.contract.space["2"]}; }
13
22
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
14
- & .crisp-settingssection-title { font-size: ${tokens_1.contract.text.xl}; line-height: 24px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.2px; color: ${tokens_1.contract.fgPrimary}; }
15
- & .crisp-settingssection-sub { margin-top: ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.md}; line-height: 18px; color: ${tokens_1.contract.fgMuted}; }
23
+ & .crisp-settingssection-title { font-size: ${tokens_1.contract.text.xl}; line-height: 20px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.16px; color: ${tokens_1.contract.fgPrimary}; }
24
+ & .crisp-settingssection-sub { margin-top: ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; color: ${tokens_1.contract.fgMuted}; }
16
25
  & .crisp-settingssection-action { flex-shrink: 0; }
17
26
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["4"]}; margin-top: ${tokens_1.contract.space["3"]}; }`,
18
27
  variants: {},
@@ -113,7 +113,7 @@ const exec = (cmd, value) => document.execCommand(cmd, false, value);
113
113
  * reach the identical menu, sitting at the end of the body's last line) —
114
114
  * only the two entry points #426 asked for.
115
115
  */
116
- export const RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", imagePromptLabel = "Image URL", textColorLabel = "Text", highlightColorLabel = "Highlight", clearColorLabel = "Default", blockMenuLabel = "Body", blockMenuSearchPlaceholder = "Search items…", blockMenuEmptyLabel = "No matching blocks", mentionTrigger = "@", renderMentionPicker, readOnly = false, ...rest }, ref) {
116
+ export const RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle = "", defaultHtml = "", titlePlaceholder = "Untitled", placeholder = "Start writing, or press a toolbar button…", onChange, className, titleLabel = "Title", bodyLabel = "Note body", linkPromptLabel = "Link URL", imagePromptLabel = "Image URL", textColorLabel = "Text", highlightColorLabel = "Highlight", clearColorLabel = "Default", blockMenuLabel = "Body", blockMenuSearchPlaceholder = "Search items…", blockMenuEmptyLabel = "No matching blocks", mentionTrigger = "@", renderMentionPicker, readOnly = false, showTitle = true, ...rest }, ref) {
117
117
  const titleRef = React.useRef(null);
118
118
  const bodyRef = React.useRef(null);
119
119
  const [bar, setBar] = React.useState(null);
@@ -514,12 +514,12 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
514
514
  ol: _jsx(ListOrdered, { ...menuIc }),
515
515
  image: _jsx(ImageIcon, { ...menuIc }),
516
516
  };
517
- return (_jsxs("div", { className: richtextRecipe({ className }), ...rest, children: [_jsx("div", { ref: titleRef, className: "crisp-rte-title", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-label": titleLabel, "aria-readonly": readOnly || undefined, "data-placeholder": titlePlaceholder, onInput: readOnly
517
+ return (_jsxs("div", { className: richtextRecipe({ className }), ...rest, children: [showTitle && (_jsx("div", { ref: titleRef, className: "crisp-rte-title", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-label": titleLabel, "aria-readonly": readOnly || undefined, "data-placeholder": titlePlaceholder, onInput: readOnly
518
518
  ? undefined
519
519
  : () => {
520
520
  syncEmpty(titleRef.current);
521
521
  emit();
522
- } }), _jsx("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
522
+ } })), _jsx("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
523
523
  ? undefined
524
524
  : () => {
525
525
  syncEmpty(bodyRef.current);
@@ -3,13 +3,22 @@ import { contract as c } from "../../tokens/index.js";
3
3
  // A settings section — measured from Settings pages: a 16px/600 title
4
4
  // with an optional subtitle, then a stack of rows (form fields, toggles…). Rows
5
5
  // gap evenly; an optional trailing action sits on the title row.
6
+ //
7
+ // Title line-height/letter-spacing and subtitle font-size/line-height
8
+ // re-measured live at app.attio.com/<workspace>/settings/account against a
9
+ // subsection heading ("Time preferences") — crisp#522. The subtitle's own
10
+ // colour (measured `rgba(0,0,0,0.63)`, a black-alpha value) is left as
11
+ // `fgMuted` pending a dark-mode measurement: a raw alpha-black has no
12
+ // existing token match and would need its own light/dark pair to stay
13
+ // legible in dark mode (Lesson: theme-constant vs theme-flipping colours
14
+ // must each be independently verified).
6
15
  const config = defineRecipe({
7
16
  name: "settingssection",
8
17
  base: `display: flex; flex-direction: column; box-sizing: border-box; font-family: ${c.font.sans};
9
18
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: ${c.space["2"]}; }
10
19
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
11
- & .crisp-settingssection-title { font-size: ${c.text.xl}; line-height: 24px; font-weight: ${c.weight.semibold}; letter-spacing: -0.2px; color: ${c.fgPrimary}; }
12
- & .crisp-settingssection-sub { margin-top: ${c.space["0.5"]}; font-size: ${c.text.md}; line-height: 18px; color: ${c.fgMuted}; }
20
+ & .crisp-settingssection-title { font-size: ${c.text.xl}; line-height: 20px; font-weight: ${c.weight.semibold}; letter-spacing: -0.16px; color: ${c.fgPrimary}; }
21
+ & .crisp-settingssection-sub { margin-top: ${c.space["0.5"]}; font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgMuted}; }
13
22
  & .crisp-settingssection-action { flex-shrink: 0; }
14
23
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: ${c.space["4"]}; margin-top: ${c.space["3"]}; }`,
15
24
  variants: {},
@@ -2,8 +2,8 @@
2
2
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans);
3
3
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: var(--crisp-space-2); }
4
4
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
5
- & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); }
6
- & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-md); line-height: 18px; color: var(--crisp-fg-muted); }
5
+ & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 20px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.16px; color: var(--crisp-fg-primary); }
6
+ & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); }
7
7
  & .crisp-settingssection-action { flex-shrink: 0; }
8
8
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); }
9
9
  }
@@ -3,8 +3,8 @@
3
3
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans);
4
4
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: var(--crisp-space-2); }
5
5
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
6
- & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); }
7
- & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-md); line-height: 18px; color: var(--crisp-fg-muted); }
6
+ & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 20px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.16px; color: var(--crisp-fg-primary); }
7
+ & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); }
8
8
  & .crisp-settingssection-action { flex-shrink: 0; }
9
9
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); }
10
10
  }
package/dist/styles.css CHANGED
@@ -2674,8 +2674,8 @@
2674
2674
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans);
2675
2675
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: var(--crisp-space-2); }
2676
2676
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
2677
- & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); }
2678
- & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-md); line-height: 18px; color: var(--crisp-fg-muted); }
2677
+ & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 20px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.16px; color: var(--crisp-fg-primary); }
2678
+ & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); }
2679
2679
  & .crisp-settingssection-action { flex-shrink: 0; }
2680
2680
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); }
2681
2681
  }
@@ -2675,8 +2675,8 @@
2675
2675
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans);
2676
2676
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: var(--crisp-space-2); }
2677
2677
  & .crisp-settingssection-heading { flex: 1; min-width: 0; }
2678
- & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); }
2679
- & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-md); line-height: 18px; color: var(--crisp-fg-muted); }
2678
+ & .crisp-settingssection-title { font-size: var(--crisp-text-xl); line-height: 20px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.16px; color: var(--crisp-fg-primary); }
2679
+ & .crisp-settingssection-sub { margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); }
2680
2680
  & .crisp-settingssection-action { flex-shrink: 0; }
2681
2681
  & .crisp-settingssection-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); }
2682
2682
  }
@@ -93,6 +93,16 @@ export type RichTextEditorProps = Omit<React.ComponentPropsWithoutRef<"div">, "c
93
93
  * headings/lists/marks all survive into it. No toolbar renders in this
94
94
  * mode. */
95
95
  readOnly?: boolean;
96
+ /**
97
+ * Renders the title field at all (default `true`, matching Attio's own
98
+ * preview — see `readOnly` above). `false` omits it from the DOM
99
+ * entirely, not just visually: for a consumer whose own layout already
100
+ * shows a note's title elsewhere (a list row's own heading, a record's
101
+ * collapsed-note summary), rendering it a second time here would
102
+ * duplicate it rather than match anything Attio does. `onChange`'s
103
+ * `title` is always `""` with this off; `defaultTitle` is ignored.
104
+ */
105
+ showTitle?: boolean;
96
106
  className?: string;
97
107
  };
98
108
  /**
@@ -187,5 +197,15 @@ export declare const RichTextEditor: React.ForwardRefExoticComponent<Omit<Omit<R
187
197
  * headings/lists/marks all survive into it. No toolbar renders in this
188
198
  * mode. */
189
199
  readOnly?: boolean;
200
+ /**
201
+ * Renders the title field at all (default `true`, matching Attio's own
202
+ * preview — see `readOnly` above). `false` omits it from the DOM
203
+ * entirely, not just visually: for a consumer whose own layout already
204
+ * shows a note's title elsewhere (a list row's own heading, a record's
205
+ * collapsed-note summary), rendering it a second time here would
206
+ * duplicate it rather than match anything Attio does. `onChange`'s
207
+ * `title` is always `""` with this off; `defaultTitle` is ignored.
208
+ */
209
+ showTitle?: boolean;
190
210
  className?: string;
191
211
  } & React.RefAttributes<RichTextEditorHandle>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.53.4",
3
+ "version": "0.53.5",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",