Skip to content

Commit

Permalink
Merge pull request #11 from selsa-inube/cmarin/ids1195/refactor-to-ma…
Browse files Browse the repository at this point in the history
…ke-collapsable

Refactor `<Nav />` to make it collapsable
  • Loading branch information
cmarin001 committed May 16, 2024
2 parents 98f6a8d + 0c35119 commit 538ce83
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 48 deletions.
146 changes: 112 additions & 34 deletions src/Nav/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ import { ITextAppearance, Text } from "@inubekit/text";
import { Stack } from "@inubekit/stack";
import { inube } from "@inubekit/foundations";

import { StyledNav, StyledFooter, SeparatorLine } from "./styles";
import {
StyledNav,
StyledFooter,
SeparatorLine,
StyledCollapseContainer,
StyledAnimatedWrapper,
StyledRotatingIcon,
} from "./styles";
import { NavLink } from "../NavLink";
import { ILink, INavNavigation } from "./props";

import { useContext } from "react";
import { useContext, useEffect, useState } from "react";
import { ThemeContext } from "styled-components";
import { Icon } from "@inubekit/icon";

interface INav {
navigation: INavNavigation;
logoutPath: string;
logoutTitle: string;
collapse?: boolean;
}

interface INavLink {
Expand All @@ -24,6 +33,11 @@ interface INavLink {

const year = new Date().getFullYear();

const defaultAnimationValues = {
duration: 0.2,
ease: "ease-in-out",
};

const Links = (props: INavLink) => {
const { section } = props;

Expand All @@ -42,38 +56,106 @@ const Links = (props: INavLink) => {
return <>{LinkElements} </>;
};

const MultiSections = ({ navigation }: INav) => {
const sections = Object.keys(navigation.sections);
const MultiSections = ({
navigation,
collapse,
}: {
navigation: INavNavigation;
collapse: boolean;
}) => {
const [expandedSection, setExpandedSection] = useState<string | null>(null);
const theme: typeof inube = useContext(ThemeContext);
const navTitleAppearance =
(theme?.nav?.title?.appearance as ITextAppearance) ||
inube.nav.title.appearance;
const navRegularTitleAppearance =
(theme?.nav?.subtitle?.appearance?.regular as ITextAppearance) ||
inube.nav.subtitle.appearance.regular;
const navExpandedTitleAppearance =
(theme?.nav?.subtitle?.appearance?.expanded as ITextAppearance) ||
inube.nav.subtitle.appearance.expanded;

useEffect(() => {
if (collapse && Object.keys(navigation.sections).length > 0) {
setExpandedSection(
Object.keys(navigation.sections)[0].toLocaleUpperCase(),
);
}
}, [collapse, navigation.sections]);

const toggleSection = (sectionName: string) => {
setExpandedSection((prevSection) =>
prevSection === sectionName ? null : sectionName,
);
};

return (
<Stack direction="column">
{sections.map((section) => (
<Stack
key={navigation.sections[section].name}
direction="column"
justifyContent="center"
>
<Text
padding="16px"
as="h2"
appearance={navTitleAppearance}
type="title"
size="small"
textAlign="start"
{Object.keys(navigation.sections).map((section) => {
const isExpanded = collapse
? expandedSection === navigation.sections[section].name
: true;

return (
<Stack
key={navigation.sections[section].name}
direction="column"
justifyContent="center"
>
{navigation.sections[section].name}
</Text>
<Stack direction="column">
<Links
section={Object.values(navigation.sections[section].links)}
/>
<StyledCollapseContainer
onClick={() =>
collapse && toggleSection(navigation.sections[section].name)
}
$collapse={collapse}
$expanded={isExpanded}
>
<Stack
direction="row"
alignItems="center"
padding="16px"
height="52px"
justifyContent={collapse ? "space-between" : "unset"}
>
<Text
as="h2"
appearance={
collapse && isExpanded
? navExpandedTitleAppearance
: navRegularTitleAppearance
}
type="title"
size="small"
textAlign="start"
>
{navigation.sections[section].name}
</Text>
{collapse && (
<Icon
appearance={
isExpanded
? navExpandedTitleAppearance
: navRegularTitleAppearance
}
icon={
<StyledRotatingIcon $expanded={isExpanded} size="20px" />
}
/>
)}
</Stack>
</StyledCollapseContainer>

<StyledAnimatedWrapper
open={isExpanded}
animation={defaultAnimationValues}
>
{isExpanded && (
<Stack direction="column">
<Links
section={Object.values(navigation.sections[section].links)}
/>
</Stack>
)}
</StyledAnimatedWrapper>
</Stack>
</Stack>
))}
);
})}
</Stack>
);
};
Expand All @@ -91,7 +173,7 @@ const OneSection = ({ navigation }: INav) => {
};

const Nav = (props: INav) => {
const { navigation, logoutTitle, logoutPath } = props;
const { navigation, logoutTitle, logoutPath, collapse = false } = props;
const theme: typeof inube = useContext(ThemeContext);
const navSubtitleAppearance =
(theme?.nav?.subtitle?.appearance?.regular as ITextAppearance) ||
Expand All @@ -114,11 +196,7 @@ const Nav = (props: INav) => {
{navigation.title}
</Text>
{Object.keys(navigation.sections).length > 1 ? (
<MultiSections
navigation={navigation}
logoutPath={logoutPath}
logoutTitle={logoutTitle}
/>
<MultiSections navigation={navigation} collapse={collapse} />
) : (
<OneSection
navigation={navigation}
Expand Down
8 changes: 8 additions & 0 deletions src/Nav/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ const props = {
description:
"is the path where the user is going to navigate when he wants to logout and is required",
},
logoutTitle: {
description:
"is the title for the path where the user is going to navigate when he wants to logout and is required",
},
collapse: {
description:
"indicates if the component should be collapsible or not (by default is false)",
},
};

export { parameters, props };
Expand Down
5 changes: 3 additions & 2 deletions src/Nav/stories/Nav.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Default.args = {
title: "MENU",
sections: {
administrate: {
name: "Administrate",
name: "ADMINISTRATE",
links: {
text: {
id: "text",
Expand Down Expand Up @@ -63,7 +63,7 @@ Default.args = {
},
},
request: {
name: "Request",
name: "REQUEST",
links: {
documents: {
id: "documents",
Expand Down Expand Up @@ -95,6 +95,7 @@ Default.args = {
},
logoutPath: "/logout",
logoutTitle: "logout",
collapse: true,
};

export { Default };
Expand Down
56 changes: 44 additions & 12 deletions src/Nav/styles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
import styled from "styled-components";
import { inube } from "@inubekit/foundations";
import { MdKeyboardArrowDown } from "react-icons/md";

const SeparatorLine = styled.div`
width: calc(100% - 32px);
margin: 8px 16px;
height: 1px;
padding: 0px;
background-color: ${({ theme }) =>
theme?.nav?.divider?.color || inube.nav.divider.color};
`;

const StyledAnimatedWrapper = styled.div`
opacity: 0;
transition: all ${(props) => props.animation.duration}s
${(props) => props.animation.ease};
opacity: ${(props) => (props.open ? 1 : 0)};
`;

const StyledCollapseContainer = styled.div`
cursor: ${({ $collapse }) => ($collapse ? "pointer" : "default")};
& > div {
background-color: ${({ theme, $collapse, $expanded }) =>
$collapse && $expanded
? theme?.nav?.subtitle?.background?.expanded ||
inube.nav.subtitle.background.expanded
: theme?.nav?.background?.color || inube.nav.background.color};
}
`;

const StyledFooter = styled.footer`
width: 100%;
`;

const StyledNav = styled.div`
width: 248px;
Expand All @@ -10,17 +42,17 @@ const StyledNav = styled.div`
${({ theme }) => theme?.nav?.divider?.color || inube.nav.divider.color};
`;

const StyledFooter = styled.footer`
width: 100%;
`;

const SeparatorLine = styled.div`
width: calc(100% - 32px);
margin: 8px 16px;
height: 1px;
padding: 0px;
background-color: ${({ theme }) =>
theme?.nav?.divider?.color || inube.nav.divider.color};
const StyledRotatingIcon = styled(MdKeyboardArrowDown)`
transition: transform 0.2s ease-in-out;
transform: ${({ $expanded }) =>
$expanded ? "rotate(180deg)" : "rotate(0deg)"};
`;

export { StyledNav, StyledFooter, SeparatorLine };
export {
SeparatorLine,
StyledAnimatedWrapper,
StyledCollapseContainer,
StyledFooter,
StyledNav,
StyledRotatingIcon,
};

0 comments on commit 538ce83

Please sign in to comment.