@crisp-ui-kit/crisp 0.54.3 → 0.54.4

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 (48) hide show
  1. package/dist/board.css +13 -1
  2. package/dist/board.layered.css +13 -1
  3. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +41 -4
  4. package/dist/cjs/index.js +2 -0
  5. package/dist/cjs/product/board/board.js +60 -2
  6. package/dist/cjs/product/board/board.recipe.js +10 -0
  7. package/dist/cjs/product/comments/comments.js +426 -0
  8. package/dist/cjs/product/comments/comments.recipe.js +126 -0
  9. package/dist/cjs/product/comments/index.js +8 -0
  10. package/dist/cjs/product/listpage/listpage.recipe.js +10 -1
  11. package/dist/cjs/product/objectsettings/index.js +16 -0
  12. package/dist/cjs/product/objectsettings/objectsettings.js +365 -0
  13. package/dist/cjs/product/objectsettings/objectsettings.recipe.js +65 -0
  14. package/dist/cjs/product/taskstable/taskstable.recipe.js +10 -1
  15. package/dist/comments.css +80 -0
  16. package/dist/comments.d.ts +7 -0
  17. package/dist/comments.layered.css +82 -0
  18. package/dist/esm/components/tabletoolbar/tabletoolbar.js +43 -6
  19. package/dist/esm/index.js +2 -0
  20. package/dist/esm/product/board/board.js +61 -3
  21. package/dist/esm/product/board/board.recipe.js +10 -0
  22. package/dist/esm/product/comments/comments.js +388 -0
  23. package/dist/esm/product/comments/comments.recipe.js +123 -0
  24. package/dist/esm/product/comments/index.js +2 -0
  25. package/dist/esm/product/listpage/listpage.recipe.js +10 -1
  26. package/dist/esm/product/objectsettings/index.js +2 -0
  27. package/dist/esm/product/objectsettings/objectsettings.js +322 -0
  28. package/dist/esm/product/objectsettings/objectsettings.recipe.js +62 -0
  29. package/dist/esm/product/taskstable/taskstable.recipe.js +10 -1
  30. package/dist/listpage.css +8 -1
  31. package/dist/listpage.layered.css +8 -1
  32. package/dist/objectsettings.css +49 -0
  33. package/dist/objectsettings.d.ts +7 -0
  34. package/dist/objectsettings.layered.css +51 -0
  35. package/dist/styles.css +160 -3
  36. package/dist/styles.layered.css +160 -3
  37. package/dist/taskstable.css +8 -1
  38. package/dist/taskstable.layered.css +8 -1
  39. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +8 -2
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/product/board/board.d.ts +20 -1
  42. package/dist/types/product/comments/comments.d.ts +192 -0
  43. package/dist/types/product/comments/comments.recipe.d.ts +3 -0
  44. package/dist/types/product/comments/index.d.ts +2 -0
  45. package/dist/types/product/objectsettings/index.d.ts +2 -0
  46. package/dist/types/product/objectsettings/objectsettings.d.ts +261 -0
  47. package/dist/types/product/objectsettings/objectsettings.recipe.d.ts +3 -0
  48. package/package.json +11 -1
package/dist/styles.css CHANGED
@@ -2025,8 +2025,20 @@
2025
2025
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
2026
2026
  & .crisp-board-card:focus { outline: none; }
2027
2027
  & .crisp-board-card:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2028
+ /* #604: a per-column calc footer, mirroring the table's own (measured
2029
+ live, Deals pipeline). Sits below the scrollable card body, not
2030
+ inside it or pinned to it -- Attio's own is position: static, not sticky, so it's a plain flex-shrink:0 sibling here too. */
2031
+ & .crisp-board-col-footer { flex-shrink: 0; padding: var(--crisp-space-1) 0; }
2032
+ & .crisp-board-col-calc { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); width: 100%; height: 28px; padding: var(--crisp-space-1) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); cursor: pointer; }
2033
+ & .crisp-board-col-calc:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2034
+ & .crisp-board-col-calc[data-empty] { opacity: 0.7; }
2035
+ & .crisp-board-col-calc-label { text-transform: capitalize; }
2036
+ & .crisp-board-col-calc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
2028
2037
  /* announced, never drawn */
