@ctrl-ui/react 0.8.0 → 0.8.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 (62) hide show
  1. package/README.md +1 -0
  2. package/dist/blocks/coding-agent.js +3 -1
  3. package/dist/blocks/file-explorer.js +3 -1
  4. package/dist/blocks/settings.js +3 -1
  5. package/dist/control-props.d.ts +2 -1
  6. package/dist/email/email.d.ts +28 -0
  7. package/dist/email/email.js +79 -0
  8. package/dist/email/length.d.ts +4 -0
  9. package/dist/email/length.js +34 -0
  10. package/dist/email/templates.d.ts +51 -0
  11. package/dist/email/templates.js +228 -0
  12. package/dist/email/theme.d.ts +35 -0
  13. package/dist/email/theme.js +113 -0
  14. package/dist/extensions/create-track-highlight.d.ts +0 -2
  15. package/dist/extensions/create-track-highlight.js +49 -8
  16. package/dist/extensions/supports-anchor-transitions.d.ts +4 -0
  17. package/dist/extensions/supports-anchor-transitions.js +31 -0
  18. package/dist/extensions/track-highlight.d.ts +0 -6
  19. package/dist/extensions/track-highlight.js +25 -9
  20. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  21. package/dist/knob-contracts/button-knobs.js +4 -1
  22. package/dist/knob-contracts/progressive-blur-knobs.d.ts +5 -0
  23. package/dist/knob-contracts/progressive-blur-knobs.js +9 -0
  24. package/dist/scripts/contrast-eval.d.ts +46 -0
  25. package/dist/scripts/contrast-eval.js +327 -0
  26. package/dist/skin.config.js +9 -1
  27. package/dist/skin.d.ts +11 -9
  28. package/dist/skin.js +0 -1
  29. package/dist/styles/index.css +3 -1
  30. package/dist/styles/recipes/button-group.css +46 -0
  31. package/dist/styles/recipes/button.css +53 -5
  32. package/dist/styles/recipes/progressive-blur.css +145 -0
  33. package/dist/styles/recipes/scroll-area.css +140 -5
  34. package/dist/styles/recipes/sidebar-menu.css +156 -0
  35. package/dist/styles/recipes/sidebar.css +25 -69
  36. package/dist/styles/recipes/table-of-contents.css +1 -1
  37. package/dist/styles/recipes/track-highlight.css +126 -2
  38. package/dist/styles/recipes/tree.css +4 -0
  39. package/dist/ui/button-group.d.ts +3 -1
  40. package/dist/ui/button-group.js +21 -18
  41. package/dist/ui/button.d.ts +3 -1
  42. package/dist/ui/button.js +13 -4
  43. package/dist/ui/checkbox-group.d.ts +5 -3
  44. package/dist/ui/checkbox-group.js +23 -6
  45. package/dist/ui/combobox.d.ts +2 -0
  46. package/dist/ui/dropdown-menu.d.ts +28 -21
  47. package/dist/ui/dropdown-menu.js +112 -13
  48. package/dist/ui/progressive-blur.d.ts +12 -0
  49. package/dist/ui/progressive-blur.js +47 -0
  50. package/dist/ui/scroll-area.d.ts +4 -1
  51. package/dist/ui/scroll-area.js +27 -4
  52. package/dist/ui/sidebar-menu.d.ts +49 -0
  53. package/dist/ui/sidebar-menu.js +160 -0
  54. package/dist/ui/sidebar-provider.d.ts +28 -0
  55. package/dist/ui/sidebar-provider.js +80 -0
  56. package/dist/ui/sidebar.d.ts +4 -61
  57. package/dist/ui/sidebar.js +23 -202
  58. package/dist/ui/table-of-contents.js +3 -11
  59. package/dist/ui/toggle.d.ts +3 -2
  60. package/dist/ui/toggle.js +22 -10
  61. package/dist/ui/tree.js +13 -19
  62. package/package.json +5 -1
