@claralight-design/abweb-navbar 0.1.2 → 0.1.4

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.
@@ -17,13 +17,13 @@
17
17
  max-height: 72px;
18
18
  }
19
19
 
20
- .header > * {
20
+ .header>* {
21
21
  corner-shape: unset;
22
22
  }
23
23
 
24
24
  .inner {
25
- width: calc(100% - 12px);
26
- max-width: calc(1200px - 12px);
25
+ width: calc(100% + 32px);
26
+ max-width: calc(1200px + 32px);
27
27
  position: relative;
28
28
  display: grid;
29
29
  grid-template-columns: auto minmax(0, 1fr) auto;
@@ -70,17 +70,6 @@
70
70
  order: 3;
71
71
  }
72
72
 
73
- .right {
74
- gap: 8px;
75
- }
76
-
77
- .rightSlot {
78
- display: inline-flex;
79
- align-items: center;
80
- justify-content: center;
81
- min-width: 0;
82
- }
83
-
84
73
  .centerColumn {
85
74
  min-width: 0;
86
75
  display: flex;
@@ -95,13 +84,6 @@
95
84
  flex: 0 1 auto;
96
85
  }
97
86
 
98
- .track {
99
- display: flex;
100
- flex-direction: column;
101
- will-change: transform;
102
- transform: translate3d(0, 0, 0);
103
- }
104
-
105
87
  .slide {
106
88
  min-height: 40px;
107
89
  height: 40px;
@@ -111,6 +93,12 @@
111
93
  gap: 10px;
112
94
  }
113
95
 
96
+ .leftContent {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ min-width: 0;
100
+ }
101
+
114
102
  .brandName {
115
103
  color: var(--color-text);
116
104
  font-size: 16px;
@@ -130,44 +118,18 @@
130
118
  display: inline-flex;
131
119
  align-items: center;
132
120
  justify-content: center;
133
- padding: 4px 8px;
121
+ padding: 4px 10px;
134
122
  border-radius: 999px;
135
123
  transition: transform 0.25s ease, opacity 0.25s ease;
136
124
  color: inherit;
137
125
  text-decoration: none;
138
126
  white-space: nowrap;
139
- min-height: 36px;
140
127
  transition:
141
128
  background 0.2s ease,
142
129
  color 0.2s ease,
143
130
  transform 0.15s ease;
144
- }
145
-
146
- .breadcrumb {
147
- display: inline-flex;
148
- align-items: center;
149
- justify-content: center;
150
- gap: 6px;
151
- padding: 4px 0;
152
- white-space: nowrap;
153
- min-width: 0;
154
- }
155
-
156
- .title {
157
- margin: 0;
158
- font-size: 18px;
159
- line-height: 20px;
160
- font-weight: 500;
161
- letter-spacing: 0.01em;
162
- color: var(--color-text);
163
- margin-block-end: 0 !important;
164
- white-space: nowrap;
165
- }
166
-
167
- .slash {
168
- opacity: 0.5;
169
- font-size: 16px;
170
- font-weight: 500;
131
+ corner-shape: unset;
132
+ min-height: 40px;
171
133
  }
172
134
 
173
135
  .iconButton {
@@ -222,8 +184,9 @@
222
184
  }
223
185
 
224
186
  .navLink {
225
- min-height: 36px;
226
- padding: 0 12px;
187
+ corner-shape: unset;
188
+ min-height: 40px;
189
+ padding: 0 15px;
227
190
  border: none;
228
191
  border-radius: 999px;
229
192
  background: transparent;
@@ -238,16 +201,18 @@
238
201
  transform 0.15s ease;
239
202
  white-space: nowrap;
240
203
  font-family: inherit;
241
- font-size: 14px;
242
- font-weight: 500;
204
+ font-size: 15px;
205
+ font-weight: 600;
243
206
  }
244
207
 
245
- .navLink:hover, .logotypeWrapper:hover {
208
+ .navLink:hover,
209
+ .logotypeWrapper:hover {
246
210
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
247
211
  color: var(--color-text);
248
212
  }
249
213
 
250
- .navLink:active, .logotypeWrapper:active {
214
+ .navLink:active,
215
+ .logotypeWrapper:active {
251
216
  transform: scale(0.96);
252
217
  }
253
218
 
@@ -264,7 +229,7 @@
264
229
  }
265
230
 
266
231
  .desktopLabel svg {
267
- max-height: 12px;
232
+ max-height: 13px;
268
233
  }
269
234
 
270
235
  .mobileMenu {
@@ -408,11 +373,6 @@ max-height: 12px;
408
373
  color: var(--color-text);
409
374
  }
410
375
 
411
- .left .slide,
412
- .right .slide {
413
- width: 40px;
414
- }
415
-
416
376
  @media (min-width: 960px) {
417
377
  .desktopNav {
418
378
  display: flex;
@@ -425,22 +385,14 @@ max-height: 12px;
425
385
 
426
386
  @media (max-width: 959px) {
427
387
  .inner {
428
- grid-template-columns: auto minmax(0, 1fr) auto auto;
388
+ display: flex;
389
+ width: calc(100% - 14px);
429
390
  }
430
391
 
431
392
  .slide {
432
393
  height: 36px;
433
394
  }
434
395
 
435
- .breadcrumb {
436
- padding: 4px 0;
437
- gap: 4px;
438
- }
439
-
440
- .title {
441
- font-size: 14px;
442
- }
443
-
444
396
  .menuDrawerInner {
445
397
  padding: 3rem 1.25rem 4.5rem;
446
398
  }
@@ -455,12 +407,13 @@ max-height: 12px;
455
407
 
456
408
  .centerColumn {
457
409
  justify-content: center;
410
+ width: 100%;
458
411
  }
459
412
  }
460
413
 
461
414
  @media (prefers-reduced-motion: reduce) {
415
+
462
416
  .inner,
463
- .track,
464
417
  .iconButton,
465
418
  .logotypeWrapper,
466
419
  .navLink,
package/NavHeader.tsx CHANGED
@@ -1,13 +1,12 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react'
1
+ import React, { useState } from 'react'
2
2
  import styles from './NavHeader.module.css'
3
- import { ArrowLeftIcon, DotsNineIcon, XIcon } from '@phosphor-icons/react'
3
+ import { DotsNineIcon, XIcon } from '@phosphor-icons/react'
4
4
  import { Drawer } from 'vaul'
5
5
  import BlurEffect from 'react-progressive-blur'
6
6
 
7
7
  export type NavHeaderLabels = {
8
8
  menu: string
9
9
  close: string
10
- back: string
11
10
  }
12
11
 
13
12
  export type NavHeaderItem = {
@@ -26,31 +25,22 @@ export type NavHeaderItem = {
26
25
  }
27
26
 
28
27
  export type NavHeaderProps = {
29
- pageTitle?: string
30
- secPageTitle?: string
31
28
  currentPath?: string
32
29
  variant?: 'default' | 'docs' | (string & {})
33
30
  showHeaderBlur?: boolean
34
31
  navItems?: NavHeaderItem[]
35
32
  leftSlot?: React.ReactNode
36
- rightSlot?: React.ReactNode
37
33
  logo?: React.ReactNode
38
34
  brandName?: string
39
35
  homeHref?: string
40
36
  logoAriaLabel?: string
41
37
  labels?: Partial<NavHeaderLabels>
42
38
  className?: string
43
- scrollTransitionDistance?: number
44
- onBack?: () => void
45
39
  }
46
40
 
47
- type HeaderState = 'home' | 'level1' | 'level2'
48
- type LeftState = 'slot' | 'back'
49
-
50
41
  const DEFAULT_LABELS: NavHeaderLabels = {
51
42
  menu: '菜单',
52
- close: '关闭',
53
- back: '返回'
43
+ close: '关闭'
54
44
  }
55
45
 
56
46
  const normalizePath = (path: string): string => {
@@ -75,40 +65,6 @@ const isMenuItemActive = (currentPath: string, itemPath: string): boolean => {
75
65
  )
76
66
  }
77
67
 
78
- const useHeaderStates = (pageTitle?: string, secPageTitle?: string) => {
79
- return useMemo<HeaderState[]>(() => {
80
- if (!pageTitle && !secPageTitle) {
81
- return ['home']
82
- }
83
-
84
- if (!pageTitle && secPageTitle) {
85
- return ['home', 'level1']
86
- }
87
-
88
- if (pageTitle && !secPageTitle) {
89
- return ['home', 'level1']
90
- }
91
-
92
- return ['level1', 'level2']
93
- }, [pageTitle, secPageTitle])
94
- }
95
-
96
- const useLeftStates = (leftSlot?: React.ReactNode, secPageTitle?: string) => {
97
- return useMemo<LeftState[]>(() => {
98
- const states: LeftState[] = []
99
-
100
- if (leftSlot) {
101
- states.push('slot')
102
- }
103
-
104
- if (secPageTitle) {
105
- states.push('back')
106
- }
107
-
108
- return states
109
- }, [leftSlot, secPageTitle])
110
- }
111
-
112
68
  const cx = (...classNames: Array<string | false | null | undefined>) =>
113
69
  classNames.filter(Boolean).join(' ')
114
70
 
@@ -196,132 +152,24 @@ const renderMobileItem = (
196
152
  }
197
153
 
198
154
  const NavHeader: React.FC<NavHeaderProps> = ({
199
- pageTitle,
200
- secPageTitle,
201
155
  currentPath = '/',
202
156
  variant = 'default',
203
157
  showHeaderBlur = true,
204
158
  navItems = [],
205
159
  leftSlot,
206
- rightSlot,
207
160
  logo,
208
161
  brandName = 'Brand',
209
162
  homeHref = '/',
210
163
  logoAriaLabel,
211
164
  labels,
212
165
  className,
213
- scrollTransitionDistance = 72,
214
- onBack
215
166
  }) => {
216
- const leftTrackRef = useRef<HTMLDivElement>(null)
217
- const centerTrackRef = useRef<HTMLDivElement>(null)
218
167
  const [menuOpen, setMenuOpen] = useState(false)
219
168
 
220
169
  const resolvedLabels = { ...DEFAULT_LABELS, ...labels }
221
- const states = useHeaderStates(pageTitle, secPageTitle)
222
- const leftStates = useLeftStates(leftSlot, secPageTitle)
223
- const hasTransition = states.length > 1
224
170
  const hasNavItems = navItems.length > 0
225
171
  const resolvedLogoAriaLabel = logoAriaLabel ?? brandName
226
172
 
227
- useEffect(() => {
228
- if (typeof window === 'undefined') return
229
-
230
- const leftTrack = leftTrackRef.current
231
- const centerTrack = centerTrackRef.current
232
- const animatableTracks = [
233
- leftStates.length > 1 ? leftTrack : null,
234
- states.length > 1 ? centerTrack : null
235
- ].filter(Boolean) as HTMLDivElement[]
236
-
237
- if (!animatableTracks.length) return
238
-
239
- let slideHeight = 0
240
- let centerOffset = 0
241
- let leftOffset = 0
242
- let rafId = 0
243
- let nextProgress = 0
244
- let lastProgress = -1
245
-
246
- const refreshOffsets = () => {
247
- const el =
248
- centerTrack?.querySelector<HTMLElement>('[data-slide]') ??
249
- leftTrack?.querySelector<HTMLElement>('[data-slide]')
250
- slideHeight = el?.offsetHeight ?? 0
251
- centerOffset = hasTransition ? -(states.length - 1) * slideHeight : 0
252
- leftOffset =
253
- leftStates.length > 1 ? -((leftStates.length - 1) * slideHeight) : 0
254
- }
255
-
256
- const applyProgress = (progress: number) => {
257
- if (leftTrack && leftStates.length > 1) {
258
- leftTrack.style.transform = `translate3d(0, ${leftOffset * progress}px, 0)`
259
- }
260
-
261
- if (centerTrack && hasTransition) {
262
- centerTrack.style.transform = `translate3d(0, ${centerOffset * progress}px, 0)`
263
- }
264
- }
265
-
266
- const queueApply = (progress: number) => {
267
- nextProgress = progress
268
- if (rafId) return
269
-
270
- rafId = window.requestAnimationFrame(() => {
271
- rafId = 0
272
- if (nextProgress === lastProgress) return
273
- lastProgress = nextProgress
274
- applyProgress(nextProgress)
275
- })
276
- }
277
-
278
- const syncFromScroll = () => {
279
- if (!slideHeight) refreshOffsets()
280
- if (!slideHeight || (!hasTransition && leftStates.length <= 1)) return
281
- const progress = Math.max(
282
- 0,
283
- Math.min(1, window.scrollY / Math.max(scrollTransitionDistance, 1))
284
- )
285
- queueApply(progress)
286
- }
287
-
288
- refreshOffsets()
289
- syncFromScroll()
290
-
291
- window.addEventListener('scroll', syncFromScroll, { passive: true })
292
-
293
- const ro = new ResizeObserver(() => {
294
- refreshOffsets()
295
- syncFromScroll()
296
- })
297
-
298
- animatableTracks.forEach((track) => ro.observe(track))
299
-
300
- return () => {
301
- if (rafId) window.cancelAnimationFrame(rafId)
302
- window.removeEventListener('scroll', syncFromScroll)
303
- ro.disconnect()
304
- if (leftTrack) leftTrack.style.transform = ''
305
- if (centerTrack) centerTrack.style.transform = ''
306
- }
307
- }, [hasTransition, leftStates.length, scrollTransitionDistance, states])
308
-
309
- const handleBack = () => {
310
- if (typeof window === 'undefined') return
311
-
312
- if (onBack) {
313
- onBack()
314
- return
315
- }
316
-
317
- if (window.history.length > 1) {
318
- window.history.back()
319
- return
320
- }
321
-
322
- window.location.href = homeHref
323
- }
324
-
325
173
  const brandContent = logo ?? <span className={styles.brandName}>{brandName}</span>
326
174
 
327
175
  return (
@@ -416,72 +264,19 @@ const NavHeader: React.FC<NavHeaderProps> = ({
416
264
  </div>
417
265
  )}
418
266
  <div className={cx(styles.column, styles.left)}>
419
- <div className={styles.track} ref={leftTrackRef}>
420
- {leftStates.map((state, index) => (
421
- <div
422
- className={styles.slide}
423
- data-slide
424
- key={`left-${state}-${index}`}
425
- >
426
- {state === 'back' ? (
427
- <button
428
- type='button'
429
- className={styles.iconButton}
430
- onClick={handleBack}
431
- aria-label={resolvedLabels.back}
432
- >
433
- <ArrowLeftIcon size={18} weight='bold' />
434
- </button>
435
- ) : (
436
- leftSlot
437
- )}
438
- </div>
439
- ))}
440
- </div>
267
+ {leftSlot && <div className={styles.leftContent}>{leftSlot}</div>}
441
268
  </div>
442
269
 
443
270
  <div className={styles.centerColumn}>
444
271
  <div className={styles.centerTrackWrapper}>
445
- <div className={styles.track} ref={centerTrackRef}>
446
- {states.map((state, index) => (
447
- <div
448
- className={styles.slide}
449
- data-slide
450
- key={`center-${state}-${index}`}
451
- >
452
- {state === 'home' && (
453
- <a
454
- className={styles.logotypeWrapper}
455
- href={homeHref}
456
- aria-label={resolvedLogoAriaLabel}
457
- >
458
- {brandContent}
459
- </a>
460
- )}
461
-
462
- {state === 'level1' && (
463
- <div className={styles.breadcrumb}>
464
- <p className={styles.title}>
465
- {pageTitle ?? secPageTitle}
466
- </p>
467
- </div>
468
- )}
469
-
470
- {state === 'level2' && (
471
- <div className={styles.breadcrumb}>
472
- {pageTitle && (
473
- <>
474
- <p className={styles.title}>
475
- {pageTitle}
476
- </p>
477
- <span className={styles.slash}>/</span>
478
- </>
479
- )}
480
- <p className={styles.title}>{secPageTitle}</p>
481
- </div>
482
- )}
483
- </div>
484
- ))}
272
+ <div className={styles.slide}>
273
+ <a
274
+ className={styles.logotypeWrapper}
275
+ href={homeHref}
276
+ aria-label={resolvedLogoAriaLabel}
277
+ >
278
+ {brandContent}
279
+ </a>
485
280
  </div>
486
281
  </div>
487
282
 
@@ -518,9 +313,6 @@ const NavHeader: React.FC<NavHeaderProps> = ({
518
313
  </nav>
519
314
  )}
520
315
  </div>
521
- <div className={cx(styles.column, styles.right)}>
522
- {rightSlot && <div className={styles.rightSlot}>{rightSlot}</div>}
523
- </div>
524
316
  </header>
525
317
  </div>
526
318
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claralight-design/abweb-navbar",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A react components for AstroBox websites.",
5
5
  "scripts": {
6
6
  "dev": "vite",