@justin_evo/evo-ui 1.0.2 → 1.2.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.
@@ -56,7 +56,7 @@
56
56
  color: $color-text-secondary;
57
57
  background: transparent;
58
58
  border: none;
59
- border-radius: $radius-sm;
59
+ border-radius: $evo-border-radius;
60
60
  cursor: pointer;
61
61
  text-align: left;
62
62
  text-decoration: none;
@@ -70,6 +70,11 @@
70
70
  &:hover {
71
71
  background-color: $color-surface-hover;
72
72
  color: $color-text-primary;
73
+
74
+ .navIcon {
75
+ color: $color-text-primary;
76
+ transform: translateX(1px);
77
+ }
73
78
  }
74
79
 
75
80
  &:focus-visible {
@@ -80,16 +85,10 @@
80
85
  &.active {
81
86
  background-color: $evo-primary-soft;
82
87
  color: $evo-primary-color;
88
+ font-weight: 600;
83
89
 
84
- &::before {
85
- content: '';
86
- position: absolute;
87
- left: 0;
88
- top: 25%;
89
- bottom: 25%;
90
- width: 2px;
91
- border-radius: $radius-full;
92
- background: $evo-primary-color;
90
+ .navIcon {
91
+ color: $evo-primary-color;
93
92
  }
94
93
  }
95
94
 
@@ -121,6 +120,20 @@
121
120
  overflow: hidden;
122
121
  text-overflow: ellipsis;
123
122
  white-space: nowrap;
123
+ clip-path: inset(0 0 0 0);
124
+ transition:
125
+ opacity 200ms ease,
126
+ transform 220ms ease,
127
+ clip-path 260ms ease;
128
+ }
129
+
130
+ // When the rail expands, labels reveal top-to-bottom (per-row delay) and
131
+ // left-to-right (clip-path sweep). Only opacity/transform/clip-path transition,
132
+ // so the delay is invisible in the steady expanded state.
133
+ @for $i from 1 through 12 {
134
+ .navList .navLi:nth-child(#{$i}) .navLabel {
135
+ transition-delay: #{($i - 1) * 14}ms;
136
+ }
124
137
  }
125
138
 
126
139
  .navIcon {
@@ -132,6 +145,15 @@
132
145
  height: 1.125rem;
133
146
  font-size: 1rem;
134
147
  line-height: 1;
148
+ color: $color-text-muted;
149
+ transition:
150
+ color $transition-fast,
151
+ transform $transition-fast;
152
+
153
+ svg {
154
+ width: 1.05rem;
155
+ height: 1.05rem;
156
+ }
135
157
  }
136
158
 
137
159
  .chevron {
@@ -161,18 +183,96 @@
161
183
 
162
184
  .navGroup {
163
185
  list-style: none;
164
- margin: 0.5rem 0 0;
186
+ margin: 0.85rem 0 0;
165
187
  padding: 0;
188
+
189
+ &:first-child {
190
+ margin-top: 0.25rem;
191
+ }
166
192
  }
167
193
 
168
194
  .navGroupLabel {
169
- display: block;
195
+ display: flex;
196
+ align-items: center;
197
+ width: 100%;
170
198
  font-size: $text-xs;
171
- font-weight: 600;
199
+ font-weight: 700;
172
200
  text-transform: uppercase;
173
- letter-spacing: 0.06em;
201
+ letter-spacing: 0.08em;
174
202
  color: $color-text-muted;
175
- padding: 0.5rem 0.75rem 0.25rem;
203
+ padding: 0.4rem 0.75rem 0.3rem;
204
+ }
205
+
206
+ .navGroupLabelText {
207
+ flex: 1;
208
+ min-width: 0;
209
+ text-align: left;
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
212
+ white-space: nowrap;
213
+ }
214
+
215
+ // Collapsible group heading — same look as the static label, plus button chrome.
216
+ .navGroupToggle {
217
+ background: transparent;
218
+ border: none;
219
+ border-radius: $radius-sm;
220
+ cursor: pointer;
221
+ font-family: inherit;
222
+ transition: color $transition-fast;
223
+
224
+ &:hover {
225
+ color: $color-text-secondary;
226
+ }
227
+
228
+ &:focus-visible {
229
+ outline: $evo-btn-outline-width solid $evo-primary-focus;
230
+ outline-offset: $evo-btn-outline-offset;
231
+ }
232
+
233
+ .chevron {
234
+ margin-left: 0.4rem;
235
+ color: $color-text-muted;
236
+ }
237
+ }
238
+
239
+ .navGroupCount {
240
+ display: inline-flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ min-width: 1.15rem;
244
+ height: 1.15rem;
245
+ padding: 0 0.35rem;
246
+ margin-left: 0.4rem;
247
+ font-size: 0.65rem;
248
+ font-weight: 600;
249
+ letter-spacing: 0;
250
+ color: $color-text-secondary;
251
+ background: $color-surface-hover;
252
+ border-radius: $radius-full;
253
+ }
254
+
255
+ // Accordion panel — animates height via grid-template-rows (0fr <-> 1fr), no
256
+ // JS measuring. The inner list must clip its overflow for the collapse to read.
257
+ .navGroupPanel {
258
+ display: grid;
259
+ grid-template-rows: 1fr;
260
+ transition: grid-template-rows 260ms cubic-bezier(0.4, 0, 0.2, 1);
261
+
262
+ &[data-open='false'] {
263
+ grid-template-rows: 0fr;
264
+ }
265
+
266
+ > .navList {
267
+ min-height: 0;
268
+ overflow: hidden;
269
+ }
270
+
271
+ // Rows inside the clipped panel draw their focus ring INSET, so the
272
+ // overflow:hidden needed for the height animation can't clip it.
273
+ .navRow:focus-visible {
274
+ outline-offset: -2px;
275
+ }
176
276
  }
177
277
 
178
278
  // ---------- Skeleton ----------
@@ -321,10 +421,65 @@
321
421
  to { opacity: 1; }
322
422
  }
323
423
 
424
+ // ---------- Collapsed (icon-only rail) ----------
425
+
426
+ .navCollapsed {
427
+ .navRow,
428
+ .navQuickAction {
429
+ justify-content: center;
430
+ gap: 0;
431
+ padding-left: 0.5rem;
432
+ padding-right: 0.5rem;
433
+ }
434
+
435
+ .navLabel {
436
+ flex: 0;
437
+ opacity: 0;
438
+ transform: translateX(-4px);
439
+ clip-path: inset(0 100% 0 0);
440
+ }
441
+
442
+ // Group heading text stays in the a11y tree (it names the heading) but is
443
+ // visually hidden; the decorative count chip is removed entirely.
444
+ .navGroupLabelText {
445
+ position: absolute;
446
+ width: 1px;
447
+ height: 1px;
448
+ padding: 0;
449
+ margin: -1px;
450
+ overflow: hidden;
451
+ clip-path: inset(50%);
452
+ white-space: nowrap;
453
+ }
454
+
455
+ .navGroupCount {
456
+ display: none;
457
+ }
458
+
459
+ .navGroupLabel {
460
+ min-height: 0.5rem;
461
+ padding: 0.25rem 0;
462
+ }
463
+
464
+ // Accordion is meaningless in rail mode — force panels open.
465
+ .navGroupPanel {
466
+ grid-template-rows: 1fr;
467
+ }
468
+ }
469
+
470
+ // Collapsing fades labels out together — outrank the per-row expand stagger.
471
+ .navCollapsed .navList .navLi .navLabel {
472
+ transition-delay: 0ms;
473
+ }
474
+
324
475
  // ---------- Reduced motion ----------
325
476
 
326
477
  @media (prefers-reduced-motion: reduce) {
327
478
  .navRow,
479
+ .navRow .navIcon,
480
+ .navLabel,
481
+ .navGroupPanel,
482
+ .navGroupToggle,
328
483
  .navQuickAction,
329
484
  .chevron,
330
485
  .navTrigger,
@@ -20,6 +20,16 @@
20
20
  to { opacity: 1; transform: translateY(0); }
21
21
  }
22
22
 
23
+ @keyframes topNavRise {
24
+ from { opacity: 0; transform: translateY(0.5rem); }
25
+ to { opacity: 1; transform: translateY(0); }
26
+ }
27
+
28
+ @keyframes topNavFade {
29
+ from { opacity: 0; }
30
+ to { opacity: 1; }
31
+ }
32
+
23
33
  // ---------------------------------------------------------------------------
24
34
  // Root
25
35
  // ---------------------------------------------------------------------------
@@ -159,6 +169,61 @@
159
169
  flex-shrink: 0;
160
170
  }
