@crisp-ui-kit/crisp 0.54.1 → 0.54.2

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 (106) hide show
  1. package/dist/cjs/components/datatable/datatable.js +2 -1
  2. package/dist/cjs/components/datatable/datatable.recipe.js +4 -0
  3. package/dist/cjs/components/mentionchip/index.js +7 -0
  4. package/dist/cjs/components/mentionchip/mentionchip.js +57 -0
  5. package/dist/cjs/components/mentionchip/mentionchip.recipe.js +38 -0
  6. package/dist/cjs/components/table/table.recipe.js +15 -3
  7. package/dist/cjs/core/floating-stack.js +18 -0
  8. package/dist/cjs/core/index.js +2 -1
  9. package/dist/cjs/index.js +6 -0
  10. package/dist/cjs/product/formdialog/formdialog.js +171 -0
  11. package/dist/cjs/product/formdialog/formdialog.recipe.js +26 -0
  12. package/dist/cjs/product/formdialog/index.js +8 -0
  13. package/dist/cjs/product/importdialog/importdialog.js +134 -0
  14. package/dist/cjs/product/importdialog/importdialog.recipe.js +92 -0
  15. package/dist/cjs/product/importdialog/index.js +18 -0
  16. package/dist/cjs/product/savedviews/index.js +7 -0
  17. package/dist/cjs/product/savedviews/savedviews.js +207 -0
  18. package/dist/cjs/product/savedviews/savedviews.recipe.js +103 -0
  19. package/dist/cjs/product/taskstable/index.js +7 -0
  20. package/dist/cjs/product/taskstable/taskstable.js +27 -0
  21. package/dist/cjs/product/taskstable/taskstable.recipe.js +47 -0
  22. package/dist/cjs/product/themepicker/index.js +17 -0
  23. package/dist/cjs/product/themepicker/themepicker.js +32 -0
  24. package/dist/cjs/product/themepicker/themepicker.recipe.js +98 -0
  25. package/dist/cjs/tokens/palette.js +30 -13
  26. package/dist/cjs/tokens/semantic.js +6 -0
  27. package/dist/datatable.css +20 -21
  28. package/dist/datatable.layered.css +20 -21
  29. package/dist/esm/components/datatable/datatable.js +2 -1
  30. package/dist/esm/components/datatable/datatable.recipe.js +4 -0
  31. package/dist/esm/components/mentionchip/index.js +2 -0
  32. package/dist/esm/components/mentionchip/mentionchip.js +21 -0
  33. package/dist/esm/components/mentionchip/mentionchip.recipe.js +35 -0
  34. package/dist/esm/components/table/table.recipe.js +15 -3
  35. package/dist/esm/core/floating-stack.js +17 -0
  36. package/dist/esm/core/index.js +1 -1
  37. package/dist/esm/index.js +6 -0
  38. package/dist/esm/product/formdialog/formdialog.js +134 -0
  39. package/dist/esm/product/formdialog/formdialog.recipe.js +23 -0
  40. package/dist/esm/product/formdialog/index.js +2 -0
  41. package/dist/esm/product/importdialog/importdialog.js +97 -0
  42. package/dist/esm/product/importdialog/importdialog.recipe.js +89 -0
  43. package/dist/esm/product/importdialog/index.js +2 -0
  44. package/dist/esm/product/savedviews/index.js +2 -0
  45. package/dist/esm/product/savedviews/savedviews.js +171 -0
  46. package/dist/esm/product/savedviews/savedviews.recipe.js +100 -0
  47. package/dist/esm/product/taskstable/index.js +2 -0
  48. package/dist/esm/product/taskstable/taskstable.js +24 -0
  49. package/dist/esm/product/taskstable/taskstable.recipe.js +44 -0
  50. package/dist/esm/product/themepicker/index.js +1 -0
  51. package/dist/esm/product/themepicker/themepicker.js +29 -0
  52. package/dist/esm/product/themepicker/themepicker.recipe.js +95 -0
  53. package/dist/esm/tokens/palette.js +30 -13
  54. package/dist/esm/tokens/semantic.js +6 -0
  55. package/dist/formdialog.css +6 -0
  56. package/dist/formdialog.d.ts +7 -0
  57. package/dist/formdialog.layered.css +8 -0
  58. package/dist/importdialog.css +38 -0
  59. package/dist/importdialog.d.ts +7 -0
  60. package/dist/importdialog.layered.css +40 -0
  61. package/dist/mentionchip.css +3 -0
  62. package/dist/mentionchip.d.ts +7 -0
  63. package/dist/mentionchip.layered.css +5 -0
  64. package/dist/native/tokens.d.ts +1 -1
  65. package/dist/native/tokens.js +14 -6
  66. package/dist/savedviews.css +78 -0
  67. package/dist/savedviews.d.ts +7 -0
  68. package/dist/savedviews.layered.css +80 -0
  69. package/dist/styles.css +223 -30
  70. package/dist/styles.layered.css +223 -30
  71. package/dist/table.css +15 -6
  72. package/dist/table.layered.css +15 -6
  73. package/dist/taskstable.css +23 -0
  74. package/dist/taskstable.d.ts +7 -0
  75. package/dist/taskstable.layered.css +25 -0
  76. package/dist/themepicker.css +27 -0
  77. package/dist/themepicker.d.ts +7 -0
  78. package/dist/themepicker.layered.css +29 -0
  79. package/dist/tokens.css +7 -3
  80. package/dist/tokens.json +14 -6
  81. package/dist/types/components/datatable/datatable.d.ts +12 -1
  82. package/dist/types/components/mentionchip/index.d.ts +2 -0
  83. package/dist/types/components/mentionchip/mentionchip.d.ts +20 -0
  84. package/dist/types/components/mentionchip/mentionchip.recipe.d.ts +18 -0
  85. package/dist/types/core/floating-stack.d.ts +14 -0
  86. package/dist/types/core/index.d.ts +1 -1
  87. package/dist/types/index.d.ts +6 -0
  88. package/dist/types/product/formdialog/formdialog.d.ts +57 -0
  89. package/dist/types/product/formdialog/formdialog.recipe.d.ts +3 -0
  90. package/dist/types/product/formdialog/index.d.ts +2 -0
  91. package/dist/types/product/importdialog/importdialog.d.ts +86 -0
  92. package/dist/types/product/importdialog/importdialog.recipe.d.ts +3 -0
  93. package/dist/types/product/importdialog/index.d.ts +2 -0
  94. package/dist/types/product/savedviews/index.d.ts +2 -0
  95. package/dist/types/product/savedviews/savedviews.d.ts +55 -0
  96. package/dist/types/product/savedviews/savedviews.recipe.d.ts +3 -0
  97. package/dist/types/product/taskstable/index.d.ts +2 -0
  98. package/dist/types/product/taskstable/taskstable.d.ts +54 -0
  99. package/dist/types/product/taskstable/taskstable.recipe.d.ts +3 -0
  100. package/dist/types/product/themepicker/index.d.ts +1 -0
  101. package/dist/types/product/themepicker/themepicker.d.ts +21 -0
  102. package/dist/types/product/themepicker/themepicker.recipe.d.ts +3 -0
  103. package/dist/types/tokens/contract.d.ts +2 -0
  104. package/dist/types/tokens/palette.d.ts +11 -3
  105. package/dist/types/tokens/semantic.d.ts +6 -0
  106. package/package.json +31 -1
