feat(ui-toolkit,governance): description preview and read more pattern (#4599)
Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
parent
97f243e5f7
commit
52dea6d0dc
@ -1,44 +1,27 @@
|
|||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { useState } from 'react';
|
import { RoundedWrapper, ShowMore } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { SubHeading } from '../../../../components/heading';
|
|
||||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
|
||||||
|
|
||||||
export const ProposalDescription = ({
|
export const ProposalDescription = ({
|
||||||
description,
|
description,
|
||||||
}: {
|
}: {
|
||||||
description: string;
|
description: string;
|
||||||
}) => {
|
}) => (
|
||||||
const { t } = useTranslation();
|
<section data-testid="proposal-description">
|
||||||
const [showDescription, setShowDescription] = useState(false);
|
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||||
|
<div className="p-2">
|
||||||
return (
|
<ShowMore>
|
||||||
<section data-testid="proposal-description">
|
<ReactMarkdown
|
||||||
<CollapsibleToggle
|
className="react-markdown-container"
|
||||||
toggleState={showDescription}
|
/* Prevents HTML embedded in the description from rendering */
|
||||||
setToggleState={setShowDescription}
|
skipHtml={true}
|
||||||
dataTestId={'proposal-description-toggle'}
|
/* Stops users embedding images which could be used for tracking */
|
||||||
>
|
disallowedElements={['img']}
|
||||||
<SubHeading title={t('proposalDescription')} />
|
linkTarget="_blank"
|
||||||
</CollapsibleToggle>
|
>
|
||||||
|
{description}
|
||||||
{showDescription && (
|
</ReactMarkdown>
|
||||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
</ShowMore>
|
||||||
<div className="p-2">
|
</div>
|
||||||
<ReactMarkdown
|
</RoundedWrapper>
|
||||||
className="react-markdown-container"
|
</section>
|
||||||
/* Prevents HTML embedded in the description from rendering */
|
);
|
||||||
skipHtml={true}
|
|
||||||
/* Stops users embedding images which could be used for tracking */
|
|
||||||
disallowedElements={['img']}
|
|
||||||
linkTarget="_blank"
|
|
||||||
>
|
|
||||||
{description}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
</RoundedWrapper>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -34,6 +34,7 @@ export * from './progress-bar';
|
|||||||
export * from './radio-group';
|
export * from './radio-group';
|
||||||
export * from './rounded-wrapper';
|
export * from './rounded-wrapper';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
|
export * from './show-more';
|
||||||
export * from './simple-grid';
|
export * from './simple-grid';
|
||||||
export * from './slider';
|
export * from './slider';
|
||||||
export * from './sparkline';
|
export * from './sparkline';
|
||||||
|
1
libs/ui-toolkit/src/components/show-more/index.ts
Normal file
1
libs/ui-toolkit/src/components/show-more/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './show-more';
|
@ -0,0 +1,9 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { ShowMore } from './show-more';
|
||||||
|
|
||||||
|
describe('Button', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShowMore>test</ShowMore>);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
import type { Story, Meta } from '@storybook/react';
|
||||||
|
import { ShowMore } from './show-more';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: ShowMore,
|
||||||
|
title: 'ShowMore',
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story = (args) => (
|
||||||
|
<ShowMore {...args}>
|
||||||
|
<p>
|
||||||
|
Spaceflight will never tolerate carelessness, incapacity, and neglect.
|
||||||
|
Somewhere, somehow, we screwed up. It could have been in design, build, or
|
||||||
|
test. Whatever it was, we should have caught it. We were too gung ho about
|
||||||
|
the schedule and we locked out all of the problems we saw each day in our
|
||||||
|
work. “Every element of the program was in trouble and so were we. The
|
||||||
|
simulators were not working, Mission Control was behind in virtually every
|
||||||
|
area, and the flight and test procedures changed daily. Nothing we did had
|
||||||
|
any shelf life. Not one of us stood up and said, ‘Dammit, stop!’ I don’t
|
||||||
|
know what Thompson’s committee will find as the cause, but I know what I
|
||||||
|
find. We are the cause! We were not ready! We did not do our job. We were
|
||||||
|
rolling the dice, hoping that things would come together by launch day,
|
||||||
|
when in our hearts we knew it would take a miracle. We were pushing the
|
||||||
|
schedule and betting that the Cape would slip before we did. “From this
|
||||||
|
day forward, Flight Control will be known by two words: ‘Tough’ and
|
||||||
|
‘Competent.’ Tough means we are forever accountable for what we do or what
|
||||||
|
we fail to do. We will never again compromise our responsibilities. Every
|
||||||
|
time we walk into Mission Control we will know what we stand for.
|
||||||
|
Competent means we will never take anything for granted. We will never be
|
||||||
|
found short in our knowledge and in our skills. Mission Control will be
|
||||||
|
perfect. When you leave this meeting today you will go to your office and
|
||||||
|
the first thing you will do there is to write ‘Tough and Competent’ on
|
||||||
|
your blackboards. It will never be erased. Each day when you enter the
|
||||||
|
room these words will remind you of the price paid by Grissom, White, and
|
||||||
|
Chaffee. These words are the price of admission to the ranks of Mission
|
||||||
|
Control.
|
||||||
|
</p>
|
||||||
|
</ShowMore>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
export const CustomMaxHeight = Template.bind({});
|
||||||
|
CustomMaxHeight.args = {
|
||||||
|
closedMaxHeightPx: 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomOverlayColour = Template.bind({});
|
||||||
|
CustomOverlayColour.args = {
|
||||||
|
overlayColourOverrides: 'to-yellow-400',
|
||||||
|
};
|
78
libs/ui-toolkit/src/components/show-more/show-more.tsx
Normal file
78
libs/ui-toolkit/src/components/show-more/show-more.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { useRef, useState, useEffect } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Button } from '../button';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
type ShowMoreProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
closedMaxHeightPx?: number;
|
||||||
|
overlayColourOverrides?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShowMore = ({
|
||||||
|
children,
|
||||||
|
closedMaxHeightPx = 125,
|
||||||
|
overlayColourOverrides,
|
||||||
|
}: ShowMoreProps) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkHeight = () => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (container) {
|
||||||
|
container.scrollHeight < closedMaxHeightPx
|
||||||
|
? setExpanded(true)
|
||||||
|
: setExpanded(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHeight();
|
||||||
|
|
||||||
|
window.addEventListener('resize', checkHeight);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', checkHeight);
|
||||||
|
};
|
||||||
|
}, [closedMaxHeightPx]);
|
||||||
|
|
||||||
|
const containerClasses = classNames(
|
||||||
|
'overflow-hidden transition-all ease-in-out duration-300',
|
||||||
|
{
|
||||||
|
'max-h-none': expanded,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayClasses = classNames(
|
||||||
|
`absolute w-full h-16 bottom-0 left-0 transition-opacity duration-300 bg-gradient-to-b from-transparent ${
|
||||||
|
overlayColourOverrides ? overlayColourOverrides : 'to-white dark:to-black'
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
hidden: expanded,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={containerClasses}
|
||||||
|
style={{ maxHeight: expanded ? 'none' : `${closedMaxHeightPx}px` }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<div className={overlayClasses}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!expanded && (
|
||||||
|
<div className="mt-1 text-center">
|
||||||
|
<Button size={'sm'} onClick={() => setExpanded(true)}>
|
||||||
|
{t('Show more')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user