Skip to content

Commit

Permalink
Merge pull request #947 from equinor/feat/richtext-variants
Browse files Browse the repository at this point in the history
feat/richtext-variants
  • Loading branch information
mariush2 authored Jan 23, 2025
2 parents cc69dbf + c446f06 commit 519fd59
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 60 deletions.
10 changes: 4 additions & 6 deletions src/molecules/RichTextDisplay/RichTextDisplay.jsdom.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { tokens } from '@equinor/eds-tokens';
import { faker } from '@faker-js/faker';

import { spacings } from 'src/atoms/style';
import { RichTextDisplay } from 'src/molecules/RichTextDisplay/RichTextDisplay';
import { act, render } from 'src/tests/jsdomtest-utils';

const { spacings } = tokens;

test('Padding props works as expected', async () => {
const content = faker.animal.bear();

Expand All @@ -16,7 +14,7 @@ test('Padding props works as expected', async () => {
});

expect(container.querySelector('.tiptap')).toHaveStyle({
padding: spacings.comfortable.medium,
padding: spacings.medium,
});

rerender(<RichTextDisplay value={'content'} padding={'none'} />);
Expand All @@ -36,7 +34,7 @@ test('Padding props works as expected', async () => {
});

expect(container.querySelector('.tiptap')).toHaveStyle({
padding: spacings.comfortable.small,
padding: spacings.small,
});

rerender(<RichTextDisplay value={'content'} padding={'lg'} />);
Expand All @@ -46,6 +44,6 @@ test('Padding props works as expected', async () => {
});

expect(container.querySelector('.tiptap')).toHaveStyle({
padding: spacings.comfortable.large,
padding: spacings.large,
});
});
45 changes: 45 additions & 0 deletions src/molecules/RichTextEditor/RichTextEditor.jsdom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { faker } from '@faker-js/faker';
import { RichTextEditor, RichTextEditorProps } from './RichTextEditor';
import { RichTextEditorFeatures } from './RichTextEditor.types';
import { colors } from 'src/atoms';
import { VARIANT_COLORS, VARIANT_HELPER_COLORS } from 'src/atoms/style/colors';
import { renderWithProviders, screen } from 'src/tests/jsdomtest-utils';

function fakeProps(withImage = false): RichTextEditorProps {
Expand Down Expand Up @@ -32,3 +33,47 @@ test('Creating table with highlight works as expected', async () => {
`box-shadow: inset 0 -2px ${colors.dataviz.darkblue.darker}`
);
});

test('Variant gets expected colors', async () => {
const props = fakeProps();

renderWithProviders(
<RichTextEditor
{...props}
removeFeatures={[RichTextEditorFeatures.IMAGES]}
variant="error"
/>
);

// Wait for tip tap to initialize
await new Promise((resolve) => setTimeout(resolve, 1000));

const editor = screen.getByTestId('richtext-editor').querySelector('.tiptap');

expect(editor).toHaveStyle(
`box-shadow: inset 0 -2px ${VARIANT_COLORS['error']}`
);
});

test('Helper text gets expected colors', async () => {
const props = fakeProps();

const helper = faker.animal.dog();

renderWithProviders(
<RichTextEditor
{...props}
removeFeatures={[RichTextEditorFeatures.IMAGES]}
variant="error"
helperText={helper}
/>
);

// Wait for tip tap to initialize
await new Promise((resolve) => setTimeout(resolve, 1000));

expect(screen.getByText(helper)).toHaveStyleRule(
'color',
VARIANT_HELPER_COLORS['error']
);
});
37 changes: 37 additions & 0 deletions src/molecules/RichTextEditor/RichTextEditor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,43 @@ export const MaxHeight: StoryFn<RichTextEditorProps> = (args) => {
);
};

export const Variants: StoryFn<RichTextEditorProps> = (args) => {
return (
<div
style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}
>
<RichTextEditor
{...args}
variant="dirty"
label="Label"
meta="Meta"
helperText="Helper text"
/>
<RichTextEditor
{...args}
variant="error"
label="Label"
meta="Meta"
helperText="Helper text"
/>
<RichTextEditor
{...args}
variant="warning"
label="Label"
meta="Meta"
helperText="Helper text"
/>
<RichTextEditor
{...args}
variant="success"
label="Label"
meta="Meta"
helperText="Helper text"
/>
</div>
);
};