@@ -0,0 +1,78 @@
1
+
2
+ /* pointer-events: auto (#375) — see Menu/MultiSelect's matching comment:
3
+ a modal Dialog sets pointer-events: none on document.body, and a
4
+ portaled layer outside Zag's knowledge inherits that lock. */
5
+ .crisp-savedviews-panel {
6
+ font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
7
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
8
+ min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1);
9
+ border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
10
+ 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);
11
+ transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
12
+ }
13
+ .crisp-savedviews-panel[data-up] { transform-origin: bottom left; }
14
+ .crisp-savedviews-panel { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
15
+
16
+ .crisp-savedviews-row {
17
+ box-sizing: border-box; position: relative; display: flex; align-items: center; gap: var(--crisp-space-1-5);
18
+ height: var(--crisp-size-menuRow); flex-shrink: 0; padding: var(--crisp-space-1-5) var(--crisp-space-2);
19
+ border-radius: var(--crisp-radius-md); cursor: pointer; -webkit-user-select: none; user-select: none;
20
+ }
21
+ .crisp-savedviews-row:hover, .crisp-savedviews-row[data-highlighted] { background: var(--crisp-bg-raised); }
22
+ .crisp-savedviews-row-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); color: var(--crisp-fg-muted); }
23
+ .crisp-savedviews-row-label { 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); }
24
+ .crisp-savedviews-row[aria-selected="true"] .crisp-savedviews-row-label { font-weight: var(--crisp-weight-semibold); }
25
+ .crisp-savedviews-row-shared { flex-shrink: 0; display: inline-flex; color: var(--crisp-fg-muted); }
26
+
27
+ /* The kebab's own <Menu> is wrapped in a slot div (savedviews.tsx) that
28
+ stops a click from bubbling — through the REACT tree, which a portal
29
+ does not leave, even though the Menu's content is a DOM sibling under
30
+ document.body — into the row's own onClick. The slot, not the kebab
31
+ button itself, is what the row's flex layout pushes to the trailing
32
+ edge. */
33
+ .crisp-savedviews-kebab-slot { flex-shrink: 0; margin-inline-start: auto; }
34
+ /* 20px / 4px padding is a component-intrinsic size (AUTHORING.md §4b —
35
+ "a badge's 20px" is the named example), used once here: raw, not a
36
+ scale step. */
37
+ .crisp-savedviews-kebab {
38
+ box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
39
+ width: 20px; height: 20px; padding: var(--crisp-space-1); margin: 0;
40
+ border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); cursor: pointer;
41
+ opacity: 0; transition: opacity .1s ease, background-color .12s ease, color .12s ease;
42
+ }
43
+ .crisp-savedviews-row:hover .crisp-savedviews-kebab,
44
+ .crisp-savedviews-row:focus-within .crisp-savedviews-kebab,
45
+ .crisp-savedviews-kebab[data-open] { opacity: 1; }
46
+ .crisp-savedviews-kebab:hover, .crisp-savedviews-kebab[data-open] { background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); }
47
+
48
+ .crisp-savedviews {
49
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
50
+ #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
51
+ screenshot/anti-aliasing noise, not a second real colour. Reusing the
52
+ token (Lesson 3 — tokens, not raw px/hex) gets a correct,
53
+ already-established dark-mode fill for free instead of a light-only
54
+ literal; AUTHORING.md Lesson 29 is the case where that shortcut went
55
+ wrong (a flat colour that stayed visible where the reference had
56
+ none) — this is the opposite risk (a token that already flips per
57
+ theme, applied to the closest measured match). */
58
+ & .crisp-savedviews-trigger {
59
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
60
+ height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
61
+ border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
62
+ font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
63
+ cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
64
+ }
65
+ & .crisp-savedviews-trigger:hover,
66
+ & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
67
+ & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
68
+ & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
69
+ & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
70
+ }
71
+
72
+ @media (prefers-reduced-motion: reduce) {
73
+ .crisp-savedviews, .crisp-savedviews * {
74
+ animation-duration: 0.01ms !important;
75
+ animation-iteration-count: 1 !important;
76
+ transition-duration: 0.01ms !important;
77
+ }
78
+ }
@@ -0,0 +1,7 @@
1
+ import type { RecipeFn } from "../core/types";
2
+
3
+ export interface SavedviewsVariants {
4
+
5
+ }
6
+
7
+ export declare const savedviewsRecipe: RecipeFn<{ }>;
@@ -0,0 +1,80 @@
1
+ @layer crisp-components {
2
+
3
+ /* pointer-events: auto (#375) — see Menu/MultiSelect's matching comment:
4
+ a modal Dialog sets pointer-events: none on document.body, and a
5
+ portaled layer outside Zag's knowledge inherits that lock. */
6
+ .crisp-savedviews-panel {
7
+ font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
8
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
9
+ min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1);
10
+ border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
11
+ 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);
12
+ transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
13
+ }
14
+ .crisp-savedviews-panel[data-up] { transform-origin: bottom left; }
15
+ .crisp-savedviews-panel { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
16
+
17
+ .crisp-savedviews-row {
18
+ box-sizing: border-box; position: relative; display: flex; align-items: center; gap: var(--crisp-space-1-5);
19
+ height: var(--crisp-size-menuRow); flex-shrink: 0; padding: var(--crisp-space-1-5) var(--crisp-space-2);
20
+ border-radius: var(--crisp-radius-md); cursor: pointer; -webkit-user-select: none; user-select: none;
21
+ }
22
+ .crisp-savedviews-row:hover, .crisp-savedviews-row[data-highlighted] { background: var(--crisp-bg-raised); }
23
+ .crisp-savedviews-row-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); color: var(--crisp-fg-muted); }
24
+ .crisp-savedviews-row-label { 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); }
25
+ .crisp-savedviews-row[aria-selected="true"] .crisp-savedviews-row-label { font-weight: var(--crisp-weight-semibold); }
26
+ .crisp-savedviews-row-shared { flex-shrink: 0; display: inline-flex; color: var(--crisp-fg-muted); }
27
+
28
+ /* The kebab's own <Menu> is wrapped in a slot div (savedviews.tsx) that
29
+ stops a click from bubbling — through the REACT tree, which a portal
30
+ does not leave, even though the Menu's content is a DOM sibling under
31
+ document.body — into the row's own onClick. The slot, not the kebab
32
+ button itself, is what the row's flex layout pushes to the trailing
33
+ edge. */
34
+ .crisp-savedviews-kebab-slot { flex-shrink: 0; margin-inline-start: auto; }
35
+ /* 20px / 4px padding is a component-intrinsic size (AUTHORING.md §4b —
36
+ "a badge's 20px" is the named example), used once here: raw, not a
37
+ scale step. */
38
+ .crisp-savedviews-kebab {
39
+ box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
40
+ width: 20px; height: 20px; padding: var(--crisp-space-1); margin: 0;
41
+ border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); cursor: pointer;
42
+ opacity: 0; transition: opacity .1s ease, background-color .12s ease, color .12s ease;
43
+ }
44
+ .crisp-savedviews-row:hover .crisp-savedviews-kebab,
45
+ .crisp-savedviews-row:focus-within .crisp-savedviews-kebab,
46
+ .crisp-savedviews-kebab[data-open] { opacity: 1; }
47
+ .crisp-savedviews-kebab:hover, .crisp-savedviews-kebab[data-open] { background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); }
48
+
49
+ .crisp-savedviews {
50
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
51
+ #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
52
+ screenshot/anti-aliasing noise, not a second real colour. Reusing the
53
+ token (Lesson 3 — tokens, not raw px/hex) gets a correct,
54
+ already-established dark-mode fill for free instead of a light-only
55
+ literal; AUTHORING.md Lesson 29 is the case where that shortcut went
56
+ wrong (a flat colour that stayed visible where the reference had
57
+ none) — this is the opposite risk (a token that already flips per
58
+ theme, applied to the closest measured match). */
59
+ & .crisp-savedviews-trigger {
60
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
61
+ height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
62
+ border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
63
+ font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
64
+ cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
65
+ }
66
+ & .crisp-savedviews-trigger:hover,
67
+ & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
68
+ & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
69
+ & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
70
+ & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
71
+ }
72
+
73
+ @media (prefers-reduced-motion: reduce) {
74
+ .crisp-savedviews, .crisp-savedviews * {
75
+ animation-duration: 0.01ms !important;
76
+ animation-iteration-count: 1 !important;
77
+ transition-duration: 0.01ms !important;
78
+ }
79
+ }
80
+ }
package/dist/styles.css CHANGED
@@ -7,6 +7,7 @@
7
7
  --crisp-bg-raised: #f4f4f5;
