Skip to content

Commit aee9bc5

Browse files
Feat/Sidepanel v1 (#223)
* feat: init sidepanel code * chore: cleanup old code from boilerplate * feat: first draft * chore: remove sidepanel from assets * chore: add sidepanel to parent and remove sidebar * remove toggle button from the component itself and expose just the funvtion to toggle * chore: rename toggle button * style: change header styling * add css for the root * style: nav item css * feat: style main section * feat: style collapsed state * feat: add resize handle on border of the root * chore: monir change * feat: add tooltip * chore: sort imports * chore: add todo comment * docs: add sidepanel docs * docs: add mdx for sidepanel * fix: separate out bottom nav * update mdx * feat: moved the profile section from list to its own prop * feat: add logo click * docs: add prop in mdx * chore: deprecate old sidebar * docs: fix new line * chore: replace ElementRef with ComponentRef * chore: remove forwardRef
1 parent 1b09245 commit aee9bc5

File tree

18 files changed

+734
-60
lines changed

18 files changed

+734
-60
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
title: Sidepanel
3+
description: A collapsible side navigation panel component.
4+
---
5+
6+
## Preview
7+
8+
<Preview>
9+
<Sidepanel.Root open={true}>
10+
<Sidepanel.Header logo={<InfoCircledIcon width={24} height={24} />} title="Company Name" />
11+
<Sidepanel.Main>
12+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />} active>Dashboard</Sidepanel.Item>
13+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Analytics</Sidepanel.Item>
14+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Settings</Sidepanel.Item>
15+
</Sidepanel.Main>
16+
<Sidepanel.Footer>
17+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Help</Sidepanel.Item>
18+
</Sidepanel.Footer>
19+
</Sidepanel.Root>
20+
</Preview>
21+
22+
## Installation
23+
24+
Install the component from your command line.
25+
26+
<LiveProvider>
27+
<LiveEditor code={`npm install @raystack/apsara`} border language="shell" />
28+
</LiveProvider>
29+
30+
## Usage
31+
32+
The Sidepanel component provides a collapsible navigation panel with header, main content, and footer sections.
33+
34+
<LiveProvider>
35+
<LiveEditor code={`import { Sidepanel } from '@raystack/apsara/v1'
36+
37+
export default function App() {
38+
return (
39+
<Sidepanel.Root open={true}>
40+
<Sidepanel.Header logo={<InfoCircledIcon />} title="Company Name" />
41+
<Sidepanel.Main>
42+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />} active>Dashboard</Sidepanel.Item>
43+
</Sidepanel.Main>
44+
<Sidepanel.Footer>
45+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Help</Sidepanel.Item>
46+
</Sidepanel.Footer>
47+
</Sidepanel.Root>
48+
)
49+
}`} border/>
50+
</LiveProvider>
51+
52+
## API Reference
53+
54+
### Root Props
55+
56+
- `open`: Controls the expanded/collapsed state (boolean)
57+
- `onOpenChange`: Callback when expanded/collapsed state changes (function)
58+
- `position`: Position of the sidepanel (`"left"` | `"right"`, default: "left")
59+
- `profile`: Optional profile information object
60+
- `icon`: ReactNode
61+
- `label`: string
62+
- `href`: string (optional)
63+
- `onIconClick`: Optional callback for icon click
64+
65+
### Header Props
66+
67+
- `logo`: ReactNode for the header icon/logo
68+
- `title`: string for the header text
69+
- `onLogoClick`: Optional callback for logo click
70+
71+
### Item Props
72+
73+
- `icon`: ReactNode for the item's icon
74+
- `href`: string for the link destination
75+
- `active`: boolean to indicate current selection
76+
- `children`: ReactNode for the item's label
77+
78+
## Sidepanel Props
79+
80+
The Sidepanel component consists of several parts, each with their own props:
81+
82+
### Root
83+
84+
- `open`: Controls the expanded/collapsed state (boolean)
85+
- `onOpenChange`: Callback when expanded/collapsed state changes (function)
86+
- `position`: Position of the sidepanel ("left" | "right", default: "left")
87+
- `account`: Optional account information object with:
88+
- `icon`: Icon element to display (ReactNode)
89+
- `label`: Text to display (string)
90+
- `href`: Optional URL the account links to (string)
91+
92+
### Header
93+
94+
- `logo`: Logo element to display (ReactNode)
95+
- `title`: Title text to display (string)
96+
97+
### Item
98+
99+
- `icon`: Icon element to display (ReactNode)
100+
- `active`: Whether the item is currently active (boolean)
101+
- `href`: URL the item links to (string)
102+
103+
## Position
104+
105+
The Sidepanel can be positioned on either the left or right side of the screen.
106+
107+
<Playground
108+
scope={{ Sidepanel, InfoCircledIcon }}
109+
tabs={[
110+
{
111+
name: "Left",
112+
code: `
113+
<Sidepanel.Root open={true} position="left">
114+
<Sidepanel.Header logo={<InfoCircledIcon />} title="Company Name" />
115+
<Sidepanel.Main>
116+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />} active>Dashboard</Sidepanel.Item>
117+
</Sidepanel.Main>
118+
</Sidepanel.Root>
119+
`,
120+
},
121+
{
122+
name: "Right",
123+
code: `
124+
<Sidepanel.Root open={true} position="right">
125+
<Sidepanel.Header logo={<InfoCircledIcon />} title="Company Name" />
126+
<Sidepanel.Main>
127+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Dashboard</Sidepanel.Item>
128+
</Sidepanel.Main>
129+
</Sidepanel.Root>
130+
`,
131+
},
132+
]}
133+
/>
134+
135+
## State
136+
137+
The Sidepanel supports expanded and collapsed states with smooth transitions.
138+
139+
<Playground
140+
scope={{ Sidepanel, InfoCircledIcon }}
141+
tabs={[
142+
{
143+
name: "Expanded",
144+
code: `
145+
<Sidepanel.Root open={true}>
146+
<Sidepanel.Header logo={<InfoCircledIcon />} title="Company Name" />
147+
<Sidepanel.Main>
148+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />} active>Dashboard</Sidepanel.Item>
149+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Settings</Sidepanel.Item>
150+
</Sidepanel.Main>
151+
</Sidepanel.Root>
152+
`,
153+
},
154+
{
155+
name: "Collapsed",
156+
code: `
157+
<Sidepanel.Root open={false}>
158+
<Sidepanel.Header logo={<InfoCircledIcon />} title="Company Name" />
159+
<Sidepanel.Main>
160+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />} active>Dashboard</Sidepanel.Item>
161+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Settings</Sidepanel.Item>
162+
</Sidepanel.Main>
163+
</Sidepanel.Root>
164+
`,
165+
},
166+
]}
167+
/>
168+
169+
## Accessibility
170+
171+
The Sidepanel implements the following accessibility features:
172+
173+
- Proper ARIA roles and attributes
174+
175+
- `role="navigation"` for the main sidebar
176+
- `role="banner"` for the header
177+
- `role="menuitem"` for navigation items
178+
- `aria-expanded` to indicate sidebar state
179+
- `aria-current="page"` for active items
180+
181+
- Keyboard navigation support
182+
183+
- Enter/Space to toggle sidebar expansion
184+
- Tab navigation through interactive elements
185+
186+
- Screen reader support
187+
- Meaningful labels for all interactive elements
188+
- Hidden decorative elements
189+
- Clear state indicators

