diff --git a/src/components/Footer/BackToTop.tsx b/src/components/Footer/BackToTop.tsx new file mode 100644 index 00000000..7322e32c --- /dev/null +++ b/src/components/Footer/BackToTop.tsx @@ -0,0 +1,14 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { Icon } from '../Icon/Icon'; + +export const BackToTop = (): JSX.Element => ( + + Back to top + + +); diff --git a/src/components/Footer/Footer.less b/src/components/Footer/Footer.less new file mode 100644 index 00000000..98149add --- /dev/null +++ b/src/components/Footer/Footer.less @@ -0,0 +1,198 @@ +@import (reference) '@cfpb/cfpb-design-system'; + +// https://github.com/cfpb/consumerfinance.gov/blob/main/cfgov/unprocessed/css/organisms/footer.less + +/* ========================================================================== + consumerfinance.gov + footer + ========================================================================== */ + +/* topdoc + name: CF.gov site-wide footer. + family: cfgov-organisms + patterns: + - notes: + - "For the markup please see _layouts/organisms/footer.html." + tags: + - cfgov-organisms +*/ + +.o-footer { + // Adding an extra 5px to the top to account for the absolute positioned + // social media icons. + padding-top: unit((50px / @base-font-size-px), em); + // There is a 10px margin-bottom on the last .o-footer_list li's, plus the + // 50px bottom padding = 60px of total padding at the bottom of the footer. + padding-bottom: unit((50px / @base-font-size-px), em); + border-top: 5px solid @green; + background: @gray-5; + + &_nav-list { + .m-list__links(); + + .m-list_link { + font-size: unit((18px / @base-font-size-px), em); + .u-link__colors( @black ); + + .respond-to-min( @bp-sm-min, { + margin-right: 1em; + + .u-link__hover-border(); + } ); + + .respond-to-min( @bp-med-min, { + margin-right: unit(( @grid_gutter-width / 22px), em ); + font-size: unit( (20px / @base-font-size-px), em ); + } ); + } + + .m-list_link.m-list_link__disabled { + border-bottom: 1px dotted; + + .respond-to-min( @bp-med-min, { + .u-link__no-border(); + } ); + } + } + + &_list { + .m-list__links(); + + .m-list_link { + .u-link__colors( @gray-dark ); + } + } + + &_pre { + position: relative; + margin-bottom: unit((45px / @base-font-size-px), em); + + .o-footer_top-button { + display: block; + width: 100%; + text-align: center; + // There's a standard margin between the top + // of the footer and the links. The button comes between + // that margin in the wireframes. + margin: -2em auto 2em; + } + + .o-footer_nav-list { + margin-bottom: 0; + } + + .respond-to-min( @bp-sm-min, { + padding-bottom: unit( (@grid_gutter-width / @base-font-size-px), em ); + margin-bottom: unit( (@grid_gutter-width / @base-font-size-px), em ); + border-bottom: 1px solid @gray-40; + + .o-footer_top-button { + display: none; + } + + .o-footer_nav-list { + .m-list__horizontal(); + + .m-list_item { + margin-bottom: 0; + } + } + } ); + + .respond-to-print( { + // !important used here to avoid being overriden by a much more specific + // selector that sets the display property for this element + // and to avoid using a selector that specific here. + display: none !important; + } ); + } + + // TODO: Refactor to use Design System Layout package. + &-middle-left { + .o-footer_list { + margin: 0; + } + + /* Fix doubled border in mobile view */ + .respond-to-max( @bp-xs-max, { + .o-footer_col:nth-child( n+2 ) { + .o-footer_list { + .m-list_item .m-list_link { + border-top-width: 0; + } + } + } + } ); + + .respond-to-min( @bp-sm-min, { + .grid_column(8); + border-right: 1px solid @gray-40; + border-left: 0; + + .o-footer_col { + .grid_column(6); + border-left: 0; + border-right: 0; + padding-right: unit( (@grid_gutter-width / 2 / @base-font-size-px), em ); + } + } ); + + .respond-to-print( { + // !important used here to avoid being overriden by a much more specific + // selector that sets the display property for this element + // and to avoid using a selector that specific here. + display: none !important; + } ); + } + + &-middle-right { + /* Fix doubled border in mobile view */ + .respond-to-max( @bp-xs-max, { + .o-footer_list { + .m-list_item .m-list_link { + border-top-width: 0; + } + } + } ); + + .respond-to-min( @bp-sm-min, { + .grid_column(4); + + .o-footer_list { + padding-left: @grid_gutter-width; + padding-right: @grid_gutter-width; + } + } ); + + .respond-to-print( { + // !important used here to avoid being overriden by a much more specific + // selector that sets the display property for this element + // and to avoid using a selector that specific here. + display: none !important; + } ); + } + + &-post { + margin-top: unit((@grid_gutter-width / @base-font-size-px), em); + + .respond-to-min( @bp-sm-min, { + padding-top: unit( (@grid_gutter-width / @base-font-size-px), em ); + border-top: 1px solid @gray-40; + } ); + + .respond-to-print( { + padding: 0; + border: none; + margin: 0; + } ); + } +} + +/* topdoc + name: EOF + eof: true +*/ + +.o-footer .cf-icon-svg__external-link { + margin-left: 3px; +} \ No newline at end of file diff --git a/src/components/Footer/Footer.stories.tsx b/src/components/Footer/Footer.stories.tsx new file mode 100644 index 00000000..7ab54b26 --- /dev/null +++ b/src/components/Footer/Footer.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Icon } from '../Icon/Icon'; +import Footer from './Footer'; +import { FooterCfGov } from './FooterCfGov'; + +const meta: Meta = { + component: Footer, + argTypes: {} +}; + +export default meta; + +type Story = StoryObj; + +const makeLink = (label: string, isExternal?: boolean): JSX.Element => ( + + {label} + {isExternal ? : null} + +); + +const makeSocialLink = (label: string): JSX.Element => ( + + + +); + +export const Example: Story = { + args: { + navLinks: [makeLink('Nav 1'), makeLink('Nav 2'), makeLink('Nav 3')], + socialLinks: [makeSocialLink('facebook'), makeSocialLink('youtube')], + linksCol1: [ + makeLink('Col1 Link 1'), + makeLink('Col1 Link 2'), + makeLink('Col1 Link 3') + ], + linksCol2: [ + makeLink('Col2 Link 1'), + makeLink('Col2 Link 2'), + makeLink('Col2 Link 3') + ], + linksCol3: [ + makeLink('Col3 Link 1', true), + makeLink('Col3 Link 2', true), + makeLink('Col3 Link 3', true) + ] + } +}; + +export const CFGov: Story = { + render: () => +}; diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx new file mode 100644 index 00000000..ced6320f --- /dev/null +++ b/src/components/Footer/Footer.tsx @@ -0,0 +1,53 @@ +import List from '../List/List'; +import { ListItemBuilder } from '../List/ListItem'; +import { BackToTop } from './BackToTop'; +import './Footer.less'; +import { FooterBanner } from './FooterBanner'; +import { FooterLinksColumn, NavLinks, SocialLinks } from './FooterLinks'; +import './SocialMedia.less'; + +interface FooterProperties { + navLinks: JSX.Element[]; + socialLinks: JSX.Element[]; + linksCol1: JSX.Element[]; + linksCol2: JSX.Element[]; + linksCol3: JSX.Element[]; +} + +/** + * Simply define the anchor elements for each section to compose your Footer + */ +export default function Footer({ + navLinks = [], + socialLinks = [], + linksCol1 = [], + linksCol2 = [], + linksCol3 = [] +}: FooterProperties): JSX.Element { + return ( +
+
+
+ + {navLinks} + {socialLinks} +
+ +
+ {linksCol1} + {linksCol2} +
+ +
+ + + {linksCol3} + + +
+ + +
+
+ ); +} diff --git a/src/components/Footer/FooterBanner.tsx b/src/components/Footer/FooterBanner.tsx new file mode 100644 index 00000000..dd45a808 --- /dev/null +++ b/src/components/Footer/FooterBanner.tsx @@ -0,0 +1,14 @@ +export const FooterBanner = (): JSX.Element => ( +
+
+ +
+ An official website of the  + United States government +
+
+
+); diff --git a/src/components/Footer/FooterCfGov.tsx b/src/components/Footer/FooterCfGov.tsx new file mode 100644 index 00000000..5ba8dc8f --- /dev/null +++ b/src/components/Footer/FooterCfGov.tsx @@ -0,0 +1,141 @@ +import { Icon } from '../Icon/Icon'; +import Footer from './Footer'; + +export const FooterCfGov = (): JSX.Element => { + const navLinks = [ + + About Us + , + + Contact Us + , + + Careers + , + + Events + , + + Industry Whistleblowers + , + + CFPB Ombudsman + + ]; + + const socialLinks = [ + + + , + + + , + + + , + + + , + + + + ]; + + const linksCol1 = [ + + FOIA + , + + Privacy + , + + Website Privacy Policy & Legal Notices + , + + Data + , + + Open Government + , + + Information Quality Guidelines + + ]; + + const linksCol2 = [ + + Diversity & Inclusion + , + + Administrative Adjudication + , + + Plain Writing + , + + Accessibility + , + + Office of Civil Rights + , + + No FEAR Act & Cummings Act + , + + Tribal + + ]; + + const linksCol3 = [ + + USA.gov + + , + + Office of Inspector General + + + ]; + + return ( +