161
171
 
172
+ // ---------------------------------------------------------------------------
173
+ // Search trigger (EvoTopNav.Search) — presentational ⌘K affordance
174
+ // ---------------------------------------------------------------------------
175
+
176
+ .topNavSearch {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ gap: 0.5rem;
180
+ height: 2rem;
181
+ min-width: 12rem;
182
+ padding: 0 0.625rem;
183
+ font-family: inherit;
184
+ font-size: $text-sm;
185
+ color: $color-text-muted;
186
+ background-color: $color-surface-sunken;
187
+ border: 1px solid $color-border;
188
+ border-radius: $radius-sm;
189
+ cursor: pointer;
190
+ transition:
191
+ background-color $transition-fast,
192
+ border-color $transition-fast,
193
+ color $transition-fast;
194
+
195
+ &:hover {
196
+ background-color: $color-surface-hover;
197
+ color: $color-text-secondary;
198
+ }
199
+
200
+ &:focus-visible {
201
+ outline: 2px solid $evo-primary-focus;
202
+ outline-offset: 2px;
203
+ }
204
+ }
205
+
206
+ .topNavSearchIcon {
207
+ display: inline-flex;
208
+ flex-shrink: 0;
209
+ }
210
+
211
+ .topNavSearchText {
212
+ flex: 1;
213
+ text-align: left;
214
+ }
215
+
216
+ .topNavSearchKbd {
217
+ flex-shrink: 0;
218
+ font-family: inherit;
219
+ font-size: $text-xs;
220
+ color: $color-text-muted;
221
+ background-color: $color-surface;
222
+ border: 1px solid $color-border;
223
+ border-radius: 4px;
224
+ padding: 0.0625rem 0.3125rem;
225
+ }
226
+
162
227
  // ---------------------------------------------------------------------------