2029
- & .crisp-board-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
2038
+ & .crisp-board-live {
2039
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
2040
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
2041
+ }
2030
2042
  }
2031
2043
 
2032
2044
  @media (prefers-reduced-motion: reduce) {
@@ -2078,6 +2090,87 @@
2078
2090
  }
2079
2091
  }
2080
2092
 
2093
+ .crisp-comments {
2094
+ display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2095
+ & * { box-sizing: border-box; }
2096
+ & .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
2097
+ & .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
2098
+ & .crisp-comments-headbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2099
+ & .crisp-comments-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }
2100
+ & .crisp-comments-title { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2101
+ & .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2102
+ & .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
2103
+ & .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
2104
+ /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
2105
+ & .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
2106
+ & .crisp-comments-row-main { flex: 1; min-width: 0; }
2107
+ & .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
2108
+ & .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
2109
+ & .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
2110
+ & .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2111
+ & .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
2112
+ /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
2113
+ & .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
2114
+ & .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
2115
+ & .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 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, color .12s; }
2116
+ & .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2117
+ & .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
2118
+ /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
2119
+ & .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
2120
+ & .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
2121
+ & .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
2122
+ & .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
2123
+ & .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
2124
+ & .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2125
+ /* Reply-in-thread view -- an indented block of replies below the root. */
2126
+ & .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
2127
+ & .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
2128
+ /* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
2129
+ & .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
2130
+ & .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
2131
+ & .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
2132
+ & .crisp-comments-field:empty::before { content: attr(data-placeholder); color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2133
+ & .crisp-comments-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--crisp-space-4); }
2134
+ & .crisp-comments-composer-tools { display: flex; align-items: center; gap: var(--crisp-space-1); }
2135
+ & .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
2136
+ & .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
2137
+ /* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
2138
+ confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
2139
+ & .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
2140
+ & .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
2141
+ & .crisp-comments-post:disabled { opacity: .4; cursor: default; }
2142
+ /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
2143
+ live here; kept as a comment for the record. */
2144
+
2145
+ /* An inserted "@mention" chip in the composer -- same shape as
2146
+ RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
2147
+ & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
2148
+
2149
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
2150
+ capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
2151
+ & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); 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); }
2152
+ & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2153
+ & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2154
+ & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2155
+ & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2156
+ & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2157
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
2158
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2159
+ icon + 12px count text, both at 55% ink, replacing the dot. */
2160
+ & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2161
+ & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2162
+ & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2163
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2164
+ }
2165
+
2166
+ @media (prefers-reduced-motion: reduce) {
2167
+ .crisp-comments, .crisp-comments * {
2168
+ animation-duration: 0.01ms !important;
2169
+ animation-iteration-count: 1 !important;
2170
+ transition-duration: 0.01ms !important;
2171
+ }
2172
+ }
2173
+
2081
2174
  .crisp-entitycard {
2082
2175
  display: flex; flex-direction: column; box-sizing: border-box; width: 100%; min-width: 0; text-align: start; padding: var(--crisp-space-3) var(--crisp-space-4); margin: 0; border: none; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); color: var(--crisp-fg-primary); font-family: var(--crisp-font-sans); cursor: pointer; transition: box-shadow .12s ease, background-color .12s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2083
2176
  &:hover { box-shadow: inset 0 0 0 1px var(--crisp-border-default), 0 1px 2px rgba(0,0,0,0.04), 0 6px 16px -6px rgba(28,40,64,0.12); }
