@crisp-ui-kit/crisp 0.55.3 → 0.55.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.
@@ -88,11 +88,19 @@
88
88
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
89
89
  /* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
90
90
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
91
- & th[data-sticky] { z-index: 3;
92
- } /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
93
- `tfoot td { z-index: 2 }
94
- `), which `td[data-sticky]`'s own higher-specificity
95
- `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
91
+ /* `thead th[data-sticky]`, not a bare `th[data-sticky]` (crisp#848, same
92
+ bare-`th` family as #829/#833/#835): this z-index:3 was written for
93
+ the HEADER's own sticky corner cell (sticky column x sticky header), meant to win over everything else. Unscoped, it also matched the primary column's BODY cell -- a real `<th scope="row" data-sticky>`
94
+ since #779 -- giving every scrolled body row's own sticky cell a HIGHER z-index than the sticky FOOTER's (2), so it painted over the
95
+ footer instead of the footer showing through. Confirmed live on a
96
+ real bounded-height, 25-row table (GRC's Risk register): scrolled partway, `elementFromPoint` at the footer's own on-screen cell
97
+ resolved to a body row's `<th>`, not the footer's `<td>`, and that
98
+ `<th>`'s computed z-index read 3. Scoped to `thead`, a body row's
99
+ own sticky `<th>` now falls back to the base rule above (z-index 1,
100
+ correct -- the same as a plain sticky `<td>`), and the footer's own explicit z-index below is unaffected either way. */
101
+ & thead th[data-sticky] { z-index: 3; }
102
+ /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
103
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
96
104
  & tfoot td[data-sticky] { z-index: 2; }
97
105
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
98
106
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -109,103 +117,96 @@
109
117
  & tbody tr[data-selected] td { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
110
118
  & tbody tr[data-selected]:hover td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface))); }
111
119
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
112
- /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%, composited OVER whatever row-state background the cell already has (hover, selected), which is why it's an ::after overlay and not a
113
- plain background. */
120
+ /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
121
+ already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
122
+ composited OVER whatever row-state background the cell already has
123
+ (hover, selected), which is why it's an ::after overlay and not a plain background. */
114
124
  & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
115
125
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
116
126
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
117
- /* An async onEdit's in-flight state (#385) -- the field/picker dims and stops accepting input rather than disappearing, so the just-typed or just-chosen value stays visible while its commit is pending. Same treatment Input already gives its own disabled state. */
127
+ /* An async onEdit's in-flight state (#385) -- the field/picker dims and
128
+ stops accepting input rather than disappearing, so the just-typed or
129
+ just-chosen value stays visible while its commit is pending. Same
130
+ treatment Input already gives its own disabled state. */
118
131
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
119
132
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
120
- /* The rejection's message, beside the cell's own content -- a sibling in the TableCell, never nested inside a column's own render output, so a
121
- hover-only wrapper (a Tooltip, say) around a custom render can't hide it. Block-level: the row grows to fit it rather than floating over the row below, so it can never end up clipped by the scroll container's overflow the way a position:fixed escape would need
122
- guarding against. */
133
+ /* The rejection's message, beside the cell's own content -- a sibling in
134
+ the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
135
+ it. Block-level: the row grows to fit it rather than floating over
136
+ the row below, so it can never end up clipped by the scroll
137
+ container's overflow the way a position:fixed escape would need guarding against. */
123
138
  & .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
124
- /* editor: "select"/"switch" -- always live, no separate edit mode, so
125
- the cell IS the picker: same chromeless-fill treatment as
126
- RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
139
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
140
+ shares RecordsTable's grid, Golden Rule 0). */
127
141
  & .crisp-datatable-td--picker { padding: 0; }
128
142
  & .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
129
143
  & .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
