Skip to content

Commit

Permalink
feat: sizing, arrow position for accordion (#243)
Browse files Browse the repository at this point in the history
* feat: sizing, arrow position for accordion

* cleanup
  • Loading branch information
mikeldking authored Oct 15, 2024
1 parent 7ad18ba commit 6181007
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 62 deletions.
101 changes: 71 additions & 30 deletions src/accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import { Heading } from '../content';
import { Icon, ArrowIosDownwardOutline } from '../icon';
import { classNames } from '../utils/classNames';
import theme from '../theme';
import { AccordionContext, useAccordionContext } from './context';

export interface AccordionProps {
children: ReactNode;
size?: 'M' | 'L';
arrowPosition?: 'start' | 'end';
}

const sizeMap: Record<NonNullable<AccordionProps['size']>, string> = {
M: 'medium',
L: 'large',
};

const accordionItemCSS = css`
cursor: pointer;
height: 40px;
height: var(--ac-accordion-item-height);
padding: 0 var(--accordion-padding-side);
display: block;
width: 100%;
Expand All @@ -37,27 +45,31 @@ const accordionItemCSS = css`
* Accordion component for having collapsible sections
* @see https://www.w3.org/TR/wai-aria-practices-1.1/#accordion
*/
export function Accordion({ children }: AccordionProps) {
export function Accordion({ children, ...props }: AccordionProps) {
return (
<div
className={`ac-accordion ac-accordion--default`}
role="region"
css={css`
--accordion-animation-duration: ${theme.animation.global.duration}ms;
&.ac-accordion--default {
--accordion-padding-top: var(--ac-global-dimension-static-size-100);
--accordion-padding-side: var(--ac-global-dimension-static-size-200);
--accordion-font-size: ${theme.typography.sizes.medium.fontSize}px;
}
.ac-accordion-item:not(:last-of-type) {
.ac-accordion-itemContent {
border-bottom: 1px solid var(--ac-global-border-color-dark);
<AccordionContext.Provider value={props}>
<div
className={`ac-accordion ac-accordion--default`}
role="region"
css={css`
--accordion-animation-duration: ${theme.animation.global.duration}ms;
&.ac-accordion--default {
--accordion-padding-top: var(--ac-global-dimension-static-size-100);
--accordion-padding-side: var(
--ac-global-dimension-static-size-200
);
--accordion-font-size: ${theme.typography.sizes.medium.fontSize}px;
}
}
`}
>
{children}
</div>
.ac-accordion-item:not(:last-of-type) {
.ac-accordion-itemContent {
border-bottom: 1px solid var(--ac-global-border-color-dark);
}
}
`}
>
{children}
</div>
</AccordionContext.Provider>
);
}

Expand Down Expand Up @@ -93,6 +105,8 @@ export function AccordionItem(props: AccordionItemProps) {
children,
extra,
} = props;
const { arrowPosition = 'end', size = 'M' } = useAccordionContext();
const sizeVariant = sizeMap[size];
const [isOpen, setIsOpen] = useState(defaultIsOpen);
const contentId = `${id}-content`,
headerId = `${id}-heading`;
Expand Down Expand Up @@ -121,6 +135,7 @@ export function AccordionItem(props: AccordionItemProps) {
<div
className={classNames('ac-accordion-item', {
'is-open': isOpen,
[`ac-accordion-item--${sizeVariant}`]: sizeVariant,
})}
role="presentation"
css={css`
Expand All @@ -129,6 +144,12 @@ export function AccordionItem(props: AccordionItemProps) {
transform: rotate(180deg);
}
}
&.ac-accordion-item--medium {
--ac-accordion-item-height: 40px;
}
&.ac-accordion-item--large {
--ac-accordion-item-height: 48px;
}
`}
>
<div
Expand All @@ -143,7 +164,10 @@ export function AccordionItem(props: AccordionItemProps) {
aria-controls={contentId}
aria-expanded={isOpen}
>
<Heading level={3}>{titleEl}</Heading>
<FlexRow>
{arrowPosition === 'start' && <ArrowIcon />}
<Heading level={3}>{titleEl}</Heading>
</FlexRow>
<div
css={css`
display: flex;
Expand All @@ -153,15 +177,7 @@ export function AccordionItem(props: AccordionItemProps) {
`}
>
{extra && <StopEventPropagation>{extra}</StopEventPropagation>}
<Icon
svg={<ArrowIosDownwardOutline />}
className="ac-accordion-itemIndicator"
css={css`
transition: transform ease var(--accordion-animation-duration);
transform: rotate(0deg);
`}
aria-hidden={true}
/>
{arrowPosition === 'end' && <ArrowIcon />}
</div>
</div>

Expand Down Expand Up @@ -195,3 +211,28 @@ function StopEventPropagation(props: PropsWithChildren) {
</div>
);
}

function ArrowIcon() {
return (
<Icon
svg={<ArrowIosDownwardOutline />}
className="ac-accordion-itemIndicator"
css={css`
transition: transform ease var(--accordion-animation-duration);
transform: rotate(0deg);
`}
aria-hidden={true}
/>
);
}

const flexRowCSS = css`
display: flex;
flex-direction: row;
align-items: center;
gap: var(--ac-global-dimension-static-size-100);
`;

function FlexRow(props: PropsWithChildren) {
return <div css={flexRowCSS}>{props.children}</div>;
}
23 changes: 23 additions & 0 deletions src/accordion/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext, useContext } from 'react';
import { AccordionProps } from './Accordion';

interface AccordionContextType {
/**
* The position of the arrow icon in the accordion item
* @default 'end'
*/
arrowPosition?: AccordionProps['arrowPosition'];
/**
* The size of the accordion item
* @default 'M'
*/
size?: AccordionProps['size'];
}
export const AccordionContext = createContext<AccordionContextType>({
arrowPosition: 'end',
size: 'M',
});

export function useAccordionContext(): AccordionContextType {
return useContext(AccordionContext);
}
99 changes: 67 additions & 32 deletions stories/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Text,
Button,
View,
Flex,
} from '../src';
import InfoTip from './components/InfoTip';
import { ThemeSplitView } from './components/ThemeSplitView';
Expand Down Expand Up @@ -40,38 +41,72 @@ export default meta;

