@crisp-ui-kit/crisp 0.52.4 → 0.52.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/board.css +6 -6
  2. package/dist/board.layered.css +6 -6
  3. package/dist/cjs/components/datatable/datatable.recipe.js +4 -1
  4. package/dist/cjs/components/input/input.recipe.js +6 -1
  5. package/dist/cjs/components/switch/switch.recipe.js +4 -0
  6. package/dist/cjs/components/table/table.recipe.js +5 -1
  7. package/dist/cjs/components/textarea/textarea.recipe.js +6 -1
  8. package/dist/cjs/product/board/board.recipe.js +16 -6
  9. package/dist/cjs/tokens/elevation.js +17 -2
  10. package/dist/cjs/tokens/palette.js +17 -0
  11. package/dist/cjs/tokens/semantic.js +2 -0
  12. package/dist/datatable.css +30 -24
  13. package/dist/datatable.layered.css +30 -24
  14. package/dist/esm/components/datatable/datatable.recipe.js +4 -1
  15. package/dist/esm/components/input/input.recipe.js +6 -1
  16. package/dist/esm/components/switch/switch.recipe.js +4 -0
  17. package/dist/esm/components/table/table.recipe.js +5 -1
  18. package/dist/esm/components/textarea/textarea.recipe.js +6 -1
  19. package/dist/esm/product/board/board.recipe.js +16 -6
  20. package/dist/esm/tokens/elevation.js +17 -2
  21. package/dist/esm/tokens/palette.js +17 -0
  22. package/dist/esm/tokens/semantic.js +2 -0
  23. package/dist/input.css +1 -1
  24. package/dist/input.layered.css +1 -1
  25. package/dist/native/tokens.d.ts +1 -1
  26. package/dist/native/tokens.js +8 -0
  27. package/dist/styles.css +46 -34
  28. package/dist/styles.layered.css +46 -34
  29. package/dist/switch.css +1 -1
  30. package/dist/switch.layered.css +1 -1
  31. package/dist/table.css +3 -1
  32. package/dist/table.layered.css +3 -1
  33. package/dist/textarea.css +1 -1
  34. package/dist/textarea.layered.css +1 -1
  35. package/dist/tokens.css +4 -0
  36. package/dist/tokens.json +8 -0
  37. package/dist/types/components/switch/switch.recipe.d.ts +1 -0
  38. package/dist/types/tokens/contract.d.ts +2 -0
  39. package/dist/types/tokens/elevation.d.ts +17 -2
  40. package/dist/types/tokens/palette.d.ts +8 -0
  41. package/dist/types/tokens/semantic.d.ts +2 -0
  42. package/package.json +1 -1
@@ -3,21 +3,31 @@ import { contract as c } from "../../tokens/index.js";
3
3
  // A kanban board — measured from a Deals pipeline: horizontally scrolling
4
4
  // columns (dot + title + count header, a "New" button, a stack of draggable
5
5
  // cards), with the hovered drop column highlighted.
