@apliteni/apliteni-ui 0.32.0 → 0.33.1

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.
@@ -9,12 +9,32 @@
9
9
  .ui-nav :where(ul, ol) { list-style: none; margin: 0; padding: 0; } /* floor, not ceiling */
10
10
 
11
11
  /* -- Sidebar rail -------------------------------------------------------- */
12
+ /* The rail's two widths, and the one place they are written. --ui-nav-col is the
13
+ open column. --ui-nav-strip is twice a row's own glyph centre — the row's
14
+ padding plus half a glyph — so a strip that wide has the glyph standing in the
15
+ middle of it, exactly where it stood in the open rail. That is what lets the
16
+ fold animate: the column keeps --ui-nav-col and is clipped to the strip, so
17
+ the width travels and nothing inside it is laid out again. layout.css adds the
18
+ shell rail's own inset to both. stories/apps/shell-states.test.js derives the
19
+ strip from the resolved cascade and refuses a number that has drifted from it.
20
+ Declared for the shell as well as the nav, because the shell's rail is this
21
+ nav with an inset around it, and two copies of 41 is the drift itself. */
22
+ .ui-nav--side,
23
+ .ui-app { --ui-nav-col: 216px; --ui-nav-strip: 41px; }
12
24
  .ui-nav--side {
13
25
  display: flex;
14
26
  flex-direction: column;
15
27
  gap: 4px;
16
- width: 216px;
28
+ width: var(--ui-nav-col);
29
+ /* The width is the only property the fold travels. Nothing is clipped here:
30
+ inside the shell it is the rail that clips, and a clip of its own would have
31
+ been a second scroll box cutting the rows off at the rail's height. */
32
+ transition: width var(--dur-med) var(--ease);
17
33
  }
34
+ /* Every block keeps the open column while the nav clips to the strip. Without
35
+ this each one stretches to whatever the nav is wide and every row inside it is
36
+ laid out again on each frame of the fold. */
37
+ .ui-nav--side > * { width: var(--ui-nav-col); flex: none; }
18
38
  .ui-nav__section + .ui-nav__section { margin-top: 14px; }
19
39
  .ui-nav__list { display: flex; flex-direction: column; gap: 2px; }
20
40
  .ui-nav__cap {
@@ -23,6 +43,9 @@
23
43
  font-weight: var(--weight-medium);
24
44
  color: var(--muted);
25
45
  padding: 4px 12px 8px;
46
+ /* Goes on the labels' clock — a section heading is words, and the fold takes
47
+ every word on the rail together. */
48
+ transition: opacity var(--dur-fast) var(--ease);
26
49
  }
27
50
 
28
51
  /* Item row — link, group toggle, and leaf share the same skin */
@@ -72,7 +95,15 @@
72
95
  .ui-nav__ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; opacity: 0.72; }
73
96
  .ui-nav__item.is-active .ui-nav__ic svg,
74
97
  .ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: var(--accent); } /* motion: still — the glyph brightens with its row, whose own highlight already transitions */
75
- .ui-nav__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
98
+ .ui-nav__label {
99
+ flex: 1 1 auto; min-width: 0;
100
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
101
+ /* The words go before the closing edge reaches them, which is most of why the
102
+ fold is worth animating — a label cut through by that edge reads as the rail
103
+ eating it. --dur-fast against the width's --dur-med, as the reference times
104
+ its own. */
105
+ transition: opacity var(--dur-fast) var(--ease);
106
+ }
76
107
 
77
108
  /* Destructive row (sign out, revoke). Quiet at rest, --pink once a pointer or
78
109
  the keyboard reaches it — as .ui-btn--danger and .ui-dropdown__item do. */
@@ -111,6 +142,10 @@
111
142
  line-height: 1;
112
143
  background: var(--surface-3);
113
144
  color: var(--dim);
145
+ /* The count fades with the words. It is never lost: sidebarNav() spells it
146
+ into the row's aria-label, so "Pending 3" is what a reader hears at either
147
+ width. */
148
+ transition: opacity var(--dur-fast) var(--ease);
114
149
  }
115
150
  .ui-nav__badge.is-accent { background: var(--glow-purple); color: var(--accent); }
116
151
  .ui-nav__badge.is-live { background: var(--chip-success-fill); color: var(--chip-success-ink); }
@@ -135,7 +170,7 @@
135
170
  border-right: 1.6px solid var(--muted);
136
171
  border-bottom: 1.6px solid var(--muted);