8
8
  --crisp-border-default: #e4e4e7;
9
9
  --crisp-border-subtle: #eeeff1;
10
+ --crisp-border-dropzone: #e6e7ea;
10
11
  --crisp-ring-neutral: transparent;
11
12
  --crisp-ring-field: rgba(0,0,0,0.06);
12
13
  --crisp-ring-switch: #d9dade;
@@ -25,6 +26,7 @@
25
26
  --crisp-fg-muted: #71717a;
26
27
  --crisp-bg-danger: #e5484d;
27
28
  --crisp-bg-danger-solid: #ba2525;
29
+ --crisp-fg-danger: #eb3b3b;
28
30
  --crisp-stroke-field-focus: #1a1c20;
29
31
  --crisp-fg-disabled: #d1d3d8;
30
32
  --crisp-bg-neutral-weak: #f3f4f5;
@@ -59,6 +61,7 @@
59
61
  --crisp-bg-raised: #101112;
60
62
  --crisp-border-default: #212224;
61
63
  --crisp-border-subtle: #1c1d1f;
64
+ --crisp-border-dropzone: #212224;
62
65
  --crisp-ring-neutral: #212224;
63
66
  --crisp-ring-field: #212224;
64
67
  --crisp-ring-switch: #212224;
@@ -77,6 +80,7 @@
77
80
  --crisp-fg-muted: #9a9aa2;