@@ -2208,7 +2301,14 @@
2208
2301
  & .crisp-listpage-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-header); flex-shrink: 0; padding: 0 var(--crisp-space-3); }
2209
2302
  & .crisp-listpage-toolbar-lead { display: flex; align-items: center; gap: var(--crisp-space-2); }
2210
2303
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
2211
- & .crisp-listpage-toolbar-trail { display: flex; align-items: center; gap: var(--crisp-space-2); }
2304
+ /* #641: trailing (View settings / New) has to reach the toolbar's own
2305
+ inline-end edge on its own -- the only thing pushing it there used
2306
+ to be .crisp-listpage-count's margin-inline-start: auto, which a consumer with no count badge (TasksTable, measured live: trailing sat right after leading, nowhere near the row's own right edge)
2307
+ never renders. A bare margin-inline-start: auto here is safely
2308
+ overridden by the more specific rule right below whenever count IS
2309
+ present, so the two cases don't fight -- count's own auto margin
2310
+ still does the pushing there, this one only fires when it's absent. */
2311
+ & .crisp-listpage-toolbar-trail { display: flex; align-items: center; gap: var(--crisp-space-2); margin-inline-start: auto; }
2212
2312
  & .crisp-listpage-count + .crisp-listpage-toolbar-trail { margin-inline-start: var(--crisp-space-3); }
2213
2313
  & .crisp-listpage-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-3) var(--crisp-space-3); }
2214
2314
  & .crisp-listpage-section-label { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -2249,6 +2349,56 @@
2249
2349
  }
2250
2350
  }
2251
2351
 
2352
+ .crisp-objectsettings {
2353
+ & .crisp-objectsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); }
2354
+ & .crisp-objectsettings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
2355
+ & .crisp-objectsettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); min-width: 0; }
2356
+ & .crisp-objectsettings-title { display: flex; align-items: center; gap: var(--crisp-space-2); font-size: 24px; line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.48px; color: var(--crisp-fg-primary); margin: 0; }
2357
+ & .crisp-objectsettings-subtitle { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); margin: 0; }
2358
+ & .crisp-objectsettings-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); }
2359
+ & .crisp-objectsettings-search { display: flex; align-items: center; flex: 1; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-muted); transition: box-shadow .15s; }
2360
+ & .crisp-objectsettings-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
2361
+ & .crisp-objectsettings-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
2362
+ & .crisp-objectsettings-search input::placeholder { color: var(--crisp-fg-muted); }
2363
+ & .crisp-objectsettings-filterbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; }
2364
+ & .crisp-objectsettings-filterbtn:hover { background: var(--crisp-bg-raised); }
2365
+ /* Object / attribute icon tile — Attio's own squircle (measured
2366
+ border-radius: 30%, not a scale token: it's a proportional radius tied to the tile's own size, unlike every other radius crisp uses,
2367
+ which is a fixed px). */
2368
+ & .crisp-objectsettings-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 16px; height: 16px; border-radius: 30%; color: var(--crisp-fg-on-brand); }
2369
+ & .crisp-objectsettings-icon--lg { width: 48px; height: 48px; border-radius: 16px; }
2370
+
2371
+ /* "Standard"/"Custom" type badge and the muted "No records" cell —
2372
+ plain text, not a Badge pill (measured: transparent background). */
2373
+ & .crisp-objectsettings-typebadge { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2374
+ & .crisp-objectsettings-muted { color: var(--crisp-fg-muted); }
2375
+
2376
+ /* Attribute-type option row (icon + label + optional trailing badge),
2377
+ composed as a Select option's `label` ReactNode. */
2378
+ & .crisp-objectsettings-typeoption { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; min-width: 0; }
2379
+ & .crisp-objectsettings-typeoption-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
2380
+ & .crisp-objectsettings-typeoption-badge { flex-shrink: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-brand-soft); }
2381
+ /* Create-attribute dialog's AI-autofill row and the submit button's ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
2382
+ .crisp-dialog-submit-key, kept local rather than reaching into
2383
+ another component's scoped classes). */
2384
+ & .crisp-objectsettings-aiautofill { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
2385
+ & .crisp-objectsettings-aiautofill-label { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
2386
+ & .crisp-objectsettings-aiautofill-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); box-shadow: inset 0 0 0 1px var(--crisp-border-default); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; }
2387
+ & .crisp-objectsettings-aiautofill-row > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2388
+ & .crisp-objectsettings-aiautofill-row > span { flex: 1; }
2389
+ & .crisp-objectsettings-kbd { display: inline-flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: var(--crisp-space-1); }
2390
+ & .crisp-objectsettings-kbd kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: inherit; font-family: inherit; font-style: normal; font-size: 10px; }
2391
+ & .crisp-objectsettings-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-4); }
2392
+ }
2393
+
2394
+ @media (prefers-reduced-motion: reduce) {
2395
+ .crisp-objectsettings, .crisp-objectsettings * {
2396
+ animation-duration: 0.01ms !important;
2397
+ animation-iteration-count: 1 !important;
2398
+ transition-duration: 0.01ms !important;
2399
+ }
2400
+ }
2401
+
2252
2402
  .crisp-onboarding {
2253
2403
  display: flex; flex-direction: column; box-sizing: border-box; width: 272px; padding: var(--crisp-space-3); gap: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent), 0 8px 28px -6px rgba(28,40,64,0.16), 0 2px 6px -2px rgba(28,40,64,0.10);