6
+ //
7
+ // Re-measured directly against a live Deals pipeline board (#463): column
8
+ // 268px (was an unmeasured 280), 8px gap between columns (was space[3]/12,
9
+ // measured space[2]), the stage dot 12x12 (was a guessed 9x9), the header's
10
+ // "+" 20x20 at radius.sm (was 24x24 at radius.md), the count as a filled
11
+ // 16px pill rather than bare text, and the card at radius.xl/12px (was
12
+ // radius.lg/9) with a single measured 1px ring and no second shadow layer —
13
+ // the extra `0 1px 2px rgba(0,0,0,0.04)` layer doesn't exist on the
14
+ // reference card at all (see AUTHORING Lesson 22: don't add polish the
15
+ // reference lacks).
6
16
  const config = defineRecipe({
7
17
  name: "board",
8
- base: `display: flex; gap: ${c.space["3"]}; height: 100%; box-sizing: border-box; padding: ${c.space["3"]}; overflow-x: auto; align-items: flex-start; font-family: ${c.font.sans};
9
- & .crisp-board-col { display: flex; flex-direction: column; width: 280px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
18
+ base: `display: flex; gap: ${c.space["2"]}; height: 100%; box-sizing: border-box; padding: ${c.space["3"]}; overflow-x: auto; align-items: flex-start; font-family: ${c.font.sans};
19
+ & .crisp-board-col { display: flex; flex-direction: column; width: 268px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
10
20
  & .crisp-board-col-header { display: flex; align-items: center; gap: ${c.space["2"]}; height: 36px; padding: 0 ${c.space["1"]}; flex-shrink: 0; }
11
- & .crisp-board-col-dot { width: 9px; height: 9px; border-radius: ${c.radius.full}; flex-shrink: 0; }
21
+ & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: ${c.radius.full}; flex-shrink: 0; }
12
22
  & .crisp-board-col-title { font-size: ${c.text.lg}; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; }
13
- & .crisp-board-col-count { font-size: ${c.text.md}; color: ${c.fgMuted}; }
14
- & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-start: auto; padding: 0; border: none; border-radius: ${c.radius.md}; background: none; color: ${c.fgMuted}; font-family: inherit; cursor: pointer; }
23
+ & .crisp-board-col-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 ${c.space["1"]}; border-radius: ${c.radius.sm}; background: color-mix(in srgb, ${c.fgPrimary} 4%, transparent); font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
24
+ & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-start: auto; padding: 0; border: none; border-radius: ${c.radius.sm}; background: none; color: ${c.fgMuted}; font-family: inherit; cursor: pointer; }
15
25
  & .crisp-board-col-add:hover { background: color-mix(in srgb, ${c.fgPrimary} 6%, transparent); color: ${c.fgPrimary}; }
16
26
  & .crisp-board-col-body { display: flex; flex-direction: column; gap: ${c.space["2"]}; flex: 1; min-height: 48px; padding: ${c.space["1"]}; border-radius: ${c.radius.lg}; overflow-y: auto; transition: background 0.12s; }
17
27
  & .crisp-board-col[data-over] .crisp-board-col-body { background: color-mix(in srgb, ${c.bgBrandSolid} 7%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.bgBrandSolid} 30%, transparent); }
18
28
  & .crisp-board-newcard { display: flex; align-items: center; justify-content: center; gap: ${c.space["1.5"]}; height: 32px; flex-shrink: 0; border: none; border-radius: ${c.radius.md}; background: color-mix(in srgb, ${c.fgPrimary} 4%, transparent); color: ${c.fgMuted}; font-family: inherit; font-size: ${c.text.md}; font-weight: ${c.weight.medium}; cursor: pointer; }
19
29
  & .crisp-board-newcard:hover { background: color-mix(in srgb, ${c.fgPrimary} 7%, transparent); color: ${c.fgPrimary}; }
20
- & .crisp-board-card { display: flex; flex-direction: column; gap: ${c.space["2"]}; box-sizing: border-box; padding: ${c.space["2.5"]} ${c.space["3"]}; border-radius: ${c.radius.lg}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}, 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
30
+ & .crisp-board-card { display: flex; flex-direction: column; gap: ${c.space["2"]}; box-sizing: border-box; padding: ${c.space["2.5"]} ${c.space["3"]}; border-radius: ${c.radius.xl}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; cursor: grab; }
21
31
  & .crisp-board-card:active { cursor: grabbing; }
22
32
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
23
33
  & .crisp-board-card:focus { outline: none; }
