@crisp-ui-kit/crisp 0.51.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/core/caret-rect.js +73 -0
  2. package/dist/cjs/core/index.js +7 -1
  3. package/dist/cjs/core/mention.js +32 -0
  4. package/dist/cjs/core/use-mention-trigger.js +124 -0
  5. package/dist/cjs/product/recordpanel/recordpanel.js +13 -81
  6. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +53 -44
  7. package/dist/cjs/product/recordstable/editable-field.js +11 -8
  8. package/dist/cjs/product/recordstable/recordstable.tag.js +8 -3
  9. package/dist/cjs/product/richtext/index.js +2 -1
  10. package/dist/cjs/product/richtext/richtext.js +440 -23
  11. package/dist/cjs/product/richtext/richtext.recipe.js +59 -9
  12. package/dist/esm/core/caret-rect.js +70 -0
  13. package/dist/esm/core/index.js +3 -0
  14. package/dist/esm/core/mention.js +29 -0
  15. package/dist/esm/core/use-mention-trigger.js +88 -0
  16. package/dist/esm/product/recordpanel/index.js +1 -1
  17. package/dist/esm/product/recordpanel/recordpanel.js +15 -83
  18. package/dist/esm/product/recordpanel/recordpanel.recipe.js +54 -45
  19. package/dist/esm/product/recordstable/editable-field.js +11 -8
  20. package/dist/esm/product/recordstable/recordstable.tag.js +8 -3
  21. package/dist/esm/product/richtext/index.js +1 -1
  22. package/dist/esm/product/richtext/richtext.js +439 -22
  23. package/dist/esm/product/richtext/richtext.recipe.js +60 -10
  24. package/dist/recordpanel.css +47 -41
  25. package/dist/recordpanel.layered.css +47 -41
  26. package/dist/richtext.css +31 -5
  27. package/dist/richtext.layered.css +31 -5
  28. package/dist/styles.css +79 -47
  29. package/dist/styles.layered.css +79 -47
  30. package/dist/types/core/caret-rect.d.ts +16 -0
  31. package/dist/types/core/index.d.ts +3 -0
  32. package/dist/types/core/mention.d.ts +27 -0
  33. package/dist/types/core/use-mention-trigger.d.ts +41 -0
  34. package/dist/types/product/recordpanel/index.d.ts +1 -1
  35. package/dist/types/product/recordpanel/recordpanel.d.ts +81 -27
  36. package/dist/types/product/recordstable/editable-field.d.ts +11 -8
  37. package/dist/types/product/recordstable/recordstable.tag.d.ts +8 -3
  38. package/dist/types/product/richtext/index.d.ts +1 -1
  39. package/dist/types/product/richtext/richtext.d.ts +168 -7
  40. package/package.json +1 -1
@@ -2,52 +2,58 @@
2
2
  position: absolute; top: var(--crisp-space-2); inset-inline-end: var(--crisp-space-2); bottom: var(--crisp-space-2); z-index: 20; display: flex; flex-direction: column; overflow: hidden; width: 420px; max-width: calc(92% - var(--crisp-space-2)); background: var(--crisp-bg-surface); border-radius: var(--crisp-radius-xl); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transform: translateX(0); transition: transform .2s ease;
3
3
  @starting-style { transform: translateX(calc(100% + var(--crisp-space-2))); }
4
4
  & * { box-sizing: border-box; }
5
- & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); }
5
+ /* #448: measured live on app.attio.com's peek -- a toolbar row above
6
+ the identity header: Close, then Previous/Next, on the left; "Open
7
+ record" on the right. 49px tall, 8px/10px padding, a hairline border
8
+ beneath (the same borderSubtle every other panel/row edge here
9
+ already uses). Close keeps the existing transparent ghost button;
10
+ Previous/Next/Open record are a distinct field-elevation style
11
+ (white fill, e.field's own ring+shadow, solid icon) -- measured as genuinely different chrome from Close, not a reuse. */
12
+ & .crisp-recordpanel-toolbar { display: flex; align-items: center; height: 49px; flex-shrink: 0; padding: var(--crisp-space-2) var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); }
13
+ & .crisp-recordpanel-toolbar-spacer { flex: 1; }
14
+ & .crisp-recordpanel-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
15
+ & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
16
+ & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
17
+ & .crisp-recordpanel-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; cursor: pointer; }
18
+ & .crisp-recordpanel-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
19
+ & .crisp-recordpanel-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
20
+ & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); }
6
21
  & .crisp-recordpanel-fav { width: 22px; height: var(--crisp-size-chip); border-radius: var(--crisp-radius-sm); flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
7
22
  & .crisp-recordpanel-title { flex: 1; min-width: 0; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8
- & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; transition: background-color .12s; }
9
- & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
10
23
  & .crisp-recordpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-2) var(--crisp-space-3); }