2254
2404
  & .crisp-onboarding-head { display: flex; align-items: center; gap: var(--crisp-space-1); }
@@ -3033,7 +3183,14 @@
3033
3183
  & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
3034
3184
  & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3035
3185
  & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
3036
- & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; }
3186
+ /* #642: measured live -- title, due date and assignee name are ALL 14px/500 on Attio's own Tasks page. Only -title/-due set their own
3187
+ font-size (and only -title its own weight), so an unstyled consumer
3188
+ slot (a plain PersonChip/RecordChip name span with no font-size of
3189
+ its own) fell through to the browser/body default, 16px/400. Set
3190
+ the ambient font here so it cascades to any slot content that
3191
+ doesn't override it -- -title/-due's own declarations still apply,
3192
+ unchanged, they're just now redundant with this baseline rather than the only source of it. */
3193
+ & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); }
3037
3194
  & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
3038
3195
  & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
3039
3196
  & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -2026,8 +2026,20 @@
2026
2026
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
2027
2027
  & .crisp-board-card:focus { outline: none; }
2028
2028
  & .crisp-board-card:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2029
+ /* #604: a per-column calc footer, mirroring the table's own (measured
2030
+ live, Deals pipeline). Sits below the scrollable card body, not
2031
+ inside it or pinned to it -- Attio's own is position: static, not sticky, so it's a plain flex-shrink:0 sibling here too. */
2032
+ & .crisp-board-col-footer { flex-shrink: 0; padding: var(--crisp-space-1) 0; }
2033
+ & .crisp-board-col-calc { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); width: 100%; height: 28px; padding: var(--crisp-space-1) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); cursor: pointer; }
2034
+ & .crisp-board-col-calc:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2035
+ & .crisp-board-col-calc[data-empty] { opacity: 0.7; }
2036
+ & .crisp-board-col-calc-label { text-transform: capitalize; }
2037
+ & .crisp-board-col-calc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
2029
2038
  /* announced, never drawn */
2030
- & .crisp-board-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
2039
+ & .crisp-board-live {
2040
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
2041
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
2042
+ }
2031
2043
  }
2032
2044
 
2033
2045
  @media (prefers-reduced-motion: reduce) {
@@ -2079,6 +2091,87 @@
2079
2091
  }
2080
2092
  }
2081
2093
 