@@ -24,8 +24,16 @@ export const elevation = {
24
24
  */
25
25
  modal: `0 8px 28px -6px ${c.shadowOverlay1}, 0 18px 88px -4px ${c.shadowOverlay2}, 0 0 1px ${c.shadowOverlay3}`,
26
26
  /**
27
- * The neutral/outlined control shadow — buttons, inputs, textareas, the
28
- * sidebar search, record-page actions.
27
+ * The neutral/outlined BUTTON-CHROME shadow — real buttons only: the
28
+ * sidebar search trigger, record-page action buttons, table toolbar
29
+ * buttons. #466 corrects this doc comment's own former claim that it
30
+ * covers "inputs, textareas" too — it doesn't: a live Attio text field
31
+ * (Settings page, a real typed input) measures a single 1px ring, none of
32
+ * this token's glow/drop-shadow layers. Confirmed the other direction
33
+ * too — Attio's own white-filled chrome buttons ("Inbox", "Ask Attio",
34
+ * "All Companies" view chip) match this three-layer value exactly, so the
35
+ * token itself was right; only Input/Textarea's use of it was wrong. See
36
+ * `textField` below for the real field shadow.
29
37
  *
30
38
  * The two raw values **are** the measured value (Lessons 22 and 29) and
31
39
  * must stay raw. `rgba(0,0,0,0.04)` equals `tileActiveShadow`'s light value by
@@ -33,6 +41,13 @@ export const elevation = {
33
41
  * dark mode. Only the ring is theme-aware, via `ringNeutral`.
34
42
  */
35
43
  field: `inset 0 0 0 1px ${c.ringNeutral}, 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04)`,
44
+ /**
45
+ * A real text-entry field (Input, Textarea) — measured live on Attio's
46
+ * Settings page: one 1px inset ring, nothing else (#466). Distinct from
47
+ * `field` above, which is button chrome and carries two extra shadow
48
+ * layers a typed field never has.
49
+ */
50
+ textField: `inset 0 0 0 1px ${c.ringField}`,
36
51
  /**
37
52
  * The same control shadow **without** the theme-aware ring. Lesson 29 added
38
53
  * the ring to the neutral-button family but deliberately left `trialbanner`
@@ -10,6 +10,23 @@ export const palette = {
10
10
  line200: { light: "#e4e4e7", dark: "#212224" },
11
11
  line100: { light: "#eeeff1", dark: "#1c1d1f" },
12
12
  ringNeutral: { light: "transparent", dark: "#212224" },
13
+ // Text-entry fields (Input, Textarea) — measured live on app.attio.com's
14
+ // Settings page, a real typed field: `inset 0 0 0 1px rgba(0,0,0,.06)`,
15
+ // ONE layer, not `elevation.field`'s button-chrome three (that token's own
16
+ // doc comment claimed "inputs, textareas" among its consumers; #466
17
+ // corrects it — a real field never carried the extra glow/drop-shadow
18
+ // layers, only its own 1px ring). Dark value not independently
19
+ // re-measured (Attio's own theme setting is off-limits to toggle) — reuses
20
+ // `ringNeutral`/`ringPopover`'s existing dark ring (#212224), the same
21
+ // "edge visible against a dark surface" role those already establish,
22
+ // rather than inventing a new value.
23
+ ringField: { light: "rgba(0,0,0,0.06)", dark: "#212224" },
24
+ // Switch's thumb ring (#463) — measured live, `rgb(217,218,222)` on the
25
+ // white thumb. Dark value not independently re-measured (same Attio-theme
26
+ // restriction as ringField above); the thumb itself stays pure white in
27
+ // both themes (Switch's own spec), so this reuses ringField's dark value
28
+ // rather than inventing a third "field-adjacent ring, dark" colour.
29
+ ringSwitch: { light: "#d9dade", dark: "#212224" },
13
30
  tileActive: { light: "#ffffff", dark: "#101112" },
14
31
  tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
15
32
  overlayFrost: { light: "#ffffff", dark: "#1c1d1f" },
@@ -11,6 +11,8 @@ export const semantic = {
11
11
  borderDefault: "line200",
12
12
  borderSubtle: "line100",
13
13
  ringNeutral: "ringNeutral",
14
+ ringField: "ringField",
15
+ ringSwitch: "ringSwitch",
14
16
  bgTileActive: "tileActive",
15
17
  shadowTileActive: "tileActiveShadow",
16
18
  bgOverlayFrost: "overlayFrost",
package/dist/input.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-input {
2
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
2
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
3
3
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
4
4
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
5
5
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-input {
3
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
3
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
4
4
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
5
5
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
6
6
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -2,7 +2,7 @@
2
2
  // The same tokens the CSS custom properties carry, resolved per theme, with
3
3
  // dimensions as numbers for React Native and Lynx style objects.
4
4
  export type Theme = "light" | "dark";
5
- export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "borderDefault" | "borderSubtle" | "ringNeutral" | "bgTileActive" | "shadowTileActive" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft", string>;
5
+ export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "borderDefault" | "borderSubtle" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft", string>;
6
6
  export declare const tokens: {
7
7
  color: Record<Theme, ColorTokens>;
8
8
  palette: Record<Theme, Record<string, string>>;
@@ -13,6 +13,8 @@ export const tokens = {
13
13
  "borderDefault": "#e4e4e7",
14
14
  "borderSubtle": "#eeeff1",
15
15
  "ringNeutral": "transparent",
16
+ "ringField": "rgba(0,0,0,0.06)",
17
+ "ringSwitch": "#d9dade",
16
18
  "bgTileActive": "#ffffff",
17
19
  "shadowTileActive": "rgba(0,0,0,0.04)",
18
20
  "bgOverlayFrost": "#ffffff",
@@ -59,6 +61,8 @@ export const tokens = {
59
61
  "borderDefault": "#212224",
60
62
  "borderSubtle": "#1c1d1f",
61
63
  "ringNeutral": "#212224",
64
+ "ringField": "#212224",
65
+ "ringSwitch": "#212224",
62
66
  "bgTileActive": "#101112",
63
67
  "shadowTileActive": "rgba(0,0,0,0)",
64
68
  "bgOverlayFrost": "#1c1d1f",
@@ -106,6 +110,8 @@ export const tokens = {
106
110
  "line200": "#e4e4e7",
107
111
  "line100": "#eeeff1",
108
112
  "ringNeutral": "transparent",
113
+ "ringField": "rgba(0,0,0,0.06)",
114
+ "ringSwitch": "#d9dade",
109
115
  "tileActive": "#ffffff",
110
116
  "tileActiveShadow": "rgba(0,0,0,0.04)",
111
117
  "overlayFrost": "#ffffff",
@@ -151,6 +157,8 @@ export const tokens = {
151
157
  "line200": "#212224",
152
158
  "line100": "#1c1d1f",
153
159
  "ringNeutral": "#212224",
160
+ "ringField": "#212224",
161
+ "ringSwitch": "#212224",
154
162
  "tileActive": "#101112",
155
163
  "tileActiveShadow": "rgba(0,0,0,0)",
156
164
  "overlayFrost": "#1c1d1f",
package/dist/styles.css CHANGED
@@ -8,6 +8,8 @@
8
8
  --crisp-border-default: #e4e4e7;
9
9
  --crisp-border-subtle: #eeeff1;
10
10
  --crisp-ring-neutral: transparent;
11
+ --crisp-ring-field: rgba(0,0,0,0.06);
12
+ --crisp-ring-switch: #d9dade;
11
13
  --crisp-bg-tile-active: #ffffff;
12
14
  --crisp-shadow-tile-active: rgba(0,0,0,0.04);
13
15
  --crisp-bg-overlay-frost: #ffffff;
@@ -55,6 +57,8 @@
55
57
  --crisp-border-default: #212224;
56
58
  --crisp-border-subtle: #1c1d1f;
57
59
  --crisp-ring-neutral: #212224;
60
+ --crisp-ring-field: #212224;
61
+ --crisp-ring-switch: #212224;
58
62
  --crisp-bg-tile-active: #101112;
59
63
  --crisp-shadow-tile-active: rgba(0,0,0,0);
60
64
  --crisp-bg-overlay-frost: #1c1d1f;
@@ -572,13 +576,15 @@
572
576
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); 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); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
573
577
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
574
578
  & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
575
- & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
576
- /* A right-aligned column's header keeps its field-icon + label packed at
577
- the START, matching its own values only in that the VALUES right-align
578
- -- Attio's own right-aligned header (measured live: "Twitter follower
579
- count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
580
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
581
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
579
+ /* #465: 12px per the comment above (and Attio's measured header cell
580
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
581
+ own documented value. */
582
+ & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
583
+ /* A right-aligned column's header keeps its field-icon + label packed at the START, matching its own values only in that the VALUES right-align -- Attio's own right-aligned header (measured live: "Twitter follower
584
+ count") keeps the icon+label left and only the trailing column-menu
585
+ icon sits pinned to the right edge, independent of the label's width (#406, reversing #393's own justify-content: flex-end here). The
586
+ chevron's own margin-inline-start: auto below already pins IT to the end of a flex-start row with no override needed -- that's what pins
587
+ the trailing icon on Attio too. */
582
588
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
583
589
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
584
590
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
@@ -602,25 +608,23 @@
602
608
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
603
609
  & .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; }
