@claralight-design/abweb-navbar 0.1.2 → 0.1.3

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,7 +17,7 @@
17
17
  max-height: 72px;
18
18
  }
19
19
 
20
- .header > * {
20
+ .header>* {
21
21
  corner-shape: unset;
22
22
  }
23
23
 
@@ -95,13 +95,6 @@
95
95
  flex: 0 1 auto;
96
96
  }
97
97
 
98
- .track {
99
- display: flex;
100
- flex-direction: column;
101
- will-change: transform;
102
- transform: translate3d(0, 0, 0);
103
- }
104
-
105
98
  .slide {
106
99
  min-height: 40px;
107
100
  height: 40px;
@@ -111,6 +104,12 @@
111
104
  gap: 10px;
112
105
  }
113
106
 
107
+ .leftContent {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ min-width: 0;
111
+ }
112
+
114
113
  .brandName {
115
114
  color: var(--color-text);
116
115
  font-size: 16px;
@@ -130,44 +129,18 @@
130
129
  display: inline-flex;
131
130
  align-items: center;
132
131
  justify-content: center;
133
- padding: 4px 8px;
132
+ padding: 4px 10px;
134
133
  border-radius: 999px;
135
134
  transition: transform 0.25s ease, opacity 0.25s ease;
136
135
  color: inherit;
137
136
  text-decoration: none;
138
137
  white-space: nowrap;
139
- min-height: 36px;
140
138
  transition:
141
139
  background 0.2s ease,
142
140
  color 0.2s ease,
143
141
  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;
142
+ corner-shape: unset;
143
+ min-height: 40px;
171
144
  }
172
145
 
173
146
  .iconButton {
@@ -222,8 +195,9 @@
222
195
  }
223
196
 
224
197
  .navLink {
225
- min-height: 36px;
226
- padding: 0 12px;
198
+ corner-shape: unset;
199
+ min-height: 40px;
200
+ padding: 0 15px;
227
201
  border: none;
228
202
  border-radius: 999px;
229
203
  background: transparent;
@@ -238,16 +212,18 @@
238
212
  transform 0.15s ease;
239
213
  white-space: nowrap;
240
214
  font-family: inherit;
241
- font-size: 14px;
242
- font-weight: 500;
215
+ font-size: 15px;
216
+ font-weight: 600;
243
217
  }
244
218
 
245
- .navLink:hover, .logotypeWrapper:hover {
219
+ .navLink:hover,
220
+ .logotypeWrapper:hover {
246
221
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
247
222
  color: var(--color-text);
248
223
  }
249
224
 
250
- .navLink:active, .logotypeWrapper:active {
225
+ .navLink:active,
226
+ .logotypeWrapper:active {
251
227
  transform: scale(0.96);
252
228
  }
253
229
 
@@ -264,7 +240,7 @@
264
240
  }
265
241
 
266
242
  .desktopLabel svg {
267
- max-height: 12px;
243
+ max-height: 13px;
268
244
  }
269
245
 
270
246
  .mobileMenu {
@@ -408,11 +384,6 @@ max-height: 12px;
408
384
  color: var(--color-text);
409
385
  }
410
386
 
411
- .left .slide,
412
- .right .slide {
413
- width: 40px;
414
- }
415
-
416
387
  @media (min-width: 960px) {
417
388
  .desktopNav {
418
389
  display: flex;
@@ -432,15 +403,6 @@ max-height: 12px;
432
403
  height: 36px;
433
404
  }
434
405
 
435
- .breadcrumb {
436
- padding: 4px 0;
437
- gap: 4px;
438
- }
439
-
440
- .title {
441
- font-size: 14px;
442
- }
443
-
444
406
  .menuDrawerInner {
445
407
  padding: 3rem 1.25rem 4.5rem;
446
408
  }
@@ -459,8 +421,8 @@ max-height: 12px;
459
421
  }
460
422
 
461
423
  @media (prefers-reduced-motion: reduce) {
424
+
462
425
  .inner,
463
- .track,
464
426
  .iconButton,
465
427
  .logotypeWrapper,
466
428
  .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,8 +25,6 @@ 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
@@ -40,17 +37,11 @@ export type NavHeaderProps = {
40
37
  logoAriaLabel?: string
41
38
  labels?: Partial<NavHeaderLabels>
42
39
  className?: string
43
- scrollTransitionDistance?: number
44
- onBack?: () => void
45
40
  }
46
41
 
47
- type HeaderState = 'home' | 'level1' | 'level2'
48
- type LeftState = 'slot' | 'back'
49
-
50
42
  const DEFAULT_LABELS: NavHeaderLabels = {
51
43
  menu: '菜单',
52
- close: '关闭',
53
- back: '返回'
44
+ close: '关闭'
54
45
  }
55
46
 
56
47
  const normalizePath = (path: string): string => {
@@ -75,40 +66,6 @@ const isMenuItemActive = (currentPath: string, itemPath: string): boolean => {
75
66
  )
76
67
  }
77
68
 
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
69
  const cx = (...classNames: Array<string | false | null | undefined>) =>
113
70
  classNames.filter(Boolean).join(' ')
114
71
 
@@ -196,8 +153,6 @@ const renderMobileItem = (
196
153
  }
197
154
 
198
155
  const NavHeader: React.FC<NavHeaderProps> = ({
199
- pageTitle,
200
- secPageTitle,
201
156
  currentPath = '/',
202
157
  variant = 'default',
203
158
  showHeaderBlur = true,
@@ -210,118 +165,13 @@ const NavHeader: React.FC<NavHeaderProps> = ({
210
165
  logoAriaLabel,
211
166
  labels,
212
167
  className,
213
- scrollTransitionDistance = 72,
214
- onBack
215
168
  }) => {
216
- const leftTrackRef = useRef<HTMLDivElement>(null)
217
- const centerTrackRef = useRef<HTMLDivElement>(null)
218
169
  const [menuOpen, setMenuOpen] = useState(false)
219
170
 
220
171
  const resolvedLabels = { ...DEFAULT_LABELS, ...labels }
221
- const states = useHeaderStates(pageTitle, secPageTitle)
222
- const leftStates = useLeftStates(leftSlot, secPageTitle)
223
- const hasTransition = states.length > 1
224
172
  const hasNavItems = navItems.length > 0
225
173
  const resolvedLogoAriaLabel = logoAriaLabel ?? brandName
226
174
 
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
175
  const brandContent = logo ?? <span className={styles.brandName}>{brandName}</span>
326
176
 
327
177
  return (
@@ -416,72 +266,19 @@ const NavHeader: React.FC<NavHeaderProps> = ({
416
266
  </div>
417
267
  )}
418
268
  <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>
269
+ {leftSlot && <div className={styles.leftContent}>{leftSlot}</div>}
441
270
  </div>
442
271
 
443
272
  <div className={styles.centerColumn}>
444
273
  <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
- ))}
274
+ <div className={styles.slide}>
275
+ <a
276
+ className={styles.logotypeWrapper}
277
+ href={homeHref}
278
+ aria-label={resolvedLogoAriaLabel}
279
+ >
280
+ {brandContent}
281
+ </a>
485
282
  </div>
486
283
  </div>
487
284
 
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.3",
4
4
  "description": "A react components for AstroBox websites.",
5
5
  "scripts": {
6
6
  "dev": "vite",