2094
+ .crisp-comments {
2095
+ display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2096
+ & * { box-sizing: border-box; }
2097
+ & .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
2098
+ & .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
2099
+ & .crisp-comments-headbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2100
+ & .crisp-comments-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }
2101
+ & .crisp-comments-title { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2102
+ & .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2103
+ & .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
2104
+ & .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
2105
+ /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
2106
+ & .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
2107
+ & .crisp-comments-row-main { flex: 1; min-width: 0; }
2108
+ & .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
2109
+ & .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
2110
+ & .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
2111
+ & .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2112
+ & .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
2113
+ /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
2114
+ & .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
2115
+ & .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
2116
+ & .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 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, color .12s; }
2117
+ & .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2118
+ & .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
2119
+ /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
2120
+ & .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
2121
+ & .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
2122
+ & .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
2123
+ & .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
2124
+ & .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
2125
+ & .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2126
+ /* Reply-in-thread view -- an indented block of replies below the root. */
2127
+ & .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
2128
+ & .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
2129
+ /* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
2130
+ & .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
2131
+ & .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
2132
+ & .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
2133
+ & .crisp-comments-field:empty::before { content: attr(data-placeholder); color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2134
+ & .crisp-comments-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--crisp-space-4); }
2135
+ & .crisp-comments-composer-tools { display: flex; align-items: center; gap: var(--crisp-space-1); }
2136
+ & .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
2137
+ & .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
2138
+ /* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
2139
+ confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
2140
+ & .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
2141
+ & .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
2142
+ & .crisp-comments-post:disabled { opacity: .4; cursor: default; }
2143
+ /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
2144
+ live here; kept as a comment for the record. */
2145
+
2146
+ /* An inserted "@mention" chip in the composer -- same shape as
2147
+ RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
2148
+ & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
2149
+
2150
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
2151
+ capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
2152
+ & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); 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); }
2153
+ & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2154
+ & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2155
+ & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2156
+ & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2157
+ & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2158
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
2159
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2160
+ icon + 12px count text, both at 55% ink, replacing the dot. */
2161
+ & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2162
+ & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2163
+ & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2164
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2165
+ }
2166
+
2167
+ @media (prefers-reduced-motion: reduce) {
2168
+ .crisp-comments, .crisp-comments * {
2169
+ animation-duration: 0.01ms !important;
2170
+ animation-iteration-count: 1 !important;
2171
+ transition-duration: 0.01ms !important;
2172
+ }
2173
+ }
2174
+
2082
2175
  .crisp-entitycard {
2083
2176
  display: flex; flex-direction: column; box-sizing: border-box; width: 100%; min-width: 0; text-align: start; padding: var(--crisp-space-3) var(--crisp-space-4); margin: 0; border: none; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); color: var(--crisp-fg-primary); font-family: var(--crisp-font-sans); cursor: pointer; transition: box-shadow .12s ease, background-color .12s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2084
2177
  &:hover { box-shadow: inset 0 0 0 1px var(--crisp-border-default), 0 1px 2px rgba(0,0,0,0.04), 0 6px 16px -6px rgba(28,40,64,0.12); }
@@ -2209,7 +2302,14 @@
2209
2302
  & .crisp-listpage-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-header); flex-shrink: 0; padding: 0 var(--crisp-space-3); }
2210
2303
  & .crisp-listpage-toolbar-lead { display: flex; align-items: center; gap: var(--crisp-space-2); }