apps/www/examples/shield-ts/assets.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const Assets = () => {
9595
const [page, setPage] = useState(1);
9696
const [hasMoreData, setHasMoreData] = useState(true);
9797
const [data, setData] = useState<Payment[]>([]);
98+
const [sidebarOpen, setSidebarOpen] = useState(true);
9899
const [selectedDate, setSelectedDate] = useState<Date>();
99100
const [dateRange, setDateRange] = useState({
100101
from: new Date(),
@@ -295,7 +296,6 @@ const AssetsHeader = () => {
295296
<Text>SEO settings content</Text>
296297
</Tabs.Content>
297298
</Tabs.Root> */}
298-
299299
{/* <Text style={{ fontWeight: 500 }}>Assets</Text> */}
300300
{/* <Spinner size={3} />
301301
<div>

apps/www/examples/shield-ts/shield.tsx

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,70 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import {
3-
ArrowRightIcon,
43
DotsVerticalIcon,
5-
RocketIcon,
4+
HomeIcon,
5+
InfoCircledIcon,
66
} from "@radix-ui/react-icons";
77
import { useRouter } from "next/navigation";
8-
import { ScrollArea, Sidebar } from "@raystack/apsara";
8+
import { Flex, Sidepanel } from "@raystack/apsara/v1";
99
import { Assets } from "./assets";
10-
import { navigationList } from "./data";
1110

1211
import "@raystack/apsara/style.css";
13-
import { AnnouncementBar, Flex } from "@raystack/apsara/v1";
1412

1513
export const Shield = () => {
1614
const router = useRouter();
15+
const [sidebarOpen, setSidebarOpen] = useState(true);
1716

1817
return (
1918
<>
20-
{/* <AnnouncementBar
21-
variant="gradient"
22-
text="We have released new components with better theme support"
23-
leadingIcon={<RocketIcon />}
24-
actionLabel="checkout"
25-
actionIcon={<ArrowRightIcon />}
26-
onActionClick={() =>
27-
router.push("/docs/primitives/overview/introduction")
28-
}
29-
/> */}
30-
<Flex style={{ height: "100%" }}>
31-
<Sidebar>
32-
<Flex direction="column">
33-
<Sidebar.Logo />
34-
<Sidebar.Navigations
35-
// @ts-ignore
36-
style={{
37-
marginTop: "22px",
38-
}}
39-
>
40-
<ScrollArea style={{ paddingRight: "var(--mr-16)" }}>
41-
{navigationList.map((nav) => (
42-
<Sidebar.NavigationCell
43-
key={nav.name}
44-
href={nav.href}
45-
leadingIcon={nav.leadingIcon}
46-
active={nav.active}
47-
disabled={nav.disabled}
48-
>
49-
{nav.name}
50-
</Sidebar.NavigationCell>
51-
))}
52-
</ScrollArea>
53-
</Sidebar.Navigations>
54-
</Flex>
55-
<Sidebar.Footer action={<DotsVerticalIcon />}>
56-
57-
</Sidebar.Footer>
58-
</Sidebar>
19+
<Flex style={{ height: "100vh" }}>
20+
<div style={{ position: 'relative' }}>
21+
<Sidepanel.Root
22+
open={sidebarOpen}
23+
onOpenChange={setSidebarOpen}
24+
position="left"
25+
profile={{
26+
icon: <DotsVerticalIcon />,
27+
28+
href: "#"
29+
}}
30+
>
31+
<Sidepanel.Header
32+
logo={<HomeIcon width={24} height={24} />}
33+
title="The North Face"
34+
/>
35+
<Sidepanel.Main>
36+
<Sidepanel.Item href="#" icon={<HomeIcon />} active>Explore</Sidepanel.Item>
37+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>AOIs</Sidepanel.Item>
38+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Workflows</Sidepanel.Item>
39+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Marketplace</Sidepanel.Item>
40+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Activity</Sidepanel.Item>
41+
</Sidepanel.Main>
42+
<Sidepanel.Footer>
43+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Feedback</Sidepanel.Item>
44+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Support</Sidepanel.Item>
45+
<Sidepanel.Item href="#" icon={<InfoCircledIcon />}>Documentation</Sidepanel.Item>
46+
</Sidepanel.Footer>
47+
</Sidepanel.Root>
48+
49+
<button
50+
onClick={() => setSidebarOpen(!sidebarOpen)}
51+
style={{
52+
position: 'absolute',
53+
top: '16px',
54+
right: '-12px',
55+
height: '24px',
56+
cursor: 'pointer',
57+
background: 'var(--rs-color-background-base-primary)',
58+
border: '1px solid var(--rs-color-border-base-primary)',
59+
borderRadius: 'var(--rs-radius-2)',
60+
padding: '4px 8px',
61+
zIndex: 1
62+
}}
63+
>
64+
65+
</button>
66+
</div>
67+
5968
<Flex style={{ flex: 1, width: "100%", overflow: "auto" }}>
6069
<Assets />
6170
</Flex>

apps/www/utils/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const primitivesRoutes = [
9393
},
9494
{ title: "Switch", slug: "docs/primitives/components/switch", newBadge: true },
9595
{ title: "Slider", slug: "docs/primitives/components/slider", newBadge: true },
96+
{ title: "Side panel", slug: "docs/primitives/components/sidepanel", newBadge: true },
9697
{ title: "Tabs", slug: "docs/primitives/components/tabs", newBadge: true },
9798
{ title: "Table", slug: "docs/primitives/components/table" },
9899
{

packages/raystack/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "./style.css"; // Old styles file. To be removed after Apsara v1 release
1+
import "./style.css"; // Old styles file. To be removed after Apsara v1 migration is complete.
22
import "./v1/styles/index.css";
33
export { Accordion } from "./accordion";
44
export { Avatar } from "./avatar";

packages/raystack/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"@radix-ui/react-accordion": "^1.1.2",
8585
"@radix-ui/react-avatar": "^1.0.3",
8686
"@radix-ui/react-checkbox": "^1.0.4",
87+
"@radix-ui/react-collapsible": "^1.1.2",
8788
"@radix-ui/react-dialog": "^1.0.4",
8889
"@radix-ui/react-dropdown-menu": "^2.0.5",
8990
"@radix-ui/react-icons": "^1.3.0",

0 commit comments

Comments
 (0)