163
228
  // Toggle (hamburger) — hidden above the collapse breakpoint
164
229
  // ---------------------------------------------------------------------------
@@ -360,6 +425,17 @@
360
425
  z-index: 55;
361
426
  animation: topNavOverlayFadeIn 180ms ease;
362
427
  }
428
+
429
+ .topNavSearch {
430
+ min-width: 0;
431
+ width: 2.75rem;
432
+ height: 2.75rem;
433
+ justify-content: center;
434
+ padding: 0;
435
+
436
+ .topNavSearchText,
437
+ .topNavSearchKbd { display: none; }
438
+ }
363
439
  }
364
440
 
365
441
  // Above the breakpoint, drawer-specific bits never render visually.
@@ -371,6 +447,85 @@
371
447
  }
372
448
  }
373
449
 
450
+ // ---------------------------------------------------------------------------
451
+ // Entrance animation (opt-in via `entrance` prop → data-entrance attribute)
452
+ // ---------------------------------------------------------------------------
453
+
454
+ .topNav[data-entrance='rise'] .topNavBrand,
455
+ .topNav[data-entrance='rise'] .topNavMenu > li,
456
+ .topNav[data-entrance='rise'] .topNavSearch,
457
+ .topNav[data-entrance='rise'] .topNavActions {
458
+ animation: topNavRise 440ms cubic-bezier(0.22, 1, 0.36, 1) both;
459
+ }
460
+
461
+ .topNav[data-entrance='fade'] .topNavBrand,
462
+ .topNav[data-entrance='fade'] .topNavMenu > li,
463
+ .topNav[data-entrance='fade'] .topNavSearch,
464
+ .topNav[data-entrance='fade'] .topNavActions {
465
+ animation: topNavFade 440ms ease both;
466
+ }
467
+
468
+ // Shared left-to-right stagger (applies to both variants).
469
+ .topNav[data-entrance] .topNavBrand { animation-delay: 40ms; }
470
+ .topNav[data-entrance] .topNavMenu > li:nth-child(1) { animation-delay: 110ms; }
471
+ .topNav[data-entrance] .topNavMenu > li:nth-child(2) { animation-delay: 160ms; }
472
+ .topNav[data-entrance] .topNavMenu > li:nth-child(3) { animation-delay: 210ms; }
473
+ .topNav[data-entrance] .topNavMenu > li:nth-child(4) { animation-delay: 260ms; }
474
+ .topNav[data-entrance] .topNavMenu > li:nth-child(n + 5) { animation-delay: 300ms; }
475
+ .topNav[data-entrance] .topNavSearch { animation-delay: 320ms; }
476
+ .topNav[data-entrance] .topNavActions { animation-delay: 360ms; }
477
+
478
+ // ---------------------------------------------------------------------------
479
+ // Sticky + scroll-aware behavior (opt-in via `sticky` / `scrollBehavior`)
480
+ // ---------------------------------------------------------------------------
481
+
482
+ .topNavSticky {
483
+ position: sticky;
484
+ top: 0;
485
+ z-index: 30;
486
+ }
487
+
488
+ .topNav[data-scroll] {
489
+ transition:
490
+ background-color $transition-fast,
491
+ box-shadow $transition-fast,
492
+ transform 220ms ease;
493
+
494
+ .topNavInner { transition: min-height $transition-fast; }
495
+ }
496
+
497
+ .topNav[data-scrolled] {
498
+ background-color: $color-surface; // fallback for browsers without color-mix()
499
+ background-color: color-mix(in srgb, $color-surface 85%, transparent);
500
+ -webkit-backdrop-filter: blur(10px);
501
+ backdrop-filter: blur(10px);
502
+ box-shadow: $shadow-md;
503
+ }
504
+
505
+ .topNav[data-scroll='shrink'][data-scrolled] .topNavInner {
506
+ min-height: 2.75rem;
507
+ }
508
+
509
+ .topNav[data-scroll='hide'] { will-change: transform; }
510
+ .topNav[data-scroll='hide'][data-hidden] { transform: translateY(-100%); }
511
+
512
+ // ---------------------------------------------------------------------------
513
+ // Scroll-progress line (opt-in via `showProgress`)
514
+ // ---------------------------------------------------------------------------
515
+
516
+ .topNavProgress {
517
+ position: absolute;
518
+ left: 0;
519
+ bottom: 0;
520
+ width: 100%;
521
+ height: 2px;
522
+ background: $evo-primary-color;
523
+ transform: scaleX(var(--evo-topnav-progress, 0));
524
+ transform-origin: left center;
525
+ pointer-events: none;
526
+ z-index: 31;
527
+ }
528
+
374
529
  // ---------------------------------------------------------------------------
375
530
  // Reduced motion — kill animations
376
531
  // ---------------------------------------------------------------------------
@@ -385,6 +540,16 @@
385
540
  .topNavDropdownChevron {
386
541
  transition: none;
387
542
  }
543
+
544
+ .topNavBrand,
545
+ .topNavMenu > li,
546
+ .topNavSearch,
547
+ .topNavActions {
548
+ animation: none !important;
549
+ }
550
+
551
+ &[data-scroll='hide'][data-hidden] { transition: none; }
552
+ .topNavProgress { transition: none; }
388
553
  }
389
554
 
390
555
  @media (prefers-reduced-motion: reduce) {
@@ -393,4 +558,11 @@
393
558
  .topNavBackdrop {
394
559
  animation: none;
395
560
  }
561
+
562
+ .topNav[data-entrance] .topNavBrand,
563
+ .topNav[data-entrance] .topNavMenu > li,
564
+ .topNav[data-entrance] .topNavSearch,
565
+ .topNav[data-entrance] .topNavActions {
566
+ animation: none !important;
567
+ }
396
568
  }