604
610
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
605
- /* An async onEdit's in-flight state (#385) -- the field/picker dims and
606
- stops accepting input rather than disappearing, so the just-typed or
607
- just-chosen value stays visible while its commit is pending. Same
608
- treatment Input already gives its own disabled state. */
611
+ /* 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. */
609
612
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
610
613
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
611
- /* The rejection's message, beside the cell's own content -- a sibling in
612
- 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
613
- it. Block-level: the row grows to fit it rather than floating over
614
- the row below, so it can never end up clipped by the scroll
615
- container's overflow the way a position:fixed escape would need guarding against. */
614
+ /* 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
615
+ 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
616
+ guarding against. */
616
617
  & .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); }
617
- /* 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
618
- shares RecordsTable's grid, Golden Rule 0). */
618
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
619
+ the cell IS the picker: same chromeless-fill treatment as
620
+ RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
619
621
  & .crisp-datatable-td--picker { padding: 0; }
620
622
  & .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); }
621
623
  & .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; }
622
624
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
623
- /* 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*. */
625
+ /* reference picker: a leading 16px avatar per option, plus a trailing
626
+ "Create X" row when the consumer supplies one (#449) -- same measured
627
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
624
628
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
625
629
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
626
630
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
@@ -635,7 +639,12 @@
635
639
  & .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); }
636
640
  & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
637
641
  & .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); }
638
- & .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); }
642
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
643
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
644
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
645
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
646
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
647
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
639
648
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
640
649
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
641
650
  & .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); }
@@ -648,10 +657,11 @@
648
657
  & .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)); }
649
658
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
650
659
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
651
- /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
652
- 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
653
- draggable + dataTransfer, the same one column reorder already uses
654
- (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
660
+ /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
661
+ hairline doubling as the drop indicator (it tints toward the brand
662
+ colour as the active drop target). The affordance + geometry are
663
+ 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
664
+ pointer-events implementation. */
655
665
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
656
666
  & .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; }