@@ -1,4 +1,12 @@
1
1
  //#region src/skin.config.tsx
2
- const skin = { id: "refined" };
2
+ const skin = {
3
+ id: "refined",
4
+ indicators: {
5
+ sidebar: "hover",
6
+ "button-group": "hover",
7
+ "toggle-group": "hover",
8
+ "checkbox-group": "hover"
9
+ }
10
+ };
3
11
  //#endregion
4
12
  export { skin };
package/dist/skin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SelectionIndicator } from "./control-props.js";
1
+ import { HoverIndicator, SelectionIndicator } from "./control-props.js";
2
2
  import { ControlTone, ControlVariant } from "./control-variants.js";
3
3
  import { ReactNode } from "react";
4
4
  //#region src/skin.d.ts
@@ -28,6 +28,13 @@ type SkinAdornmentContexts = {
28
28
  type SkinAdornmentScope = keyof SkinAdornmentContexts;
29
29
  type SkinAdornmentPart<Scope extends SkinAdornmentScope> = keyof SkinAdornmentContexts[Scope];
30
30
  type AdornmentEntry<Ctx> = ReactNode | ((ctx: Ctx) => ReactNode);
31
+ type SkinIndicators = {
32
+ sidebar?: SelectionIndicator | HoverIndicator;
33
+ tree?: SelectionIndicator;
34
+ "button-group"?: HoverIndicator;
35
+ "toggle-group"?: HoverIndicator;
36
+ "checkbox-group"?: HoverIndicator;
37
+ };
31
38
  type ControlUiSkin = {
32
39
  /** Scopes theme.css/skin.css via data-skin; components stamp it on portal containers. */
33
40
  id: string;
@@ -37,11 +44,7 @@ type ControlUiSkin = {
37
44
  colorScheme?: "light" | "dark";
38
45
  /** Geometry, not slot restyle — padding, gap, rounding, and shadow across sidebar gap/container/inner. explicit `variant` prop wins; undefined keeps "sidebar". */
39
46
  sidebarLayout?: SidebarLayout;
40
- /** App-wide choice: sidebars and trees all glide or none do. Explicit `indicator` still wins; undefined also skips highlight engine's lazy chunk. */
41
- indicators?: {
42
- sidebar?: SelectionIndicator;
43
- tree?: SelectionIndicator;
44
- };
47
+ indicators?: SkinIndicators;
45
48
  /** Any CSS length. Seeds --sidebar-width; caller's own value still wins. Undefined keeps shadcn's 16rem. */
46
49
  sidebarWidth?: string;
47
50
  /** effects.css keys off data-effects ancestor attribute. top-shine is CSS-only; ripple needs runtime's document pointer listener. */
@@ -57,8 +60,7 @@ declare function activeSkin(): ControlUiSkin;
57
60
  declare function skinId(): string;
58
61
  /** Sidebar falls back: variant prop → this → "sidebar". */
59
62
  declare function skinSidebarLayout(): SidebarLayout | undefined;
60
- /** Component falls back: indicator prop → this → "none". */
61
- declare function skinIndicator(component: "sidebar" | "tree"): SelectionIndicator | undefined;
63
+ declare function skinIndicator<Component extends keyof SkinIndicators>(component: Component): SkinIndicators[Component];
62
64
  /** SidebarProvider seeds --sidebar-width from this; caller-provided value still wins. */
63
65
  declare function skinSidebarWidth(): string | undefined;
64
66
  /** Portals stamp result next to data-skin; ControlEffectsRuntime mirrors it on <html>. */
@@ -66,4 +68,4 @@ declare function skinEffects(): string | undefined;
66
68
  declare function skinAdornment<Scope extends SkinAdornmentScope, Part extends SkinAdornmentPart<Scope>>(scope: Scope, part: Part, ctx: SkinAdornmentContexts[Scope][Part]): ReactNode | undefined;
67
69
  declare function hasSkinAdornment<Scope extends SkinAdornmentScope, Part extends SkinAdornmentPart<Scope>>(scope: Scope, part: Part): boolean;
68
70
  //#endregion
69
- export { ControlEffect, ControlEffectValue, ControlUiSkin, SidebarLayout, SkinAdornmentContexts, SkinAdornmentPart, SkinAdornmentScope, activeSkin, controlEffectsAttribute, hasSkinAdornment, skinAdornment, skinEffects, skinId, skinIndicator, skinSidebarLayout, skinSidebarWidth };
71
+ export { ControlEffect, ControlEffectValue, ControlUiSkin, SidebarLayout, SkinAdornmentContexts, SkinAdornmentPart, SkinAdornmentScope, SkinIndicators, activeSkin, controlEffectsAttribute, hasSkinAdornment, skinAdornment, skinEffects, skinId, skinIndicator, skinSidebarLayout, skinSidebarWidth };
package/dist/skin.js CHANGED
@@ -36,7 +36,6 @@ function skinId() {
36
36
  function skinSidebarLayout() {
37
37
  return skin.sidebarLayout;
38
38
  }
39
- /** Component falls back: indicator prop → this → "none". */
40
39
  function skinIndicator(component) {
41
40
  return skin.indicators?.[component];
42
41
  }
@@ -3,6 +3,7 @@
3
3
  @import "./recipes/button.css";
4
4
  @import "./recipes/action-bar.css";
5
5
  @import "./recipes/collapsible.css";
6
+ @import "./recipes/progressive-blur.css";
6
7
  @import "./recipes/scroll-area.css";
7
8
  @import "./recipes/activity.css";
8
9
  @import "./recipes/alert.css";
@@ -16,6 +17,7 @@
16
17
  @import "./recipes/field-parts.css";
17
18
  @import "./recipes/avatar.css";
18
19
  @import "./recipes/badge.css";
20
+ @import "./recipes/track-highlight.css";
19
21
  @import "./recipes/button-group.css";
20
22
  @import "./recipes/calendar.css";
21
23
  @import "./recipes/card.css";
@@ -30,8 +32,8 @@
30
32
  @import "./recipes/code.css";
31
33
  @import "./recipes/code-block-editor.css";
32
34
  @import "./recipes/code-diff.css";
33
- @import "./recipes/track-highlight.css";
34
35
  @import "./recipes/sidebar.css";
36
+ @import "./recipes/sidebar-menu.css";
35
37
  @import "./recipes/color-picker.css";
36
38
  @import "./recipes/color-picker-details.css";
37
39
  @import "./recipes/color-picker-values.css";
@@ -5,6 +5,7 @@
5
5
  --cui-button-group-text-foreground: var(--muted-foreground);
6
6
  --cui-button-group-separator-background: var(--border);
7
7
  }
8
+
8
9
  :where([data-control-family="button-group"][data-slot="text"]) {
9
10
  block-size: var(--control-h-md);
10
11
  border: 1px solid var(--border);
@@ -37,6 +38,51 @@
37
38
  :where([data-control-family="button-group"][data-slot="separator"]) {
38
39
  background: var(--cui-button-group-separator-background);
39
40
  }
41
+ :where([data-control-family="button-group"][data-slot="root"] > :not([data-control-family="track-highlight"])) {
42
+ position: relative;
43
+ }
44
+
45
+ :where(
46
+ [data-control-family="button-group"][data-orientation="horizontal"]
47
+ > :not([data-control-family="track-highlight"])
48
+ + :not([data-control-family="track-highlight"])
49
+ ) {
50
+ margin-inline-start: -1px;
51
+ border-start-start-radius: 0;
52
+ border-end-start-radius: 0;
53
+ }
54
+
55
+ :where([data-control-family="button-group"][data-orientation="horizontal"] > :has(~ :not([data-control-family="track-highlight"]))) {
56
+ border-start-end-radius: 0;
57
+ border-end-end-radius: 0;
58
+ }
59
+
60
+ :where(
61
+ [data-control-family="button-group"][data-orientation="vertical"]
62
+ > :not([data-control-family="track-highlight"])
63
+ + :not([data-control-family="track-highlight"])
64
+ ) {
65
+ margin-block-start: -1px;
66
+ border-start-start-radius: 0;
67
+ border-start-end-radius: 0;
68
+ }
69
+
70
+ :where([data-control-family="button-group"][data-orientation="vertical"] > :has(~ :not([data-control-family="track-highlight"]))) {
71
+ border-end-start-radius: 0;
72
+ border-end-end-radius: 0;
73
+ }
74
+
75
+ :where([data-control-family="button-group"][data-slot="root"] > :focus-within) {
76
+ z-index: 1;
77
+ }
78
+
79
+ :where([data-control-family="button-group"][data-slot="root"] > [data-active="true"]) {
80
+ z-index: 2;
81
+ }
82
+
83
+ :where([data-control-family="button-group"][data-slot="root"] > [data-active="true"]:focus-within) {
84
+ z-index: 3;
85
+ }
40
86
  }
41
87
 
42
88
  @property --cui-button-group-text-radius {
@@ -20,6 +20,9 @@
20
20
  --cui-button-shadow: 0 0 transparent;
21
21
  --cui-button-hover-shadow: var(--cui-button-shadow);
22
22
  --cui-button-press-shadow: var(--cui-button-hover-shadow);
23
+ --cui-button-open-background: var(--cui-button-hover-background);
24
+ --cui-button-open-foreground: var(--cui-button-hover-foreground);
25
+ --cui-button-open-shadow: var(--cui-button-shadow);
23
26
  border-radius: var(--cui-button-radius);
24
27
  background-color: var(--cui-button-background);
25
28
  background-image: var(--cui-button-background-image);
@@ -33,7 +36,6 @@
33
36
  transition-duration: var(--duration-fast);
34
37
  }
35
38
 
36
- /* Keyed on the family alone so every trigger owns the knob. */
37
39
  :where([data-control-family="button"]) {
38
40
  gap: var(--cui-button-gap);
39
41
  }
@@ -68,7 +70,6 @@
68
70
  padding-inline: 0;
69
71
  }
70
72
 
71
- /* Default clamp for unclassed icons (lucide ships width/height 24); zero specificity, so any utility on the svg wins. */
72
73
  :where([data-control-family="button"][data-control="true"] svg) {
73
74
  pointer-events: none;
74
75
  flex-shrink: 0;
@@ -110,9 +111,8 @@
110
111
  --cui-button-active-hover-background: oklch(from var(--primary) l c h / 0.1);
111
112
  }
112
113
 
113
- :where([data-control-family="button"]:is([data-variant="surface"], [data-variant="ghost"], [data-variant="quiet"])[data-popup-open]) {
114
- background-color: oklch(from var(--foreground) l c h / 0.07);
115
- color: var(--cui-button-hover-foreground);
114
+ :where([data-control-family="button"]:is([data-variant="surface"], [data-variant="ghost"], [data-variant="quiet"])) {
115
+ --cui-button-open-background: oklch(from var(--foreground) l c h / 0.07);
116
116
  }
117
117
 
118
118
  :where([data-control-family="button"][data-tone="primary"]:not([data-variant="solid"])) {
@@ -147,6 +147,12 @@
147
147
  }
148
148
  }
149
149
 
150
+ :where([data-control-family="button"][data-popup-open]) {
151
+ background-color: var(--cui-button-open-background);
152
+ color: var(--cui-button-open-foreground);
153
+ box-shadow: var(--cui-button-open-shadow);
154
+ }
155
+
150
156
  :where([data-control-family="button"][data-active="true"]) {
151
157
  background-color: var(--cui-button-active-background);
152
158
  box-shadow: var(--cui-button-active-shadow);
@@ -160,6 +166,30 @@
160
166
  }
161
167
  }
162
168
 
169
+ @scope ([data-track="hover"]) to ([data-track]) {
170
+ @media (hover: hover) {
171
+ :where(
172
+ [data-track="hover"] [data-control-family="button"][data-track-item]:hover:not([data-active="true"], [data-popup-open], :active)
173
+ ) {
174
+ background-color: var(--cui-button-background);
175
+ box-shadow: var(--cui-button-shadow);
176
+ }
177
+
178
+ :where(
179
+ [data-track="hover"] [data-control-family="button"][data-track-item][data-active="true"]:hover:not([data-popup-open], :active)
180
+ ) {
181
+ background-color: var(--cui-button-active-background);
182
+ }
183
+ }
184
+ }
185
+
186
+ @scope ([data-track="slide"]) to ([data-track]) {
187
+ :where([data-track="slide"] [data-control-family="button"][data-track-item]:not([data-popup-open], :active)) {
188
+ background-color: var(--cui-button-background);
189
+ box-shadow: var(--cui-button-shadow);
190
+ }
191
+ }
192
+
163
193
  :where([data-control-family="button"][data-control="true"]:active) {
164
194
  background-color: var(--cui-button-press-background);
165
195
  box-shadow: var(--cui-button-press-shadow);
@@ -303,3 +333,21 @@
303
333
  syntax: "*";
304
334
  inherits: true;
305
335
  }
336
+
337
+ @property --cui-button-open-background {
338
+ syntax: "<color>";
339
+ inherits: true;
340
+ initial-value: transparent;
341
+ }
342
+
343
+ @property --cui-button-open-foreground {
344
+ syntax: "<color>";
345
+ inherits: true;
346
+ initial-value: transparent;
347
+ }
348
+
349
+ @property --cui-button-open-shadow {
350
+ syntax: "*";
351
+ inherits: true;
352
+ initial-value: none;
353
+ }
@@ -0,0 +1,145 @@
1
+ @layer components {
2
+ :where([data-control-family="progressive-blur"][data-slot="root"]) {
3
+ --cui-progressive-blur-size: 3rem;
4
+ --cui-progressive-blur-backdrop-blur: 16px;
5
+ --cui-progressive-blur-transition-duration: var(--duration-base);
6
+ --cui-progressive-blur-transition-delay: calc(var(--cui-progressive-blur-transition-duration) * 0.06);
7
+ --_progressive-blur-direction: to bottom;
8
+ display: none;
9
+ position: absolute;
10
+ z-index: 1;
11
+ pointer-events: none;
12
+ user-select: none;
13
+ }
14
+
15
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-visible]) {
16
+ --_progressive-blur-visibility: visible;
17
+ --_progressive-blur-enter: 1;
18
+ }
19
+
20
+ :where([data-control-family="progressive-blur"][data-slot="root"]:is([data-side="top"], [data-side="bottom"])) {
21
+ inset-inline: 0;
22
+ height: min(50%, max(0px, var(--cui-progressive-blur-size)));
23
+ }
24
+
25
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="top"]) {
26
+ top: 0;
27
+ --_progressive-blur-direction: to top;
28
+ }
29
+
30
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="bottom"]) {
31
+ bottom: 0;
32
+ }
33
+
34
+ :where([data-control-family="progressive-blur"][data-slot="root"]:is([data-side="inline-start"], [data-side="inline-end"])) {
35
+ inset-block: 0;
36
+ width: min(50%, max(0px, var(--cui-progressive-blur-size)));
37
+ }
38
+
39
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="inline-start"]) {
40
+ inset-inline-start: 0;
41
+ --_progressive-blur-direction: to left;
42
+ }
43
+
44
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="inline-end"]) {
45
+ inset-inline-end: 0;
46
+ --_progressive-blur-direction: to right;
47
+ }
48
+
49
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="inline-start"]:dir(rtl)) {
50
+ --_progressive-blur-direction: to right;
51
+ }
52
+
53
+ :where([data-control-family="progressive-blur"][data-slot="root"][data-side="inline-end"]:dir(rtl)) {
54
+ --_progressive-blur-direction: to left;
55
+ }
56
+
57
+ @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) and (mask-image: linear-gradient(black, transparent)) {
58
+ :where([data-control-family="progressive-blur"][data-slot="root"]) {
59
+ display: block;
60
+ }
61
+ }
62
+
63
+ :where([data-control-family="progressive-blur"][data-slot="layer"]) {
64
+ --_progressive-blur-index: 0;
65
+ --_progressive-blur-divisor: 32;
66
+ --_progressive-blur-duration: calc(var(--cui-progressive-blur-transition-duration) * (0.65 + var(--_progressive-blur-enter, 0) * 0.35));
67
+ position: absolute;
68
+ inset: 0;
69
+ opacity: var(--_progressive-blur-enter, 0);
70
+ visibility: var(--_progressive-blur-visibility, hidden);
71
+ -webkit-backdrop-filter: blur(max(0px, calc(var(--cui-progressive-blur-backdrop-blur) / var(--_progressive-blur-divisor))));
72
+ backdrop-filter: blur(max(0px, calc(var(--cui-progressive-blur-backdrop-blur) / var(--_progressive-blur-divisor))));
73
+ mask-image: linear-gradient(
74
+ var(--_progressive-blur-direction),
75
+ oklch(0 0 0 / 0) calc(var(--_progressive-blur-index) * 100% / 7),
76
+ oklch(0 0 0) calc((var(--_progressive-blur-index) + 1) * 100% / 7),
77
+ oklch(0 0 0) calc((var(--_progressive-blur-index) + 2) * 100% / 7),
78
+ oklch(0 0 0 / 0) calc((var(--_progressive-blur-index) + 3) * 100% / 7)
79
+ );
80
+ transition:
81
+ opacity var(--_progressive-blur-duration) var(--ease-standard)
82
+ calc(var(--_progressive-blur-enter, 0) * var(--_progressive-blur-index) * var(--cui-progressive-blur-transition-delay)),
83
+ visibility calc(var(--duration-fast) * 0) var(--ease-standard)
84
+ calc((1 - var(--_progressive-blur-enter, 0)) * var(--_progressive-blur-duration));
85
+
86
+ @starting-style {
87
+ opacity: 0;
88
+ }
89
+ }
90
+
91
+ :where([data-control-family="progressive-blur"][data-slot="layer"]:nth-child(2)) {
92
+ --_progressive-blur-index: 1;
93
+ --_progressive-blur-divisor: 16;
94
+ }
95
+
96
+ :where([data-control-family="progressive-blur"][data-slot="layer"]:nth-child(3)) {
97
+ --_progressive-blur-index: 2;
98
+ --_progressive-blur-divisor: 8;
99
+ }
100
+
101
+ :where([data-control-family="progressive-blur"][data-slot="layer"]:nth-child(4)) {
102
+ --_progressive-blur-index: 3;
103
+ --_progressive-blur-divisor: 4;
104
+ }
105
+
106
+ :where([data-control-family="progressive-blur"][data-slot="layer"]:nth-child(5)) {
107
+ --_progressive-blur-index: 4;
108
+ --_progressive-blur-divisor: 2;
109
+ }
110
+
111
+ :where([data-control-family="progressive-blur"][data-slot="layer"]:nth-child(6)) {
112
+ --_progressive-blur-index: 5;
113
+ --_progressive-blur-divisor: 1;
114
+ }
115
+
116
+ @media (prefers-reduced-transparency: reduce), (forced-colors: active) {
117
+ :where([data-control-family="progressive-blur"][data-slot="root"]) {
118
+ display: none;
119
+ }
120
+ }
121
+ }
122
+
123
+ @property --cui-progressive-blur-size {
124
+ syntax: "<length-percentage>";
125
+ inherits: true;
126
+ initial-value: 0px;
127
+ }
128
+
129
+ @property --cui-progressive-blur-backdrop-blur {
130
+ syntax: "<length>";
131
+ inherits: true;
132
+ initial-value: 0px;
133
+ }
134
+
135
+ @property --cui-progressive-blur-transition-duration {
136
+ syntax: "<time>";
137
+ inherits: true;
138
+ initial-value: 0s;
139
+ }
140
+
141
+ @property --cui-progressive-blur-transition-delay {
142
+ syntax: "<time>";
143
+ inherits: true;
144
+ initial-value: 0s;
145
+ }
@@ -5,6 +5,121 @@
5
5
  --cui-scroll-area-corner-background: transparent;
