Skip to content
This repository was archived by the owner on Mar 20, 2023. It is now read-only.

Commit eb40dd6

Browse files
feat(ui): create Sidebar and SidebarItem components (#10) (#163)
* add draft sidebar, sidebarItem and stories for them * add conditionals to components and small changes in storybook * fix pls * fixD * add small changes to sidebar and sidebar item * fix sidebar router * add wrapper with sidebar for authorized routes * add basic tests for sidebar and sbitem * fix * fix typo in sidebarItem stories * fix sidebarItem disable test * fix sidebarItem styles and sidebarItem stories
1 parent d25ac78 commit eb40dd6

File tree

13 files changed

+2575
-130
lines changed

13 files changed

+2575
-130
lines changed

packages/panel/src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { BrowserRouter, Switch } from 'react-router-dom';
33

4+
import { Layout } from './components/Layout';
45
import { AuthorizedRoute, Redirect404, UnauthorizedRoute } from './components/RoutingAuth';
56
import { WaitForUser } from './components/WaitForUser';
67
import { CalendarPage } from './screens/CalendarPage';
@@ -21,8 +22,10 @@ export const App = () => {
2122
</UnauthorizedRoute>
2223
<UnauthorizedRoute exact path="/register" component={Register} />
2324
<UnauthorizedRoute exact path="/login" component={Login} />
24-
<AuthorizedRoute exact path="/calendar" component={CalendarPage} />
25-
<AuthorizedRoute exact path="/dashboard" component={Dashboard} />
25+
<Layout>
26+
<AuthorizedRoute exact path="/calendar" component={CalendarPage} />
27+
<AuthorizedRoute exact path="/dashboard" component={Dashboard} />
28+
</Layout>
2629
<Redirect404 />
2730
</Switch>
2831
</BrowserRouter>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
3+
import { Box } from '@coderscamp/ui/components/Box';
4+
import { Sidebar } from '@coderscamp/ui/components/Sidebar';
5+
import { HStack } from '@coderscamp/ui/components/Stack';
6+
7+
import { useIsUserAuthorized } from '@/modules/user/userHooks';
8+
9+
export const Layout: React.FC = ({ children }) => {
10+
const isAuthorized = useIsUserAuthorized();
11+
12+
return (
13+
<HStack>
14+
{isAuthorized && <Sidebar width="256px" />}
15+
<Box w="100%">{children}</Box>
16+
</HStack>
17+
);
18+
};

packages/ui/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@emotion/react": "11.4.1",
2323
"@emotion/styled": "11.3.0",
2424
"@types/react-router-dom": "5.1.9",
25+
"@types/storybook-react-router": "1.0.1",
2526
"framer-motion": "4.1.17",
2627
"react-router-dom": "5.3.0"
2728
},
@@ -40,6 +41,7 @@
4041
"@testing-library/user-event": "13.2.1",
4142
"@types/react": "17.0.21",
4243
"@types/react-dom": "17.0.9",
43-
"react": "17.0.2"
44+
"react": "17.0.2",
45+
"storybook-react-router": "1.0.8"
4446
}
4547
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Divider } from '@chakra-ui/react';
2+
export type { DividerProps } from '@chakra-ui/react';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { Meta, Story } from '@storybook/react';
3+
import StoryRouter from 'storybook-react-router';
4+
5+
import { Sidebar, SidebarProps } from './Sidebar';
6+
7+
const meta: Meta = {
8+
title: 'Components / Sidebar',
9+
component: Sidebar,
10+
decorators: [StoryRouter()],
11+
};
12+
13+
export default meta;
14+
15+
const Template: Story<SidebarProps> = (args) => <Sidebar {...args} />;
16+
17+
export const Playground = Template.bind({});
18+
19+
Playground.args = {
20+
width: '256px',
21+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import { MemoryRouter } from 'react-router-dom';
3+
import { render, screen } from '@testing-library/react';
4+
5+
import { Sidebar } from './Sidebar';
6+
7+
describe('Sidebar', () => {
8+
it('renders correctly', () => {
9+
render(<Sidebar data-testid="sidebar" width="256px" />, { wrapper: MemoryRouter });
10+
11+
const getSidebar = screen.getByTestId('sidebar');
12+
13+
expect(getSidebar).toBeInTheDocument();
14+
});
15+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
3+
import {
4+
OutlinedCalendarIcon,
5+
OutlinedDashboardIcon,
6+
OutlinedRocketIcon,
7+
OutlinedSelectorIcon,
8+
SolidCalendarIcon,
9+
SolidDashboardIcon,
10+
SolidRocketIcon,
11+
} from '../../icons';
12+
import { Avatar } from '../Avatar';
13+
import { Center } from '../Center';
14+
import { Divider } from '../Divider';
15+
import { Flex } from '../Flex';
16+
import { IconButton } from '../IconButton';
17+
import { Logo } from '../Logo';
18+
import { SidebarItem } from '../SidebarItem';
19+
import { HStack, Stack, VStack } from '../Stack';
20+
import { Typography } from '../Typography';
21+
22+
export interface SidebarProps {
23+
width: string;
24+
}
25+
26+
const SidebarItems = [
27+
{
28+
title: 'Panel kursu',
29+
path: '/dashboard',
30+
icon: <OutlinedDashboardIcon />,
31+
iconSelected: <SolidDashboardIcon />,
32+
},
33+
{
34+
title: 'Projekty',
35+
path: '/projects',
36+
icon: <OutlinedRocketIcon />,
37+
iconSelected: <SolidRocketIcon />,
38+
},
39+
{
40+
title: 'Kalendarz',
41+
path: '/calendar',
42+
icon: <OutlinedCalendarIcon />,
43+
iconSelected: <SolidCalendarIcon />,
44+
},
45+
];
46+
47+
export const Sidebar = ({ width, ...rest }: SidebarProps) => {
48+
return (
49+
<Flex
50+
w={width}
51+
h="100vh"
52+
pos="relative"
53+
top="0"
54+
left="0"
55+
bottom="0"
56+
flexDir="column"
57+
justify="space-between"
58+
{...rest}
59+
>
60+
<VStack h="max" p="20px 0 16px" spacing="20px">
61+
<Center p="0 12px">
62+
<Logo color="black" />
63+
</Center>
64+
<VStack w="100%" p="0 8px" spacing="4px" flexGrow={1}>
65+
{SidebarItems.map((item) => (
66+
<SidebarItem key={item.title} path={item.path} icon={item.icon} iconSelected={item.iconSelected}>
67+
{item.title}
68+
</SidebarItem>
69+
))}
70+
</VStack>
71+
</VStack>
72+
<VStack w={width} h="73px" spacing="0">
73+
<Divider bg="gray.200" />
74+
<Center w="100%" p="16px">
75+
<HStack w="100%" spacing="0">
76+
<HStack w="100%" spacing="12px">
77+
{/* in the future add src to the avatar */}
78+
<Avatar size="sm" />
79+
<Stack spacing="0">
80+
{/* in the future add name and surname from user*/}
81+
<Typography color="gray.700" size="sm" weight="medium">
82+
Christiana
83+
</Typography>
84+
<Typography color="gray.700" size="sm" weight="medium">
85+
Michaelson
86+
</Typography>
87+
</Stack>
88+
</HStack>
89+
<IconButton icon={<OutlinedSelectorIcon />} variant="ghost" size="md" aria-label="Selector" />
90+
</HStack>
91+
</Center>
92+
</VStack>
93+
</Flex>
94+
);
95+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Sidebar } from './Sidebar';
2+
export type { SidebarProps } from './Sidebar';
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from 'react';
2+
import { VStack } from '@chakra-ui/react';
3+
import { Meta, Story } from '@storybook/react';
4+
import StoryRouter from 'storybook-react-router';
5+
6+
import {
7+
OutlinedCalendarIcon,
8+
OutlinedDashboardIcon,
9+
OutlinedRocketIcon,
10+
SolidCalendarIcon,
11+
SolidDashboardIcon,
12+
SolidRocketIcon,
13+
} from '../../icons';
14+
import { HStack } from '../Stack';
15+
import { SidebarItem, SidebarItemProps } from './SidebarItem';
16+
17+
const icons = {
18+
Dashboard: <OutlinedDashboardIcon />,
19+
Rocket: <OutlinedRocketIcon />,
20+
Calendar: <OutlinedCalendarIcon />,
21+
None: undefined,
22+
};
23+
24+
const iconsSelected = {
25+
Dashboard: <SolidDashboardIcon />,
26+
Rocket: <SolidRocketIcon />,
27+
Calendar: <SolidCalendarIcon />,
28+
None: undefined,
29+
};
30+
31+
const meta: Meta = {
32+
title: 'Components / SidebarItem',
33+
component: SidebarItem,
34+
argTypes: {
35+
icon: {
36+
options: Object.keys(icons),
37+
mapping: icons,
38+
control: {
39+
type: 'select',
40+
},
41+
},
42+
iconSelected: {
43+
options: Object.keys(iconsSelected),
44+
mapping: iconsSelected,
45+
control: {
46+
type: 'select',
47+
},
48+
},
49+
},
50+
decorators: [StoryRouter()],
51+
};
52+
53+
export default meta;
54+
55+
const Template: Story<SidebarItemProps> = (args) => <SidebarItem {...args} />;
56+
57+
export const Playground = Template.bind({});
58+
59+
Playground.args = {
60+
children: 'Dashboard',
61+
count: undefined,
62+
path: '/panel',
63+
};
64+
65+
export const Other = () => (
66+
<VStack>
67+
<HStack w="788px" spacing="10px">
68+
<SidebarItem path="/k">Dashboard</SidebarItem>
69+
<SidebarItem path="/">Active</SidebarItem>
70+
<SidebarItem path="/a" disabled>
71+
Disabled
72+
</SidebarItem>
73+
</HStack>
74+
<HStack w="788px" spacing="10px">
75+
<SidebarItem icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} path="/m">
76+
Dashboard
77+
</SidebarItem>
78+
<SidebarItem icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} path="/">
79+
Active
80+
</SidebarItem>
81+
<SidebarItem icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} path="/i" disabled>
82+
Disabled
83+
</SidebarItem>
84+
</HStack>
85+
<HStack w="788px" spacing="10px">
86+
<SidebarItem count={1} path="/l">
87+
Dashboard
88+
</SidebarItem>
89+
<SidebarItem count={2} path="">
90+
Active
91+
</SidebarItem>
92+
<SidebarItem count={3} path="/t" disabled>
93+
Disabled
94+
</SidebarItem>
95+
</HStack>
96+
<HStack w="788px" spacing="10px">
97+
<SidebarItem icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} count={314} path="/u">
98+
Dashboard
99+
</SidebarItem>
100+
<SidebarItem icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} count={314} path="">
101+
Active
102+
</SidebarItem>
103+
<SidebarItem
104+
icon={<OutlinedDashboardIcon />}
105+
iconSelected={<SolidDashboardIcon />}
106+
count={314}
107+
path="/byl"
108+
disabled
109+
>
110+
Disabled
111+
</SidebarItem>
112+
</HStack>
113+
</VStack>
114+
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import { MemoryRouter } from 'react-router-dom';
3+
import { render, screen } from '@testing-library/react';
4+
5+
import { OutlinedDashboardIcon, SolidDashboardIcon } from '../../icons';
6+
import { SidebarItem } from './SidebarItem';
7+
8+
describe('SidebarItem', () => {
9+
it('renders correctly', () => {
10+
render(
11+
<SidebarItem path="/" icon={<OutlinedDashboardIcon />} iconSelected={<SolidDashboardIcon />} count={2021}>
12+
Dashboard
13+
</SidebarItem>,
14+
{ wrapper: MemoryRouter },
15+
);
16+
17+
const sidebarItem = screen.getByRole('group');
18+
const sidebarItemIcon = screen.getByLabelText(/dashboard/i);
19+
const sidebarItemBadge = screen.getByText(2021);
20+
21+
expect(sidebarItem).toBeInTheDocument();
22+
expect(sidebarItemIcon).toBeInTheDocument();
23+
expect(sidebarItemBadge).toBeInTheDocument();
24+
});
25+
26+
it('renders correctly in disabled state', () => {
27+
render(
28+
<SidebarItem path="/" disabled>
29+
Dashboard
30+
</SidebarItem>,
31+
{ wrapper: MemoryRouter },
32+
);
33+
34+
const sidebarItem = screen.getByRole(/group/i);
35+
// eslint-disable-next-line
36+
expect(sidebarItem).toHaveAttribute('disabled');
37+
});
38+
});

0 commit comments

Comments
 (0)