Skip to content

Commit e42b87f

Browse files
committed
update sidebar layout to allow nested groups and extract transforms into separate utilities
1 parent c0cd44a commit e42b87f

File tree

9 files changed

+856
-76
lines changed

9 files changed

+856
-76
lines changed

src/components/Navigation/Navigation.astro

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Support } from "../Support";
55
import { SideNav } from "./SideNav";
66
import { Container } from "./styles";
77
import { DropdownNav } from "./DropdownNav";
8+
import { transformNavGroups } from "./transform-nav-groups";
89
910
interface Props {
1011
url?: string;
@@ -34,33 +35,7 @@ interface Props {
3435
3536
const { navItems, url } = Astro.props;
3637
37-
const navGroups = navItems
38-
? navItems.map((group) => ({
39-
...group,
40-
items: group.items
41-
.map((item) => ({
42-
...item,
43-
label: item.data?.sidebar?.label || item.data.title,
44-
slug: item.data?.isHome ? "" : (item.slug as any),
45-
data: {
46-
...item.data,
47-
sidebar: {
48-
label: item.data?.sidebar?.label || item.data.title,
49-
order: item.data?.sidebar?.order || 999,
50-
hide: item.data?.sidebar?.hide || false,
51-
},
52-
},
53-
}))
54-
.filter((item) => !item.data.sidebar.hide)
55-
.sort((p1, p2) =>
56-
p1.data.sidebar.order > p2.data.sidebar.order
57-
? 1
58-
: p1.data.sidebar.order < p2.data.sidebar.order
59-
? -1
60-
: 0,
61-
),
62-
}))
63-
: [];
38+
const navGroups = navItems ? transformNavGroups(navItems) : [];
6439
---
6540

6641
<Container>

src/components/Navigation/SideNav.stories.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,82 @@ const mockSidebarItems: SidebarItem[] = [
141141
},
142142
];
143143

144+
const mockSidebarItemsWithNestedItems: SidebarItem = {
145+
...mockSidebarItems[1],
146+
items: mockSidebarItems[1].items.concat([
147+
{
148+
title: "Modes",
149+
items: [
150+
{
151+
id: "modes.mdx",
152+
slug: "modes",
153+
collection: "modes",
154+
data: {
155+
title: "Story Modes",
156+
sidebar: {
157+
label: "Story Modes",
158+
order: 1,
159+
hide: false,
160+
},
161+
},
162+
},
163+
{
164+
id: "viewports.mdx",
165+
slug: "viewports",
166+
collection: "modes",
167+
data: {
168+
title: "Viewports",
169+
sidebar: {
170+
label: "Viewports",
171+
order: 2,
172+
hide: false,
173+
},
174+
},
175+
},
176+
{
177+
id: "themes.md",
178+
slug: "themes",
179+
collection: "modes",
180+
data: {
181+
title: "Themes",
182+
sidebar: {
183+
label: "Themes",
184+
order: 3,
185+
hide: false,
186+
},
187+
},
188+
},
189+
{
190+
id: "custom-decorators.md",
191+
slug: "custom-decorators",
192+
collection: "modes",
193+
data: {
194+
title: "Custom decorators and globals",
195+
sidebar: {
196+
label: "Custom decorators",
197+
order: 4,
198+
hide: false,
199+
},
200+
},
201+
},
202+
{
203+
id: "legacy-viewports.md",
204+
slug: "legacy-viewports",
205+
collection: "modes",
206+
data: {
207+
title: "Viewports (legacy)",
208+
sidebar: {
209+
label: "Viewports (legacy)",
210+
order: 5,
211+
hide: false,
212+
},
213+
},
214+
},
215+
],
216+
},
217+
]),
218+
};
219+
144220
export const Default: Story = {
145221
args: {
146222
sidebarItems: mockSidebarItems,
@@ -156,6 +232,24 @@ export const DefaultOpen: Story = {
156232
},
157233
};
158234

235+
export const Nested: Story = {
236+
args: {
237+
sidebarItems: [
238+
{ ...mockSidebarItems[0], defaultOpen: true },
239+
mockSidebarItemsWithNestedItems,
240+
],
241+
},
242+
};
243+
244+
export const NestedOpen: Story = {
245+
args: {
246+
sidebarItems: [
247+
{ ...mockSidebarItems[0], defaultOpen: true },
248+
mockSidebarItemsWithNestedItems,
249+
],
250+
},
251+
};
252+
159253
export const WithActiveUrl: Story = {
160254
args: {
161255
url: "/docs/test",

src/components/Navigation/SideNav.tsx

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,15 @@ const IconWrapper = styled.div`
3232
display: flex;
3333
align-items: center;
3434
justify-content: center;
35-
width: 14px;
36-
height: 14px;
35+
width: 10px;
36+
height: 10px;
3737
transition: all 0.2s ease-in-out;
38-
margin-top: -2px;
3938
`;
4039

4140
const ContentWrapper = styled.div<{ isTimeline: boolean }>`
4241
display: flex;
4342
flex-direction: column;
4443
gap: 4px;
45-
margin-left: 3px;
4644
position: relative;
4745
4846
&:before {
@@ -80,6 +78,11 @@ const ContentItem = styled.div<{ isActive: boolean; isTimeline?: boolean }>`
8078
}
8179
`;
8280

81+
const NestedContent = styled(Collapsible.Content)`
82+
padding-left: 20px;
83+
margin-top: 4px;
84+
`;
85+
8386
const Bullet = styled.div<{ isActive: boolean }>`
8487
position: relative;
8588
z-index: 1;
@@ -93,6 +96,8 @@ const Bullet = styled.div<{ isActive: boolean }>`
9396

9497
const SidebarContainer = styled.div`
9598
display: none;
99+
width: 100%;
100+
padding: 0 ${spacing[8]} 0 ${spacing[2]};
96101
97102
${minMd} {
98103
display: flex;
@@ -128,9 +133,14 @@ type Item = (
128133
};
129134
};
130135

131-
export interface SidebarItem {
136+
interface NestedItem {
132137
title: string;
133138
items: Item[];
139+
}
140+
141+
export interface SidebarItem {
142+
title: string;
143+
items: (Item | NestedItem)[];
134144
defaultOpen?: boolean;
135145
timeline?: boolean;
136146
}
@@ -145,51 +155,93 @@ const withBase = (url: string) =>
145155

146156
const homeUrl = withBase("");
147157

158+
function isNestedItem(item: Item | NestedItem): item is NestedItem {
159+
return (item as NestedItem).items !== undefined;
160+
}
161+
162+
const CollapsibleItem = ({
163+
item,
164+
url,
165+
timeline,
166+
isHome,
167+
}: {
168+
item: Item;
169+
url?: string;
170+
timeline?: boolean;
171+
isHome?: boolean;
172+
}) => {
173+
const isActive =
174+
isHome && item.data.isHome ? true : withBase(item.slug) === url;
175+
176+
return (
177+
<Collapsible.Content asChild>
178+
<Line href={withBase(item.slug)}>
179+
{!!timeline && <Bullet isActive={isActive} />}
180+
<ContentItem isActive={isActive} isTimeline={!!timeline}>
181+
{item.data.sidebar.label}
182+
</ContentItem>
183+
</Line>
184+
</Collapsible.Content>
185+
);
186+
};
187+
188+
const CollapsibleGroup = ({
189+
group,
190+
url,
191+
isHome,
192+
}: {
193+
group: SidebarItem;
194+
url?: string;
195+
isHome?: boolean;
196+
}) => {
197+
const isSomeActive = group.items.some((item) => {
198+
if (isNestedItem(item)) {
199+
return item.items.some((nestedItem) => withBase(nestedItem.slug) === url);
200+
} else {
201+
return withBase(item.slug) === url;
202+
}
203+
});
204+
205+
return (
206+
<Collapsible.Root defaultOpen={group.defaultOpen || isSomeActive}>
207+
<Trigger>
208+
{group.title}
209+
<IconWrapper className="icon-wrapper">
210+
<Icon size={10} name="arrowright" />
211+
</IconWrapper>
212+
</Trigger>
213+
<ContentWrapper isTimeline={!!group.timeline}>
214+
{group.items.map((item, j) => {
215+
if (isNestedItem(item)) {
216+
return (
217+
<NestedContent>
218+
<CollapsibleGroup group={item} url={url} isHome={isHome} />
219+
</NestedContent>
220+
);
221+
}
222+
return (
223+
<CollapsibleItem
224+
item={item}
225+
url={url}
226+
timeline={group.timeline}
227+
isHome={isHome}
228+
/>
229+
);
230+
})}
231+
</ContentWrapper>
232+
</Collapsible.Root>
233+
);
234+
};
235+
148236
export const SideNav = ({ url, sidebarItems }: SideNavProps) => {
149237
const isHome = url === homeUrl;
150238

151239
return (
152240
<SidebarContainer>
153241
{sidebarItems &&
154242
sidebarItems.map((group, i) => {
155-
const isSomeActive = group.items.some(
156-
(item) => withBase(item.slug) === url,
157-
);
158-
159243
return (
160-
<Collapsible.Root
161-
defaultOpen={group.defaultOpen || isSomeActive}
162-
key={i}
163-
>
164-
<Trigger>
165-
<IconWrapper className="icon-wrapper">
166-
<Icon name="arrowright" />
167-
</IconWrapper>
168-
{group.title}
169-
</Trigger>
170-
<ContentWrapper isTimeline={!!group.timeline}>
171-
{group.items.map((item, j) => {
172-
const isActive =
173-
isHome && item.data.isHome
174-
? true
175-
: withBase(item.slug) === url;
176-
177-
return (
178-
<Collapsible.Content key={j} asChild>
179-
<Line href={withBase(item.slug)}>
180-
{!!group.timeline && <Bullet isActive={isActive} />}
181-
<ContentItem
182-
isActive={isActive}
183-
isTimeline={!!group.timeline}
184-
>
185-
{item.data.sidebar.label}
186-
</ContentItem>
187-
</Line>
188-
</Collapsible.Content>
189-
);
190-
})}
191-
</ContentWrapper>
192-
</Collapsible.Root>
244+
<CollapsibleGroup group={group} url={url} isHome={isHome} key={i} />
193245
);
194246
})}
195247
</SidebarContainer>

0 commit comments

Comments
 (0)