diff --git a/apps/website/pages/components/footer/code.tsx b/apps/website/pages/components/footer/code.tsx new file mode 100644 index 0000000000..9dadb77cfe --- /dev/null +++ b/apps/website/pages/components/footer/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import FooterPageLayout from "screens/components/footer/FooterPageLayout"; +import FooterCodePage from "screens/components/footer/code/FooterCodePage"; + +const Code = () => ( + <> + + Footer code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/footer/index.tsx b/apps/website/pages/components/footer/index.tsx index 68d3cc6c88..0a2734e535 100644 --- a/apps/website/pages/components/footer/index.tsx +++ b/apps/website/pages/components/footer/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; import FooterPageLayout from "screens/components/footer/FooterPageLayout"; -import FooterCodePage from "screens/components/footer/code/FooterCodePage"; +import FooterOverviewPage from "screens/components/footer/overview/FooterOverviewPage"; -const Index = () => { - return ( - <> - - Footer — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Footer — 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/footer/specifications.tsx b/apps/website/pages/components/footer/specifications.tsx deleted file mode 100644 index 8cd96b72b2..0000000000 --- a/apps/website/pages/components/footer/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import FooterPageLayout from "screens/components/footer/FooterPageLayout"; -import FooterSpecsPage from "screens/components/footer/specs/FooterSpecsPage"; - -const Specifications = () => { - return ( - <> - - Footer Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/footer/usage.tsx b/apps/website/pages/components/footer/usage.tsx deleted file mode 100644 index d0f5bddf50..0000000000 --- a/apps/website/pages/components/footer/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import FooterPageLayout from "screens/components/footer/FooterPageLayout"; -import FooterUsagePage from "screens/components/footer/usage/FooterUsagePage"; - -const Usage = () => { - return ( - <> - - Footer Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/components/footer/FooterPageLayout.tsx b/apps/website/screens/components/footer/FooterPageLayout.tsx index 42886525eb..412bde097f 100644 --- a/apps/website/screens/components/footer/FooterPageLayout.tsx +++ b/apps/website/screens/components/footer/FooterPageLayout.tsx @@ -7,9 +7,8 @@ import { ReactNode } from "react"; const FooterPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/footer" }, - { label: "Usage", path: "/components/footer/usage" }, - { label: "Specifications", path: "/components/footer/specifications" }, + { label: "Overview", path: "/components/footer" }, + { label: "Code", path: "/components/footer/code" }, ]; return ( @@ -18,11 +17,8 @@ const FooterPageHeading = ({ children }: { children: ReactNode }) => { - Footers are a secondary element in a web page because they usually appear at the bottom and it is the last - thing that the user interacts with. But the presence of the footer must be designed in every application and - be part of it (consumer or back-office) as it is a key layout element to the overall experience. It is a - choice of the designer to either leave the footer visible by default or push it down, depending on the use - case. + The footer is a UI component placed at the bottom of the page, providing informational context, secondary + navigation, and legal or support links. The footer is part of the application layout, so it can only be used inside of it. Please check the{" "} @@ -31,7 +27,7 @@ const FooterPageHeading = ({ children }: { children: ReactNode }) => { {" "} documentation. - + {children} diff --git a/apps/website/screens/components/footer/code/FooterCodePage.tsx b/apps/website/screens/components/footer/code/FooterCodePage.tsx index 155e0b8937..329423f79f 100644 --- a/apps/website/screens/components/footer/code/FooterCodePage.tsx +++ b/apps/website/screens/components/footer/code/FooterCodePage.tsx @@ -3,7 +3,13 @@ import QuickNavContainer from "@/common/QuickNavContainer"; import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; import DocFooter from "@/common/DocFooter"; import StatusBadge from "@/common/StatusBadge"; -import Code, { TableCode } from "@/common/Code"; +import Code, { ExtendedTableCode, TableCode } from "@/common/Code"; + +const logoTypeString = `{ + href?: string; + src: string; + title?: string; +}`; const sections = [ { @@ -19,6 +25,68 @@ const sections = [ + + bottomLinks + + {"{ href: string; text: string; }[]"} + + + An array of objects representing the links that will be rendered at the bottom part of the footer. Each + object has the following properties: + + + - + + + children + + React.ReactNode + + The center section of the footer. Can be used to render custom content in this area. + - + + + copyright + + string + + The text that will be displayed as copyright disclaimer. + - + + + + + + logo + + + + + {"Logo"} +

+ being Logo an object with the following properties: +

+ {logoTypeString} + + + Logo to be displayed inside the header. + - + + + margin + + 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' + + Size of the top margin to be applied to the footer. + - + @@ -75,49 +143,6 @@ const sections = [ - - - bottomLinks - - {"{ href: string; text: string; }[]"} - - - An array of objects representing the links that will be rendered at the bottom part of the footer. Each - object has the following properties: -
    -
  • - text: Text for the link. -
  • -
  • - href: URL of the page the link goes to. -
  • -
- - - - - - copyright - - string - - The text that will be displayed as copyright disclaimer. - - - - - children - - React.ReactNode - - The center section of the footer. Can be used to render custom content in this area. - - - - - margin - - 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' - - Size of the top margin to be applied to the footer. - - - tabIndex diff --git a/apps/website/screens/components/footer/overview/FooterOverviewPage.tsx b/apps/website/screens/components/footer/overview/FooterOverviewPage.tsx new file mode 100644 index 0000000000..1087c7d0a4 --- /dev/null +++ b/apps/website/screens/components/footer/overview/FooterOverviewPage.tsx @@ -0,0 +1,131 @@ +import { DxcBulletedList, DxcFlex, DxcParagraph } from "@dxc-technology/halstack-react"; +import DocFooter from "@/common/DocFooter"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import anatomy from "./images/footer_anatomy.png"; +import variants from "./images/footer_variants.png"; +import Image from "@/common/Image"; +import Figure from "@/common/Figure"; + +const sections = [ + { + title: "Introduction", + content: ( + + The footer is used as the final section of a page to display utility elements such as legal disclaimers, + secondary links, copyright information, or the brand logo. Its purpose is to reinforce brand identity and + provide consistent access across digital experiences without disrupting the main user flow. + + ), + }, + { + title: "Anatomy", + content: ( + <> + Footer's anatomy + + + Container: The outer wrapper that defines the overall layout, padding, and alignment of all + footer content. Ensures consistency across screen sizes. + + + Logo: Represents the brand identity visually. Positioned on the left side, it helps + reinforce company recognition across all pages. + + + Social icons: A set of clickable icons linking to the company’s social media platforms + (e.g., LinkedIn, Facebook). Placed on the right side for easy visibility and access. + + + Copyright: Text displaying legal ownership of the content. Ensures users know the site is + officially owned. + + + Company links: A horizontal list of navigational hyperlinks such as Privacy Policy, Terms & + Conditions, etc. Offers users access to important legal or informational resources. + + + + ), + }, + { + title: "Variants", + content: ( + <> + + To maintain consistency in layout flexibility and brand presentation, the footer offers three primary + variants: Default, With Navigation, and Small. + + + + Default: provides a balanced layout with branding and essential legal links. It offers a + clean, uncluttered appearance suitable for most standard applications. + + + With Navigation: includes additional navigational sections, enabling users to quickly + access key areas of the site. This layout is ideal for content-heavy pages or enterprise-level applications + requiring enhanced footer functionality. + + + Small: offers a compact version of the footer, typically limited to branding and minimal + legal text. It's best suited for lightweight experiences, login pages, or environments with constrained + vertical space. + + +
+ Application layout design specifications +
+ + Choosing between these variants helps tailor the footer to a wide range of contexts, whether prioritizing + simplicity, providing extended navigation, or optimizing for space efficiency. + + + ), + }, + { + title: "Best practices", + content: ( + <> + + + Dock the footer to the bottom of the page: the footer should remain fixed at the bottom + edge of the viewport and not scroll with the page content to maintain visibility and separation from dynamic + areas. + + + Ensure full-width alignment: the footer container should always span the full width of the + screen to create a clean, structured boundary and support responsive behavior across breakpoints. + + + Display copyright information on the right: consistently place legal disclaimers or + copyright text aligned to the right edge of the footer to support predictable user expectations. + + + Use a simplified or alternate logo: consider using a smaller or alternative version of the + brand logo (isotype, imagotype, or monochrome variant) rather than duplicating the main header image to + reduce visual redundancy. + + + Limit the number of links: include only the most essential company links (e.g., Terms, + Privacy, Accessibility) to avoid overwhelming users with excessive options. + + + Select the most appropriate variant for context: choose the footer variant that best fits + the content density and user goals of the page. + + + + ), + }, +]; + +const FooterOverviewPage = () => ( + + + + + + +); + +export default FooterOverviewPage; diff --git a/apps/website/screens/components/footer/overview/images/footer_anatomy.png b/apps/website/screens/components/footer/overview/images/footer_anatomy.png new file mode 100644 index 0000000000..ff389c8552 Binary files /dev/null and b/apps/website/screens/components/footer/overview/images/footer_anatomy.png differ diff --git a/apps/website/screens/components/footer/overview/images/footer_variants.png b/apps/website/screens/components/footer/overview/images/footer_variants.png new file mode 100644 index 0000000000..91dcef94b9 Binary files /dev/null and b/apps/website/screens/components/footer/overview/images/footer_variants.png differ diff --git a/apps/website/screens/components/footer/specs/FooterSpecsPage.tsx b/apps/website/screens/components/footer/specs/FooterSpecsPage.tsx deleted file mode 100644 index d3c8889a38..0000000000 --- a/apps/website/screens/components/footer/specs/FooterSpecsPage.tsx +++ /dev/null @@ -1,458 +0,0 @@ -import { DxcBulletedList, DxcFlex, DxcTable, DxcParagraph } from "@dxc-technology/halstack-react"; -import Code from "@/common/Code"; -import DocFooter from "@/common/DocFooter"; -import Figure from "@/common/Figure"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Image from "@/common/Image"; -import footerAnatomy from "./images/footer_anatomy.png"; -import footerSpecs from "./images/footer_specs.png"; - -const sections = [ - { - title: "Specifications", - content: ( -
- Footer design specifications -
- ), - }, - { - title: "Anatomy", - content: ( - <> - Footer anatomy - - Container - Logo - Social icons - Copyright - Company links - - - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - - - - Component token - Element - Core token - Value - - - - - - backgroundColor - - Footer container - - color-black - - #000000 - - - - bottomLinksDividerColor - - Divider - - color-blue-600 - - #0095ff - - - - bottomLinksFontColor - - Bottom links - - color-white - - #ffffff - - - - copyrightFontColor - - Copyright - - color-white - - #ffffff - - - - socialLinksColor - - Social icons - - color-white - - #ffffff - - - - ), - }, - { - title: "Typography", - content: ( - - - - Component token - Element - Core token - Value - - - - - - bottomLinksFontFamily - - Bottom links - - font-family-sans - - 'Open Sans', sans-serif - - - - bottomLinksFontSize - - Bottom links - - font-scale-01 - - 0.75rem / 12px - - - - bottomLinksFontWeight - - Bottom links - - font-weight-regular - - 400 - - - - bottomLinksFontStyle - - Bottom links - - font-style-normal - - normal - - - - bottomLinksTextDecoration - - Bottom links - - font-style-no-line - - none - - - - copyrightFontFamily - - Copyright - - font-family-sans - - 'Open Sans', sans-serif - - - - copyrightFontSize - - Copyright - - font-scale-01 - - 0.75rem / 12px - - - - copyrightFontWeight - - Copyright - - font-weight-regular - - 400 - - - - copyrightFontStyle - - Copyright - - font-style-normal - - normal - - - - ), - }, - { - title: "Border", - content: ( - - - - Component token - Element - Core token - Value - - - - - - border-width - - Divider - - border-width-1 - - 1px - - - - border-style - - Divider - - border-style-solid - - solid - - - - ), - }, - { - title: "Height", - content: ( - - - - Property - Value - - - - - - min-height - - 124px - - - - ), - }, - { - title: "Margin", - content: ( - - - - Margin - Value - - - - - - xxsmall - - 6px - - - - xsmall - - 16px - - - - small - - 24px - - - - medium - - 36px - - - - large - - 48px - - - - xlarge - - 64px - - - - xxlarge - - 100px - - - - ), - }, - { - title: "Iconography", - content: ( - - - - Property - Element - Value - - - - - - height/ width - - Social media icons - 24/24px - - - - max-height - - DXC logo - 32px - - - - ), - }, - { - title: "Bottom Links", - content: ( - - - - Property - Element - Core token - Value - - - - - - min-height - - Links container - - - 20px - - - - padding-top - - Links container - - spacing-8 - - 0.5rem / 8px - - - - ), - }, - { - title: "Custom content", - content: ( - <> - - - - Property - Element - Value - - - - - - min-height - - Custom container - 16px - - - - - The content of the footer should be adapt to the space available depending on the screen device. - - - ), - }, - ], - }, - { - title: "Responsive version for mobile and tablet", - content: ( - <> - - The same content in the footer will be displayed for the responsive versions and the only modification will be - the width of it. With less space available to display the content, some of the items will be relocated to fit - well in the screen. - - - Regarding his behavior, the footer must be pushed down always so it is not visible by default after page load, - even when the content is smaller than the device screen size. This includes the splash screen, which must push - the footer down. Of course, if the content is larger than the device screen size, the footer will be pushed - down anyway. - - - On the mobile version, first we have the logo. Below it the links to privacy and terms to let a space for - custom component and at the bottom the copyright terms, centered. At this stage, the custom content and the - disposition is responsability of the user, the same way as it is in the desktop and tablet version. - - - ), - }, -]; - -const FooterSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default FooterSpecsPage; diff --git a/apps/website/screens/components/footer/specs/images/footer_anatomy.png b/apps/website/screens/components/footer/specs/images/footer_anatomy.png deleted file mode 100644 index 4f51b6b4eb..0000000000 Binary files a/apps/website/screens/components/footer/specs/images/footer_anatomy.png and /dev/null differ diff --git a/apps/website/screens/components/footer/specs/images/footer_specs.png b/apps/website/screens/components/footer/specs/images/footer_specs.png deleted file mode 100644 index 58896e27ca..0000000000 Binary files a/apps/website/screens/components/footer/specs/images/footer_specs.png and /dev/null differ diff --git a/apps/website/screens/components/footer/usage/FooterUsagePage.tsx b/apps/website/screens/components/footer/usage/FooterUsagePage.tsx deleted file mode 100644 index 7489d0b0aa..0000000000 --- a/apps/website/screens/components/footer/usage/FooterUsagePage.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { DxcBulletedList, DxcFlex, DxcParagraph } from "@dxc-technology/halstack-react"; -import DocFooter from "@/common/DocFooter"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; - -const sections = [ - { - title: "Usage", - content: ( - <> - {/* */} - - - The footer frame should be docked at the bottom of the page and should not scroll with any of the data - within working section of the screen. - - The footer frame should span the entire screen width. - - The footer frame should display the copyright information at the right margin. - - - We recommend uploading either an alternate version or a smaller brand image than the used in the header. If - the company has an alternate version of the logo, isotype, imagotype or isologo available, we encourage to - use it. In the opposite case a smaller version of the main brand image can be used. - - - - ), - }, - { - title: "Content", - content: ( - <> - {/* */} - - The footer component has a custom area where many kinds of content can be placed, some of them are - contemplated in the following list: - - - - Plain text or content - - Informational purpose text - - - - Menu links - - Global navigation - Sitemap - Useful links or resources - - - - Forms - - Select language - Login / Sing up - Provide email adress / Subscribe - - - - Actions - - Ask for help / Support - Business related actions / Call to action - Search - - - - - ), - }, -]; - -const FooterUsagePage = () => { - return ( - - - - - - - ); -}; - -export default FooterUsagePage; diff --git a/apps/website/screens/components/header/code/HeaderCodePage.tsx b/apps/website/screens/components/header/code/HeaderCodePage.tsx index d4debc4f24..a7d21a5a99 100644 --- a/apps/website/screens/components/header/code/HeaderCodePage.tsx +++ b/apps/website/screens/components/header/code/HeaderCodePage.tsx @@ -4,6 +4,7 @@ import QuickNavContainer from "@/common/QuickNavContainer"; import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; import Link from "next/link"; import Code, { ExtendedTableCode, TableCode } from "@/common/Code"; +import StatusBadge from "@/common/StatusBadge"; const logoTypeString = `{ href?: string; @@ -38,7 +39,12 @@ const sections = [ - - logo + + + + logo + + {"Logo"} diff --git a/packages/lib/src/footer/Footer.stories.tsx b/packages/lib/src/footer/Footer.stories.tsx index f7d86038d6..49f995c952 100644 --- a/packages/lib/src/footer/Footer.stories.tsx +++ b/packages/lib/src/footer/Footer.stories.tsx @@ -8,6 +8,7 @@ import DxcFlex from "../flex/Flex"; import DxcTypography from "../typography/Typography"; import DxcFooter from "./Footer"; import { Meta, StoryObj } from "@storybook/react"; +import DxcLink from "../link/Link"; const social = [ { @@ -145,25 +146,25 @@ const Footer = () => ( <DxcFooter copyright="Copyright" socialLinks={social} bottomLinks={bottom}> - <div> - <a href="https://www.linkedin.com/company/dxctechnology">Linkedin</a> - </div> + <DxcLink href="https://www.linkedin.com/company/dxctechnology" inheritColor> + Linkedin + </DxcLink> </DxcFooter> </ExampleContainer> <ExampleContainer> <Title title="With children, copyright, bottom links and social links from material" theme="light" level={4} /> <DxcFooter copyright="Copyright" socialLinks={socialMaterialIcons} bottomLinks={bottom}> - <div> - <a href="https://www.linkedin.com/company/dxctechnology">Linkedin</a> - </div> + <DxcLink href="https://www.linkedin.com/company/dxctechnology" inheritColor> + Linkedin + </DxcLink> </DxcFooter> </ExampleContainer> <ExampleContainer pseudoState="pseudo-focus"> <Title title="Focused bottom and social links" theme="light" level={4} /> <DxcFooter copyright="Copyright" socialLinks={social} bottomLinks={bottom}> - <div> - <a href="https://www.linkedin.com/company/dxctechnology">Linkedin</a> - </div> + <DxcLink href="https://www.linkedin.com/company/dxctechnology" inheritColor> + Linkedin + </DxcLink> </DxcFooter> </ExampleContainer> <ExampleContainer> @@ -199,9 +200,9 @@ const Footer = () => ( <ExampleContainer> <HalstackProvider theme={opinionatedTheme}> <DxcFooter copyright="Copyright" socialLinks={social} bottomLinks={bottom}> - <div> - <a href="https://www.linkedin.com/company/dxctechnology">Linkedin</a> - </div> + <DxcLink href="https://www.linkedin.com/company/dxctechnology" inheritColor> + Linkedin + </DxcLink> </DxcFooter> </HalstackProvider> </ExampleContainer> diff --git a/packages/lib/src/footer/Footer.tsx b/packages/lib/src/footer/Footer.tsx index 4c0a920ec7..5c71dfbd45 100644 --- a/packages/lib/src/footer/Footer.tsx +++ b/packages/lib/src/footer/Footer.tsx @@ -1,105 +1,34 @@ -import { useMemo, useContext } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import { useContext } from "react"; +import styled from "styled-components"; import { responsiveSizes, spaces } from "../common/variables"; import DxcFlex from "../flex/Flex"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; import { dxcLogo, dxcSmallLogo } from "./Icons"; import FooterPropsType from "./types"; -import { CoreSpacingTokensType } from "../common/coreTokens"; -import HalstackContext, { HalstackLanguageContext } from "../HalstackContext"; - -const DxcFooter = ({ - socialLinks, - bottomLinks, - copyright, - children, - margin, - tabIndex = 0, - mode = "default", -}: FooterPropsType): JSX.Element => { - const colorsTheme = useContext(HalstackContext); - const translatedLabels = useContext(HalstackLanguageContext); - - const footerLogo = useMemo( - () => - !colorsTheme.footer.logo ? ( - mode === "default" ? ( - dxcLogo - ) : ( - dxcSmallLogo - ) - ) : typeof colorsTheme.footer.logo === "string" ? ( - <LogoImg mode={mode} alt={translatedLabels.formFields.logoAlternativeText} src={colorsTheme.footer.logo} /> - ) : ( - colorsTheme.footer.logo - ), - [colorsTheme, translatedLabels] - ); - - return ( - <ThemeProvider theme={colorsTheme.footer}> - <FooterContainer margin={margin} mode={mode}> - <DxcFlex justifyContent="space-between" alignItems="center" wrap="wrap" gap="var(--spacing-gap-l)"> - <LogoContainer mode={mode}>{footerLogo}</LogoContainer> - {mode === "default" && ( - <DxcFlex gap={colorsTheme.footer.socialLinksGutter as CoreSpacingTokensType}> - {socialLinks?.map((link, index) => ( - <Tooltip label={link.title} key={`social${index}${link.href}`}> - <SocialAnchor - href={link.href} - tabIndex={tabIndex} - aria-label={link.title} - key={`social${index}${link.href}`} - index={index} - > - <SocialIconContainer> - {typeof link.logo === "string" ? <DxcIcon icon={link.logo} /> : link.logo} - </SocialIconContainer> - </SocialAnchor> - </Tooltip> - ))} - </DxcFlex> - )} - </DxcFlex> - <ChildComponents>{children}</ChildComponents> - {mode === "default" && ( - <BottomContainer> - <BottomLinks> - {bottomLinks?.map((link, index) => ( - <span key={`bottom${index}${link.text}`}> - <BottomLink href={link.href} tabIndex={tabIndex}> - {link.text} - </BottomLink> - </span> - ))} - </BottomLinks> - <Copyright>{copyright ?? translatedLabels.footer.copyrightText(new Date().getFullYear())}</Copyright> - </BottomContainer> - )} - </FooterContainer> - </ThemeProvider> - ); -}; +import { HalstackLanguageContext } from "../HalstackContext"; const FooterContainer = styled.footer<{ margin: FooterPropsType["margin"]; mode?: FooterPropsType["mode"]; }>` - background-color: ${(props) => props.theme.backgroundColor}; + background-color: var(--color-bg-neutral-strongest); box-sizing: border-box; display: flex; flex-direction: ${(props) => (props?.mode === "default" ? "column" : "row")}; justify-content: space-between; - margin-top: ${(props) => (props.margin ? spaces[props.margin] : "0px")}; - min-height: ${(props) => (props?.mode === "default" ? props.theme.height : "40px")}; + margin-top: ${(props) => (props.margin ? spaces[props.margin] : "var(--spacing-padding-none)")}; + min-height: ${(props) => (props?.mode === "default" ? "124px" : "40px")}; width: 100%; - gap: ${(props) => (props?.mode === "default" ? "0px" : "32px")}; - @media (min-width: ${responsiveSizes.small}rem) { - padding: ${(props) => (props?.mode === "default" ? "24px 32px" : "12px 32px")}; + gap: var(--spacing-gap-m); + padding: ${(props) => + props?.mode === "default" + ? "var(--spacing-padding-m) var(--spacing-padding-xl)" + : "var(--spacing-padding-s) var(--spacing-padding-xl)"}; + @media (max-width: ${responsiveSizes.medium}rem) { + padding: var(--spacing-padding-l) var(--spacing-padding-ml) } @media (max-width: ${responsiveSizes.small}rem) { - padding: 20px; flex-direction: column; } `; @@ -117,23 +46,21 @@ const BottomContainer = styled.div` align-items: center; } - border-top: ${(props) => - `${props.theme.bottomLinksDividerThickness} ${props.theme.bottomLinksDividerStyle} ${props.theme.bottomLinksDividerColor}`}; - margin-top: 16px; + border-top: var(--border-width-s) var(--border-style-default) var(--border-color-primary-medium); + margin-top: var(--spacing-gap-m); `; const ChildComponents = styled.div` - min-height: 16px; - overflow: hidden; + min-height: var(--height-xxs); + color: var(--color-fg-neutral-bright); `; const Copyright = styled.div` - padding-top: ${(props) => props.theme.bottomLinksDividerSpacing}; - font-family: ${(props) => props.theme.copyrightFontFamily}; - font-size: ${(props) => props.theme.copyrightFontSize}; - font-style: ${(props) => props.theme.copyrightFontStyle}; - font-weight: ${(props) => props.theme.copyrightFontWeight}; - color: ${(props) => props.theme.copyrightFontColor}; + margin-top: var(--spacing-padding-xs); + font-family: var(--typography-font-family); + font-size: var(--typography-label-s); + font-weight: var(--typography-label-regular); + color: var(--color-fg-neutral-bright); @media (min-width: ${responsiveSizes.small}rem) { max-width: 40%; @@ -148,34 +75,34 @@ const Copyright = styled.div` `; const LogoContainer = styled.span<{ mode?: FooterPropsType["mode"] }>` - max-height: ${(props) => (props?.mode === "default" ? props.theme.logoHeight : "16px")}; - width: ${(props) => props.theme.logoWidth}; + max-height: ${(props) => (props?.mode === "default" ? "var(--height-m)" : "var(--height-xxs)")}; + width: auto; `; const LogoImg = styled.img<{ mode?: FooterPropsType["mode"] }>` - max-height: ${(props) => (props?.mode === "default" ? props.theme.logoHeight : "16px")}; - width: ${(props) => props.theme.logoWidth}; + max-height: ${(props) => (props?.mode === "default" ? "var(--height-m)" : "var(--height-xxs)")}; + width: auto; `; const SocialAnchor = styled.a<{ index: number }>` - border-radius: 4px; + border-radius: var(--border-radius-s); &:focus { - outline: 2px solid #0095ff; - outline-offset: 2px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + outline-offset: var(--border-width-m); } `; const SocialIconContainer = styled.div` display: flex; align-items: center; - color: ${(props) => props.theme.socialLinksColor}; + color: var(--color-fg-neutral-bright); overflow: hidden; - font-size: ${(props) => props.theme.socialLinksSize}; + font-size: var(--height-s); svg { - height: ${(props) => props.theme.socialLinksSize}; - width: ${(props) => props.theme.socialLinksSize}; + height: var(--height-s); + width: 24px; } `; @@ -183,8 +110,8 @@ const BottomLinks = styled.div` display: inline-flex; flex-wrap: wrap; align-self: center; - padding-top: ${(props) => props.theme.bottomLinksDividerSpacing}; - color: #fff; + margin-top: var(--spacing-padding-xs); + color: var(--color-fg-neutral-bright); @media (min-width: ${responsiveSizes.small}rem) { max-width: 60%; @@ -196,22 +123,86 @@ const BottomLinks = styled.div` & > span:not(:first-child):before { content: "·"; - padding: 0 0.5rem; + padding: var(--spacing-padding-none) var(--spacing-padding-xs); } `; const BottomLink = styled.a` - text-decoration: ${(props) => props.theme.bottomLinksTextDecoration}; - color: ${(props) => props.theme.bottomLinksFontColor}; - font-family: ${(props) => props.theme.bottomLinksFontFamily}; - font-size: ${(props) => props.theme.bottomLinksFontSize}; - font-style: ${(props) => props.theme.bottomLinksFontStyle}; - font-weight: ${(props) => props.theme.bottomLinksFontWeight}; - border-radius: 2px; + text-decoration: none; + border-radius: var(--border-radius-xs); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + color: var(--color-fg-neutral-bright); &:focus { - outline: 2px solid #0095ff; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); } `; +const getLogoElement = (mode: FooterPropsType["mode"], logo?: FooterPropsType["logo"]) => { + if (logo) { + return <LogoImg alt={logo.title} src={logo.src} title={logo.title} />; + } else { + return mode === "default" ? dxcLogo : dxcSmallLogo; + } +}; + +const DxcFooter = ({ + bottomLinks, + children, + copyright, + logo, + margin, + mode = "default", + socialLinks, + tabIndex = 0, +}: FooterPropsType): JSX.Element => { + const translatedLabels = useContext(HalstackLanguageContext); + + const footerLogo = getLogoElement(mode, logo); + + return ( + <FooterContainer margin={margin} mode={mode}> + <DxcFlex justifyContent="space-between" alignItems="center" wrap="wrap"> + <LogoContainer mode={mode}>{footerLogo}</LogoContainer> + {mode === "default" && ( + <DxcFlex gap="var(--spacing-gap-ml)"> + {socialLinks?.map((link, index) => ( + <Tooltip label={link.title} key={`social${index}${link.href}`}> + <SocialAnchor + href={link.href} + tabIndex={tabIndex} + aria-label={link.title} + key={`social${index}${link.href}`} + index={index} + > + <SocialIconContainer> + {typeof link.logo === "string" ? <DxcIcon icon={link.logo} /> : link.logo} + </SocialIconContainer> + </SocialAnchor> + </Tooltip> + ))} + </DxcFlex> + )} + </DxcFlex> + <ChildComponents>{children}</ChildComponents> + {mode === "default" && ( + <BottomContainer> + <BottomLinks> + {bottomLinks?.map((link, index) => ( + <span key={`bottom${index}${link.text}`}> + <BottomLink href={link.href} tabIndex={tabIndex}> + {link.text} + </BottomLink> + </span> + ))} + </BottomLinks> + <Copyright>{copyright ?? translatedLabels.footer.copyrightText(new Date().getFullYear())}</Copyright> + </BottomContainer> + )} + </FooterContainer> + ); +}; + export default DxcFooter; diff --git a/packages/lib/src/footer/types.ts b/packages/lib/src/footer/types.ts index e87c1c1fd3..881324c5d8 100644 --- a/packages/lib/src/footer/types.ts +++ b/packages/lib/src/footer/types.ts @@ -27,41 +27,60 @@ type BottomLink = { text: string; }; -type FooterPropsType = { +type Logo = { /** - * An array of objects representing the links that will be rendered as - * icons at the top-right side of the footer. + * URL to navigate when the logo is clicked. */ - socialLinks?: SocialLink[]; + href?: string; + /** + * Source of the logo image. + */ + src: string; + /** + * Alternative text for the logo image. + */ + title?: string; +}; + +type FooterPropsType = { /** * An array of objects representing the links that will be rendered at * the bottom part of the footer. */ bottomLinks?: BottomLink[]; + /** + * The center section of the footer. Can be used to render custom + * content in this area. + */ + children?: ReactNode; /** * The text that will be displayed as copyright disclaimer. */ copyright?: string; /** - * The center section of the footer. Can be used to render custom - * content in this area. + * Logo to be displayed inside the footer */ - children?: ReactNode; + logo?: Logo; /** * Size of the top margin to be applied to the footer. */ margin?: Space; - /** - * Value of the tabindex for all interactive elements, except those - * inside the custom area. - */ - tabIndex?: number; /** * Determines the visual style and layout * - "default": The default mode with full content and styling. * - "reduced": A reduced mode with minimal content and styling. */ mode?: "default" | "reduced"; + /** + * An array of objects representing the links that will be rendered as + * icons at the top-right side of the footer. + */ + socialLinks?: SocialLink[]; + /** + * Value of the tabindex for all interactive elements, except those + * inside the custom area. + */ + tabIndex?: number; }; export default FooterPropsType;