@claralight-design/abweb-navbar 0.1.0 → 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,6 +17,10 @@
17
17
  max-height: 72px;
18
18
  }
19
19
 
20
+ .header>* {
21
+ corner-shape: unset;
22
+ }
23
+
20
24
  .inner {
21
25
  width: calc(100% - 12px);
22
26
  max-width: calc(1200px - 12px);
@@ -91,13 +95,6 @@
91
95
  flex: 0 1 auto;
92
96
  }
93
97
 
94
- .track {
95
- display: flex;
96
- flex-direction: column;
97
- will-change: transform;
98
- transform: translate3d(0, 0, 0);
99
- }
100
-
101
98
  .slide {
102
99
  min-height: 40px;
103
100
  height: 40px;
@@ -107,6 +104,12 @@
107
104
  gap: 10px;
108
105
  }
109
106
 
107
+ .leftContent {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ min-width: 0;
111
+ }
112
+
110
113
  .brandName {
111
114
  color: var(--color-text);
112
115
  font-size: 16px;
@@ -126,44 +129,18 @@
126
129
  display: inline-flex;
127
130
  align-items: center;
128
131
  justify-content: center;
129
- padding: 4px 8px;
130
- border-radius: 12px;
132
+ padding: 4px 10px;
133
+ border-radius: 999px;
131
134
  transition: transform 0.25s ease, opacity 0.25s ease;
132
135
  color: inherit;
133
136
  text-decoration: none;
134
137
  white-space: nowrap;
135
- }
136
-
137
- .logotypeWrapper:hover {
138
- transform: translateY(-1px);
139
- opacity: 0.92;
140
- }
141
-
142
- .breadcrumb {
143
- display: inline-flex;
144
- align-items: center;
145
- justify-content: center;
146
- gap: 6px;
147
- padding: 4px 0;
148
- white-space: nowrap;
149
- min-width: 0;
150
- }
151
-
152
- .title {
153
- margin: 0;
154
- font-size: 18px;
155
- line-height: 20px;
156
- font-weight: 500;
157
- letter-spacing: 0.01em;
158
- color: var(--color-text);
159
- margin-block-end: 0 !important;
160
- white-space: nowrap;
161
- }
162
-
163
- .slash {
164
- opacity: 0.5;
165
- font-size: 16px;
166
- font-weight: 500;
138
+ transition:
139
+ background 0.2s ease,
140
+ color 0.2s ease,
141
+ transform 0.15s ease;
142
+ corner-shape: unset;
143
+ min-height: 40px;
167
144
  }
168
145
 
169
146
  .iconButton {
@@ -218,8 +195,9 @@
218
195
  }
219
196
 
220
197
  .navLink {
221
- min-height: 36px;
222
- padding: 0 12px;
198
+ corner-shape: unset;
199
+ min-height: 40px;
200
+ padding: 0 15px;
223
201
  border: none;
224
202
  border-radius: 999px;
225
203
  background: transparent;
@@ -228,23 +206,24 @@
228
206
  align-items: center;
229
207
  justify-content: center;
230
208
  text-decoration: none;
231
- cursor: pointer;
232
209
  transition:
233
210
  background 0.2s ease,
234
211
  color 0.2s ease,
235
212
  transform 0.15s ease;
236
213
  white-space: nowrap;
237
214
  font-family: inherit;
238
- font-size: 14px;
239
- font-weight: 500;
215
+ font-size: 15px;
216
+ font-weight: 600;
240
217
  }
241
218
 