657
667
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -1016,7 +1026,7 @@
1016
1026
  }
1017
1027
 
1018
1028
  .crisp-input {
1019
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1029
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1020
1030
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
1021
1031
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
1022
1032
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -1546,7 +1556,7 @@
1546
1556
 
1547
1557
  .crisp-switch {
1548
1558
  display: inline-flex; align-items: center; flex-shrink: 0; width: 24px; height: 16px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .2s ease; -webkit-tap-highlight-color: transparent;
1549
- & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; transition: transform .2s ease; }
1559
+ & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; box-shadow: 0 0 0 1px var(--crisp-ring-switch); transition: transform .2s ease; }
1550
1560
  &[data-checked] { background: var(--crisp-bg-brand-solid); }
1551
1561
  &[data-checked] .crisp-switch-thumb { transform: translateX(8px); }
1552
1562
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
@@ -1565,7 +1575,9 @@
1565
1575
  display: block; box-sizing: border-box; background: var(--crisp-bg-surface);
1566
1576
  & .crisp-table-scroll { overflow-x: auto; }
1567
1577
  & table { width: 100%; border-collapse: separate; border-spacing: 0; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1568
- & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1578
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell
1579
+ (padding: 0 12px) and independently on a body cell's text inset (getBoundingClientRect delta from the cell edge). Matches ColumnCalc's own already-correct 12px trigger inset. */
1580
+ & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1569
1581
  & th { height: var(--crisp-size-tableHeader); background: var(--crisp-bg-surface); border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); user-select: none; }
1570
1582
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
1571
1583
  & th:last-child, & td:last-child { border-inline-end: none; }
@@ -1704,7 +1716,7 @@
1704
1716
 