11
- & .crisp-recordpanel-sectionlabel { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); padding: var(--crisp-space-2) var(--crisp-space-1) var(--crisp-space-1); }
12
- & .crisp-recordpanel-field { display: flex; align-items: flex-start; gap: var(--crisp-space-2); min-height: 32px; padding: var(--crisp-space-1) var(--crisp-space-1); border-radius: var(--crisp-radius-md); }
13
- & .crisp-recordpanel-field:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); }
14
- & .crisp-recordpanel-field-label { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 132px; flex-shrink: 0; padding-top: 3px; color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); }
15
- & .crisp-recordpanel-field-label svg { flex-shrink: 0; }
16
- & .crisp-recordpanel-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 3px; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
17
- & .crisp-recordpanel-field-value[data-editable] { cursor: pointer; }
18
- /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
19
- drops a button's accessible semantics in some engines. */
20
- & .crisp-recordpanel-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
21
- & .crisp-recordpanel-empty { color: var(--crisp-fg-muted); }
22
- /* #431: measured live on app.attio.com's record side panel -- clicking a
23
- field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no box-shadow), same font
24
- as the value it replaces (hence `font: inherit`, not a restated
25
- size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't
26
- Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
27
- & .crisp-recordpanel-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
28
- /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
29
- .crisp-datatable-edit:focus, e.g.). */
30
- & .crisp-recordpanel-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
31
- & .crisp-recordpanel-edit:disabled { opacity: 0.5; cursor: not-allowed; }
32
- & .crisp-recordpanel-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
33
- & .crisp-recordpanel-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
34
- & .crisp-recordpanel-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
35
- & .crisp-recordpanel-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
36
-
37
- & .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
38
- & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
39
- & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
40
- & .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
41
- & .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
42
- & .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
43
- & .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
44
- & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
45
- & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
46
- & .crisp-recordpanel-link:hover { text-decoration: underline; }
24
+ /* #448: measured live on app.attio.com's peek (hover a Companies row,
25
+ click "Open preview"). Section label reads identically for both
26
+ Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix
27
+ RecordPage's own field-label already uses (product/recordpage's
28
+ .crisp-recordpage-field-label) -- one more surface landing on the
29
+ same measured "muted section heading" colour. */
30
+ & .crisp-recordpanel-section { padding: var(--crisp-space-2) var(--crisp-space-1) 0; }
31
+ & .crisp-recordpanel-section-head { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); height: 20px; margin-bottom: var(--crisp-space-2); }
32
+ & .crisp-recordpanel-section-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
33
+ /* "View all" -- measured 10px/400, a lighter mix than the section label
34
+ (55%, not 63%) -- its own distinct, honestly-measured value, not a
35
+ reuse of a nearby one that happened to be close. */
36
+ & .crisp-recordpanel-section-action { border: none; background: none; padding: 0; font-family: inherit; font-size: 10px; font-weight: var(--crisp-weight-regular); color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
37
+ & .crisp-recordpanel-section-action:hover { color: var(--crisp-fg-primary); }
47
38
 
48
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the
49
- header's own measured values rather than inventing new ones -- the same horizontal inset (space.3), the same hairline border token (borderSubtle, flipped to border-top since this sits at the opposite edge), and the header/body gap (space.2) for its own vertical padding and for spacing between whatever it's given. */
39
+ /* Highlights: a 2-column grid of label+icon+value cards, no card
40
+ chrome at all (no border/background/radius/shadow -- confirmed on
41
+ the live DOM: every highlight's computed style is fully transparent). 8px row/column gap; two rows measured "233x86" inside 473px content width is exactly (473 - 8) / 2. */
42
+ & .crisp-recordpanel-highlights { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-2); }
43
+ & .crisp-recordpanel-highlight { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); min-width: 0; }
44
+ & .crisp-recordpanel-highlight-label { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
45
+ & .crisp-recordpanel-highlight-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
46
+ & .crisp-recordpanel-highlight-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
47
+ & .crisp-recordpanel-highlight-value { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; }
48
+ /* Activity is a consumer-supplied slot (crisp has no activity-feed model of its own to render) -- this only wraps it with the measured section heading above. */
49
+ & .crisp-recordpanel-activity { font-size: var(--crisp-text-lg); }
50
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
51
+ same horizontal inset (space.3), the same hairline border token
52
+ (borderSubtle, flipped to border-top since this sits at the opposite
53
+ edge), and the header/body gap (space.2) for its own vertical padding
54
+ and for spacing between whatever it's given. */
50
55
  & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
56
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
51
57
  }