78
81
  --crisp-bg-danger: #ff5a5f;
79
82
  --crisp-bg-danger-solid: #ba2525;
83
+ --crisp-fg-danger: #ff5a5f;
80
84
  --crisp-stroke-field-focus: #f3f4f5;
81
85
  --crisp-fg-disabled: #5b606a;
82
86
  --crisp-bg-neutral-weak: #1d2025;
@@ -97,9 +101,9 @@
97
101
  --crisp-fg-warning-soft: #e0982a;
98
102
  --crisp-fg-danger-soft: #ff5a5f;
99
103
  --crisp-fg-neutral-soft: #9a9aa2;
100
- --crisp-bg-purple-soft: #f5f0ff;
101
- --crisp-fg-purple-soft: #6238b5;
102
- --crisp-ring-purple-soft: #e8ddfe;
104
+ --crisp-bg-purple-soft: #2f1e5a;
105
+ --crisp-fg-purple-soft: #d8c4ff;
106
+ --crisp-ring-purple-soft: #45297d;
103
107
  }
104
108
 
105
109
  :root {
@@ -601,6 +605,9 @@
601
605
  /* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
602
606
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
603
607
  & th[data-sticky] { z-index: 3; }
608
+ /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
609
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
610
+ & tfoot td[data-sticky] { z-index: 2; }
604
611
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
605
612
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
606
613
  & .crisp-table-scroll[data-scrolled-x] [data-sticky-edge] { clip-path: inset(0 -38px 0 0); box-shadow: 6px 0 16px 4px rgba(0,0,0,0.12); }
@@ -618,23 +625,25 @@
618
625
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
619
626
  & .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; }
620
627
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
621
- /* 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. */
628
+ /* An async onEdit's in-flight state (#385) -- the field/picker dims and
629
+ stops accepting input rather than disappearing, so the just-typed or
630
+ just-chosen value stays visible while its commit is pending. Same
631
+ treatment Input already gives its own disabled state. */
622
632
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
623
633
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
624
- /* 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
625
- 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
626
- guarding against. */
634
+ /* The rejection's message, beside the cell's own content -- a sibling in
635
+ 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
636
+ it. Block-level: the row grows to fit it rather than floating over
637
+ the row below, so it can never end up clipped by the scroll
638
+ container's overflow the way a position:fixed escape would need guarding against. */
627
639
  & .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); }