export const CustomEditor: StoryFn<RichTextEditorProps> = (args) => {
const string = `<p>The rich text editor is built off a compound component architecture. This means that you can use the individual primitives to take full control. Notice in this example that the MenuBar is below the rich text editor content even though theres no prop to allow you to do this. If you look at the code you will notice that in this example we are no longer using the higher level RichTextEditor component. Instead we are breaking out the smaller primitive components that make up the RichTextEditor. This way you have full control of all the parts in the editor. You can mix an match them as you please. Decide which individual parts you need to take over while still using the other parts.</p>`;

Expand Down
43 changes: 33 additions & 10 deletions src/molecules/RichTextEditor/RichTextEditor.styles.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { tokens } from '@equinor/eds-tokens';
import { EditorContent as TiptapContent } from '@tiptap/react';

import styled from 'styled-components';
import { colors, shape, spacings, typography } from 'src/atoms/style';
import { VARIANT_COLORS } from 'src/atoms/style/colors';
import { Variants } from 'src/atoms/types/variants';

const { spacings, typography, shape, colors } = tokens;
import { colors as AmplifyColors } from 'src/atoms';
import styled from 'styled-components';

export interface RichTextContentProps {
$minHeight?: string;
Expand All @@ -25,6 +25,7 @@ export interface EditorStylingProps {
$highlighted?: boolean;
$padding?: 'sm' | 'md' | 'lg' | 'none';
$border?: boolean;
$variant?: Variants;
}

export const EditorStyling = styled.div<EditorStylingProps>`
Expand Down Expand Up @@ -52,20 +53,22 @@ export const EditorStyling = styled.div<EditorStylingProps>`
: `${colors.ui.background__light.rgba}`};
&[contenteditable='true'] {
box-shadow: ${({ $highlighted }) =>
box-shadow: ${({ $highlighted, $variant }) =>
$highlighted
? `inset 0 -2px ${AmplifyColors.dataviz.darkblue.darker}`
: `inset 0 -1px ${colors.ui.background__medium.rgba}`};
? `inset 0 -2px ${colors.dataviz.darkblue.darker}`
: $variant
? `inset 0 -2px ${VARIANT_COLORS[$variant]}`
: `inset 0 -1px ${colors.ui.background__medium.rgba}`};
}
padding: ${(props) => {
switch (props.$padding) {
case 'sm':
return spacings.comfortable.small;
return spacings.small;
case 'md':
return spacings.comfortable.medium;
return spacings.medium;
case 'lg':
return spacings.comfortable.large;
return spacings.large;
case 'none':
default:
return 0;
Expand Down Expand Up @@ -179,3 +182,23 @@ export const EditorStyling = styled.div<EditorStylingProps>`
pointer-events: none;
}
`;

export const Wrapper = styled.div`
display: flex;
flex-direction: column;
`;

export const LabelWrapper = styled.div`
display: flex;
justify-content: space-between;
padding: 0 ${spacings.small};
> p {
color: ${colors.text.static_icons__tertiary.rgba};
}
`;

export const HelperWrapper = styled.div`
display: flex;
padding-left: ${spacings.small};
margin-top: ${spacings.small};
`;
26 changes: 26 additions & 0 deletions src/molecules/RichTextEditor/RichTextEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,32 @@ test('Throws error if trying to use both remove strategies', () => {
).toThrowError();
});

test('Shows label / meta / helper as expected', async () => {
const props = fakeProps(true);
const label = faker.airline.airline().name;
const meta = faker.commerce.department();
const helper = faker.food.fruit();
const helperIcon = <div data-testid="helper-icon" />;

renderWithProviders(
<RichTextEditor
{...props}
label={label}
meta={meta}
helperText={helper}
helperIcon={helperIcon}
/>
);

// Wait for tip tap to initialize
await new Promise((resolve) => setTimeout(resolve, 1000));

expect(screen.getByText(label)).toBeInTheDocument();
expect(screen.getByText(meta)).toBeInTheDocument();
expect(screen.getByText(helper)).toBeInTheDocument();
expect(screen.getByTestId('helper-icon')).toBeInTheDocument();
});

describe('Editor defaults can be merged', () => {
const uniqe: Partial<AmplifyKitOptions> = {
bold: { HTMLAttributes: { class: 'bolder' } },
Expand Down
99 changes: 79 additions & 20 deletions src/molecules/RichTextEditor/RichTextEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { FC } from 'react';
import { FC, ReactNode } from 'react';

import { Typography } from '@equinor/eds-core-react';

import { AmplifyBar } from './MenuBar/MenuBar';
import { EditorProvider } from './EditorProvider';
import { EditorContent, EditorStyling } from './RichTextEditor.styles';
import {
EditorContent,
EditorStyling,
HelperWrapper,
LabelWrapper,
Wrapper,
} from './RichTextEditor.styles';
import {
ImageExtensionFnProps,
RichTextEditorFeatures,
} from './RichTextEditor.types';
import { colors, VARIANT_HELPER_COLORS } from 'src/atoms/style/colors';
import { Variants } from 'src/atoms/types/variants';
import { getFeatures } from 'src/atoms/utils/richtext';

export interface RichTextEditorProps extends ImageExtensionFnProps {
Expand All @@ -21,7 +31,15 @@ export interface RichTextEditorProps extends ImageExtensionFnProps {
minHeight?: string;
lightBackground?: boolean;
border?: boolean;
/**
* @deprecated - Use new 'variant' prop instead
*/
highlighted?: boolean;
variant?: Variants;
label?: string;
meta?: string;
helperText?: string;
helperIcon?: ReactNode;
}

/**
Expand All @@ -41,7 +59,12 @@ export interface RichTextEditorProps extends ImageExtensionFnProps {
* @param minHeight - minHeight of the text box
* @param lightBackground - if it should have a different BG color
* @param border - if it should have a border
* @param variant - field variants - for example 'error'
* @param highlighted - if it should have a highlighted border
* @param label - Label text at top left
* @param meta - Meta text at top right
* @param helperText - Helper text bottom left
* @param helperIcon - Icon / element to be placed before helper text
*/
export const RichTextEditor: FC<RichTextEditorProps> = ({
value,
Expand All @@ -60,6 +83,11 @@ export const RichTextEditor: FC<RichTextEditorProps> = ({
lightBackground,
border = true,
highlighted = false,
variant,
label,
meta,
helperText,
helperIcon,
}) => {
if (onImageRemove && onRemovedImagesChange) {
throw new Error(
Expand All @@ -85,24 +113,55 @@ export const RichTextEditor: FC<RichTextEditorProps> = ({
onRemovedImagesChange={onRemovedImagesChange}
>
{(editor) => (
<EditorStyling
data-testid="richtext-editor"
$border={border}
$padding={padding}
$lightBackground={lightBackground}
$highlighted={highlighted}
>
<AmplifyBar
editor={editor}
features={usedFeatured}
onImageUpload={onImageUpload}
/>
<EditorContent
editor={editor}
$maxHeight={maxHeight}
$minHeight={minHeight}
/>
</EditorStyling>
<Wrapper>
{label && (
<LabelWrapper>
<Typography variant="label" group="input">
{label}
</Typography>
{meta && (
<Typography variant="helper" group="input">
{meta}
</Typography>
)}
</LabelWrapper>
)}
<EditorStyling
data-testid="richtext-editor"
$border={border}
$padding={padding}
$lightBackground={lightBackground}
$highlighted={highlighted}
$variant={variant}
>
<AmplifyBar
editor={editor}
features={usedFeatured}
onImageUpload={onImageUpload}
/>
<EditorContent
editor={editor}
$maxHeight={maxHeight}
$minHeight={minHeight}
/>
</EditorStyling>
{helperText && (
<HelperWrapper>
{helperIcon ? helperIcon : undefined}
<Typography
variant="helper"
group="input"
color={
variant
? VARIANT_HELPER_COLORS[variant]
: colors.text.static_icons__tertiary.rgba
}
>
{helperText}
</Typography>
</HelperWrapper>
)}
</Wrapper>
)}
</EditorProvider>
);
Expand Down
Loading

0 comments on commit 519fd59

Please sign in to comment.