130
144
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
131
- /* reference picker: a leading 16px avatar per option, plus a trailing
132
- "Create X" row when the consumer supplies one (#449) -- same measured
133
- values as RecordsTable's own .crisp-recordstable-refoption*. */
145
+ /* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
134
146
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
135
147
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
136
148
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
137
149
  & .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
138
- /* #826 -- the full-body loading state (a filter/sort re-query, or a fresh page boot), measured live on Attio's Companies grid on both
139
- triggers: icon+text centred in the row-body area, 10px gap, 14px/500
140
- weight/20px line-height, both icon and text in the SAME muted
141
- rgba(0,0,0,0.63) -- the same color-mix figure this file already uses
142
- for other muted text (e.g. -phone-dial). Distinct from the cold
143
- in-place row load (blank, no indicator) and the warm-navigation
144
- per-cell shimmer (#599) -- neither of those swaps the whole body. */
150
+ /* #826 -- the full-body loading state (a filter/sort re-query, or a
151
+ fresh page boot), measured live on Attio's Companies grid on both triggers: icon+text centred in the row-body area, 10px gap, 14px/500 weight/20px line-height, both icon and text in the SAME muted rgba(0,0,0,0.63) -- the same color-mix figure this file already uses for other muted text (e.g. -phone-dial). Distinct from the cold in-place row load (blank, no indicator) and the warm-navigation per-cell shimmer (#599) -- neither of those swaps the whole body. */
145
152
  & .crisp-datatable-loading { display: flex; align-items: center; justify-content: center; gap: 10px; padding: var(--crisp-space-8) var(--crisp-space-3); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
146
153
  & .crisp-datatable-loading .crisp-spinner { border-top-color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
147
- /* #579: click-to-expand overlay for a truncated cell -- a read-only,
148
- full-value card, settled live as click (not Attio's usual hover-expand) and measured position: absolute, unlike this file's
149
- floating chrome (Menu/Select popovers) which portals and escapes the
150
- scroll container. Deliberately NOT the edit-error pattern above
151
- (grow the row, block-level, never clipped): a virtualized grid
152
- assumes a fixed row height for its scroll math (owned by the grid
153
- core, not this cell), so growing a row here would fight it. Anchored
154
- to the SAME column width the cell itself renders at (measured:
155
- Attio's own overlay came out only slightly wider than the triggering cell's clientWidth -- the overshoot is this padding),
156
- wrapping onto as many lines as the value needs; it CAN be clipped by
157
- `.crisp-table-scroll`'s own `overflow: auto` if it grows past the visible viewport, same as the edit-error comment above already notes for any position:absolute escape -- unmeasured against Attio for this exact case, flagged rather than guessed. */
154
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
155
+ hover-expand) and measured position: absolute, unlike this file's floating chrome (Menu/Select popovers) which portals and escapes the scroll container. Deliberately NOT the edit-error pattern above (grow the row, block-level, never clipped): a virtualized grid assumes a fixed row height for its scroll math (owned by the grid core, not this cell), so growing a row here would fight it. Anchored to the SAME column width the cell itself renders at (measured: Attio's own overlay came out only slightly wider than the
156
+ triggering cell's clientWidth -- the overshoot is this padding), wrapping onto as many lines as the value needs; it CAN be clipped by `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
157
+ visible viewport, same as the edit-error comment above already
158
+ notes for any position:absolute escape -- unmeasured against Attio
159
+ for this exact case, flagged rather than guessed. */
158
160
  & td[data-cell-expanded] { overflow: visible; z-index: 7; }
159
- & .crisp-datatable-cell-overlay { position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box; padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface); border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default; }
161
+ & .crisp-datatable-cell-overlay {
162
+ position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
163
+ padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
164
+ border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
165
+ font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
166
+ color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
167
+ }
160
168
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
161
169
  & table { table-layout: fixed; }
162
- /* read-only/computed column marker (#596) -- sits just inside the resize handle, same absolute-positioned-against-the-th approach the handle itself uses, since th has zero padding and the button/inner row fills the whole cell with no room left for a trailing sibling. */
170
+ /* read-only/computed column marker (#596) -- sits just inside the
171
+ resize handle, same absolute-positioned-against-the-th approach the
172
+ handle itself uses, since th has zero padding and the button/inner
173
+ row fills the whole cell with no room left for a trailing sibling. */
163
174
  & .crisp-datatable-readonly { position: absolute; top: 50%; inset-inline-end: 11px; transform: translateY(-50%); color: var(--crisp-fg-muted); pointer-events: none; z-index: 5; }
