From 0e4faee1b88b2895809f062f3522162262d40db9 Mon Sep 17 00:00:00 2001 From: Eirik Backer Date: Fri, 20 Sep 2024 09:49:33 +0200 Subject: [PATCH] fix(HelpText): css alignments (#2438) Part of https://github.com/digdir/designsystemet/issues/2295 --------- Co-authored-by: Tobias Barsnes --- .changeset/tame-rats-mix.md | 9 ++ packages/css/button.css | 7 +- packages/css/helptext.css | 92 ++++++++----------- .../src/components/HelpText/HelpText.mdx | 6 -- .../components/HelpText/HelpText.stories.tsx | 11 +-- .../src/components/HelpText/HelpText.test.tsx | 2 +- .../src/components/HelpText/HelpText.tsx | 75 +++------------ .../components/HelpText/HelpTextIcon.test.tsx | 60 ------------ .../src/components/HelpText/HelpTextIcon.tsx | 31 ------- 9 files changed, 70 insertions(+), 223 deletions(-) create mode 100644 .changeset/tame-rats-mix.md delete mode 100644 packages/react/src/components/HelpText/HelpTextIcon.test.tsx delete mode 100644 packages/react/src/components/HelpText/HelpTextIcon.tsx diff --git a/.changeset/tame-rats-mix.md b/.changeset/tame-rats-mix.md new file mode 100644 index 0000000000..827c655035 --- /dev/null +++ b/.changeset/tame-rats-mix.md @@ -0,0 +1,9 @@ +--- +"@digdir/designsystemet-css": patch +"@digdir/designsystemet-react": patch +--- + +HelpText: +- Use Popover API +- Remove `portal` prop +- Render icon with pseudo element and require aria-label diff --git a/packages/css/button.css b/packages/css/button.css index bbd72ae8c6..6effbd8f56 100644 --- a/packages/css/button.css +++ b/packages/css/button.css @@ -142,8 +142,8 @@ */ @media (hover: hover) and (pointer: fine) { - /* Only use hover for non-touch devices to prevent sticky-hovering */ - &:not(:disabled, [aria-disabled='true'], [aria-busy='true']):hover { + /* Only use hover for non-touch devices to prevent sticky-hovering, using :where to prevent adding specificity */ + &:where(:not(:disabled, [aria-disabled='true'], [aria-busy='true'])):hover { background: var(--dsc-button-background--hover); } } @@ -157,7 +157,8 @@ opacity: var(--ds-disabled-opacity); } - &:not(:disabled, [aria-disabled='true'], [aria-busy='true']):active { + /* Using :where to prevent adding specificity */ + &:where(:not(:disabled, [aria-disabled='true'], [aria-busy='true'])):active { background-color: var(--dsc-button-background--active); } } diff --git a/packages/css/helptext.css b/packages/css/helptext.css index 0d3deaa9ff..604a3fe138 100644 --- a/packages/css/helptext.css +++ b/packages/css/helptext.css @@ -1,60 +1,48 @@ -.ds-helptext__button { - --dsc-helptext-color: var(--ds-color-neutral-text-default); - --dsc-helptext-icon-color: var(--ds-color-accent-base-default); - --dsc-helptext-icon-width: var(--ds-sizing-7); - --dsc-helptext-icon-height: var(--ds-sizing-7); +.ds-helptext { + --dsc-helptext-icon-size: 65%; + --dsc-helptext-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 8 14'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M4 11a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM4 0c2.2 0 4 1.66 4 3.7 0 .98-.42 1.9-1.17 2.6l-.6.54-.29.29c-.48.46-.87.93-.94 1.41V9.5H3v-.81c0-1.26.84-2.22 1.68-3L5.42 5C5.8 4.65 6 4.2 6 3.7 6 2.66 5.1 1.83 4 1.83c-1.06 0-1.92.75-2 1.7v.15H0C0 1.66 1.8 0 4 0Z' clip-rule='evenodd'/%3E%3C/svg%3E"); + --dsc-helptext-size: var(--ds-sizing-7); - background-color: transparent; - border-radius: 50px; - padding: 0 !important; - cursor: pointer; - display: flex; - border: none; - min-width: 0; - min-height: 0; -} + @composes ds-focus from '../css/utilities.css'; -@media print { - .ds-helptext__button { - display: none; + border-radius: var(--ds-border-radius-full); + border: 2px solid; + height: var(--dsc-helptext-size); + min-height: 0; + min-width: 0; + padding: 0; + position: relative; + width: var(--dsc-helptext-size); + + &::before { + content: ''; + border-radius: inherit; + background: currentcolor; + mask-composite: exclude; + mask-image: var(--dsc-helptext-icon-url); + mask-position: center; + mask-repeat: no-repeat; + mask-size: + var(--dsc-helptext-icon-size) var(--dsc-helptext-icon-size), + cover; + scale: 1.1; /* Hide tiny half pixel rendeing bug */ + width: 100%; + height: 100%; } -} - -.ds-helptext__icon--filled { - display: none; -} -.ds-helptext__icon { - color: var(--dsc-helptext-icon-color); - width: var(--dsc-helptext-icon-width); - height: var(--dsc-helptext-icon-height); -} - -.ds-helptext__button:where(:hover, :focus, [data-state^='open']) > .ds-helptext__icon { - display: none; -} - -.ds-helptext__button:where(:hover, :focus, [data-state^='open']) > .ds-helptext__icon--filled { - display: inline-block; -} - -.ds-helptext__content { - color: var(--dsc-helptext-color); - width: fit-content; - max-width: 700px; -} + &:has(+ :popover-open)::before { + mask-image: var(--dsc-helptext-icon-url), linear-gradient(currentcolor, currentcolor); /* Cut icon out of currentcolor surface */ + } -.ds-helptext--sm .ds-helptext__icon { - --dsc-helptext-icon-width: var(--ds-sizing-6); - --dsc-helptext-icon-height: var(--ds-sizing-6); -} + &[data-size='sm'] { + --dsc-helptext-size: var(--ds-sizing-6); + } -.ds-helptext--md .ds-helptext__icon { - --dsc-helptext-icon-width: var(--ds-sizing-7); - --dsc-helptext-icon-height: var(--ds-sizing-7); -} + &[data-size='lg'] { + --dsc-helptext-size: var(--ds-sizing-8); + } -.ds-helptext--lg .ds-helptext__icon { - --dsc-helptext-icon-width: var(--ds-sizing-8); - --dsc-helptext-icon-height: var(--ds-sizing-8); + @media print { + display: none; + } } diff --git a/packages/react/src/components/HelpText/HelpText.mdx b/packages/react/src/components/HelpText/HelpText.mdx index fd8b6e4375..b2369d2d81 100644 --- a/packages/react/src/components/HelpText/HelpText.mdx +++ b/packages/react/src/components/HelpText/HelpText.mdx @@ -8,9 +8,3 @@ import * as HelpTextStories from './HelpText.stories.tsx'; - -## Portal - -Aktiver `portal` dersom innholdet blir klippet av andre elementer for at `Popover` skal vises øverst. - - diff --git a/packages/react/src/components/HelpText/HelpText.stories.tsx b/packages/react/src/components/HelpText/HelpText.stories.tsx index 9325684ae7..cdab113f32 100644 --- a/packages/react/src/components/HelpText/HelpText.stories.tsx +++ b/packages/react/src/components/HelpText/HelpText.stories.tsx @@ -19,18 +19,9 @@ export default { export const Preview: Story = { args: { - title: 'Help text title', + 'aria-label': 'Help text title', children: 'Help text content', size: 'md', }, decorators, }; - -export const Portal: Story = { - args: { - title: 'Help text title', - children: 'Help text content', - size: 'md', - placement: 'top', - }, -}; diff --git a/packages/react/src/components/HelpText/HelpText.test.tsx b/packages/react/src/components/HelpText/HelpText.test.tsx index d2ed4db9d3..410f48e770 100644 --- a/packages/react/src/components/HelpText/HelpText.test.tsx +++ b/packages/react/src/components/HelpText/HelpText.test.tsx @@ -10,7 +10,7 @@ const render = (props: Partial = {}) => { ...props, }; renderRtl( - + Help , ); diff --git a/packages/react/src/components/HelpText/HelpText.tsx b/packages/react/src/components/HelpText/HelpText.tsx index f0cd0b439b..fcd96cde35 100644 --- a/packages/react/src/components/HelpText/HelpText.tsx +++ b/packages/react/src/components/HelpText/HelpText.tsx @@ -1,18 +1,15 @@ import type { Placement } from '@floating-ui/utils'; import cl from 'clsx/lite'; -import { forwardRef, useId, useState } from 'react'; +import { forwardRef } from 'react'; import type { ButtonHTMLAttributes } from 'react'; import { Popover, type PopoverProps } from '../Popover'; -import { Paragraph } from '../Typography/Paragraph'; - -import { HelpTextIcon } from './HelpTextIcon'; export type HelpTextProps = { /** - * Title for screen readers. + * Required descriptive label for screen readers. **/ - title: string; + 'aria-label': string; /** * Size of the helptext * @default md @@ -23,68 +20,26 @@ export type HelpTextProps = { * @default 'right' */ placement?: Placement; -} & ButtonHTMLAttributes; +} & Omit, 'color'>; export const HelpText = forwardRef( function HelpText( - { - title, - placement = 'right', - size = 'md', - className, - children, - ...rest - }: HelpTextProps, + { placement = 'right', size = 'md', className, children, ...rest }, ref, ) { - const [open, setOpen] = useState(false); - const randomId = useId(); - return ( - <> - - {/* TODO: Why is popover wrapped in paragraph here? */} - - setOpen(false)} - open={open} - placement={placement} - size={size} - variant='info' - > - {children} - - - + /> + + {children} + + ); }, ); diff --git a/packages/react/src/components/HelpText/HelpTextIcon.test.tsx b/packages/react/src/components/HelpText/HelpTextIcon.test.tsx deleted file mode 100644 index 4f40f72881..0000000000 --- a/packages/react/src/components/HelpText/HelpTextIcon.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { render as renderRtl, screen } from '@testing-library/react'; - -import type { HelpTextIconProps } from './HelpTextIcon'; -import { HelpTextIcon } from './HelpTextIcon'; - -// Test data: -const className = 'test-class'; -const defaultProps: HelpTextIconProps = { - className, - openState: true, -}; - -describe('HelpTextIcon', () => { - it('Renders an icon', () => { - render(); - expect(getIcon()).toBeInTheDocument(); - }); - - it('Renders with correct path when the `filled` property is `true`', () => { - render({ filled: true }); - const path = getIcon().firstChild; - expect(path).toHaveAttribute( - 'd', - expect.stringMatching( - /^M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0Zm0 16/, - ), - ); - }); - - it('Renders with correct path when the `filled` property is `false`', () => { - render({ filled: false }); - const path = getIcon().firstChild; - expect(path).toHaveAttribute( - 'd', - expect.stringMatching( - /^M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0Zm0 2C/, - ), - ); - }); - - it('Renders with given className', () => { - render({ className }); - expect(getIcon()).toHaveClass(className); - }); - - it('Renders with data-state="open" when `openState` is `true`', () => { - render({ openState: true }); - expect(getIcon()).toHaveAttribute('data-state', 'open'); - }); - - it('Renders with data-state="closed" when `openState` is `false`', () => { - render({ openState: false }); - expect(getIcon()).toHaveAttribute('data-state', 'closed'); - }); - - const getIcon = () => screen.getByRole('img', { hidden: true }); -}); - -const render = (props: Partial = {}) => - renderRtl(); diff --git a/packages/react/src/components/HelpText/HelpTextIcon.tsx b/packages/react/src/components/HelpText/HelpTextIcon.tsx deleted file mode 100644 index 9dd6b3c5a4..0000000000 --- a/packages/react/src/components/HelpText/HelpTextIcon.tsx +++ /dev/null @@ -1,31 +0,0 @@ -export type HelpTextIconProps = { - className: string; - filled?: boolean; - openState: boolean; -}; - -export const HelpTextIcon = ({ - className, - filled = false, - openState, -}: HelpTextIconProps) => { - const d = filled - ? 'M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0Zm0 16a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-11c2.205 0 4 1.657 4 3.693 0 .986-.416 1.914-1.172 2.612l-.593.54-.294.28c-.477.466-.869.94-.936 1.417l-.01.144v.814h-1.991v-.814c0-1.254.84-2.214 1.675-3.002l.74-.68c.38-.35.59-.816.59-1.31 0-1.024-.901-1.856-2.01-1.856-1.054 0-1.922.755-2.002 1.71l-.006.145H8C8 6.657 9.794 5 12 5Z' - : 'M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0Zm0 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm0 14a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-11c2.205 0 4 1.657 4 3.693 0 .986-.416 1.914-1.172 2.612l-.593.54-.294.28c-.477.466-.869.94-.936 1.417l-.01.144v.814h-1.991v-.814c0-1.254.84-2.214 1.675-3.002l.74-.68c.38-.35.59-.816.59-1.31 0-1.024-.901-1.856-2.01-1.856-1.054 0-1.922.755-2.002 1.71l-.006.145H8C8 6.657 9.794 5 12 5Z'; - - return ( - - - - ); -}; - -HelpTextIcon.displayName = 'HelpText.Icon';