242
- .navLink:hover {
219
+ .navLink:hover,
220
+ .logotypeWrapper:hover {
243
221
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
244
222
  color: var(--color-text);
245
223
  }
246
224
 
247
- .navLink:active {
225
+ .navLink:active,
226
+ .logotypeWrapper:active {
248
227
  transform: scale(0.96);
249
228
  }
250
229
 
@@ -257,10 +236,11 @@
257
236
  display: inline-flex;
258
237
  align-items: center;
259
238
  min-width: 0;
239
+ font-weight: 600;
260
240
  }
261
241
 
262
242
  .desktopLabel svg {
263
- max-height: 12px;
243
+ max-height: 13px;
264
244
  }
265
245
 
266
246
  .mobileMenu {
@@ -272,8 +252,8 @@ max-height: 12px;
272
252
  inset: 0;
273
253
  z-index: 1001;
274
254
  background: rgba(0, 0, 0, 0.52);
275
- -webkit-backdrop-filter: blur(6px);
276
- backdrop-filter: blur(6px);
255
+ -webkit-backdrop-filter: blur(16px);
256
+ backdrop-filter: blur(16px);
277
257
  }
278
258
 
279
259
  .menuDrawerContent {
@@ -315,6 +295,8 @@ max-height: 12px;
315
295
  .menuDrawerHeader {
316
296
  width: 100%;
317
297
  position: relative;
298
+ display: none;
299
+ visibility: hidden;
318
300
  }
319
301
 
320
302
  .menuTitle {
@@ -402,11 +384,6 @@ max-height: 12px;
402
384
  color: var(--color-text);
403
385
  }
404
386
 
405
- .left .slide,
406
- .right .slide {
407
- width: 40px;
408
- }
409
-
410
387
  @media (min-width: 960px) {
411
388
  .desktopNav {
412
389
  display: flex;
@@ -426,15 +403,6 @@ max-height: 12px;
426
403
  height: 36px;
427
404
  }
428
405
 
429
- .breadcrumb {
430
- padding: 4px 0;
431
- gap: 4px;
432
- }
433
-
434
- .title {
435
- font-size: 14px;
436
- }
437
-
438
406
  .menuDrawerInner {
439
407
  padding: 3rem 1.25rem 4.5rem;
440
408
  }
@@ -442,11 +410,19 @@ max-height: 12px;
442
410
  .menuTitle {
443
411
  font-size: 36px;
444
412
  }
413
+
414
+ .logotypeWrapper {
415
+ margin: 0 auto;
416
+ }
417
+
418
+ .centerColumn {
419
+ justify-content: center;
420
+ }
445
421
  }
446
422
 
447
423
  @media (prefers-reduced-motion: reduce) {
424
+
448
425
  .inner,
449
- .track,
450
426
  .iconButton,
451
427
  .logotypeWrapper,
452
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, OptionIcon, 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 (
@@ -338,7 +188,6 @@ const NavHeader: React.FC<NavHeaderProps> = ({
338
188
  open={menuOpen}
339
189
  onOpenChange={setMenuOpen}
340
190
  shouldScaleBackground
341
- disablePreventScroll={false}
342
191
  direction='top'
343
192
  >
344
193
  <Drawer.Trigger asChild>
@@ -347,7 +196,7 @@ const NavHeader: React.FC<NavHeaderProps> = ({
347
196
  className={styles.iconButton}
348
197
  aria-label={resolvedLabels.menu}
349
198
  >
350
- <OptionIcon size={18} weight='bold' />
199
+ <DotsNineIcon size={18} weight='bold' />
351
200
  </button>
352
201
  </Drawer.Trigger>
353
202
 
@@ -417,72 +266,19 @@ const NavHeader: React.FC<NavHeaderProps> = ({
417
266
  </div>
418
267
  )}
419
268
  <div className={cx(styles.column, styles.left)}>
420
- <div className={styles.track} ref={leftTrackRef}>
421
- {leftStates.map((state, index) => (
422
- <div
423
- className={styles.slide}
424
- data-slide
425
- key={`left-${state}-${index}`}
426
- >
427
- {state === 'back' ? (
428
- <button
429
- type='button'
430
- className={styles.iconButton}
431
- onClick={handleBack}
432
- aria-label={resolvedLabels.back}
433
- >
434
- <ArrowLeftIcon size={18} weight='bold' />
435
- </button>
436
- ) : (
437
- leftSlot
438
- )}
439
- </div>
440
- ))}
441
- </div>
269
+ {leftSlot && <div className={styles.leftContent}>{leftSlot}</div>}
442
270
  </div>
443
271
 
444
272
  <div className={styles.centerColumn}>
445
273
  <div className={styles.centerTrackWrapper}>
446
- <div className={styles.track} ref={centerTrackRef}>
447
- {states.map((state, index) => (
448
- <div
449
- className={styles.slide}
450
- data-slide
451
- key={`center-${state}-${index}`}
452
- >
453
- {state === 'home' && (
454
- <a
455
- className={styles.logotypeWrapper}
456
- href={homeHref}
457
- aria-label={resolvedLogoAriaLabel}
458
- >
459
- {brandContent}
460
- </a>
461
- )}
462
-
463
- {state === 'level1' && (
464
- <div className={styles.breadcrumb}>
465
- <p className={styles.title}>
466
- {pageTitle ?? secPageTitle}
467
- </p>
468
- </div>
469
- )}
470
-
471
- {state === 'level2' && (
472
- <div className={styles.breadcrumb}>
473
- {pageTitle && (
474
- <>
475
- <p className={styles.title}>
476
- {pageTitle}
477
- </p>
478
- <span className={styles.slash}>/</span>
479
- </>
480
- )}
481
- <p className={styles.title}>{secPageTitle}</p>
482
- </div>
483
- )}
484
- </div>
485
- ))}
274
+ <div className={styles.slide}>
275
+ <a
276
+ className={styles.logotypeWrapper}
277
+ href={homeHref}
278
+ aria-label={resolvedLogoAriaLabel}
279
+ >
280
+ {brandContent}
281
+ </a>
486
282
  </div>
487
283
  </div>
488
284
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claralight-design/abweb-navbar",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "A react components for AstroBox websites.",
5
5
  "scripts": {
6
6
  "dev": "vite",