628
- /* editor: "select"/"switch" -- always live, no separate edit mode, so
629
- the cell IS the picker: same chromeless-fill treatment as
630
- RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
640
+ /* 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
641
+ shares RecordsTable's grid, Golden Rule 0). */
631
642
  & .crisp-datatable-td--picker { padding: 0; }
632
643
  & .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); }
633
644
  & .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; }
634
645
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
635
- /* reference picker: a leading 16px avatar per option, plus a trailing
636
- "Create X" row when the consumer supplies one (#449) -- same measured
637
- values as RecordsTable's own .crisp-recordstable-refoption*. */
646
+ /* 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*. */
638
647
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
639
648
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
640
649
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
@@ -651,12 +660,7 @@
651
660
  & .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
652
661
  & 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%; }
653
662
  & .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); }
654
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
655
- & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
656
- & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
657
- & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
658
- & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
659
- & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
663
+ & .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); }
660
664
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
661
665
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
662
666
  & .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); }
@@ -669,11 +673,10 @@
669
673
  & .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)); }
670
674
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
671
675
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
672
- /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
673
- hairline doubling as the drop indicator (it tints toward the brand
674
- colour as the active drop target). The affordance + geometry are
675
- 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
676
- pointer-events implementation. */
676
+ /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
677
+ 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
678
+ draggable + dataTransfer, the same one column reorder already uses
679
+ (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
677
680
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
678
681
  & .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; }
679
682
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -1118,6 +1121,10 @@
1118
1121
  & .crisp-linechart-axis-title { margin-top: var(--crisp-space-1); padding-inline-start: var(--crisp-linechart-yaxis-width); text-align: center; font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
1119
1122
  }
1120
1123
 
1124
+ .crisp-mention-chip {
1125
+ display: inline; border-radius: var(--crisp-radius-chip); padding-inline: var(--crisp-space-1); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); font-weight: var(--crisp-weight-medium); cursor: default;
1126
+ }
1127
+
1121
1128
 
1122
1129
  /* pointer-events: auto (#375): a modal Dialog sets pointer-events: none
1123
1130
  on document.body while open (Zag re-enables it only on layers IT
@@ -1626,17 +1633,26 @@
1626
1633
  }
1627
1634
 
1628
1635
  .crisp-table {
1629
- display: block; box-sizing: border-box; background: var(--crisp-bg-surface);
1630
- & .crisp-table-scroll { overflow-x: auto; }
1636
+ display: block; box-sizing: border-box; background: var(--crisp-bg-surface); /* #554: both axes -- a consumer bounds the height via `scrollProps` (style/className on this element), and the header/footer below pin inside it exactly the way Attio's own grid does ("Both the header and
1637
+ the summary row are sticky, on the scroll container rather than the
1638
+ cells" -- docs/study/attio-table.md). Unbounded, `overflow: auto`
1639
+ shows no vertical scrollbar and nothing sticks, so this is a no-op
1640
+ for every existing consumer that never set a height. */
1641
+ & .crisp-table-scroll { overflow: auto; }
1631
1642
  & 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); }
1632
- /* #465: 12px, not crisp's old 8px -- measured on a live header cell
1633
- (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. */
1643
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell (padding: 0 12px) and independently on a body cell's text inset
1644
+ (getBoundingClientRect delta from the cell edge). Matches
1645
+ ColumnCalc's own already-correct 12px trigger inset. */
1634
1646
  & 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; }
1635
- & 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; }
1647
+ /* #554: sticky on the vertical axis, mirroring RecordsTable's own
1648
+ `.crisp-recordstable-th`. z-index 2 sits above a plain body cell (auto)
1649
+ and below a sticky-column corner cell (DataTable's `th[data-sticky]`, z-index 3), so the corner always wins. */
1650
+ & th { position: sticky; top: 0; z-index: 2; 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; }
1636
1651
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
1637
1652
  & th:last-child, & td:last-child { border-inline-end: none; }
1638
1653
  & tbody tr:last-child td { border-bottom: none; }