52
58
 
53
59
  @media (prefers-reduced-motion: reduce) {
@@ -3,52 +3,58 @@
3
3
  position: absolute; top: var(--crisp-space-2); inset-inline-end: var(--crisp-space-2); bottom: var(--crisp-space-2); z-index: 20; display: flex; flex-direction: column; overflow: hidden; width: 420px; max-width: calc(92% - var(--crisp-space-2)); background: var(--crisp-bg-surface); border-radius: var(--crisp-radius-xl); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transform: translateX(0); transition: transform .2s ease;
4
4
  @starting-style { transform: translateX(calc(100% + var(--crisp-space-2))); }
5
5
  & * { box-sizing: border-box; }
6
- & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); }
6
+ /* #448: measured live on app.attio.com's peek -- a toolbar row above
7
+ the identity header: Close, then Previous/Next, on the left; "Open
8
+ record" on the right. 49px tall, 8px/10px padding, a hairline border
9
+ beneath (the same borderSubtle every other panel/row edge here
10
+ already uses). Close keeps the existing transparent ghost button;
11
+ Previous/Next/Open record are a distinct field-elevation style
12
+ (white fill, e.field's own ring+shadow, solid icon) -- measured as genuinely different chrome from Close, not a reuse. */
13
+ & .crisp-recordpanel-toolbar { display: flex; align-items: center; height: 49px; flex-shrink: 0; padding: var(--crisp-space-2) var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); }
14
+ & .crisp-recordpanel-toolbar-spacer { flex: 1; }
15
+ & .crisp-recordpanel-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
16
+ & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
17
+ & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
18
+ & .crisp-recordpanel-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; cursor: pointer; }
19
+ & .crisp-recordpanel-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
20
+ & .crisp-recordpanel-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
21
+ & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); }
7
22
  & .crisp-recordpanel-fav { width: 22px; height: var(--crisp-size-chip); border-radius: var(--crisp-radius-sm); flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
8
23
  & .crisp-recordpanel-title { flex: 1; min-width: 0; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
9
- & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; transition: background-color .12s; }
10
- & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
11
24
  & .crisp-recordpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-2) var(--crisp-space-3); }