6
6
  }
7
7
 
8
+ :where([data-control-family="scroll-area"][data-slot="root"] > [data-scroll-area-viewport]) {
9
+ position: relative;
10
+ z-index: 0;
11
+ border-radius: inherit;
12
+ outline: none;
13
+ transition:
14
+ --_scroll-area-fade-top var(--duration-fast) var(--ease-standard),
15
+ --_scroll-area-fade-bottom var(--duration-fast) var(--ease-standard),
16
+ --_scroll-area-fade-left var(--duration-fast) var(--ease-standard),
17
+ --_scroll-area-fade-right var(--duration-fast) var(--ease-standard);
18
+ }
19
+
20
+ :where(
21
+ [data-control-family="scroll-area"][data-slot="root"][data-mask]:is(
22
+ [data-overflow-x-start],
23
+ [data-overflow-x-end],
24
+ [data-overflow-y-start],
25
+ [data-overflow-y-end]
26
+ )
27
+ > [data-scroll-area-viewport]
28
+ ) {
29
+ mask-image:
30
+ linear-gradient(
31
+ to bottom,
32
+ oklch(0 0 0 / 0),
33
+ oklch(0 0 0) min(50%, var(--_scroll-area-fade-top)),
34
+ oklch(0 0 0) calc(100% - min(50%, var(--_scroll-area-fade-bottom))),
35
+ oklch(0 0 0 / 0)
36
+ ),
37
+ linear-gradient(
38
+ to right,
39
+ oklch(0 0 0 / 0),
40
+ oklch(0 0 0) min(50%, var(--_scroll-area-fade-left)),
41
+ oklch(0 0 0) calc(100% - min(50%, var(--_scroll-area-fade-right))),
42
+ oklch(0 0 0 / 0)
43
+ );
44
+ mask-composite: intersect;
45
+ }
46
+
47
+ :where(
48
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-y-start]:not([data-lock-axis="y"], [data-lock-axis="both"])
49
+ > [data-scroll-area-viewport]
50
+ ) {
51
+ --_scroll-area-fade-top: var(--scroll-fade-size);
52
+ }
53
+
54
+ :where(
55
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-y-end]:not([data-lock-axis="y"], [data-lock-axis="both"])
56
+ > [data-scroll-area-viewport]
57
+ ) {
58
+ --_scroll-area-fade-bottom: var(--scroll-fade-size);
59
+ }
60
+
61
+ :where(
62
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-start]:not([data-lock-axis="x"], [data-lock-axis="both"]):dir(ltr)
63
+ > [data-scroll-area-viewport]
64
+ ),
65
+ :where(
66
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-end]:not([data-lock-axis="x"], [data-lock-axis="both"]):dir(rtl)
67
+ > [data-scroll-area-viewport]
68
+ ) {
69
+ --_scroll-area-fade-left: var(--scroll-fade-size);
70
+ }
71
+
72
+ :where(
73
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-end]:not([data-lock-axis="x"], [data-lock-axis="both"]):dir(ltr)
74
+ > [data-scroll-area-viewport]
75
+ ),
76
+ :where(
77
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-start]:not([data-lock-axis="x"], [data-lock-axis="both"]):dir(rtl)
78
+ > [data-scroll-area-viewport]
79
+ ) {
80
+ --_scroll-area-fade-right: var(--scroll-fade-size);
81
+ }
82
+
83
+ :where(
84
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-y-start] > [data-control-family="progressive-blur"][data-side="top"]
85
+ ),
86
+ :where(
87
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-y-end]
88
+ > [data-control-family="progressive-blur"][data-side="bottom"]
89
+ ),
90
+ :where(
91
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-start]
92
+ > [data-control-family="progressive-blur"][data-side="inline-start"]
93
+ ),
94
+ :where(
95
+ [data-control-family="scroll-area"][data-slot="root"][data-overflow-x-end]
96
+ > [data-control-family="progressive-blur"][data-side="inline-end"]
97
+ ) {
98
+ --_progressive-blur-visibility: visible;
99
+ --_progressive-blur-enter: 1;
100
+ }
101
+
102
+ @media (prefers-reduced-transparency: reduce), (forced-colors: active) {
103
+ :where([data-control-family="scroll-area"][data-slot="root"][data-mask] > [data-scroll-area-viewport]) {
104
+ mask-image: none;
105
+ }
106
+ }
107
+
108
+ :where(
109
+ [data-control-family="scroll-area"][data-slot="root"][data-mask] > [data-scroll-area-viewport]:is(:focus-visible, :has(:focus-visible))
110
+ ) {
111
+ mask-image: none;
112
+ }
113
+
114
+ :where(
115
+ [data-control-family="scroll-area"][data-slot="root"]
116
+ > [data-scroll-area-viewport]:is(:focus-visible, :has(:focus-visible))
117
+ ~ [data-control-family="progressive-blur"]
118
+ ) {
119
+ --_progressive-blur-visibility: hidden;
120
+ --_progressive-blur-enter: 0;
121
+ }
122
+
8
123
  :where([data-control-family="scroll-area"][data-slot="scrollbar"][data-orientation="vertical"]) {
9
124
  inline-size: calc(var(--spacing) * 1.5);
10
125
  }