1639
- & tfoot td { border-top: 1px solid var(--crisp-border-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1654
+ /* #554: sticky to the bottom, mirroring `.crisp-recordstable-sum`. */
1655
+ & tfoot td { position: sticky; bottom: 0; z-index: 2; border-top: 1px solid var(--crisp-border-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1640
1656
  & caption { padding: var(--crisp-space-2) var(--crisp-space-2); text-align: start; font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); caption-side: bottom; }
1641
1657
  & td[data-align="right"], & th[data-align="right"] { text-align: end; }
1642
1658
  & td[data-align="center"], & th[data-align="center"] { text-align: center; }
@@ -2063,6 +2079,13 @@
2063
2079
  & .crisp-flowcanvas-tool-sep { width: 1px; height: 18px; background: var(--crisp-border-subtle); }
2064
2080
  }
2065
2081
 
2082
+ .crisp-formdialog {
2083
+ & .crisp-formdialog-createfoot { display: flex; align-items: center; gap: var(--crisp-space-4); }
2084
+ & .crisp-formdialog-more-label { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
2085
+ & .crisp-formdialog-error { font-size: var(--crisp-text-md); color: var(--crisp-fg-danger); margin: 0; }
2086
+ & .crisp-formdialog-form { display: grid; gap: var(--crisp-space-3); }
2087
+ }
2088
+
2066
2089
  .crisp-headerbar {
2067
2090
  box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); height: var(--crisp-size-header); flex-shrink: 0; padding: 0 var(--crisp-space-3); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2068
2091
  &[data-bordered] { border-bottom: 1px solid var(--crisp-border-subtle); }
@@ -2071,6 +2094,45 @@
2071
2094
  & .crisp-headerbar-trailing { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; }
2072
2095
  }
2073
2096
 
2097
+ .crisp-importdialog {
2098
+ display: contents; font-family: var(--crisp-font-sans);
2099
+ & .crisp-importdialog-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; }
2100
+ & .crisp-importdialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; }
2101
+ & .crisp-importdialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 100%; height: 100%; border-radius: 0; background: var(--crisp-bg-surface); box-shadow: none; transform: none; opacity: 1; transition: opacity 0.2s ease; @starting-style { opacity: 0; } }
2102
+ & .crisp-importdialog-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-6); border-bottom: 1px solid var(--crisp-border-subtle); }
2103
+ & .crisp-importdialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); margin: 0; }
2104
+ & .crisp-importdialog-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; }
2105
+ & .crisp-importdialog-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
2106
+ & .crisp-importdialog-steps { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; padding: var(--crisp-space-4) var(--crisp-space-6) 0; }
2107
+ & .crisp-importdialog-step { display: inline-flex; align-items: center; height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: default; transition: background-color .12s, color .12s; }
2108
+ & button.crisp-importdialog-step { cursor: pointer; }
2109
+ & .crisp-importdialog-step[data-active] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
2110
+ & .crisp-importdialog-step:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 1px; }
2111
+ & .crisp-importdialog-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; padding: var(--crisp-space-6); }
2112
+ & .crisp-importdialog-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-6); border-top: 1px solid var(--crisp-border-subtle); }
2113
+ & .crisp-importdialog-dropzone { box-sizing: border-box; position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); width: 100%; border: 1px dashed var(--crisp-border-dropzone); border-radius: 20px; background: var(--crisp-bg-subtle); text-align: center; transition: background-color .12s, border-color .12s; }
2114
+ & .crisp-importdialog-dropzone[data-dragging-over] { border-color: var(--crisp-bg-brand-solid); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 6%, transparent); }
2115
+ & .crisp-importdialog-dropzone[data-invalid] { border-color: var(--crisp-bg-danger); }
2116
+ & .crisp-importdialog-dropzone-title { color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); }
2117
+ & .crisp-importdialog-dropzone-or { color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); line-height: 18px; }
2118
+ & .crisp-importdialog-dropzone-choose { border: none; background: none; padding: 0; margin: 0; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-bg-brand-solid); cursor: pointer; }
2119
+ & .crisp-importdialog-dropzone-choose:hover { color: var(--crisp-bg-brand-solid-pressed); }
2120
+ & .crisp-importdialog-dropzone-choose:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; border-radius: var(--crisp-radius-sm); }
2121
+ & .crisp-importdialog-dropzone-learnmore { color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-decoration: underline; }
2122
+ & .crisp-importdialog-dropzone-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
2123
+ & .crisp-importdialog-dropzone-errors { margin: 0; padding: 0; list-style: none; }
2124
+ & .crisp-importdialog-dropzone-errors:empty { position: absolute; }
2125
+ & .crisp-importdialog-dropzone-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
2126
+ }
2127
+
2128
+ @media (prefers-reduced-motion: reduce) {
2129
+ .crisp-importdialog, .crisp-importdialog * {
2130
+ animation-duration: 0.01ms !important;
2131
+ animation-iteration-count: 1 !important;
2132
+ transition-duration: 0.01ms !important;
2133
+ }
2134
+ }
2135
+
2074
2136
  .crisp-inbox {
2075
2137
  display: flex; flex-direction: column; box-sizing: border-box; width: 380px; max-height: 588px; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); overflow: hidden; 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); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2076
2138
  & .crisp-inbox-head { padding: var(--crisp-space-3) var(--crisp-space-3) 0; }
@@ -2708,6 +2770,85 @@
2708
2770
  }
2709
2771
  }