12
- & .crisp-recordpanel-sectionlabel { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); padding: var(--crisp-space-2) var(--crisp-space-1) var(--crisp-space-1); }
13
- & .crisp-recordpanel-field { display: flex; align-items: flex-start; gap: var(--crisp-space-2); min-height: 32px; padding: var(--crisp-space-1) var(--crisp-space-1); border-radius: var(--crisp-radius-md); }
14
- & .crisp-recordpanel-field:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); }
15
- & .crisp-recordpanel-field-label { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 132px; flex-shrink: 0; padding-top: 3px; color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); }
16
- & .crisp-recordpanel-field-label svg { flex-shrink: 0; }
17
- & .crisp-recordpanel-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 3px; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
18
- & .crisp-recordpanel-field-value[data-editable] { cursor: pointer; }
19
- /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
20
- drops a button's accessible semantics in some engines. */
21
- & .crisp-recordpanel-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
22
- & .crisp-recordpanel-empty { color: var(--crisp-fg-muted); }
23
- /* #431: measured live on app.attio.com's record side panel -- clicking a
24
- field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no box-shadow), same font
25
- as the value it replaces (hence `font: inherit`, not a restated
26
- size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't
27
- Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
28
- & .crisp-recordpanel-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
29
- /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
30
- .crisp-datatable-edit:focus, e.g.). */
31
- & .crisp-recordpanel-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
32
- & .crisp-recordpanel-edit:disabled { opacity: 0.5; cursor: not-allowed; }
33
- & .crisp-recordpanel-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
34
- & .crisp-recordpanel-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
35
- & .crisp-recordpanel-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
36
- & .crisp-recordpanel-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
37
-
38
- & .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
39
- & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
40
- & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
41
- & .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
42
- & .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
43
- & .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
44
- & .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
45
- & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
46
- & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
47
- & .crisp-recordpanel-link:hover { text-decoration: underline; }
25
+ /* #448: measured live on app.attio.com's peek (hover a Companies row,
26
+ click "Open preview"). Section label reads identically for both
27
+ Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix
28
+ RecordPage's own field-label already uses (product/recordpage's
29
+ .crisp-recordpage-field-label) -- one more surface landing on the
30
+ same measured "muted section heading" colour. */
31
+ & .crisp-recordpanel-section { padding: var(--crisp-space-2) var(--crisp-space-1) 0; }
32
+ & .crisp-recordpanel-section-head { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); height: 20px; margin-bottom: var(--crisp-space-2); }
33
+ & .crisp-recordpanel-section-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
34
+ /* "View all" -- measured 10px/400, a lighter mix than the section label
35
+ (55%, not 63%) -- its own distinct, honestly-measured value, not a
36
+ reuse of a nearby one that happened to be close. */
37
+ & .crisp-recordpanel-section-action { border: none; background: none; padding: 0; font-family: inherit; font-size: 10px; font-weight: var(--crisp-weight-regular); color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
38
+ & .crisp-recordpanel-section-action:hover { color: var(--crisp-fg-primary); }
48
39
 
49
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the
50
- header's own measured values rather than inventing new ones -- the same horizontal inset (space.3), the same hairline border token (borderSubtle, flipped to border-top since this sits at the opposite edge), and the header/body gap (space.2) for its own vertical padding and for spacing between whatever it's given. */
40
+ /* Highlights: a 2-column grid of label+icon+value cards, no card
41
+ chrome at all (no border/background/radius/shadow -- confirmed on
42
+ the live DOM: every highlight's computed style is fully transparent). 8px row/column gap; two rows measured "233x86" inside 473px content width is exactly (473 - 8) / 2. */
43
+ & .crisp-recordpanel-highlights { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-2); }
44
+ & .crisp-recordpanel-highlight { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); min-width: 0; }
45
+ & .crisp-recordpanel-highlight-label { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
46
+ & .crisp-recordpanel-highlight-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
47
+ & .crisp-recordpanel-highlight-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
48
+ & .crisp-recordpanel-highlight-value { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; }
49
+ /* Activity is a consumer-supplied slot (crisp has no activity-feed model of its own to render) -- this only wraps it with the measured section heading above. */
50
+ & .crisp-recordpanel-activity { font-size: var(--crisp-text-lg); }
51
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
52
+ same horizontal inset (space.3), the same hairline border token
53
+ (borderSubtle, flipped to border-top since this sits at the opposite
54
+ edge), and the header/body gap (space.2) for its own vertical padding
55
+ and for spacing between whatever it's given. */
51
56
  & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
57
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
52
58
  }
53
59
 
