From bc191e45c818d45e0fe110c43d81fbbffeadb268 Mon Sep 17 00:00:00 2001 From: barsnes Date: Mon, 7 Oct 2024 12:29:36 +0200 Subject: [PATCH 01/19] chore: add Une to codeowners --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 05eb4f2c6e..ee8e13fb19 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @mimarz @Barsnes @eirikbacker +* @mimarz @Barsnes @eirikbacker @unekinn From fe20145e53b3e72c47602270220d184cbd080aad Mon Sep 17 00:00:00 2001 From: Tobias Barsnes Date: Mon, 7 Oct 2024 16:18:21 +0200 Subject: [PATCH 02/19] chore(skiplink): simplify DOM (#2577) resolves #2574 --- .changeset/cyan-adults-roll.md | 8 +++++++ packages/css/skiplink.css | 11 +-------- .../components/SkipLink/SkipLink.stories.tsx | 12 ++++++---- .../src/components/SkipLink/SkipLink.tsx | 24 ++++++++----------- 4 files changed, 26 insertions(+), 29 deletions(-) create mode 100644 .changeset/cyan-adults-roll.md diff --git a/.changeset/cyan-adults-roll.md b/.changeset/cyan-adults-roll.md new file mode 100644 index 0000000000..a302adfd3a --- /dev/null +++ b/.changeset/cyan-adults-roll.md @@ -0,0 +1,8 @@ +--- +"@digdir/designsystemet-css": patch +"@digdir/designsystemet-react": patch +--- + +Skiplink: +- Simplify DOM +- Add support for `forwardRef` diff --git a/packages/css/skiplink.css b/packages/css/skiplink.css index 50d8ddf02b..f16e5ee23d 100644 --- a/packages/css/skiplink.css +++ b/packages/css/skiplink.css @@ -14,20 +14,11 @@ clip: auto; clip-path: none; white-space: inherit; - text-decoration: none; padding: var(--ds-spacing-4); - color: var(--ds-color-neutral-text-default); + color: var(--ds-color-accent-text-default); background: var(--ds-color-accent-surface-hover); box-sizing: border-box; -} - -.ds-skiplink__content { - color: var(--dsc-link-color); text-decoration: underline; text-decoration-thickness: max(1px, 0.0625rem); text-underline-offset: max(5px, 0.25rem); - display: inline-flex; - align-items: center; - gap: var(--ds-spacing-1); - margin: 0; } diff --git a/packages/react/src/components/SkipLink/SkipLink.stories.tsx b/packages/react/src/components/SkipLink/SkipLink.stories.tsx index 544f65854c..b05659c879 100644 --- a/packages/react/src/components/SkipLink/SkipLink.stories.tsx +++ b/packages/react/src/components/SkipLink/SkipLink.stories.tsx @@ -11,12 +11,14 @@ export default { } as Meta; export const Preview: Story = () => ( - - For å vise skiplinken, tab til dette eksempelet, eller klikk inni eksempelet - og trykk Tab. - Hopp til hovedinnhold + <> + + For å vise skiplinken, tab til dette eksempelet, eller klikk inni + eksempelet og trykk Tab. + Hopp til hovedinnhold +
Region som kan motta fokus fra skiplink.
-
+ ); diff --git a/packages/react/src/components/SkipLink/SkipLink.tsx b/packages/react/src/components/SkipLink/SkipLink.tsx index d500d2f6d8..d0e7c92e8e 100644 --- a/packages/react/src/components/SkipLink/SkipLink.tsx +++ b/packages/react/src/components/SkipLink/SkipLink.tsx @@ -1,5 +1,5 @@ import cl from 'clsx/lite'; -import type { AnchorHTMLAttributes, ReactNode } from 'react'; +import { type AnchorHTMLAttributes, type ReactNode, forwardRef } from 'react'; export type SkipLinkProps = { /** The content to display inside the skiplink. */ @@ -9,16 +9,12 @@ export type SkipLinkProps = { href: string; } & AnchorHTMLAttributes; -export const SkipLink = ({ - children, - className, - ...rest -}: SkipLinkProps): JSX.Element => { - return ( - -

{children}

-
- ); -}; - -SkipLink.displayName = 'SkipLink'; +export const SkipLink = forwardRef( + function SkipLink({ children, className, ...rest }) { + return ( + + {children} + + ); + }, +); From 7ad1485d4796c9092954a6d2221dffa434fd741f Mon Sep 17 00:00:00 2001 From: Eirik Backer Date: Tue, 8 Oct 2024 09:05:22 +0200 Subject: [PATCH 03/19] fix(Breadcrumbs): back only bug (#2564) Fixes #2563 --- packages/css/breadcrumbs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/css/breadcrumbs.css b/packages/css/breadcrumbs.css index a70b3b0838..b48bd8e7bb 100644 --- a/packages/css/breadcrumbs.css +++ b/packages/css/breadcrumbs.css @@ -62,7 +62,7 @@ } @media (min-width: 651px) { - & > :not(ol, ul) { + & > :is(:not(ol, ul)):not(:only-child) { display: none; /* Hide back link when desktop and having list */ } } From 9db9fd49c61d1354fd0928179db570e0eef18260 Mon Sep 17 00:00:00 2001 From: Tobias Barsnes Date: Tue, 8 Oct 2024 09:57:50 +0200 Subject: [PATCH 04/19] fix(skiplink): add missing ref prop (#2581) --- packages/react/src/components/SkipLink/SkipLink.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/SkipLink/SkipLink.tsx b/packages/react/src/components/SkipLink/SkipLink.tsx index d0e7c92e8e..d6788699d2 100644 --- a/packages/react/src/components/SkipLink/SkipLink.tsx +++ b/packages/react/src/components/SkipLink/SkipLink.tsx @@ -10,9 +10,9 @@ export type SkipLinkProps = { } & AnchorHTMLAttributes; export const SkipLink = forwardRef( - function SkipLink({ children, className, ...rest }) { + function SkipLink({ children, className, ...rest }, ref) { return ( - + {children} ); From bad8f495dd5657eb66cbf242f0f10cc61d19542f Mon Sep 17 00:00:00 2001 From: Eirik Backer Date: Tue, 8 Oct 2024 10:59:03 +0200 Subject: [PATCH 05/19] docs(Accordion): fix typo (#2582) Fixes typo in docs https://designsystemet.slack.com/archives/C05RF86A3K7/p1728035315902779 --- .../react/src/components/Accordion/AccordionHeading.tsx | 4 ++-- packages/react/src/components/Accordion/AccordionItem.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Accordion/AccordionHeading.tsx b/packages/react/src/components/Accordion/AccordionHeading.tsx index aa5dacfc8f..ece30e5878 100644 --- a/packages/react/src/components/Accordion/AccordionHeading.tsx +++ b/packages/react/src/components/Accordion/AccordionHeading.tsx @@ -7,9 +7,9 @@ export type AccordionHeadingProps = { } & HTMLAttributes; /** - * Accordion header component, contains a button to toggle the content. + * Accordion heading component, contains a button to toggle the content. * @example - * Header + * Heading */ export const AccordionHeading = forwardRef( function AccordionHeading({ className, ...rest }, ref) { diff --git a/packages/react/src/components/Accordion/AccordionItem.tsx b/packages/react/src/components/Accordion/AccordionItem.tsx index a5609dfe64..ecea56b65a 100644 --- a/packages/react/src/components/Accordion/AccordionItem.tsx +++ b/packages/react/src/components/Accordion/AccordionItem.tsx @@ -20,7 +20,7 @@ export type AccordionItemProps = { defaultOpen?: boolean; /** Callback function when AccordionItem toggles due to click on summary or find in page-search */ onToggle?: (event: Event) => void; - /** Content should be one `` and `` */ + /** Content should be one `` and `` */ children?: ReactNode; } & Omit, 'onToggle'> & ( @@ -29,10 +29,10 @@ export type AccordionItemProps = { ); /** - * Accordion item component, contains `Accordion.Header` and `Accordion.Content` components. + * Accordion item component, contains `Accordion.Heading` and `Accordion.Content` components. * @example * - * Header + * Header * Content * */ From ef73ac3005c451f9963b703b500363617bfa1241 Mon Sep 17 00:00:00 2001 From: Tobias Barsnes Date: Tue, 8 Oct 2024 11:29:47 +0200 Subject: [PATCH 06/19] chore(combobox): sync with main changes (#2576) this PR is only to reflect changes made in an older version in PR #2575 --- packages/react/src/components/form/Combobox/Combobox.tsx | 7 ++----- .../components/form/Combobox/internal/ComboboxInput.tsx | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react/src/components/form/Combobox/Combobox.tsx b/packages/react/src/components/form/Combobox/Combobox.tsx index eac27dde28..c790cec1d6 100644 --- a/packages/react/src/components/form/Combobox/Combobox.tsx +++ b/packages/react/src/components/form/Combobox/Combobox.tsx @@ -1,7 +1,7 @@ import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; import { useVirtualizer } from '@tanstack/react-virtual'; import cl from 'clsx/lite'; -import { forwardRef, useEffect, useId, useRef, useState } from 'react'; +import { forwardRef, useEffect, useRef, useState } from 'react'; import type { InputHTMLAttributes, ReactNode } from 'react'; import type { PortalProps } from '../../../types/Portal'; @@ -156,8 +156,6 @@ export const ComboboxComponent = forwardRef( const portalRef = useRef(null); const listRef = useRef>([]); - const listId = useId(); - const [inputValue, setInputValue] = useState(rest.inputValue || ''); useEffect(() => { @@ -396,7 +394,7 @@ export const ComboboxComponent = forwardRef( ( visuallyHiddenDismiss >
Date: Tue, 8 Oct 2024 13:12:00 +0200 Subject: [PATCH 07/19] fix(Modal): block (#2583) Fixes #2522 --------- Co-authored-by: Tobias Barsnes --- .changeset/nine-cameras-peel.md | 6 ++ .../_components/src/ColorModal/ColorModal.tsx | 8 +- packages/css/modal.css | 26 +++--- packages/react/src/components/Card/Card.mdx | 2 +- .../src/components/Modal/ModaContent.tsx | 28 ------- packages/react/src/components/Modal/Modal.mdx | 17 ++-- .../src/components/Modal/Modal.stories.tsx | 83 ++++++++++--------- .../react/src/components/Modal/Modal.test.tsx | 4 +- .../Modal/{ModalFooter.tsx => ModalBlock.tsx} | 8 +- .../src/components/Modal/ModalHeader.tsx | 26 ------ packages/react/src/components/Modal/index.ts | 14 ++-- 11 files changed, 87 insertions(+), 135 deletions(-) create mode 100644 .changeset/nine-cameras-peel.md delete mode 100644 packages/react/src/components/Modal/ModaContent.tsx rename packages/react/src/components/Modal/{ModalFooter.tsx => ModalBlock.tsx} (68%) delete mode 100644 packages/react/src/components/Modal/ModalHeader.tsx diff --git a/.changeset/nine-cameras-peel.md b/.changeset/nine-cameras-peel.md new file mode 100644 index 0000000000..189341970a --- /dev/null +++ b/.changeset/nine-cameras-peel.md @@ -0,0 +1,6 @@ +--- +"@digdir/designsystemet-css": patch +"@digdir/designsystemet-react": patch +--- + +Modal: Remove `Modal.Header` and `Modal.Footer`, replace with `Modal.Block` diff --git a/apps/_components/src/ColorModal/ColorModal.tsx b/apps/_components/src/ColorModal/ColorModal.tsx index da7bcce160..ffbf03cadf 100644 --- a/apps/_components/src/ColorModal/ColorModal.tsx +++ b/apps/_components/src/ColorModal/ColorModal.tsx @@ -59,10 +59,10 @@ export const ColorModal = ({ maxWidth: '1050px', }} > - + {`${capitalizeFirstLetter(namespace)} ${capitalizeFirstLetter(getColorNameFromNumber(weight))}`} - -
+ +
{getColorDescription({ weight, @@ -120,7 +120,7 @@ export const ColorModal = ({ */} -
+
); diff --git a/packages/css/modal.css b/packages/css/modal.css index 83c4773665..6c0129239b 100644 --- a/packages/css/modal.css +++ b/packages/css/modal.css @@ -6,8 +6,8 @@ --dsc-modal-divider: 1px solid var(--ds-color-neutral-border-subtle); --dsc-modal-max-height: 80vh; --dsc-modal-max-width: 40rem; - --dsc-modal-padding-invert: calc(var(--dsc-modal-padding) * -1); --dsc-modal-padding: var(--ds-spacing-6); + --close-top-right: calc(var(--dsc-modal-padding) * -1 + var(--dsc-modal-close-margin)); background: var(--dsc-modal-background); border-radius: min(1rem, var(--ds-border-radius-lg)); @@ -31,6 +31,12 @@ fade-in 300ms ease-in-out; } + &:has(> .ds-modal__block) { + --close-top-right: var(--dsc-modal-close-margin); + + padding: 0; /* Let Modal.Block own the padding */ + } + @media (max-width: 40rem) { min-width: 100%; max-width: 100%; @@ -46,10 +52,8 @@ /* Close button */ & > form[method='dialog']:first-child > button:only-child { - --margin-top-right: calc(var(--dsc-modal-padding-invert) + var(--dsc-modal-close-margin)); - float: right; - margin: var(--margin-top-right) var(--margin-top-right) var(--dsc-modal-close-margin) var(--dsc-modal-close-margin); + margin: var(--close-top-right) var(--close-top-right) var(--dsc-modal-close-margin) var(--dsc-modal-close-margin); color: inherit; &::before { @@ -63,18 +67,12 @@ } } -.ds-modal__header { - border-bottom: var(--dsc-modal-divider); - margin-block: var(--dsc-modal-padding-invert) var(--dsc-modal-padding); - margin-inline: var(--dsc-modal-padding-invert); +.ds-modal__block { padding: var(--dsc-modal-padding); -} -.ds-modal__footer { - border-top: var(--dsc-modal-divider); - margin-block: var(--dsc-modal-padding) var(--dsc-modal-padding-invert); - margin-inline: var(--dsc-modal-padding-invert); - padding: var(--dsc-modal-padding); + & + & { + border-top: var(--dsc-modal-divider); + } } /* Prevent scroll when open */ diff --git a/packages/react/src/components/Card/Card.mdx b/packages/react/src/components/Card/Card.mdx index 3d8ad2e4cc..8ea730266d 100644 --- a/packages/react/src/components/Card/Card.mdx +++ b/packages/react/src/components/Card/Card.mdx @@ -26,7 +26,7 @@ import { Card } from '@digdir/designsystemet-react';
-### Card.Block +### Med `Block` Bruk flere `Card.Block` hvis du vil dele opp kortet med skillelinjer eller legge inn utfallende bilder eller video. Merk at innhold kan ikke plasseres direkte i `Card` dersom du bruker `Card.Block`; da burde alt innholdet være inni en av av kortets `Card.Block`-seksjoner. diff --git a/packages/react/src/components/Modal/ModaContent.tsx b/packages/react/src/components/Modal/ModaContent.tsx deleted file mode 100644 index 4ac56d5393..0000000000 --- a/packages/react/src/components/Modal/ModaContent.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Slot } from '@radix-ui/react-slot'; -import cl from 'clsx/lite'; -import { forwardRef } from 'react'; -import type { HTMLAttributes } from 'react'; - -export type ModalContentProps = { - /** - * Change the default rendered element for the one passed as a child, merging their props and behavior. - * @default false - */ - asChild?: boolean; -} & HTMLAttributes; - -export const ModalContent = forwardRef( - ({ asChild, className, ...rest }, ref) => { - const Component = asChild ? Slot : 'div'; - - return ( - - ); - }, -); - -ModalContent.displayName = 'ModalContent'; diff --git a/packages/react/src/components/Modal/Modal.mdx b/packages/react/src/components/Modal/Modal.mdx index ad4c089768..3d2cfcc26d 100644 --- a/packages/react/src/components/Modal/Modal.mdx +++ b/packages/react/src/components/Modal/Modal.mdx @@ -23,11 +23,11 @@ Les [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) Open Modal - + Header - - Content - Footer + + Content + Footer ``` @@ -57,9 +57,10 @@ Vi bruker `backdropClose={true}` proppen for å lukke modalen når brukeren klik -### Med `Header` og `Footer` +### Med `Block` -Du kan legge til `Modal.Header` og `Modal.Footer` for å få på topp- og bunn-område med sikkelinje. +Bruk flere `Modal.Block` hvis du vil dele opp modalen med skillelinjer til for eksempel topp- og bunn-område. +Merk at innhold kan ikke plasseres direkte i `Modal` dersom du bruker `Modal.Block`; da burde alt innholdet være inni en av av modalens `Modal.Block`-seksjoner. @@ -81,6 +82,6 @@ Bruk `overflow: visible` for å la innhold gå utenfor modalen. -### `Modal.Header` +### `Modal.Block` - + diff --git a/packages/react/src/components/Modal/Modal.stories.tsx b/packages/react/src/components/Modal/Modal.stories.tsx index 63f3442cc3..3fb4f15aac 100644 --- a/packages/react/src/components/Modal/Modal.stories.tsx +++ b/packages/react/src/components/Modal/Modal.stories.tsx @@ -80,32 +80,35 @@ export const WithHeaderAndFooter: StoryFn = () => ( Open Modal - + Her er det også divider Vi kan legge divider under header - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur - sodales eros justo. Aenean non mi ipsum. Cras viverra elit nec vulputate - mattis. Nunc placerat euismod pulvinar. Sed nec fringilla nulla, sit - amet ultricies ante. Morbi egestas venenatis massa, eu interdum leo - rutrum eu. Nulla varius, mi ac feugiat lacinia, magna eros ullamcorper - arcu, vel tincidunt erat leo nec tortor. Sed ut dui arcu. Morbi commodo - ipsum hendrerit est imperdiet imperdiet. Etiam sed maximus nisi. Quisque - posuere posuere orci, non egestas risus facilisis a. Vivamus non tempus - felis, in maximus lorem. Class aptent taciti sociosqu ad litora torquent - per conubia nostra, per inceptos himenaeos. - - - Etiam nec tincidunt est. Integer semper sodales efficitur. Pellentesque - pellentesque varius leo id congue. Integer lacinia porttitor massa id - euismod. Maecenas porta, magna nec interdum eleifend, risus magna - condimentum neque, a gravida nisl risus a elit. Donec accumsan metus et - lectus placerat varius. Donec tristique odio arcu. Donec cursus leo a - dui auctor pulvinar. Sed in elit urna. Nunc vitae magna sed nibh - elementum dignissim et ut massa. - - Og over footer + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur + sodales eros justo. Aenean non mi ipsum. Cras viverra elit nec + vulputate mattis. Nunc placerat euismod pulvinar. Sed nec fringilla + nulla, sit amet ultricies ante. Morbi egestas venenatis massa, eu + interdum leo rutrum eu. Nulla varius, mi ac feugiat lacinia, magna + eros ullamcorper arcu, vel tincidunt erat leo nec tortor. Sed ut dui + arcu. Morbi commodo ipsum hendrerit est imperdiet imperdiet. Etiam sed + maximus nisi. Quisque posuere posuere orci, non egestas risus + facilisis a. Vivamus non tempus felis, in maximus lorem. Class aptent + taciti sociosqu ad litora torquent per conubia nostra, per inceptos + himenaeos. + + + Etiam nec tincidunt est. Integer semper sodales efficitur. + Pellentesque pellentesque varius leo id congue. Integer lacinia + porttitor massa id euismod. Maecenas porta, magna nec interdum + eleifend, risus magna condimentum neque, a gravida nisl risus a elit. + Donec accumsan metus et lectus placerat varius. Donec tristique odio + arcu. Donec cursus leo a dui auctor pulvinar. Sed in elit urna. Nunc + vitae magna sed nibh elementum dignissim et ut massa. + + + Og over footer ); @@ -175,28 +178,30 @@ export const ModalWithCombobox: StoryFn = () => { Open Modal - + Modal med combobox - - - Fant ingen treff - Leikanger - Oslo - Brønnøysund - Stavanger - Trondheim - Tromsø - Bergen - Mo i Rana - - + + + + Fant ingen treff + Leikanger + Oslo + Brønnøysund + Stavanger + Trondheim + Tromsø + Bergen + Mo i Rana + + + - + diff --git a/packages/react/src/components/Modal/Modal.test.tsx b/packages/react/src/components/Modal/Modal.test.tsx index 1c3262b8e9..e7bf3ac8af 100644 --- a/packages/react/src/components/Modal/Modal.test.tsx +++ b/packages/react/src/components/Modal/Modal.test.tsx @@ -40,7 +40,7 @@ describe('Modal', () => { const { user } = await render({ children: ( <> - {HEADER_TITLE} + {HEADER_TITLE} ), }); @@ -99,7 +99,7 @@ describe('Modal', () => { open: true, children: ( <> - {HEADER_TITLE} + {HEADER_TITLE} ), }); diff --git a/packages/react/src/components/Modal/ModalFooter.tsx b/packages/react/src/components/Modal/ModalBlock.tsx similarity index 68% rename from packages/react/src/components/Modal/ModalFooter.tsx rename to packages/react/src/components/Modal/ModalBlock.tsx index 955c8c5413..3f044e9474 100644 --- a/packages/react/src/components/Modal/ModalFooter.tsx +++ b/packages/react/src/components/Modal/ModalBlock.tsx @@ -3,7 +3,7 @@ import cl from 'clsx/lite'; import type { HTMLAttributes } from 'react'; import { forwardRef } from 'react'; -export type ModalFooterProps = { +export type ModalBlockProps = { /** * Change the default rendered element for the one passed as a child, merging their props and behavior. * @default false @@ -11,13 +11,13 @@ export type ModalFooterProps = { asChild?: boolean; } & HTMLAttributes; -export const ModalFooter = forwardRef( - function ModalFooter({ asChild, className, ...rest }, ref) { +export const ModalBlock = forwardRef( + function ModalBlock({ asChild, className, ...rest }, ref) { const Component = asChild ? Slot : 'div'; return ( diff --git a/packages/react/src/components/Modal/ModalHeader.tsx b/packages/react/src/components/Modal/ModalHeader.tsx deleted file mode 100644 index 8d3de83406..0000000000 --- a/packages/react/src/components/Modal/ModalHeader.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Slot } from '@radix-ui/react-slot'; -import cl from 'clsx/lite'; -import type { HTMLAttributes } from 'react'; -import { forwardRef } from 'react'; - -export type ModalHeaderProps = { - /** - * Change the default rendered element for the one passed as a child, merging their props and behavior. - * @default false - */ - asChild?: boolean; -} & HTMLAttributes; - -export const ModalHeader = forwardRef( - function ModalHeader({ className, asChild, ...rest }, ref) { - const Component = asChild ? Slot : 'div'; - - return ( - - ); - }, -); diff --git a/packages/react/src/components/Modal/index.ts b/packages/react/src/components/Modal/index.ts index 996db95df2..9dfbff06ef 100644 --- a/packages/react/src/components/Modal/index.ts +++ b/packages/react/src/components/Modal/index.ts @@ -1,24 +1,20 @@ import { Modal as ModalParent } from './Modal'; +import { ModalBlock } from './ModalBlock'; import { ModalContext } from './ModalContext'; -import { ModalFooter } from './ModalFooter'; -import { ModalHeader } from './ModalHeader'; import { ModalTrigger } from './ModalTrigger'; const Modal = Object.assign(ModalParent, { + Block: ModalBlock, Context: ModalContext, - Footer: ModalFooter, - Header: ModalHeader, Trigger: ModalTrigger, }); +Modal.Block.displayName = 'Modal.Block'; Modal.Context.displayName = 'Modal.Context'; -Modal.Footer.displayName = 'Modal.Footer'; -Modal.Header.displayName = 'Modal.Header'; Modal.Trigger.displayName = 'Modal.Trigger'; +export type { ModalBlockProps } from './ModalBlock'; export type { ModalContextProps } from './ModalContext'; -export type { ModalFooterProps } from './ModalFooter'; -export type { ModalHeaderProps } from './ModalHeader'; export type { ModalProps } from './Modal'; export type { ModalTriggerProps } from './ModalTrigger'; -export { Modal, ModalContext, ModalFooter, ModalHeader, ModalTrigger }; +export { Modal, ModalBlock, ModalContext, ModalTrigger }; From 735eb8886228d88735d60404af1eba5265f9e727 Mon Sep 17 00:00:00 2001 From: Eirik Backer Date: Tue, 8 Oct 2024 13:25:23 +0200 Subject: [PATCH 08/19] feat(Skeleton): text wrapping (#2569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes [#2518](https://github.com/digdir/designsystemet/issues/2518) - Discussed with design ✅ --- packages/css/skeleton.css | 22 ++----- .../components/loaders/Skeleton/Skeleton.mdx | 4 +- .../loaders/Skeleton/Skeleton.stories.tsx | 64 +++++++++---------- .../components/loaders/Skeleton/Skeleton.tsx | 15 ++++- 4 files changed, 51 insertions(+), 54 deletions(-) diff --git a/packages/css/skeleton.css b/packages/css/skeleton.css index 1f6b1e1acc..e1cd7f90f7 100644 --- a/packages/css/skeleton.css +++ b/packages/css/skeleton.css @@ -19,30 +19,18 @@ } &[data-variant='text'] { - animation: none; border-radius: var(--ds-border-radius-full); - height: auto; - position: relative; /* So we can position ::after */ - - /* Render with ::after so we can add some space to top and bottom */ - &::after { - content: ''; - animation: var(--dsc-skeleton-animation); - border-radius: inherit; - inset: 20% 0; - position: absolute; - } - - &:empty::before { - content: '\00a0'; /* Add non breaking space if empty so we follow line height */ - } + box-decoration-break: clone; + display: inline; + font-size: 0.8em; /* Scale down font to have larger gap between lines */ + letter-spacing: 0.1em; /* But scale up letter-spacing to have circa same line-length */ } /* When having children, let them define size */ &:not(:empty) { width: fit-content; height: fit-content; - color: transparent !important; + color: transparent; & > * { visibility: hidden; diff --git a/packages/react/src/components/loaders/Skeleton/Skeleton.mdx b/packages/react/src/components/loaders/Skeleton/Skeleton.mdx index 3bb9fa4b8b..e02f1c5356 100644 --- a/packages/react/src/components/loaders/Skeleton/Skeleton.mdx +++ b/packages/react/src/components/loaders/Skeleton/Skeleton.mdx @@ -23,7 +23,7 @@ Du kan bygge opp komponenter og seksjoner av siden din ved å bruke disse som by ### Skalering av komponenten Alle varianter av skeleton har `height` og `width` props, som kan brukes til å manuelt sette størrelser. Du kan oppgi størrelsene i `px`, `%`, eller andre enheter som kan settes direkte på style. -For `Skeleton variant="text"` holder det ofte å sette kun `width`, da høyden automatisk skaleres etter tekst-størrelsen til `parent`-elementet. +For `Skeleton variant="text"` holder det ofte å sette kun `width` til det antallet bokstaver du antar vil oppstå, da høyden automatisk skaleres etter tekstinnholdet. I de fleste tilfeller er manuell setting av høyde og bredde nok, men du kan også sette andre elementer til å rendres som skeleton, gjennom å bruke propen `asChild`. Dette er hovedsaklig tenkt brukt for typografi komponenter. @@ -36,6 +36,6 @@ Skeleton vil også tilpasse seg etter `children` som du sender inn til komponent ## Text `Skeleton variant="text"` skalerer automatisk etter den lokale fontstørrelsen, enten den kommer fra `parent`, `children` , eller fordi typografi-komponenter er satt til Skeletons gjennom `asChild`-propen. -For best mulig resultat anbefaler vi at du bruker flere `Skeleton variant="text"` komponenter for å representere en blokk med tekst, én for hver linje. +For best mulig resultat anbefaler vi at du bruker flere `Skeleton variant="text"` med `width` til det antallet bokstaver du antar vil oppstå. diff --git a/packages/react/src/components/loaders/Skeleton/Skeleton.stories.tsx b/packages/react/src/components/loaders/Skeleton/Skeleton.stories.tsx index c6509e4c87..9582694281 100644 --- a/packages/react/src/components/loaders/Skeleton/Skeleton.stories.tsx +++ b/packages/react/src/components/loaders/Skeleton/Skeleton.stories.tsx @@ -9,6 +9,14 @@ type Story = StoryObj; export default { title: 'Komponenter/Loaders/Skeleton', component: Skeleton, + parameters: { + a11y: { + config: { + // Disable a11y empty heading rule as we intentionally set aria-hidden="true" on the Skeleton component inside Headings + rules: [{ id: 'empty-heading', selector: ':has(.ds-skeleton)' }], + }, + }, + }, } as Meta; export const Preview: Story = { @@ -29,7 +37,7 @@ export const Components: StoryFn = () => { > - +
); }; @@ -51,13 +59,11 @@ export const UsageExample: StoryFn = () => { }} > - + En medium tittel
- - - + ); }; @@ -79,16 +85,16 @@ export const Children: StoryFn = () => { export const As: StoryFn = () => { return ( <> - + Her er en heading - + Her er en paragraf-komponent som blir rendret som en Skeleton variant="text". - + Se hvordan Skeleton da overskriver stylingen til det enkelte elementet. @@ -98,27 +104,21 @@ export const As: StoryFn = () => { ); }; -export const TextExample: StoryFn = () => { - return ( - <> -
-
- Heading - - Her er en paragraf som går over flere linjer - -
-
- - Heading - - - - - - -
-
- - ); -}; +export const TextExample: StoryFn = () => ( +
+
+ Heading + + Her er en paragraf som går over flere linjer + +
+
+ + Heading + + + + +
+
+); diff --git a/packages/react/src/components/loaders/Skeleton/Skeleton.tsx b/packages/react/src/components/loaders/Skeleton/Skeleton.tsx index ae1eeabaf4..e8573164ff 100644 --- a/packages/react/src/components/loaders/Skeleton/Skeleton.tsx +++ b/packages/react/src/components/loaders/Skeleton/Skeleton.tsx @@ -20,13 +20,18 @@ export type SkeletonProps = { * @default 'rectangle' * */ variant?: 'rectangle' | 'circle' | 'text'; -} & HTMLAttributes; +} & HTMLAttributes & + ( + | { variant: 'text'; characters?: number } + | { variant?: 'rectangle' | 'circle'; characters?: never } + ); export const Skeleton = forwardRef( function Skeleton( { asChild, className, + children, height, style, variant = 'rectangle', @@ -36,6 +41,8 @@ export const Skeleton = forwardRef( ref, ) { const Component = asChild ? Slot : 'span'; + const isText = variant === 'text'; + const childrenText = isText && '-'.repeat(Number(width) || 1); // s followed by a ­ makes the most average character length const animationRef = useSynchronizedAnimation( 'ds-skeleton-opacity-fade', ); @@ -47,9 +54,11 @@ export const Skeleton = forwardRef( className={cl('ds-skeleton', className)} data-variant={variant} ref={mergedRefs} - style={{ width, height, ...style }} + style={isText ? style : { width, height, ...style }} {...rest} - /> + > + {children || childrenText} + ); }, ); From 8d3a3c571c85236ec8a5a514fa4070b8b72ebf56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:32:02 +0200 Subject: [PATCH 09/19] chore: new release candidate (next) (#2486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated. ⚠️⚠️⚠️⚠️⚠️⚠️ `next` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run `changeset pre exit` on `next`. ⚠️⚠️⚠️⚠️⚠️⚠️ # Releases ## @digdir/designsystemet@1.0.0-next.35 ### Minor Changes - Fix crash when running CLI command `tokens build`: ([#2549](https://github.com/digdir/designsystemet/pull/2549)) - add --verbose option to `tokens build` for easier debugging - `tokens build` crashed when run on result of `tokens create` Update tokens template used by CLI command `tokens create` - removes `ingress`, renames `paragraph` to `body`, and adds `xl` size ### Patch Changes - Make sure the internal order of sections in the CSS generated by the CLI is deterministic, to avoid unnecessary git diffs ([#2560](https://github.com/digdir/designsystemet/pull/2560)) ## @digdir/designsystemet-css@1.0.0-next.35 ### Patch Changes - Pagination: ([#2460](https://github.com/digdir/designsystemet/pull/2460)) - Remove attributes `currentPage` and `totalPages` on `Pagination` - Replace `Pagination.Root` with `Paginaton` - Replace `Pagination.Next`, `Pagination.Previous` and `Pagination.Ellipsis` with `Paginaton.Button` - Make `usePagination` return spreadable props for subcomponents - Add support for `showPages` and `onChange` in `usePagination` - Skiplink: ([#2577](https://github.com/digdir/designsystemet/pull/2577)) - Simplify DOM - Add support for `forwardRef` - Accordion: Animate open/close with CSS ([#2527](https://github.com/digdir/designsystemet/pull/2527)) - Replace onFound with onToggle - Heading: ([#2525](https://github.com/digdir/designsystemet/pull/2525)) - Classes with data attributes - Move base style to utility classes - Modal: ([#2440](https://github.com/digdir/designsystemet/pull/2440)) - Rename `Modal.Dialog` to `Modal` - Rename `Modal.Root` to `Modal.Context` - Replace `onInteractOutside` event with `backdropClose` boolean - Replace `closeButton` and `closeButtonTitle` on `Modal.Header` with `closeButton` on `Modal` - Add border to `Modal.Header` and `Modal.Footer` - Remove `Modal.Content` - Remove `onBeforeClose` - Remove `subtitle` from `Modal.Header` - Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) - Add body-xl token - Add xl paragraph - Remove ingress tokens - Ingress: Remove component ([#2515](https://github.com/digdir/designsystemet/pull/2515)) - Use `Paragraph variant='long'` instead - Heading: Fix `md` heading size ([#2485](https://github.com/digdir/designsystemet/pull/2485)) - Card: ([#2509](https://github.com/digdir/designsystemet/pull/2509)) - Allow `Card` with content placed directly inside - Replace `Card.Header`, `Card.Content` and `Card.Footer` with `Card.Block` - Replace `isLink` with anchor-in-heading + `click` handler for better accessibility - Modal: Remove `Modal.Header` and `Modal.Footer`, replace with `Modal.Block` ([#2583](https://github.com/digdir/designsystemet/pull/2583)) - SkipLink: Remove ds-sr-only class ([#2546](https://github.com/digdir/designsystemet/pull/2546)) - Paragraph: Add css classes and style with data attributes ([#2523](https://github.com/digdir/designsystemet/pull/2523)) - Chip: ([#2493](https://github.com/digdir/designsystemet/pull/2493)) - Add `Chip.Button` - Rename `Chip.Toggle` to `Chip.Radio` and `Chip.Checkbox` - Remove `Chip.Group` ## @digdir/designsystemet-react@1.0.0-next.35 ### Patch Changes - Pagination: ([#2460](https://github.com/digdir/designsystemet/pull/2460)) - Remove attributes `currentPage` and `totalPages` on `Pagination` - Replace `Pagination.Root` with `Paginaton` - Replace `Pagination.Next`, `Pagination.Previous` and `Pagination.Ellipsis` with `Paginaton.Button` - Make `usePagination` return spreadable props for subcomponents - Add support for `showPages` and `onChange` in `usePagination` - Skiplink: ([#2577](https://github.com/digdir/designsystemet/pull/2577)) - Simplify DOM - Add support for `forwardRef` - Accordion: Animate open/close with CSS ([#2527](https://github.com/digdir/designsystemet/pull/2527)) - Replace onFound with onToggle - Heading: ([#2525](https://github.com/digdir/designsystemet/pull/2525)) - Classes with data attributes - Move base style to utility classes - Modal: ([#2440](https://github.com/digdir/designsystemet/pull/2440)) - Rename `Modal.Dialog` to `Modal` - Rename `Modal.Root` to `Modal.Context` - Replace `onInteractOutside` event with `backdropClose` boolean - Replace `closeButton` and `closeButtonTitle` on `Modal.Header` with `closeButton` on `Modal` - Add border to `Modal.Header` and `Modal.Footer` - Remove `Modal.Content` - Remove `onBeforeClose` - Remove `subtitle` from `Modal.Header` - Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) - Add body-xl token - Add xl paragraph - Remove ingress tokens - Ingress: Remove component ([#2515](https://github.com/digdir/designsystemet/pull/2515)) - Use `Paragraph variant='long'` instead - Card: ([#2509](https://github.com/digdir/designsystemet/pull/2509)) - Allow `Card` with content placed directly inside - Replace `Card.Header`, `Card.Content` and `Card.Footer` with `Card.Block` - Replace `isLink` with anchor-in-heading + `click` handler for better accessibility - Modal: Remove `Modal.Header` and `Modal.Footer`, replace with `Modal.Block` ([#2583](https://github.com/digdir/designsystemet/pull/2583)) - SkipLink: Remove ds-sr-only class ([#2546](https://github.com/digdir/designsystemet/pull/2546)) - Paragraph: Add css classes and style with data attributes ([#2523](https://github.com/digdir/designsystemet/pull/2523)) - Chip: ([#2493](https://github.com/digdir/designsystemet/pull/2493)) - Add `Chip.Button` - Rename `Chip.Toggle` to `Chip.Radio` and `Chip.Checkbox` - Remove `Chip.Group` ## @digdir/designsystemet-theme@1.0.0-next.35 ### Patch Changes - Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) - Add body-xl token - Add xl paragraph - Remove ingress tokens Co-authored-by: github-actions[bot] --- .changeset/pre.json | 17 +++++++++- packages/cli/CHANGELOG.md | 17 ++++++++++ packages/cli/package.json | 2 +- packages/css/CHANGELOG.md | 66 +++++++++++++++++++++++++++++++++++++ packages/css/package.json | 2 +- packages/react/CHANGELOG.md | 64 +++++++++++++++++++++++++++++++++++ packages/react/package.json | 2 +- packages/theme/CHANGELOG.md | 9 +++++ packages/theme/package.json | 2 +- 9 files changed, 176 insertions(+), 5 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index aff91c3a94..1981d4b915 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -15,6 +15,7 @@ "@designsystemet/storybook": "0.1.0" }, "changesets": [ + "angry-planes-thank", "beige-grapes-report", "blue-rocks-pull", "blue-singers-switch", @@ -27,12 +28,14 @@ "clever-cobras-rescue", "cool-lamps-drive", "curvy-oranges-notice", + "cyan-adults-roll", "eighty-cougars-think", "eleven-bags-shop", "eleven-peaches-agree", "empty-singers-yell", "famous-pillows-cheat", "few-brooms-confess", + "few-plums-drum", "fifty-buses-beam", "five-apricots-scream", "flat-experts-drop", @@ -40,17 +43,24 @@ "friendly-islands-punch", "gold-chairs-jog", "gorgeous-readers-burn", + "gorgeous-shrimps-crash", "happy-hounds-tie", "happy-worms-applaud", "healthy-apples-explode", "heavy-rabbits-boil", "hip-masks-greet", + "hip-schools-greet", "honest-roses-hunt", + "hot-ligers-rush", + "hot-weeks-tease", + "lemon-countries-smoke", "long-boxes-sniff", "loud-tips-return", "mean-ducks-argue", "metal-bananas-notice", + "metal-tomatoes-compete", "mighty-days-eat", + "nine-cameras-peel", "odd-hornets-sleep", "plenty-parents-rest", "plenty-vans-sneeze", @@ -66,13 +76,17 @@ "shaggy-rockets-repair", "shiny-kiwis-switch", "short-walls-judge", + "six-carrots-guess", "six-trees-tie", "slimy-bees-arrive", "small-queens-breathe", "spotty-oranges-guess", "spotty-pumas-cross", + "strong-flowers-ring", + "strong-ghosts-marry", "stupid-tables-applaud", "swift-forks-drop", + "tall-guests-arrive", "tame-rats-mix", "tender-grapes-refuse", "tender-ties-swim", @@ -82,6 +96,7 @@ "three-ducks-chew", "wise-countries-double", "witty-clouds-judge", - "witty-moons-sleep" + "witty-moons-sleep", + "yellow-zoos-camp" ] } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 9a8ca61127..cbbc5709fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## 1.0.0-next.35 + +### Minor Changes + +- Fix crash when running CLI command `tokens build`: ([#2549](https://github.com/digdir/designsystemet/pull/2549)) + + - add --verbose option to `tokens build` for easier debugging + - `tokens build` crashed when run on result of `tokens create` + + Update tokens template used by CLI command `tokens create` + + - removes `ingress`, renames `paragraph` to `body`, and adds `xl` size + +### Patch Changes + +- Make sure the internal order of sections in the CSS generated by the CLI is deterministic, to avoid unnecessary git diffs ([#2560](https://github.com/digdir/designsystemet/pull/2560)) + ## 1.0.0-next.34 ## 1.0.0-next.33 diff --git a/packages/cli/package.json b/packages/cli/package.json index 81b2782ee9..3e75826117 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@digdir/designsystemet", - "version": "1.0.0-next.34", + "version": "1.0.0-next.35", "description": "CLI for Designsystemet", "author": "Designsystemet team", "repository": { diff --git a/packages/css/CHANGELOG.md b/packages/css/CHANGELOG.md index 8c2d055db6..87d0e0f66d 100644 --- a/packages/css/CHANGELOG.md +++ b/packages/css/CHANGELOG.md @@ -1,5 +1,71 @@ # Change Log +## 1.0.0-next.35 + +### Patch Changes + +- Pagination: ([#2460](https://github.com/digdir/designsystemet/pull/2460)) + + - Remove attributes `currentPage` and `totalPages` on `Pagination` + - Replace `Pagination.Root` with `Paginaton` + - Replace `Pagination.Next`, `Pagination.Previous` and `Pagination.Ellipsis` with `Paginaton.Button` + - Make `usePagination` return spreadable props for subcomponents + - Add support for `showPages` and `onChange` in `usePagination` + +- Skiplink: ([#2577](https://github.com/digdir/designsystemet/pull/2577)) + + - Simplify DOM + - Add support for `forwardRef` + +- Accordion: Animate open/close with CSS ([#2527](https://github.com/digdir/designsystemet/pull/2527)) + + - Replace onFound with onToggle + +- Heading: ([#2525](https://github.com/digdir/designsystemet/pull/2525)) + + - Classes with data attributes + - Move base style to utility classes + +- Modal: ([#2440](https://github.com/digdir/designsystemet/pull/2440)) + + - Rename `Modal.Dialog` to `Modal` + - Rename `Modal.Root` to `Modal.Context` + - Replace `onInteractOutside` event with `backdropClose` boolean + - Replace `closeButton` and `closeButtonTitle` on `Modal.Header` with `closeButton` on `Modal` + - Add border to `Modal.Header` and `Modal.Footer` + - Remove `Modal.Content` + - Remove `onBeforeClose` + - Remove `subtitle` from `Modal.Header` + +- Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) + + - Add body-xl token + - Add xl paragraph + - Remove ingress tokens + +- Ingress: Remove component ([#2515](https://github.com/digdir/designsystemet/pull/2515)) + + - Use `Paragraph variant='long'` instead + +- Heading: Fix `md` heading size ([#2485](https://github.com/digdir/designsystemet/pull/2485)) + +- Card: ([#2509](https://github.com/digdir/designsystemet/pull/2509)) + + - Allow `Card` with content placed directly inside + - Replace `Card.Header`, `Card.Content` and `Card.Footer` with `Card.Block` + - Replace `isLink` with anchor-in-heading + `click` handler for better accessibility + +- Modal: Remove `Modal.Header` and `Modal.Footer`, replace with `Modal.Block` ([#2583](https://github.com/digdir/designsystemet/pull/2583)) + +- SkipLink: Remove ds-sr-only class ([#2546](https://github.com/digdir/designsystemet/pull/2546)) + +- Paragraph: Add css classes and style with data attributes ([#2523](https://github.com/digdir/designsystemet/pull/2523)) + +- Chip: ([#2493](https://github.com/digdir/designsystemet/pull/2493)) + - Add `Chip.Button` + - Rename `Chip.Toggle` to `Chip.Radio` and `Chip.Checkbox` + - Remove `Chip.Group` + ## 1.0.0-next.34 ### Patch Changes diff --git a/packages/css/package.json b/packages/css/package.json index 75849619d8..60d8b5140e 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@digdir/designsystemet-css", - "version": "1.0.0-next.34", + "version": "1.0.0-next.35", "description": "CSS for Designsystemet", "author": "Designsystemet team", "repository": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index e480329fe2..0d8f21d62f 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,69 @@ # Change Log +## 1.0.0-next.35 + +### Patch Changes + +- Pagination: ([#2460](https://github.com/digdir/designsystemet/pull/2460)) + + - Remove attributes `currentPage` and `totalPages` on `Pagination` + - Replace `Pagination.Root` with `Paginaton` + - Replace `Pagination.Next`, `Pagination.Previous` and `Pagination.Ellipsis` with `Paginaton.Button` + - Make `usePagination` return spreadable props for subcomponents + - Add support for `showPages` and `onChange` in `usePagination` + +- Skiplink: ([#2577](https://github.com/digdir/designsystemet/pull/2577)) + + - Simplify DOM + - Add support for `forwardRef` + +- Accordion: Animate open/close with CSS ([#2527](https://github.com/digdir/designsystemet/pull/2527)) + + - Replace onFound with onToggle + +- Heading: ([#2525](https://github.com/digdir/designsystemet/pull/2525)) + + - Classes with data attributes + - Move base style to utility classes + +- Modal: ([#2440](https://github.com/digdir/designsystemet/pull/2440)) + + - Rename `Modal.Dialog` to `Modal` + - Rename `Modal.Root` to `Modal.Context` + - Replace `onInteractOutside` event with `backdropClose` boolean + - Replace `closeButton` and `closeButtonTitle` on `Modal.Header` with `closeButton` on `Modal` + - Add border to `Modal.Header` and `Modal.Footer` + - Remove `Modal.Content` + - Remove `onBeforeClose` + - Remove `subtitle` from `Modal.Header` + +- Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) + + - Add body-xl token + - Add xl paragraph + - Remove ingress tokens + +- Ingress: Remove component ([#2515](https://github.com/digdir/designsystemet/pull/2515)) + + - Use `Paragraph variant='long'` instead + +- Card: ([#2509](https://github.com/digdir/designsystemet/pull/2509)) + + - Allow `Card` with content placed directly inside + - Replace `Card.Header`, `Card.Content` and `Card.Footer` with `Card.Block` + - Replace `isLink` with anchor-in-heading + `click` handler for better accessibility + +- Modal: Remove `Modal.Header` and `Modal.Footer`, replace with `Modal.Block` ([#2583](https://github.com/digdir/designsystemet/pull/2583)) + +- SkipLink: Remove ds-sr-only class ([#2546](https://github.com/digdir/designsystemet/pull/2546)) + +- Paragraph: Add css classes and style with data attributes ([#2523](https://github.com/digdir/designsystemet/pull/2523)) + +- Chip: ([#2493](https://github.com/digdir/designsystemet/pull/2493)) + - Add `Chip.Button` + - Rename `Chip.Toggle` to `Chip.Radio` and `Chip.Checkbox` + - Remove `Chip.Group` + ## 1.0.0-next.34 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 4466d7ed37..f85d321fed 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@digdir/designsystemet-react", - "version": "1.0.0-next.34", + "version": "1.0.0-next.35", "description": "React components for Designsystemet", "author": "Designsystemet team", "repository": { diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index e098195a9e..42385d5bab 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 1.0.0-next.35 + +### Patch Changes + +- Body/Paragraph ([#2529](https://github.com/digdir/designsystemet/pull/2529)) + - Add body-xl token + - Add xl paragraph + - Remove ingress tokens + ## 1.0.0-next.34 ## 1.0.0-next.33 diff --git a/packages/theme/package.json b/packages/theme/package.json index 198d6e4861..0bcc897076 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@digdir/designsystemet-theme", - "version": "1.0.0-next.34", + "version": "1.0.0-next.35", "description": "Predefined themes for Designsystemet", "author": "Designsystemet team", "repository": { From 1898de7e4d6e53794fa34449ec8cb250e9628bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Thune?= Date: Tue, 8 Oct 2024 14:16:29 +0200 Subject: [PATCH 10/19] chore: updated design for themebuilder modal (#2558) resolves #2356 --- .../TokenModal/TokenModal.module.css | 107 +++++++--- .../components/TokenModal/TokenModal.tsx | 192 +++++++++++------- apps/theme/public/img/figma-logo.png | Bin 0 -> 80051 bytes 3 files changed, 197 insertions(+), 102 deletions(-) create mode 100644 apps/theme/public/img/figma-logo.png diff --git a/apps/theme/components/TokenModal/TokenModal.module.css b/apps/theme/components/TokenModal/TokenModal.module.css index 4e48084efe..d493b29862 100644 --- a/apps/theme/components/TokenModal/TokenModal.module.css +++ b/apps/theme/components/TokenModal/TokenModal.module.css @@ -1,33 +1,80 @@ .content { display: flex; flex-wrap: wrap; - gap: 32px; + margin-top: var(--ds-spacing-6); } -.column { - width: calc(50% - 16px); +.leftSection, +.rightSection { + flex-grow: 1; + flex-basis: 0; } -.snippet { - max-height: 250px; - overflow: auto; +.leftSection { + border-right: 1px solid var(--ds-color-neutral-border-subtle); + padding-right: var(--ds-spacing-8); } -.snippet code { - background-color: transparent; +.rightSection { + padding: var(--ds-spacing-4) 0 var(--ds-spacing-4) var(--ds-spacing-8); +} + +.infoBoxes { + border-top: 1px solid var(--ds-color-neutral-border-subtle); + margin-top: var(--ds-spacing-8); + padding-top: var(--ds-spacing-8); + display: flex; + flex-direction: column; + gap: var(--ds-spacing-8); +} + +.infoBox { + display: flex; + gap: var(--ds-spacing-3); +} + +.infoBox__container { + gap: var(--ds-spacing-1); + display: flex; + flex-direction: column; +} + +.infoBox__icon { + height: 32px; + width: 32px; + background-color: black; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + color: white; + margin-top: -3px; } -.tabsContent { - padding-left: 0; - padding-right: 0; +.infoBox__icon img { + height: 16px; } -.tabs { - margin-bottom: 16px; +.infoBox__icon--code { + background: linear-gradient(137deg, #4ebbff 4.89%, #1c49ff 95.99%); } -.title { - margin-bottom: 16px; +.contact { + margin-top: var(--ds-spacing-6); + gap: 8px; + display: flex; +} + +.contact__content { + display: flex; + flex-direction: column; + gap: 4px; +} + +.snippet code { + background-color: transparent; + height: 290px; + display: block; } .modalHeader { @@ -38,25 +85,27 @@ .headerText { margin-right: auto; - font-size: 24px; + font-size: var(--ds-spacing-6); } .emblem { height: 26px; - margin-right: 10px; + margin-right: var(--ds-spacing-2); } -.shareBtn { - margin-right: 8px; - font-weight: 500; -} +@media (max-width: 1150px) { + .content { + flex-direction: column; + } -.hiddenGlobalBtn { - position: absolute; - left: 0; - bottom: 0; - height: 48px; - width: 48px; - background-color: transparent; - border: transparent; + .leftSection { + border: none; + padding: 0; + } + + .rightSection { + padding: 0; + margin-top: 32px; + width: 100%; + } } diff --git a/apps/theme/components/TokenModal/TokenModal.tsx b/apps/theme/components/TokenModal/TokenModal.tsx index c30ace5170..778e3c6069 100644 --- a/apps/theme/components/TokenModal/TokenModal.tsx +++ b/apps/theme/components/TokenModal/TokenModal.tsx @@ -9,9 +9,12 @@ import { Textfield, } from '@digdir/designsystemet-react'; import { createTokens } from '@digdir/designsystemet/tokens/create.js'; +import { CodeIcon, InformationSquareIcon } from '@navikt/aksel-icons'; import { CodeSnippet } from '@repo/components'; import { useEffect, useRef, useState } from 'react'; +import cl from 'clsx/lite'; + import classes from './TokenModal.module.css'; type TokenModalProps = { @@ -40,7 +43,7 @@ export const TokenModal = ({ const [darkThemeSnippet, setDarkThemeSnippet] = useState(''); const [themeName, setThemeName] = useState('theme'); - const cliSnippet = `npx @digdir/designsystemet tokens create \\ + const cliSnippet = `npx @digdir/designsystemet@next tokens create \\ --accent "${accentColor}" \\ --neutral "${neutralColor}" \\ --brand1 "${brand1Color}" \\ @@ -67,6 +70,36 @@ export const TokenModal = ({ setDarkThemeSnippet(toFigmaSnippet(tokens.colors.dark.theme)); }, []); + type InfoBoxType = { + title: string; + desc: React.ReactNode; + img: React.ReactNode; + type?: 'code' | 'figma'; + }; + + const InfoBox = ({ title, desc, img, type = 'figma' }: InfoBoxType) => { + return ( +
+
+
+ {img} +
+
+
+
+ {title} + {desc} +
+
+
+ ); + }; + return ( - Kopier fargetema + Ta i bruk tema - - Velg et av alternativene under for å ta i bruk design-tokens med ditt - tema. - - - Alt 1. Design tokens - - { - const value = e.currentTarget.value - .replace(/\s+/g, '-') - .replace(/[^A-Z0-9-]+/gi, '') - .toLowerCase(); - setThemeName(value); - }} - style={{ marginBlock: 'var(--ds-spacing-4)' }} - /> - - Kopier kommandosnutten under og kjør på maskinen din for å generere - alle design tokens (json-filer). Sørg for at du har{' '} - - Node.js (åpnes i ny fane) - {' '} - installert på maskinen din. - -
- {cliSnippet} -
- - Alt 2. Figma plugin - - - JSON for bruk med Designsystemet{' '} - - Figma Plugin (åpnes i ny fane) - {' '} - og{' '} - - Figma UI kit (åpnes i ny fane) - - . - - - Dette alternativet er kun ment for rask prototyping av valgt tema i - Figma. For å bruke design tokens i produksjon, anbefales det å bruke - alternativ 1. -
-
- - Light Mode - -
- {lightThemeSnippet} +
+ { + const value = e.currentTarget.value + .replace(/\s+/g, '-') + .replace(/[^A-Z0-9-]+/gi, '') + .toLowerCase(); + + setThemeName(value); + }} + /> +
+
-
- - Dark Mode - +
- {darkThemeSnippet} + {cliSnippet} +
+
+
+
+
+ Noe som ikke fungerer? + + Send oss en melding på{' '} + + Slack + {' '} + eller lag et{' '} + + Github issue + + . + +
diff --git a/apps/theme/public/img/figma-logo.png b/apps/theme/public/img/figma-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8e56a58387031ebcf669ef1c7d7e10e8e5bd00 GIT binary patch literal 80051 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 Date: Wed, 9 Oct 2024 12:43:42 +0200 Subject: [PATCH 11/19] chore: fix typography attrs and layering (#2588) resolves #2586 Also streamlines use of composes and data attrs on all typography components. Removes `baseline` layer and file. Renamed `utilities.css` to `base.css` Adds a story telling and showing that we change color and background --- .changeset/smooth-radios-leave.md | 8 + .changeset/tidy-cheetahs-cry.md | 5 + packages/css/accordion.css | 4 +- packages/css/alert.css | 6 +- packages/css/avatar.css | 10 +- packages/css/badge.css | 6 +- packages/css/{utilities.css => base/base.css} | 62 ++++- packages/css/{baseline => base}/ds-reset.css | 0 packages/css/baseline/baseline.css | 5 - packages/css/baseline/typography.css | 216 ------------------ packages/css/breadcrumbs.css | 6 +- packages/css/button.css | 8 +- packages/css/card.css | 5 +- packages/css/chip.css | 6 +- packages/css/dropdown.css | 6 +- packages/css/fieldset.css | 2 +- packages/css/heading.css | 29 +++ packages/css/helptext.css | 2 +- packages/css/index.css | 12 +- packages/css/label.css | 30 +++ packages/css/list.css | 6 +- packages/css/paragraph.css | 61 +++++ packages/css/popover.css | 6 +- packages/css/skiplink.css | 2 +- packages/css/table.css | 6 +- packages/css/tabs.css | 8 +- packages/css/tag.css | 6 +- packages/css/tooltip.css | 2 +- packages/css/validation-message.css | 21 ++ .../src/components/Label/Label.stories.tsx | 1 + packages/react/src/components/Label/Label.tsx | 8 +- .../Paragraph/Paragraph.stories.tsx | 1 + .../ValidationMessage/ValidationMessage.tsx | 5 +- packages/react/stories/Typography.mdx | 4 + packages/react/stories/Typography.stories.tsx | 78 +++++++ 35 files changed, 360 insertions(+), 283 deletions(-) create mode 100644 .changeset/smooth-radios-leave.md create mode 100644 .changeset/tidy-cheetahs-cry.md rename packages/css/{utilities.css => base/base.css} (74%) rename packages/css/{baseline => base}/ds-reset.css (100%) delete mode 100644 packages/css/baseline/baseline.css delete mode 100644 packages/css/baseline/typography.css create mode 100644 packages/css/heading.css create mode 100644 packages/css/label.css create mode 100644 packages/css/paragraph.css create mode 100644 packages/css/validation-message.css diff --git a/.changeset/smooth-radios-leave.md b/.changeset/smooth-radios-leave.md new file mode 100644 index 0000000000..742c987187 --- /dev/null +++ b/.changeset/smooth-radios-leave.md @@ -0,0 +1,8 @@ +--- +"@digdir/designsystemet-css": patch +"@digdir/designsystemet-react": patch +--- + +Label: Use data attributes for styling + +ValidationMessage: Use data attributes for styling diff --git a/.changeset/tidy-cheetahs-cry.md b/.changeset/tidy-cheetahs-cry.md new file mode 100644 index 0000000000..45f8ba1609 --- /dev/null +++ b/.changeset/tidy-cheetahs-cry.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-css": patch +--- + +Remove `baseline` layer and fix layerorder for typography diff --git a/packages/css/accordion.css b/packages/css/accordion.css index f1afe560f5..4855227122 100644 --- a/packages/css/accordion.css +++ b/packages/css/accordion.css @@ -72,8 +72,8 @@ padding-block: var(--dsc-accordion-padding); padding-inline: calc(var(--dsc-accordion-padding) + var(--dsc-accordion-chevron-size) + var(--dsc-accordion-chevron-gap)) var(--dsc-accordion-padding); - @composes ds-focus from './utilities.css'; - @composes ds-body-text--sm from './utilities.css'; + @composes ds-focus from './base/base.css'; + @composes ds-body-text--sm from './base/base.css'; &:focus-visible { position: relative; /* Ensure foucs outline renders on top */ diff --git a/packages/css/alert.css b/packages/css/alert.css index 095c0f2749..e6f400e797 100644 --- a/packages/css/alert.css +++ b/packages/css/alert.css @@ -17,7 +17,7 @@ padding-block: var(--dsc-alert-padding); padding-inline: calc(var(--dsc-alert-padding) + var(--dsc-alert-icon-size) + var(--dsc-alert-gap)) var(--dsc-alert-padding); - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; & > :is(h1,h2,h3,h4,h5,h6):first-child::before, /* If Alert starts with Heading, align icon to this */ &:not(:has(> :is(h1,h2,h3,h4,h5,h6):first-child))::before /* If there is no Heading, align icon to Alert itself */ { @@ -66,13 +66,13 @@ --dsc-alert-icon-size: var(--ds-sizing-6); --dsc-alert-padding: var(--ds-spacing-5); - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-alert-icon-size: var(--ds-sizing-8); --dsc-alert-padding: var(--ds-spacing-7); - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } } diff --git a/packages/css/avatar.css b/packages/css/avatar.css index 90e5ce779a..960f218904 100644 --- a/packages/css/avatar.css +++ b/packages/css/avatar.css @@ -19,7 +19,7 @@ text-transform: uppercase; user-select: none; - @composes ds-body-text--xs from './utilities.css'; + @composes ds-body-text--xs from './base/base.css'; &:not(:has(> img)) { padding: var(--dsc-avatar-padding); @@ -83,27 +83,27 @@ --dsc-avatar-size: var(--ds-sizing-7); --dsc-avatar-padding: var(--ds-spacing-1); - @composes ds-body-text--xs from './utilities.css'; + @composes ds-body-text--xs from './base/base.css'; } &[data-size='sm'] { --dsc-avatar-size: var(--ds-sizing-9); --dsc-avatar-padding: var(--ds-spacing-1); - @composes ds-heading-text--2xs from './utilities.css'; + @composes ds-heading-text--2xs from './base/base.css'; } &[data-size='md'] { --dsc-avatar-size: var(--ds-sizing-12); --dsc-avatar-padding: var(--ds-spacing-2); - @composes ds-heading-text--sm from './utilities.css'; + @composes ds-heading-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-avatar-size: var(--ds-sizing-15); --dsc-avatar-padding: var(--ds-spacing-2); - @composes ds-heading-text--md from './utilities.css'; + @composes ds-heading-text--md from './base/base.css'; } } diff --git a/packages/css/badge.css b/packages/css/badge.css index 3ad5c182ae..6d171b3bf8 100644 --- a/packages/css/badge.css +++ b/packages/css/badge.css @@ -8,7 +8,7 @@ display: inline-flex; position: relative; - @composes ds-body-text--short-sm from './utilities.css'; + @composes ds-body-text--short-sm from './base/base.css'; &::before { content: attr(data-count); @@ -27,7 +27,7 @@ --dsc-badge-size: var(--ds-sizing-3); --dsc-badge-padding: 0 var(--ds-spacing-1); - @composes ds-body-text--short-xs from './utilities.css'; + @composes ds-body-text--short-xs from './base/base.css'; &[data-count] { --dsc-badge-size: var(--ds-sizing-5); @@ -38,7 +38,7 @@ --dsc-badge-size: var(--ds-sizing-4); --dsc-badge-padding: 0 var(--ds-spacing-2); - @composes ds-body-text--short-md from './utilities.css'; + @composes ds-body-text--short-md from './base/base.css'; &[data-count] { --dsc-badge-size: var(--ds-sizing-7); diff --git a/packages/css/utilities.css b/packages/css/base/base.css similarity index 74% rename from packages/css/utilities.css rename to packages/css/base/base.css index ce7150fe85..1b1cb382d8 100644 --- a/packages/css/utilities.css +++ b/packages/css/base/base.css @@ -17,13 +17,16 @@ --dsc-focus-border-width: 3px; /* Default focus border width */ --dsc-focus-boxShadow: 0 0 0 var(--dsc-focus-border-width) var(--ds-color-focus-inner); --dsc-focus-outline: var(--ds-color-focus-outer) solid var(--dsc-focus-border-width); + + color: var(--ds-color-neutral-text-default); + background-color: var(--ds-color-neutral-background-default); } /** * Apply a focus outline on an element when it is focused with keyboard */ .ds-focus:focus-visible { - @composes ds-focus--visible from './utilities.css'; + @composes ds-focus--visible from './base.css'; } /** @@ -195,3 +198,60 @@ font-size: var(--ds-heading-2xl-font-size); letter-spacing: var(--ds-heading-2xl-letter-spacing); } + +.ds-label--md { + font-size: var(--ds-label-md-font-size); + font-weight: var(--ds-label-md-font-weight); + letter-spacing: var(--ds-label-md-letter-spacing); + line-height: var(--ds-label-md-line-height); +} + +.ds-label--xs { + font-weight: var(--ds-label-xs-font-weight); + line-height: var(--ds-label-xs-line-height); + font-size: var(--ds-label-xs-font-size); + letter-spacing: var(--ds-label-xs-letter-spacing); +} + +.ds-label--sm { + font-weight: var(--ds-label-sm-font-weight); + line-height: var(--ds-label-sm-line-height); + font-size: var(--ds-label-sm-font-size); + letter-spacing: var(--ds-label-sm-letter-spacing); +} + +.ds-label--lg { + font-weight: var(--ds-label-lg-font-weight); + line-height: var(--ds-label-lg-line-height); + font-size: var(--ds-label-lg-font-size); + letter-spacing: var(--ds-label-lg-letter-spacing); +} + +.ds-validation-message--xs { + font-weight: var(--ds-error_message-xs-font-weight); + line-height: var(--ds-error_message-xs-line-height); + font-size: var(--ds-error_message-xs-font-size); + letter-spacing: var(--ds-error_message-xs-letter-spacing); +} + +.ds-validation-message--sm { + font-weight: var(--ds-error_message-sm-font-weight); + line-height: var(--ds-error_message-sm-line-height); + font-size: var(--ds-error_message-sm-font-size); + letter-spacing: var(--ds-error_message-sm-letter-spacing); +} + +.ds-validation-message--md { + font-size: var(--ds-error_message-md-font-size); + font-weight: var(--ds-error_message-md-font-weight); + letter-spacing: var(--ds-error_message-md-letter-spacing); + line-height: var(--ds-error_message-md-line-height); + margin: 0; +} + +.ds-validation-message--lg { + font-weight: var(--ds-error_message-lg-font-weight); + line-height: var(--ds-error_message-lg-line-height); + font-size: var(--ds-error_message-lg-font-size); + letter-spacing: var(--ds-error_message-lg-letter-spacing); +} diff --git a/packages/css/baseline/ds-reset.css b/packages/css/base/ds-reset.css similarity index 100% rename from packages/css/baseline/ds-reset.css rename to packages/css/base/ds-reset.css diff --git a/packages/css/baseline/baseline.css b/packages/css/baseline/baseline.css deleted file mode 100644 index 14e13a4ab4..0000000000 --- a/packages/css/baseline/baseline.css +++ /dev/null @@ -1,5 +0,0 @@ -:root, -[data-ds-color-mode] { - color: var(--ds-color-neutral-text-default); - background-color: var(--ds-color-neutral-background-default); -} diff --git a/packages/css/baseline/typography.css b/packages/css/baseline/typography.css deleted file mode 100644 index 6d9eee1418..0000000000 --- a/packages/css/baseline/typography.css +++ /dev/null @@ -1,216 +0,0 @@ -/** Heading */ - -.ds-heading { - --dsc-bottom-spacing: var(--ds-spacing-4); - - margin: 0; - - @composes ds-heading-text--md from '../utilities.css'; - - &[data-size='2xs'] { - @composes ds-heading-text--2xs from '../utilities.css'; - } - - &[data-size='xs'] { - @composes ds-heading-text--xs from '../utilities.css'; - } - - &[data-size='sm'] { - @composes ds-heading-text--sm from '../utilities.css'; - } - - &[data-size='lg'] { - @composes ds-heading-text--lg from '../utilities.css'; - } - - &[data-size='xl'] { - @composes ds-heading-text--xl from '../utilities.css'; - } - - &[data-size='2xl'] { - @composes ds-heading-text--2xl from '../utilities.css'; - } -} - -/** Paragraph */ -.ds-paragraph { - margin: 0; - - @composes ds-body-text--md from '../utilities.css'; - - &[data-size='xs'] { - @composes ds-body-text--xs from '../utilities.css'; - } - - &[data-size='sm'] { - @composes ds-body-text--sm from '../utilities.css'; - } - - &[data-size='lg'] { - @composes ds-body-text--lg from '../utilities.css'; - } - - &[data-size='xl'] { - @composes ds-body-text--xl from '../utilities.css'; - } - - &[data-variant='long'] { - @composes ds-body-text--long-md from '../utilities.css'; - - &[data-size='xs'] { - @composes ds-body-text--long-xs from '../utilities.css'; - } - - &[data-size='sm'] { - @composes ds-body-text--long-sm from '../utilities.css'; - } - - &[data-size='lg'] { - @composes ds-body-text--long-lg from '../utilities.css'; - } - - &[data-size='xl'] { - @composes ds-body-text--long-xl from '../utilities.css'; - } - } - - &[data-variant='short'] { - @composes ds-body-text--short-md from '../utilities.css'; - - &[data-size='xs'] { - @composes ds-body-text--short-xs from '../utilities.css'; - } - - &[data-size='sm'] { - @composes ds-body-text--short-sm from '../utilities.css'; - } - - &[data-size='lg'] { - @composes ds-body-text--short-lg from '../utilities.css'; - } - - &[data-size='xl'] { - @composes ds-body-text--short-xl from '../utilities.css'; - } - } -} - -/** Label */ -.ds-label--md { - font-size: var(--ds-label-md-font-size); - font-weight: var(--ds-label-md-font-weight); - letter-spacing: var(--ds-label-md-letter-spacing); - line-height: var(--ds-label-md-line-height); - color: var(--ds-color-neutral-text-default); - display: inline-block; - margin: 0; - padding: 0; -} - -.ds-label--xs { - font-weight: var(--ds-label-xs-font-weight); - line-height: var(--ds-label-xs-line-height); - font-size: var(--ds-label-xs-font-size); - letter-spacing: var(--ds-label-xs-letter-spacing); - color: var(--ds-color-neutral-text-default); - display: inline-block; - margin: 0; - padding: 0; -} - -.ds-label--sm { - font-weight: var(--ds-label-sm-font-weight); - line-height: var(--ds-label-sm-line-height); - font-size: var(--ds-label-sm-font-size); - letter-spacing: var(--ds-label-sm-letter-spacing); - color: var(--ds-color-neutral-text-default); - display: inline-block; - margin: 0; - padding: 0; -} - -.ds-label--lg { - font-weight: var(--ds-label-lg-font-weight); - line-height: var(--ds-label-lg-line-height); - font-size: var(--ds-label-lg-font-size); - letter-spacing: var(--ds-label-lg-letter-spacing); - color: var(--ds-color-neutral-text-default); - display: inline-block; - margin: 0; - padding: 0; -} - -/** Validation message */ - -.ds-validation-message--xs { - font-weight: var(--ds-error_message-xs-font-weight); - line-height: var(--ds-error_message-xs-line-height); - font-size: var(--ds-error_message-xs-font-size); - letter-spacing: var(--ds-error_message-xs-letter-spacing); - - &[data-error] { - color: var(--ds-color-danger-text-subtle); - } -} - -.ds-validation-message--sm { - font-weight: var(--ds-error_message-sm-font-weight); - line-height: var(--ds-error_message-sm-line-height); - font-size: var(--ds-error_message-sm-font-size); - letter-spacing: var(--ds-error_message-sm-letter-spacing); - - &[data-error] { - color: var(--ds-color-danger-text-subtle); - } -} - -.ds-validation-message--md { - font-size: var(--ds-error_message-md-font-size); - font-weight: var(--ds-error_message-md-font-weight); - letter-spacing: var(--ds-error_message-md-letter-spacing); - line-height: var(--ds-error_message-md-line-height); - margin: 0; - - &[data-error] { - color: var(--ds-color-danger-text-subtle); - } -} - -.ds-validation-message--lg { - font-weight: var(--ds-error_message-lg-font-weight); - line-height: var(--ds-error_message-lg-line-height); - font-size: var(--ds-error_message-lg-font-size); - letter-spacing: var(--ds-error_message-lg-letter-spacing); - - &[data-error] { - color: var(--ds-color-danger-text-subtle); - } -} - -/** Font weight - ** used for weight on Label */ -.ds-font-weight--medium { - font-weight: var(--ds-font-weight-medium); -} - -.ds-font-weight--semibold { - font-weight: var(--ds-font-weight-semibold); -} - -.ds-font-weight--regular { - font-weight: var(--ds-font-weight-regular); -} - -/** Line height - ** used for long, short Paragraph */ -.ds-line-height--sm { - line-height: var(--ds-line-height-sm); -} - -.ds-line-height--md { - line-height: var(--ds-line-height-md); -} - -.ds-line-height--lg { - line-height: var(--ds-line-height-lg); -} diff --git a/packages/css/breadcrumbs.css b/packages/css/breadcrumbs.css index b48bd8e7bb..366e99955f 100644 --- a/packages/css/breadcrumbs.css +++ b/packages/css/breadcrumbs.css @@ -2,20 +2,20 @@ --dsc-breadcrumbs-spacing: var(--ds-spacing-2); --dsc-breadcrumbs-chevron-size: var(--ds-sizing-6); - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; &[data-size='sm'] { --dsc-breadcrumbs-spacing: var(--ds-spacing-1); --dsc-breadcrumbs-chevron-size: var(--ds-sizing-5); - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-breadcrumbs-spacing: var(--ds-spacing-3); --dsc-breadcrumbs-chevron-size: var(--ds-sizing-7); - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } & > :is(ol, ul) { diff --git a/packages/css/button.css b/packages/css/button.css index 179e20b5c9..cb58b69ac1 100644 --- a/packages/css/button.css +++ b/packages/css/button.css @@ -9,8 +9,8 @@ --dsc-button-padding-inline: var(--ds-spacing-4); --dsc-button-size: var(--ds-sizing-12); - @composes ds-body-text--short-md from './utilities.css'; - @composes ds-focus from './utilities.css'; + @composes ds-body-text--short-md from './base/base.css'; + @composes ds-focus from './base/base.css'; align-items: center; background: var(--dsc-button-background); @@ -43,7 +43,7 @@ --dsc-button-padding-inline: var(--ds-spacing-3); --dsc-button-size: var(--ds-sizing-10); - @composes ds-body-text--short-sm from './utilities.css'; + @composes ds-body-text--short-sm from './base/base.css'; } &[data-size='lg'] { @@ -51,7 +51,7 @@ --dsc-button-padding-inline: var(--ds-spacing-5); --dsc-button-size: var(--ds-sizing-14); - @composes ds-body-text--short-lg from './utilities.css'; + @composes ds-body-text--short-lg from './base/base.css'; } &[data-icon] { diff --git a/packages/css/card.css b/packages/css/card.css index 01cf02e5f3..0aa93ad616 100644 --- a/packages/css/card.css +++ b/packages/css/card.css @@ -18,7 +18,7 @@ overflow: clip; /* Needed to clip media elements and and Card.Block */ padding: var(--dsc-card-padding); - @composes ds-paragraph from './baseline/typography.css'; /* Must be after all: unset */ + @composes ds-paragraph from './paragraph.css'; /* Must be after all: unset */ /* Style link in heading, or heading when Card is anchor */ :is(h1, h2, h3, h4, h5, h6) a:any-link, /* Using :any-link to target both a and a:visited */ @@ -42,9 +42,8 @@ } } - /* Using CSS custom property instead of composes since we need to support :has(:focus-visible) */ &:where(:focus-visible, :has(:focus-visible)) { - @composes ds-focus--visible from './utilities.css'; + @composes ds-focus--visible from './base/base.css'; } &:active { diff --git a/packages/css/chip.css b/packages/css/chip.css index 7e0a7f401b..ab677be1ab 100644 --- a/packages/css/chip.css +++ b/packages/css/chip.css @@ -9,8 +9,8 @@ --dsc-chip-input-size: var(--ds-spacing-5); --dsc-chip-padding: 0 var(--ds-spacing-3); - @composes ds-focus from './utilities.css'; - @composes ds-paragraph-short from './utilities.css'; + @composes ds-focus from './base/base.css'; + @composes ds-paragraph-short from './base/base.css'; align-items: center; background: var(--dsc-chip-background); @@ -27,7 +27,7 @@ /* Make focus ring also when input inside is focused */ &:has(:focus-visible) { - @composes ds-focus--visible from './utilities.css'; + @composes ds-focus--visible from './base/base.css'; } &:disabled, diff --git a/packages/css/dropdown.css b/packages/css/dropdown.css index 919c316471..4e1e3fadd2 100644 --- a/packages/css/dropdown.css +++ b/packages/css/dropdown.css @@ -21,7 +21,7 @@ .ds-dropdown__heading { padding: var(--dsc-dropdown-header-padding); - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; } &[data-size='sm'] { @@ -29,7 +29,7 @@ --dsc-dropdown-min-width: 15rem; & .ds-dropdown__heading { - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } } @@ -38,7 +38,7 @@ --dsc-dropdown-min-width: 18rem; & .ds-dropdown__heading { - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } } diff --git a/packages/css/fieldset.css b/packages/css/fieldset.css index d9c7b73654..fe5969709a 100644 --- a/packages/css/fieldset.css +++ b/packages/css/fieldset.css @@ -21,7 +21,7 @@ &[data-hidelegend] > legend, &[data-hidelegend] > legend + p { - @composes ds-sr-only from '../css/utilities.css'; + @composes ds-sr-only from './base/base.css'; } &[data-readonly] > legend::before { diff --git a/packages/css/heading.css b/packages/css/heading.css new file mode 100644 index 0000000000..264ee46297 --- /dev/null +++ b/packages/css/heading.css @@ -0,0 +1,29 @@ +.ds-heading { + margin: 0; + + @composes ds-heading-text--md from './base/base.css'; + + &[data-size='2xs'] { + @composes ds-heading-text--2xs from './base/base.css'; + } + + &[data-size='xs'] { + @composes ds-heading-text--xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-heading-text--sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-heading-text--lg from './base/base.css'; + } + + &[data-size='xl'] { + @composes ds-heading-text--xl from './base/base.css'; + } + + &[data-size='2xl'] { + @composes ds-heading-text--2xl from './base/base.css'; + } +} diff --git a/packages/css/helptext.css b/packages/css/helptext.css index 9fccccfe9b..4cc3167bd7 100644 --- a/packages/css/helptext.css +++ b/packages/css/helptext.css @@ -3,7 +3,7 @@ --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); - @composes ds-focus from '../css/utilities.css'; + @composes ds-focus from './base/base.css'; border-radius: var(--ds-border-radius-full); border: 2px solid; diff --git a/packages/css/index.css b/packages/css/index.css index d75a1544ef..98eb113623 100644 --- a/packages/css/index.css +++ b/packages/css/index.css @@ -1,12 +1,14 @@ @charset "UTF-8"; -@layer ds.reset, ds.baseline, ds.theme, ds.base, ds.components; +@layer ds.reset, ds.base, ds.typography, ds.theme, ds.components; /** Import order defines ordinal specificity for layers */ -@import url('./baseline/ds-reset.css') layer(ds.reset); -@import url('./baseline/baseline.css') layer(ds.baseline); -@import url('./baseline/typography.css') layer(ds.base.typography); -@import url('./utilities.css') layer(ds.base.utilities); +@import url('./base/ds-reset.css') layer(ds.reset); +@import url('./base/base.css') layer(ds.base); +@import url('./heading.css') layer(ds.typography); +@import url('./label.css') layer(ds.typography); +@import url('./paragraph.css') layer(ds.typography); +@import url('./validation-message.css') layer(ds.typography); @import url('./button.css') layer(ds.components); @import url('./alert.css') layer(ds.components); @import url('./popover.css') layer(ds.components); diff --git a/packages/css/label.css b/packages/css/label.css new file mode 100644 index 0000000000..952da6952c --- /dev/null +++ b/packages/css/label.css @@ -0,0 +1,30 @@ +.ds-label { + /* We set these three styles when label is used in `legend` */ + display: inline-block; + margin: 0; + padding: 0; + + @composes ds-label--md from './base/base.css'; + + font-weight: var(--ds-font-weight-medium); + + &[data-size='xs'] { + @composes ds-label--xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-label--sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-label--lg from './base/base.css'; + } + + &[data-weight='semibold'] { + font-weight: var(--ds-font-weight-semibold); + } + + &[data-weight='regular'] { + font-weight: var(--ds-font-weight-regular); + } +} diff --git a/packages/css/list.css b/packages/css/list.css index 44f8ed0ca1..cb7a19b13c 100644 --- a/packages/css/list.css +++ b/packages/css/list.css @@ -6,7 +6,7 @@ margin: 0; padding-left: var(--dsc-list-padding-left); - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; & > li + li { margin-top: var(--dsc-list-spacing); @@ -27,12 +27,12 @@ &[data-size='sm'] { --dsc-list-padding-left: var(--ds-spacing-4); - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-list-spacing: var(--ds-spacing-4); - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } } diff --git a/packages/css/paragraph.css b/packages/css/paragraph.css new file mode 100644 index 0000000000..52ac9b10a3 --- /dev/null +++ b/packages/css/paragraph.css @@ -0,0 +1,61 @@ +.ds-paragraph { + margin: 0; + + @composes ds-body-text--md from './base/base.css'; + + &[data-size='xs'] { + @composes ds-body-text--xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-body-text--sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-body-text--lg from './base/base.css'; + } + + &[data-size='xl'] { + @composes ds-body-text--xl from './base/base.css'; + } + + &[data-variant='long'] { + @composes ds-body-text--long-md from './base/base.css'; + + &[data-size='xs'] { + @composes ds-body-text--long-xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-body-text--long-sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-body-text--long-lg from './base/base.css'; + } + + &[data-size='xl'] { + @composes ds-body-text--long-xl from './base/base.css'; + } + } + + &[data-variant='short'] { + @composes ds-body-text--short-md from './base/base.css'; + + &[data-size='xs'] { + @composes ds-body-text--short-xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-body-text--short-sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-body-text--short-lg from './base/base.css'; + } + + &[data-size='xl'] { + @composes ds-body-text--short-xl from './base/base.css'; + } + } +} diff --git a/packages/css/popover.css b/packages/css/popover.css index 73a6790815..f342e1114b 100644 --- a/packages/css/popover.css +++ b/packages/css/popover.css @@ -18,7 +18,7 @@ padding: var(--dsc-popover-padding); position: fixed; - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; &::before { background: var(--dsc-popover-background); @@ -38,13 +38,13 @@ &[data-size='sm'] { --dsc-popover-padding: var(--ds-spacing-2) var(--ds-spacing-3); - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-popover-padding: var(--ds-spacing-3) var(--ds-spacing-5); - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } &[data-placement='top']::before { diff --git a/packages/css/skiplink.css b/packages/css/skiplink.css index f16e5ee23d..bda8b28a33 100644 --- a/packages/css/skiplink.css +++ b/packages/css/skiplink.css @@ -1,5 +1,5 @@ .ds-skiplink { - @composes ds-sr-only from './utilities.css'; + @composes ds-sr-only from './base/base.css'; } .ds-skiplink:focus { diff --git a/packages/css/table.css b/packages/css/table.css index ea1cbecf7d..a719b4919a 100644 --- a/packages/css/table.css +++ b/packages/css/table.css @@ -19,7 +19,7 @@ color: var(--dsc-table-color); width: 100%; - @composes ds-body-text--md from './utilities.css'; + @composes ds-body-text--md from './base/base.css'; & > :is(tbody, thead) > tr > :is(th, td) { background: var(--dsc-table-background); @@ -116,13 +116,13 @@ &[data-size='sm'] { --dsc-table-padding: var(--ds-spacing-1) var(--ds-spacing-3); - @composes ds-body-text--sm from './utilities.css'; + @composes ds-body-text--sm from './base/base.css'; } &[data-size='lg'] { --dsc-table-padding: var(--ds-spacing-3) var(--ds-spacing-3); - @composes ds-body-text--lg from './utilities.css'; + @composes ds-body-text--lg from './base/base.css'; } /** diff --git a/packages/css/tabs.css b/packages/css/tabs.css index 3053b11e8f..af4b665671 100644 --- a/packages/css/tabs.css +++ b/packages/css/tabs.css @@ -6,20 +6,20 @@ --dsc-tabs-content-color: var(--ds-color-neutral-text-default); --dsc-tabs-list-border-color: var(--ds-color-neutral-border-subtle); - @composes ds-body-text--short-md from './utilities.css'; + @composes ds-body-text--short-md from './base/base.css'; &[data-size='sm'] { --dsc-tabs-tab-padding: var(--ds-spacing-2) var(--ds-spacing-4); --dsc-tabs-content-padding: var(--ds-spacing-4); - @composes ds-body-text--short-sm from './utilities.css'; + @composes ds-body-text--short-sm from './base/base.css'; } &[data-size='lg'] { --dsc-tabs-tab-padding: var(--ds-spacing-4) var(--ds-spacing-6); --dsc-tabs-content-padding: var(--ds-spacing-6); - @composes ds-body-text--short-lg from './utilities.css'; + @composes ds-body-text--short-lg from './base/base.css'; } } @@ -54,7 +54,7 @@ --dsc-tabs-tab-color: var(--ds-color-accent-text-subtle); } - @composes ds-focus from './utilities.css'; + @composes ds-focus from './base/base.css'; /* We set z-index to make sure the active line does not bleed over the focus indicator */ &:focus-visible { diff --git a/packages/css/tag.css b/packages/css/tag.css index bae3e80b6c..51abf4219c 100644 --- a/packages/css/tag.css +++ b/packages/css/tag.css @@ -15,20 +15,20 @@ width: max-content; word-break: break-word; - @composes ds-body-text--short-md from './utilities.css'; + @composes ds-body-text--short-md from './base/base.css'; &[data-size='sm'] { --dsc-tag-padding: 0 var(--ds-spacing-2); --dsc-tag-min-height: var(--ds-sizing-7); - @composes ds-body-text--short-sm from './utilities.css'; + @composes ds-body-text--short-sm from './base/base.css'; } &[data-size='lg'] { --dsc-tag-padding: 0 var(--ds-spacing-3); --dsc-tag-min-height: var(--ds-sizing-9); - @composes ds-body-text--short-lg from './utilities.css'; + @composes ds-body-text--short-lg from './base/base.css'; } &[data-color='info'] { diff --git a/packages/css/tooltip.css b/packages/css/tooltip.css index 01ce4df927..5dd1f07edf 100644 --- a/packages/css/tooltip.css +++ b/packages/css/tooltip.css @@ -7,7 +7,7 @@ color: var(--ds-color-neutral-background-default); padding: var(--ds-spacing-1) var(--ds-spacing-2); - @composes ds-body-text--short-xs from './utilities.css'; + @composes ds-body-text--short-xs from './base/base.css'; } .ds-tooltip__arrow { diff --git a/packages/css/validation-message.css b/packages/css/validation-message.css new file mode 100644 index 0000000000..97a82c1dd5 --- /dev/null +++ b/packages/css/validation-message.css @@ -0,0 +1,21 @@ +.ds-validation-message { + margin: 0; + + @composes ds-validation-message--md from './base/base.css'; + + &[data-size='xs'] { + @composes ds-validation-message--xs from './base/base.css'; + } + + &[data-size='sm'] { + @composes ds-validation-message--sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-validation-message--lg from './base/base.css'; + } + + &[data-error] { + color: var(--ds-color-danger-text-subtle); + } +} diff --git a/packages/react/src/components/Label/Label.stories.tsx b/packages/react/src/components/Label/Label.stories.tsx index 2ca81bd4bc..871a579316 100644 --- a/packages/react/src/components/Label/Label.stories.tsx +++ b/packages/react/src/components/Label/Label.stories.tsx @@ -15,5 +15,6 @@ export const Preview: Story = { args: { children: 'Vennligst skriv inn fødselsnummer. 11 tegn', size: 'md', + weight: 'medium', }, }; diff --git a/packages/react/src/components/Label/Label.tsx b/packages/react/src/components/Label/Label.tsx index f651d843c4..0c41dd2f66 100644 --- a/packages/react/src/components/Label/Label.tsx +++ b/packages/react/src/components/Label/Label.tsx @@ -36,11 +36,9 @@ export const Label = forwardRef(function Label( return ( ); diff --git a/packages/react/src/components/Paragraph/Paragraph.stories.tsx b/packages/react/src/components/Paragraph/Paragraph.stories.tsx index d4a78ee331..9534aec880 100644 --- a/packages/react/src/components/Paragraph/Paragraph.stories.tsx +++ b/packages/react/src/components/Paragraph/Paragraph.stories.tsx @@ -16,5 +16,6 @@ export const Preview: Story = { children: 'Personvernerklæringen gir informasjon om hvilke personopplysninger vi behandler, hvordan disse blir behandlet og hvilke rettigheter du har.', size: 'md', + variant: 'default', }, }; diff --git a/packages/react/src/components/ValidationMessage/ValidationMessage.tsx b/packages/react/src/components/ValidationMessage/ValidationMessage.tsx index 5776999d89..dfc45ece26 100644 --- a/packages/react/src/components/ValidationMessage/ValidationMessage.tsx +++ b/packages/react/src/components/ValidationMessage/ValidationMessage.tsx @@ -30,9 +30,10 @@ export const ValidationMessage = forwardRef< return ( ); diff --git a/packages/react/stories/Typography.mdx b/packages/react/stories/Typography.mdx index 07bc32cdae..3c97e573ca 100644 --- a/packages/react/stories/Typography.mdx +++ b/packages/react/stories/Typography.mdx @@ -69,3 +69,7 @@ Brukes når det trengs tekst over flere linjer. Mesteparten av teksten på et ne ## Eksempel + +Vi endrer `color` og `background-color` for deg når du setter `data-ds-color-mode`. + + diff --git a/packages/react/stories/Typography.stories.tsx b/packages/react/stories/Typography.stories.tsx index d63cdecd3b..6658f767de 100644 --- a/packages/react/stories/Typography.stories.tsx +++ b/packages/react/stories/Typography.stories.tsx @@ -85,3 +85,81 @@ export const EksempelTekst: StoryFn = () => ( ); + +export const EksempelTekstDark: StoryFn = () => ( +
+ + Samordnet registermelding (H1) + + + + Her kan du registrere nye virksomheter, som for eksempel + enkeltpersonforetak, foreninger, aksjeselskap, ansvarlige selskap, + samvirkeforetak og stiftelser. De aller fleste organisasjonsformene kan + bruke denne tjenesten. + + + + Når skal du bruke skjemaet? (H2) + + + + Denne tjenesten kan du bruke for å melde opplysninger til + Enhetsregisteret, Foretaksregisteret, Frivillighetsregisteret, NAV + Aa-registeret, Virksomhets- og foretaksregisteret hos SSB, + Stiftelsesregisteret og Skattedirektoratets register over upersonlige + skattytere. + + + + Signering (H3) + + + + Når du skal signere meldingen vil du motta en signeringsoppgave i + meldingsboksen din i Altinn. Meldingen blir ikke sendt til behandling før + alle har signert. + + + + Krav om rolle for signering (H4) + + + + For å signere på vegne av en virksomhet, trenger du Altinn-rollen Signerer + av Samordnet registermelding. Du kan se hvilke roller du har for en aktør + på menypunktet Profil, Skjema og tjenester du har rettighet til. Om du + ikke har disse rollene, må du få noen som har rollene til å delegere dem + til deg. + + + + Personvern (H5) + + + + Personvernerklæringen gir informasjon om hvilke personopplysninger vi + behandler, hvordan disse blir behandlet og hvilke rettigheter du har. + +
+); From 22382936ddd97ca6c149a5f0828709f17c041d18 Mon Sep 17 00:00:00 2001 From: Une Sofie Kinn Ekroll Date: Thu, 10 Oct 2024 12:16:39 +0200 Subject: [PATCH 12/19] test(storybook): support running visual tests in Chromatic (#2570) Closes #963 To better make use of visual tests: - "Avatar/In Dropdown" story renders with the dropdown open - Modals, comboboxes, dropdowns, and popovers are now opened automatically (when not in docs) through interaction tests - SkipLink: added a story showing the SkipLink when it has become visible - Switch: added examples of enabled and hovered switches - Added a default 1rem padding to stories, to account for normally overflowing content like focus styles, floating badges etc - Several stories have custom styling to ensure elements that have been removed from the normal layout flow (e.g. with absolute positioning) or are rendered outside the Story root element (e.g. with a React portal) are actually visible in the snapshots. To easier facilitate this, a global customStylesDecorator has been added, which adds styles configured through `parameters.customStyles`. This commit also replaces story-specific decorators which only added styling with `parameter.customStyles`, simplifying the stories a bit. Visual tests on 320px wide screen revealed a bug where Combobox was wider than the screen. This has been fixed. With the changes that open modals, add pseudo states etc, some new accessibility violations surfaced. - Fixed axe violation `svg-img-alt` in Dropdown stories - Disabled axe rule `color-contrast` when the pseudo-class :active is emulated through `storybook-addon-pseudo-states`, since we concluded that 4.5:1 text contrast during press actions is unnecessary There is also a `yarn patch` applied to support axe running on multiple root elements (to support portals) when run through the Storybook a11y addon "Accessibility" tab. The patch is only necessary for the GUI, not for the CI tests which are run using `test-storybook` Chromatic is run automatically for pull requests through Github Actions -- see `.github/workflows/test.yml`. If you want to trigger visual tests from your own machine, add (or edit) the file `apps/storybook/.env`. This file is in .gitignore and will not be committed. Add the following to `.env`: ``` CHROMATIC_PROJECT_TOKEN= ``` Replace `` with the token found [here](https://www.chromatic.com/manage?appId=66fe736b9d639fe6801bf130&setup=true), under "Setup Chromatic with this project token". Then run these commands: ``` yarn build:storybook yarn chromatic ``` You can also replace the last command with e.g. ``` yarn chromatic --no-only-changed --only-story-names "Komponenter/Modal/*" ``` ...to only run tests for the Modal components --- .changeset/slimy-buttons-train.md | 5 + .github/workflows/test.yml | 14 ++ ...book-addon-a11y-npm-8.3.4-1c07bc384c.patch | 20 +++ apps/storybook/.env.example | 11 ++ apps/storybook/.storybook/main.ts | 1 + apps/storybook/.storybook/preview.tsx | 23 ++- apps/storybook/.storybook/test-runner.ts | 4 +- apps/storybook/chromatic.config.json | 7 + apps/storybook/package.json | 5 +- .../story-utils/customStylesDecorator.tsx | 54 +++++++ apps/storybook/story-utils/modes.ts | 6 + .../story-utils/type-extensions.d.ts | 147 ++++++++++++++++++ package.json | 5 +- packages/css/combobox.css | 1 + .../src/components/Avatar/Avatar.stories.tsx | 30 ++-- .../react/src/components/Button/Button.mdx | 4 +- .../src/components/Button/Button.stories.tsx | 121 +++++++------- .../src/components/Card/Card.stories.tsx | 34 ++-- .../src/components/Chip/Chip.stories.tsx | 12 +- .../components/Dropdown/Dropdown.stories.tsx | 33 +++- .../ErrorSummary/ErrorSummary.stories.tsx | 23 ++- .../components/HelpText/HelpText.stories.tsx | 29 ++-- .../src/components/Link/Link.stories.tsx | 11 +- .../src/components/List/List.stories.tsx | 13 +- .../src/components/Modal/Modal.stories.tsx | 51 ++++-- .../components/Popover/Popover.stories.tsx | 119 +++++++++----- .../components/SkipLink/SkipLink.stories.tsx | 27 +++- .../src/components/Table/Table.stories.tsx | 10 +- .../react/src/components/Tag/Tag.stories.tsx | 12 +- .../components/Tooltip/Tooltip.stories.tsx | 15 +- .../form/Combobox/Combobox.stories.tsx | 77 +++++++-- .../components/form/Switch/Switch.stories.tsx | 26 +++- .../form/Textarea/Textarea.stories.tsx | 12 +- .../loaders/Spinner/Spinner.stories.tsx | 24 +-- .../useDebounceCallback.stories.tsx | 1 + .../useMediaQuery/useMediaQuery.stories.tsx | 1 + .../useSynchronizedAnimation.stories.tsx | 29 ++-- packages/react/stories/showcase.stories.tsx | 9 ++ packages/react/stories/testing.stories.tsx | 18 +-- packages/react/tsconfig.json | 7 +- yarn.lock | 48 +++++- 41 files changed, 798 insertions(+), 301 deletions(-) create mode 100644 .changeset/slimy-buttons-train.md create mode 100644 .yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch create mode 100644 apps/storybook/.env.example create mode 100644 apps/storybook/chromatic.config.json create mode 100644 apps/storybook/story-utils/customStylesDecorator.tsx create mode 100644 apps/storybook/story-utils/modes.ts create mode 100644 apps/storybook/story-utils/type-extensions.d.ts diff --git a/.changeset/slimy-buttons-train.md b/.changeset/slimy-buttons-train.md new file mode 100644 index 0000000000..06e0a25d73 --- /dev/null +++ b/.changeset/slimy-buttons-train.md @@ -0,0 +1,5 @@ +--- +'@digdir/designsystemet-css': patch +--- + +Combobox: fix overflow on screens narrower than ~340px diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72c0ad6324..aee56beeca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # Necessary for Chromatic + fetch-depth: 0 - uses: ./.github/actions/gh-setup - name: Build run: yarn build - name: Types run: yarn types:react + - name: Test run: yarn test - name: 'Report Coverage' @@ -36,8 +40,10 @@ jobs: check_name: Unit Test Report check_annotations: true check_title_template: '{{FILE_NAME}} / {{TEST_NAME}}' + - name: Test CLI (create tokens, then build the theme) run: yarn test:cli + - name: Install Playwright run: npx playwright install --with-deps - name: Build storybook @@ -61,3 +67,11 @@ jobs: check_name: Storybook Test Report check_annotations: true check_title_template: '{{FILE_NAME}} / {{TEST_NAME}}' + + - name: Run Chromatic (visual and interaction tests) + uses: chromaui/action@latest + with: + workingDir: apps/storybook + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + exitZeroOnChanges: true + autoAcceptChanges: '{main,next}' diff --git a/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch b/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch new file mode 100644 index 0000000000..98cf5abcc4 --- /dev/null +++ b/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch @@ -0,0 +1,20 @@ +diff --git a/dist/preview.js b/dist/preview.js +index 2ebc8126dbb113e1d43f39f70f0ef9016b96f53f..9392d52eff52f083ced74c93b68680dc718f741a 100644 +--- a/dist/preview.js ++++ b/dist/preview.js +@@ -3,4 +3,4 @@ + var previewApi = require('storybook/internal/preview-api'); + var global = require('@storybook/global'); + +-var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global.global,channel=previewApi.addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelector(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); ++var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global.global,channel=previewApi.addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelectorAll(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); +diff --git a/dist/preview.mjs b/dist/preview.mjs +index 6e52b1b0b1c137af769e84a59ca32a0e8c91bbb7..dee1c98bfde648dd5f63037f16e954ef69913bb8 100644 +--- a/dist/preview.mjs ++++ b/dist/preview.mjs +@@ -1,4 +1,4 @@ + import { addons } from 'storybook/internal/preview-api'; + import { global } from '@storybook/global'; + +-var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global,channel=addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelector(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); ++var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global,channel=addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelectorAll(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); diff --git a/apps/storybook/.env.example b/apps/storybook/.env.example new file mode 100644 index 0000000000..498893f50e --- /dev/null +++ b/apps/storybook/.env.example @@ -0,0 +1,11 @@ + +## +## This file contains examples of useful environment variables for the Storybook project. +## To actually use them, create a .env file in this directory, and set the desired +## environment variables. That file is in .gitignore and will never be commited. +## + +# To run Chromatic locally, set this var and replace with the token from +# https://www.chromatic.com/manage?appId=66fe736b9d639fe6801bf130&view=configure +# ...under "Setup Chromatic with this project token". +CHROMATIC_PROJECT_TOKEN= diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts index c642868661..f3686651f6 100644 --- a/apps/storybook/.storybook/main.ts +++ b/apps/storybook/.storybook/main.ts @@ -38,6 +38,7 @@ const config: StorybookConfig = { getAbsolutePath('@chromatic-com/storybook'), getAbsolutePath('@storybook/addon-storysource'), '@storybook/addon-themes', + 'storybook-addon-pseudo-states', ], staticDirs: ['../assets'], framework: { diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx index 9a127c6920..4fce15c045 100644 --- a/apps/storybook/.storybook/preview.tsx +++ b/apps/storybook/.storybook/preview.tsx @@ -8,10 +8,11 @@ import type { Preview } from '@storybook/react'; import type { LinkProps } from '@digdir/designsystemet-react'; import { Link, List, Paragraph, Table } from '@digdir/designsystemet-react'; +import { customStylesDecorator } from '../story-utils/customStylesDecorator'; +import { allModes, viewportWidths } from '../story-utils/modes'; import customTheme from './customTheme'; const viewports: Record = {}; -const viewportWidths = [320, 375, 576, 768, 992, 1200, 1440]; for (const width of viewportWidths) { viewports[`${width}px`] = { @@ -143,10 +144,30 @@ const preview: Preview = { viewport: { viewports, }, + chromatic: { + modes: { + mobile: allModes[320], + desktop: allModes[1200], + }, + }, backgrounds: { disable: true, }, + a11y: { + element: ['#storybook-root', '[data-floating-ui-portal]'], + config: { + rules: [ + { + // Ignore the color-contrast rule for the ":active" pseudo-state + id: 'color-contrast', + selector: + '#storybook-root:not(.pseudo-active-all) *:not(.pseudo-active)', + }, + ], + }, + }, }, + decorators: [customStylesDecorator], }; /* Add this back when https://github.com/storybookjs/storybook/issues/29189 is fixed */ diff --git a/apps/storybook/.storybook/test-runner.ts b/apps/storybook/.storybook/test-runner.ts index c76f512342..ad4ccffd61 100644 --- a/apps/storybook/.storybook/test-runner.ts +++ b/apps/storybook/.storybook/test-runner.ts @@ -23,7 +23,7 @@ const config: TestRunnerConfig = { */ await configureAxe(page, { - // Apply story-level a11y rules + // Apply a11y rules set through parameters (global, component or story level) rules: storyContext.parameters?.a11y?.config?.rules, }); @@ -31,7 +31,7 @@ const config: TestRunnerConfig = { if (!isA11yDisabled) { await checkA11y( page, - '#storybook-root', + storyContext.parameters?.a11y?.element ?? ['#storybook-root'], { detailedReport: true, detailedReportOptions: { diff --git a/apps/storybook/chromatic.config.json b/apps/storybook/chromatic.config.json new file mode 100644 index 0000000000..25df8739d1 --- /dev/null +++ b/apps/storybook/chromatic.config.json @@ -0,0 +1,7 @@ +{ + "onlyChanged": true, + "projectId": "Project:66fe736b9d639fe6801bf130", + "storybookBaseDir": "apps/storybook", + "storybookBuildDir": "dist", + "zip": true +} diff --git a/apps/storybook/package.json b/apps/storybook/package.json index 8d44d205c8..1fbeb38b86 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -7,7 +7,8 @@ "build": "storybook build -o ./dist", "static-storybook": "npx http-server dist --port 6006 --silent", "test-storybook": "rimraf test-report.xml && wait-on tcp:6006 && test-storybook --junit", - "run-and-test-storybook": "concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'yarn static-storybook' 'yarn test-storybook'" + "run-and-test-storybook": "concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'yarn static-storybook' 'yarn test-storybook'", + "chromatic": "npx chromatic" }, "author": "", "license": "ISC", @@ -22,7 +23,7 @@ "@digdir/designsystemet-css": "workspace:^", "@digdir/designsystemet-react": "workspace:^", "@digdir/designsystemet-theme": "workspace:^", - "@storybook/addon-a11y": "^8.3.4", + "@storybook/addon-a11y": "patch:@storybook/addon-a11y@npm%3A8.3.4#~/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch", "@storybook/addon-essentials": "^8.3.4", "@storybook/addon-interactions": "^8.3.4", "@storybook/addon-links": "^8.3.4", diff --git a/apps/storybook/story-utils/customStylesDecorator.tsx b/apps/storybook/story-utils/customStylesDecorator.tsx new file mode 100644 index 0000000000..fb9f9fdda3 --- /dev/null +++ b/apps/storybook/story-utils/customStylesDecorator.tsx @@ -0,0 +1,54 @@ +import type { Decorator } from '@storybook/react'; + +/** + * This decorator is used to customize the style of the root story element. + * It is useful for customizing the layout, or when you need to account + * for elements that would otherwise not be visible in Chromatic's visual snapshots. + * + * The decorator is added globally, and can be configured through `parameters.customStyles` + * at the meta or story level. E.g. + * ```ts + * parameters: { + * customStyles: { + * // These apply both in docs mode and story mode + * display: 'flex', + * gap: '8px', + * docs: { + * // These apply only when the story renders in a docs page + * height: '200px' + * }, + * story: { + * // These apply only when the story is viewed individually + * height: '100vh' + * } + * } + * } + * ``` + * + * By default, the decorator sets `overflow: hidden` so you can see in Storybook exactly + * what Chromatic's snapshot will be, and `padding: 1rem` to account for most overflowing + * elements like focus styles, badges etc. + * + * From Chromatic's documentation: + * > Snapshots can sometimes exclude outline and other focus styles because Chromatic + * > trims each snapshot to the dimensions of the root node of the story. To capture + * > those styles, wrap the story in a decorator that adds slight padding. + */ +export const customStylesDecorator: Decorator = (Story, ctx) => { + const { docs, story, ...style } = ctx.parameters.customStyles ?? {}; + + return ( +
+ +
+ ); +}; diff --git a/apps/storybook/story-utils/modes.ts b/apps/storybook/story-utils/modes.ts new file mode 100644 index 0000000000..7f50b65c74 --- /dev/null +++ b/apps/storybook/story-utils/modes.ts @@ -0,0 +1,6 @@ +import { fromPairs } from 'ramda'; +export const viewportWidths = [320, 375, 576, 768, 992, 1200, 1440] as const; + +export const allModes = fromPairs( + viewportWidths.map((width) => [width, { viewport: { width } }]), +); diff --git a/apps/storybook/story-utils/type-extensions.d.ts b/apps/storybook/story-utils/type-extensions.d.ts new file mode 100644 index 0000000000..07e3899186 --- /dev/null +++ b/apps/storybook/story-utils/type-extensions.d.ts @@ -0,0 +1,147 @@ +import type {} from '@storybook/types'; +import type { ElementContext, Spec } from 'axe-core'; +import type { configureAxe } from 'axe-playwright'; +import type { CSSProperties } from 'react'; + +type AxeConfig = Parameters[1]; + +type ChromaticViewport = { + width?: number | `${string}px`; + height?: number | `${string}px`; +}; + +declare module '@storybook/types' { + type PseudoState = + | 'hover' + | 'active' + | 'focusVisible' + | 'focusWithin' + | 'focus' + | 'visited' + | 'link' + | 'target'; + + type PseudoValue = boolean | string | string[]; + + interface Parameters { + /** + * Set custom styling for the story's root element. The default styling is: + * ```css + * { overflow: hidden; padding: 1rem; } + * ``` + * + * This is a custom parameter, implemented by `customStylesDecorator.ts`. + * */ + customStyles?: CSSProperties & { + /** Styles that only apply when viewing a docs page */ + docs?: CSSProperties; + /** Styles that only apply when viewing an individual story */ + story?: CSSProperties; + }; + + /** + * Set the story layout. + * + * This is a standard Storybook parameter, + * [see the docs](https://storybook.js.org/docs/configure/story-layout) + */ + layout?: 'centered' | 'fullscreen' | 'padded'; + + /** + * Configure `@storybook/addon-a11y`. See [the documentation](https://storybook.js.org/addons/@storybook/addon-a11y) + */ + a11y?: { + disable?: boolean; + element?: ElementContext; + config?: Spec; + manual?: boolean; + }; + + /** + * Configure Chromatic. See [the documentation](https://www.chromatic.com/docs/config-with-story-params/). + */ + chromatic?: { + /** Disable visual snapshots at the component or story level */ + disableSnapshot?: true; + /** + * By default, CSS animations are paused at the end of their animation cycle + * when tests are run in Chromatic. Setting this to false will pause animations + * at the first frame instead. + */ + pauseAnimationAtEnd?: false; + /** Delay in ms before running tests in Chromatic */ + delay?: number; + /** + * Allows you to fine-tune the threshold for visual change between snapshots before + * Chromatic flags them. Must be a number from 0 to 1. 0 is the most accurate, while + * 1 is the least accurate. + * + * @default 0.063 + */ + diffThreshold?: number; + /** + * Modes allow separate snapshots and baselines for a collection + * of parameters like viewport size, theme etc. + */ + modes?: Record< + string, + { + /** + * Disable a mode that has been enabled at a higher level. + * E.g. disable a global mode for a specific story. + **/ + disable?: true; + /** + * The viewport to use. + * + * This parameter can either be an object with height and/or width (in px), or + * the name of one of the viewports configured in `parameters.viewports` in `.storybook/preview.tsx` + */ + viewport?: ChromaticViewport | string; + // ...any other globals from Storybook, addons or decorators which we want + // to use in modes can also be added here + } + >; + }; + + /** + * Toggle pseudo states. Supported states are listed in {@link PseudoState}. + * Read [Storybook Pseudo States documentation](https://github.com/chromaui/storybook-addon-pseudo-states) + * for more info. + * + * Each state can be toggled on/off: + * ```ts + * export const Hover = () => + * Hover.parameters = { pseudo: { hover: true } } + * ``` + * + * You can also use CSS selectors to target the elements you want to enable the state for: + * ```ts + * export const Buttons = () => ( + * <> + * + * + * + * + * ) + * Buttons.parameters = { + * pseudo: { + * hover: ["#one", "#two", "#three"], + * focus: ["#two", "#three"], + * active: "#three", + * }, + * } + * ``` + */ + pseudo?: { + /** + * If you need to render elements outside Storybook's root element, you can set + * rootSelector to override it. This is convenient for portals, dialogs, tooltips, etc. + * @default "#storybook-root" + */ + rootSelector?: string; + } & { + [K in PseudoState]?: PseudoValue; + }; + } +} diff --git a/package.json b/package.json index f842b55ba9..11700ba30e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "types:react": "yarn workspace @digdir/designsystemet-react types", "types:storefront": "yarn workspace storefront types", "version-packages": "changeset version", - "publish": "yarn build && changeset publish" + "publish": "yarn build && changeset publish", + "chromatic": "yarn workspace @designsystemet/storybook chromatic" }, "devDependencies": { "@biomejs/biome": "1.8.3", @@ -46,8 +47,10 @@ "@vitejs/plugin-react-swc": "^3.7.0", "@vitest/coverage-v8": "^2.0.5", "@vitest/expect": "^2.0.5", + "chromatic": "^11.11.0", "copyfiles": "^2.4.1", "prettier": "^3.3.3", + "storybook-addon-pseudo-states": "^4.0.2", "stylelint": "^16.8.1", "stylelint-config-standard": "^36.0.1", "typescript": "^5.5.4", diff --git a/packages/css/combobox.css b/packages/css/combobox.css index bbc7420ca2..704013e5bb 100644 --- a/packages/css/combobox.css +++ b/packages/css/combobox.css @@ -40,6 +40,7 @@ .ds-combobox__input__wrapper .ds-combobox__input { height: 100%; min-width: 50px; + width: 100%; flex-grow: 1; appearance: none; border: none; diff --git a/packages/react/src/components/Avatar/Avatar.stories.tsx b/packages/react/src/components/Avatar/Avatar.stories.tsx index bdceb4b65d..3c1f80c25a 100644 --- a/packages/react/src/components/Avatar/Avatar.stories.tsx +++ b/packages/react/src/components/Avatar/Avatar.stories.tsx @@ -7,26 +7,18 @@ import { Badge, Dropdown } from '../'; type Story = StoryFn; -const meta: Meta = { +const meta: Meta = { title: 'Komponenter/Avatar', component: Avatar, parameters: { layout: 'padded', + customStyles: { + display: 'flex', + gap: 'var(--ds-spacing-2)', + justifyContent: 'center', + alignItems: 'center', + }, }, - decorators: [ - (Story) => ( -
- -
- ), - ], }; export default meta; @@ -101,7 +93,7 @@ export const InDropdown: Story = () => ( Velg Profil - + Alle kontoer @@ -122,6 +114,12 @@ export const InDropdown: Story = () => ( ); +InDropdown.parameters = { + layout: 'fullscreen', + customStyles: { + height: '320px', + }, +}; export const AsLink: Story = () => (
diff --git a/packages/react/src/components/Button/Button.mdx b/packages/react/src/components/Button/Button.mdx index f547a35a39..117f245aa9 100644 --- a/packages/react/src/components/Button/Button.mdx +++ b/packages/react/src/components/Button/Button.mdx @@ -54,11 +54,11 @@ Knappene kommer i to hovedfarger `accent` og `neutral`. Hvis du har overstyrt fa - `accent`-fargen stikker seg mer ut enn `neutral` og brukes gjerne når handlingen skal ha oppmerksomhet. - + - `neutral`-fargen er mer nøytral og kan brukes når det er flere knapper på en side som ikke skal ta så mye oppmerksomhet. - + ### Danger diff --git a/packages/react/src/components/Button/Button.stories.tsx b/packages/react/src/components/Button/Button.stories.tsx index 3ee84be702..28417350bc 100644 --- a/packages/react/src/components/Button/Button.stories.tsx +++ b/packages/react/src/components/Button/Button.stories.tsx @@ -1,4 +1,3 @@ -import { Stack } from '@doc-components'; import { ArrowForwardIcon, ArrowRightIcon, @@ -8,15 +7,12 @@ import { ExternalLinkIcon, PencilWritingIcon, PlusCircleIcon, - PlusIcon, PrinterSmallIcon, TrashIcon, } from '@navikt/aksel-icons'; -import type { Meta, ReactRenderer, StoryFn, StoryObj } from '@storybook/react'; -import type { PartialStoryFn } from '@storybook/types'; - -import { Spinner, Tooltip } from '../'; +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; +import { Tooltip } from '../'; import { Button } from './'; type Story = StoryObj; @@ -24,15 +20,20 @@ type Story = StoryObj; const meta: Meta = { title: 'Komponenter/Button', component: Button, + parameters: { + customStyles: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + flexWrap: 'wrap', + gap: 'var(--ds-spacing-4)', + }, + }, }; export default meta; -const stack = (Story: PartialStoryFn) => ( - - - -); export const Preview: Story = { render: ({ ...args }) => { return @@ -104,14 +109,24 @@ export const Second: StoryFn = () => ( Rediger - ); -Second.decorators = [stack]; +export const NeutralHover = Neutral.bind({}); +NeutralHover.parameters = { + pseudo: { hover: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; + +export const NeutralPressed = Neutral.bind({}); +NeutralPressed.parameters = { + pseudo: { active: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; export const Danger: StoryFn = () => ( <> @@ -119,21 +134,28 @@ export const Danger: StoryFn = () => ( Slett + + ); -Danger.decorators = [ - (Story) => ( - - - - ), -]; +export const DangerHover = Danger.bind({}); +DangerHover.parameters = { + pseudo: { hover: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; + +export const DangerPressed = Danger.bind({}); +DangerPressed.parameters = { + pseudo: { active: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; export const CombinedColors: StoryFn = () => ( <> @@ -149,8 +171,6 @@ export const CombinedColors: StoryFn = () => ( ); -CombinedColors.decorators = [stack]; - export const AsLink: StoryFn = () => ( ); - -IconsOnlyPrimary.decorators = [stack]; diff --git a/packages/react/src/components/Card/Card.stories.tsx b/packages/react/src/components/Card/Card.stories.tsx index 696a9d177b..0f0f3e32d9 100644 --- a/packages/react/src/components/Card/Card.stories.tsx +++ b/packages/react/src/components/Card/Card.stories.tsx @@ -18,27 +18,21 @@ type Story = StoryFn; export default { title: 'Komponenter/Card', component: Card, - decorators: [ - (Story) => ( -
- -
- ), - ], -} as Meta; + parameters: { + layout: 'fullscreen', + customStyles: { + width: '100%', + maxWidth: 800, + alignItems: 'center', + display: 'grid', + gap: 'var(--ds-spacing-4)', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px , 1fr))', + }, + }, +} satisfies Meta; export const Preview: Story = (args) => ( - + Card Neutral @@ -170,7 +164,7 @@ export const Media: Story = () => ( ); export const Video: Story = () => ( - +