@keenmate/pure-css 1.0.0-rc08 → 1.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/CHANGELOG.md +50 -0
- package/README.md +213 -184
- package/dist/css/base.css +3 -0
- package/dist/css/pure-css.css +11 -8
- package/package.json +1 -1
- package/src/scss/_base-css-variables.scss +17 -0
- package/src/scss/_fit-flyout.scss +1 -1
- package/src/scss/_navbar-elements.scss +4 -4
- package/src/scss/_resize-handle.scss +1 -1
- package/src/scss/_sidebar.scss +443 -443
- package/src/scss/variables/_base.scss +382 -368
package/src/scss/_sidebar.scss
CHANGED
|
@@ -1,443 +1,443 @@
|
|
|
1
|
-
/* ========================================
|
|
2
|
-
Sidebar Component
|
|
3
|
-
Sidebar navigation with nested submenus
|
|
4
|
-
======================================== */
|
|
5
|
-
@use 'variables/index' as *;
|
|
6
|
-
@use 'resize-handle' as *;
|
|
7
|
-
|
|
8
|
-
// Local CSS variables for sidebar (runtime state, modified by JS for resize)
|
|
9
|
-
:root {
|
|
10
|
-
--pc-local-sidebar-width: #{$sidebar-width};
|
|
11
|
-
--pc-local-sidebar-min-width: 18rem;
|
|
12
|
-
--pc-local-sidebar-max-width: 50rem;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Sidebar layout element - width in :where() for low specificity (utility classes can override)
|
|
16
|
-
:where(.pc-layout__sidebar) {
|
|
17
|
-
width: var(--pc-local-sidebar-width);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.pc-layout__sidebar {
|
|
21
|
-
background-color: var(--pc-sidebar-bg, var(--base-page-bg));
|
|
22
|
-
border-inline-end: $border-width-base solid var(--pc-border-color); // RTL: flips to border-left
|
|
23
|
-
flex-shrink: 0;
|
|
24
|
-
position: relative;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Resize handle on the sidebar edge. Shows the shared resize knob (see
|
|
28
|
-
// _resize-handle.scss) so it's the same control as the splitter gutter — slim on
|
|
29
|
-
// desktop, chunkier on small viewports. The knob is sticky at mid-viewport so it
|
|
30
|
-
// stays on screen on long, page-height sidebars.
|
|
31
|
-
.pc-sidebar-resize {
|
|
32
|
-
position: absolute;
|
|
33
|
-
top: 0;
|
|
34
|
-
inset-inline-end: 0; // RTL: flips to left
|
|
35
|
-
width: 6px;
|
|
36
|
-
height: 100%;
|
|
37
|
-
cursor: ew-resize;
|
|
38
|
-
z-index: 100;
|
|
39
|
-
// Own the touch gesture. Without this the browser's touch-action heuristic can
|
|
40
|
-
// interpret a drag as a scroll/pan and CANCEL the touch sequence after
|
|
41
|
-
// touchstart — so touchmove never reaches the resize handler and the drag
|
|
42
|
-
// silently dies. Harmless for mouse (mousedown path is unaffected).
|
|
43
|
-
touch-action: none;
|
|
44
|
-
// Flex so the sticky knob can pin to the inline-end edge and ride the viewport.
|
|
45
|
-
display: flex;
|
|
46
|
-
justify-content: flex-end;
|
|
47
|
-
align-items: flex-start;
|
|
48
|
-
|
|
49
|
-
// Shared grab-knob. `position: sticky; top: 50vh` keeps it at the middle of the
|
|
50
|
-
// VISIBLE viewport, not the middle of the sidebar's full length — on a long
|
|
51
|
-
// page the sidebar stretches to the whole scroll height, so a plain top:50%
|
|
52
|
-
// knob ends up far off-screen. flex-end pins it to the inline-end edge;
|
|
53
|
-
// translateX(50%) straddles it onto the sidebar's border line; translateY(-50%)
|
|
54
|
-
// centres it on the 50vh sticky point.
|
|
55
|
-
&::before {
|
|
56
|
-
@include pa-resize-knob($axis: block);
|
|
57
|
-
position: sticky;
|
|
58
|
-
top: 50vh;
|
|
59
|
-
transform: translate(50%, -50%);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
&:hover::before,
|
|
63
|
-
&--active::before {
|
|
64
|
-
@include pa-resize-knob-active;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Touch: widen only the (invisible) grab area to a finger-sized target. The knob
|
|
69
|
-
// LOOK is viewport-driven (handled by the shared mixin), so it's unchanged here.
|
|
70
|
-
@media (pointer: coarse) {
|
|
71
|
-
.pc-sidebar-resize {
|
|
72
|
-
width: 24px;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Sticky LAYOUT mode: the whole page doesn't scroll (body overflow:hidden) and
|
|
77
|
-
// the sidebar is already ~viewport height, where position:sticky never engages.
|
|
78
|
-
// Absolute-centre the knob instead — 50% of a viewport-height sidebar IS the
|
|
79
|
-
// viewport centre.
|
|
80
|
-
body.pc-layout--sticky .pc-sidebar-resize::before {
|
|
81
|
-
position: absolute;
|
|
82
|
-
top: 50%;
|
|
83
|
-
inset-inline-end: 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Disable text selection during resize
|
|
87
|
-
body.pc-sidebar-resizing {
|
|
88
|
-
cursor: ew-resize !important;
|
|
89
|
-
user-select: none;
|
|
90
|
-
|
|
91
|
-
* {
|
|
92
|
-
cursor: ew-resize !important;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Sticky mode - sidebar scrolls
|
|
97
|
-
body.pc-layout--sticky .pc-layout__sidebar {
|
|
98
|
-
overflow-y: auto;
|
|
99
|
-
overflow-y: overlay; // Chromium: scrollbar overlays content, doesn't reduce width
|
|
100
|
-
overflow-x: visible; // Allow tooltips to escape (right in LTR, left in RTL)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Sidebar component (separate BEM block)
|
|
104
|
-
.pc-sidebar {
|
|
105
|
-
&__nav {
|
|
106
|
-
padding: $spacing-base 0;
|
|
107
|
-
|
|
108
|
-
ul {
|
|
109
|
-
list-style: none;
|
|
110
|
-
margin: 0;
|
|
111
|
-
padding: 0;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
&__item {
|
|
116
|
-
// Base styles for all sidebar items (regular links and submenu toggles)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Flat section heading — a non-interactive group label ("PROJECT",
|
|
120
|
-
// "GUIDES", "LIVE DEMOS"). Use when the nav is grouped but the groups are
|
|
121
|
-
// NOT collapsible; for collapsible groups use __toggle + __submenu instead.
|
|
122
|
-
// Rendered as an <li class="pc-sidebar__section"> inside __nav's <ul>, so it
|
|
123
|
-
// sits in the list flow without a bullet (the <ul> already resets list-style).
|
|
124
|
-
&__section {
|
|
125
|
-
padding: $spacing-md $spacing-base $spacing-xs; // 16px inset aligns with __link text
|
|
126
|
-
font-size: $font-size-xs;
|
|
127
|
-
font-weight: $font-weight-semibold;
|
|
128
|
-
text-transform: uppercase;
|
|
129
|
-
letter-spacing: 0.05em;
|
|
130
|
-
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
131
|
-
user-select: none;
|
|
132
|
-
|
|
133
|
-
// First heading shouldn't push the whole nav down
|
|
134
|
-
&:first-child {
|
|
135
|
-
padding-top: $spacing-xs;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Thin horizontal rule separating groups of items — e.g. between nav items
|
|
140
|
-
// folded in from the navbar (fit.js nav-collapse, sidebar sink) and the
|
|
141
|
-
// sidebar's own links. Rendered as an <li class="pc-sidebar__divider"> so it
|
|
142
|
-
// sits in the list flow; carries no text.
|
|
143
|
-
&__divider {
|
|
144
|
-
height: 0;
|
|
145
|
-
margin: $spacing-xs $spacing-base;
|
|
146
|
-
padding: 0;
|
|
147
|
-
border: 0;
|
|
148
|
-
border-top: $border-width-base solid var(--pc-border-color);
|
|
149
|
-
list-style: none;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
&__link {
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
gap: $sidebar-item-gap;
|
|
156
|
-
padding: $sidebar-padding;
|
|
157
|
-
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
158
|
-
text-decoration: none;
|
|
159
|
-
font-weight: $font-weight-medium;
|
|
160
|
-
border-inline-end: $border-width-thick solid transparent; // RTL: flips to border-left
|
|
161
|
-
|
|
162
|
-
body.loaded & {
|
|
163
|
-
transition: all $transition-fast $easing-snappy;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
&:hover {
|
|
167
|
-
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
168
|
-
background-color: var(--pc-accent-light);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
&--active {
|
|
172
|
-
color: var(--pc-accent);
|
|
173
|
-
background-color: var(--pc-accent-hover);
|
|
174
|
-
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
175
|
-
font-weight: $font-weight-semibold;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Search affordance at the top of the sidebar — a TRIGGER (a consumer wires it
|
|
180
|
-
// to open a command palette / their own search), rendered as a framed
|
|
181
|
-
// faux-input row so it reads as "search" rather than another nav link:
|
|
182
|
-
// magnifier icon + muted label, inset from the sidebar edges. In icon-collapse
|
|
183
|
-
// mode it sheds the frame and centres to just the icon, exactly like the links
|
|
184
|
-
// below (its .pc-sidebar__label is hidden by the shared icon-collapse rule).
|
|
185
|
-
&__search {
|
|
186
|
-
display: flex;
|
|
187
|
-
align-items: center;
|
|
188
|
-
gap: $sidebar-item-gap;
|
|
189
|
-
width: calc(100% - #{$spacing-sm} * 2);
|
|
190
|
-
margin: $spacing-sm;
|
|
191
|
-
// Sized on the md input height (35px) to match the navbar search boxes,
|
|
192
|
-
// rather than the icon + $sidebar-padding metric of a nav row (40px). Trade-off:
|
|
193
|
-
// it sits ~5px shorter than the links below it. Block padding is dropped (fixed
|
|
194
|
-
// height + align-items:center owns the vertical).
|
|
195
|
-
// Inline padding: right = $spacing-base (the nav-row inset); left = $spacing-sm
|
|
196
|
-
// so that margin ($spacing-sm) + padding-left ($spacing-sm) = $spacing-base —
|
|
197
|
-
// landing the magnifier exactly where the nav-row icons below it sit, instead
|
|
198
|
-
// of one box-margin too far in.
|
|
199
|
-
height: #{$base-input-size-md-height}rem;
|
|
200
|
-
padding: 0 $spacing-base 0 $spacing-sm;
|
|
201
|
-
color: var(--pc-text-color-2);
|
|
202
|
-
background-color: var(--
|
|
203
|
-
border: $border-width-base solid var(--pc-border-color);
|
|
204
|
-
border-radius: var(--pc-border-radius);
|
|
205
|
-
text-align: start; // RTL: flips to right
|
|
206
|
-
font-weight: $font-weight-medium;
|
|
207
|
-
cursor: pointer;
|
|
208
|
-
|
|
209
|
-
// Honor the standard HTML hidden attribute (display:flex above beats the UA
|
|
210
|
-
// `[hidden]{display:none}` rule otherwise).
|
|
211
|
-
&[hidden] {
|
|
212
|
-
display: none;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
body.loaded & {
|
|
216
|
-
transition: all $transition-fast $easing-snappy;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
&:hover {
|
|
220
|
-
// $accent-color (foundation) — was $input-focus-border-color, an exact
|
|
221
|
-
// alias; inlined so the shell no longer depends on the component layer
|
|
222
|
-
// (variables/_components.scss now lives in pure-admin-core).
|
|
223
|
-
border-color: $accent-color;
|
|
224
|
-
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Collapsed rail ONLY (icon-collapse mode AND actually collapsed, i.e.
|
|
228
|
-
// body.sidebar-hidden): drop the frame, icon-only, matching link padding so
|
|
229
|
-
// the icon lands in the same column as every other sidebar icon. Must be
|
|
230
|
-
// gated by .sidebar-hidden — .pc-layout__sidebar--icon-collapse is the MODE
|
|
231
|
-
// class and stays on the element while the rail is expanded, so an unscoped
|
|
232
|
-
// rule would strip the search frame in the expanded state too.
|
|
233
|
-
.sidebar-hidden .pc-layout__sidebar--icon-collapse & {
|
|
234
|
-
width: auto;
|
|
235
|
-
margin: 0;
|
|
236
|
-
padding: $sidebar-padding;
|
|
237
|
-
background: transparent;
|
|
238
|
-
border-color: transparent;
|
|
239
|
-
border-radius: 0;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Type-and-go variant: the framed row hosts a <form> whose native GET submit
|
|
243
|
-
// redirects to a results page on Enter — instead of a button that opens the
|
|
244
|
-
// palette. A single text field submits on Enter with no submit button; the
|
|
245
|
-
// magnifier is a type="submit" so that in icon-collapse mode (field hidden)
|
|
246
|
-
// clicking it still submits — an empty query lands on the bare results page.
|
|
247
|
-
// Keeps the base $sidebar-padding frame untouched so the outer box is
|
|
248
|
-
// identical to the trigger button; the submit icon mirrors .pc-sidebar__icon
|
|
249
|
-
// (fixed size, line-height 1) so it drives the row height exactly like a link,
|
|
250
|
-
// and the borderless field just fills the rest. (Don't feed $sidebar-padding —
|
|
251
|
-
// a two-value 0.5rem/1rem token — to padding-block: it maps to top/bottom and
|
|
252
|
-
// inflates the box.)
|
|
253
|
-
&--input {
|
|
254
|
-
cursor: text;
|
|
255
|
-
|
|
256
|
-
.pc-sidebar__search-icon {
|
|
257
|
-
width: $sidebar-icon-size;
|
|
258
|
-
min-width: $sidebar-icon-size;
|
|
259
|
-
height: $sidebar-icon-size;
|
|
260
|
-
display: flex;
|
|
261
|
-
align-items: center;
|
|
262
|
-
justify-content: center;
|
|
263
|
-
flex-shrink: 0;
|
|
264
|
-
padding: 0;
|
|
265
|
-
border: none;
|
|
266
|
-
background: transparent;
|
|
267
|
-
color: inherit;
|
|
268
|
-
font-size: $font-size-base;
|
|
269
|
-
line-height: 1; // prevent emoji baseline shift, matching __icon
|
|
270
|
-
cursor: pointer;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.pc-sidebar__search-field {
|
|
274
|
-
flex: 1;
|
|
275
|
-
min-width: 0;
|
|
276
|
-
border: none;
|
|
277
|
-
outline: none;
|
|
278
|
-
background: transparent;
|
|
279
|
-
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
280
|
-
font: inherit;
|
|
281
|
-
padding: 0;
|
|
282
|
-
|
|
283
|
-
&::placeholder {
|
|
284
|
-
color: var(--pc-text-color-2);
|
|
285
|
-
opacity: $opacity-muted;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
&::-webkit-search-decoration,
|
|
289
|
-
&::-webkit-search-cancel-button {
|
|
290
|
-
-webkit-appearance: none;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Collapsed rail ONLY: the input variant sheds its field and centres on the
|
|
296
|
-
// submit icon; clicking it submits with an empty query (the bare results
|
|
297
|
-
// page). Gated by .sidebar-hidden so the field reappears when the icon-collapse
|
|
298
|
-
// rail is expanded (see frame rule above for why the mode class isn't enough).
|
|
299
|
-
.sidebar-hidden .pc-layout__sidebar--icon-collapse &--input {
|
|
300
|
-
justify-content: center;
|
|
301
|
-
|
|
302
|
-
.pc-sidebar__search-field {
|
|
303
|
-
display: none;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
&__toggle {
|
|
309
|
-
// For submenu toggle buttons (non-link items)
|
|
310
|
-
display: flex;
|
|
311
|
-
align-items: center;
|
|
312
|
-
gap: $sidebar-item-gap;
|
|
313
|
-
padding: $sidebar-padding;
|
|
314
|
-
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
315
|
-
background: none;
|
|
316
|
-
border: none;
|
|
317
|
-
width: 100%;
|
|
318
|
-
text-align: start; // RTL: flips to right
|
|
319
|
-
font-weight: $font-weight-medium;
|
|
320
|
-
cursor: pointer;
|
|
321
|
-
|
|
322
|
-
body.loaded & {
|
|
323
|
-
transition: all $transition-fast $easing-snappy;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
&:hover {
|
|
327
|
-
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
328
|
-
background-color: var(--pc-accent-light);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
&--active {
|
|
332
|
-
color: var(--pc-accent);
|
|
333
|
-
background-color: var(--pc-accent-hover);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Icon and label styles
|
|
338
|
-
&__icon {
|
|
339
|
-
width: $sidebar-icon-size;
|
|
340
|
-
min-width: $sidebar-icon-size;
|
|
341
|
-
height: $sidebar-icon-size;
|
|
342
|
-
display: flex;
|
|
343
|
-
align-items: center;
|
|
344
|
-
justify-content: center;
|
|
345
|
-
flex-shrink: 0;
|
|
346
|
-
font-size: $font-size-base;
|
|
347
|
-
text-align: center;
|
|
348
|
-
line-height: 1; // Prevent emoji baseline shift
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
&__label {
|
|
352
|
-
flex: 1;
|
|
353
|
-
overflow: hidden;
|
|
354
|
-
text-overflow: ellipsis;
|
|
355
|
-
white-space: nowrap;
|
|
356
|
-
|
|
357
|
-
body.loaded & {
|
|
358
|
-
transition: opacity $transition-normal $easing-snappy, width $transition-normal $easing-snappy;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Submenu styles - Level 2
|
|
363
|
-
&__submenu {
|
|
364
|
-
list-style: none;
|
|
365
|
-
margin: 0;
|
|
366
|
-
padding: 0;
|
|
367
|
-
display: none;
|
|
368
|
-
background-color: var(--pc-sidebar-submenu-bg, var(--base-subtle-bg));
|
|
369
|
-
|
|
370
|
-
&--open {
|
|
371
|
-
display: block;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Level 2 submenu links
|
|
376
|
-
&__submenu > &__item > &__link {
|
|
377
|
-
padding-inline-start: $sidebar-submenu-indent; // RTL: flips to padding-right
|
|
378
|
-
font-size: $font-size-md;
|
|
379
|
-
border-inline-end: $border-width-medium solid transparent; // RTL: flips to border-left
|
|
380
|
-
|
|
381
|
-
&:hover {
|
|
382
|
-
background-color: var(--pc-sidebar-submenu-hover-bg, var(--base-hover-bg));
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
&--active {
|
|
386
|
-
background-color: var(--pc-sidebar-submenu-active-bg, var(--base-active-bg));
|
|
387
|
-
color: var(--pc-sidebar-submenu-active-text, var(--base-text-color-1));
|
|
388
|
-
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Level 2 submenu toggles (for third level)
|
|
393
|
-
&__submenu > &__item > &__toggle {
|
|
394
|
-
padding-inline-start: $sidebar-submenu-indent; // RTL: flips to padding-right
|
|
395
|
-
font-size: $font-size-md;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Level 3 submenu links
|
|
399
|
-
&__submenu &__submenu > &__item > &__link {
|
|
400
|
-
padding-inline-start: calc($sidebar-submenu-indent * $sidebar-submenu-indent-multiplier); // RTL: flips
|
|
401
|
-
font-size: $font-size-sm;
|
|
402
|
-
border-inline-end: $border-width-base solid transparent; // RTL: flips to border-left
|
|
403
|
-
|
|
404
|
-
&--active {
|
|
405
|
-
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Chevron icon for toggle buttons. Rendered as an SVG mask painted in
|
|
410
|
-
// currentColor (was a bare "›" text glyph, which rendered inconsistently
|
|
411
|
-
// across platforms/fonts). Reads the shared --base-icon-chevron so it aligns
|
|
412
|
-
// with pure-admin + the web components; --
|
|
413
|
-
// override, and #{$base-icon-chevron} is the standalone literal fallback.
|
|
414
|
-
// `font-size: 0` collapses any legacy "›" still present in older markup so it
|
|
415
|
-
// never double-renders alongside the mask.
|
|
416
|
-
&__chevron {
|
|
417
|
-
margin-inline-start: auto; // RTL: flips to margin-right: auto
|
|
418
|
-
inline-size: 1.6rem;
|
|
419
|
-
block-size: 1.6rem;
|
|
420
|
-
flex-shrink: 0;
|
|
421
|
-
font-size: 0;
|
|
422
|
-
background-color: currentColor;
|
|
423
|
-
-webkit-mask: var(--
|
|
424
|
-
mask: var(--
|
|
425
|
-
|
|
426
|
-
body.loaded & {
|
|
427
|
-
transition: transform $transition-fast $easing-snappy;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Chevron points right (›) in LTR, left (‹) in RTL
|
|
431
|
-
// When open, rotates 90deg clockwise in LTR, -90deg (counter-clockwise) in RTL.
|
|
432
|
-
// Scoped to the item's OWN chevron (direct toggle child) — a plain descendant
|
|
433
|
-
// selector would also rotate nested items' chevrons whenever an ANCESTOR is
|
|
434
|
-
// open, so a closed sub-group would wrongly show a "down" chevron.
|
|
435
|
-
.pc-sidebar__item--open > .pc-sidebar__toggle > & {
|
|
436
|
-
transform: rotate(90deg);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
[dir="rtl"] .pc-sidebar__item--open > .pc-sidebar__toggle > & {
|
|
440
|
-
transform: rotate(-90deg);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
1
|
+
/* ========================================
|
|
2
|
+
Sidebar Component
|
|
3
|
+
Sidebar navigation with nested submenus
|
|
4
|
+
======================================== */
|
|
5
|
+
@use 'variables/index' as *;
|
|
6
|
+
@use 'resize-handle' as *;
|
|
7
|
+
|
|
8
|
+
// Local CSS variables for sidebar (runtime state, modified by JS for resize)
|
|
9
|
+
:root {
|
|
10
|
+
--pc-local-sidebar-width: #{$sidebar-width};
|
|
11
|
+
--pc-local-sidebar-min-width: 18rem;
|
|
12
|
+
--pc-local-sidebar-max-width: 50rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Sidebar layout element - width in :where() for low specificity (utility classes can override)
|
|
16
|
+
:where(.pc-layout__sidebar) {
|
|
17
|
+
width: var(--pc-local-sidebar-width);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.pc-layout__sidebar {
|
|
21
|
+
background-color: var(--pc-sidebar-bg, var(--base-page-bg));
|
|
22
|
+
border-inline-end: $border-width-base solid var(--pc-border-color); // RTL: flips to border-left
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
position: relative;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Resize handle on the sidebar edge. Shows the shared resize knob (see
|
|
28
|
+
// _resize-handle.scss) so it's the same control as the splitter gutter — slim on
|
|
29
|
+
// desktop, chunkier on small viewports. The knob is sticky at mid-viewport so it
|
|
30
|
+
// stays on screen on long, page-height sidebars.
|
|
31
|
+
.pc-sidebar-resize {
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 0;
|
|
34
|
+
inset-inline-end: 0; // RTL: flips to left
|
|
35
|
+
width: 6px;
|
|
36
|
+
height: 100%;
|
|
37
|
+
cursor: ew-resize;
|
|
38
|
+
z-index: 100;
|
|
39
|
+
// Own the touch gesture. Without this the browser's touch-action heuristic can
|
|
40
|
+
// interpret a drag as a scroll/pan and CANCEL the touch sequence after
|
|
41
|
+
// touchstart — so touchmove never reaches the resize handler and the drag
|
|
42
|
+
// silently dies. Harmless for mouse (mousedown path is unaffected).
|
|
43
|
+
touch-action: none;
|
|
44
|
+
// Flex so the sticky knob can pin to the inline-end edge and ride the viewport.
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: flex-end;
|
|
47
|
+
align-items: flex-start;
|
|
48
|
+
|
|
49
|
+
// Shared grab-knob. `position: sticky; top: 50vh` keeps it at the middle of the
|
|
50
|
+
// VISIBLE viewport, not the middle of the sidebar's full length — on a long
|
|
51
|
+
// page the sidebar stretches to the whole scroll height, so a plain top:50%
|
|
52
|
+
// knob ends up far off-screen. flex-end pins it to the inline-end edge;
|
|
53
|
+
// translateX(50%) straddles it onto the sidebar's border line; translateY(-50%)
|
|
54
|
+
// centres it on the 50vh sticky point.
|
|
55
|
+
&::before {
|
|
56
|
+
@include pa-resize-knob($axis: block);
|
|
57
|
+
position: sticky;
|
|
58
|
+
top: 50vh;
|
|
59
|
+
transform: translate(50%, -50%);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&:hover::before,
|
|
63
|
+
&--active::before {
|
|
64
|
+
@include pa-resize-knob-active;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Touch: widen only the (invisible) grab area to a finger-sized target. The knob
|
|
69
|
+
// LOOK is viewport-driven (handled by the shared mixin), so it's unchanged here.
|
|
70
|
+
@media (pointer: coarse) {
|
|
71
|
+
.pc-sidebar-resize {
|
|
72
|
+
width: 24px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Sticky LAYOUT mode: the whole page doesn't scroll (body overflow:hidden) and
|
|
77
|
+
// the sidebar is already ~viewport height, where position:sticky never engages.
|
|
78
|
+
// Absolute-centre the knob instead — 50% of a viewport-height sidebar IS the
|
|
79
|
+
// viewport centre.
|
|
80
|
+
body.pc-layout--sticky .pc-sidebar-resize::before {
|
|
81
|
+
position: absolute;
|
|
82
|
+
top: 50%;
|
|
83
|
+
inset-inline-end: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Disable text selection during resize
|
|
87
|
+
body.pc-sidebar-resizing {
|
|
88
|
+
cursor: ew-resize !important;
|
|
89
|
+
user-select: none;
|
|
90
|
+
|
|
91
|
+
* {
|
|
92
|
+
cursor: ew-resize !important;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Sticky mode - sidebar scrolls
|
|
97
|
+
body.pc-layout--sticky .pc-layout__sidebar {
|
|
98
|
+
overflow-y: auto;
|
|
99
|
+
overflow-y: overlay; // Chromium: scrollbar overlays content, doesn't reduce width
|
|
100
|
+
overflow-x: visible; // Allow tooltips to escape (right in LTR, left in RTL)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Sidebar component (separate BEM block)
|
|
104
|
+
.pc-sidebar {
|
|
105
|
+
&__nav {
|
|
106
|
+
padding: $spacing-base 0;
|
|
107
|
+
|
|
108
|
+
ul {
|
|
109
|
+
list-style: none;
|
|
110
|
+
margin: 0;
|
|
111
|
+
padding: 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&__item {
|
|
116
|
+
// Base styles for all sidebar items (regular links and submenu toggles)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Flat section heading — a non-interactive group label ("PROJECT",
|
|
120
|
+
// "GUIDES", "LIVE DEMOS"). Use when the nav is grouped but the groups are
|
|
121
|
+
// NOT collapsible; for collapsible groups use __toggle + __submenu instead.
|
|
122
|
+
// Rendered as an <li class="pc-sidebar__section"> inside __nav's <ul>, so it
|
|
123
|
+
// sits in the list flow without a bullet (the <ul> already resets list-style).
|
|
124
|
+
&__section {
|
|
125
|
+
padding: $spacing-md $spacing-base $spacing-xs; // 16px inset aligns with __link text
|
|
126
|
+
font-size: $font-size-xs;
|
|
127
|
+
font-weight: $font-weight-semibold;
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
letter-spacing: 0.05em;
|
|
130
|
+
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
131
|
+
user-select: none;
|
|
132
|
+
|
|
133
|
+
// First heading shouldn't push the whole nav down
|
|
134
|
+
&:first-child {
|
|
135
|
+
padding-top: $spacing-xs;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Thin horizontal rule separating groups of items — e.g. between nav items
|
|
140
|
+
// folded in from the navbar (fit.js nav-collapse, sidebar sink) and the
|
|
141
|
+
// sidebar's own links. Rendered as an <li class="pc-sidebar__divider"> so it
|
|
142
|
+
// sits in the list flow; carries no text.
|
|
143
|
+
&__divider {
|
|
144
|
+
height: 0;
|
|
145
|
+
margin: $spacing-xs $spacing-base;
|
|
146
|
+
padding: 0;
|
|
147
|
+
border: 0;
|
|
148
|
+
border-top: $border-width-base solid var(--pc-border-color);
|
|
149
|
+
list-style: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&__link {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: $sidebar-item-gap;
|
|
156
|
+
padding: $sidebar-padding;
|
|
157
|
+
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
158
|
+
text-decoration: none;
|
|
159
|
+
font-weight: $font-weight-medium;
|
|
160
|
+
border-inline-end: $border-width-thick solid transparent; // RTL: flips to border-left
|
|
161
|
+
|
|
162
|
+
body.loaded & {
|
|
163
|
+
transition: all $transition-fast $easing-snappy;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
&:hover {
|
|
167
|
+
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
168
|
+
background-color: var(--pc-accent-light);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&--active {
|
|
172
|
+
color: var(--pc-accent);
|
|
173
|
+
background-color: var(--pc-accent-hover);
|
|
174
|
+
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
175
|
+
font-weight: $font-weight-semibold;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Search affordance at the top of the sidebar — a TRIGGER (a consumer wires it
|
|
180
|
+
// to open a command palette / their own search), rendered as a framed
|
|
181
|
+
// faux-input row so it reads as "search" rather than another nav link:
|
|
182
|
+
// magnifier icon + muted label, inset from the sidebar edges. In icon-collapse
|
|
183
|
+
// mode it sheds the frame and centres to just the icon, exactly like the links
|
|
184
|
+
// below (its .pc-sidebar__label is hidden by the shared icon-collapse rule).
|
|
185
|
+
&__search {
|
|
186
|
+
display: flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
gap: $sidebar-item-gap;
|
|
189
|
+
width: calc(100% - #{$spacing-sm} * 2);
|
|
190
|
+
margin: $spacing-sm;
|
|
191
|
+
// Sized on the md input height (35px) to match the navbar search boxes,
|
|
192
|
+
// rather than the icon + $sidebar-padding metric of a nav row (40px). Trade-off:
|
|
193
|
+
// it sits ~5px shorter than the links below it. Block padding is dropped (fixed
|
|
194
|
+
// height + align-items:center owns the vertical).
|
|
195
|
+
// Inline padding: right = $spacing-base (the nav-row inset); left = $spacing-sm
|
|
196
|
+
// so that margin ($spacing-sm) + padding-left ($spacing-sm) = $spacing-base —
|
|
197
|
+
// landing the magnifier exactly where the nav-row icons below it sit, instead
|
|
198
|
+
// of one box-margin too far in.
|
|
199
|
+
height: #{$base-input-size-md-height}rem;
|
|
200
|
+
padding: 0 $spacing-base 0 $spacing-sm;
|
|
201
|
+
color: var(--pc-text-color-2);
|
|
202
|
+
background-color: var(--base-input-bg);
|
|
203
|
+
border: $border-width-base solid var(--pc-border-color);
|
|
204
|
+
border-radius: var(--pc-border-radius);
|
|
205
|
+
text-align: start; // RTL: flips to right
|
|
206
|
+
font-weight: $font-weight-medium;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
|
|
209
|
+
// Honor the standard HTML hidden attribute (display:flex above beats the UA
|
|
210
|
+
// `[hidden]{display:none}` rule otherwise).
|
|
211
|
+
&[hidden] {
|
|
212
|
+
display: none;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
body.loaded & {
|
|
216
|
+
transition: all $transition-fast $easing-snappy;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
&:hover {
|
|
220
|
+
// $accent-color (foundation) — was $input-focus-border-color, an exact
|
|
221
|
+
// alias; inlined so the shell no longer depends on the component layer
|
|
222
|
+
// (variables/_components.scss now lives in pure-admin-core).
|
|
223
|
+
border-color: $accent-color;
|
|
224
|
+
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Collapsed rail ONLY (icon-collapse mode AND actually collapsed, i.e.
|
|
228
|
+
// body.sidebar-hidden): drop the frame, icon-only, matching link padding so
|
|
229
|
+
// the icon lands in the same column as every other sidebar icon. Must be
|
|
230
|
+
// gated by .sidebar-hidden — .pc-layout__sidebar--icon-collapse is the MODE
|
|
231
|
+
// class and stays on the element while the rail is expanded, so an unscoped
|
|
232
|
+
// rule would strip the search frame in the expanded state too.
|
|
233
|
+
.sidebar-hidden .pc-layout__sidebar--icon-collapse & {
|
|
234
|
+
width: auto;
|
|
235
|
+
margin: 0;
|
|
236
|
+
padding: $sidebar-padding;
|
|
237
|
+
background: transparent;
|
|
238
|
+
border-color: transparent;
|
|
239
|
+
border-radius: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Type-and-go variant: the framed row hosts a <form> whose native GET submit
|
|
243
|
+
// redirects to a results page on Enter — instead of a button that opens the
|
|
244
|
+
// palette. A single text field submits on Enter with no submit button; the
|
|
245
|
+
// magnifier is a type="submit" so that in icon-collapse mode (field hidden)
|
|
246
|
+
// clicking it still submits — an empty query lands on the bare results page.
|
|
247
|
+
// Keeps the base $sidebar-padding frame untouched so the outer box is
|
|
248
|
+
// identical to the trigger button; the submit icon mirrors .pc-sidebar__icon
|
|
249
|
+
// (fixed size, line-height 1) so it drives the row height exactly like a link,
|
|
250
|
+
// and the borderless field just fills the rest. (Don't feed $sidebar-padding —
|
|
251
|
+
// a two-value 0.5rem/1rem token — to padding-block: it maps to top/bottom and
|
|
252
|
+
// inflates the box.)
|
|
253
|
+
&--input {
|
|
254
|
+
cursor: text;
|
|
255
|
+
|
|
256
|
+
.pc-sidebar__search-icon {
|
|
257
|
+
width: $sidebar-icon-size;
|
|
258
|
+
min-width: $sidebar-icon-size;
|
|
259
|
+
height: $sidebar-icon-size;
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
flex-shrink: 0;
|
|
264
|
+
padding: 0;
|
|
265
|
+
border: none;
|
|
266
|
+
background: transparent;
|
|
267
|
+
color: inherit;
|
|
268
|
+
font-size: $font-size-base;
|
|
269
|
+
line-height: 1; // prevent emoji baseline shift, matching __icon
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.pc-sidebar__search-field {
|
|
274
|
+
flex: 1;
|
|
275
|
+
min-width: 0;
|
|
276
|
+
border: none;
|
|
277
|
+
outline: none;
|
|
278
|
+
background: transparent;
|
|
279
|
+
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
280
|
+
font: inherit;
|
|
281
|
+
padding: 0;
|
|
282
|
+
|
|
283
|
+
&::placeholder {
|
|
284
|
+
color: var(--pc-text-color-2);
|
|
285
|
+
opacity: $opacity-muted;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
&::-webkit-search-decoration,
|
|
289
|
+
&::-webkit-search-cancel-button {
|
|
290
|
+
-webkit-appearance: none;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Collapsed rail ONLY: the input variant sheds its field and centres on the
|
|
296
|
+
// submit icon; clicking it submits with an empty query (the bare results
|
|
297
|
+
// page). Gated by .sidebar-hidden so the field reappears when the icon-collapse
|
|
298
|
+
// rail is expanded (see frame rule above for why the mode class isn't enough).
|
|
299
|
+
.sidebar-hidden .pc-layout__sidebar--icon-collapse &--input {
|
|
300
|
+
justify-content: center;
|
|
301
|
+
|
|
302
|
+
.pc-sidebar__search-field {
|
|
303
|
+
display: none;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
&__toggle {
|
|
309
|
+
// For submenu toggle buttons (non-link items)
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: $sidebar-item-gap;
|
|
313
|
+
padding: $sidebar-padding;
|
|
314
|
+
color: var(--pc-sidebar-text-secondary, var(--base-text-color-2));
|
|
315
|
+
background: none;
|
|
316
|
+
border: none;
|
|
317
|
+
width: 100%;
|
|
318
|
+
text-align: start; // RTL: flips to right
|
|
319
|
+
font-weight: $font-weight-medium;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
|
|
322
|
+
body.loaded & {
|
|
323
|
+
transition: all $transition-fast $easing-snappy;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
&:hover {
|
|
327
|
+
color: var(--pc-sidebar-text, var(--base-text-color-1));
|
|
328
|
+
background-color: var(--pc-accent-light);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
&--active {
|
|
332
|
+
color: var(--pc-accent);
|
|
333
|
+
background-color: var(--pc-accent-hover);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Icon and label styles
|
|
338
|
+
&__icon {
|
|
339
|
+
width: $sidebar-icon-size;
|
|
340
|
+
min-width: $sidebar-icon-size;
|
|
341
|
+
height: $sidebar-icon-size;
|
|
342
|
+
display: flex;
|
|
343
|
+
align-items: center;
|
|
344
|
+
justify-content: center;
|
|
345
|
+
flex-shrink: 0;
|
|
346
|
+
font-size: $font-size-base;
|
|
347
|
+
text-align: center;
|
|
348
|
+
line-height: 1; // Prevent emoji baseline shift
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
&__label {
|
|
352
|
+
flex: 1;
|
|
353
|
+
overflow: hidden;
|
|
354
|
+
text-overflow: ellipsis;
|
|
355
|
+
white-space: nowrap;
|
|
356
|
+
|
|
357
|
+
body.loaded & {
|
|
358
|
+
transition: opacity $transition-normal $easing-snappy, width $transition-normal $easing-snappy;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Submenu styles - Level 2
|
|
363
|
+
&__submenu {
|
|
364
|
+
list-style: none;
|
|
365
|
+
margin: 0;
|
|
366
|
+
padding: 0;
|
|
367
|
+
display: none;
|
|
368
|
+
background-color: var(--pc-sidebar-submenu-bg, var(--base-subtle-bg));
|
|
369
|
+
|
|
370
|
+
&--open {
|
|
371
|
+
display: block;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Level 2 submenu links
|
|
376
|
+
&__submenu > &__item > &__link {
|
|
377
|
+
padding-inline-start: $sidebar-submenu-indent; // RTL: flips to padding-right
|
|
378
|
+
font-size: $font-size-md;
|
|
379
|
+
border-inline-end: $border-width-medium solid transparent; // RTL: flips to border-left
|
|
380
|
+
|
|
381
|
+
&:hover {
|
|
382
|
+
background-color: var(--pc-sidebar-submenu-hover-bg, var(--base-hover-bg));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
&--active {
|
|
386
|
+
background-color: var(--pc-sidebar-submenu-active-bg, var(--base-active-bg));
|
|
387
|
+
color: var(--pc-sidebar-submenu-active-text, var(--base-text-color-1));
|
|
388
|
+
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Level 2 submenu toggles (for third level)
|
|
393
|
+
&__submenu > &__item > &__toggle {
|
|
394
|
+
padding-inline-start: $sidebar-submenu-indent; // RTL: flips to padding-right
|
|
395
|
+
font-size: $font-size-md;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Level 3 submenu links
|
|
399
|
+
&__submenu &__submenu > &__item > &__link {
|
|
400
|
+
padding-inline-start: calc($sidebar-submenu-indent * $sidebar-submenu-indent-multiplier); // RTL: flips
|
|
401
|
+
font-size: $font-size-sm;
|
|
402
|
+
border-inline-end: $border-width-base solid transparent; // RTL: flips to border-left
|
|
403
|
+
|
|
404
|
+
&--active {
|
|
405
|
+
border-inline-end-color: var(--pc-accent); // RTL: flips to border-left-color
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Chevron icon for toggle buttons. Rendered as an SVG mask painted in
|
|
410
|
+
// currentColor (was a bare "›" text glyph, which rendered inconsistently
|
|
411
|
+
// across platforms/fonts). Reads the shared --base-icon-chevron so it aligns
|
|
412
|
+
// with pure-admin + the web components; --pa-icon-chevron (core) lets pure-admin
|
|
413
|
+
// override, and #{$base-icon-chevron} is the standalone literal fallback.
|
|
414
|
+
// `font-size: 0` collapses any legacy "›" still present in older markup so it
|
|
415
|
+
// never double-renders alongside the mask.
|
|
416
|
+
&__chevron {
|
|
417
|
+
margin-inline-start: auto; // RTL: flips to margin-right: auto
|
|
418
|
+
inline-size: 1.6rem;
|
|
419
|
+
block-size: 1.6rem;
|
|
420
|
+
flex-shrink: 0;
|
|
421
|
+
font-size: 0;
|
|
422
|
+
background-color: currentColor;
|
|
423
|
+
-webkit-mask: var(--base-icon-chevron, #{$base-icon-chevron}) center / 1.2rem no-repeat;
|
|
424
|
+
mask: var(--base-icon-chevron, #{$base-icon-chevron}) center / 1.2rem no-repeat;
|
|
425
|
+
|
|
426
|
+
body.loaded & {
|
|
427
|
+
transition: transform $transition-fast $easing-snappy;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Chevron points right (›) in LTR, left (‹) in RTL
|
|
431
|
+
// When open, rotates 90deg clockwise in LTR, -90deg (counter-clockwise) in RTL.
|
|
432
|
+
// Scoped to the item's OWN chevron (direct toggle child) — a plain descendant
|
|
433
|
+
// selector would also rotate nested items' chevrons whenever an ANCESTOR is
|
|
434
|
+
// open, so a closed sub-group would wrongly show a "down" chevron.
|
|
435
|
+
.pc-sidebar__item--open > .pc-sidebar__toggle > & {
|
|
436
|
+
transform: rotate(90deg);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
[dir="rtl"] .pc-sidebar__item--open > .pc-sidebar__toggle > & {
|
|
440
|
+
transform: rotate(-90deg);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|