@@ -14,6 +129,7 @@
14
129
  }
15
130
 
16
131
  :where([data-control-family="scroll-area"][data-slot="scrollbar"]) {
132
+ z-index: 1;
17
133
  opacity: 0;
18
134
  transition: opacity var(--duration-fast) var(--ease-standard);
19
135
  }
@@ -30,16 +146,35 @@
30
146
  transition: background-color var(--duration-fast) var(--ease-standard);
31
147
  }
32
148
 
33
- :where([data-control-family="scroll-area"][data-slot="viewport"]) {
34
- border-radius: inherit;
35
- outline: none;
36
- }
37
-
38
149
  :where([data-control-family="scroll-area"][data-slot="corner"]) {
39
150
  background: var(--cui-scroll-area-corner-background);
40
151
  }
41
152
  }
42
153
 
154
+ @property --_scroll-area-fade-top {
155
+ syntax: "<length-percentage>";
156
+ inherits: false;
157
+ initial-value: 0px;
158
+ }
159
+
160
+ @property --_scroll-area-fade-bottom {
161
+ syntax: "<length-percentage>";
162
+ inherits: false;
163
+ initial-value: 0px;
164
+ }
165
+
166
+ @property --_scroll-area-fade-left {
167
+ syntax: "<length-percentage>";
168
+ inherits: false;
169
+ initial-value: 0px;
170
+ }
171
+
172
+ @property --_scroll-area-fade-right {
173
+ syntax: "<length-percentage>";
174
+ inherits: false;
175
+ initial-value: 0px;
176
+ }
177
+
43
178
  @property --cui-scroll-area-thumb-radius {
44
179
  syntax: "<length-percentage>";
45
180
  inherits: true;