canny-ui 6.7.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canny-ui.min.css +1 -1
- package/dist/table-resize.js +16 -1
- package/package.json +1 -1
- package/src/_utilities.scss +4 -4
- package/src/base/_motion.scss +32 -0
- package/src/base/_reset.scss +7 -0
- package/src/base/_scrollbar-mixin.scss +84 -0
- package/src/base/_touch.scss +60 -0
- package/src/canny-ui.scss +5 -0
- package/src/components/_accordion.scss +10 -1
- package/src/components/_alerts.scss +18 -8
- package/src/components/_autocomplete.scss +16 -10
- package/src/components/_avatar.scss +22 -1
- package/src/components/_breadcrumb.scss +36 -2
- package/src/components/_buttons.scss +35 -24
- package/src/components/_charts-extra.scss +432 -0
- package/src/components/_charts.scss +90 -0
- package/src/components/_chip.scss +8 -7
- package/src/components/_command-palette.scss +27 -5
- package/src/components/_confirm.scss +14 -0
- package/src/components/_datatable.scss +383 -53
- package/src/components/_datepicker.scss +44 -21
- package/src/components/_dialog.scss +12 -0
- package/src/components/_drawer.scss +12 -0
- package/src/components/_fields.scss +115 -27
- package/src/components/_forms.scss +28 -10
- package/src/components/_gantt.scss +554 -0
- package/src/components/_inputs.scss +12 -7
- package/src/components/_list.scss +37 -3
- package/src/components/_menu.scss +23 -9
- package/src/components/_navbar.scss +98 -7
- package/src/components/_pagination.scss +105 -2
- package/src/components/_panels.scss +19 -5
- package/src/components/_popover.scss +3 -5
- package/src/components/_progress.scss +31 -0
- package/src/components/_scrollbar.scss +16 -159
- package/src/components/_sidebar.scss +24 -20
- package/src/components/_skeleton.scss +40 -1
- package/src/components/_slider.scss +8 -2
- package/src/components/_stepper.scss +22 -4
- package/src/components/_table.scss +23 -3
- package/src/components/_tabs.scss +120 -4
- package/src/components/_timeline.scss +26 -0
- package/src/components/_toast.scss +24 -12
- package/src/components/_tooltip-radix.scss +25 -4
- package/src/components/_tree.scss +51 -3
- package/src/components/_uploader.scss +97 -1
- package/src/scripts/table-resize.js +16 -1
- package/src/tokens/_semantic.scss +85 -21
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
@use '../base/scrollbar-mixin' as sb;
|
|
1
2
|
@use '../tokens/primitives' as *;
|
|
2
3
|
|
|
3
4
|
// Menu (DropdownMenu) — popup menu with items, checkboxes, radios, submenus.
|
|
@@ -8,24 +9,30 @@
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
.cny-menu-content {
|
|
11
|
-
z-index: 1010;
|
|
12
|
+
z-index: var(--z-popover, 1010);
|
|
12
13
|
display: flex;
|
|
13
14
|
flex-direction: column;
|
|
14
15
|
gap: 1px;
|
|
15
16
|
min-width: 180px;
|
|
17
|
+
max-width: min(360px, calc(100vw - 16px));
|
|
18
|
+
// Long menus scroll inside the viewport instead of running off-screen.
|
|
19
|
+
max-height: var(--radix-popper-available-height, 70vh);
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
@include sb.expand-scrollbar;
|
|
16
22
|
padding: 4px;
|
|
17
23
|
background: var(--bg-surface-lowest);
|
|
18
|
-
border: 1px solid var(--border-color);
|
|
24
|
+
border: 1px solid var(--border-color-strong);
|
|
19
25
|
border-radius: var(--radius-sm);
|
|
20
|
-
box-shadow:
|
|
21
|
-
0 1px 0 rgba(15, 23, 42, 0.04),
|
|
22
|
-
0 8px 24px -8px rgba(15, 23, 42, 0.2);
|
|
26
|
+
box-shadow: var(--shadow-popover);
|
|
23
27
|
font-family: var(--font-body);
|
|
24
28
|
outline: none;
|
|
25
29
|
animation: cny-menu-in 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
26
30
|
|
|
27
31
|
&.size-sm { min-width: 140px; padding: 3px; }
|
|
28
32
|
|
|
33
|
+
// A long label truncates instead of widening the menu past the viewport.
|
|
34
|
+
.cny-menu-item-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
35
|
+
|
|
29
36
|
&[data-side='top'] { transform-origin: bottom; }
|
|
30
37
|
&[data-side='bottom'] { transform-origin: top; }
|
|
31
38
|
&[data-side='left'] { transform-origin: right; }
|
|
@@ -77,7 +84,7 @@
|
|
|
77
84
|
color: var(--text-primary);
|
|
78
85
|
}
|
|
79
86
|
&[data-highlighted] .cny-menu-item-icon,
|
|
80
|
-
&[data-highlighted] .cny-menu-item-chevron { color: var(--
|
|
87
|
+
&[data-highlighted] .cny-menu-item-chevron { color: var(--text-accent); }
|
|
81
88
|
|
|
82
89
|
&[data-disabled] {
|
|
83
90
|
color: var(--text-disabled);
|
|
@@ -85,12 +92,19 @@
|
|
|
85
92
|
pointer-events: none;
|
|
86
93
|
}
|
|
87
94
|
|
|
95
|
+
// Active (checked) checkbox / radio rows read as selected, not just ticked.
|
|
96
|
+
&.is-check[data-state='checked'],
|
|
97
|
+
&.is-radio[data-state='checked'] {
|
|
98
|
+
font-weight: 600;
|
|
99
|
+
.cny-menu-item-icon { color: var(--text-accent); }
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
&.is-destructive {
|
|
89
|
-
color: var(--color-danger);
|
|
90
|
-
.cny-menu-item-icon { color: var(--color-danger); }
|
|
103
|
+
color: var(--color-danger-text);
|
|
104
|
+
.cny-menu-item-icon { color: var(--color-danger-text); }
|
|
91
105
|
&[data-highlighted] {
|
|
92
106
|
background: color-mix(in srgb, var(--color-danger) 10%, var(--bg-surface-low));
|
|
93
|
-
color: var(--color-danger);
|
|
107
|
+
color: var(--color-danger-text);
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
}
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
&:hover { background: var(--bg-surface-low); }
|
|
56
56
|
|
|
57
57
|
&:focus-visible {
|
|
58
|
-
outline:
|
|
59
|
-
|
|
58
|
+
outline: 2px solid var(--border-focus);
|
|
59
|
+
outline-offset: 2px;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
.brand-mark {
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
&:focus-visible {
|
|
112
|
-
outline:
|
|
113
|
-
|
|
112
|
+
outline: 2px solid var(--border-focus);
|
|
113
|
+
outline-offset: 2px;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
&.active {
|
|
@@ -165,8 +165,32 @@
|
|
|
165
165
|
&:hover { background: var(--bg-surface-low); }
|
|
166
166
|
|
|
167
167
|
&:focus-visible {
|
|
168
|
-
outline:
|
|
169
|
-
|
|
168
|
+
outline: 2px solid var(--border-focus);
|
|
169
|
+
outline-offset: 2px;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Built-in menu button for NavbarNav: only below 640px, where the nav becomes a dropdown.
|
|
174
|
+
.cny-navbar-menu-btn {
|
|
175
|
+
display: none;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
width: 36px;
|
|
179
|
+
height: 36px;
|
|
180
|
+
margin-left: auto;
|
|
181
|
+
border-radius: var(--radius-md);
|
|
182
|
+
border: 1px solid transparent;
|
|
183
|
+
background: transparent;
|
|
184
|
+
color: var(--text-primary);
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
flex-shrink: 0;
|
|
187
|
+
transition: background-color var(--transition-fast);
|
|
188
|
+
|
|
189
|
+
&:hover { background: var(--bg-surface-low); }
|
|
190
|
+
|
|
191
|
+
&:focus-visible {
|
|
192
|
+
outline: 2px solid var(--border-focus);
|
|
193
|
+
outline-offset: 2px;
|
|
170
194
|
}
|
|
171
195
|
}
|
|
172
196
|
|
|
@@ -181,7 +205,74 @@
|
|
|
181
205
|
padding: 0 $space-4;
|
|
182
206
|
gap: $space-2;
|
|
183
207
|
}
|
|
184
|
-
.cny-navbar-
|
|
208
|
+
.cny-navbar-menu-btn { display: inline-flex; }
|
|
209
|
+
.cny-navbar-menu-btn ~ .cny-navbar-actions { margin-left: 0; }
|
|
210
|
+
// Dropdown list under the bar; hidden until the menu button opens it.
|
|
211
|
+
.cny-navbar-nav {
|
|
212
|
+
display: none;
|
|
213
|
+
position: absolute;
|
|
214
|
+
top: 100%;
|
|
215
|
+
left: 0;
|
|
216
|
+
right: 0;
|
|
217
|
+
flex: none;
|
|
218
|
+
flex-direction: column;
|
|
219
|
+
align-items: stretch;
|
|
220
|
+
gap: 2px;
|
|
221
|
+
padding: $space-2 $space-4;
|
|
222
|
+
max-height: calc(100vh - 52px);
|
|
223
|
+
overflow-x: visible;
|
|
224
|
+
overflow-y: auto;
|
|
225
|
+
background: var(--bg-surface-lowest);
|
|
226
|
+
border-bottom: 1px solid var(--border-color);
|
|
227
|
+
box-shadow: var(--shadow-md);
|
|
228
|
+
|
|
229
|
+
&.is-open { display: flex; }
|
|
230
|
+
|
|
231
|
+
.cny-navbar-item {
|
|
232
|
+
height: 40px;
|
|
233
|
+
|
|
234
|
+
&.active::after {
|
|
235
|
+
left: 0;
|
|
236
|
+
right: auto;
|
|
237
|
+
top: 8px;
|
|
238
|
+
bottom: 8px;
|
|
239
|
+
width: 2px;
|
|
240
|
+
height: auto;
|
|
241
|
+
border-radius: 2px;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
185
245
|
.cny-navbar-brand { flex-shrink: 1; min-width: 0; overflow: hidden; }
|
|
186
246
|
.cny-navbar-actions { gap: 2px; }
|
|
187
247
|
}
|
|
248
|
+
|
|
249
|
+
// Coarse pointers: >=44px targets.
|
|
250
|
+
@media (pointer: coarse) {
|
|
251
|
+
.cny-navbar-item { min-height: 44px; height: auto; }
|
|
252
|
+
.cny-navbar-toggle,
|
|
253
|
+
.cny-navbar-menu-btn { width: 44px; height: 44px; }
|
|
254
|
+
.cny-navbar-nav .cny-navbar-item { min-height: 44px; }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Skip-to-content link (Navbar `skipLinkTarget`): off-screen until keyboard focus.
|
|
258
|
+
.cny-skip-link {
|
|
259
|
+
position: fixed;
|
|
260
|
+
top: 8px;
|
|
261
|
+
left: 8px;
|
|
262
|
+
z-index: 1000;
|
|
263
|
+
padding: 8px 14px;
|
|
264
|
+
border-radius: var(--radius-md);
|
|
265
|
+
background: var(--bg-surface-lowest);
|
|
266
|
+
color: var(--text-primary);
|
|
267
|
+
font-family: var(--font-body);
|
|
268
|
+
font-size: 0.875rem;
|
|
269
|
+
font-weight: 550;
|
|
270
|
+
text-decoration: none;
|
|
271
|
+
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.18);
|
|
272
|
+
transform: translateY(-200%);
|
|
273
|
+
&:focus-visible {
|
|
274
|
+
transform: none;
|
|
275
|
+
outline: 2px solid var(--border-focus);
|
|
276
|
+
outline-offset: 2px;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
color: var(--text-primary);
|
|
30
30
|
}
|
|
31
31
|
&:focus-visible {
|
|
32
|
-
outline: 2px solid var(--
|
|
33
|
-
outline-offset:
|
|
32
|
+
outline: 2px solid var(--border-focus);
|
|
33
|
+
outline-offset: 2px;
|
|
34
34
|
}
|
|
35
35
|
&:disabled {
|
|
36
36
|
opacity: 0.4;
|
|
@@ -60,3 +60,106 @@
|
|
|
60
60
|
.cny-pagination-btn { min-width: 36px; height: 36px; font-size: 0.875rem; padding: 0 10px; }
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
// Rows-per-page picker and go-to-page input
|
|
65
|
+
.cny-pagination {
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.cny-pagination-field {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 6px;
|
|
73
|
+
margin: 0 8px;
|
|
74
|
+
font-size: 0.8125rem;
|
|
75
|
+
color: var(--text-muted);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.cny-pagination-select,
|
|
79
|
+
.cny-pagination-input {
|
|
80
|
+
height: 32px;
|
|
81
|
+
padding: 0 8px;
|
|
82
|
+
background: var(--bg-surface-lowest);
|
|
83
|
+
color: var(--text-primary);
|
|
84
|
+
border: 1px solid var(--border-color);
|
|
85
|
+
border-radius: var(--radius-sm);
|
|
86
|
+
font: inherit;
|
|
87
|
+
|
|
88
|
+
&:focus-visible {
|
|
89
|
+
outline: 2px solid var(--border-focus);
|
|
90
|
+
outline-offset: 2px;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.cny-pagination-input {
|
|
95
|
+
width: 4.5rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// "1–10 of 243" range text
|
|
99
|
+
.cny-pagination-range {
|
|
100
|
+
margin: 0 8px;
|
|
101
|
+
font-size: 0.8125rem;
|
|
102
|
+
font-variant-numeric: tabular-nums;
|
|
103
|
+
color: var(--text-secondary);
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@mixin cny-pagination-sr {
|
|
108
|
+
position: absolute;
|
|
109
|
+
width: 1px;
|
|
110
|
+
height: 1px;
|
|
111
|
+
margin: -1px;
|
|
112
|
+
padding: 0;
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
clip: rect(0 0 0 0);
|
|
115
|
+
clip-path: inset(50%);
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
border: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// "Page 3 of 12" — only shown in the compact layout
|
|
121
|
+
.cny-pagination-summary {
|
|
122
|
+
display: none;
|
|
123
|
+
padding: 0 8px;
|
|
124
|
+
font-size: 0.8125rem;
|
|
125
|
+
color: var(--text-secondary);
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cny-pagination-compact {
|
|
130
|
+
.cny-pagination-btn:not(.cny-pagination-control),
|
|
131
|
+
.cny-pagination-ellipsis,
|
|
132
|
+
.cny-pagination-edge {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
.cny-pagination-summary {
|
|
136
|
+
display: inline;
|
|
137
|
+
}
|
|
138
|
+
// Labels collapse to visually-hidden text (the controls keep their names).
|
|
139
|
+
.cny-pagination-field-label {
|
|
140
|
+
@include cny-pagination-sr;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@media (max-width: 480px) {
|
|
145
|
+
.cny-pagination-auto {
|
|
146
|
+
.cny-pagination-btn:not(.cny-pagination-control),
|
|
147
|
+
.cny-pagination-ellipsis,
|
|
148
|
+
.cny-pagination-edge {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
.cny-pagination-summary {
|
|
152
|
+
display: inline;
|
|
153
|
+
}
|
|
154
|
+
.cny-pagination-field-label {
|
|
155
|
+
@include cny-pagination-sr;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Coarse pointers: >=44px targets.
|
|
161
|
+
@media (pointer: coarse) {
|
|
162
|
+
.cny-pagination-btn { min-width: 44px; min-height: 44px; }
|
|
163
|
+
.cny-pagination-select,
|
|
164
|
+
.cny-pagination-input { height: 44px; }
|
|
165
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
@use '../base/scrollbar-mixin' as sb;
|
|
1
2
|
@use '../tokens/primitives' as *;
|
|
2
3
|
|
|
3
4
|
// PanelGroup — resizable split panels. Percentage-based, so it fills any container responsively.
|
|
@@ -19,28 +20,41 @@
|
|
|
19
20
|
min-width: 0;
|
|
20
21
|
min-height: 0;
|
|
21
22
|
overflow: auto;
|
|
23
|
+
@include sb.expand-scrollbar;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
.cny-panel-handle {
|
|
25
27
|
position: relative;
|
|
26
28
|
flex: none;
|
|
27
29
|
align-self: stretch;
|
|
28
|
-
background: var(--hairline-
|
|
30
|
+
background: var(--hairline-strong);
|
|
29
31
|
transition: background-color 120ms ease;
|
|
30
32
|
touch-action: none;
|
|
31
33
|
z-index: 1;
|
|
32
34
|
|
|
33
|
-
// generous invisible hit area around the thin visible line
|
|
35
|
+
// generous invisible hit area (>=17px; 25px on touch) around the thin visible line
|
|
34
36
|
&::before {
|
|
35
37
|
content: '';
|
|
36
38
|
position: absolute;
|
|
37
|
-
inset: -
|
|
39
|
+
inset: 0 -6px;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
&-v::before { inset: -6px 0; }
|
|
43
|
+
|
|
44
|
+
@media (pointer: coarse) {
|
|
45
|
+
&::before { inset: 0 -10px; }
|
|
46
|
+
&-v::before { inset: -10px 0; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:hover {
|
|
50
|
+
background: var(--color-primary);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Strong, offset ring on top of the filled handle (>=3:1 against either panel).
|
|
41
54
|
&:focus-visible {
|
|
42
55
|
background: var(--color-primary);
|
|
43
|
-
outline:
|
|
56
|
+
outline: 2px solid var(--border-focus);
|
|
57
|
+
outline-offset: 2px;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
&-h {
|
|
@@ -8,14 +8,12 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.cny-popover-content {
|
|
11
|
-
z-index: 1010;
|
|
11
|
+
z-index: var(--z-popover, 1010);
|
|
12
12
|
padding: 14px;
|
|
13
13
|
background: var(--bg-surface-lowest);
|
|
14
|
-
border: 1px solid var(--border-color);
|
|
14
|
+
border: 1px solid var(--border-color-strong);
|
|
15
15
|
border-radius: var(--radius-sm);
|
|
16
|
-
box-shadow:
|
|
17
|
-
0 1px 0 rgba(15, 23, 42, 0.04),
|
|
18
|
-
0 12px 28px -12px rgba(15, 23, 42, 0.22);
|
|
16
|
+
box-shadow: var(--shadow-popover);
|
|
19
17
|
font-family: var(--font-body);
|
|
20
18
|
color: var(--text-primary);
|
|
21
19
|
outline: none;
|
|
@@ -35,6 +35,37 @@
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Indeterminate shimmer — full-width bar with a light sweep
|
|
39
|
+
.progress.indeterminate.shimmer .progress-bar {
|
|
40
|
+
width: 100%;
|
|
41
|
+
background-color: var(--color-primary);
|
|
42
|
+
background-image: linear-gradient(
|
|
43
|
+
90deg,
|
|
44
|
+
transparent 0%,
|
|
45
|
+
rgba(255, 255, 255, 0.55) 50%,
|
|
46
|
+
transparent 100%
|
|
47
|
+
);
|
|
48
|
+
background-size: 40% 100%;
|
|
49
|
+
background-repeat: no-repeat;
|
|
50
|
+
background-position: -40% 0;
|
|
51
|
+
animation: cny-progress-shimmer 1.4s var(--ease-standard) infinite;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Reduced motion: no travelling chunk — a static, faded bar still reads as "working"
|
|
55
|
+
@media (prefers-reduced-motion: reduce) {
|
|
56
|
+
.progress.indeterminate .progress-bar,
|
|
57
|
+
.progress.indeterminate.shimmer .progress-bar {
|
|
58
|
+
width: 100%;
|
|
59
|
+
animation: none;
|
|
60
|
+
opacity: 0.5;
|
|
61
|
+
background-image: none;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes cny-progress-shimmer {
|
|
66
|
+
100% { background-position: 140% 0; }
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
@keyframes cny-progress-slide {
|
|
39
70
|
0% { transform: translateX(-100%); }
|
|
40
71
|
100% { transform: translateX(250%); }
|
|
@@ -1,167 +1,17 @@
|
|
|
1
|
-
@use '../
|
|
1
|
+
@use '../base/scrollbar-mixin' as sb;
|
|
2
2
|
|
|
3
3
|
// ── Scrollbar ────────────────────────────────────────────────────
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// .overlay — hides the thumb until hover/scroll
|
|
8
|
-
// .subtle — track visible only on hover
|
|
9
|
-
// .expand — thin by default, thumb widens on container hover
|
|
10
|
-
// .primary — thumb uses primary color
|
|
4
|
+
// Apply `.cny-scroll` to any overflow container. There is one look — the
|
|
5
|
+
// expand-on-hover scrollbar (see base/_scrollbar-mixin.scss) — shared with
|
|
6
|
+
// every scrolling Canny-UI component.
|
|
11
7
|
|
|
12
8
|
.cny-scroll {
|
|
13
|
-
scrollbar
|
|
14
|
-
scrollbar-color: var(--border-color) transparent;
|
|
9
|
+
@include sb.expand-scrollbar;
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
&::-webkit-scrollbar-track {
|
|
22
|
-
background: transparent;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
&::-webkit-scrollbar-thumb {
|
|
26
|
-
background-color: var(--border-color);
|
|
27
|
-
border-radius: var(--radius-full);
|
|
28
|
-
border: 3px solid transparent;
|
|
29
|
-
background-clip: content-box;
|
|
30
|
-
transition: background-color var(--transition-fast);
|
|
31
|
-
|
|
32
|
-
&:hover {
|
|
33
|
-
background-color: var(--text-muted);
|
|
34
|
-
background-clip: content-box;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
&:active {
|
|
38
|
-
background-color: var(--text-secondary);
|
|
39
|
-
background-clip: content-box;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&::-webkit-scrollbar-corner {
|
|
44
|
-
background: transparent;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Variants that live in the WebKit pseudo-elements ───────────
|
|
48
|
-
// Chromium drops every ::-webkit-scrollbar-* rule on an element whose
|
|
49
|
-
// scrollbar-width OR scrollbar-color is non-`auto`, and the base sets both.
|
|
50
|
-
// These three variants exist only as those rules, so both are reset here or
|
|
51
|
-
// none of them render. Firefox has no pseudo-element styling and falls back
|
|
52
|
-
// to a native scrollbar — these variants are a Chromium enhancement.
|
|
53
|
-
//
|
|
54
|
-
// `.thin` is deliberately NOT in this list: its look comes from the standard
|
|
55
|
-
// `scrollbar-width: thin`, the one path that works in Firefox too. Resetting
|
|
56
|
-
// it to `auto` would trade a close-enough 10px bar in Chromium for a
|
|
57
|
-
// full-width one in Firefox.
|
|
58
|
-
//
|
|
59
|
-
// @supports can't be used to split the two engines: Firefox parses
|
|
60
|
-
// `selector(::-webkit-scrollbar)` as valid and reports true, despite not
|
|
61
|
-
// implementing it.
|
|
62
|
-
&.subtle,
|
|
63
|
-
&.overlay,
|
|
64
|
-
&.expand {
|
|
65
|
-
scrollbar-width: auto;
|
|
66
|
-
scrollbar-color: auto;
|
|
67
|
-
|
|
68
|
-
// .primary re-declares scrollbar-color further down; this higher-specificity
|
|
69
|
-
// guard keeps WebKit alive when the two are combined, and the thumb rules
|
|
70
|
-
// below carry the colour instead.
|
|
71
|
-
&.primary {
|
|
72
|
-
scrollbar-color: auto;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ── Thin ───────────────────────────────────────────────────────
|
|
77
|
-
&.thin {
|
|
78
|
-
&::-webkit-scrollbar {
|
|
79
|
-
width: 8px;
|
|
80
|
-
height: 8px;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
&::-webkit-scrollbar-thumb {
|
|
84
|
-
border-width: 2px;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ── Subtle: track appears on hover ─────────────────────────────
|
|
89
|
-
&.subtle {
|
|
90
|
-
&::-webkit-scrollbar-thumb {
|
|
91
|
-
background-color: transparent;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
&:hover::-webkit-scrollbar-thumb,
|
|
95
|
-
&:focus-within::-webkit-scrollbar-thumb {
|
|
96
|
-
background-color: var(--border-color);
|
|
97
|
-
background-clip: content-box;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── Overlay: thumb floats over content ─────────────────────────
|
|
102
|
-
&.overlay {
|
|
103
|
-
&::-webkit-scrollbar {
|
|
104
|
-
width: 10px;
|
|
105
|
-
height: 10px;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&::-webkit-scrollbar-track {
|
|
109
|
-
background: transparent;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
&::-webkit-scrollbar-thumb {
|
|
113
|
-
background-color: color-mix(in srgb, var(--text-muted) 40%, transparent);
|
|
114
|
-
border-width: 2px;
|
|
115
|
-
|
|
116
|
-
&:hover {
|
|
117
|
-
background-color: color-mix(in srgb, var(--text-muted) 70%, transparent);
|
|
118
|
-
background-clip: content-box;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── Expand: slim thumb that fattens when the pointer is on the scrollbar ─
|
|
124
|
-
&.expand {
|
|
125
|
-
// The standard-property reset this variant needs is shared with
|
|
126
|
-
// .subtle/.overlay above.
|
|
127
|
-
&::-webkit-scrollbar {
|
|
128
|
-
width: 16px;
|
|
129
|
-
height: 16px;
|
|
130
|
-
background: transparent;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Rest: 16px track with a 6px transparent border → 4px visible thumb.
|
|
134
|
-
// The track stays 16px so the grab target is generous despite the hairline.
|
|
135
|
-
&::-webkit-scrollbar-thumb {
|
|
136
|
-
border-width: 6px;
|
|
137
|
-
background-color: var(--border-color-strong);
|
|
138
|
-
transition:
|
|
139
|
-
border-width 0.15s ease,
|
|
140
|
-
background-color var(--transition-fast);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Pointer over the thumb (anywhere in its 16px column) → fatten to 14px visible.
|
|
144
|
-
// The transparent border is still part of the thumb's hit region.
|
|
145
|
-
&::-webkit-scrollbar-thumb:hover,
|
|
146
|
-
&::-webkit-scrollbar-thumb:active {
|
|
147
|
-
border-width: 1px;
|
|
148
|
-
background-color: var(--text-muted);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── Primary color ──────────────────────────────────────────────
|
|
153
|
-
&.primary {
|
|
154
|
-
scrollbar-color: var(--color-primary) transparent;
|
|
155
|
-
|
|
156
|
-
&::-webkit-scrollbar-thumb {
|
|
157
|
-
background-color: var(--color-primary);
|
|
158
|
-
background-clip: content-box;
|
|
159
|
-
|
|
160
|
-
&:hover {
|
|
161
|
-
background-color: var(--color-primary);
|
|
162
|
-
background-clip: content-box;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
11
|
+
// Scroll / ScrollRegion are tab stops while they overflow: show the strong ring.
|
|
12
|
+
&:focus-visible {
|
|
13
|
+
outline: 2px solid var(--border-focus);
|
|
14
|
+
outline-offset: 2px;
|
|
165
15
|
}
|
|
166
16
|
}
|
|
167
17
|
|
|
@@ -185,3 +35,10 @@
|
|
|
185
35
|
background-attachment: local, local, scroll, scroll;
|
|
186
36
|
background-color: var(--bg-surface-lowest);
|
|
187
37
|
}
|
|
38
|
+
|
|
39
|
+
// Plain-CSS scroll wrapper for tables: give it `tabindex="0" role="region" aria-label="…"`
|
|
40
|
+
// so keyboard users can scroll it, and it shows the same strong focus ring.
|
|
41
|
+
.table-wrap:focus-visible {
|
|
42
|
+
outline: 2px solid var(--border-focus);
|
|
43
|
+
outline-offset: 2px;
|
|
44
|
+
}
|