diff --git a/site/components/illustrations/components/button/usage/do3.tsx b/site/components/illustrations/components/button/usage/do3.tsx new file mode 100644 index 0000000000..eec61ac7ab --- /dev/null +++ b/site/components/illustrations/components/button/usage/do3.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Do3: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Do3; diff --git a/site/components/illustrations/components/tooltip/anatomy.tsx b/site/components/illustrations/components/tooltip/anatomy.tsx new file mode 100644 index 0000000000..374c6402c8 --- /dev/null +++ b/site/components/illustrations/components/tooltip/anatomy.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import {Svg} from '../../svg'; +import {Path} from '../../path'; +import {Rect} from '../../rect'; + +export const Anatomy: React.FC = () => ( + + + + + + + + + + + + + + + + +); + +export default Anatomy; diff --git a/site/components/illustrations/components/tooltip/behaviours/flip.tsx b/site/components/illustrations/components/tooltip/behaviours/flip.tsx new file mode 100644 index 0000000000..f148f88de5 --- /dev/null +++ b/site/components/illustrations/components/tooltip/behaviours/flip.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Flip: React.FC = () => ( + + + + + + + + + + + + + + + + + + + +); + +export default Flip; diff --git a/site/components/illustrations/components/tooltip/behaviours/shift.tsx b/site/components/illustrations/components/tooltip/behaviours/shift.tsx new file mode 100644 index 0000000000..d8d3635dd5 --- /dev/null +++ b/site/components/illustrations/components/tooltip/behaviours/shift.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Shift: React.FC = () => { + const clip0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Shift; diff --git a/site/components/illustrations/components/tooltip/behaviours/transition.tsx b/site/components/illustrations/components/tooltip/behaviours/transition.tsx new file mode 100644 index 0000000000..cc8bd3f440 --- /dev/null +++ b/site/components/illustrations/components/tooltip/behaviours/transition.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Transition: React.FC = () => ( + + + + + + + + + + + +); + +export default Transition; diff --git a/site/components/illustrations/components/tooltip/behaviours/triggering-and-closing.tsx b/site/components/illustrations/components/tooltip/behaviours/triggering-and-closing.tsx new file mode 100644 index 0000000000..68f1de8c55 --- /dev/null +++ b/site/components/illustrations/components/tooltip/behaviours/triggering-and-closing.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const TriggeringAndClosing: React.FC = () => { + const clip0 = getSSRId(); + const clip1 = getSSRId(); + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TriggeringAndClosing; diff --git a/site/components/illustrations/components/tooltip/hero.tsx b/site/components/illustrations/components/tooltip/hero.tsx new file mode 100644 index 0000000000..a497a298c6 --- /dev/null +++ b/site/components/illustrations/components/tooltip/hero.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import {Svg} from '../../svg'; +import {Path} from '../../path'; +import {Rect} from '../../rect'; + +export const Hero: React.FC = () => ( + + + + + + + + + + + +); + +export default Hero; diff --git a/site/components/illustrations/components/tooltip/options/distance.tsx b/site/components/illustrations/components/tooltip/options/distance.tsx new file mode 100644 index 0000000000..32c8aefe46 --- /dev/null +++ b/site/components/illustrations/components/tooltip/options/distance.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Distance: React.FC = () => ( + + + + + + + + + + + + + + + + + +); + +export default Distance; diff --git a/site/components/illustrations/components/tooltip/options/placement.tsx b/site/components/illustrations/components/tooltip/options/placement.tsx new file mode 100644 index 0000000000..4e2359cf74 --- /dev/null +++ b/site/components/illustrations/components/tooltip/options/placement.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Placement: React.FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default Placement; diff --git a/site/components/illustrations/components/tooltip/options/pointer.tsx b/site/components/illustrations/components/tooltip/options/pointer.tsx new file mode 100644 index 0000000000..1c48d20a9b --- /dev/null +++ b/site/components/illustrations/components/tooltip/options/pointer.tsx @@ -0,0 +1,187 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Pointer: React.FC = () => { + const clip0 = getSSRId(); + const filter0 = getSSRId(); + const filter1 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Pointer; diff --git a/site/components/illustrations/components/tooltip/options/size.tsx b/site/components/illustrations/components/tooltip/options/size.tsx new file mode 100644 index 0000000000..a0fe3e506f --- /dev/null +++ b/site/components/illustrations/components/tooltip/options/size.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Size: React.FC = () => ( + + + + + + + + + + + + + + + + + + + +); + +export default Size; diff --git a/site/components/illustrations/components/tooltip/usage/do1.tsx b/site/components/illustrations/components/tooltip/usage/do1.tsx new file mode 100644 index 0000000000..9903408df8 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/do1.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Do1: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Do1; diff --git a/site/components/illustrations/components/tooltip/usage/do2.tsx b/site/components/illustrations/components/tooltip/usage/do2.tsx new file mode 100644 index 0000000000..ca51cc99b2 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/do2.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Do2: React.FC = () => ( + + + + + + + +); + +export default Do2; diff --git a/site/components/illustrations/components/tooltip/usage/do3.tsx b/site/components/illustrations/components/tooltip/usage/do3.tsx new file mode 100644 index 0000000000..4834f562b4 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/do3.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Do3: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Do3; diff --git a/site/components/illustrations/components/tooltip/usage/do4.tsx b/site/components/illustrations/components/tooltip/usage/do4.tsx new file mode 100644 index 0000000000..e9c07276ec --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/do4.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Do4: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Do4; diff --git a/site/components/illustrations/components/tooltip/usage/dont1.tsx b/site/components/illustrations/components/tooltip/usage/dont1.tsx new file mode 100644 index 0000000000..eece1de884 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/dont1.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; +import {Circle} from '../../../circle'; + +export const Dont1: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Dont1; diff --git a/site/components/illustrations/components/tooltip/usage/dont2.tsx b/site/components/illustrations/components/tooltip/usage/dont2.tsx new file mode 100644 index 0000000000..abf1d277bf --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/dont2.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Dont2: React.FC = () => ( + + + + + + + + + + +); + +export default Dont2; diff --git a/site/components/illustrations/components/tooltip/usage/dont3.tsx b/site/components/illustrations/components/tooltip/usage/dont3.tsx new file mode 100644 index 0000000000..7fd24b1f67 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/dont3.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Dont3: React.FC = () => { + const clip0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Dont3; diff --git a/site/components/illustrations/components/tooltip/usage/dont4.tsx b/site/components/illustrations/components/tooltip/usage/dont4.tsx new file mode 100644 index 0000000000..f3a4c47aec --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/dont4.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Dont4: React.FC = () => { + const clip0 = getSSRId(); + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Dont4; diff --git a/site/components/illustrations/components/tooltip/usage/dont5.tsx b/site/components/illustrations/components/tooltip/usage/dont5.tsx new file mode 100644 index 0000000000..ae475ac629 --- /dev/null +++ b/site/components/illustrations/components/tooltip/usage/dont5.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import {getSSRId} from 'newskit'; +import {Svg} from '../../../svg'; +import {Path} from '../../../path'; +import {Rect} from '../../../rect'; + +export const Dont5: React.FC = () => { + const filter0 = getSSRId(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Dont5; diff --git a/site/components/media-list/types.ts b/site/components/media-list/types.ts index ad11fc3541..4a6b35743c 100644 --- a/site/components/media-list/types.ts +++ b/site/components/media-list/types.ts @@ -17,6 +17,6 @@ export interface MediaListProps { spaceStack?: string; gridProps?: GridProps; horizontalRatio?: string; - notice?: string; + notice?: string | React.ReactNode; } export type MediaItem = BaseCardProps | UsageCardProps | FeatureCardProps; diff --git a/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap b/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap index cb938ffc1e..a3b060fa60 100644 --- a/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap +++ b/site/components/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap @@ -566,7 +566,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` display: block; } -.emotion-608 { +.emotion-613 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -574,7 +574,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` margin-bottom: 8px; } -.emotion-609 { +.emotion-614 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -584,7 +584,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` width: 100%; } -.emotion-612 { +.emotion-617 { margin-bottom: 32px; } @@ -2117,6 +2117,30 @@ exports[`Sidebar renders correctly when closed 1`] = ` +
  • + +
    +
    + + Tooltip + +
    +
    +
    +
  • @@ -3644,7 +3668,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` />
    @@ -4338,7 +4362,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` display: block; } -.emotion-606 { +.emotion-611 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -4346,7 +4370,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` margin-bottom: 8px; } -.emotion-607 { +.emotion-612 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -4357,55 +4381,55 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 480px) { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 768px) { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 1024px) { - .emotion-608 { + .emotion-613 { display: block; } } @media screen and (min-width: 1440px) { - .emotion-608 { + .emotion-613 { display: block; } } -.emotion-610 { +.emotion-615 { margin-bottom: 32px; } -.emotion-611 { +.emotion-616 { margin-inline: 32px; margin-block: 24px; } -.emotion-612 { +.emotion-617 { -webkit-text-decoration: none; text-decoration: none; } -.emotion-613 { +.emotion-618 { display: grid; grid-template-columns: 1fr 24px; } -.emotion-614 { +.emotion-619 { margin: 0; color: #2E2E2E; font-family: "Poppins",sans-serif; @@ -4415,11 +4439,11 @@ exports[`Sidebar renders correctly when closed 1`] = ` letter-spacing: 0; } -.emotion-614 svg { +.emotion-619 svg { fill: #2E2E2E; } -.emotion-615 { +.emotion-620 { display: inline-block; vertical-align: middle; overflow: hidden; @@ -4429,7 +4453,7 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen and (prefers-reduced-motion: no-preference) { - .emotion-615 { + .emotion-620 { transition-property: fill; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); @@ -4437,19 +4461,19 @@ exports[`Sidebar renders correctly when closed 1`] = ` } @media screen and (prefers-reduced-motion: reduce) { - .emotion-615 { + .emotion-620 { transition-property: fill; transition-duration: 0ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); } } -.emotion-615.emotion-615 { +.emotion-620.emotion-620 { width: 24px; height: 24px; } -.emotion-617 { +.emotion-622 { margin-block: 24px; } @@ -6052,6 +6076,31 @@ exports[`Sidebar renders correctly when closed 1`] = `
  • +
  • + +
    +
    + + Tooltip + +
    +
    +
    +
  • @@ -7622,7 +7671,7 @@ exports[`Sidebar renders correctly when closed 1`] = `
    @@ -8256,7 +8305,7 @@ exports[`Sidebar renders correctly when open 1`] = ` display: block; } -.emotion-608 { +.emotion-613 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -8264,7 +8313,7 @@ exports[`Sidebar renders correctly when open 1`] = ` margin-bottom: 8px; } -.emotion-609 { +.emotion-614 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -8274,7 +8323,7 @@ exports[`Sidebar renders correctly when open 1`] = ` width: 100%; } -.emotion-612 { +.emotion-617 { margin-bottom: 32px; } @@ -9811,6 +9860,30 @@ exports[`Sidebar renders correctly when open 1`] = `
  • +
  • + +
    +
    + + Tooltip + +
    +
    +
    +
  • @@ -11338,7 +11411,7 @@ exports[`Sidebar renders correctly when open 1`] = ` />
    @@ -12031,7 +12104,7 @@ exports[`Sidebar renders correctly when open 1`] = ` display: block; } -.emotion-606 { +.emotion-611 { width: 100%; position: relative; left: calc(-50vw + 50%); @@ -12039,7 +12112,7 @@ exports[`Sidebar renders correctly when open 1`] = ` margin-bottom: 8px; } -.emotion-607 { +.emotion-612 { border-style: solid; border-color: #E4E4E4; border-width: 1px; @@ -12050,55 +12123,55 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 480px) { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 768px) { - .emotion-608 { + .emotion-613 { display: none; } } @media screen and (min-width: 1024px) { - .emotion-608 { + .emotion-613 { display: block; } } @media screen and (min-width: 1440px) { - .emotion-608 { + .emotion-613 { display: block; } } -.emotion-610 { +.emotion-615 { margin-bottom: 32px; } -.emotion-611 { +.emotion-616 { margin-inline: 32px; margin-block: 24px; } -.emotion-612 { +.emotion-617 { -webkit-text-decoration: none; text-decoration: none; } -.emotion-613 { +.emotion-618 { display: grid; grid-template-columns: 1fr 24px; } -.emotion-614 { +.emotion-619 { margin: 0; color: #2E2E2E; font-family: "Poppins",sans-serif; @@ -12108,11 +12181,11 @@ exports[`Sidebar renders correctly when open 1`] = ` letter-spacing: 0; } -.emotion-614 svg { +.emotion-619 svg { fill: #2E2E2E; } -.emotion-615 { +.emotion-620 { display: inline-block; vertical-align: middle; overflow: hidden; @@ -12122,7 +12195,7 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen and (prefers-reduced-motion: no-preference) { - .emotion-615 { + .emotion-620 { transition-property: fill; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); @@ -12130,19 +12203,19 @@ exports[`Sidebar renders correctly when open 1`] = ` } @media screen and (prefers-reduced-motion: reduce) { - .emotion-615 { + .emotion-620 { transition-property: fill; transition-duration: 0ms; transition-timing-function: cubic-bezier(0, 0, .5, 1); } } -.emotion-615.emotion-615 { +.emotion-620.emotion-620 { width: 24px; height: 24px; } -.emotion-617 { +.emotion-622 { margin-block: 24px; } @@ -13694,6 +13767,30 @@ exports[`Sidebar renders correctly when open 1`] = `
  • +
  • + +
    +
    + + Tooltip + +
    +
    +
    +
  • @@ -15208,7 +15305,7 @@ exports[`Sidebar renders correctly when open 1`] = `
    diff --git a/site/pages/components/button.tsx b/site/pages/components/button.tsx index c012324942..5587d86bd2 100644 --- a/site/pages/components/button.tsx +++ b/site/pages/components/button.tsx @@ -490,10 +490,15 @@ const ButtonComponent = (layoutProps: LayoutProps) => ( media: getIllustrationComponent('components/button/usage/dont2'), }, { - description: - 'Avoid placing more than one primary (high emphasis) Button on a screen to help guide the user to the primary action.', - kind: UsageKind.DONT, - media: getIllustrationComponent('components/button/usage/dont3'), + description: ( + <> + The Tooltip can be + applied to the Icon Button in order to provide additional context + relating to the intended action or destination for users. + + ), + kind: UsageKind.DO, + media: getIllustrationComponent('components/button/usage/do3'), }, { description: @@ -501,6 +506,12 @@ const ButtonComponent = (layoutProps: LayoutProps) => ( kind: UsageKind.DONT, media: getIllustrationComponent('components/button/usage/dont4'), }, + { + description: + 'Avoid placing more than one primary (high emphasis) Button on a screen to help guide the user to the primary action.', + kind: UsageKind.DONT, + media: getIllustrationComponent('components/button/usage/dont3'), + }, ], }} accessibility={{ diff --git a/site/pages/components/tooltip.tsx b/site/pages/components/tooltip.tsx new file mode 100644 index 0000000000..5f12bce7ca --- /dev/null +++ b/site/pages/components/tooltip.tsx @@ -0,0 +1,655 @@ +import React from 'react'; +import {InlineMessage, UnorderedList, toNewsKitIcon} from 'newskit'; +import {Info as FilledInfo} from '@emotion-icons/material/Info'; +import {Link} from '../../components/link'; +import {UsageKind} from '../../components/usage-card'; +import {InlineCode} from '../../components/markdown-elements'; +import {MetaStatus} from '../../components/meta/types'; +import {LayoutProps} from '../../components/layout'; +import {IconFilledCircle} from '../../components/icons'; +import {ComponentPageTemplate} from '../../templates/component-page-template'; +import {getIllustrationComponent} from '../../components/illustrations/illustration-loader'; + +const IconFilledInfo = toNewsKitIcon(FilledInfo); + +const infoIcon = ( + +); + +const TooltipComponent = (layoutProps: LayoutProps) => ( + + The pointer is used to indicate to the user the reference element + to which the Tooltip is attributed. It can be set to be visible or + hidden. +
    +
    + The pointer position changes depending on the placement of the + Tooltip. + + ), + media: getIllustrationComponent('components/tooltip/options/pointer'), + }, + { + title: 'Distance', + description: ( + <> + The space between the Tooltip and the UI element can be changed + with spacing tokens. By default, there is 8px of space between the + tooltip and the element to which it is attributed. This is + measured from the tip of the pointer, or from the panel if a + pointer is not used. +
    +
    + If no pointer is visible, then the distance to the reference + element is reduced to zero. + + ), + media: getIllustrationComponent( + 'components/tooltip/options/distance', + ), + }, + ], + }} + behaviors={{ + introduction: 'The following guidance describes how a Tooltip behaves.', + cards: [ + { + title: 'Triggering and closing the Tooltip', + description: + 'The Tooltip is triggered by hovering and/or focussing on the UI element to which it is attributed. Removing focus or hover will dismiss the Tooltip.', + media: getIllustrationComponent( + 'components/tooltip/behaviours/triggering-and-closing', + ), + }, + { + title: 'Shift', + description: ( + <> + The Tooltip shifts in order to remain in view of the visible area, + with the pointer maintaining the context that the Tooltip is + attributed to. +
    +
    + + For more information, refer to the Floating UI library. + + + ), + media: getIllustrationComponent( + 'components/tooltip/behaviours/shift', + ), + }, + { + title: 'Flip', + description: ( + <> + The Tooltip flips to the opposite side once it’s about to overflow + the visible area, with the pointer maintaining the context that + the Tooltip is attributed to. Once enough space is detected on its + preferred side, it will flip back to its original position. +
    +
    + + For more information, refer to the Floating UI library. + + + ), + media: getIllustrationComponent('components/tooltip/behaviours/flip'), + }, + { + title: 'Transition & delay', + description: ( + <> + The Tooltip transitions using the fade{' '} + transition preset when it is triggered. + + A delay is applied to the first hovered item, after which there + is no (or reduced) delay on subsequent Tooltips until the user + has stopped hovering over any relevant components. + + + ), + media: getIllustrationComponent( + 'components/tooltip/behaviours/transition', + ), + }, + ], + }} + usage={{ + introduction: + 'The following guidance describes how and when to appropriately use the Tooltip component.', + cards: [ + { + description: + 'Tooltips are intended for displaying supplemental information related to an element on hover or focus.', + kind: UsageKind.DO, + media: getIllustrationComponent('components/tooltip/usage/do1'), + }, + { + description: + 'Do not put essential information in Tooltips, as the content passed will not be announced by screen readers. Content passed to the Tooltip should match the Aria-label and description.', + kind: UsageKind.DONT, + media: getIllustrationComponent('components/tooltip/usage/dont1'), + }, + { + description: + 'Avoid using large chunks of text in Tooltips as this may result in cognitive overload for some users.', + kind: UsageKind.DO, + media: getIllustrationComponent('components/tooltip/usage/do2'), + }, + { + description: ( + <> + Any interactive content such as links or buttons should not be + placed within a Tooltip. In these cases, consider using a{' '} + Popover instead. + + ), + kind: UsageKind.DONT, + media: getIllustrationComponent('components/tooltip/usage/dont2'), + }, + { + description: + 'Allow the mouse to easily move over the Tooltip without dismissing it. Tooltips should remain in view until a user hovers or focuses away from them.', + kind: UsageKind.DO, + media: getIllustrationComponent('components/tooltip/usage/do3'), + }, + { + description: + 'Avoid rich text. Formatting will not be conveyed to screen reader users eg. bold, italicised text, or icons).', + kind: UsageKind.DONT, + media: getIllustrationComponent('components/tooltip/usage/dont3'), + }, + { + description: + 'The Tooltip can be applied to the Icon Button or standalone Link in order to provide additional context relating to the intended action or destination for users.', + kind: UsageKind.DO, + media: getIllustrationComponent('components/tooltip/usage/do4'), + }, + { + description: 'Avoid using a timeout to hide the Tooltip.', + kind: UsageKind.DONT, + media: getIllustrationComponent('components/tooltip/usage/dont4'), + }, + { + description: + 'Avoid covering the element that the Tooltip is attributed to, as it will lose its context.', + kind: UsageKind.DONT, + media: getIllustrationComponent('components/tooltip/usage/dont5'), + }, + ], + notice: ( + <> + Tooltip is not triggered if an element inside it has a disabled prop. + If you would like to wrap a disabled component around Tooltip,{' '} + + add a wrapper element + {' '} + span for example. + + ), + }} + accessibility={{ + introduction: ( + <> + The Tooltip has the following accessibility considerations: + + <> + Tooltips must be discoverable and readable with a mouse, other + pointer devices, keyboard, screen reader, zoom software, and any + other assistive technology. + + <> + They should provide relevant information that may be helpful for + learning the UI, but is not required to operate it. + + <> + When open, Tooltips should not block a user from performing any + other task on the screen, this should be tested across all + breakpoints. + + + + ), + focusOrder: { + title: 'Focus order', + }, + infoNoticeFocus: + 'It is not recommeded to pass links or other interactive elements to a Tooltip, due to the intent of a Tooltip being intended for short, informational messages on hover or focus.', + interaction: { + title: 'Keyboard Interactions', + tableRows: [ + { + command: ['Tab'], + description: `Toggle the Tooltip when the element that triggers it is focused.`, + }, + ], + }, + aria: { + title: 'WAI-ARIA', + tableRows: [ + { + element: 'Popover', + attribute: 'aria-hidden', + value: 'true', + description: 'If true, hides the Tooltip', + }, + ], + }, + infoNoticeAria: [ + <> + By default, the Tooltip only describes its child element. The content + of the Tooltip acts as an accessible description and{' '} + aria-describedby will be added to the + Tooltip's child elements. +
    +
    + Note that if the Tooltip provides the only visual label, eg an icon + button, then you should use Tooltip to label its child elements. + Otherwise, the children would have no accessible name and the Tooltip + would violate{' '} + + success criterion 2.5.3 in WCAG 2.1. + +
    +
    + You can pass the asLabel prop to make the + Tooltip act as a label. In this case,{' '} + role=tooltip will be removed, and if Tooltip + content is a string, aria-label will be added + to child elements. Otherwise, aria-labelledby{' '} + will be added. + , + ], + }} + seo={{ + title: 'SEO Considerations', + introduction: ( + <> + + <> + The Tooltip component and its content are rendered to the DOM, but + only visible to the user when the Tooltip is open. + + + + ), + }} + componentAPI={{ + components: [ + { + propsSummary: + 'The Tooltip has a range of props that can be used to define an appropriate experience for different use cases.', + overridesSummary: + 'The Tooltip has a range of predefined elements and attributes that can be overridden to define their appearance.', + propsRows: [ + { + name: 'children', + type: 'React.ReactElement & { ref?: React.Ref; };', + description: 'Tooltip reference element.', + required: true, + }, + { + name: 'content', + type: 'React.ReactNode', + description: 'Default content value.', + required: true, + }, + { + name: 'asLabel', + type: 'boolean', + default: 'false', + description: ( + <> + Set to true if the tooltip acts as an accessible label for the + child component. +
    +
    + By default the title acts as an accessible description for the + child. + + ), + required: undefined, + }, + { + name: 'trigger', + type: 'string | string[]', + default: `['hover', 'focus']`, + description: `Defines how the Tooltip is triggered`, + required: undefined, + }, + { + name: 'open', + type: 'boolean', + default: 'false', + description: 'If true, the Tooltip is shown', + required: undefined, + }, + { + name: 'fallbackBehaviours', + type: `'flip' | 'shift' | ['flip', 'shift']`, + default: `‘flip', 'shift’`, + description: ( + <> + Behavior the Tooltip should follow for fallback. +
    +
    + + For more information refer to the documentation from + Floating UI. + + + ), + required: undefined, + }, + { + name: 'boundary', + type: `'clippingAncestors' | Element | Array;`, + default: `'clippingAncestors'`, + description: ( + <> + Describes the clipping element(s) that overflow will be + checked relative to. +
    +
    + + For more information refer to the documentation from + Floating UI. + + + ), + required: undefined, + }, + { + name: 'placement', + type: [ + 'top', + 'top-start', + 'top-end', + 'right', + 'right-start', + 'right-end', + 'bottom', + 'bottom-start', + 'bottom-end', + 'left', + 'left-start', + 'left-end', + ], + default: `top`, + description: + 'Defines the placement of the Tooltip, with the optional indicator to denote the direction of the context it is attributed.', + required: undefined, + }, + { + name: 'hidePointer', + type: 'boolean', + default: 'false', + description: 'If provided, hides the pointer.', + required: undefined, + }, + ], + overridesRows: [ + { + attribute: 'maxWidth', + type: 'MQ', + description: 'Overrides the maxWidth property of the Tooltip.', + }, + { + attribute: 'minWidth', + type: 'MQ', + description: 'Overrides the minWidth property of the Tooltip.', + }, + { + attribute: 'zIndex', + type: 'number', + default: '80', + description: 'Overrides the zIndex of the Tooltip.', + }, + { + attribute: 'distance', + type: 'MQ', + default: 'space020', + description: + 'Overrides the distance between the Tooltip and the item it is attributed to.', + }, + { + attribute: 'stylePreset', + type: 'MQ', + description: 'Overrides the stylePreset applied to the Tooltip.', + }, + { + attribute: 'transitionPreset', + type: 'MQ', + default: 'fade', + description: 'Overrides the transitionPrese of the Tooltip.', + }, + { + attribute: 'panel.paddingBlock', + type: 'MQ', + default: 'spaceInset020', + description: 'Overrides the inset space of the Tooltip panel.', + }, + { + attribute: 'panel.paddingInline', + type: 'MQ', + default: 'spaceInset020', + description: 'Overrides the inset space of the Tooltip panel.', + }, + { + attribute: 'panel.stylePreset', + type: 'MQ', + default: 'tooltipPanel', + description: + 'Overrides the stylePreset applied to the Tooltip panel.', + }, + { + attribute: 'panel.typographyPreset', + type: 'MQ', + default: 'utilityLabel010', + description: + 'Overrides the typographyPreset applied to the Tooltip panel.', + }, + { + attribute: 'panel.paddingBlockStart', + type: 'MQ', + description: + 'It can take one space token to specify the logical block start padding of the container. This space token can also be used on breakpoints', + }, + { + attribute: 'panel.paddingBlockEnd', + type: 'MQ', + description: + 'It can take one space token to specify the logical block end padding of the container. This space token can also be used on breakpoints.', + }, + { + attribute: 'panel.paddingInlineStart', + type: 'MQ', + description: + 'It can take one space token to specify the logical inline start padding of the container. This space token can also be used on breakpoints.', + }, + { + attribute: 'panel.paddingInlineEnd', + type: 'MQ', + description: + 'It can take one space token to specify the logical inline end padding of the container. This space token can also be used on breakpoints.', + }, + { + attribute: 'pointer.size', + type: 'MQ', + default: 'sizing010', + description: 'Overrides the size of the Tooltip pointer.', + }, + { + attribute: 'pointer.stylePreset', + type: 'MQ', + default: 'tooltipPointer', + description: 'Overrides the stylePreset of the Tooltip pointer.', + }, + { + attribute: 'pointer.edgeOffset', + type: 'MQ', + default: 'space020', + description: + 'Overrides the edgeOffset of the Tooltip indicator. The edgeOffset is the padding between the indicator and the edges of the popover container.', + }, + ], + propsFooter: ( + <> + + If the Tooltip is wrapping a functional component, ensure that + the functional component accepts a ref using{' '} + + forwardRef. + + + + ), + }, + ], + }} + compliance={{ + states: true, + variations: true, + themes: true, + behaviours: true, + usage: true, + accessibility: true, + seo: true, + performance: false, + design: true, + props: true, + uiKit: true, + }} + related={{ + related: ['Popover', 'Modal'], + }} + /> +); + +export default TooltipComponent; diff --git a/site/routes.ts b/site/routes.ts index 53b30d5757..6fabd22a57 100644 --- a/site/routes.ts +++ b/site/routes.ts @@ -440,6 +440,14 @@ export default [ illustration: 'components/inline-message/inline-message-illustration', }, + { + title: 'Tooltip', + page: true, + id: '/components/tooltip', + description: + 'A Tooltip is a feedback component that displays a short, informational message when a user hovers over or focuses on a UI element. ', + illustration: 'components/tooltip/hero', + }, ], }, { diff --git a/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap b/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap index ea5337b562..14479d7c5b 100644 --- a/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap +++ b/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap @@ -142,30 +142,34 @@ exports[`AccessibilitySection renders section as expected 1`] = ` display: block; } +.emotion-8 { + margin-bottom: 40px; +} + @media screen and (max-width: 767px) { - .emotion-8 { + .emotion-9 { margin-bottom: 16px; } } @media screen and (min-width: 768px) { - .emotion-8 { + .emotion-9 { margin-bottom: 20px; } } -.emotion-9 { +.emotion-10 { margin: 0; color: #0A0A0A; display: inline-block; } -.emotion-9 svg { +.emotion-10 svg { fill: #0A0A0A; } @media screen and (max-width: 1439px) { - .emotion-9 { + .emotion-10 { font-family: "Poppins",sans-serif; font-size: 20px; line-height: 22.5px; @@ -174,13 +178,13 @@ exports[`AccessibilitySection renders section as expected 1`] = ` padding: 0.5px 0px; } - .emotion-9::before { + .emotion-10::before { content: ''; margin-bottom: -0.2155em; display: block; } - .emotion-9::after { + .emotion-10::after { content: ''; margin-top: -0.2125em; display: block; @@ -188,7 +192,7 @@ exports[`AccessibilitySection renders section as expected 1`] = ` } @media screen and (min-width: 1440px) { - .emotion-9 { + .emotion-10 { font-family: "Poppins",sans-serif; font-size: 22px; line-height: 24.75px; @@ -197,34 +201,30 @@ exports[`AccessibilitySection renders section as expected 1`] = ` padding: 0.5px 0px; } - .emotion-9::before { + .emotion-10::before { content: ''; margin-bottom: -0.2155em; display: block; } - .emotion-9::after { + .emotion-10::after { content: ''; margin-top: -0.2125em; display: block; } } -.emotion-10 { - margin-bottom: 40px; -} - -.emotion-11 { +.emotion-12 { margin: 0; color: #2E2E2E; } -.emotion-11 svg { +.emotion-12 svg { fill: #2E2E2E; } @media screen and (max-width: 1439px) { - .emotion-11 { + .emotion-12 { font-family: "DM Sans",sans-serif; font-size: 16px; line-height: 24px; @@ -233,13 +233,13 @@ exports[`AccessibilitySection renders section as expected 1`] = ` padding: 0.5px 0px; } - .emotion-11::before { + .emotion-12::before { content: ''; margin-bottom: -0.391em; display: block; } - .emotion-11::after { + .emotion-12::after { content: ''; margin-top: -0.409em; display: block; @@ -247,7 +247,7 @@ exports[`AccessibilitySection renders section as expected 1`] = ` } @media screen and (min-width: 1440px) { - .emotion-11 { + .emotion-12 { font-family: "DM Sans",sans-serif; font-size: 18px; line-height: 27px; @@ -256,19 +256,23 @@ exports[`AccessibilitySection renders section as expected 1`] = ` padding: 0.5px 0px; } - .emotion-11::before { + .emotion-12::before { content: ''; margin-bottom: -0.391em; display: block; } - .emotion-11::after { + .emotion-12::after { content: ''; margin-top: -0.409em; display: block; } } +.emotion-13 { + margin-bottom: 64px; +} +
    - - Focus order - -
    -
    -

    + Focus order + +

    +
    - focus order description -

    -
    -
    - Table Component with props:{ +

    + focus order description +

    +
    +
    + Table Component with props:{ "columns": [ "Order", "Element", @@ -412,38 +419,34 @@ exports[`AccessibilitySection renders section as expected 1`] = ` } ] } +
    -
    -
    +
    - - Interaction - -
    -
    -

    + Interaction + +

    +
    - interaction description -

    -
    -
    - Table Component with props:{ +

    + interaction description +

    +
    +
    + Table Component with props:{ "columns": [ "Command", "Description" @@ -474,38 +477,31 @@ exports[`AccessibilitySection renders section as expected 1`] = ` } ] } +
    -
    -
    - - ARIA - -
    -
    -

    + ARIA + +

    +
    - aria description -

    -
    -
    - Table Component with props:{ +

    + aria description +

    +
    +
    + Table Component with props:{ "columns": [ "Element", "Attribute", @@ -537,6 +533,7 @@ exports[`AccessibilitySection renders section as expected 1`] = ` } ] } +
    diff --git a/site/templates/template-sections/accessibility-section.tsx b/site/templates/template-sections/accessibility-section.tsx index 325ded9f26..1101ba3a0c 100644 --- a/site/templates/template-sections/accessibility-section.tsx +++ b/site/templates/template-sections/accessibility-section.tsx @@ -1,11 +1,22 @@ -import {Grid, Cell, InlineMessage, Block} from 'newskit'; import React from 'react'; +import {Grid, Cell, InlineMessage, Block, toNewsKitIcon} from 'newskit'; +import {Info as FilledInfo} from '@emotion-icons/material/Info'; import {IntroductionText} from './types'; import {CommonSection} from './common-section'; import {ContentText} from '../../components/text-section/content-text'; import {Table} from '../../components/table'; import {ComponentPageCell} from '../../components/layout-cells'; +const IconFilledInfo = toNewsKitIcon(FilledInfo); + +const infoIcon = ( + +); + interface A11ySubSection { title: string; description?: string; @@ -43,12 +54,12 @@ const A11yTable: React.FC< | AccessibilitySectionProps['aria'] ) > = ({title, description, columns, tableRows}) => ( - + {description} {tableRows && } - + ); const renderInfoNotice = ( @@ -58,7 +69,7 @@ const renderInfoNotice = ( ) => { if (Array.isArray(notice)) { return ( - + <> {notice.map((note, index) => ( {note} ))} - + ); } if (notice !== undefined) { return ( - - - {notice} - - + + {notice} + ); } return null; @@ -103,29 +118,34 @@ export const AccessibilitySection: React.FC = ({ > - {focusOrder && ( - - )} + + {focusOrder && ( + + )} + + + {renderInfoNotice(infoNoticeFocus, 'Focus order notice', 'Note')} + - {renderInfoNotice(infoNoticeFocus, 'Note')} + {interaction && ( + + )} - {interaction && ( - - )} - {aria && ( - - )} + {aria && ( + + )} - {renderInfoNotice(infoNoticeAria, 'WAI Aria notice', 'Note')} + {renderInfoNotice(infoNoticeAria, 'WAI Aria notice', 'Note')} + diff --git a/site/templates/template-sections/usage-section.tsx b/site/templates/template-sections/usage-section.tsx index 4b30daa5b9..4ea4542e2c 100644 --- a/site/templates/template-sections/usage-section.tsx +++ b/site/templates/template-sections/usage-section.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import {InlineMessage, toNewsKitIcon} from 'newskit'; +import {Info as FilledInfo} from '@emotion-icons/material/Info'; import {ComponentPageCell} from '../../components/layout-cells'; import {MediaList, MediaListProps} from '../../components/media-list'; import {IntroductionText} from './types'; @@ -6,8 +8,19 @@ import {CommonSection} from './common-section'; export type UsageSectionProps = MediaListProps & IntroductionText; +const IconFilledInfo = toNewsKitIcon(FilledInfo); + +const infoIcon = ( + +); + export const UsageSection: React.FC = ({ introduction, + notice, ...usage }) => ( @@ -18,6 +31,19 @@ export const UsageSection: React.FC = ({ layout="2-span" {...usage} /> + {notice && ( + + {notice} + + )} );