54
60
  @media (prefers-reduced-motion: reduce) {
package/dist/richtext.css CHANGED
@@ -15,12 +15,38 @@
15
15
  & .crisp-rte-body code { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; font-size: var(--crisp-text-md); padding: 1px var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
16
16
  & .crisp-rte-body a { color: var(--crisp-bg-brand-solid); text-decoration: underline; text-underline-offset: 2px; }
17
17
  & .crisp-rte-body strong { font-weight: var(--crisp-weight-semibold); }
18
- & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: var(--crisp-space-0-5); border-radius: var(--crisp-radius-md); background: #20242c; box-shadow: 0 8px 28px -6px rgba(0,0,0,0.36), 0 2px 6px -2px rgba(0,0,0,0.24); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
18
+ & .crisp-rte-body img { display: block; max-width: 100%; border-radius: var(--crisp-radius-md); }
19
+ /* Not measured against Attio (#428) — nothing in a read-only pass over app.attio.com had a live @mention rendered to read computed styles from, and typing one to create one is off-limits there. Reused verbatim from the one measured "brand" tag/chip this repo already has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
20
+ inventing a new color or shape. */
21
+ & .crisp-rte-mention { display: inline; border-radius: var(--crisp-radius-chip); padding: 0 var(--crisp-space-1); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); font-weight: var(--crisp-weight-medium); cursor: default; }
22
+
23
+ & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: 2px; border-radius: 10px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
19
24
  & .crisp-rte-toolbar { @starting-style { opacity: 0; } }
20
- & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: rgba(255,255,255,0.86); font-family: inherit; cursor: pointer; transition: background-color .1s; }
21
- & .crisp-rte-tool:hover { background: rgba(255,255,255,0.12); color: #fff; }
22
- & .crisp-rte-tool[data-active] { color: var(--crisp-bg-brand-solid); }
23
- & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: rgba(255,255,255,0.16); }
25
+ & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); min-width: 30px; height: 28px; padding: 0 7px; margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .1s; }
26
+ & .crisp-rte-tool--labelled { width: auto; }
27
+ & .crisp-rte-tool:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
28
+ & .crisp-rte-tool[data-active], & .crisp-rte-tool[aria-expanded="true"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
29
+ & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--crisp-border-subtle); }
30
+
31
+ & .crisp-rte-swatchpanel { position: fixed; z-index: 71; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--crisp-space-1); width: 168px; padding: var(--crisp-space-1-5); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, var(--crisp-space-1-5)); opacity: 1; transition: opacity 0.1s ease; }
32
+ & .crisp-rte-swatchpanel { @starting-style { opacity: 0; } }
33
+ & .crisp-rte-swatch { width: 28px; height: 28px; padding: 0; margin: 0; border: none; border-radius: 50%; background: none; font-family: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); cursor: pointer; }
34
+ & .crisp-rte-swatch--none { background: var(--crisp-bg-surface); position: relative; }
35
+ & .crisp-rte-swatch--none::after { content: ""; position: absolute; inset: 6px; border-top: 1.5px solid color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); transform: rotate(-45deg); }
36
+
37
+ & .crisp-rte-blockmenu { position: fixed; z-index: 71; box-sizing: border-box; width: 356px; padding: 0; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transition: opacity 0.1s ease; }
38
+ & .crisp-rte-blockmenu { @starting-style { opacity: 0; } }
39
+ & .crisp-rte-blockmenu-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-2-5) var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
40
+ & .crisp-rte-blockmenu-input { flex: 1; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); height: 20px; color: var(--crisp-fg-primary); }
41
+ & .crisp-rte-blockmenu-input::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
42
+ & .crisp-rte-blockmenu-list { display: flex; flex-direction: column; padding: var(--crisp-space-1-5); gap: var(--crisp-space-px); }
43
+ & .crisp-rte-blockmenu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 52px; padding: var(--crisp-space-1-5) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; text-align: start; cursor: pointer; }
44
+ & .crisp-rte-blockmenu-item svg { flex-shrink: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
45
+ & .crisp-rte-blockmenu-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
46
+ & .crisp-rte-blockmenu-item-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
47
+ & .crisp-rte-blockmenu-item-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
48
+ & .crisp-rte-blockmenu-item-desc { font-size: var(--crisp-text-sm); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
49
+ & .crisp-rte-blockmenu-empty { padding: var(--crisp-space-4) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-md); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
24
50
  }
25
51
 
26
52
  @media (prefers-reduced-motion: reduce) {
@@ -16,12 +16,38 @@
16
16
  & .crisp-rte-body code { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; font-size: var(--crisp-text-md); padding: 1px var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
17
17
  & .crisp-rte-body a { color: var(--crisp-bg-brand-solid); text-decoration: underline; text-underline-offset: 2px; }
18
18
  & .crisp-rte-body strong { font-weight: var(--crisp-weight-semibold); }
19
- & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: var(--crisp-space-0-5); border-radius: var(--crisp-radius-md); background: #20242c; box-shadow: 0 8px 28px -6px rgba(0,0,0,0.36), 0 2px 6px -2px rgba(0,0,0,0.24); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
19
+ & .crisp-rte-body img { display: block; max-width: 100%; border-radius: var(--crisp-radius-md); }
20
+ /* Not measured against Attio (#428) — nothing in a read-only pass over app.attio.com had a live @mention rendered to read computed styles from, and typing one to create one is off-limits there. Reused verbatim from the one measured "brand" tag/chip this repo already has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
21
+ inventing a new color or shape. */
22
+ & .crisp-rte-mention { display: inline; border-radius: var(--crisp-radius-chip); padding: 0 var(--crisp-space-1); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); font-weight: var(--crisp-weight-medium); cursor: default; }
23
+
24
+ & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: 2px; border-radius: 10px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
20
25
  & .crisp-rte-toolbar { @starting-style { opacity: 0; } }
21
- & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: rgba(255,255,255,0.86); font-family: inherit; cursor: pointer; transition: background-color .1s; }
22
- & .crisp-rte-tool:hover { background: rgba(255,255,255,0.12); color: #fff; }
23
- & .crisp-rte-tool[data-active] { color: var(--crisp-bg-brand-solid); }
24
- & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: rgba(255,255,255,0.16); }
26
+ & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); min-width: 30px; height: 28px; padding: 0 7px; margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .1s; }
27
+ & .crisp-rte-tool--labelled { width: auto; }
28
+ & .crisp-rte-tool:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
29
+ & .crisp-rte-tool[data-active], & .crisp-rte-tool[aria-expanded="true"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
30
+ & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--crisp-border-subtle); }
31
+
32
+ & .crisp-rte-swatchpanel { position: fixed; z-index: 71; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--crisp-space-1); width: 168px; padding: var(--crisp-space-1-5); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, var(--crisp-space-1-5)); opacity: 1; transition: opacity 0.1s ease; }
33
+ & .crisp-rte-swatchpanel { @starting-style { opacity: 0; } }
34
+ & .crisp-rte-swatch { width: 28px; height: 28px; padding: 0; margin: 0; border: none; border-radius: 50%; background: none; font-family: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); cursor: pointer; }
35
+ & .crisp-rte-swatch--none { background: var(--crisp-bg-surface); position: relative; }
36
+ & .crisp-rte-swatch--none::after { content: ""; position: absolute; inset: 6px; border-top: 1.5px solid color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); transform: rotate(-45deg); }
37
+
38
+ & .crisp-rte-blockmenu { position: fixed; z-index: 71; box-sizing: border-box; width: 356px; padding: 0; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transition: opacity 0.1s ease; }
39
+ & .crisp-rte-blockmenu { @starting-style { opacity: 0; } }
40
+ & .crisp-rte-blockmenu-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-2-5) var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
41
+ & .crisp-rte-blockmenu-input { flex: 1; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); height: 20px; color: var(--crisp-fg-primary); }
42
+ & .crisp-rte-blockmenu-input::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
43
+ & .crisp-rte-blockmenu-list { display: flex; flex-direction: column; padding: var(--crisp-space-1-5); gap: var(--crisp-space-px); }
44
+ & .crisp-rte-blockmenu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 52px; padding: var(--crisp-space-1-5) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; text-align: start; cursor: pointer; }
45
+ & .crisp-rte-blockmenu-item svg { flex-shrink: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
46
+ & .crisp-rte-blockmenu-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
47
+ & .crisp-rte-blockmenu-item-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
48
+ & .crisp-rte-blockmenu-item-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
49
+ & .crisp-rte-blockmenu-item-desc { font-size: var(--crisp-text-sm); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
50
+ & .crisp-rte-blockmenu-empty { padding: var(--crisp-space-4) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-md); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
25
51
  }
26
52
 
27
53
  @media (prefers-reduced-motion: reduce) {
package/dist/styles.css CHANGED
@@ -2238,52 +2238,58 @@
2238
2238
  position: absolute; top: var(--crisp-space-2); inset-inline-end: var(--crisp-space-2); bottom: var(--crisp-space-2); z-index: 20; display: flex; flex-direction: column; overflow: hidden; width: 420px; max-width: calc(92% - var(--crisp-space-2)); background: var(--crisp-bg-surface); border-radius: var(--crisp-radius-xl); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transform: translateX(0); transition: transform .2s ease;
2239
2239
  @starting-style { transform: translateX(calc(100% + var(--crisp-space-2))); }
2240
2240
  & * { box-sizing: border-box; }
2241
- & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); }
2241
+ /* #448: measured live on app.attio.com's peek -- a toolbar row above
2242
+ the identity header: Close, then Previous/Next, on the left; "Open
2243
+ record" on the right. 49px tall, 8px/10px padding, a hairline border
2244
+ beneath (the same borderSubtle every other panel/row edge here
2245
+ already uses). Close keeps the existing transparent ghost button;
2246
+ Previous/Next/Open record are a distinct field-elevation style
2247
+ (white fill, e.field's own ring+shadow, solid icon) -- measured as genuinely different chrome from Close, not a reuse. */
2248
+ & .crisp-recordpanel-toolbar { display: flex; align-items: center; height: 49px; flex-shrink: 0; padding: var(--crisp-space-2) var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); }
2249
+ & .crisp-recordpanel-toolbar-spacer { flex: 1; }
2250
+ & .crisp-recordpanel-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
2251
+ & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
2252
+ & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2253
+ & .crisp-recordpanel-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; cursor: pointer; }
2254
+ & .crisp-recordpanel-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
2255
+ & .crisp-recordpanel-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
2256
+ & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); }
2242
2257
  & .crisp-recordpanel-fav { width: 22px; height: var(--crisp-size-chip); border-radius: var(--crisp-radius-sm); flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
2243
2258
  & .crisp-recordpanel-title { flex: 1; min-width: 0; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2244
- & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; transition: background-color .12s; }
2245
- & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2246
2259
  & .crisp-recordpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-2) var(--crisp-space-3); }
