@crisp-ui-kit/crisp 0.54.6 → 0.54.8
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.
- package/dist/authshell.css +5 -1
- package/dist/authshell.layered.css +5 -1
- package/dist/badge.css +5 -5
- package/dist/badge.layered.css +5 -5
- package/dist/board.css +13 -9
- package/dist/board.layered.css +13 -9
- package/dist/button.css +3 -1
- package/dist/button.layered.css +3 -1
- package/dist/cjs/components/badge/badge.recipe.js +28 -5
- package/dist/cjs/components/button/button.js +33 -2
- package/dist/cjs/components/button/button.recipe.js +31 -5
- package/dist/cjs/components/datatable/datatable.js +66 -7
- package/dist/cjs/components/datatable/datatable.recipe.js +57 -2
- package/dist/cjs/components/dialog/dialog.js +8 -5
- package/dist/cjs/components/dialog/dialog.recipe.js +15 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +19 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +239 -50
- package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
- package/dist/cjs/components/tabs/tabs.recipe.js +11 -2
- package/dist/cjs/components/tooltip/tooltip.js +23 -9
- package/dist/cjs/components/tooltip/tooltip.recipe.js +10 -9
- package/dist/cjs/product/authshell/authshell.js +14 -1
- package/dist/cjs/product/authshell/authshell.recipe.js +13 -1
- package/dist/cjs/product/board/board.js +122 -27
- package/dist/cjs/product/board/board.recipe.js +14 -2
- package/dist/cjs/product/comments/comments.js +23 -10
- package/dist/cjs/product/comments/comments.recipe.js +17 -3
- package/dist/cjs/product/formdialog/formdialog.js +10 -4
- package/dist/cjs/product/navitem/navitem.js +7 -2
- package/dist/cjs/product/navitem/navitem.recipe.js +16 -0
- package/dist/cjs/product/objectsettings/index.js +1 -2
- package/dist/cjs/product/objectsettings/objectsettings.js +3 -13
- package/dist/cjs/product/richtext/richtext.js +76 -1
- package/dist/cjs/product/sidebar/sidebar.js +68 -15
- package/dist/cjs/product/sidebar/sidebar.recipe.js +6 -1
- package/dist/cjs/tokens/palette.js +32 -1
- package/dist/cjs/tokens/semantic.js +11 -0
- package/dist/comments.css +36 -10
- package/dist/comments.layered.css +36 -10
- package/dist/datatable.css +47 -9
- package/dist/datatable.layered.css +47 -9
- package/dist/dialog.css +13 -1
- package/dist/dialog.layered.css +13 -1
- package/dist/esm/components/badge/badge.recipe.js +28 -5
- package/dist/esm/components/button/button.js +34 -3
- package/dist/esm/components/button/button.recipe.js +31 -5
- package/dist/esm/components/datatable/datatable.js +67 -8
- package/dist/esm/components/datatable/datatable.recipe.js +57 -2
- package/dist/esm/components/dialog/dialog.js +8 -5
- package/dist/esm/components/dialog/dialog.recipe.js +15 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +19 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +240 -51
- package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
- package/dist/esm/components/tabs/tabs.recipe.js +11 -2
- package/dist/esm/components/tooltip/tooltip.js +23 -9
- package/dist/esm/components/tooltip/tooltip.recipe.js +10 -9
- package/dist/esm/product/authshell/authshell.js +15 -2
- package/dist/esm/product/authshell/authshell.recipe.js +13 -1
- package/dist/esm/product/authshell/index.js +1 -1
- package/dist/esm/product/board/board.js +122 -27
- package/dist/esm/product/board/board.recipe.js +14 -2
- package/dist/esm/product/comments/comments.js +23 -10
- package/dist/esm/product/comments/comments.recipe.js +17 -3
- package/dist/esm/product/formdialog/formdialog.js +10 -4
- package/dist/esm/product/navitem/index.js +1 -1
- package/dist/esm/product/navitem/navitem.js +7 -2
- package/dist/esm/product/navitem/navitem.recipe.js +16 -0
- package/dist/esm/product/objectsettings/index.js +1 -1
- package/dist/esm/product/objectsettings/objectsettings.js +2 -12
- package/dist/esm/product/richtext/richtext.js +76 -1
- package/dist/esm/product/sidebar/sidebar.js +69 -16
- package/dist/esm/product/sidebar/sidebar.recipe.js +6 -1
- package/dist/esm/tokens/palette.js +32 -1
- package/dist/esm/tokens/semantic.js +11 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +16 -2
- package/dist/navitem.css +7 -0
- package/dist/navitem.layered.css +7 -0
- package/dist/recordstable.css +2 -1
- package/dist/recordstable.layered.css +2 -1
- package/dist/sidebar.css +6 -1
- package/dist/sidebar.layered.css +6 -1
- package/dist/styles.css +165 -42
- package/dist/styles.layered.css +165 -42
- package/dist/tabletoolbar.css +17 -1
- package/dist/tabletoolbar.layered.css +17 -1
- package/dist/tabs.css +1 -1
- package/dist/tabs.layered.css +1 -1
- package/dist/tokens.css +9 -1
- package/dist/tokens.json +16 -2
- package/dist/tooltip.css +1 -1
- package/dist/tooltip.layered.css +1 -1
- package/dist/types/components/badge/badge.recipe.d.ts +5 -1
- package/dist/types/components/button/button.recipe.d.ts +18 -5
- package/dist/types/components/datatable/datatable.d.ts +9 -0
- package/dist/types/components/dialog/dialog.d.ts +25 -2
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +47 -5
- package/dist/types/components/tooltip/tooltip.d.ts +12 -4
- package/dist/types/components/tooltip/tooltip.recipe.d.ts +1 -1
- package/dist/types/product/authshell/authshell.d.ts +26 -2
- package/dist/types/product/authshell/index.d.ts +1 -1
- package/dist/types/product/board/board.d.ts +5 -4
- package/dist/types/product/comments/comments.d.ts +10 -2
- package/dist/types/product/formdialog/formdialog.d.ts +2 -1
- package/dist/types/product/navitem/index.d.ts +1 -1
- package/dist/types/product/navitem/navitem.d.ts +23 -0
- package/dist/types/product/objectsettings/index.d.ts +1 -1
- package/dist/types/product/objectsettings/objectsettings.d.ts +0 -10
- package/dist/types/product/sidebar/sidebar.d.ts +33 -5
- package/dist/types/tokens/contract.d.ts +4 -0
- package/dist/types/tokens/palette.d.ts +13 -1
- package/dist/types/tokens/semantic.d.ts +11 -0
- package/package.json +1 -1
package/dist/datatable.css
CHANGED
|
@@ -9,8 +9,15 @@
|
|
|
9
9
|
& .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
10
10
|
/* announced, never drawn */
|
|
11
11
|
& .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
12
|
-
/* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix.
|
|
13
|
-
|
|
12
|
+
/* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
|
|
13
|
+
own fix live): DataTable's root nests `Table`'s own wrapper, whose
|
|
14
|
+
`table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
|
|
15
|
+
SAME specificity (.crisp-table th vs .crisp-datatable th, both one
|
|
16
|
+
class + one element) -- a bare `& th` here only won by source
|
|
17
|
+
order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
|
|
18
|
+
datatable < table), silently reinstating 12px on every header cell
|
|
19
|
+
in the whole product and making every interactive column's button double it to 24px. Scoping through `.crisp-table` adds a second class to the selector (0,2,1 beats 0,1,1) so this can never lose a specificity tie to source order again. */
|
|
20
|
+
& .crisp-table th { padding: 0; }
|
|
14
21
|
/* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
|
|
15
22
|
Menu wraps its trigger in a flex div that sizes to its content, so a
|
|
16
23
|
long header grew past the column instead of filling it -- with nothing
|
|
@@ -19,6 +26,15 @@
|
|
|
19
26
|
& th .crisp-menu { width: 100%; height: 100%; }
|
|
20
27
|
& .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
|
|
21
28
|
& .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
29
|
+
/* crisp/#605 follow-up: a header with neither a Menu nor a sort button (the primary/entity column's now-inert path, or any column with no
|
|
30
|
+
columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
|
|
31
|
+
DIRECT child of th instead of nested inside a button -- and loses
|
|
32
|
+
the button's own 12px inset, drifting left of the td's 12px base
|
|
33
|
+
inset. Measured live on Attio's own Companies grid: the header label and the cell's own text land at the exact same x (335px from the
|
|
34
|
+
viewport, both 40px from the column's left edge) via two different padding+icon+gap combinations -- Attio does not leave the inert primary header flush left either. The `>` combinator targets only the button-less case: Menu's own wrapper (`.crisp-menu`) and the
|
|
35
|
+
plain sort `button` both put >=1 more level between `th` and this
|
|
36
|
+
span, so their own 12px (already on the button) isn't doubled. */
|
|
37
|
+
& th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
|
|
22
38
|
/* The summary cell has NO padding of its own -- the calculation trigger owns the whole 246x37 area so a click anywhere in the cell opens the menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
|
|
23
39
|
& tfoot td { padding: 0; }
|
|
24
40
|
/* Group heading row -- a view is grouped under a subtle label with the
|
|
@@ -89,8 +105,23 @@
|
|
|
89
105
|
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
90
106
|
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
91
107
|
& .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); }
|
|
108
|
+
/* #579: click-to-expand overlay for a truncated cell -- a read-only,
|
|
109
|
+
full-value card, settled live as click (not Attio's usual hover-expand) and measured position: absolute, unlike this file's
|
|
110
|
+
floating chrome (Menu/Select popovers) which portals and escapes the
|
|
111
|
+
scroll container. Deliberately NOT the edit-error pattern above
|
|
112
|
+
(grow the row, block-level, never clipped): a virtualized grid
|
|
113
|
+
assumes a fixed row height for its scroll math (owned by the grid
|
|
114
|
+
core, not this cell), so growing a row here would fight it. Anchored
|
|
115
|
+
to the SAME column width the cell itself renders at (measured:
|
|
116
|
+
Attio's own overlay came out only slightly wider than the triggering cell's clientWidth -- the overshoot is this padding),
|
|
117
|
+
wrapping onto as many lines as the value needs; it CAN be clipped by
|
|
118
|
+
`.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. */
|
|
119
|
+
& td[data-cell-expanded] { overflow: visible; z-index: 7; }
|
|
120
|
+
& .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; }
|
|
92
121
|
/* fixed layout so column widths (and resize) are exact — like RecordsTable */
|
|
93
122
|
& table { table-layout: fixed; }
|
|
123
|
+
/* 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. */
|
|
124
|
+
& .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; }
|
|
94
125
|
/* column resize grip */
|
|
95
126
|
& .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
|
|
96
127
|
& .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; }
|
|
@@ -101,7 +132,12 @@
|
|
|
101
132
|
& .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
|
|
102
133
|
& 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%; }
|
|
103
134
|
& .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); }
|
|
104
|
-
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
135
|
+
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
136
|
+
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
137
|
+
& .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
|
|
138
|
+
& .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
|
|
139
|
+
& .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
|
|
140
|
+
& .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
105
141
|
& .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
|
|
106
142
|
& .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
107
143
|
& .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); }
|
|
@@ -114,14 +150,16 @@
|
|
|
114
150
|
& .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)); }
|
|
115
151
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
116
152
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
117
|
-
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
153
|
+
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
154
|
+
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
155
|
+
rest, underline on hover, own-colour text rather than a browser-blue
|
|
156
|
+
link. */
|
|
118
157
|
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
119
158
|
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
120
|
-
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
121
|
-
hairline doubling as the drop indicator (it tints toward the brand
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
pointer-events implementation. */
|
|
159
|
+
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
160
|
+
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
|
|
161
|
+
draggable + dataTransfer, the same one column reorder already uses
|
|
162
|
+
(RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
|
|
125
163
|
& .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
|
|
126
164
|
& .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; }
|
|
127
165
|
& .crisp-datatable-grip:active { cursor: grabbing; }
|
|
@@ -10,8 +10,15 @@
|
|
|
10
10
|
& .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
11
11
|
/* announced, never drawn */
|
|
12
12
|
& .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
13
|
-
/* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix.
|
|
14
|
-
|
|
13
|
+
/* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
|
|
14
|
+
own fix live): DataTable's root nests `Table`'s own wrapper, whose
|
|
15
|
+
`table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
|
|
16
|
+
SAME specificity (.crisp-table th vs .crisp-datatable th, both one
|
|
17
|
+
class + one element) -- a bare `& th` here only won by source
|
|
18
|
+
order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
|
|
19
|
+
datatable < table), silently reinstating 12px on every header cell
|
|
20
|
+
in the whole product and making every interactive column's button double it to 24px. Scoping through `.crisp-table` adds a second class to the selector (0,2,1 beats 0,1,1) so this can never lose a specificity tie to source order again. */
|
|
21
|
+
& .crisp-table th { padding: 0; }
|
|
15
22
|
/* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
|
|
16
23
|
Menu wraps its trigger in a flex div that sizes to its content, so a
|
|
17
24
|
long header grew past the column instead of filling it -- with nothing
|
|
@@ -20,6 +27,15 @@
|
|
|
20
27
|
& th .crisp-menu { width: 100%; height: 100%; }
|
|
21
28
|
& .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
|
|
22
29
|
& .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
30
|
+
/* crisp/#605 follow-up: a header with neither a Menu nor a sort button (the primary/entity column's now-inert path, or any column with no
|
|
31
|
+
columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
|
|
32
|
+
DIRECT child of th instead of nested inside a button -- and loses
|
|
33
|
+
the button's own 12px inset, drifting left of the td's 12px base
|
|
34
|
+
inset. Measured live on Attio's own Companies grid: the header label and the cell's own text land at the exact same x (335px from the
|
|
35
|
+
viewport, both 40px from the column's left edge) via two different padding+icon+gap combinations -- Attio does not leave the inert primary header flush left either. The `>` combinator targets only the button-less case: Menu's own wrapper (`.crisp-menu`) and the
|
|
36
|
+
plain sort `button` both put >=1 more level between `th` and this
|
|
37
|
+
span, so their own 12px (already on the button) isn't doubled. */
|
|
38
|
+
& th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
|
|
23
39
|
/* The summary cell has NO padding of its own -- the calculation trigger owns the whole 246x37 area so a click anywhere in the cell opens the menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
|
|
24
40
|
& tfoot td { padding: 0; }
|
|
25
41
|
/* Group heading row -- a view is grouped under a subtle label with the
|
|
@@ -90,8 +106,23 @@
|
|
|
90
106
|
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
91
107
|
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
92
108
|
& .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); }
|
|
109
|
+
/* #579: click-to-expand overlay for a truncated cell -- a read-only,
|
|
110
|
+
full-value card, settled live as click (not Attio's usual hover-expand) and measured position: absolute, unlike this file's
|
|
111
|
+
floating chrome (Menu/Select popovers) which portals and escapes the
|
|
112
|
+
scroll container. Deliberately NOT the edit-error pattern above
|
|
113
|
+
(grow the row, block-level, never clipped): a virtualized grid
|
|
114
|
+
assumes a fixed row height for its scroll math (owned by the grid
|
|
115
|
+
core, not this cell), so growing a row here would fight it. Anchored
|
|
116
|
+
to the SAME column width the cell itself renders at (measured:
|
|
117
|
+
Attio's own overlay came out only slightly wider than the triggering cell's clientWidth -- the overshoot is this padding),
|
|
118
|
+
wrapping onto as many lines as the value needs; it CAN be clipped by
|
|
119
|
+
`.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. */
|
|
120
|
+
& td[data-cell-expanded] { overflow: visible; z-index: 7; }
|
|
121
|
+
& .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; }
|
|
93
122
|
/* fixed layout so column widths (and resize) are exact — like RecordsTable */
|
|
94
123
|
& table { table-layout: fixed; }
|
|
124
|
+
/* 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. */
|
|
125
|
+
& .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; }
|
|
95
126
|
/* column resize grip */
|
|
96
127
|
& .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
|
|
97
128
|
& .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; }
|
|
@@ -102,7 +133,12 @@
|
|
|
102
133
|
& .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
|
|
103
134
|
& 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%; }
|
|
104
135
|
& .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); }
|
|
105
|
-
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
136
|
+
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
137
|
+
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
138
|
+
& .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
|
|
139
|
+
& .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
|
|
140
|
+
& .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
|
|
141
|
+
& .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
106
142
|
& .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
|
|
107
143
|
& .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
108
144
|
& .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); }
|
|
@@ -115,14 +151,16 @@
|
|
|
115
151
|
& .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)); }
|
|
116
152
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
117
153
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
118
|
-
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
154
|
+
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
155
|
+
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
156
|
+
rest, underline on hover, own-colour text rather than a browser-blue
|
|
157
|
+
link. */
|
|
119
158
|
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
120
159
|
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
121
|
-
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
122
|
-
hairline doubling as the drop indicator (it tints toward the brand
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
pointer-events implementation. */
|
|
160
|
+
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
161
|
+
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
|
|
162
|
+
draggable + dataTransfer, the same one column reorder already uses
|
|
163
|
+
(RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
|
|
126
164
|
& .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
|
|
127
165
|
& .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; }
|
|
128
166
|
& .crisp-datatable-grip:active { cursor: grabbing; }
|
package/dist/dialog.css
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
.crisp-dialog {
|
|
2
2
|
display: contents; font-family: var(--crisp-font-sans);
|
|
3
|
-
& .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background:
|
|
3
|
+
& .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-scrim); }
|
|
4
4
|
& .crisp-dialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); }
|
|
5
|
+
& .crisp-dialog-positioner[data-variant="alert"] { align-items: center; padding: var(--crisp-space-4); }
|
|
5
6
|
& .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-2xl); background: color-mix(in srgb, var(--crisp-bg-overlay-frost) 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 28px -6px var(--crisp-shadow-overlay1), 0 18px 88px -4px var(--crisp-shadow-overlay2), 0 0 1px var(--crisp-shadow-overlay3); opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
|
|
7
|
+
& .crisp-dialog-content[data-variant="alert"] { padding: 0; background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; }
|
|
6
8
|
& .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
|
|
9
|
+
& .crisp-dialog-content[data-variant="alert"] .crisp-dialog-card { border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); 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); }
|
|
7
10
|
& .crisp-dialog-header { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
8
11
|
& .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
9
12
|
& .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); margin: 0; }
|
|
10
13
|
& .crisp-dialog-close { 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: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
11
14
|
& .crisp-dialog-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
|
|
15
|
+
/* The discard-confirm's own stacked title + sentence (crisp#677) — no
|
|
16
|
+
border, no close button, unlike the panel header above: measured
|
|
17
|
+
live, both themes, 18px/24px 600-weight title (no scale token
|
|
18
|
+
reaches 18px — the scale tops out at text.xl's 16px, so this is raw px per Golden Rule 3's "genuinely intrinsic size" exception, same as
|
|
19
|
+
this file's own submit-key glyphs) then a 14px/20px 500-weight sentence 4px below it. */
|
|
20
|
+
& .crisp-dialog-alert-header { display: flex; flex-direction: column; gap: var(--crisp-space-1); padding: var(--crisp-space-4) var(--crisp-space-4) 0; }
|
|
21
|
+
& .crisp-dialog-alert-title { margin: 0; font-size: 18px; line-height: 24px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
22
|
+
& .crisp-dialog-alert-description { margin: 0; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-alert-description); }
|
|
12
23
|
& .crisp-dialog-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-4); padding: var(--crisp-space-5) var(--crisp-space-4); }
|
|
13
24
|
& .crisp-dialog-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-3); flex-shrink: 0; min-height: 56px; padding: 0 var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
25
|
+
& .crisp-dialog-footer[data-variant="alert"] { min-height: 45px; padding: var(--crisp-space-2); }
|
|
14
26
|
& .crisp-dialog-field { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
|
|
15
27
|
& .crisp-dialog-field-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
16
28
|
& .crisp-dialog-more { display: inline-flex; align-items: center; gap: var(--crisp-space-2); margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
|
package/dist/dialog.layered.css
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
@layer crisp-components {
|
|
2
2
|
.crisp-dialog {
|
|
3
3
|
display: contents; font-family: var(--crisp-font-sans);
|
|
4
|
-
& .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background:
|
|
4
|
+
& .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-scrim); }
|
|
5
5
|
& .crisp-dialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); }
|
|
6
|
+
& .crisp-dialog-positioner[data-variant="alert"] { align-items: center; padding: var(--crisp-space-4); }
|
|
6
7
|
& .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-2xl); background: color-mix(in srgb, var(--crisp-bg-overlay-frost) 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 28px -6px var(--crisp-shadow-overlay1), 0 18px 88px -4px var(--crisp-shadow-overlay2), 0 0 1px var(--crisp-shadow-overlay3); opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
|
|
8
|
+
& .crisp-dialog-content[data-variant="alert"] { padding: 0; background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; }
|
|
7
9
|
& .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
|
|
10
|
+
& .crisp-dialog-content[data-variant="alert"] .crisp-dialog-card { border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); 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); }
|
|
8
11
|
& .crisp-dialog-header { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
9
12
|
& .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
10
13
|
& .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); margin: 0; }
|
|
11
14
|
& .crisp-dialog-close { 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: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
12
15
|
& .crisp-dialog-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
|
|
16
|
+
/* The discard-confirm's own stacked title + sentence (crisp#677) — no
|
|
17
|
+
border, no close button, unlike the panel header above: measured
|
|
18
|
+
live, both themes, 18px/24px 600-weight title (no scale token
|
|
19
|
+
reaches 18px — the scale tops out at text.xl's 16px, so this is raw px per Golden Rule 3's "genuinely intrinsic size" exception, same as
|
|
20
|
+
this file's own submit-key glyphs) then a 14px/20px 500-weight sentence 4px below it. */
|
|
21
|
+
& .crisp-dialog-alert-header { display: flex; flex-direction: column; gap: var(--crisp-space-1); padding: var(--crisp-space-4) var(--crisp-space-4) 0; }
|
|
22
|
+
& .crisp-dialog-alert-title { margin: 0; font-size: 18px; line-height: 24px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
23
|
+
& .crisp-dialog-alert-description { margin: 0; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-alert-description); }
|
|
13
24
|
& .crisp-dialog-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-4); padding: var(--crisp-space-5) var(--crisp-space-4); }
|
|
14
25
|
& .crisp-dialog-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-3); flex-shrink: 0; min-height: 56px; padding: 0 var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
26
|
+
& .crisp-dialog-footer[data-variant="alert"] { min-height: 45px; padding: var(--crisp-space-2); }
|
|
15
27
|
& .crisp-dialog-field { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
|
|
16
28
|
& .crisp-dialog-field-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
17
29
|
& .crisp-dialog-more { display: inline-flex; align-items: center; gap: var(--crisp-space-2); margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
|
|
@@ -11,7 +11,11 @@ const spec = defineSpec({
|
|
|
11
11
|
gap: c.space["1"],
|
|
12
12
|
fontFamily: c.font.sans,
|
|
13
13
|
fontWeight: c.weight.medium,
|
|
14
|
-
|
|
14
|
+
// crisp#696: every Attio pill measured this round -- the count pill and
|
|
15
|
+
// the tag pill both -- reads `normal` tracking, not the `-0.1px` this
|
|
16
|
+
// used to carry as a base default. `large` (unmeasured this round) keeps
|
|
17
|
+
// the old `-0.1px` as its own variant override, below.
|
|
18
|
+
letterSpacing: "normal",
|
|
15
19
|
whiteSpace: "nowrap",
|
|
16
20
|
},
|
|
17
21
|
parts: [
|
|
@@ -38,7 +42,12 @@ const spec = defineSpec({
|
|
|
38
42
|
neutral: {
|
|
39
43
|
background: c.bgRaised,
|
|
40
44
|
color: c.fgNeutralSoft,
|
|
41
|
-
|
|
45
|
+
// crisp#696: Attio's neutral pill ring measures rgb(238,239,241) --
|
|
46
|
+
// borderSubtle/line100 (#eeeff1), not borderDefault/line200
|
|
47
|
+
// (#e4e4e7). Background is unchanged: Attio measured
|
|
48
|
+
// rgb(246,247,247) against crisp's bgRaised #f4f4f5, a 2-unit
|
|
49
|
+
// difference the `savedviews` recipe already treats as noise.
|
|
50
|
+
boxShadow: `inset 0 0 0 1px ${c.borderSubtle}`,
|
|
42
51
|
},
|
|
43
52
|
brand: {
|
|
44
53
|
background: soft(c.bgBrandSolid, 14),
|
|
@@ -72,22 +81,36 @@ const spec = defineSpec({
|
|
|
72
81
|
},
|
|
73
82
|
},
|
|
74
83
|
size: {
|
|
84
|
+
// crisp#696: Attio's count pill (sidebar "Tasks 1", the "+1" overflow)
|
|
85
|
+
// measures 16px h / 4px padding / 12px text / 16px line-height -- was
|
|
86
|
+
// 6px padding, 11px text, and no line-height at all.
|
|
75
87
|
small: {
|
|
76
88
|
height: "16px",
|
|
77
|
-
paddingInline: c.space["1
|
|
78
|
-
fontSize: c.text.
|
|
89
|
+
paddingInline: c.space["1"],
|
|
90
|
+
fontSize: c.text.sm,
|
|
91
|
+
lineHeight: "16px",
|
|
79
92
|
borderRadius: c.radius.sm,
|
|
80
93
|
},
|
|
94
|
+
// crisp#696: Attio's tag pill (Standard/System/Enriched) measures
|
|
95
|
+
// 20px h / 4px padding / 12px text / 16px line-height / 3px gap -- was
|
|
96
|
+
// 8px padding, no line-height, and the base's (now normal-tracking)
|
|
97
|
+
// 4px gap.
|
|
81
98
|
medium: {
|
|
82
99
|
height: "20px",
|
|
83
|
-
paddingInline: c.space["
|
|
100
|
+
paddingInline: c.space["1"],
|
|
84
101
|
fontSize: c.text.sm,
|
|
102
|
+
lineHeight: "16px",
|
|
103
|
+
gap: "3px",
|
|
85
104
|
borderRadius: c.radius.sm,
|
|
86
105
|
},
|
|
106
|
+
// crisp#696: not re-measured this round -- kept exactly as it rendered
|
|
107
|
+
// before the base tracking changed to `normal`, by pinning the old
|
|
108
|
+
// `-0.1px` here instead. Gap is untouched (base's 4px).
|
|
87
109
|
large: {
|
|
88
110
|
height: c.size.chip,
|
|
89
111
|
paddingInline: "9px",
|
|
90
112
|
fontSize: c.text.lg,
|
|
113
|
+
letterSpacing: "-0.1px",
|
|
91
114
|
borderRadius: c.radius.chip,
|
|
92
115
|
},
|
|
93
116
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Primitive } from "../../core/index.js";
|
|
4
4
|
import { buttonRecipe } from "./button.recipe.js";
|
|
@@ -6,10 +6,41 @@ import { buttonRecipe } from "./button.recipe.js";
|
|
|
6
6
|
function Spinner() {
|
|
7
7
|
return (_jsxs("svg", { width: "1em", height: "1em", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", style: { flexShrink: 0 }, children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "2" }), _jsx("path", { d: "M8 1.5a6.5 6.5 0 0 1 6.5 6.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: _jsx("animateTransform", { attributeName: "transform", type: "rotate", from: "0 8 8", to: "360 8 8", dur: "0.7s", repeatCount: "indefinite" }) })] }));
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* crisp#682: an "icon" part is a valid element whose type is the host `svg`
|
|
11
|
+
* tag or isn't a string at all (a lucide icon, `Spinner`, any component) --
|
|
12
|
+
* everything else (strings, numbers, host elements like `span`) is a "text"
|
|
13
|
+
* part.
|
|
14
|
+
*/
|
|
15
|
+
function isIconPart(node) {
|
|
16
|
+
return (React.isValidElement(node) &&
|
|
17
|
+
(node.type === "svg" || typeof node.type !== "string"));
|
|
18
|
+
}
|
|
9
19
|
export const Button = React.forwardRef(({ intent, size, shape, touchTarget, className, asChild, loading, disabled, children, type, ...props }, ref) => {
|
|
10
20
|
const isDisabled = disabled || loading;
|
|
11
21
|
// Slot (asChild) requires a single child — never inject the spinner in that mode.
|
|
12
|
-
|
|
22
|
+
// An array (not a Fragment) so React.Children.toArray below sees the
|
|
23
|
+
// Spinner as its own top-level part instead of one opaque Fragment node.
|
|
24
|
+
const content = loading && !asChild
|
|
25
|
+
? [_jsx(Spinner, {}, "crisp-button-spinner"), children]
|
|
26
|
+
: children;
|
|
27
|
+
// crisp#682: md's icon-side padding (6px vs 8px) depends on whether a
|
|
28
|
+
// side ends in an icon or text, which CSS alone can't tell apart
|
|
29
|
+
// (`<svg/>Label` vs `Label<svg/>` both skip text nodes under
|
|
30
|
+
// `:first-child`). Skipped for `asChild`, which renders someone else's
|
|
31
|
+
// single element rather than a list of parts.
|
|
32
|
+
let iconStart;
|
|
33
|
+
let iconEnd;
|
|
34
|
+
if (!asChild) {
|
|
35
|
+
const parts = React.Children.toArray(content);
|
|
36
|
+
const hasText = parts.some((part) => !isIconPart(part));
|
|
37
|
+
if (hasText && parts.length > 0) {
|
|
38
|
+
if (isIconPart(parts[0]))
|
|
39
|
+
iconStart = true;
|
|
40
|
+
if (isIconPart(parts[parts.length - 1]))
|
|
41
|
+
iconEnd = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
13
44
|
return (_jsx(Primitive.button, { ref: ref, asChild: asChild, className: buttonRecipe({
|
|
14
45
|
intent,
|
|
15
46
|
size,
|
|
@@ -21,6 +52,6 @@ export const Button = React.forwardRef(({ intent, size, shape, touchTarget, clas
|
|
|
21
52
|
// a design system should submit a form by accident, so default to
|
|
22
53
|
// "button" and make submitting explicit. `asChild` renders someone
|
|
23
54
|
// else's element, which must not be given a button-only attribute.
|
|
24
|
-
type: asChild ? type : (type ?? "button"), disabled: isDisabled, "aria-busy": loading || undefined, "data-loading": loading ? "" : undefined, ...props, children: content }));
|
|
55
|
+
type: asChild ? type : (type ?? "button"), disabled: isDisabled, "aria-busy": loading || undefined, "data-loading": loading ? "" : undefined, "data-icon-start": iconStart ? "" : undefined, "data-icon-end": iconEnd ? "" : undefined, ...props, children: content }));
|
|
25
56
|
});
|
|
26
57
|
Button.displayName = "Button";
|
|
@@ -9,7 +9,11 @@ const spec = defineSpec({
|
|
|
9
9
|
gap: c.space["1.5"],
|
|
10
10
|
fontFamily: c.font.sans,
|
|
11
11
|
fontWeight: c.weight.medium,
|
|
12
|
-
|
|
12
|
+
// crisp#682: every 14px button label on app.attio.com measures -0.07px
|
|
13
|
+
// (17 of 17 on the Companies view, plus 222 sidebar/nav labels).
|
|
14
|
+
// -0.14px is Attio's table-cell tracking, not its button tracking --
|
|
15
|
+
// that's where this value came from.
|
|
16
|
+
letterSpacing: "-0.07px",
|
|
13
17
|
lineHeight: "20px",
|
|
14
18
|
whiteSpace: "nowrap",
|
|
15
19
|
border: "none",
|
|
@@ -77,6 +81,9 @@ const spec = defineSpec({
|
|
|
77
81
|
},
|
|
78
82
|
},
|
|
79
83
|
size: {
|
|
84
|
+
// sm/lg unchanged by crisp#682 -- neither was measured against Attio
|
|
85
|
+
// (Attio's small button is 20px, not sm's 24px; see crisp#682), so
|
|
86
|
+
// only md's icon-side padding is touched here.
|
|
80
87
|
sm: {
|
|
81
88
|
height: "24px",
|
|
82
89
|
paddingInline: c.space["1.5"],
|
|
@@ -84,10 +91,29 @@ const spec = defineSpec({
|
|
|
84
91
|
borderRadius: c.radius.md,
|
|
85
92
|
},
|
|
86
93
|
md: {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
decls: {
|
|
95
|
+
height: "28px",
|
|
96
|
+
paddingInline: c.space["2"],
|
|
97
|
+
fontSize: c.text.lg,
|
|
98
|
+
borderRadius: c.radius.md,
|
|
99
|
+
},
|
|
100
|
+
// crisp#682: a side that ends in an icon gets 6px padding instead of
|
|
101
|
+
// 8px. CSS can't tell `<svg/>Label` apart from `Label<svg/>`
|
|
102
|
+
// (`:first-child` skips text nodes), so `Button` marks the icon
|
|
103
|
+
// sides itself via `data-icon-start`/`data-icon-end` (button.tsx).
|
|
104
|
+
// The attribute selector outranks the plain class above it, so this
|
|
105
|
+
// always wins over the shorthand `paddingInline` regardless of
|
|
106
|
+
// declaration order.
|
|
107
|
+
parts: [
|
|
108
|
+
{
|
|
109
|
+
selector: "&[data-icon-start]",
|
|
110
|
+
decls: { paddingInlineStart: c.space["1.5"] },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
selector: "&[data-icon-end]",
|
|
114
|
+
decls: { paddingInlineEnd: c.space["1.5"] },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
91
117
|
},
|
|
92
118
|
lg: {
|
|
93
119
|
height: "40px",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, GripVertical, Plus, Search, SlidersHorizontal, } from "lucide-react";
|
|
2
|
+
import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, GripVertical, Lock, Plus, Search, SlidersHorizontal, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, useCrispMessage, useCrispMessages, } from "../../core/index.js";
|
|
5
5
|
import { Avatar } from "../avatar/index.js";
|
|
@@ -492,6 +492,34 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
492
492
|
const showColumn = React.useCallback((key) => applyColumns(allColumns.map((c) => (c.key === key ? { ...c, hidden: false } : c))), [allColumns, applyColumns]);
|
|
493
493
|
const [selCell, setSelCell] = React.useState(null);
|
|
494
494
|
const gridRef = React.useRef(null);
|
|
495
|
+
// #579: the click-to-expand overlay for a truncated cell. Only one is ever
|
|
496
|
+
// open (a second click-to-expand elsewhere closes the first, matching "one
|
|
497
|
+
// live session"-style singular UI state already used for `selCell`).
|
|
498
|
+
const [expandedCell, setExpandedCell] = React.useState(null);
|
|
499
|
+
// Closes on any click outside the overlay itself (a click ON it is
|
|
500
|
+
// stopped from propagating, see the overlay's own onClick) and on
|
|
501
|
+
// Escape, kept for accessibility even though Attio's own Escape handling
|
|
502
|
+
// wasn't reliable when this was measured live (crisp#579).
|
|
503
|
+
React.useEffect(() => {
|
|
504
|
+
if (!expandedCell)
|
|
505
|
+
return;
|
|
506
|
+
const close = (e) => {
|
|
507
|
+
const target = e.target;
|
|
508
|
+
if (target?.closest?.(".crisp-datatable-cell-overlay"))
|
|
509
|
+
return;
|
|
510
|
+
setExpandedCell(null);
|
|
511
|
+
};
|
|
512
|
+
const onKey = (e) => {
|
|
513
|
+
if (e.key === "Escape")
|
|
514
|
+
setExpandedCell(null);
|
|
515
|
+
};
|
|
516
|
+
document.addEventListener("click", close, true);
|
|
517
|
+
document.addEventListener("keydown", onKey);
|
|
518
|
+
return () => {
|
|
519
|
+
document.removeEventListener("click", close, true);
|
|
520
|
+
document.removeEventListener("keydown", onKey);
|
|
521
|
+
};
|
|
522
|
+
}, [expandedCell]);
|
|
495
523
|
const sort = sortProp !== undefined ? sortProp : internalSort;
|
|
496
524
|
// One shape downstream: [] when unsorted, ordered by precedence otherwise.
|
|
497
525
|
const sorts = React.useMemo(() => (sort == null ? [] : Array.isArray(sort) ? sort : [sort]), [sort]);
|
|
@@ -920,6 +948,10 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
920
948
|
function openEditing(id, key) {
|
|
921
949
|
setEditError(null);
|
|
922
950
|
setEditing({ id, key });
|
|
951
|
+
// #579: the editing branch pre-empts the plain-cell render (and its
|
|
952
|
+
// overlay) entirely, but `expandedCell` itself would survive a later
|
|
953
|
+
// cancel-back-to-plain-view unless cleared here too.
|
|
954
|
+
setExpandedCell(null);
|
|
923
955
|
}
|
|
924
956
|
// Async-aware (#385): a sync `onEdit` (returns undefined) commits exactly
|
|
925
957
|
// as before -- close the editor immediately. A Promise-returning `onEdit`
|
|
@@ -1175,7 +1207,17 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1175
1207
|
// aria-sort is specified as one header per table, and with the
|
|
1176
1208
|
// multi-sort the `sort` prop allows, a second "ascending" says
|
|
1177
1209
|
// nothing about which column came first.
|
|
1178
|
-
|
|
1210
|
+
// crisp/#605: the primary/entity column is inert -- no menu,
|
|
1211
|
+
// no click-to-sort, no aria-sort claim -- matching Attio's
|
|
1212
|
+
// own measured behaviour exactly ("no popover opens, no
|
|
1213
|
+
// right-click menu either... primary column is fixed by
|
|
1214
|
+
// construction, not a menu target", checklist G-023). A
|
|
1215
|
+
// consumer setting `sortable`/`columnMenu` on an entity
|
|
1216
|
+
// column doesn't get a sort affordance there; this is
|
|
1217
|
+
// `!isPrimary` gating every interactive branch below, not
|
|
1218
|
+
// just the menu.
|
|
1219
|
+
const isPrimary = col.type === "entity";
|
|
1220
|
+
const sortable = !isPrimary && (col.sortable || !!columnMenu);
|
|
1179
1221
|
const ariaSort = !sortable
|
|
1180
1222
|
? undefined
|
|
1181
1223
|
: sorts[0]?.key === col.key
|
|
@@ -1196,9 +1238,9 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1196
1238
|
}
|
|
1197
1239
|
else if (e.key === "Escape")
|
|
1198
1240
|
setRenaming(null);
|
|
1199
|
-
} })) : columnMenu ? (_jsxs(Menu, { align: "start", trigger: _jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), "aria-label": `${typeof col.header === "string"
|
|
1241
|
+
} })) : columnMenu && !isPrimary ? (_jsxs(Menu, { align: "start", trigger: _jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), "aria-label": `${typeof col.header === "string"
|
|
1200
1242
|
? col.header
|
|
1201
|
-
: col.key} ${crispMessages["column options"] ?? "column options"}`, children: inner }), children: [_jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children: sortAscendingLabel }), _jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "desc" }), children: sortDescendingLabel }), col.managed === false ? null : (_jsxs(_Fragment, { children: [_jsx(MenuItem, { onSelect: () => moveColumn(col.key, -1), children: moveColumnLeftLabel }), _jsx(MenuItem, { onSelect: () => moveColumn(col.key, 1), children: moveColumnRightLabel }), _jsx(MenuItem, { onSelect: () => setRenaming(col.key), children: editColumnLabel }), _jsx(MenuItem, { onSelect: () => hideColumn(col.key), children: hideColumnLabel })] }))] })) : col.sortable ? (_jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), onClick: () => cycleSort(col.key), children: inner })) : (inner), col.resizable !== false && (_jsx("span", { className: "crisp-datatable-resize", "data-resizing": resizingKey === col.key ? "" : undefined, onPointerDown: (e) => startResize(col, e), onPointerMove: moveResize, onPointerUp: endResize }))] }, col.key));
|
|
1243
|
+
: col.key} ${crispMessages["column options"] ?? "column options"}`, children: inner }), children: [_jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children: sortAscendingLabel }), _jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "desc" }), children: sortDescendingLabel }), col.managed === false ? null : (_jsxs(_Fragment, { children: [_jsx(MenuItem, { onSelect: () => moveColumn(col.key, -1), children: moveColumnLeftLabel }), _jsx(MenuItem, { onSelect: () => moveColumn(col.key, 1), children: moveColumnRightLabel }), _jsx(MenuItem, { onSelect: () => setRenaming(col.key), children: editColumnLabel }), _jsx(MenuItem, { onSelect: () => hideColumn(col.key), children: hideColumnLabel })] }))] })) : col.sortable && !isPrimary ? (_jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), onClick: () => cycleSort(col.key), children: inner })) : (inner), col.readOnly && (_jsx(Lock, { className: "crisp-datatable-readonly", size: 12, strokeWidth: 1.75, "aria-hidden": "true" })), col.resizable !== false && (_jsx("span", { className: "crisp-datatable-resize", "data-resizing": resizingKey === col.key ? "" : undefined, onPointerDown: (e) => startResize(col, e), onPointerMove: moveResize, onPointerUp: endResize }))] }, col.key));
|
|
1202
1244
|
})] }) }), _jsx(TableBody, { children: error ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: totalCols, children: _jsx("div", { className: "crisp-datatable-empty", children: error }) }) })) : rows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: totalCols, children: _jsx("div", { className: "crisp-datatable-empty", children: empty }) }) })) : (_jsxs(_Fragment, { children: [topSpacerHeight > 0 && (_jsx("tr", { "aria-hidden": "true", children: _jsx("td", { colSpan: totalCols, style: {
|
|
1203
1245
|
height: topSpacerHeight,
|
|
1204
1246
|
padding: 0,
|
|
@@ -1300,22 +1342,39 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1300
1342
|
commitEdit(row.id, col.key, e.currentTarget.value);
|
|
1301
1343
|
} }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }, col.key));
|
|
1302
1344
|
}
|
|
1345
|
+
const rendered = col.render
|
|
1346
|
+
? col.render(value, row)
|
|
1347
|
+
: renderTyped(col, value, avatarTone, row, col.format?.(value, row), locale);
|
|
1348
|
+
const isExpanded = expandedCell?.id === row.id &&
|
|
1349
|
+
expandedCell?.key === col.key;
|
|
1303
1350
|
return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: col.editable
|
|
1304
1351
|
? "crisp-datatable-editable"
|
|
1305
1352
|
: undefined, "data-selected": selCell?.id === row.id && selCell?.key === col.key
|
|
1306
1353
|
? ""
|
|
1307
|
-
: undefined, onClick: () => {
|
|
1354
|
+
: undefined, "data-cell-expanded": isExpanded ? "" : undefined, onClick: (e) => {
|
|
1308
1355
|
// the keyboard model's anchor -- a single click
|
|
1309
1356
|
// only selects. A genuine double-click (or Enter
|
|
1310
1357
|
// on the selected cell) opens the editor (#411),
|
|
1311
1358
|
// matching RecordsTable's own onDoubleClick.
|
|
1312
1359
|
setSelCell({ id: row.id, key: col.key });
|
|
1313
1360
|
gridRef.current?.focus({ preventScroll: true });
|
|
1361
|
+
// #579: a truncated cell also opens a read-only
|
|
1362
|
+
// overlay with its full value -- settled live
|
|
1363
|
+
// that this is click, not Attio's usual
|
|
1364
|
+
// hover-expand (a 1s hover on a truncated
|
|
1365
|
+
// Description cell produced nothing). Measured
|
|
1366
|
+
// the same way as `ThLabel`'s header tooltip
|
|
1367
|
+
// (#534): scrollWidth vs clientWidth on the
|
|
1368
|
+
// element the CSS ellipsis actually applies to,
|
|
1369
|
+
// which for a data cell is the `td` itself
|
|
1370
|
+
// (table.recipe.ts's own `overflow: hidden;
|
|
1371
|
+
// text-overflow: ellipsis`), not a nested span.
|
|
1372
|
+
setExpandedCell(isLabelTruncated(e.currentTarget)
|
|
1373
|
+
? { id: row.id, key: col.key }
|
|
1374
|
+
: null);
|
|
1314
1375
|
}, onDoubleClick: col.editable
|
|
1315
1376
|
? () => openEditing(row.id, col.key)
|
|
1316
|
-
: undefined, children: [col.
|
|
1317
|
-
? col.render(value, row)
|
|
1318
|
-
: renderTyped(col, value, avatarTone, row, col.format?.(value, row), locale), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1377
|
+
: undefined, children: [!isExpanded && rendered, errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere })), isExpanded && (_jsx("div", { className: "crisp-datatable-cell-overlay", onClick: (e) => e.stopPropagation(), children: rendered }))] }, col.key));
|
|
1319
1378
|
})] }, row.id));
|
|
1320
1379
|
}), bottomSpacerHeight > 0 && (_jsx("tr", { "aria-hidden": "true", children: _jsx("td", { colSpan: totalCols, style: {
|
|
1321
1380
|
height: bottomSpacerHeight,
|