2211
2304
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
2212
- & .crisp-listpage-toolbar-trail { display: flex; align-items: center; gap: var(--crisp-space-2); }
2305
+ /* #641: trailing (View settings / New) has to reach the toolbar's own
2306
+ inline-end edge on its own -- the only thing pushing it there used
2307
+ to be .crisp-listpage-count's margin-inline-start: auto, which a consumer with no count badge (TasksTable, measured live: trailing sat right after leading, nowhere near the row's own right edge)
2308
+ never renders. A bare margin-inline-start: auto here is safely
2309
+ overridden by the more specific rule right below whenever count IS
2310
+ present, so the two cases don't fight -- count's own auto margin
2311
+ still does the pushing there, this one only fires when it's absent. */
2312
+ & .crisp-listpage-toolbar-trail { display: flex; align-items: center; gap: var(--crisp-space-2); margin-inline-start: auto; }
2213
2313
  & .crisp-listpage-count + .crisp-listpage-toolbar-trail { margin-inline-start: var(--crisp-space-3); }
2214
2314
  & .crisp-listpage-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-3) var(--crisp-space-3); }
2215
2315
  & .crisp-listpage-section-label { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -2250,6 +2350,56 @@
2250
2350
  }
2251
2351
  }
2252
2352
 
2353
+ .crisp-objectsettings {
2354
+ & .crisp-objectsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); }
2355
+ & .crisp-objectsettings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
2356
+ & .crisp-objectsettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); min-width: 0; }
2357
+ & .crisp-objectsettings-title { display: flex; align-items: center; gap: var(--crisp-space-2); font-size: 24px; line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.48px; color: var(--crisp-fg-primary); margin: 0; }
2358
+ & .crisp-objectsettings-subtitle { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); margin: 0; }
2359
+ & .crisp-objectsettings-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); }
2360
+ & .crisp-objectsettings-search { display: flex; align-items: center; flex: 1; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-muted); transition: box-shadow .15s; }
2361
+ & .crisp-objectsettings-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
2362
+ & .crisp-objectsettings-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
2363
+ & .crisp-objectsettings-search input::placeholder { color: var(--crisp-fg-muted); }
2364
+ & .crisp-objectsettings-filterbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; }
2365
+ & .crisp-objectsettings-filterbtn:hover { background: var(--crisp-bg-raised); }
2366
+ /* Object / attribute icon tile — Attio's own squircle (measured
2367
+ border-radius: 30%, not a scale token: it's a proportional radius tied to the tile's own size, unlike every other radius crisp uses,
2368
+ which is a fixed px). */
2369
+ & .crisp-objectsettings-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 16px; height: 16px; border-radius: 30%; color: var(--crisp-fg-on-brand); }
2370
+ & .crisp-objectsettings-icon--lg { width: 48px; height: 48px; border-radius: 16px; }
2371
+
2372
+ /* "Standard"/"Custom" type badge and the muted "No records" cell —
2373
+ plain text, not a Badge pill (measured: transparent background). */
2374
+ & .crisp-objectsettings-typebadge { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2375
+ & .crisp-objectsettings-muted { color: var(--crisp-fg-muted); }
2376
+
2377
+ /* Attribute-type option row (icon + label + optional trailing badge),
2378
+ composed as a Select option's `label` ReactNode. */
2379
+ & .crisp-objectsettings-typeoption { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; min-width: 0; }
2380
+ & .crisp-objectsettings-typeoption-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
2381
+ & .crisp-objectsettings-typeoption-badge { flex-shrink: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-brand-soft); }
2382
+ /* Create-attribute dialog's AI-autofill row and the submit button's ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
2383
+ .crisp-dialog-submit-key, kept local rather than reaching into
2384
+ another component's scoped classes). */
2385
+ & .crisp-objectsettings-aiautofill { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
2386
+ & .crisp-objectsettings-aiautofill-label { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
2387
+ & .crisp-objectsettings-aiautofill-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); box-shadow: inset 0 0 0 1px var(--crisp-border-default); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; }
2388
+ & .crisp-objectsettings-aiautofill-row > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2389
+ & .crisp-objectsettings-aiautofill-row > span { flex: 1; }
2390
+ & .crisp-objectsettings-kbd { display: inline-flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: var(--crisp-space-1); }
2391
+ & .crisp-objectsettings-kbd kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: inherit; font-family: inherit; font-style: normal; font-size: 10px; }
2392
+ & .crisp-objectsettings-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-4); }
2393
+ }
2394
+
2395
+ @media (prefers-reduced-motion: reduce) {
2396
+ .crisp-objectsettings, .crisp-objectsettings * {
2397
+ animation-duration: 0.01ms !important;
2398
+ animation-iteration-count: 1 !important;
2399
+ transition-duration: 0.01ms !important;
2400
+ }
2401
+ }
2402
+
2253
2403
  .crisp-onboarding {
2254
2404
  display: flex; flex-direction: column; box-sizing: border-box; width: 272px; padding: var(--crisp-space-3); gap: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent), 0 8px 28px -6px rgba(28,40,64,0.16), 0 2px 6px -2px rgba(28,40,64,0.10);
