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 {
KeyValueTable,
KeyValueTableRow,
AccordionPanel,
Accordion,
} from '@vegaprotocol/ui-toolkit';
import startCase from 'lodash/startCase';
import pick from 'lodash/pick';
@ -23,97 +23,94 @@ export interface InfoProps {
export const Info = ({ market }: InfoProps) => {
const headerClassName =
'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 (
<div className="p-16 flex flex-col gap-32">
<div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market data')}</p>
<AccordionPanel
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}
/>
}
/>
<Accordion panels={marketDataPanels} />
</div>
<div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market specification')}</p>
<AccordionPanel
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} />}
/>
)
)}
<Accordion panels={marketSpecPanels} />
</div>
</div>
);

View File

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

View File

@ -1,17 +1,27 @@
import type { Story, Meta } from '@storybook/react';
import { AccordionPanel } from './accordion';
import { Accordion } from './accordion';
export default {
component: AccordionPanel,
component: Accordion,
title: 'Accordion',
} as Meta;
const Template: Story = (args) => (
<AccordionPanel title={args.title} content={args.content} />
);
const Template: Story = (args) => <Accordion panels={args.panels} />;
export const Default = Template.bind({});
Default.args = {
title: 'Title of expansion panel',
content: 'Lorem ipsum',
panels: [
{
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;
content: React.ReactNode;
}
export const AccordionPanel = ({ title, content }: AccordionProps) => {
const [active, setActive] = useState(false);
const [height, setHeight] = useState('0px');
const [rotate, setRotate] = useState(
'transform duration-300 ease rotate-180'
export interface AccordionProps {
panels: AccordionItemProps[];
}
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 (
<div className="flex flex-col">
<button
data-testid="accordion-toggle"
className="py-2 box-border appearance-none cursor-pointer focus:outline-none flex items-center justify-between border-b border-muted"
onClick={toggleAccordion}
>
<p
className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5 "
data-testid="accordion-title"
>
{title}
</p>
<svg
width="20"
height="20"
aria-label="chevron icon"
data-testid="accordion-chevron-icon"
className={`${rotate} inline-block fill-black dark:fill-white`}
viewBox="0 0 20 20"
fill="fillCurrent"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="3" y="12" width="2" height="2" />
<rect x="5" y="10" width="2" height="2" />
<rect x="7" y="8" width="2" height="2" />
<rect x="9" y="6" width="2" height="2" />
<rect x="11" y="8" width="2" height="2" />
<rect x="13" y="10" width="2" height="2" />
<rect x="15" y="12" width="2" height="2" />
</svg>
</button>
<div
ref={contentSpace}
style={{ maxHeight: `${height}` }}
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>
<AccordionPrimitive.Root
type="single"
className="flex flex-col"
value={value}
onValueChange={setValue}
collapsible
>
{panels.map(({ title, content }, i) => (
<AccordionPrimitive.Item value={`item-${i + 1}`} key={`item-${i + 1}`}>
<AccordionPrimitive.Header>
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={triggerClassNames}
>
<p
className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5"
data-testid="accordion-title"
>
{title}
</p>
<ChevronDownIcon active={value === `item-${i + 1}`} aria-hidden />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
<AccordionPrimitive.Content
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>
</AccordionPrimitive.Content>
</AccordionPrimitive.Item>
))}
</AccordionPrimitive.Root>
);
};

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",
"@mui/material": "^5.6.2",
"@nrwl/next": "13.10.3",
"@radix-ui/react-accordion": "^0.1.6",
"@radix-ui/react-dialog": "^0.1.5",
"@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-tabs": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7",
"@sentry/nextjs": "^6.19.3",
"@sentry/react": "^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",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",

View File

@ -3364,6 +3364,21 @@
dependencies:
"@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":
version "0.1.4"
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"
"@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":
version "0.1.4"
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-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":
version "0.1.5"
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"
eslint-visitor-keys "^3.0.0"
"@vegaprotocol/vegawallet-service-api-client@^0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.11.tgz#41a623afc9957dcf8b5425f74280ba7861e92b74"
integrity sha512-yiodc3YFWG+RGG+wjpTjYmNAECP/Nv244mVu8IGVtj8LZo02KC/LpNCgmMhGaK4ZcqVtxHv9t7OUCSEWZhSgOg==
"@vegaprotocol/vegawallet-service-api-client@^0.4.12":
version "0.4.12"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.12.tgz#65551b9a4d2e00b2c2e9ca9619d95453954a0dbf"
integrity sha512-Z680W8rsjz2U8R/gss7+hI0eik0euDJLlh7LzWGXUJxUC3XWO9rwJmzlqN/ZlEB4L9OzSLTSZsvlBAGwiHzUSQ==
dependencies:
es6-promise "^4.2.4"
url-parse "^1.4.3"