2710
2772
 
2773
+
2774
+ /* pointer-events: auto (#375) — see Menu/MultiSelect's matching comment:
2775
+ a modal Dialog sets pointer-events: none on document.body, and a
2776
+ portaled layer outside Zag's knowledge inherits that lock. */
2777
+ .crisp-savedviews-panel {
2778
+ font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2779
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
2780
+ min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1);
2781
+ border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
2782
+ 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);
2783
+ transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
2784
+ }
2785
+ .crisp-savedviews-panel[data-up] { transform-origin: bottom left; }
2786
+ .crisp-savedviews-panel { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
2787
+
2788
+ .crisp-savedviews-row {
2789
+ box-sizing: border-box; position: relative; display: flex; align-items: center; gap: var(--crisp-space-1-5);
2790
+ height: var(--crisp-size-menuRow); flex-shrink: 0; padding: var(--crisp-space-1-5) var(--crisp-space-2);
2791
+ border-radius: var(--crisp-radius-md); cursor: pointer; -webkit-user-select: none; user-select: none;
2792
+ }
2793
+ .crisp-savedviews-row:hover, .crisp-savedviews-row[data-highlighted] { background: var(--crisp-bg-raised); }
2794
+ .crisp-savedviews-row-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); color: var(--crisp-fg-muted); }
2795
+ .crisp-savedviews-row-label { 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); }
2796
+ .crisp-savedviews-row[aria-selected="true"] .crisp-savedviews-row-label { font-weight: var(--crisp-weight-semibold); }
2797
+ .crisp-savedviews-row-shared { flex-shrink: 0; display: inline-flex; color: var(--crisp-fg-muted); }
2798
+
2799
+ /* The kebab's own <Menu> is wrapped in a slot div (savedviews.tsx) that
2800
+ stops a click from bubbling — through the REACT tree, which a portal
2801
+ does not leave, even though the Menu's content is a DOM sibling under
2802
+ document.body — into the row's own onClick. The slot, not the kebab
2803
+ button itself, is what the row's flex layout pushes to the trailing
2804
+ edge. */
2805
+ .crisp-savedviews-kebab-slot { flex-shrink: 0; margin-inline-start: auto; }
2806
+ /* 20px / 4px padding is a component-intrinsic size (AUTHORING.md §4b —
2807
+ "a badge's 20px" is the named example), used once here: raw, not a
2808
+ scale step. */
2809
+ .crisp-savedviews-kebab {
2810
+ box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
2811
+ width: 20px; height: 20px; padding: var(--crisp-space-1); margin: 0;
2812
+ border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); cursor: pointer;
2813
+ opacity: 0; transition: opacity .1s ease, background-color .12s ease, color .12s ease;
2814
+ }
2815
+ .crisp-savedviews-row:hover .crisp-savedviews-kebab,
2816
+ .crisp-savedviews-row:focus-within .crisp-savedviews-kebab,
2817
+ .crisp-savedviews-kebab[data-open] { opacity: 1; }
2818
+ .crisp-savedviews-kebab:hover, .crisp-savedviews-kebab[data-open] { background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); }
2819
+
2820
+ .crisp-savedviews {
2821
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
2822
+ #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
2823
+ screenshot/anti-aliasing noise, not a second real colour. Reusing the
2824
+ token (Lesson 3 — tokens, not raw px/hex) gets a correct,
2825
+ already-established dark-mode fill for free instead of a light-only
2826
+ literal; AUTHORING.md Lesson 29 is the case where that shortcut went
2827
+ wrong (a flat colour that stayed visible where the reference had
2828
+ none) — this is the opposite risk (a token that already flips per
2829
+ theme, applied to the closest measured match). */
2830
+ & .crisp-savedviews-trigger {
2831
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
2832
+ height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
2833
+ border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
2834
+ font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
2835
+ cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
2836
+ }
2837
+ & .crisp-savedviews-trigger:hover,
2838
+ & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
2839
+ & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
2840
+ & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
2841
+ & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
2842
+ }
2843
+
2844
+ @media (prefers-reduced-motion: reduce) {
2845
+ .crisp-savedviews, .crisp-savedviews * {
2846
+ animation-duration: 0.01ms !important;
2847
+ animation-iteration-count: 1 !important;
2848
+ transition-duration: 0.01ms !important;
2849
+ }
2850
+ }
2851
+
2711
2852
  .crisp-settingssection {
2712
2853
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans);
2713
2854
  & .crisp-settingssection-head { display: flex; align-items: flex-start; gap: var(--crisp-space-2); }
@@ -2830,6 +2971,58 @@
2830
2971
  }
2831
2972
  }
2832
2973
 