2255
2405
  & .crisp-onboarding-head { display: flex; align-items: center; gap: var(--crisp-space-1); }
@@ -3034,7 +3184,14 @@
3034
3184
  & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
3035
3185
  & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3036
3186
  & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
3037
- & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; }
3187
+ /* #642: measured live -- title, due date and assignee name are ALL 14px/500 on Attio's own Tasks page. Only -title/-due set their own
3188
+ font-size (and only -title its own weight), so an unstyled consumer
3189
+ slot (a plain PersonChip/RecordChip name span with no font-size of
3190
+ its own) fell through to the browser/body default, 16px/400. Set
3191
+ the ambient font here so it cascades to any slot content that
3192
+ doesn't override it -- -title/-due's own declarations still apply,
3193
+ unchanged, they're just now redundant with this baseline rather than the only source of it. */
3194
+ & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); }
3038
3195
  & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
3039
3196
  & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
3040
3197
  & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -3,7 +3,14 @@
3
3
  & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
4
4
  & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
5
5
  & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
6
- & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; }
6
+ /* #642: measured live -- title, due date and assignee name are ALL 14px/500 on Attio's own Tasks page. Only -title/-due set their own
7
+ font-size (and only -title its own weight), so an unstyled consumer
8
+ slot (a plain PersonChip/RecordChip name span with no font-size of
9
+ its own) fell through to the browser/body default, 16px/400. Set
10
+ the ambient font here so it cascades to any slot content that
11
+ doesn't override it -- -title/-due's own declarations still apply,
12
+ unchanged, they're just now redundant with this baseline rather than the only source of it. */
13
+ & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); }
7
14
  & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
8
15
  & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
9
16
  & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -4,7 +4,14 @@
4
4
  & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
5
5
  & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
6
6
  & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
7
- & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; }
7
+ /* #642: measured live -- title, due date and assignee name are ALL 14px/500 on Attio's own Tasks page. Only -title/-due set their own
8
+ font-size (and only -title its own weight), so an unstyled consumer
9
+ slot (a plain PersonChip/RecordChip name span with no font-size of
10
+ its own) fell through to the browser/body default, 16px/400. Set
11
+ the ambient font here so it cascades to any slot content that
12
+ doesn't override it -- -title/-due's own declarations still apply,
13
+ unchanged, they're just now redundant with this baseline rather than the only source of it. */
14
+ & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); }
8
15
  & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
9
16
  & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
10
17
  & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -8,7 +8,7 @@ export type ToolbarSort = {
8
8
  key: string;
9
9
  dir: SortDir;
10
10
  };