1705
1717
  .crisp-textarea {
1706
1718
  position: relative; display: block; width: 100%; font-family: var(--crisp-font-sans);
1707
- & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
1719
+ & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
1708
1720
  & .crisp-textarea-field::placeholder { color: var(--crisp-fg-muted); }
1709
1721
  & .crisp-textarea-field:disabled { opacity: 0.5; cursor: not-allowed; }
1710
1722
  /* seed-design's focus treatment, same as Input: a 2px ring on an ::after
@@ -1860,19 +1872,19 @@
1860
1872
  }
1861
1873
 
1862
1874
  .crisp-board {
1863
- display: flex; gap: var(--crisp-space-3); height: 100%; box-sizing: border-box; padding: var(--crisp-space-3); overflow-x: auto; align-items: flex-start; font-family: var(--crisp-font-sans);
1864
- & .crisp-board-col { display: flex; flex-direction: column; width: 280px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
1875
+ display: flex; gap: var(--crisp-space-2); height: 100%; box-sizing: border-box; padding: var(--crisp-space-3); overflow-x: auto; align-items: flex-start; font-family: var(--crisp-font-sans);
1876
+ & .crisp-board-col { display: flex; flex-direction: column; width: 268px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
1865
1877
  & .crisp-board-col-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding: 0 var(--crisp-space-1); flex-shrink: 0; }
1866
- & .crisp-board-col-dot { width: 9px; height: 9px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
1878
+ & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
1867
1879
  & .crisp-board-col-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1868
- & .crisp-board-col-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1869
- & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-start: auto; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
1880
+ & .crisp-board-col-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
1881
+ & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-start: auto; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
1870
1882
  & .crisp-board-col-add:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
1871
1883
  & .crisp-board-col-body { display: flex; flex-direction: column; gap: var(--crisp-space-2); flex: 1; min-height: 48px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-lg); overflow-y: auto; transition: background 0.12s; }
1872
1884
  & .crisp-board-col[data-over] .crisp-board-col-body { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 7%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
1873
1885
  & .crisp-board-newcard { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1-5); height: 32px; flex-shrink: 0; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
1874
1886
  & .crisp-board-newcard:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); }
1875
- & .crisp-board-card { display: flex; flex-direction: column; gap: var(--crisp-space-2); box-sizing: border-box; padding: var(--crisp-space-2-5) var(--crisp-space-3); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle), 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
1887
+ & .crisp-board-card { display: flex; flex-direction: column; gap: var(--crisp-space-2); box-sizing: border-box; padding: var(--crisp-space-2-5) var(--crisp-space-3); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); cursor: grab; }
1876
1888
  & .crisp-board-card:active { cursor: grabbing; }
1877
1889
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
1878
1890
  & .crisp-board-card:focus { outline: none; }
@@ -8,6 +8,8 @@
8
8
  --crisp-border-default: #e4e4e7;
9
9
  --crisp-border-subtle: #eeeff1;
10
10
  --crisp-ring-neutral: transparent;
11
+ --crisp-ring-field: rgba(0,0,0,0.06);
12
+ --crisp-ring-switch: #d9dade;
11
13
  --crisp-bg-tile-active: #ffffff;
12
14
  --crisp-shadow-tile-active: rgba(0,0,0,0.04);
13
15
  --crisp-bg-overlay-frost: #ffffff;
@@ -55,6 +57,8 @@
55
57
  --crisp-border-default: #212224;
56
58
  --crisp-border-subtle: #1c1d1f;
57
59
  --crisp-ring-neutral: #212224;
60
+ --crisp-ring-field: #212224;
61
+ --crisp-ring-switch: #212224;
58
62
  --crisp-bg-tile-active: #101112;
59
63
  --crisp-shadow-tile-active: rgba(0,0,0,0);
60
64
  --crisp-bg-overlay-frost: #1c1d1f;
@@ -573,13 +577,15 @@
573
577
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); 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); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
574
578
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
575
579
  & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
576
- & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
577
- /* A right-aligned column's header keeps its field-icon + label packed at
578
- the START, matching its own values only in that the VALUES right-align
579
- -- Attio's own right-aligned header (measured live: "Twitter follower
580
- count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
581
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
582
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
580
+ /* #465: 12px per the comment above (and Attio's measured header cell
581
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
582
+ own documented value. */
583
+ & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
584
+ /* A right-aligned column's header keeps its field-icon + label packed at the START, matching its own values only in that the VALUES right-align -- Attio's own right-aligned header (measured live: "Twitter follower
585
+ count") keeps the icon+label left and only the trailing column-menu
586
+ icon sits pinned to the right edge, independent of the label's width (#406, reversing #393's own justify-content: flex-end here). The
587
+ chevron's own margin-inline-start: auto below already pins IT to the end of a flex-start row with no override needed -- that's what pins
588
+ the trailing icon on Attio too. */
583
589
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
584
590
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
585
591
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
@@ -603,25 +609,23 @@
603
609
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
604
610
  & .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; }