2974
+ .crisp-taskstable {
2975
+ display: flex; flex-direction: column; min-height: 0; box-sizing: border-box; font-family: var(--crisp-font-sans); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2976
+ & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
2977
+ & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
2978
+ & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
2979
+ & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; }
2980
+ & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
2981
+ & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
2982
+ & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
2983
+ & .crisp-taskstable-check:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2984
+ & .crisp-taskstable-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); }
2985
+ & .crisp-taskstable-due { font-size: var(--crisp-text-lg); color: var(--crisp-fg-muted); white-space: nowrap; }
2986
+ & .crisp-taskstable-due[data-overdue] { color: var(--crisp-fg-danger); }
2987
+ & .crisp-taskstable-empty { padding: var(--crisp-space-8) var(--crisp-space-4); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
2988
+ }
2989
+
2990
+ @media (prefers-reduced-motion: reduce) {
2991
+ .crisp-taskstable, .crisp-taskstable * {
2992
+ animation-duration: 0.01ms !important;
2993
+ animation-iteration-count: 1 !important;
2994
+ transition-duration: 0.01ms !important;
2995
+ }
2996
+ }
2997
+
2998
+ .crisp-themepicker {
2999
+ display: flex; gap: var(--crisp-space-2); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3000
+ & .crisp-themepicker-option { box-sizing: border-box; display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); width: 224px; padding: var(--crisp-space-1); border: 1px solid var(--crisp-border-subtle); border-radius: 16px; background: var(--crisp-bg-surface); cursor: pointer; font-family: inherit; }
3001
+ & .crisp-themepicker-option[data-selected] { border-color: var(--crisp-bg-brand-solid); }
3002
+ & .crisp-themepicker-preview { box-sizing: border-box; display: flex; width: 100%; height: 114px; padding: var(--crisp-space-2); gap: var(--crisp-space-1-5); border-radius: var(--crisp-radius-xl); overflow: hidden; }
3003
+ & .crisp-themepicker-preview-side { flex-shrink: 0; width: 28%; display: flex; flex-direction: column; gap: var(--crisp-space-1); }
3004
+ & .crisp-themepicker-preview-bar { height: 4px; border-radius: var(--crisp-radius-full); }
3005
+ & .crisp-themepicker-preview-bar:nth-child(1) { width: 70%; background: var(--crisp-bg-brand-solid); }
3006
+ & .crisp-themepicker-preview-bar:nth-child(2) { width: 90%; }
3007
+ & .crisp-themepicker-preview-bar:nth-child(3) { width: 60%; }
3008
+ & .crisp-themepicker-preview-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1-5); border-radius: var(--crisp-radius-md); }
3009
+ /* Light / System (default) swatch colours. */
3010
+ & .crisp-themepicker-preview[data-swatch="light"], & .crisp-themepicker-preview[data-swatch="system"] { border: 1px solid #eeeff1; background: #fbfbfb; }
3011
+ & [data-swatch="light"] .crisp-themepicker-preview-bar:not(:nth-child(1)), & [data-swatch="system"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #eeeff1; }
3012
+ & [data-swatch="light"] .crisp-themepicker-preview-main, & [data-swatch="system"] .crisp-themepicker-preview-main { background: #ffffff; }
3013
+ & [data-swatch="light"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar, & [data-swatch="system"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar { background: #eeeff1; }
3014
+ /* Dark swatch colours -- always for "dark", and for "system" only when the OS itself reports dark. */
3015
+ & .crisp-themepicker-preview[data-swatch="dark"] { border: 1px solid #1c1d1f; background: #0e0f10; }
3016
+ & [data-swatch="dark"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #1c1d1f; }
3017
+ & [data-swatch="dark"] .crisp-themepicker-preview-main { background: #0a0a0a; }
3018
+ & [data-swatch="dark"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar { background: #1c1d1f; }
3019
+ @media (prefers-color-scheme: dark) { & .crisp-themepicker-preview[data-swatch="system"] { border: 1px solid #1c1d1f; background: #0e0f10; } & [data-swatch="system"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #1c1d1f; } & [data-swatch="system"] .crisp-themepicker-preview-main { background: #0a0a0a; } & [data-swatch="system"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar { background: #1c1d1f; } }
3020
+ & .crisp-themepicker-label { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
3021
+ & .crisp-themepicker-option[data-selected] .crisp-themepicker-label { color: var(--crisp-fg-primary); }
3022
+ & .crisp-themepicker-radio { flex-shrink: 0; width: 14px; height: 14px; border-radius: var(--crisp-radius-full); border: 1px solid var(--crisp-border-default); box-sizing: border-box; }
3023
+ & .crisp-themepicker-option[data-selected] .crisp-themepicker-radio { border: 4px solid var(--crisp-bg-brand-solid); }
3024
+ }
3025
+
2833
3026
  .crisp-trialbanner {
2834
3027
  display: flex; align-items: center; gap: var(--crisp-space-1-5); box-sizing: border-box; min-height: 28px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-lg); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2835
3028
  & .crisp-trialbanner-icon { display: inline-flex; flex-shrink: 0; color: var(--crisp-fg-muted); }