11
- export type ToolbarFilterOp = "contains" | "is" | "isnot" | "isanyof" | "empty" | "notempty";
11
+ export type ToolbarFilterOp = "contains" | "doesnotcontain" | "startswith" | "endswith" | "is" | "isnot" | "isanyof" | "empty" | "notempty";
12
12
  export type ToolbarFilter = {
13
13
  id: string;
14
14
  key: string;
@@ -92,6 +92,12 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
92
92
  filterFieldPlaceholder?: string;
93
93
  /** Placeholder in a filter row's value input (default "Value…"). */
94
94
  filterValuePlaceholder?: string;
95
+ /** aria-label on a filter chip's "⋮" kebab (default "Filter options"). */
96
+ filterOptionsLabel?: string;
97
+ /** Label on the filter chip kebab's "Delete filter" action (default
98
+ * "Delete filter") — Attio's own chip kebab (#594) has exactly this one
99
+ * item. */
100
+ deleteFilterLabel?: string;
95
101
  /** Label on the "ascending" sort-direction option (default "Ascending"). */
96
102
  ascendingLabel?: string;
97
103
  /** Label on the "descending" sort-direction option (default "Descending"). */
@@ -139,7 +145,7 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
139
145
  * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
140
146
  * Created by), which one shared list can't reproduce.
141
147
  */
142
- export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
148
+ export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
143
149
  export type ToolbarToolProps = React.ComponentPropsWithoutRef<"button"> & {
144
150
  /** Drawn the way Sort/Filter are once a condition is set (#399). */
145
151
  active?: boolean;
@@ -65,6 +65,7 @@ export * from "./product/appshell";
65
65
  export * from "./product/authshell";
66
66
  export * from "./product/board";
67
67
  export * from "./product/command";
68
+ export * from "./product/comments";
68
69
  export * from "./product/entitycard";
69
70
  export * from "./product/flowcanvas";
70
71
  export * from "./product/formdialog";
@@ -73,6 +74,7 @@ export * from "./product/importdialog";
73
74
  export * from "./product/inbox";
74
75
  export * from "./product/listpage";
75
76
  export * from "./product/navitem";
77
+ export * from "./product/objectsettings";
76
78
  export * from "./product/onboarding";
77
79
  export * from "./product/recordpage";
78
80
  export * from "./product/recordpanel";
@@ -9,6 +9,16 @@ export type BoardCard = {
9
9
  id: string;
10
10
  columnId: string;
11
11
  };
12
+ /**
13
+ * #604: the eight aggregations Attio's own per-column board calc footer
14
+ * offers (measured live, Deals pipeline) -- a different set from the
15
+ * table's own calc footer (`count`/`filled`/`sum`/`avg`/`min`/`max`,
16
+ * `product/recordstable`): Board additionally splits "not empty" into a
17
+ * count AND a percent, and does the same for "empty". Not reconciled with
18
+ * the table's vocabulary -- these are two different Attio surfaces with
19
+ * two different measured menus, not one concept with two names.
20
+ */
21
+ export type BoardAgg = "countEmpty" | "countFilled" | "percentEmpty" | "percentFilled" | "sum" | "average" | "max" | "min";
12
22
  export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
13
23
  columns: BoardColumn[];
14
24
  cards: T[];
@@ -26,6 +36,15 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
26
36
  getCardLabel?: (card: T) => string;
27
37
  /** What a screen reader hears after a keyboard move. */
28
38
  announceMove?: (columnTitle: string, cardLabel?: string) => string;
39
+ /**
40
+ * A per-column calc footer (#604), mirroring the table's. Omit to skip
41
+ * it entirely -- Board has no footer by default. `getValue` reads the
42
+ * number to aggregate off a card (e.g. a deal's value); return
43
+ * null/undefined for a card with nothing set there ("empty").
44
+ */
45
+ calc?: {
46
+ getValue: (card: T) => number | null | undefined;
47
+ };
29
48
  className?: string;
30
49
  };
31
50
  /**
@@ -34,7 +53,7 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
34
53
  * (the parent owns the cards, so the move is a controlled state update).
35
54
  * Measured from a deals pipeline.
36
55
  */
37
- export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, className, ...rest }: BoardProps<T>): React.JSX.Element;
56
+ export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, calc, className, ...rest }: BoardProps<T>): React.JSX.Element;
38
57
  export declare namespace Board {
39
58
  var displayName: string;
40
59
  }