Skip to content

Commit d8f977a

Browse files
committed
cleanup sidebar
1 parent c5ff771 commit d8f977a

File tree

18 files changed

+3278
-59
lines changed

18 files changed

+3278
-59
lines changed

apps/docs/components/custom-sidebar.tsx

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,43 @@ import { contents } from "./sidebar-content";
1212

1313
export default function CustomSidebar() {
1414
const [currentOpen, setCurrentOpen] = useState<number>(0);
15+
const [nestedOpen, setNestedOpen] = useState<Set<string>>(new Set());
1516
const pathname = usePathname();
1617
const { setOpenSearch } = useSearchContext();
1718

1819
const getDefaultValue = useCallback(() => {
1920
const defaultValue = contents.findIndex((item) =>
20-
item.list.some((listItem) => listItem.href === pathname)
21+
item.list.some((listItem) => {
22+
if (listItem.href === pathname) {
23+
return true;
24+
}
25+
if (listItem.children) {
26+
return listItem.children.some((child) => child.href === pathname);
27+
}
28+
return false;
29+
})
2130
);
2231
return defaultValue === -1 ? 0 : defaultValue;
2332
}, [pathname]);
2433

2534
useEffect(() => {
2635
setCurrentOpen(getDefaultValue());
27-
}, [getDefaultValue]);
36+
// Auto-open nested items that contain the current path
37+
const openNested = new Set<string>();
38+
for (const section of contents) {
39+
for (const item of section.list) {
40+
if (item.children) {
41+
const hasActiveChild = item.children.some(
42+
(child) => child.href === pathname
43+
);
44+
if (hasActiveChild) {
45+
openNested.add(item.title);
46+
}
47+
}
48+
}
49+
}
50+
setNestedOpen(openNested);
51+
}, [getDefaultValue, pathname]);
2852

