diff --git a/apps/website/pages/components/sidenav/code.tsx b/apps/website/pages/components/sidenav/code.tsx new file mode 100644 index 0000000000..9bb8d2993a --- /dev/null +++ b/apps/website/pages/components/sidenav/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import SidenavPageLayout from "screens/components/sidenav/SidenavPageLayout"; +import SidenavCodePage from "screens/components/sidenav/code/SidenavCodePage"; + +const Code = () => ( + <> + + Sidenav code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/sidenav/index.tsx b/apps/website/pages/components/sidenav/index.tsx index 6a3bcca9bc..50ec17a206 100644 --- a/apps/website/pages/components/sidenav/index.tsx +++ b/apps/website/pages/components/sidenav/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; import SidenavPageLayout from "screens/components/sidenav/SidenavPageLayout"; -import SidenavCodePage from "screens/components/sidenav/code/SidenavCodePage"; +import SidenavOverviewPage from "screens/components/sidenav/overview/SidenavOverviewPage"; -const Index = () => { - return ( - <> - - Sidenav — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Sidenav — Halstack Design System + + + +); -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; export default Index; diff --git a/apps/website/pages/components/sidenav/specifications.tsx b/apps/website/pages/components/sidenav/specifications.tsx deleted file mode 100644 index 14362eefee..0000000000 --- a/apps/website/pages/components/sidenav/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import SidenavPageLayout from "screens/components/sidenav/SidenavPageLayout"; -import SidenavSpecsPage from "screens/components/sidenav/specs/SidenavSpecsPage"; - -const Specifications = () => { - return ( - <> - - Sidenav Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/sidenav/usage.tsx b/apps/website/pages/components/sidenav/usage.tsx deleted file mode 100644 index 5e1005e65e..0000000000 --- a/apps/website/pages/components/sidenav/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import SidenavPageLayout from "screens/components/sidenav/SidenavPageLayout"; -import SidenavUsagePage from "screens/components/sidenav/usage/SidenavUsagePage"; - -const Usage = () => { - return ( - <> - - Sidenav Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/components/sidenav/SidenavPageLayout.tsx b/apps/website/screens/components/sidenav/SidenavPageLayout.tsx index a0033c7a9e..9396a7316a 100644 --- a/apps/website/screens/components/sidenav/SidenavPageLayout.tsx +++ b/apps/website/screens/components/sidenav/SidenavPageLayout.tsx @@ -7,9 +7,8 @@ import { ReactNode } from "react"; const SidenavPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/sidenav" }, - { label: "Usage", path: "/components/sidenav/usage" }, - { label: "Specifications", path: "/components/sidenav/specifications" }, + { label: "Overview", path: "/components/sidenav" }, + { label: "Code", path: "/components/sidenav/code" }, ]; return ( @@ -18,9 +17,7 @@ const SidenavPageHeading = ({ children }: { children: ReactNode }) => { - The sidenav component is part of the layout of the application and it makes easier to divide the main screen - into two different areas. The main area will have all the content and the sidenav as a secondary element as - an index, including links to different resources on the web page. + The sidenav component provides a vertical navigation structure placed on the left side of the interface. The sidenav is part of the application layout, so it can only be used inside of it. Please check the{" "} diff --git a/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx b/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx index 566514582b..8c924a88b7 100644 --- a/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx +++ b/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx @@ -11,33 +11,37 @@ const sections = [ title: "Props", content: ( - - Name - Type - Description - Default - - - title - - React.ReactNode - - The area assigned to render the title. It is highly recommended to use the sidenav title. - - - - - - - - children - - - - React.ReactNode - - The area inside the sidenav. - - - + + + Name + Type + Description + Default + + + + + + + + children + + + + React.ReactNode + + The area inside the sidenav. + - + + + title + + React.ReactNode + + The area assigned to render the title. It is highly recommended to use the sidenav title. + - + + ), }, @@ -53,26 +57,29 @@ const sections = [ title: "Props", content: ( - - Name - Type - Description - Default - - - - {" "} - - - children - - - - React.ReactNode - - The area inside the sidenav title. This area can be used to render custom content. - - - + + + Name + Type + Description + Default + + + + + + + + children + + + + React.ReactNode + + The area inside the sidenav title. This area can be used to render custom content. + - + + ), }, @@ -92,26 +99,29 @@ const sections = [ title: "Props", content: ( - - Name - Type - Description - Default - - - - {" "} - - - children - - - - React.ReactNode - - The area inside the sidenav section. Child items will be stacked inside a flex container. - - - + + + Name + Type + Description + Default + + + + + + + + children + + + + React.ReactNode + + The area inside the sidenav section. Child items will be stacked inside a flex container. + - + + ), }, @@ -130,61 +140,65 @@ const sections = [ title: "Props", content: ( - - Name - Type - Description - Default - - - title - - string - - The title of the sidenav group. - - - - - collapsable - - boolean - - - If true, the sidenav group will be a button that will allow you to collapse the links contained within - it. In addition, if it's collapsed and contains the currently selected link, the group title will also - be marked as selected. - - - false - - - - icon - - string | {"(React.ReactNode & React.SVGProps )"} - - - A{" "} - - Material Symbol - {" "} - or a SVG element to be displayed next to the title of the group as an icon. - - - - - - - - - children - - - - React.ReactNode - - The area inside the sidenav group. This area can be used to render sidenav links. - - - + + + Name + Type + Description + Default + + + + + + + + children + + + + React.ReactNode + + The area inside the sidenav group. This area can be used to render sidenav links. + - + + + collapsable + + boolean + + + If true, the sidenav group will be a button that will allow you to collapse the links contained within + it. In addition, if it's collapsed and contains the currently selected link, the group title will also + be marked as selected. + + + false + + + + icon + + string | {"(React.ReactNode & React.SVGProps )"} + + + A{" "} + + Material Symbol + {" "} + or a SVG element to be displayed next to the title of the group as an icon. + + - + + + title + + string + + The title of the sidenav group. + - + + ), }, @@ -207,94 +221,98 @@ const sections = [ title: "Props", content: ( - - Name - Type - Description - Default - - - href - - string - - Page to be opened when the user clicks on the link. - - - - - newWindow - - boolean - - If true, the page is opened in a new browser tab. - - false - - - - icon - - string | {"(React.ReactNode & React.SVGProps )"} - - - A{" "} - - Material Symbol - {" "} - or a SVG element to be displayed left to the link as an icon. - - - - - - selected - - boolean - - - If true, the link will be marked as selected. Moreover, in that same case, if it is contained within a - collapsed group, and consequently, the currently selected link is not visible, the group title will - appear as selected too. - - - false - - - - onClick - - {"(event: React.MouseEvent ) => void"} - - - This function will be called when the user clicks the link and the event will be passed to this - function. - - - - - - - - - children - - - - React.ReactNode - - The area inside the sidenav link. - - - - - tabIndex - - number - - - Value of the tabindex attribute. - - - 0 - - + + + Name + Type + Description + Default + + + + + + + + children + + + + React.ReactNode + + The area inside the sidenav link. + - + + + href + + string + + Page to be opened when the user clicks on the link. + - + + + icon + + string | {"(React.ReactNode & React.SVGProps )"} + + + A{" "} + + Material Symbol + {" "} + or a SVG element to be displayed left to the link as an icon. + + - + + + newWindow + + boolean + + If true, the page is opened in a new browser tab. + + false + + + + onClick + + {"(event: React.MouseEvent ) => void"} + + + This function will be called when the user clicks the link and the event will be passed to this + function. + + - + + + selected + + boolean + + + If true, the link will be marked as selected. Moreover, in that same case, if it is contained within a + collapsed group, and consequently, the currently selected link is not visible, the group title will + appear as selected too. + + + false + + + + tabIndex + + number + + + Value of the tabindex attribute. + + + 0 + + + ), }, @@ -325,15 +343,13 @@ const sections = [ }, ]; -const SidenavCodePage = () => { - return ( - - - - - - - ); -}; +const SidenavCodePage = () => ( + + + + + + +); export default SidenavCodePage; diff --git a/apps/website/screens/components/sidenav/overview/SidenavOverviewPage.tsx b/apps/website/screens/components/sidenav/overview/SidenavOverviewPage.tsx new file mode 100644 index 0000000000..914c32f76e --- /dev/null +++ b/apps/website/screens/components/sidenav/overview/SidenavOverviewPage.tsx @@ -0,0 +1,200 @@ +import { DxcParagraph, DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import Image from "@/common/Image"; +import anatomy from "./images/sidenav_anatomy.png"; +import responsive from "./images/sidenav_responsive.png"; + +const sections = [ + { + title: "Introduction", + content: ( + <> + + The sidenav component is designed to support efficient and intuitive navigation across the main sections of an + application. Its vertical layout provides persistent access to navigation links, improving discoverability and + reducing the steps needed to move between pages. It supports nested groups, collapsible sections, and the + ability to highlight the active route, making it especially useful for applications with deep or complex + navigation structures. + + + ), + }, + { + title: "Anatomy", + content: ( + <> + Sidenav's anatomy + + + Title: the main label displayed at the top of the sidenav, typically used to indicate the + name of the product or section. + + + Group title: a label that groups related links together, providing visual structure and + context within the navigation. + + + Page or section link: the navigational element that redirects users to a specific view or + section of the application. + + + Single group: a container that holds a set of related links that are always visible and not + collapsible. + + + Collapsible group: a group of links that can be expanded or collapsed, helping reduce + visual noise and support progressive disclosure. + + + Divider: a horizontal line used to visually separate groups or sections within the sidenav + for better readability. + + + Single link: an individual navigational item not grouped with others, typically used for + stand-alone destinations. + + + Icon: an optional visual element placed before the link label, used to reinforce meaning or + improve scannability. + + + + ), + }, + + { + title: "Key interactions and features", + subSections: [ + { + title: "Hierarchical navigation", + content: ( + + The sidenav supports hierarchical structures,{" "} + organizing navigation links into nested groups. Though it only supports one level of + nesting, this helps users quickly understand the content structure and navigate between related pages or + sections more efficiently. + + ), + }, + { + title: "Collapsible groups", + content: ( + + Groups can be expanded or collapsed, allowing users to control the visibility of nested + navigation links. This feature is particularly useful for reducing visual noise and keeping the interface + tidy, especially when dealing with large or complex structures. + + ), + }, + { + title: "Active link highlighting", + content: ( + + The currently active link is visually highlighted in the sidenav. This gives users clear feedback about + where they are within the application, improving orientation and navigation consistency. + + ), + }, + { + title: "Persistent visibility", + content: ( + + The sidenav remains visible and accessible while the user navigates through the product. + This persistent placement makes it easy to switch sections quickly, supporting efficient multitasking and + exploration. + + ), + }, + { + title: "Icon support", + content: ( + + Each navigation link can include an icon that visually represents the content or + functionality it leads to. Icons enhance scannability and make the navigation more intuitive, especially + when combined with meaningful labels. + + ), + }, + { + title: "Dividers for visual grouping", + content: ( + + The sidenav allows the inclusion of visual dividers between groups or links, making it easier to identify + content categories and improving the overall readability of the menu. + + ), + }, + { + title: "Responsive behavior", + content: ( + <> + + On mobile and tablet screens, the sidenav becomes an{" "} + overlay panel that can be toggled open or closed. This responsive version ensures that + navigation remains accessible without occupying valuable screen space. Users can open the sidenav using a + dedicated menu icon, and close it either by interacting with the backdrop or selecting a navigation + option. This enhances usability and maintains focus on content in smaller viewports. + + Responsive sidenav + + ), + }, + ], + }, + { + title: "Best practices", + content: ( + <> + + + Use the sidenav component to improve discoverability: make navigation links easy to find + and access, helping users understand the structure of the application. + + + Keep in mind the type of devices you're developing for: ensure the sidenav behaves in a way + that doesn't block or reduce the space available for the main content, especially on smaller screens. + + + Follow a clear structure and hierarchy: organize the content using group titles, dividers, + and indentation to visually differentiate between sections, titles, and individual links. + + + Use clear and concise labels: navigation items should use simple, intuitive wording that + clearly describes their destination or action. + + + Organize links into logical groups: related navigation items should be grouped together + under meaningful group titles for easier scanning. + + + Use icons to reinforce meaning: include icons where relevant to improve visual recognition + and support faster navigation. + + + Use collapsible groups for long menus: when there are many links, collapsible groups help + keep the sidenav organized and reduce visual clutter. + + + Use dividers to separate sections: visual separators make different content areas more + distinct and improve readability. + + + + ), + }, +]; + +const SidenavOverviewPage = () => { + return ( + + + + + + + ); +}; + +export default SidenavOverviewPage; diff --git a/apps/website/screens/components/sidenav/overview/images/sidenav_anatomy.png b/apps/website/screens/components/sidenav/overview/images/sidenav_anatomy.png new file mode 100644 index 0000000000..c091445a89 Binary files /dev/null and b/apps/website/screens/components/sidenav/overview/images/sidenav_anatomy.png differ diff --git a/apps/website/screens/components/sidenav/overview/images/sidenav_responsive.png b/apps/website/screens/components/sidenav/overview/images/sidenav_responsive.png new file mode 100644 index 0000000000..de7c1cc916 Binary files /dev/null and b/apps/website/screens/components/sidenav/overview/images/sidenav_responsive.png differ diff --git a/apps/website/screens/components/sidenav/specs/SidenavSpecsPage.tsx b/apps/website/screens/components/sidenav/specs/SidenavSpecsPage.tsx deleted file mode 100644 index 76c5eb72c5..0000000000 --- a/apps/website/screens/components/sidenav/specs/SidenavSpecsPage.tsx +++ /dev/null @@ -1,460 +0,0 @@ -import { DxcBulletedList, DxcTable, DxcFlex } from "@dxc-technology/halstack-react"; -import Image from "@/common/Image"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Figure from "@/common/Figure"; -import DocFooter from "@/common/DocFooter"; -import Code from "@/common/Code"; -import sidenavAnatomy from "./images/sidenav_anatomy.png"; -import sidenavSpecs from "./images/sidenav_specs.png"; - -const sections = [ - { - title: "Specifications", - content: ( -
- Sidenav design specifications -
- ), - }, - { - title: "Anatomy", - content: ( - <> - Sidenav anatomy - - - Title (Optional) - - - Group Title (Optional) - - Page or Section Link - Divider - Current Page or Section - - - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - - - - Component token - Element - Core token - Value - - - - - - backgroundColor - - Sidenav container - - color-grey-100 - - #f2f2f2 - - - - titleFontColor - - Title - - color-grey-800 - - #4d4d4d - - - - groupTitleFontColor - - Group title - - color-black - - #000000 - - - - groupTitleHoverBackgroundColor - - Group title background:hover - - color-grey-200 - - #e6e6e6 - - - - groupTitleActiveBackgroundColor - - Group title background:active - - color-grey-800 - - #4d4d4d - - - - groupTitleSelectedFontColor - - Group title:selected - - color-white - - #ffffff - - - - groupTitleSelectedBackgroundColor - - Group title background:selected - - color-grey-800 - - #4d4d4d - - - - groupTitleSelectedHoverFontColor - - Group title:hover selected - - color-white - - #ffffff - - - - groupTitleSelectedHoverBackgroundColor - - Group title background:selected hover - - color-grey-900 - - #333333 - - - - linkFontColor - - Link - - color-grey-800 - - #4d4d4d - - - - linkHoverBackgroundColor - - Link background:hover - - color-grey-200 - - #e6e6e6 - - - - linkSelectedFontColor - - Link:selected - - color-white - - #ffffff - - - - linkSelectedBackgroundColor - - Link background:selected - - color-grey-800 - - #4d4d4d - - - - linkSelectedHoverFontColor - - Link:selected hover - - color-white - - #ffffff - - - - linkSelectedHoverBackgroundColor - - Link background:selected hover - - color-grey-900 - - #333333 - - - - linkFocusColor - - Link:focus - - color-blue-600 - - #0095ff - - - - scrollBarThumbColor - - Scroll thumb - - color-grey-200-a - - #0000001a - - - - scrollBarTrackColor - - Scroll track - - color-transparent - - transparent - - - - ), - }, - { - title: "Typography", - content: ( - - - - Component token - Element - Core token - Value - - - - - - titleFontFamily - - Title - - font-family-sans - - 'Open Sans', sans-serif - - - - titleFontSize - - Title - - font-scale-04 - - 1.25rem / 20px - - - - titleFontStyle - - Title - - font-style-normal - - normal - - - - titleFontWeight - - Title - - font-weight-semibold - - 600 - - - - titleFontLetterSpacing - - Title - - spacing-0 - - 0em - - - - groupTitleFontFamily - - Group title - - font-family-sans - - 'Open Sans', sans-serif - - - - groupTitleFontSize - - Group title - - font-scale-02 - - 0.875rem / 14px - - - - groupTitleFontStyle - - Group title - - font-style-normal - - normal - - - - groupTitleFontWeight - - Group title - - font-weight-semibold - - 600 - - - - linkFontFamily - - Link - - font-family-sans - - 'Open Sans', sans-serif - - - - linkFontSize - - Link - - font-scale-02 - - 0.875rem - - - - linkFontStyle - - Link - - font-style-normal - - normal - - - - linkFontWeight - - Link - - font-weight-regular - - 400 - - - - ), - }, - { - title: "Spacing", - content: ( - <> - - - - Property - Element - Core token - Value - - - - - - margin-top - - Link - - spacing-4 - - 0.25rem / 4px - - - - margin-bottom - - Link - - spacing-4 - - 0.25rem / 4px - - - - margin-right - - Link - - spacing-16 - - 1rem / 16px - - - - margin-left - - Link - - spacing-16 - - 1rem / 16px - - - - - ), - }, - ], - }, -]; - -const SidenavSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default SidenavSpecsPage; diff --git a/apps/website/screens/components/sidenav/specs/images/sidenav_anatomy.png b/apps/website/screens/components/sidenav/specs/images/sidenav_anatomy.png deleted file mode 100644 index 5378a3a036..0000000000 Binary files a/apps/website/screens/components/sidenav/specs/images/sidenav_anatomy.png and /dev/null differ diff --git a/apps/website/screens/components/sidenav/specs/images/sidenav_specs.png b/apps/website/screens/components/sidenav/specs/images/sidenav_specs.png deleted file mode 100644 index 280c480163..0000000000 Binary files a/apps/website/screens/components/sidenav/specs/images/sidenav_specs.png and /dev/null differ diff --git a/apps/website/screens/components/sidenav/usage/SidenavUsagePage.tsx b/apps/website/screens/components/sidenav/usage/SidenavUsagePage.tsx deleted file mode 100644 index 2dcd877dfc..0000000000 --- a/apps/website/screens/components/sidenav/usage/SidenavUsagePage.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { DxcParagraph, DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import DocFooter from "@/common/DocFooter"; -import Figure from "@/common/Figure"; -import Image from "@/common/Image"; -import sidenavResponsive from "./images/sidenav_responsive.png"; - -const sections = [ - { - title: "Usage", - content: ( - <> - - - Use the sidenav component to improve the discoverability of the application, making the navigation links - accessible to the users. - - - Keep in mind the type of the devices that you are developing for, and handle the behavior in a way that - doesn't block or reduce the available space of the main area in the application. - - - Try to follow and order for the Sidenav content and make use of hierarchy to differentiate between a title - and a link. - - - - ), - }, - { - title: "Responsive version for mobile and tablet", - content: ( - <> - - The responsive version of the component for mobile and tablet works a little bit different compared to the - version for desktop. As the size of the screen in those devices is reduced and once the breakpoint has been - reached (720px), the sidenav is displayed in mobile responsive view. - -
- Sidenav responsive version -
- - ), - }, -]; - -const SidenavUsagePage = () => { - return ( - - - - - - - ); -}; - -export default SidenavUsagePage; diff --git a/apps/website/screens/components/sidenav/usage/images/sidenav_responsive.png b/apps/website/screens/components/sidenav/usage/images/sidenav_responsive.png deleted file mode 100644 index 9a0545c702..0000000000 Binary files a/apps/website/screens/components/sidenav/usage/images/sidenav_responsive.png and /dev/null differ diff --git a/packages/lib/src/sidenav/Sidenav.tsx b/packages/lib/src/sidenav/Sidenav.tsx index 87556a7047..0f9407cbd7 100644 --- a/packages/lib/src/sidenav/Sidenav.tsx +++ b/packages/lib/src/sidenav/Sidenav.tsx @@ -1,11 +1,9 @@ import { forwardRef, MouseEvent, useContext, useEffect, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import styled from "styled-components"; import DxcBleed from "../bleed/Bleed"; -import CoreTokens from "../common/coreTokens"; import { responsiveSizes } from "../common/variables"; import DxcFlex from "../flex/Flex"; import DxcIcon from "../icon/Icon"; -import HalstackContext from "../HalstackContext"; import { GroupContext, GroupContextProvider, useResponsiveSidenavVisibility } from "./SidenavContext"; import SidenavPropsType, { SidenavGroupPropsType, @@ -13,105 +11,9 @@ import SidenavPropsType, { SidenavSectionPropsType, SidenavTitlePropsType, } from "./types"; - -const DxcSidenav = ({ title, children }: SidenavPropsType): JSX.Element => { - const colorsTheme = useContext(HalstackContext); - - return ( - - - {title} - - {children} - - - - ); -}; - -const Title = ({ children }: SidenavTitlePropsType): JSX.Element => ( - - {children} - -); - -const Section = ({ children }: SidenavSectionPropsType): JSX.Element => ( - <> - - {children} - - - -); - -const Group = ({ title, collapsable = false, icon, children }: SidenavGroupPropsType): JSX.Element => { - const [collapsed, setCollapsed] = useState(false); - const [isSelected, changeIsSelected] = useState(false); - - return ( - - - {collapsable && title ? ( - setCollapsed(!collapsed)} - selectedGroup={collapsed && isSelected} - > - - {typeof icon === "string" ? : icon} - {title} - - - - ) : ( - title && ( - - {typeof icon === "string" ? : icon} - {title} - - ) - )} - {!collapsed && children} - - - ); -}; - -const Link = forwardRef( - ( - { href, newWindow = false, selected = false, icon, onClick, tabIndex = 0, children, ...otherProps }, - ref - ): JSX.Element => { - const changeIsGroupSelected = useContext(GroupContext); - const setIsSidenavVisibleResponsive = useResponsiveSidenavVisibility(); - const handleClick = ($event: MouseEvent) => { - onClick?.($event); - setIsSidenavVisibleResponsive?.(false); - }; - - useEffect(() => { - changeIsGroupSelected?.((isGroupSelected) => (!isGroupSelected ? selected : isGroupSelected)); - }, [selected, changeIsGroupSelected]); - - return ( - - - {typeof icon === "string" ? : icon} - {children} - - {newWindow && } - - ); - } -); +import { scrollbarStyles } from "../styles/scroll"; +import DxcDivider from "../divider/Divider"; +import DxcInset from "../inset/Inset"; const SidenavContainer = styled.div` box-sizing: border-box; @@ -121,50 +23,38 @@ const SidenavContainer = styled.div` @media (max-width: ${responsiveSizes.large}rem) { width: 100vw; } - padding: 2rem 1rem; - background-color: ${(props) => props.theme.backgroundColor}; + padding: var(--spacing-padding-xl) var(--spacing-padding-none); + background-color: var(--color-bg-neutral-light); overflow-y: auto; overflow-x: hidden; - ::-webkit-scrollbar { - width: 2px; - } - ::-webkit-scrollbar-track { - background-color: ${(props) => props.theme.scrollBarTrackColor}; - border-radius: 3px; - } - ::-webkit-scrollbar-thumb { - background-color: ${(props) => props.theme.scrollBarThumbColor}; - border-radius: 3px; - } + ${scrollbarStyles} `; const SidenavTitle = styled.div` display: flex; align-items: center; - padding: 0.5rem 1.2rem; - font-family: ${(props) => props.theme.titleFontFamily}; - font-style: ${(props) => props.theme.titleFontStyle}; - font-weight: ${(props) => props.theme.titleFontWeight}; - font-size: ${(props) => props.theme.titleFontSize}; - color: ${(props) => props.theme.titleFontColor}; - letter-spacing: ${(props) => props.theme.titleFontLetterSpacing}; - text-transform: ${(props) => props.theme.titleFontTextTransform}; + padding: var(--spacing-padding-xs) var(--spacing-padding-m); + font-family: var(--typography-font-family); + font-size: var(--typography-label-xl); + color: var(--color-fg-neutral-stronger); + font-weight: var(--typography-label-semibold); `; -const Divider = styled.div` - width: 100%; - height: 1px; - background-color: ${CoreTokens.color_grey_400}; - - &:last-child { - display: none; +const SidenavGroup = styled.div` + a { + padding: var(--spacing-padding-xs) var(--spacing-padding-xxl); } `; -const SidenavGroup = styled.div` - a { - padding: 0.5rem 1.2rem 0.5rem 2.25rem; +const SectionContainer = styled.div` + display: flex; + flex-direction: column; + gap: var(--spacing-gap-ml); + &:last-child { + hr { + display: none; + } } `; @@ -172,17 +62,17 @@ const SidenavGroupTitle = styled.span` box-sizing: border-box; display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1.2rem; - font-family: ${(props) => props.theme.groupTitleFontFamily}; - font-style: ${(props) => props.theme.groupTitleFontStyle}; - font-weight: ${(props) => props.theme.groupTitleFontWeight}; - font-size: ${(props) => props.theme.groupTitleFontSize}; + gap: var(--spacing-gap-s); + padding: var(--spacing-padding-xs) var(--spacing-padding-ml); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-semibold); + color: var(--color-fg-neutral-dark); span::before { - font-size: 16px; + font-size: var(--height-xxs); } svg { - height: 16px; + height: var(--height-xxs); width: 16px; } `; @@ -194,37 +84,31 @@ const SidenavGroupTitleButton = styled.button<{ selectedGroup: boolean }>` align-items: center; justify-content: space-between; width: 100%; - padding: 0.5rem 1.2rem; - font-family: ${(props) => props.theme.groupTitleFontFamily}; - font-style: ${(props) => props.theme.groupTitleFontStyle}; - font-weight: ${(props) => props.theme.groupTitleFontWeight}; - font-size: ${(props) => props.theme.groupTitleFontSize}; + padding: var(--spacing-padding-xs) var(--spacing-padding-ml); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-semibold); cursor: pointer; ${(props) => props.selectedGroup - ? `color: ${props.theme.groupTitleSelectedFontColor}; background-color: ${props.theme.groupTitleSelectedBackgroundColor};` - : `color: ${props.theme.groupTitleFontColor}; background-color: transparent;`} + ? `color: var(--color-fg-neutral-bright); background-color: var(--color-bg-neutral-stronger);` + : `color: var(--color-fg-neutral-stronger); background-color: transparent;`} &:focus, &:focus-visible { - outline: 2px solid ${(props) => props.theme.linkFocusColor}; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); outline-offset: -2px; } - &:hover { - ${(props) => - props.selectedGroup - ? `color: ${props.theme.groupTitleSelectedHoverFontColor}; background-color: ${props.theme.groupTitleSelectedHoverBackgroundColor};` - : `color: ${props.theme.groupTitleFontColor}; background-color: ${props.theme.groupTitleHoverBackgroundColor};`} - } + &:hover, &:active { - color: #fff; - background-color: ${(props) => (props.selectedGroup ? "#333" : props.theme.groupTitleActiveBackgroundColor)}; + background-color: ${(props) => + props.selectedGroup ? "var(--color-bg-neutral-strongest)" : "var(--color-bg-neutral-medium)"}; } span::before { - font-size: 16px; + font-size: var(--height-xxs); } svg { - height: 16px; + height: var(--height-xxs); width: 16px; } `; @@ -233,48 +117,127 @@ const SidenavLink = styled.a<{ selected: SidenavLinkPropsType["selected"] }>` display: flex; align-items: center; justify-content: space-between; - gap: 0.5rem; - padding: 0.5rem 1.2rem; - box-shadow: 0 0 0 2px transparent; - font-family: ${(props) => props.theme.linkFontFamily}; - font-style: ${(props) => props.theme.linkFontStyle}; - font-weight: ${(props) => props.theme.linkFontWeight}; - font-size: ${(props) => props.theme.linkFontSize}; - letter-spacing: ${(props) => props.theme.linkFontLetterSpacing}; - text-transform: ${(props) => props.theme.linkFontTextTransform}; - text-decoration: ${(props) => props.theme.linkTextDecoration}; + padding: var(--spacing-padding-xs) var(--spacing-padding-ml); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + text-decoration: none; cursor: pointer; ${(props) => props.selected - ? `color: ${props.theme.linkSelectedFontColor}; background-color: ${props.theme.linkSelectedBackgroundColor};` - : `color: ${props.theme.linkFontColor}; background-color: transparent;`} + ? `color: var(--color-fg-neutral-bright); background-color: var(--color-bg-neutral-stronger);` + : `color: var(--color-fg-neutral-stronger); background-color: transparent;`} &:focus, &:focus-visible { - outline: 2px solid ${(props) => props.theme.linkFocusColor}; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); outline-offset: -2px; } - &:hover { - ${(props) => - props.selected - ? `color: ${props.theme.linkSelectedHoverFontColor}; background-color: ${props.theme.linkSelectedHoverBackgroundColor};` - : `color: ${props.theme.linkFontColor}; background-color: ${props.theme.linkHoverBackgroundColor};`} - } + &:hover, &:active { - color: #fff; - background-color: ${(props) => (props.selected ? "#333" : "#4d4d4d")}; - outline: 2px solid #0095ff; - outline-offset: -2px; + background-color: ${(props) => + props.selected ? "var(--color-bg-neutral-strongest)" : "var(--color-bg-neutral-medium)"}; } span::before { - font-size: 16px; + font-size: var(--height-xxs); } svg { - height: 16px; + height: var(--height-xxs); width: 16px; } `; +const DxcSidenav = ({ title, children }: SidenavPropsType): JSX.Element => { + return ( + + {title} + + {children} + + + ); +}; + +const Title = ({ children }: SidenavTitlePropsType): JSX.Element => {children}; + +const Section = ({ children }: SidenavSectionPropsType): JSX.Element => ( + + {children} + + + + +); + +const Group = ({ title, collapsable = false, icon, children }: SidenavGroupPropsType): JSX.Element => { + const [collapsed, setCollapsed] = useState(false); + const [isSelected, changeIsSelected] = useState(false); + + return ( + + + {collapsable && title ? ( + setCollapsed(!collapsed)} + selectedGroup={collapsed && isSelected} + > + + {typeof icon === "string" ? : icon} + {title} + + + + ) : ( + title && ( + + {typeof icon === "string" ? : icon} + {title} + + ) + )} + {!collapsed && children} + + + ); +}; + +const Link = forwardRef( + ( + { href, newWindow = false, selected = false, icon, onClick, tabIndex = 0, children, ...otherProps }, + ref + ): JSX.Element => { + const changeIsGroupSelected = useContext(GroupContext); + const setIsSidenavVisibleResponsive = useResponsiveSidenavVisibility(); + const handleClick = ($event: MouseEvent) => { + onClick?.($event); + setIsSidenavVisibleResponsive?.(false); + }; + + useEffect(() => { + changeIsGroupSelected?.((isGroupSelected) => (!isGroupSelected ? selected : isGroupSelected)); + }, [selected, changeIsGroupSelected]); + + return ( + + + {typeof icon === "string" ? : icon} + {children} + + {newWindow && } + + ); + } +); + DxcSidenav.Section = Section; DxcSidenav.Group = Group; DxcSidenav.Link = Link;