2247
- & .crisp-recordpanel-sectionlabel { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); padding: var(--crisp-space-2) var(--crisp-space-1) var(--crisp-space-1); }
2248
- & .crisp-recordpanel-field { display: flex; align-items: flex-start; gap: var(--crisp-space-2); min-height: 32px; padding: var(--crisp-space-1) var(--crisp-space-1); border-radius: var(--crisp-radius-md); }
2249
- & .crisp-recordpanel-field:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); }
2250
- & .crisp-recordpanel-field-label { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 132px; flex-shrink: 0; padding-top: 3px; color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); }
2251
- & .crisp-recordpanel-field-label svg { flex-shrink: 0; }
2252
- & .crisp-recordpanel-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 3px; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
2253
- & .crisp-recordpanel-field-value[data-editable] { cursor: pointer; }
2254
- /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
2255
- drops a button's accessible semantics in some engines. */
2256
- & .crisp-recordpanel-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
2257
- & .crisp-recordpanel-empty { color: var(--crisp-fg-muted); }
2258
- /* #431: measured live on app.attio.com's record side panel -- clicking a
2259
- field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no box-shadow), same font
2260
- as the value it replaces (hence `font: inherit`, not a restated
2261
- size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't
2262
- Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
2263
- & .crisp-recordpanel-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
2264
- /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
2265
- .crisp-datatable-edit:focus, e.g.). */
2266
- & .crisp-recordpanel-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
2267
- & .crisp-recordpanel-edit:disabled { opacity: 0.5; cursor: not-allowed; }
2268
- & .crisp-recordpanel-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
2269
- & .crisp-recordpanel-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
2270
- & .crisp-recordpanel-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
2271
- & .crisp-recordpanel-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
2272
-
2273
- & .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
2274
- & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
2275
- & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
2276
- & .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
2277
- & .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
2278
- & .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
2279
- & .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
2280
- & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
2281
- & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
2282
- & .crisp-recordpanel-link:hover { text-decoration: underline; }
2283
-
2284
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the
2285
- header's own measured values rather than inventing new ones -- the same horizontal inset (space.3), the same hairline border token (borderSubtle, flipped to border-top since this sits at the opposite edge), and the header/body gap (space.2) for its own vertical padding and for spacing between whatever it's given. */
2260
+ /* #448: measured live on app.attio.com's peek (hover a Companies row,
2261
+ click "Open preview"). Section label reads identically for both
2262
+ Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix
2263
+ RecordPage's own field-label already uses (product/recordpage's
2264
+ .crisp-recordpage-field-label) -- one more surface landing on the
2265
+ same measured "muted section heading" colour. */
2266
+ & .crisp-recordpanel-section { padding: var(--crisp-space-2) var(--crisp-space-1) 0; }
2267
+ & .crisp-recordpanel-section-head { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); height: 20px; margin-bottom: var(--crisp-space-2); }
2268
+ & .crisp-recordpanel-section-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2269
+ /* "View all" -- measured 10px/400, a lighter mix than the section label
2270
+ (55%, not 63%) -- its own distinct, honestly-measured value, not a
2271
+ reuse of a nearby one that happened to be close. */
2272
+ & .crisp-recordpanel-section-action { border: none; background: none; padding: 0; font-family: inherit; font-size: 10px; font-weight: var(--crisp-weight-regular); color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
2273
+ & .crisp-recordpanel-section-action:hover { color: var(--crisp-fg-primary); }
2274
+
2275
+ /* Highlights: a 2-column grid of label+icon+value cards, no card
2276
+ chrome at all (no border/background/radius/shadow -- confirmed on
2277
+ the live DOM: every highlight's computed style is fully transparent). 8px row/column gap; two rows measured "233x86" inside 473px content width is exactly (473 - 8) / 2. */
2278
+ & .crisp-recordpanel-highlights { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-2); }
2279
+ & .crisp-recordpanel-highlight { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); min-width: 0; }
2280
+ & .crisp-recordpanel-highlight-label { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2281
+ & .crisp-recordpanel-highlight-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2282
+ & .crisp-recordpanel-highlight-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2283
+ & .crisp-recordpanel-highlight-value { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; }
2284
+ /* Activity is a consumer-supplied slot (crisp has no activity-feed model of its own to render) -- this only wraps it with the measured section heading above. */
2285
+ & .crisp-recordpanel-activity { font-size: var(--crisp-text-lg); }
2286
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
2287
+ same horizontal inset (space.3), the same hairline border token
2288
+ (borderSubtle, flipped to border-top since this sits at the opposite
2289
+ edge), and the header/body gap (space.2) for its own vertical padding
2290
+ and for spacing between whatever it's given. */
2286
2291
  & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
2292
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2287
2293
  }