605
611
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
606
- /* An async onEdit's in-flight state (#385) -- the field/picker dims and
607
- stops accepting input rather than disappearing, so the just-typed or
608
- just-chosen value stays visible while its commit is pending. Same
609
- treatment Input already gives its own disabled state. */
612
+ /* 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. */
610
613
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
611
614
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
612
- /* The rejection's message, beside the cell's own content -- a sibling in
613
- 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
614
- it. Block-level: the row grows to fit it rather than floating over
615
- the row below, so it can never end up clipped by the scroll
616
- container's overflow the way a position:fixed escape would need guarding against. */
615
+ /* 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
616
+ 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
617
+ guarding against. */
617
618
  & .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); }
618
- /* 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
619
- shares RecordsTable's grid, Golden Rule 0). */
619
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
620
+ the cell IS the picker: same chromeless-fill treatment as
621
+ RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
620
622
  & .crisp-datatable-td--picker { padding: 0; }
621
623
  & .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); }
622
624
  & .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; }
623
625
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
624
- /* 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*. */
626
+ /* reference picker: a leading 16px avatar per option, plus a trailing
627
+ "Create X" row when the consumer supplies one (#449) -- same measured
628
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
625
629
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
626
630
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
627
631
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
@@ -636,7 +640,12 @@
636
640
  & .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); }
637
641
  & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
638
642
  & .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); }
639
- & .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); }
643
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
644
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
645
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
646
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
647
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
648
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
640
649
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
641
650
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
642
651
  & .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); }
@@ -649,10 +658,11 @@
649
658
  & .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)); }
650
659
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
651
660
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
652
- /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
653
- 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
654
- draggable + dataTransfer, the same one column reorder already uses
655
- (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
661
+ /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
662
+ hairline doubling as the drop indicator (it tints toward the brand
663
+ colour as the active drop target). The affordance + geometry are
664
+ 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
665
+ pointer-events implementation. */
656
666
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
657
667
  & .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; }
658
668
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -1017,7 +1027,7 @@
1017
1027
  }
1018
1028
 
1019
1029
  .crisp-input {
1020
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1030
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1021
1031
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
1022
1032
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
1023
1033
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -1547,7 +1557,7 @@
1547
1557
 
1548
1558
  .crisp-switch {
1549
1559
  display: inline-flex; align-items: center; flex-shrink: 0; width: 24px; height: 16px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .2s ease; -webkit-tap-highlight-color: transparent;
1550
- & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; transition: transform .2s ease; }
1560
+ & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; box-shadow: 0 0 0 1px var(--crisp-ring-switch); transition: transform .2s ease; }
1551
1561
  &[data-checked] { background: var(--crisp-bg-brand-solid); }
1552
1562
  &[data-checked] .crisp-switch-thumb { transform: translateX(8px); }
1553
1563
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
@@ -1566,7 +1576,9 @@
1566
1576
  display: block; box-sizing: border-box; background: var(--crisp-bg-surface);
1567
1577
  & .crisp-table-scroll { overflow-x: auto; }
1568
1578
  & table { width: 100%; border-collapse: separate; border-spacing: 0; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1569
- & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1579
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell
1580
+ (padding: 0 12px) and independently on a body cell's text inset (getBoundingClientRect delta from the cell edge). Matches ColumnCalc's own already-correct 12px trigger inset. */
1581
+ & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1570
1582
  & th { height: var(--crisp-size-tableHeader); background: var(--crisp-bg-surface); border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); user-select: none; }
1571
1583
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
1572
1584
  & th:last-child, & td:last-child { border-inline-end: none; }
@@ -1705,7 +1717,7 @@
1705
1717
 