164
175
  /* column resize grip */
165
176
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
166
177
  & .crisp-datatable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: var(--crisp-bg-brand-solid); opacity: 0; transition: opacity .12s; }
167
178
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
168
- /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
169
- own grid (the issue's own comment): the dragged header keeps full opacity and the plain `pointer` cursor throughout (Attio draws no ghost and no "grabbing" cursor of its own), so this adds nothing to `[data-dragging]` itself -- the ONLY new paint is the drop-indicator line, a 3px bar in the same measured brand blue the resize handle's
170
- own hover state (above) already uses, positioned on whichever edge of
171
- the hovered header the pointer is closer to. */
179
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
180
+ opacity and the plain `pointer` cursor throughout (Attio draws no
181
+ ghost and no "grabbing" cursor of its own), so this adds nothing to
182
+ `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
183
+ line, a 3px bar in the same measured brand blue the resize handle's own hover state (above) already uses, positioned on whichever edge of the hovered header the pointer is closer to. */
172
184
  & th[data-dragging] { cursor: pointer; }
173
- & th[data-drop-before]::before, & th[data-drop-after]::after {
174
- content: ""; position: absolute; top: 0; width: 3px; height: 100%;
175
- background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none;
176
- }
185
+ & th[data-drop-before]::before, & th[data-drop-after]::after { content: ""; position: absolute; top: 0; width: 3px; height: 100%; background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none; }
177
186
  & th[data-drop-before]::before { inset-inline-start: -1.5px; }
178
187
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
179
188
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
180
189
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
181
- /* crisp/#601 supersession follow-up -- the same flex-row shape
182
- .crisp-datatable-entity gives its own avatar+name+actions, but
183
- structural only (no entity-specific colour/weight): lets
184
- onOpenComments/onOpenPreview compose alongside ANY column's own content (a custom render, or a non-entity built-in type), not just type: "entity"'s own inline rendering. */
190
+ /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
191
+ content (a custom render, or a non-entity built-in type), not just
192
+ type: "entity"'s own inline rendering. */
185
193
  & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
186
- /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
187
- plain hover vs rest) that Attio's underline is unconditional, not a hover reveal -- identical at rest and on hover: text-decoration-color is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
188
- colour. The previous :hover-gated, full-contrast rule here was itself
189
- unmeasured and wrong in two ways at once. */
194
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
195
+ hover reveal -- identical at rest and on hover: text-decoration-color
196
+ is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */
190
197
  & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
191
198
  & button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
192
- /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
193
- column, revealed on ROW hover (the issue's own title says "row
194
- hover", and a live check confirmed hovering well past the name -- not just the icons' own hit target -- still reveals them) or
195
- keyboard focus. Measured live: border-radius 6px, icon colour
196
- rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
197
- rgba(0,0,0,0.04) background on the icon's OWN hover -- both expressed as color-mix against fgPrimary so they stay correct in dark mode instead of a literal black that would invert. */
199
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary column, revealed on ROW hover (the issue's own title says "row
200
+ hover", and a live check confirmed hovering well past the name --
201
+ not just the icons' own hit target -- still reveals them) or keyboard focus. Measured live: border-radius 6px, icon colour rgba(0,0,0,0.55) at rest, darkening to the full text colour with a rgba(0,0,0,0.04) background on the icon's OWN hover -- both
202
+ expressed as color-mix against fgPrimary so they stay correct in
203
+ dark mode instead of a literal black that would invert. */
198
204
  & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
199
205
  & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
200
206
  & .crisp-datatable-entity-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
201
207
  & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
202
208
  & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
203
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
204
- & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
205
- & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
206
- & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
207
- & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
208
- & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
209
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
209
210
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
210
211
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
211
212
  & .crisp-datatable-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); }
@@ -218,58 +219,74 @@
218
219
  & .crisp-datatable-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)); }
219
220
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
220
221
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
221
- /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
222
- TasksTable's own overdue due-date (crisp#462), plain text colour, no fill/weight change. */
222
+ /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as TasksTable's own overdue due-date (crisp#462), plain text colour, no
223
+ fill/weight change. */
223
224
  & .crisp-datatable-danger { color: var(--crisp-fg-danger); }
224
- /* url/email cell types (#654) -- same measured values as RecordsTable's
225
- own .crisp-recordstable-link, which this mirrors: no underline at
226
- rest, underline on hover, own-colour text rather than a browser-blue
227
- link. */
225
+ /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
228
226
  & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
229
227
  & .crisp-datatable-link:hover { text-decoration: underline; }
230
- /* phone cell type/editor (#765) -- measured live: the dial-code prefix
231
- is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
232
- value already measured elsewhere in this file. */
228
+ /* phone cell type/editor (#765) -- measured live: the dial-code prefix is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text value already measured elsewhere in this file. */
233
229
  & .crisp-datatable-phone { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
234
230
  & .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
235
231
  & .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
236
232
  & .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
237
- /* country-picker option row (#842) -- confirmed live: Attio's own popover is plain text, no flag anywhere (not the option row, not the closed trigger), name opaque + dial code muted rgba(0,0,0,0.63) -- the SAME color-mix figure as -phone-dial above. The internal name/dial gap wasn't independently measured this pass (Attio's own two <div>s gave no exact number); reused space["1"], the same gap -phone already uses for its own flag/dial/digits row. */
233
+ /* country-picker option row (#842) -- confirmed live: Attio's own popover
234
+ is plain text, no flag anywhere (not the option row, not the closed
235
+ trigger), name opaque + dial code muted rgba(0,0,0,0.63) -- the SAME
236
+ color-mix figure as -phone-dial above. The internal name/dial gap
237
+ wasn't independently measured this pass (Attio's own two <div>s gave
238
+ no exact number); reused space["1"], the same gap -phone already uses
239
+ for its own flag/dial/digits row. */
238
240
  & .crisp-datatable-country-option { display: flex; align-items: center; gap: var(--crisp-space-1); }
239
241
  & .crisp-datatable-country-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
240
242
  /* multi-value phone (#816) -- confirmed live (a real custom "Multiple
241
243
  values" Phone Number attribute, and the built-in People "Phone
242
- numbers" field): the collapsed cell is a 4px-gap flex row of real tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled; reused as the closest already-tokened blue, brand600, rather than adding a third blue token for a delta this small), underlined, UNLIKE the single-value cell above (plain text, no link, no underline) -- multi-value is the one shape Attio makes into a real link. The cell hard-clips on overflow (no ellipsis -- confirmed live, a second value can end mid-digit with no "..."). */
244
+ numbers" field): the collapsed cell is a 4px-gap flex row of real
245
+ tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
246
+ reused as the closest already-tokened blue, brand600, rather than
247
+ adding a third blue token for a delta this small), underlined,
248
+ UNLIKE the single-value cell above (plain text, no link, no
249
+ underline) -- multi-value is the one shape Attio makes into a real
250
+ link. The cell hard-clips on overflow (no ellipsis -- confirmed
251
+ live, a second value can end mid-digit with no "..."). */
243
252
  & .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
244
253
  & .crisp-datatable-phone-link { color: var(--crisp-bg-brand-solid); text-decoration: underline; white-space: nowrap; }
245
- /* multi-value editor (#816) -- confirmed live: a stack of committed- value rows (32px = size.menuRow, 5px 8px padding, 8px gap between the grip/value/actions, 16px icons) above one ALWAYS-blank trailing DataPhoneEditor-shaped row -- that blank row is the only "add
246
- another" affordance, there's no separate button for it. The
247
- copy/delete pair reuses .crisp-datatable-entity-action's own hover-reveal chrome (not independently re-measured as its own hit target this pass, only the 16px icon glyphs were). */
254
+ /* multi-value editor (#816) -- confirmed live: a stack of committed-
255
+ value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
256
+ the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
257
+ DataPhoneEditor-shaped row -- that blank row is the only "add
258
+ another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
259
+ hover-reveal chrome (not independently re-measured as its own hit
260
+ target this pass, only the 16px icon glyphs were). */
248
261
  & .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
249
262
  & .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 5px var(--crisp-space-2); }
250
263
  & .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
251
264
  & .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
252
265
  & .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
253
- & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions, & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
266
+ & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
267
+ & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
254
268
  & .crisp-datatable-phone-multi-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
255
269
  & .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
256
270
  /* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
257
271
  & .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
258
- /* rating cell type/editor (#760) -- measured live: 16px stars on an 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a committed/filled star at brand600 (--crisp-bg-brand-solid, an exact match to the measured #266df0), and a distinct hover-preview colour -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
259
- (#1f5fd6); kept as its own literal rather than forced onto that token,
260
- since the two were measured stable and different. The read-only svg
261
- and the editable button's own inner svg both carry the crisp-datatable-rating-star class on the svg itself (not the button), so one set of fill rules covers both -- the button only adds its own reset + cursor via a separate -btn class. */
272
+ /* rating cell type/editor (#760) -- measured live: 16px stars on an
273
+ 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a
274
+ committed/filled star at brand600 (--crisp-bg-brand-solid, an exact
275
+ match to the measured #266df0), and a distinct hover-preview colour
276
+ -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed (#1f5fd6); kept as its own literal rather than forced onto that token, since the two were measured stable and different. The read-only svg and the editable button's own inner svg both carry the
277
+ crisp-datatable-rating-star class on the svg itself (not the
278
+ button), so one set of fill rules covers both -- the button only
279
+ adds its own reset + cursor via a separate -btn class. */
262
280
  & .crisp-datatable-rating { display: inline-flex; align-items: center; gap: 2px; }
263
281
  & .crisp-datatable-rating-star { display: block; }
264
282
  & .crisp-datatable-rating-star path { fill: rgba(0, 0, 0, 0.1); }
265
283
  & .crisp-datatable-rating-star[data-filled] path { fill: var(--crisp-bg-brand-solid); }
266
284
  & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
267
285
  & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
268
- /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
269
- hairline doubling as the drop indicator (it tints toward the brand
270
- colour as the active drop target). The affordance + geometry are
271
- measured; the drag MECHANISM is crisp's existing house idiom -- HTML5 draggable + dataTransfer, the same one column reorder already uses (RecordsTable's reorderColumn) -- not a proprietary
272
- pointer-events implementation. */
286
+ /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
287
+ a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
288
+ draggable + dataTransfer, the same one column reorder already uses
289
+ (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
273
290
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
274
291
  & .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
275
292
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -132,7 +132,22 @@ const config = defineRecipe({
132
132
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: ${c.fgPrimary}; }
133
133
  /* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
134
134
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: ${c.bgSurface}; }
135
- & th[data-sticky] { z-index: 3; }
135
+ /* \`thead th[data-sticky]\`, not a bare \`th[data-sticky]\` (crisp#848, same
136
+ bare-\`th\` family as #829/#833/#835): this z-index:3 was written for
137
+ the HEADER's own sticky corner cell (sticky column x sticky header),
138
+ meant to win over everything else. Unscoped, it also matched the
139
+ primary column's BODY cell -- a real \`<th scope="row" data-sticky>\`
140
+ since #779 -- giving every scrolled body row's own sticky cell a
141
+ HIGHER z-index than the sticky FOOTER's (2), so it painted over the
142
+ footer instead of the footer showing through. Confirmed live on a
143
+ real bounded-height, 25-row table (GRC's Risk register): scrolled
144
+ partway, \`elementFromPoint\` at the footer's own on-screen cell
145
+ resolved to a body row's \`<th>\`, not the footer's \`<td>\`, and that
146
+ \`<th>\`'s computed z-index read 3. Scoped to \`thead\`, a body row's
147
+ own sticky \`<th>\` now falls back to the base rule above (z-index 1,
148
+ correct -- the same as a plain sticky \`<td>\`), and the footer's own
149
+ explicit z-index below is unaffected either way. */
150
+ & thead th[data-sticky] { z-index: 3; }
136
151
  /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
137
152
  \`tfoot td { z-index: 2 }\`), which \`td[data-sticky]\`'s own higher-specificity
138
153
  \`z-index: 1\` would otherwise undercut -- keep it level with the row it's in. */