2288
2294
 
2289
2295
  @media (prefers-reduced-motion: reduce) {
@@ -2531,12 +2537,38 @@
2531
2537
  & .crisp-rte-body code { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; font-size: var(--crisp-text-md); padding: 1px var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
2532
2538
  & .crisp-rte-body a { color: var(--crisp-bg-brand-solid); text-decoration: underline; text-underline-offset: 2px; }
2533
2539
  & .crisp-rte-body strong { font-weight: var(--crisp-weight-semibold); }
2534
- & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: var(--crisp-space-0-5); border-radius: var(--crisp-radius-md); background: #20242c; box-shadow: 0 8px 28px -6px rgba(0,0,0,0.36), 0 2px 6px -2px rgba(0,0,0,0.24); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
2540
+ & .crisp-rte-body img { display: block; max-width: 100%; border-radius: var(--crisp-radius-md); }
2541
+ /* Not measured against Attio (#428) — nothing in a read-only pass over app.attio.com had a live @mention rendered to read computed styles from, and typing one to create one is off-limits there. Reused verbatim from the one measured "brand" tag/chip this repo already has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
2542
+ inventing a new color or shape. */
2543
+ & .crisp-rte-mention { display: inline; border-radius: var(--crisp-radius-chip); padding: 0 var(--crisp-space-1); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); font-weight: var(--crisp-weight-medium); cursor: default; }
2544
+
2545
+ & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: 2px; border-radius: 10px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
2535
2546
  & .crisp-rte-toolbar { @starting-style { opacity: 0; } }
2536
- & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: rgba(255,255,255,0.86); font-family: inherit; cursor: pointer; transition: background-color .1s; }
2537
- & .crisp-rte-tool:hover { background: rgba(255,255,255,0.12); color: #fff; }
2538
- & .crisp-rte-tool[data-active] { color: var(--crisp-bg-brand-solid); }
2539
- & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: rgba(255,255,255,0.16); }
2547
+ & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); min-width: 30px; height: 28px; padding: 0 7px; margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .1s; }
2548
+ & .crisp-rte-tool--labelled { width: auto; }
2549
+ & .crisp-rte-tool:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
2550
+ & .crisp-rte-tool[data-active], & .crisp-rte-tool[aria-expanded="true"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
2551
+ & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--crisp-border-subtle); }
2552
+
2553
+ & .crisp-rte-swatchpanel { position: fixed; z-index: 71; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--crisp-space-1); width: 168px; padding: var(--crisp-space-1-5); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, var(--crisp-space-1-5)); opacity: 1; transition: opacity 0.1s ease; }
2554
+ & .crisp-rte-swatchpanel { @starting-style { opacity: 0; } }
2555
+ & .crisp-rte-swatch { width: 28px; height: 28px; padding: 0; margin: 0; border: none; border-radius: 50%; background: none; font-family: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); cursor: pointer; }
2556
+ & .crisp-rte-swatch--none { background: var(--crisp-bg-surface); position: relative; }
2557
+ & .crisp-rte-swatch--none::after { content: ""; position: absolute; inset: 6px; border-top: 1.5px solid color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); transform: rotate(-45deg); }
2558
+
2559
+ & .crisp-rte-blockmenu { position: fixed; z-index: 71; box-sizing: border-box; width: 356px; padding: 0; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transition: opacity 0.1s ease; }
2560
+ & .crisp-rte-blockmenu { @starting-style { opacity: 0; } }
2561
+ & .crisp-rte-blockmenu-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-2-5) var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2562
+ & .crisp-rte-blockmenu-input { flex: 1; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); height: 20px; color: var(--crisp-fg-primary); }
2563
+ & .crisp-rte-blockmenu-input::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2564
+ & .crisp-rte-blockmenu-list { display: flex; flex-direction: column; padding: var(--crisp-space-1-5); gap: var(--crisp-space-px); }
2565
+ & .crisp-rte-blockmenu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 52px; padding: var(--crisp-space-1-5) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; text-align: start; cursor: pointer; }
2566
+ & .crisp-rte-blockmenu-item svg { flex-shrink: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2567
+ & .crisp-rte-blockmenu-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
2568
+ & .crisp-rte-blockmenu-item-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
2569
+ & .crisp-rte-blockmenu-item-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
2570
+ & .crisp-rte-blockmenu-item-desc { font-size: var(--crisp-text-sm); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
2571
+ & .crisp-rte-blockmenu-empty { padding: var(--crisp-space-4) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-md); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
2540
2572
  }
2541
2573
 
2542
2574
  @media (prefers-reduced-motion: reduce) {