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:
parent
74c245ca7a
commit
c85e6a313f
@ -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,14 +23,10 @@ export interface InfoProps {
|
||||
export const Info = ({ market }: InfoProps) => {
|
||||
const headerClassName =
|
||||
'text-h5 font-bold uppercase text-black dark:text-white';
|
||||
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={
|
||||
const marketDataPanels = [
|
||||
{
|
||||
title: t('Current fees'),
|
||||
content: (
|
||||
<>
|
||||
<MarketInfoTable data={market.fees.factors} asPercentage={true} />
|
||||
<p className="text-ui-small">
|
||||
@ -39,26 +35,22 @@ export const Info = ({ market }: InfoProps) => {
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<AccordionPanel
|
||||
key="market-data"
|
||||
title={t('Market data')}
|
||||
content={
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Market data'),
|
||||
content: (
|
||||
<MarketInfoTable
|
||||
data={market.data}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-12">
|
||||
<p className={headerClassName}>{t('Market specification')}</p>
|
||||
<AccordionPanel
|
||||
title={t('Key details')}
|
||||
key="details"
|
||||
content={
|
||||
),
|
||||
},
|
||||
];
|
||||
const marketSpecPanels = [
|
||||
{
|
||||
title: t('Key details'),
|
||||
content: (
|
||||
<MarketInfoTable
|
||||
data={pick(
|
||||
market,
|
||||
@ -69,51 +61,56 @@ export const Info = ({ market }: InfoProps) => {
|
||||
'state'
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionPanel
|
||||
title={t('Instrument')}
|
||||
key="instrument"
|
||||
content={
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Instrument'),
|
||||
content: (
|
||||
<MarketInfoTable
|
||||
data={{
|
||||
product: market.tradableInstrument.instrument.product,
|
||||
...market.tradableInstrument.instrument.product.settlementAsset,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionPanel
|
||||
title={t('Risk factors')}
|
||||
key="risk-factors"
|
||||
content={
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Risk factors'),
|
||||
content: (
|
||||
<MarketInfoTable
|
||||
data={market.riskFactors}
|
||||
unformatted={true}
|
||||
omits={['market', '__typename']}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionPanel
|
||||
title={t('Risk model')}
|
||||
key="risk-model"
|
||||
content={
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('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} />}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
),
|
||||
},
|
||||
...(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>
|
||||
<Accordion panels={marketDataPanels} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-12">
|
||||
<p className={headerClassName}>{t('Market specification')}</p>
|
||||
<Accordion panels={marketSpecPanels} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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'));
|
||||
|
@ -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 = {
|
||||
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',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,37 +1,39 @@
|
||||
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[];
|
||||
}
|
||||
|
||||
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'
|
||||
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'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<button
|
||||
<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="py-2 box-border appearance-none cursor-pointer focus:outline-none flex items-center justify-between border-b border-muted"
|
||||
onClick={toggleAccordion}
|
||||
className={triggerClassNames}
|
||||
>
|
||||
<p
|
||||
className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5"
|
||||
@ -39,35 +41,19 @@ export const AccordionPanel = ({ title, content }: AccordionProps) => {
|
||||
>
|
||||
{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}` }}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
||||
</AccordionPrimitive.Item>
|
||||
))}
|
||||
</AccordionPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
31
libs/ui-toolkit/src/components/accordion/chevron-down.tsx
Normal file
31
libs/ui-toolkit/src/components/accordion/chevron-down.tsx
Normal 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;
|
@ -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",
|
||||
|
43
yarn.lock
43
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user