1706
1718
  .crisp-textarea {
1707
1719
  position: relative; display: block; width: 100%; font-family: var(--crisp-font-sans);
1708
- & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
1720
+ & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
1709
1721
  & .crisp-textarea-field::placeholder { color: var(--crisp-fg-muted); }
1710
1722
  & .crisp-textarea-field:disabled { opacity: 0.5; cursor: not-allowed; }
1711
1723
  /* seed-design's focus treatment, same as Input: a 2px ring on an ::after
@@ -1861,19 +1873,19 @@
1861
1873
  }
1862
1874
 
1863
1875
  .crisp-board {
1864
- display: flex; gap: var(--crisp-space-3); height: 100%; box-sizing: border-box; padding: var(--crisp-space-3); overflow-x: auto; align-items: flex-start; font-family: var(--crisp-font-sans);
1865
- & .crisp-board-col { display: flex; flex-direction: column; width: 280px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
1876
+ display: flex; gap: var(--crisp-space-2); height: 100%; box-sizing: border-box; padding: var(--crisp-space-3); overflow-x: auto; align-items: flex-start; font-family: var(--crisp-font-sans);
1877
+ & .crisp-board-col { display: flex; flex-direction: column; width: 268px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; }
1866
1878
  & .crisp-board-col-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding: 0 var(--crisp-space-1); flex-shrink: 0; }
1867
- & .crisp-board-col-dot { width: 9px; height: 9px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
1879
+ & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
1868
1880
  & .crisp-board-col-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1869
- & .crisp-board-col-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1870
- & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-start: auto; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
1881
+ & .crisp-board-col-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
1882
+ & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-start: auto; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
1871
1883
  & .crisp-board-col-add:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
1872
1884
  & .crisp-board-col-body { display: flex; flex-direction: column; gap: var(--crisp-space-2); flex: 1; min-height: 48px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-lg); overflow-y: auto; transition: background 0.12s; }
1873
1885
  & .crisp-board-col[data-over] .crisp-board-col-body { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 7%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
1874
1886
  & .crisp-board-newcard { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1-5); height: 32px; flex-shrink: 0; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
1875
1887
  & .crisp-board-newcard:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); }
1876
- & .crisp-board-card { display: flex; flex-direction: column; gap: var(--crisp-space-2); box-sizing: border-box; padding: var(--crisp-space-2-5) var(--crisp-space-3); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle), 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
1888
+ & .crisp-board-card { display: flex; flex-direction: column; gap: var(--crisp-space-2); box-sizing: border-box; padding: var(--crisp-space-2-5) var(--crisp-space-3); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); cursor: grab; }
1877
1889
  & .crisp-board-card:active { cursor: grabbing; }
1878
1890
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
1879
1891
  & .crisp-board-card:focus { outline: none; }
package/dist/switch.css CHANGED
@@ -1,6 +1,6 @@
1
1
  .crisp-switch {
2
2
  display: inline-flex; align-items: center; flex-shrink: 0; width: 24px; height: 16px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .2s ease; -webkit-tap-highlight-color: transparent;
3
- & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; transition: transform .2s ease; }
3
+ & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; box-shadow: 0 0 0 1px var(--crisp-ring-switch); transition: transform .2s ease; }
4
4
  &[data-checked] { background: var(--crisp-bg-brand-solid); }
5
5
  &[data-checked] .crisp-switch-thumb { transform: translateX(8px); }
6
6
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
@@ -1,7 +1,7 @@
1
1
  @layer crisp-components {
2
2
  .crisp-switch {
3
3
  display: inline-flex; align-items: center; flex-shrink: 0; width: 24px; height: 16px; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .2s ease; -webkit-tap-highlight-color: transparent;
4
- & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; transition: transform .2s ease; }
4
+ & .crisp-switch-thumb { width: 12px; height: 12px; margin: 0 2px; border-radius: var(--crisp-radius-full); background: #fff; box-shadow: 0 0 0 1px var(--crisp-ring-switch); transition: transform .2s ease; }
5
5
  &[data-checked] { background: var(--crisp-bg-brand-solid); }
6
6
  &[data-checked] .crisp-switch-thumb { transform: translateX(8px); }
7
7
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
package/dist/table.css CHANGED
@@ -2,7 +2,9 @@
2
2
  display: block; box-sizing: border-box; background: var(--crisp-bg-surface);
3
3
  & .crisp-table-scroll { overflow-x: auto; }
4
4
  & table { width: 100%; border-collapse: separate; border-spacing: 0; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
5
- & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
5
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell
6
+ (padding: 0 12px) and independently on a body cell's text inset (getBoundingClientRect delta from the cell edge). Matches ColumnCalc's own already-correct 12px trigger inset. */
7
+ & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
6
8
  & th { height: var(--crisp-size-tableHeader); background: var(--crisp-bg-surface); border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); user-select: none; }
7
9
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
8
10
  & th:last-child, & td:last-child { border-inline-end: none; }