Feat/590 convert accordiong to use radix (#601)

* fix: [#590] fix full length on radix accordion

* fix: [#590] accordion to handle multiple panels

* fix: [#590] add radix ui react icons

* fix: [#590] follolw primitive naming conventions and remove stitches

* fix: [#590] make it collapsible

* fix: #590 remove key prop for accordion item
This commit is contained in:
m.ray 2022-06-22 13:50:38 +01:00 committed by GitHub
parent 74c245ca7a
commit c85e6a313f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 230 additions and 167 deletions

View File

@ -8,7 +8,7 @@ import {
import { import {
KeyValueTable, KeyValueTable,
KeyValueTableRow, KeyValueTableRow,
AccordionPanel, Accordion,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import startCase from 'lodash/startCase'; import startCase from 'lodash/startCase';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
@ -23,97 +23,94 @@ export interface InfoProps {
export const Info = ({ market }: InfoProps) => { export const Info = ({ market }: InfoProps) => {
const headerClassName = const headerClassName =
'text-h5 font-bold uppercase text-black dark:text-white'; 'text-h5 font-bold uppercase text-black dark:text-white';
const marketDataPanels = [
{
title: t('Current fees'),
content: (
<>
<MarketInfoTable data={market.fees.factors} asPercentage={true} />
<p className="text-ui-small">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
),
},
{
title: t('Market data'),
content: (
<MarketInfoTable
data={market.data}
decimalPlaces={market.decimalPlaces}
/>
),
},
];
const marketSpecPanels = [
{
title: t('Key details'),
content: (
<MarketInfoTable
data={pick(
market,
'name',
'decimalPlaces',
'positionDecimalPlaces',
'tradingMode',
'state'
)}
/>
),
},
{
title: t('Instrument'),
content: (
<MarketInfoTable
data={{
product: market.tradableInstrument.instrument.product,
...market.tradableInstrument.instrument.product.settlementAsset,
}}
/>
),
},
{
title: t('Risk factors'),
content: (
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
),
},
{
title: t('Risk model'),
content: (
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
),
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable data={trigger} />,
})
),
];
return ( return (
<div className="p-16 flex flex-col gap-32"> <div className="p-16 flex flex-col gap-32">
<div className="flex flex-col gap-12"> <div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market data')}</p> <p className={headerClassName}>{t('Market data')}</p>
<AccordionPanel <Accordion panels={marketDataPanels} />
key="fees"
title={t('Current fees')}
content={
<>
<MarketInfoTable data={market.fees.factors} asPercentage={true} />
<p className="text-ui-small">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
}
/>
<AccordionPanel
key="market-data"
title={t('Market data')}
content={
<MarketInfoTable
data={market.data}
decimalPlaces={market.decimalPlaces}
/>
}
/>
</div> </div>
<div className="flex flex-col gap-12"> <div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market specification')}</p> <p className={headerClassName}>{t('Market specification')}</p>
<AccordionPanel <Accordion panels={marketSpecPanels} />
title={t('Key details')}
key="details"
content={
<MarketInfoTable
data={pick(
market,
'name',
'decimalPlaces',
'positionDecimalPlaces',
'tradingMode',
'state'
)}
/>
}
/>
<AccordionPanel
title={t('Instrument')}
key="instrument"
content={
<MarketInfoTable
data={{
product: market.tradableInstrument.instrument.product,
...market.tradableInstrument.instrument.product.settlementAsset,
}}
/>
}
/>
<AccordionPanel
title={t('Risk factors')}
key="risk-factors"
content={
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
}
/>
<AccordionPanel
title={t('Risk model')}
key="risk-model"
content={
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
}
/>
{(market.priceMonitoringSettings?.parameters?.triggers ?? []).map(
(trigger, i) => (
<AccordionPanel
key={`trigger-${i}`}
title={t(`Price monitoring trigger ${i + 1}`)}
content={<MarketInfoTable data={trigger} />}
/>
)
)}
</div> </div>
</div> </div>
); );

View File

@ -1,13 +1,14 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import { AccordionPanel } from './accordion'; import { Accordion } from './accordion';
describe('Accordion', () => { describe('Accordion', () => {
it('should render successfully', () => { it('should render successfully', () => {
render( render(
<AccordionPanel <Accordion
title={'Lorem ipsum title'} panels={[
content={'Lorem ipsum content'} { title: 'Lorem ipsum title', content: 'Lorem ipsum content' },
]}
/> />
); );
expect(screen.queryByTestId('accordion-title')).toHaveTextContent( expect(screen.queryByTestId('accordion-title')).toHaveTextContent(
@ -17,9 +18,10 @@ describe('Accordion', () => {
it('should toggle and open expansion panel', () => { it('should toggle and open expansion panel', () => {
render( render(
<AccordionPanel <Accordion
title={'Lorem ipsum title'} panels={[
content={'Lorem ipsum content'} { title: 'Lorem ipsum title', content: 'Lorem ipsum content' },
]}
/> />
); );
fireEvent.click(screen.getByTestId('accordion-toggle')); fireEvent.click(screen.getByTestId('accordion-toggle'));

View File

@ -1,17 +1,27 @@
import type { Story, Meta } from '@storybook/react'; import type { Story, Meta } from '@storybook/react';
import { AccordionPanel } from './accordion'; import { Accordion } from './accordion';
export default { export default {
component: AccordionPanel, component: Accordion,
title: 'Accordion', title: 'Accordion',
} as Meta; } as Meta;
const Template: Story = (args) => ( const Template: Story = (args) => <Accordion panels={args.panels} />;
<AccordionPanel title={args.title} content={args.content} />
);
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
title: 'Title of expansion panel', panels: [
content: 'Lorem ipsum', {
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
{
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
{
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
],
}; };

View File

@ -1,73 +1,59 @@
import React, { useRef, useState } from 'react'; import React, { useState } from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import ChevronDownIcon from './chevron-down';
import classNames from 'classnames';
export interface AccordionProps { export interface AccordionItemProps {
title: React.ReactNode; title: React.ReactNode;
content: React.ReactNode; content: React.ReactNode;
} }
export const AccordionPanel = ({ title, content }: AccordionProps) => { export interface AccordionProps {
const [active, setActive] = useState(false); panels: AccordionItemProps[];
const [height, setHeight] = useState('0px'); }
const [rotate, setRotate] = useState(
'transform duration-300 ease rotate-180' export const Accordion = ({ panels }: AccordionProps) => {
const [value, setValue] = useState<string>('');
const triggerClassNames = classNames(
'w-full py-2 box-border',
'appearance-none cursor-pointer focus:outline-none',
'flex items-center justify-between border-b border-muted'
); );
const contentSpace = useRef(null);
const toggleAccordion = () => {
setActive((prevState) => !prevState);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
setHeight(active ? '0px' : `${contentSpace.current.scrollHeight}px`);
setRotate(
active
? 'transform duration-300 ease rotate-180'
: 'transform duration-300 ease'
);
};
return ( return (
<div className="flex flex-col"> <AccordionPrimitive.Root
<button type="single"
data-testid="accordion-toggle" className="flex flex-col"
className="py-2 box-border appearance-none cursor-pointer focus:outline-none flex items-center justify-between border-b border-muted" value={value}
onClick={toggleAccordion} onValueChange={setValue}
> collapsible
<p >
className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5 " {panels.map(({ title, content }, i) => (
data-testid="accordion-title" <AccordionPrimitive.Item value={`item-${i + 1}`} key={`item-${i + 1}`}>
> <AccordionPrimitive.Header>
{title} <AccordionPrimitive.Trigger
</p> data-testid="accordion-toggle"
<svg className={triggerClassNames}
width="20" >
height="20" <p
aria-label="chevron icon" className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5"
data-testid="accordion-chevron-icon" data-testid="accordion-title"
className={`${rotate} inline-block fill-black dark:fill-white`} >
viewBox="0 0 20 20" {title}
fill="fillCurrent" </p>
xmlns="http://www.w3.org/2000/svg" <ChevronDownIcon active={value === `item-${i + 1}`} aria-hidden />
> </AccordionPrimitive.Trigger>
<rect x="3" y="12" width="2" height="2" /> </AccordionPrimitive.Header>
<rect x="5" y="10" width="2" height="2" /> <AccordionPrimitive.Content
<rect x="7" y="8" width="2" height="2" /> data-testid="accordion-content-ref"
<rect x="9" y="6" width="2" height="2" /> className="overflow-auto transition-max-height duration-300 ease-in-out"
<rect x="11" y="8" width="2" height="2" /> >
<rect x="13" y="10" width="2" height="2" /> <div className="pb-5" data-testid="accordion-content">
<rect x="15" y="12" width="2" height="2" /> {content}
</svg> </div>
</button> </AccordionPrimitive.Content>
<div </AccordionPrimitive.Item>
ref={contentSpace} ))}
style={{ maxHeight: `${height}` }} </AccordionPrimitive.Root>
data-testid="accordion-content-ref"
className="overflow-auto transition-max-height duration-300 ease-in-out"
>
<div className="pb-5" data-testid="accordion-content">
{content}
</div>
</div>
</div>
); );
}; };

View File

@ -0,0 +1,31 @@
export const ChevronDownIcon = ({ active }: ChevronDownIconProps) => {
const rotate = active
? 'transform duration-300 ease rotate-180'
: 'transform duration-300 ease';
return (
<svg
width="20"
height="20"
aria-label="chevron icon"
data-testid="accordion-chevron-icon"
viewBox="0 0 20 20"
fill="fillCurrent"
xmlns="http://www.w3.org/2000/svg"
className={`${rotate} inline-block fill-black dark:fill-white`}
>
<rect x="17" y="8" width="2" height="2" />
<rect x="15" y="10" width="2" height="2" />
<rect x="13" y="12" width="2" height="2" />
<rect x="11" y="14" width="2" height="2" />
<rect x="9" y="12" width="2" height="2" />
<rect x="7" y="10" width="2" height="2" />
<rect x="5" y="8" width="2" height="2" />
</svg>
);
};
export interface ChevronDownIconProps {
active: boolean;
}
export default ChevronDownIcon;

View File

@ -19,15 +19,17 @@
"@emotion/styled": "^11.8.1", "@emotion/styled": "^11.8.1",
"@mui/material": "^5.6.2", "@mui/material": "^5.6.2",
"@nrwl/next": "13.10.3", "@nrwl/next": "13.10.3",
"@radix-ui/react-accordion": "^0.1.6",
"@radix-ui/react-dialog": "^0.1.5", "@radix-ui/react-dialog": "^0.1.5",
"@radix-ui/react-dropdown-menu": "^0.1.6", "@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-radio-group": "^0.1.5", "@radix-ui/react-radio-group": "^0.1.5",
"@radix-ui/react-tabs": "^0.1.5", "@radix-ui/react-tabs": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7", "@radix-ui/react-tooltip": "^0.1.7",
"@sentry/nextjs": "^6.19.3", "@sentry/nextjs": "^6.19.3",
"@sentry/react": "^6.19.2", "@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2", "@sentry/tracing": "^6.19.2",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.11", "@vegaprotocol/vegawallet-service-api-client": "^0.4.12",
"@walletconnect/ethereum-provider": "^1.7.5", "@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0", "@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0", "@web3-react/metamask": "8.0.16-beta.0",

View File

@ -3364,6 +3364,21 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-accordion@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-0.1.6.tgz#b76613d56717ed24b8cf6cb1897cbd54f04714ed"
integrity sha512-LOXlqPU6y6EMBopdRIKCWFvMPY1wPTQ4uJiX7ZVxldrMJcM7imBzI3wlRTkPCHZ3FLHmpuw+cQi3du23pzJp1g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collapsible" "0.1.6"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-arrow@0.1.4": "@radix-ui/react-arrow@0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b"
@ -3372,6 +3387,21 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-collapsible@0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-0.1.6.tgz#3eeadac476761b3c9b8dd91e8a32eb1a547e5a06"
integrity sha512-Gkf8VuqMc6HTLzA2AxVYnyK6aMczVLpatCjdD9Lj4wlYLXCz9KtiqZYslLMeqnQFLwLyZS0WKX/pQ8j5fioIBw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-collection@0.1.4": "@radix-ui/react-collection@0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9"
@ -3462,6 +3492,11 @@
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-icons@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.1.1.tgz#38d2aa548035dd3b799c169bd17177b1cec3152b"
integrity sha512-xc3wQC59rsFylVbSusQCrrM+6695ppF730Q6yqzhRdqDcRNWIm2R6ngpzBoSOQMcwnq4p805F+Gr7xo4fmtN1A==
"@radix-ui/react-id@0.1.5": "@radix-ui/react-id@0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.5.tgz#010d311bedd5a2884c1e9bb6aaaa4e6cc1d1d3b8" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.5.tgz#010d311bedd5a2884c1e9bb6aaaa4e6cc1d1d3b8"
@ -6715,10 +6750,10 @@
"@typescript-eslint/types" "5.22.0" "@typescript-eslint/types" "5.22.0"
eslint-visitor-keys "^3.0.0" eslint-visitor-keys "^3.0.0"
"@vegaprotocol/vegawallet-service-api-client@^0.4.11": "@vegaprotocol/vegawallet-service-api-client@^0.4.12":
version "0.4.11" version "0.4.12"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.11.tgz#41a623afc9957dcf8b5425f74280ba7861e92b74" resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.12.tgz#65551b9a4d2e00b2c2e9ca9619d95453954a0dbf"
integrity sha512-yiodc3YFWG+RGG+wjpTjYmNAECP/Nv244mVu8IGVtj8LZo02KC/LpNCgmMhGaK4ZcqVtxHv9t7OUCSEWZhSgOg== integrity sha512-Z680W8rsjz2U8R/gss7+hI0eik0euDJLlh7LzWGXUJxUC3XWO9rwJmzlqN/ZlEB4L9OzSLTSZsvlBAGwiHzUSQ==
dependencies: dependencies:
es6-promise "^4.2.4" es6-promise "^4.2.4"
url-parse "^1.4.3" url-parse "^1.4.3"