@allbluecn/web-app 0.4.15 → 0.4.16

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/web-app",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -136,13 +136,13 @@
136
136
  "tw-animate-css": "^1.4.0",
137
137
  "zod": "^4.4.3",
138
138
  "@allbluecn/ai-gateway": "0.4.11",
139
- "@allbluecn/database": "0.4.11",
140
139
  "@allbluecn/kernel": "0.3.0",
141
140
  "@allbluecn/plugins-core": "0.4.11",
142
141
  "@allbluecn/prototype": "0.1.1",
142
+ "@allbluecn/shared": "0.4.11",
143
143
  "@allbluecn/tiptap": "0.4.11",
144
144
  "@allbluecn/ui": "0.4.11",
145
- "@allbluecn/shared": "0.4.11"
145
+ "@allbluecn/database": "0.4.11"
146
146
  },
147
147
  "devDependencies": {
148
148
  "@babel/parser": "^8.0.4",
@@ -19,12 +19,7 @@ import { cn } from '@/lib/utils'
19
19
  import { ScrollText, AlertTriangle, ArrowLeft, ChevronDown } from 'lucide-react'
20
20
  import ReactMarkdown from 'react-markdown'
21
21
  import { useTranslation } from 'react-i18next'
22
- import { useMemo, useState } from 'react'
23
- import {
24
- Collapsible,
25
- CollapsibleContent,
26
- CollapsibleTrigger,
27
- } from '@/components/ui/collapsible'
22
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
28
23
  import { Route } from '@/routes/_nav/changelog/route'
29
24
  import type { DeveloperLogEntry } from '@allbluecn/shared'
30
25
  import { SDK_GROUPS } from '@/lib/changelog/sdk-versions'
@@ -36,22 +31,68 @@ const REQUIRES_ACTION_TAGS = new Set(['breaking'])
36
31
 
37
32
  function ChangelogSidebar() {
38
33
  const { t } = useTranslation('common')
39
- const [openGroups, setOpenGroups] = useState<Set<string>>(
40
- new Set(SDK_GROUPS.map((g) => g.id))
41
- )
34
+ const [activeGroup, setActiveGroup] = useState<string | null>(null)
35
+ const [isOverflowing, setIsOverflowing] = useState(false)
36
+ const outerRef = useRef<HTMLDivElement>(null)
37
+ const isOverflowingRef = useRef(false)
38
+ const expandedHeightRef = useRef(0)
39
+
40
+ const checkOverflow = useCallback(() => {
41
+ if (!outerRef.current) return
42
+
43
+ let isOverflow: boolean
44
+ if (!isOverflowingRef.current) {
45
+ // Expanded: trust DOM scrollHeight
46
+ isOverflow = outerRef.current.scrollHeight > outerRef.current.clientHeight
47
+ if (isOverflow) {
48
+ expandedHeightRef.current = outerRef.current.scrollHeight
49
+ }
50
+ } else {
51
+ // Collapsed: compare cached expanded height against container
52
+ isOverflow = expandedHeightRef.current > outerRef.current.clientHeight
53
+ }
54
+
55
+ if (isOverflow !== isOverflowingRef.current) {
56
+ isOverflowingRef.current = isOverflow
57
+ setIsOverflowing(isOverflow)
58
+ if (isOverflow) setActiveGroup(null)
59
+ }
60
+ }, [])
61
+
62
+ useEffect(() => {
63
+ checkOverflow()
64
+ const ro = new ResizeObserver(checkOverflow)
65
+ if (outerRef.current) ro.observe(outerRef.current)
66
+ window.addEventListener('resize', checkOverflow)
67
+ return () => {
68
+ ro.disconnect()
69
+ window.removeEventListener('resize', checkOverflow)
70
+ }
71
+ }, [checkOverflow])
72
+
73
+ const handleGroupToggle = (groupId: string) => {
74
+ if (!isOverflowing) return
75
+ setActiveGroup((prev) => (prev === groupId ? null : groupId))
76
+ }
42
77
 
43
- const toggleGroup = (id: string) => {
44
- setOpenGroups((prev) => {
45
- const next = new Set(prev)
46
- next.has(id) ? next.delete(id) : next.add(id)
47
- return next
48
- })
78
+ const isGroupOpen = (groupId: string) => {
79
+ if (!isOverflowing) return true
80
+ return activeGroup === groupId
49
81
  }
50
82
 
83
+ const outerClasses = useMemo(
84
+ () =>
85
+ cn(
86
+ 'flex-1 flex flex-col gap-6 justify-center min-h-0',
87
+ isOverflowing && 'overflow-y-auto'
88
+ ),
89
+ [isOverflowing]
90
+ )
91
+
51
92
  return (
52
- <aside className="shrink-0 w-1/2 max-w-130 h-full flex flex-col py-6 pr-12 pl-10 border-r border-border bg-muted/50">
53
- <div className="flex-1 flex flex-col gap-6 justify-between min-h-0">
54
- <div>
93
+ <aside className="shrink-0 w-1/2 max-w-130 h-full flex flex-col py-6 pr-12 pl-10 border-r border-border bg-muted/50 overflow-hidden">
94
+ <div ref={outerRef} className={outerClasses}>
95
+ <div className="shrink-0">
55
96
  <h1 className="text-4xl font-medium tracking-tight">
56
97
  {t('changelog.title')}
57
98
  </h1>
@@ -60,24 +101,25 @@ function ChangelogSidebar() {
60
101
  </p>
61
102
  </div>
62
103
 
63
- <div className="flex flex-col gap-3 min-h-0 flex-1">
64
- <h2 className="text-sm font-semibold tracking-tight shrink-0">
104
+ <div className="flex flex-col gap-3">
105
+ <h2 className="shrink-0 text-sm font-semibold tracking-tight">
65
106
  {t('changelog.sdkVersions')}
66
107
  </h2>
67
- <div className="flex flex-col gap-3 min-h-0 flex-1">
108
+ <div className="flex flex-col gap-3">
68
109
  {SDK_GROUPS.map((group) => (
69
110
  <SdkGroupSection
70
111
  key={group.id}
71
112
  group={group}
72
113
  t={t}
73
- open={openGroups.has(group.id)}
74
- onToggle={() => toggleGroup(group.id)}
114
+ open={isGroupOpen(group.id)}
115
+ onToggle={() => handleGroupToggle(group.id)}
116
+ isAccordion={isOverflowing}
75
117
  />
76
118
  ))}
77
119
  </div>
78
120
  </div>
79
121
 
80
- <div>
122
+ <div className="shrink-0">
81
123
  <h3 className="text-sm font-semibold tracking-tight">
82
124
  {t('changelog.subscribe.title')}
83
125
  </h3>
@@ -278,21 +320,23 @@ function SdkGroupSection({
278
320
  t,
279
321
  open,
280
322
  onToggle,
323
+ isAccordion,
281
324
  }: {
282
325
  group: (typeof SDK_GROUPS)[0]
283
326
  t: (key: string) => string
284
327
  open: boolean
285
328
  onToggle: () => void
329
+ isAccordion: boolean
286
330
  }) {
287
331
  const isTanstack = group.id === 'tanstack'
288
332
 
289
333
  return (
290
- <Collapsible
291
- open={open}
292
- onOpenChange={() => onToggle()}
293
- className="flex flex-col min-h-0 flex-1 overflow-hidden"
294
- >
295
- <CollapsibleTrigger className="flex items-center gap-1.5 w-full py-1 group shrink-0">
334
+ <div className="flex flex-col">
335
+ <button
336
+ type="button"
337
+ onClick={onToggle}
338
+ className="flex items-center gap-1.5 w-full py-1 group"
339
+ >
296
340
  <span className="flex-1 min-w-0 flex items-center gap-1.5">
297
341
  {isTanstack ? (
298
342
  <span className="shrink-0">
@@ -314,12 +358,17 @@ function SdkGroupSection({
314
358
  {t(`changelog.sdkGroups.${group.label}`)}
315
359
  </span>
316
360
  </span>
317
- <ChevronDown
318
- className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:text-foreground"
319
- />
320
- </CollapsibleTrigger>
321
- <CollapsibleContent>
322
- <ul className="mt-1 flex flex-col min-h-0 overflow-y-auto pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border">
361
+ {isAccordion && (
362
+ <ChevronDown
363
+ className={cn(
364
+ 'size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:text-foreground',
365
+ open && 'rotate-180'
366
+ )}
367
+ />
368
+ )}
369
+ </button>
370
+ {open && (
371
+ <ul className="mt-1 flex flex-col">
323
372
  {group.items.map((sdk) => (
324
373
  <li key={sdk.name}>
325
374
  <a
@@ -338,8 +387,8 @@ function SdkGroupSection({
338
387
  </li>
339
388
  ))}
340
389
  </ul>
341
- </CollapsibleContent>
342
- </Collapsible>
390
+ )}
391
+ </div>
343
392
  )
344
393
  }
345
394
 
@@ -85,7 +85,7 @@ export const SDK_GROUPS: SdkGroup[] = [
85
85
  "items": [
86
86
  {
87
87
  "name": "@allbluecn/web-app",
88
- "version": "v0.4.15",
88
+ "version": "v0.4.16",
89
89
  "lastUpdate": "Aug 23, 2026",
90
90
  "source": "apps/web/package.json",
91
91
  "docsUrl": "https://github.com/allbluecn/allblue"