Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Accordion): use native <details> element #2190

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 10 additions & 18 deletions packages/css/accordion.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
}

.ds-accordion__header {
cursor: pointer;
padding: var(--ds-spacing-4);
margin: 0;
width: 100%;
display: flex;
Expand All @@ -41,32 +43,22 @@
background-color: var(--dsc-accordion-button-background);
}

.ds-accordion__button {
cursor: pointer;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
gap: var(--ds-spacing-2);
margin: 0;
padding: var(--ds-spacing-4);
background-color: transparent;
border: none;
font-family: inherit;
}

.ds-accordion__item--open .ds-accordion__header {
.ds-accordion__item[open] .ds-accordion__header {
background-color: var(--dsc-accordion-button-background-open);
}

.ds-accordion__item:focus-within {
position: relative;
}

.ds-accordion__item:where(.ds-accordion__item--open) .ds-accordion__expand-icon {
.ds-accordion__item[open] .ds-accordion__expand-icon {
transform: rotateZ(180deg);
}

.ds-accordion__item--controlled:not([open]) .ds-accordion__content {
display: none; /* Turn off search-in-page-to-open when state is controlled */
}

.ds-accordion__item:not(:first-child) .ds-accordion__header {
border-top: 1px solid var(--dsc-accordion-border-color);
}
Expand All @@ -80,7 +72,7 @@
border-top-right-radius: var(--dsc-accordion-border-radius);
}

.ds-accordion--border .ds-accordion__item:last-of-type:not(.ds-accordion__item--open) .ds-accordion__header:first-of-type {
.ds-accordion--border .ds-accordion__item:last-of-type:not([open]) .ds-accordion__header:first-of-type {
border-bottom-left-radius: var(--dsc-accordion-border-radius);
border-bottom-right-radius: var(--dsc-accordion-border-radius);
}
Expand All @@ -90,7 +82,7 @@
background-color: var(--dsc-accordion-icon-background-hover);
}

.ds-accordion__item--open .ds-accordion__header:hover .ds-accordion__expand-icon {
.ds-accordion__item[open] .ds-accordion__header:hover .ds-accordion__expand-icon {
background-color: var(--dsc-accordion-icon-background-active);
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"@floating-ui/react": "0.26.12",
"@navikt/aksel-icons": "^5.12.2",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-virtual": "^3.5.1"
"@tanstack/react-virtual": "^3.5.1",
"@u-elements/u-details": "^0.0.5"
},
"devDependencies": {
"copyfiles": "^2.4.1",
Expand Down
16 changes: 8 additions & 8 deletions packages/react/src/components/Accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default {
export const Preview: StoryFn<typeof Accordion.Root> = (args) => (
<Accordion.Root {...args}>
<Accordion.Item>
<Accordion.Heading level={3}>
<Accordion.Heading>
Hvem kan registrere seg i Frivillighetsregisteret?
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -27,7 +27,7 @@ export const Preview: StoryFn<typeof Accordion.Root> = (args) => (
</Accordion.Content>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading level={3}>
<Accordion.Heading>
Hvordan går jeg fram for å registrere i Frivillighetsregisteret?
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -45,7 +45,7 @@ export const AccordionBorder: StoryFn<typeof Accordion.Root> = () => (
color='subtle'
>
<Accordion.Item>
<Accordion.Heading level={3}>Vedlegg</Accordion.Heading>
<Accordion.Heading>Vedlegg</Accordion.Heading>
<Accordion.Content>Vedlegg 1, vedlegg 2, vedlegg 3</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
Expand All @@ -57,7 +57,7 @@ export const AccordionColor: StoryFn<typeof Accordion.Root> = () => (
color='brand2'
>
<Accordion.Item>
<Accordion.Heading level={3}>
<Accordion.Heading>
Hvordan får jeg tildelt et jegernummer?
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -66,7 +66,7 @@ export const AccordionColor: StoryFn<typeof Accordion.Root> = () => (
</Accordion.Content>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading level={3}>
<Accordion.Heading>
Jeg har glemt jegernummeret mitt. Hvor finner jeg dette?
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -92,7 +92,7 @@ export const Controlled: StoryFn<typeof Accordion.Root> = () => {
<br />
<Accordion.Root>
<Accordion.Item open={open}>
<Accordion.Heading onHeaderClick={() => setOpen(!open)}>
<Accordion.Heading onClick={() => setOpen(!open)}>
Enkeltpersonforetak
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -104,7 +104,7 @@ export const Controlled: StoryFn<typeof Accordion.Root> = () => {
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={open}>
<Accordion.Heading onHeaderClick={() => setOpen(!open)}>
<Accordion.Heading onClick={() => setOpen(!open)}>
Aksjeselskap (AS)
</Accordion.Heading>
<Accordion.Content>
Expand All @@ -116,7 +116,7 @@ export const Controlled: StoryFn<typeof Accordion.Root> = () => {
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={open}>
<Accordion.Heading onHeaderClick={() => setOpen(!open)}>
<Accordion.Heading onClick={() => setOpen(!open)}>
Ansvarlig selskap (ANS/DA)
</Accordion.Heading>
<Accordion.Content>
Expand Down
16 changes: 0 additions & 16 deletions packages/react/src/components/Accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ describe('Accordion', () => {
render(<TestComponent />);
const accordionExpandButton = screen.getByRole('button');

expect(
screen.getByRole('heading', {
name: 'Accordion Header Title Text',
}),
).toBeInTheDocument();
expect(screen.getByText('The fantastic accordion content text'));
expect(screen.getByText('Accordion Header Title Text'));
expect(accordionExpandButton).toHaveAttribute('aria-expanded', 'false');
Expand All @@ -56,17 +51,6 @@ describe('Accordion', () => {
const accordionExpandButton = screen.getByRole('button');
expect(accordionExpandButton).toHaveAttribute('aria-expanded', 'true');
});

test('should render heading as level 1 by default', () => {
render(<TestComponent />);

expect(
screen.getByRole('heading', {
name: 'Accordion Header Title Text',
level: 1,
}),
);
});
});

describe('Accordion Accessibility', () => {
Expand Down
43 changes: 12 additions & 31 deletions packages/react/src/components/Accordion/AccordionContent.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import cl from 'clsx/lite';
import type { HTMLAttributes } from 'react';
import { forwardRef, useContext } from 'react';
import { forwardRef } from 'react';

import { AnimateHeight } from '../../utilities/AnimateHeight';
import { Paragraph } from '../Typography';

import { AccordionItemContext } from './AccordionItem';
import { Paragraph } from '..';

export type AccordionContentProps = HTMLAttributes<HTMLDivElement>;

Expand All @@ -17,34 +14,18 @@ export type AccordionContentProps = HTMLAttributes<HTMLDivElement>;
export const AccordionContent = forwardRef<
HTMLDivElement,
AccordionContentProps
>(({ children, className, ...rest }, ref) => {
const context = useContext(AccordionItemContext);

if (context === null) {
console.error(
'<Accordion.Content> has to be used within an <Accordion.Item>',
);
return null;
}

>(({ className, ...rest }, ref) => {
return (
<AnimateHeight
id={context.contentId}
open={context.open}
<Paragraph
asChild
size='sm'
>
<Paragraph
asChild
size='sm'
>
<div
ref={ref}
className={cl('ds-accordion__content', className)}
{...rest}
>
{children}
</div>
</Paragraph>
</AnimateHeight>
<div
ref={ref}
className={cl('ds-accordion__content', className)}
{...rest}
/>
</Paragraph>
);
});

Expand Down
78 changes: 21 additions & 57 deletions packages/react/src/components/Accordion/AccordionHeading.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,40 @@
import { ChevronDownIcon } from '@navikt/aksel-icons';
import cl from 'clsx/lite';
import type { ReactNode, MouseEventHandler, HTMLAttributes } from 'react';
import { forwardRef, useContext } from 'react';
import type { ReactNode, HTMLAttributes } from 'react';
import { forwardRef } from 'react';

import { Paragraph, Heading } from '../Typography';

import { AccordionItemContext } from './AccordionItem';
import { Paragraph } from '..';

export type AccordionHeaderProps = {
/**
* Heading level. Use this to make sure the heading is correct according to you page heading levels
* @default 1
*/
level?: 1 | 2 | 3 | 4 | 5 | 6;
/** Handle when clicked on header */
onHeaderClick?: MouseEventHandler<HTMLButtonElement> | undefined;
/** Heading text */
children: ReactNode;
} & HTMLAttributes<HTMLHeadingElement>;
} & HTMLAttributes<HTMLElement>;

/**
* Accordion header component, contains a button to toggle the content.
* @example
* <AccordionHeader>Header</AccordionHeader>
*/
export const AccordionHeading = forwardRef<
HTMLHeadingElement,
AccordionHeaderProps
>(({ level = 1, children, className, onHeaderClick, ...rest }, ref) => {
const context = useContext(AccordionItemContext);

if (context === null) {
console.error(
'<Accordion.Header> has to be used within an <Accordion.Item>',
);
return null;
}

const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
context.toggleOpen();
onHeaderClick && onHeaderClick(e);
};

return (
<Heading
export const AccordionHeading = forwardRef<HTMLElement, AccordionHeaderProps>(
({ children, className, ...rest }, ref) => (
<u-summary
ref={ref}
size='xs'
level={level}
className={cl('ds-accordion__header', className)}
class={cl('ds-accordion__header ds-focus', className)}
{...rest}
>
<button
type='button'
className={cl('ds-accordion__button', `ds-focus`)}
onClick={handleClick}
aria-expanded={context.open}
aria-controls={context.contentId}
<ChevronDownIcon
aria-hidden
className='ds-accordion__expand-icon'
fontSize='1.5rem'
/>
<Paragraph
asChild
size='sm'
>
<ChevronDownIcon
aria-hidden
className='ds-accordion__expand-icon'
fontSize={'1.5rem'}
/>
<Paragraph
asChild
size='sm'
>
<span>{children}</span>
</Paragraph>
</button>
</Heading>
);
});
<span>{children}</span>
</Paragraph>
</u-summary>
),
);

AccordionHeading.displayName = 'AccordionHeading';
Loading
Loading