Skip to content

Commit

Permalink
feat: navigation tree item
Browse files Browse the repository at this point in the history
  • Loading branch information
gndz07 authored Mar 1, 2023
1 parent f661fc0 commit 9511631
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 0 deletions.
66 changes: 66 additions & 0 deletions components/NavigationTree/NavigationTree.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useState } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { NavigationTreeItem } from './NavigationTreeItem';
import { NavigationTreeContainer } from './NavigationTreeContainer';
import { NavigationDrawer } from '../Navigation';
import {
ArchiveIcon,
EyeOpenIcon,
EyeClosedIcon,
EnvelopeClosedIcon,
EnvelopeOpenIcon,
} from '@radix-ui/react-icons';

export default {
title: 'Components/NavigationTree',
component: NavigationTreeContainer,
argTypes: {
defaultCollapseIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeClosedIcon />,
Envelope: <EnvelopeOpenIcon />,
},
},
defaultExpandIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeOpenIcon />,
Envelope: <EnvelopeClosedIcon />,
},
},
},
} as ComponentMeta<typeof NavigationTreeContainer>;

const Template: ComponentStory<typeof NavigationTreeContainer> = (args) => {
const [currentRoute, setCurrentRoute] = useState('/');

const navigationHandlerProps = (route: string) => ({
active: route === currentRoute,
onClick: () => setCurrentRoute(route),
});

return (
<NavigationDrawer>
<NavigationTreeContainer {...args}>
<NavigationTreeItem {...navigationHandlerProps('one')} label="One">
<NavigationTreeItem {...navigationHandlerProps('one-one')} as="a" label="One.One" />
<NavigationTreeItem {...navigationHandlerProps('one-two')} label="One.Two">
<NavigationTreeItem
{...navigationHandlerProps('one-two-one')}
startAdornment={<ArchiveIcon />}
label="One.Two.One"
/>
</NavigationTreeItem>
</NavigationTreeItem>
<NavigationTreeItem {...navigationHandlerProps('two')} label="Two" />
</NavigationTreeContainer>
</NavigationDrawer>
);
};

export const Basic = Template.bind({});

Basic.args = {};
26 changes: 26 additions & 0 deletions components/NavigationTree/NavigationTreeContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons';
import React from 'react';
import { NavigationContainer, NavigationContainerProps } from '../Navigation';
import { CSS } from '../../stitches.config';

export interface TreeViewProps {
children: React.ReactNode;
defaultExpandIcon?: React.ReactNode;
defaultCollapseIcon?: React.ReactNode;
css?: CSS;
}

export const NavigationTreeContainer = ({
children,
defaultCollapseIcon = <ChevronDownIcon />,
defaultExpandIcon = <ChevronRightIcon />,
...props
}: TreeViewProps & NavigationContainerProps) => {
const renderChildren = React.Children.map(children, (child) => {
return React.cloneElement(child as React.ReactElement, {
defaultCollapseIcon,
defaultExpandIcon,
});
});
return <NavigationContainer {...props}>{renderChildren}</NavigationContainer>;
};
114 changes: 114 additions & 0 deletions components/NavigationTree/NavigationTreeItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { NavigationTreeItem } from './NavigationTreeItem';
import { NavigationTreeContainer } from './NavigationTreeContainer';
import { NavigationDrawer } from '../Navigation';
import {
ArchiveIcon,
EyeOpenIcon,
EyeClosedIcon,
EnvelopeClosedIcon,
EnvelopeOpenIcon,
DashboardIcon,
PersonIcon,
GearIcon,
QuestionMarkCircledIcon,
} from '@radix-ui/react-icons';