const Template: Story<AccordionProps> = args => {
const content = (
<Card
title="Model Health"
subTitle={'An overview of the the health of your model'}
bodyStyle={{ padding: 0, overflow: 'hidden' }}
style={{ width: 700 }}
collapsible
>
<Accordion {...args}>
<AccordionItem
title="2 Predictions"
titleExtra={<InfoTip>Description of predictions</InfoTip>}
extra={
<Button variant="default" size="compact">
Add
</Button>
}
id="predictions"
>
<AccordionContents />
</AccordionItem>
<AccordionItem
title="Features"
titleExtra={<Counter variant="light">100</Counter>}
id="features"
>
<AccordionContents />
</AccordionItem>
<AccordionItem title="10 Actuals" id="actuals">
<AccordionContents />
</AccordionItem>
</Accordion>
</Card>
<Flex direction="column" gap="size-200">
<Card
title="Medium Accordion"
subTitle={'An overview of the the health of your model'}
bodyStyle={{ padding: 0, overflow: 'hidden' }}
style={{ width: 700 }}
collapsible
>
<Accordion {...args}>
<AccordionItem
title="2 Predictions"
titleExtra={<InfoTip>Description of predictions</InfoTip>}
extra={
<Button variant="default" size="compact">
Add
</Button>
}
id="predictions"
>
<AccordionContents />
</AccordionItem>
<AccordionItem
title="Features"
titleExtra={<Counter variant="light">100</Counter>}
id="features"
>
<AccordionContents />
</AccordionItem>
<AccordionItem title="10 Actuals" id="actuals">
<AccordionContents />
</AccordionItem>
</Accordion>
</Card>
<Card
title="Large Accordion"
subTitle={'An overview of the the health of your model'}
bodyStyle={{ padding: 0, overflow: 'hidden' }}
style={{ width: 700 }}
collapsible
>
<Accordion {...args} size="L" arrowPosition="start">
<AccordionItem
title="2 Predictions"
titleExtra={<InfoTip>Description of predictions</InfoTip>}
extra={
<Button variant="default" size="compact">
Add
</Button>
}
id="predictions"
>
<AccordionContents />
</AccordionItem>
<AccordionItem
title="Features"
titleExtra={<Counter variant="light">100</Counter>}
id="features"
>
<AccordionContents />
</AccordionItem>
<AccordionItem title="10 Actuals" id="actuals">
<AccordionContents />
</AccordionItem>
</Accordion>
</Card>
</Flex>
);
return <ThemeSplitView>{content}</ThemeSplitView>;
};
Expand Down

0 comments on commit 6181007

Please sign in to comment.