137
172
  transform: rotate(-45deg);
138
- transition: transform var(--dur-fast) var(--ease);
173
+ transition: transform var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
139
174
  }
140
175
  .ui-nav__group.is-open > .ui-nav__toggle .ui-nav__caret { transform: rotate(45deg); }
141
176
  .ui-nav__sub {
@@ -164,16 +199,59 @@
164
199
  /* Footer slot (divider + trailing links, e.g. sign-out) */
165
200
  .ui-nav__foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
166
201
 
167
- /* Collapsed (icon-only) rail */
168
- .ui-nav--side.is-collapsed { width: 60px; align-items: stretch; }
202
+ /* A row with no glyph, on a rail folded to its glyphs, would be a blank target.
203
+ It gets a dot on the glyph column instead — out of flow, so the open rail's
204
+ labels stay where they are, and resting at nothing, so what the fold does is
205
+ raise it rather than conjure it. 18px is the row's own 12px padding plus half
206
+ a 17px glyph less half the dot: the column the glyph beside it stands on.
207
+ sidebarNav() takes `icon` as optional at every level, sub rows included. */
208
+ .ui-nav--side .ui-nav__item:not(:has(.ui-nav__ic))::after {
209
+ content: "";
210
+ position: absolute;
211
+ left: 18px; top: 50%;
212
+ width: 5px; height: 5px;
213
+ margin-top: -2.5px;
214
+ border-radius: 50%;
215
+ background: currentColor;
216
+ opacity: 0;
217
+ transition: opacity var(--dur-fast) var(--ease);
218
+ }
219
+
220
+ /* The nested list's indent, which the strip has no room for. It travels with the
221
+ width rather than vanishing under it: the guide hairline thins to nothing and
222
+ the children walk back onto the glyph column their parent stands on. */
223
+ .ui-nav__sub {
224
+ transition: margin-inline-start var(--dur-med) var(--ease),
225
+ padding-inline-start var(--dur-med) var(--ease),
226
+ border-inline-start-width var(--dur-med) var(--ease);
227
+ }
228
+
229
+ /* Collapsed (icon-only) rail — the same fold the shell's rail makes, drawn once
230
+ rather than pressed: the column keeps its width and the nav clips to the
231
+ strip, so no row is laid out again and no glyph moves. A group's children stay
232
+ rows here, because wireNav() toggles the list's `hidden` attribute and nothing
233
+ else, so folding .ui-nav__sub away left the toggle announcing a list it could
234
+ not open. What goes is the indent and the words, and sidebarNav({ collapsed })
235
+ hands each row its name as a `title` so a pointer can still read it. The
236
+ shell's rail draws a chip of its own instead, on keyboard focus as well — see
237
+ layout.css. */
238
+ .ui-nav--side.is-collapsed {
239
+ width: var(--ui-nav-strip);
240
+ /* clip, not hidden: a standalone rail has nothing around it to clip for it,
241
+ and `hidden` would make this a scroll box in both axes and cut the rows off
242
+ at whatever height it was given. `clip` across leaves `visible` down alone,
243
+ which is the one pair that does. */
244
+ overflow-x: clip;
245
+ overflow-y: visible;
246
+ }
169
247
  .ui-nav--side.is-collapsed .ui-nav__cap,
170
248
  .ui-nav--side.is-collapsed .ui-nav__label,
171
249
  .ui-nav--side.is-collapsed .ui-nav__badge,
172
- .ui-nav--side.is-collapsed .ui-nav__caret { display: none; } /* motion: still — folding the rail is a layout change of the whole rail; animating these would reflow the page */
173
- .ui-nav--side.is-collapsed .ui-nav__item { justify-content: center; padding: 11px 0; gap: 0; }
174
- .ui-nav--side.is-collapsed .ui-nav__ic svg { width: 19px; height: 19px; stroke-width: 2; }
175
- .ui-nav--side.is-collapsed .ui-nav__item.is-active::before { left: 3px; }
176
- .ui-nav--side.is-collapsed .ui-nav__foot { text-align: center; }
250
+ .ui-nav--side.is-collapsed .ui-nav__caret { opacity: 0; pointer-events: none; }
251
+ .ui-nav--side.is-collapsed .ui-nav__sub {
252
+ margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0;
253
+ }
254
+ .ui-nav--side.is-collapsed .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
177
255
 
178
256
  /* -- Horizontal tabs ----------------------------------------------------- */
179
257
  .ui-nav--tabs {
@@ -26,7 +26,10 @@
26
26
  max-width: var(--prose-caption);
27
27
  padding: var(--space-2) var(--space-3);
28
28
  background: var(--ui-tip-bg);
29
- border: 1px solid var(--border);
29
+ /* The two-step edge and the drop: --border-strong outside, --border one pixel
30
+ inside it, then the broad faint drop. why: docs/specification.md#elevation */
31
+ border: 1px solid var(--border-strong);
32
+ box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
30
33
  border-radius: var(--radius-sm);
31
34
  font-family: var(--font-sans);
32
35
  font-size: var(--text-xs);
@@ -123,11 +123,18 @@
123
123
  left: 0;
124
124
  min-width: 266px;
125
125
  background: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
126
- border: 1px solid var(--border);
126
+ /* The two-step edge and the drop: --border-strong outside, --border one pixel
127
+ inside it, then the broad faint drop. why: docs/specification.md#elevation */
128
+ border: 1px solid var(--border-strong);
129
+ box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
127
130
  border-radius: 16px;
128
131
  padding: 6px;
129
132
  opacity: 0;
130
133
  visibility: hidden;
134
+ /* Drawn for the whole of the fade out, so the rows do not vanish mid-fade — and a box
135
+ that is drawn is a box that is hit. The open rule takes this back.
136
+ why: docs/specification.md#the-dropdown-panel */
137
+ pointer-events: none;
131
138
  transform: translateY(-6px);
132
139
  /* Named properties, not the bare shorthand: `transition: 0.18s` is `all`, and `all`
133
140
  includes visibility — held at the OLD value for the whole duration, so the menu is
@@ -138,7 +145,20 @@
138
145
  visibility var(--dur-med) linear;
139
146
  z-index: var(--z-dropdown);
140
147
  }
141
- .vsw.open .vsw__menu { opacity: 1; visibility: visible; transform: translateY(0); }
148
+ /* Open, the menu is clickable and visible AT ONCE. Both menus in this file are
149
+ `wireDropdown()` in bespoke clothes — same hooks, same keyboard, same fade — so they
150
+ owe a panel's two rules as much as `.ui-dropdown__panel` does: `visibility` off the
151
+ open transition, because the frame the open class lands is the frame the wiring moves
152
+ focus onto a row in and a browser will not focus inside a hidden box; and
153
+ `pointer-events` back on, because the closed rule turned it off.
154
+ why: docs/specification.md#the-dropdown-panel */
155
+ .vsw.open .vsw__menu {
156
+ opacity: 1;
157
+ visibility: visible;
158
+ pointer-events: auto;
159
+ transform: translateY(0);
160
+ transition-property: opacity, transform;
161
+ }
142
162
  .vopt {
143
163
  display: flex;
144
164
  gap: 11px;
@@ -160,7 +180,8 @@
160
180
  transition: background var(--dur-fast) var(--ease);
161
181
  }
162
182
  /* The menu is raised, so a hovered row takes the step ABOVE it — see
163
- src/styles/dropdown.css:130. why: docs/specification.md#elevation */
183
+ src/styles/dropdown.css:144 `.ui-dropdown__item:hover`.
184
+ why: docs/specification.md#elevation */
164
185
  .vopt:hover { background: var(--surface-3); }
165
186
  .vopt .vname { font-family: var(--font-sans); font-size: 12.5px; color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
166
187
  .vopt .vmeta { font-size: 11px; color: var(--muted); margin-top: 2px; }
@@ -205,11 +226,15 @@
205
226
  right: 0; top: 44px;
206
227
  width: 252px;
207
228
  background: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
208
- border: 1px solid var(--border);
229
+ /* The two-step edge and the drop: --border-strong outside, --border one pixel
230
+ inside it, then the broad faint drop. why: docs/specification.md#elevation */
231
+ border: 1px solid var(--border-strong);
232
+ box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
209
233
  border-radius: 14px;
210
234
  overflow: hidden;
211
235
  opacity: 0;
212
236
  visibility: hidden;
237
+ pointer-events: none; /* see .vsw__menu above */
213
238
  transform: translateY(-6px);
214
239
  /* Named properties — see .vsw__menu above. */
215
240
  transition:
@@ -218,7 +243,15 @@
218
243
  visibility var(--dur-med) linear;
219
244
  z-index: var(--z-dropdown);
220
245
  }
221
- .acct.open .amenu { opacity: 1; visibility: visible; transform: translateY(0); }
246
+ /* Clickable and visible at once — see .vsw.open .vsw__menu above. The row this one
247
+ costs is `.aout`: a stray click in the fade out ends the reader's session. */
248
+ .acct.open .amenu {
249
+ opacity: 1;
250
+ visibility: visible;
251
+ pointer-events: auto;
252
+ transform: translateY(0);
253
+ transition-property: opacity, transform;
254
+ }
222
255
  .amenu .ahead { padding: 13px 15px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 11px; }
223
256
  .amenu .ahead .avatar { cursor: default; }
224
257
  .amenu .ahead .aw { min-width: 0; display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
@@ -133,9 +133,10 @@
133
133
  --pink: #e97ca5;
134
134
  --amber: #ffcf6a;
135
135
 
136
- /* The elevation ladder, bottom to top. Nothing casts; a surface says how high it is
137
- with its step and with the hairline around it, and --muted caps how far the ladder
138
- can go. why: docs/specification.md#elevation */
136
+ /* The elevation ladder, bottom to top. A surface says how high it is with its step and
137
+ with the hairline around it, and --muted caps how far the ladder can go. Nothing casts
138
+ below the floating step; the floating step itself adds --elev-drop under its inner
139
+ line and nothing else in the kit does. why: docs/specification.md#elevation */
139
140
  --bg: #0e0d14;
140
141
  --surface-2: #161520; /* SUNKEN — fields, tracks, disabled, code */
141
142
  --surface: #211e2d; /* the card */
@@ -214,9 +215,10 @@
214
215
  --signal-solid-info: var(--cyan);
215
216
  --signal-solid-neutral: var(--muted);
216
217
 
217
- /* DEPRECATED since #295: the kit casts no shadow anywhere. Published still, so a
218
- consumer reading one gets nothing rather than an unresolved var(), and a consumer who
219
- wants a cast shadow sets them. Nothing in src/ reads them.
218
+ /* DEPRECATED since #295: no surface below the floating step casts, and the floating
219
+ step reads --elev-drop rather than any of these. Published still, so a consumer
220
+ reading one gets nothing rather than an unresolved var(), and a consumer who wants a
221
+ cast shadow sets them. Nothing in src/ reads them.
220
222
 
221
223
  A transparent shadow, not `none`, because these are read in a LIST. The kit's own
222
224
  pre-0.32 pattern was `box-shadow: var(--shadow-lg), var(--ring)`, and `none` is only
@@ -239,6 +241,18 @@
239
241
  --shadow-ink: #000000;
240
242
  --sheen: #ffffff;
241
243
 
244
+ /* The drops of the floating treatment, in Primer's --shadow-floating-* order:
245
+ the inner half of the two-step edge, then these. The line is NOT in this
246
+ token and cannot be — a var() inside a custom property is substituted on the
247
+ element that DECLARES it, so an --elev-edge read here resolves at :root and
248
+ nothing below could re-point it. A floating surface writes the pair itself:
249
+ box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
250
+ Dark spends 62% / 50% of the ink and reaches 1.20 at the drop's core, having
251
+ nowhere to go on a near-black page. why: docs/specification.md#elevation */
252
+ --elev-drop:
253
+ 0 14px 30px -12px color-mix(in srgb, var(--shadow-ink) 62%, transparent),
254
+ 0 3px 9px -4px color-mix(in srgb, var(--shadow-ink) 50%, transparent);
255
+
242
256
  /* Backdrop that dims the page behind an overlay (drawer, modal, composer). */
243
257
  --scrim: rgba(0, 0, 0, 0.58);
244
258
 
@@ -345,6 +359,13 @@
345
359
  --shadow-ink: #1e1e32;
346
360
  --sheen: #ffffff;
347
361
 
362
+ /* 18% / 10% against dark's 62% / 50%. Light's ink is #1e1e32 on a card at
363
+ #f8f9fc, and 18% is what puts the drop core at #d1d2d8 — 1.44, the strongest
364
+ separation either theme gets. why: docs/specification.md#elevation */
365
+ --elev-drop:
366
+ 0 14px 30px -12px color-mix(in srgb, var(--shadow-ink) 18%, transparent),
367
+ 0 3px 9px -4px color-mix(in srgb, var(--shadow-ink) 10%, transparent);
368
+
348
369
  --scrim: rgba(20, 22, 40, 0.38);
349
370
 
350
371
  --grad-from: var(--purple-light);