2953
const handleSearch = () => {
3054
setOpenSearch(true);
@@ -65,7 +89,7 @@ export default function CustomSidebar() {
6589
weight="fill"
6690
/>
6791
<span className="flex-1 text-sm">{item.title}</span>
68-
{item.isNew && <NewBadge />}
92+
{item.isNew ? <NewBadge /> : null}
6993
<motion.div
7094
animate={{ rotate: currentOpen === index ? 180 : 0 }}
7195
className="shrink-0"
@@ -77,7 +101,7 @@ export default function CustomSidebar() {
77101
</motion.div>
78102
</button>
79103
<AnimatePresence initial={false}>
80-
{currentOpen === index && (
104+
{currentOpen === index ? (
81105
<motion.div
82106
animate={{ opacity: 1, height: "auto" }}
83107
className="relative overflow-hidden"
@@ -95,6 +119,85 @@ export default function CustomSidebar() {
95119
</p>
96120
<div className="h-px flex-grow bg-linear-to-r from-stone-800/90 to-stone-800/60" />
97121
</div>
122+
) : listItem.children ? (
123+
<div>
124+
<button
125+
className="flex w-full items-center gap-3 px-6 py-2 text-left text-muted-foreground text-sm hover:bg-muted/50 hover:text-foreground"
126+
onClick={() => {
127+
const newOpen = new Set(nestedOpen);
128+
if (newOpen.has(listItem.title)) {
129+
newOpen.delete(listItem.title);
130+
} else {
131+
newOpen.add(listItem.title);
132+
}
133+
setNestedOpen(newOpen);
134+
}}
135+
type="button"
136+
>
137+
{listItem.icon ? (
138+
<listItem.icon
139+
className="size-5 shrink-0"
140+
weight="duotone"
141+
/>
142+
) : null}
143+
<span className="flex-1">
144+
{listItem.title}
145+
</span>
146+
{listItem.isNew ? <NewBadge /> : null}
147+
<motion.div
148+
animate={{
149+
rotate: nestedOpen.has(listItem.title)
150+
? 90
151+
: 0,
152+
}}
153+
className="shrink-0"
154+
>
155+
<CaretDownIcon
156+
className="size-3 text-muted-foreground"
157+
weight="duotone"
158+
/>
159+
</motion.div>
160+
</button>
161+
<AnimatePresence initial={false}>
162+
{nestedOpen.has(listItem.title) && (
163+
<motion.div
164+
animate={{
165+
opacity: 1,
166+
height: "auto",
167+
}}
168+
className="relative overflow-hidden"
169+
exit={{ opacity: 0, height: 0 }}
170+
initial={{ opacity: 0, height: 0 }}
171+
>
172+
<div className="ml-4 border-border border-l pl-2">
173+
{listItem.children.map((child) => (
174+
<AsideLink
175+
activeClassName="!bg-muted !text-foreground font-medium"
176+
className="flex items-center gap-3 px-6 py-2 text-muted-foreground text-sm hover:bg-muted/50 hover:text-foreground"
177+
href={child.href || "#"}
178+
key={child.title}
179+
startWith="/docs"
180+
title={child.title}
181+
>
182+
{child.icon ? (
183+
<child.icon
184+
className="size-4 shrink-0"
185+
weight="duotone"
186+
/>
187+
) : null}
188+
<span className="flex-1">
189+
{child.title}
190+
</span>
191+
{child.isNew ? (
192+
<NewBadge />
193+
) : null}
194+
</AsideLink>
195+
))}
196+
</div>
197+
</motion.div>
198+
)}
199+
</AnimatePresence>
200+
</div>
98201
) : (
99202
<AsideLink
100203
activeClassName="!bg-muted !text-foreground font-medium"
@@ -103,22 +206,24 @@ export default function CustomSidebar() {
103206
startWith="/docs"
104207
title={listItem.title}
105208
>
106-
<listItem.icon
107-
className="size-5 shrink-0"
108-
weight="duotone"
109-
/>
209+
{listItem.icon ? (
210+
<listItem.icon
211+
className="size-5 shrink-0"
212+
weight="duotone"
213+
/>
214+
) : null}
110215
<span className="flex-1">
111216
{listItem.title}
112217
</span>
113-
{listItem.isNew && <NewBadge />}
218+
{listItem.isNew ? <NewBadge /> : null}
114219
</AsideLink>
115220
)}
116221
</Suspense>
117222
</div>
118223
))}
119224
</motion.div>
120225
</motion.div>
121-
)}
226+
) : null}
122227
</AnimatePresence>
123228
</div>
124229
))}
@@ -136,7 +241,7 @@ function NewBadge({ isSelected }: { isSelected?: boolean }) {
136241
<Badge
137242
className={cn(
138243
"!no-underline !decoration-transparent pointer-events-none border-dashed",
139-
isSelected && "!border-solid"
244+
isSelected ? "!border-solid" : ""
140245
)}
141246
variant={isSelected ? "default" : "outline"}
142247
>

apps/docs/components/sidebar-content.tsx

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import {
33
BookOpenIcon,
44
CalendarIcon,
55
ChartBarIcon,
6+
CircleIcon,
67
CodeIcon,
78
CreditCardIcon,
89
DatabaseIcon,
10+
DiamondIcon,
911
FileTextIcon,
1012
FlagIcon,
1113
GlobeIcon,
1214
GoogleLogoIcon,
15+
HexagonIcon,
1316
type IconWeight,
1417
KeyIcon,
1518
LightningAIcon,
@@ -22,25 +25,30 @@ import {
2225
ShieldCheckIcon,
2326
ShieldStarIcon,
2427
ShoppingCartIcon,
28+
SparkleIcon,
2529
SpeedometerIcon,
30+
SquareIcon,
31+
StarIcon,
2632
TrendUpIcon,
33+
TriangleIcon,
2734
UserCheckIcon,
2835
} from "@phosphor-icons/react";
2936

30-
export interface SidebarItem {
37+
export type SidebarItem = {
3138
title: string;
3239
href?: string;
33-
icon: React.ComponentType<{ className?: string; weight?: IconWeight }>;
40+
icon?: React.ComponentType<{ className?: string; weight?: IconWeight }>;
3441
isNew?: boolean;
3542
group?: boolean;
36-
}
43+
children?: SidebarItem[];
44+
};
3745

38-
export interface SidebarSection {
46+
export type SidebarSection = {
3947
title: string;
4048
Icon: React.ComponentType<{ className?: string; weight?: IconWeight }>;
4149
isNew?: boolean;
4250
list: SidebarItem[];
43-
}
51+
};
4452

4553
export const contents: SidebarSection[] = [
4654
{
@@ -107,6 +115,11 @@ export const contents: SidebarSection[] = [
107115
title: "Integrations",
108116
Icon: PlugIcon,
109117
list: [
118+
{
119+
title: "Angular",
120+
href: "/docs/Integrations/angular",
121+
icon: CircleIcon,
122+
},
110123
{
111124
title: "React",
112125
href: "/docs/Integrations/react",
@@ -117,16 +130,47 @@ export const contents: SidebarSection[] = [
117130
href: "/docs/Integrations/nextjs",
118131
icon: LightningIcon,
119132
},
133+
{
134+
title: "Svelte",
135+
icon: HexagonIcon,
136+
children: [
137+
{
138+
title: "Svelte",
139+
href: "/docs/Integrations/svelte",
140+
icon: HexagonIcon,
141+
},
142+
{
143+
title: "SvelteKit",
144+
href: "/docs/Integrations/sveltekit",
145+
icon: DiamondIcon,
146+
},
147+
],
148+
},
120149
{
121150
title: "WordPress",
122151
href: "/docs/Integrations/wordpress",
123152
icon: GlobeIcon,
124153
},
154+
{
155+
title: "Webflow",
156+
href: "/docs/Integrations/webflow",
157+
icon: SquareIcon,
158+
},
159+
{
160+
title: "Wix",
161+
href: "/docs/Integrations/wix",
162+
icon: TriangleIcon,
163+
},
125164
{
126165
title: "Shopify",
127166
href: "/docs/Integrations/shopify",
128167
icon: ShoppingCartIcon,
129168
},
169+
{
170+
title: "Squarespace",
171+
href: "/docs/Integrations/squarespace",
172+
icon: StarIcon,
173+
},
130174
{
131175
title: "Stripe",
132176
href: "/docs/Integrations/stripe",
@@ -137,6 +181,11 @@ export const contents: SidebarSection[] = [
137181
href: "/docs/Integrations/framer",
138182
icon: PaletteIcon,
139183
},
184+
{
185+
title: "Bubble.io",
186+
href: "/docs/Integrations/bubble",
187+
icon: SparkleIcon,
188+
},
140189
{
141190
title: "Cal.com",
142191
href: "/docs/Integrations/cal",
@@ -147,6 +196,21 @@ export const contents: SidebarSection[] = [
147196
href: "/docs/Integrations/gtm",
148197
icon: GoogleLogoIcon,
149198
},
199+
{
200+
title: "Hugo",
201+
href: "/docs/Integrations/hugo",
202+
icon: FileTextIcon,
203+
},
204+
{
205+
title: "Jekyll",
206+
href: "/docs/Integrations/jekyll",
207+
icon: BookOpenIcon,
208+
},
209+
{
210+
title: "Laravel",
211+
href: "/docs/Integrations/laravel",
212+
icon: CodeIcon,
213+
},
150214
],
151215
},
152216
{

0 commit comments

Comments
 (0)