diff --git a/site/components/illustrations/patterns/forms/date-picker/error-state.tsx b/site/components/illustrations/patterns/forms/date-picker/error-state.tsx new file mode 100644 index 0000000000..476fe9edf9 --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/error-state.tsx @@ -0,0 +1,210 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; + +export const ErrorState: React.FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default ErrorState; diff --git a/site/components/illustrations/patterns/forms/date-picker/hero.tsx b/site/components/illustrations/patterns/forms/date-picker/hero.tsx new file mode 100644 index 0000000000..db88bb7141 --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/hero.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Hero: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Hero; diff --git a/site/components/illustrations/patterns/forms/date-picker/how-to-use/long-term.tsx b/site/components/illustrations/patterns/forms/date-picker/how-to-use/long-term.tsx new file mode 100644 index 0000000000..19198bfdfe --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/how-to-use/long-term.tsx @@ -0,0 +1,277 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../../svg'; +import {Path} from '../../../../path'; +import {Rect} from '../../../../rect'; + +export const LongTerm: React.FC = () => { + const clip0 = getSSRId(); + const clip1 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default LongTerm; diff --git a/site/components/illustrations/patterns/forms/date-picker/how-to-use/mobile.tsx b/site/components/illustrations/patterns/forms/date-picker/how-to-use/mobile.tsx new file mode 100644 index 0000000000..bcd26b62b7 --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/how-to-use/mobile.tsx @@ -0,0 +1,588 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../../svg'; +import {Path} from '../../../../path'; +import {Rect} from '../../../../rect'; + +export const Mobile: React.FC = () => { + const clip0 = getSSRId(); + const clip1 = getSSRId(); + const clip2 = getSSRId(); + const clip3 = getSSRId(); + const clip4 = getSSRId(); + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Mobile; diff --git a/site/components/illustrations/patterns/forms/date-picker/how-to-use/short-term.tsx b/site/components/illustrations/patterns/forms/date-picker/how-to-use/short-term.tsx new file mode 100644 index 0000000000..274bf6cea5 --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/how-to-use/short-term.tsx @@ -0,0 +1,380 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../../svg'; +import {Path} from '../../../../path'; +import {Rect} from '../../../../rect'; + +export const ShortTerm: React.FC = () => { + const clip0 = getSSRId(); + const clip1 = getSSRId(); + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ShortTerm; diff --git a/site/components/illustrations/patterns/forms/date-picker/tip.tsx b/site/components/illustrations/patterns/forms/date-picker/tip.tsx new file mode 100644 index 0000000000..e009219144 --- /dev/null +++ b/site/components/illustrations/patterns/forms/date-picker/tip.tsx @@ -0,0 +1,424 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Tip: React.FC = () => { + const clip0 = getSSRId(); + const clip1 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Tip; diff --git a/site/components/illustrations/patterns/forms/email-address/error-state.tsx b/site/components/illustrations/patterns/forms/email-address/error-state.tsx new file mode 100644 index 0000000000..8af4925f75 --- /dev/null +++ b/site/components/illustrations/patterns/forms/email-address/error-state.tsx @@ -0,0 +1,181 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; + +export const ErrorState: React.FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default ErrorState; diff --git a/site/components/illustrations/patterns/forms/email-address/hero.tsx b/site/components/illustrations/patterns/forms/email-address/hero.tsx new file mode 100644 index 0000000000..e43e5087d7 --- /dev/null +++ b/site/components/illustrations/patterns/forms/email-address/hero.tsx @@ -0,0 +1,153 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Hero: React.FC = () => { + const filter0 = getSSRId(); + const filter1 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Hero; diff --git a/site/components/illustrations/patterns/forms/email-address/how-to-ask.tsx b/site/components/illustrations/patterns/forms/email-address/how-to-ask.tsx new file mode 100644 index 0000000000..bb62d95303 --- /dev/null +++ b/site/components/illustrations/patterns/forms/email-address/how-to-ask.tsx @@ -0,0 +1,249 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; + +export const HowToAsk: React.FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default HowToAsk; diff --git a/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap b/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap index ea32198b5f..f622cbf106 100644 --- a/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap +++ b/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap @@ -691,7 +691,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` display: block; } -.emotion-557 { +.emotion-567 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -699,7 +699,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` margin-bottom: 8px; } -.emotion-558 { +.emotion-568 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -710,55 +710,55 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 480px) { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 768px) { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 1024px) { - .emotion-559 { + .emotion-569 { display: block; } } @media screen and (min-width: 1440px) { - .emotion-559 { + .emotion-569 { display: block; } } -.emotion-561 { +.emotion-571 { margin-bottom: 32px; } -.emotion-562 { +.emotion-572 { margin-inline: 32px; margin-block: 24px; } -.emotion-563 { +.emotion-573 { -webkit-text-decoration: none; text-decoration: none; } -.emotion-564 { +.emotion-574 { display: grid; grid-template-columns: 1fr 24px; } -.emotion-565 { +.emotion-575 { margin: 0; color: #2E2E2E; font-family: "Poppins",sans-serif; @@ -768,11 +768,11 @@ exports[`Sidebar renders correctly when closed 1`] = ` letter-spacing: 0; } -.emotion-565 svg { +.emotion-575 svg { fill: #2E2E2E; } -.emotion-566 { +.emotion-576 { display: inline-block; vertical-align: middle; overflow: hidden; @@ -782,7 +782,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen and (prefers-reduced-motion: no-preference) { - .emotion-566 { + .emotion-576 { transition-property: fill; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); @@ -790,19 +790,19 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen and (prefers-reduced-motion: reduce) { - .emotion-566 { + .emotion-576 { transition-property: fill; transition-duration: 0ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); } } -.emotion-566.emotion-566 { +.emotion-576.emotion-576 { width: 24px; height: 24px; } -.emotion-568 { +.emotion-578 { margin-block: 24px; } @@ -3705,16 +3705,66 @@ exports[`Sidebar renders correctly when closed 1`] = ` +
  • + +
    +
    + + Date picker + +
    +
    +
    +
  • +
  • + +
    +
    + + Email address + +
    +
    +
    +
  • @@ -3722,7 +3772,7 @@ exports[`Sidebar renders correctly when closed 1`] = `
    @@ -4350,7 +4400,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` display: block; } -.emotion-557 { +.emotion-567 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -4358,7 +4408,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` margin-bottom: 8px; } -.emotion-558 { +.emotion-568 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -4368,7 +4418,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` width: 100%; } -.emotion-561 { +.emotion-571 { margin-bottom: 32px; } @@ -7154,16 +7204,64 @@ exports[`Sidebar renders correctly when closed 1`] = `
    +
  • + +
    +
    + + Date picker + +
    +
    +
    +
  • +
  • + +
    +
    + + Email address + +
    +
    +
    +
  • @@ -7184,7 +7282,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` />
    @@ -7882,7 +7980,7 @@ exports[`Sidebar renders correctly when open 1`] = ` display: block; } -.emotion-557 { +.emotion-567 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -7890,7 +7988,7 @@ exports[`Sidebar renders correctly when open 1`] = ` margin-bottom: 8px; } -.emotion-558 { +.emotion-568 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -7901,55 +7999,55 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 480px) { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 768px) { - .emotion-559 { + .emotion-569 { display: none; } } @media screen and (min-width: 1024px) { - .emotion-559 { + .emotion-569 { display: block; } } @media screen and (min-width: 1440px) { - .emotion-559 { + .emotion-569 { display: block; } } -.emotion-561 { +.emotion-571 { margin-bottom: 32px; } -.emotion-562 { +.emotion-572 { margin-inline: 32px; margin-block: 24px; } -.emotion-563 { +.emotion-573 { -webkit-text-decoration: none; text-decoration: none; } -.emotion-564 { +.emotion-574 { display: grid; grid-template-columns: 1fr 24px; } -.emotion-565 { +.emotion-575 { margin: 0; color: #2E2E2E; font-family: "Poppins",sans-serif; @@ -7959,11 +8057,11 @@ exports[`Sidebar renders correctly when open 1`] = ` letter-spacing: 0; } -.emotion-565 svg { +.emotion-575 svg { fill: #2E2E2E; } -.emotion-566 { +.emotion-576 { display: inline-block; vertical-align: middle; overflow: hidden; @@ -7973,7 +8071,7 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen and (prefers-reduced-motion: no-preference) { - .emotion-566 { + .emotion-576 { transition-property: fill; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); @@ -7981,19 +8079,19 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen and (prefers-reduced-motion: reduce) { - .emotion-566 { + .emotion-576 { transition-property: fill; transition-duration: 0ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); } } -.emotion-566.emotion-566 { +.emotion-576.emotion-576 { width: 24px; height: 24px; } -.emotion-568 { +.emotion-578 { margin-block: 24px; } @@ -10797,16 +10895,64 @@ exports[`Sidebar renders correctly when open 1`] = ` +
  • + +
    +
    + + Date picker + +
    +
    +
    +
  • +
  • + +
    +
    + + Email address + +
    +
    +
    +
  • @@ -10814,7 +10960,7 @@ exports[`Sidebar renders correctly when open 1`] = `
    @@ -11441,7 +11587,7 @@ exports[`Sidebar renders correctly when open 1`] = ` display: block; } -.emotion-557 { +.emotion-567 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -11449,7 +11595,7 @@ exports[`Sidebar renders correctly when open 1`] = ` margin-bottom: 8px; } -.emotion-558 { +.emotion-568 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -11459,7 +11605,7 @@ exports[`Sidebar renders correctly when open 1`] = ` width: 100%; } -.emotion-561 { +.emotion-571 { margin-bottom: 32px; } @@ -14248,16 +14394,64 @@ exports[`Sidebar renders correctly when open 1`] = `
    +
  • + +
    +
    + + Date picker + +
    +
    +
    +
  • +
  • + +
    +
    + + Email address + +
    +
    +
    +
  • @@ -14278,7 +14472,7 @@ exports[`Sidebar renders correctly when open 1`] = ` />
    diff --git a/site/pages/patterns/forms/date-picker.tsx b/site/pages/patterns/forms/date-picker.tsx new file mode 100644 index 0000000000..447a75043f --- /dev/null +++ b/site/pages/patterns/forms/date-picker.tsx @@ -0,0 +1,244 @@ +import React from 'react'; +import {UnorderedList} from 'newskit'; +import {Link} from '../../../components/link'; +import {MediaList} from '../../../components/media-list'; +import {LayoutProps} from '../../../components/layout'; +import {IconFilledCircle} from '../../../components/icons'; +import {ComponentPageCell} from '../../../components/layout-cells'; +import {PatternPageTemplate} from '../../../templates/pattern-page-template/pattern-page-template'; +import {getIllustrationComponent} from '../../../components/illustrations/illustration-loader'; +import { + ContentSection, + ContentPrimary, + ContentSecondary, + ContentTertiary, + ContentColSpan, +} from '../../../components/content-structure'; +import { + StyledHeading, + StyledDoHeading, + StyledDontHeading, +} from '../../../utils/styled'; + +const unorderedListOverrides = { + spaceStack: 'space040', + content: { + typographyPreset: 'editorialParagraph020', + }, +}; + +const DatePicker = (layoutProps: LayoutProps) => ( + + + + + + If the user is selecting a date more than 2 months in + advance, the date picker should display per month so the + user can click through each month. Including a year dropdown + within the date picker should be avoided as it becomes + cumbersome and awkward. It is also unlikely that the user + would need to select a date in the distant future.{' '} + + (See Google example) + + + ), + media: getIllustrationComponent( + 'patterns/forms/date-picker/how-to-use/long-term', + ), + }, + { + title: 'Mobile devices', + description: + 'On mobile, users should be able to scroll or swipe through months as this is an easier interaction than tapping a small CTA.', + media: getIllustrationComponent( + 'patterns/forms/date-picker/how-to-use/mobile', + ), + }, + ]} + /> + + Do} + childrenColSpan={ContentColSpan.TEXT} + > + + <>Clearly show a selected date via UI. + <>Use an appropriate date picker for relevant timescales. + <> + Make it easy for users to change their mind after selecting a + date. + + <>Make navigating between months easy. + <>Consider the device the user is on. + <> + Make it clear if a date is available unavailable/disabled via the + UI. + + + + Don’t} + childrenColSpan={ContentColSpan.TEXT} + > + + <>Use a date picker for date of birth. + <>Use a drop down for selecting the year. + + + + Although less commonly used in our industry, there may be + occasions where a date range needs to be selected by the user. E.g + holiday payments. +
    +
    + These are displayed similarly to a date picker but need to allow + for the following: + + } + > + + <> + Show today’s date by highlighting the date in the calendar via UI. + + <>Allow the user to easily browse through months. + <>First date selected should be the start date. + <> + Second date selected should be the end date. (Users should not be + allowed to select a date in the past). + + <> + The start and end date should be clearly highlighted in the + calendar and visually highlight the days/dates in between the + start and end date. + + +
    + + + + + + Please select a date (If left empty) +
    +
    + Please select a date range (If left empty) + + ), + media: getIllustrationComponent( + 'patterns/forms/date-picker/error-state', + ), + }, + ]} + /> +
    +
    + + + Help improve this page} + description={ + <> + To help make sure this page is as useful as it can be, relevant + and kept up to date with industry best practices, please get in + touch to share your research findings, and contribute to this + page. +
    +
    + + Propose a change or contribution by suggesting a feature + request. + + + } + showSeparator + /> +
    +
    +
    +); + +export default DatePicker; diff --git a/site/pages/patterns/forms/email-address.tsx b/site/pages/patterns/forms/email-address.tsx new file mode 100644 index 0000000000..530c662138 --- /dev/null +++ b/site/pages/patterns/forms/email-address.tsx @@ -0,0 +1,202 @@ +import React from 'react'; +import {Block, TextBlock, UnorderedList} from 'newskit'; +import {Link} from '../../../components/link'; +import {MediaList} from '../../../components/media-list'; +import {LayoutProps} from '../../../components/layout'; +import {IconFilledCircle} from '../../../components/icons'; +import {ComponentPageCell} from '../../../components/layout-cells'; +import {PatternPageTemplate} from '../../../templates/pattern-page-template/pattern-page-template'; +import { + Illustration, + getIllustrationComponent, +} from '../../../components/illustrations/illustration-loader'; +import { + ContentSection, + ContentPrimary, + ContentSecondary, + ContentTertiary, + ContentColSpan, +} from '../../../components/content-structure'; +import { + StyledHeading, + StyledDoHeading, + StyledDontHeading, + StyledEmailText, +} from '../../../utils/styled'; + +const unorderedListOverrides = { + spaceStack: 'space040', + content: { + typographyPreset: 'editorialParagraph020', + }, +}; + +const EmailAddress = (layoutProps: LayoutProps) => ( + + + + + + + + + + Do} + childrenColSpan={ContentColSpan.TEXT} + > + + + <>Check users have used the correct format. + <>Inform users why you are collecting this data. + <> + Consider email verification if the business needs to check that + users have access to the email address they give you. + + <> + Allow autofill so the user can easily fill out this form based + on saved personal data on their device i.e set autocomplete + attribute to ‘email’. + + <> + Give plenty of room in the{' '} + Text Field for + (Most) users to fit their whole email address. + + + + + + Help users to enter a valid email address by: + + + + <>Checking they have entered the correct format. + <>Allowing users to paste the email address. + <> + Setting the type attribute to email so that devices display the + correct keyboard. + + <> + Setting the spellcheck attribute to false so that browsers do not + spell check the email address. + + <> + Allowing keyboard shortcuts such as ‘@@’ to enter email address on + iOS devices. + + + + Don’t} + childrenColSpan={ContentColSpan.TEXT} + > + + <> + Ask for ‘Email address’ twice, it’s an extra action and may lead + to more errors when filling out the form. + + <> + Show example/hint placeholder text, entering an email address is + very common. + + <> + Check if email is valid whilst the user types, this should be done + when they tab out of the field (On blur). + + + + + + Please enter your email address (If empty on tab out or + selection of ‘Submit’) +
    +
    + Please enter a valid email address e.g{' '} + name@example.co.uk + + ), + media: getIllustrationComponent( + 'patterns/forms/email-address/error-state', + ), + }, + ]} + /> +
    +
    + + + Help improve this page} + description={ + <> + To help make sure this page is as useful as it can be, relevant + and kept up to date with industry best practices, please get in + touch to share your research findings, and contribute to this + page. +
    +
    + + Propose a change or contribution by suggesting a feature + request. + + + } + showSeparator + /> +
    +
    +
    +); + +export default EmailAddress; diff --git a/site/routes.ts b/site/routes.ts index 8df91a5e13..fedf9b5e48 100644 --- a/site/routes.ts +++ b/site/routes.ts @@ -827,6 +827,20 @@ export default [ description: `Ask for a user’s date of birth when we need to validate the user’s age. This should not be collected unless you have a need for it to validate a user’s age or benefit them in some way.`, illustration: 'patterns/forms/date-of-birth/hero', }, + { + title: 'Date picker', + page: true, + id: '/patterns/forms/date-picker', + description: `Use this data entry type when capturing a date that is in the future such as a delivery date or booking date.`, + illustration: 'patterns/forms/date-picker/hero', + }, + { + title: 'Email address', + page: true, + id: '/patterns/forms/email-address', + description: `Ask for a user’s email address to provide a service, to contact them directly, or as a unique way of identifying them.`, + illustration: 'patterns/forms/email-address/hero', + }, ], }, ], diff --git a/site/utils/styled.tsx b/site/utils/styled.tsx index aee15038b4..f433a88752 100644 --- a/site/utils/styled.tsx +++ b/site/utils/styled.tsx @@ -11,3 +11,7 @@ export const StyledDoHeading = styled.span` export const StyledDontHeading = styled.span` ${getColorCssFromTheme('color', 'inkNegative')}; `; + +export const StyledEmailText = styled.span` + ${getColorCssFromTheme('color', 'blue040')}; +`;