export default {
title: 'Components/NavigationTree',
component: NavigationTreeItem,
argTypes: {
customExpandIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeClosedIcon />,
Envelope: <EnvelopeOpenIcon />,
},
},
customCollapseIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeOpenIcon />,
Envelope: <EnvelopeClosedIcon />,
},
},
defaultCollapseIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeClosedIcon />,
Envelope: <EnvelopeOpenIcon />,
},
},
defaultExpandIcon: {
options: ['Default', 'Eye', 'Envelope'],
mapping: {
Default: undefined,
Eye: <EyeOpenIcon />,
Envelope: <EnvelopeClosedIcon />,
},
},
label: {
control: 'text',
},
startAdornment: {
options: ['None', 'Dashboard', 'Gear', 'Person', 'Question'],
mapping: {
None: null,
Dashboard: <DashboardIcon />,
Gear: <GearIcon />,
Person: <PersonIcon />,
Question: <QuestionMarkCircledIcon />,
},
},
endAdornment: {
options: ['None', 'Dashboard', 'Gear', 'Person', 'Question'],
mapping: {
None: null,
Dashboard: <DashboardIcon />,
Gear: <GearIcon />,
Person: <PersonIcon />,
Question: <QuestionMarkCircledIcon />,
},
},
active: {
control: 'boolean',
},
},
} as ComponentMeta<typeof NavigationTreeItem>;

const Template: ComponentStory<typeof NavigationTreeItem> = (args) => {
const [currentRoute, setCurrentRoute] = useState('/');

const navigationHandlerProps = (route: string) => ({
active: route === currentRoute,
onClick: () => setCurrentRoute(route),
});

return (
<NavigationDrawer>
<NavigationTreeContainer
defaultCollapseIcon={args.defaultCollapseIcon}
defaultExpandIcon={args.defaultExpandIcon}
>
<NavigationTreeItem {...navigationHandlerProps('one')} label="One">
<NavigationTreeItem {...navigationHandlerProps('one-one')} {...args}>
<NavigationTreeItem
{...navigationHandlerProps('one-one-one')}
startAdornment={<ArchiveIcon />}
label="One.One.One"
/>
</NavigationTreeItem>
</NavigationTreeItem>
</NavigationTreeContainer>
</NavigationDrawer>
);
};

export const TreeItem = Template.bind({});
TreeItem.args = {
label: 'One.One',
};
71 changes: 71 additions & 0 deletions components/NavigationTree/NavigationTreeItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useMemo, useState } from 'react';
import { Box } from '../Box';
import { NavigationItem, NavigationItemProps } from '../Navigation';
import { NavigationTreeContainer } from './NavigationTreeContainer';

export interface NavigationTreeItemProps {
label: string;
children?: React.ReactNode;
onClick?: () => void;
defaultExpandIcon?: React.ReactNode;
defaultCollapseIcon?: React.ReactNode;
customExpandIcon?: React.ReactNode;
customCollapseIcon?: React.ReactNode;
}

export const NavigationTreeItem = ({
label,
children,
onClick,
defaultCollapseIcon,
defaultExpandIcon,
customCollapseIcon,
customExpandIcon,
...props
}: NavigationTreeItemProps & NavigationItemProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const isExpandable = useMemo(() => React.Children.count(children) > 0, [children]);
const hasStartAdornment = useMemo(() => !!props.startAdornment, [props.startAdornment]);
const usedStartAdornment = useMemo(
() =>
hasStartAdornment
? props.startAdornment
: isExpandable
? isExpanded
? customCollapseIcon || defaultCollapseIcon
: customExpandIcon || defaultExpandIcon
: null,
[
hasStartAdornment,
isExpandable,
isExpanded,
defaultCollapseIcon,
defaultExpandIcon,
customCollapseIcon,
customExpandIcon,
props.startAdornment,
]
);

return (
<Box>
<NavigationItem
css={{ width: '100%' }}
{...props}
startAdornment={usedStartAdornment}
onClick={isExpandable ? () => setIsExpanded(!isExpanded) : onClick}
>
<Box css={{ ml: isExpandable || hasStartAdornment ? 0 : '$4' }}>{label}</Box>
</NavigationItem>
{isExpanded && (
<NavigationTreeContainer
defaultCollapseIcon={defaultCollapseIcon}
defaultExpandIcon={defaultExpandIcon}
css={{ ml: '$4' }}
>
{children}
</NavigationTreeContainer>
)}
</Box>
);
};
2 changes: 2 additions & 0 deletions components/NavigationTree/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './NavigationTreeItem';
export * from './NavigationTreeContainer';
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export {
NavigationItem,
NavigationLink,
} from './components/Navigation';
export { NavigationTreeContainer, NavigationTreeItem } from './components/NavigationTree';
export { Overlay } from './components/Overlay';
export {
Popover,
Expand Down

0 comments on commit 9511631

Please sign in to comment.