Add workflow email action (#7279)
- Add the SAVE_EMAIL action. This action requires more setting parameters than the Serverless Function action. - Changed the way we computed the workflow diagram. It now preserves some properties, like the `selected` property. That's necessary to not close the right drawer when the workflow back-end data change. - Added the possibility to set a label to a TextArea. This uses a `<label>` HTML element and the `useId()` hook to create an id linking the label with the input.
This commit is contained in:
committed by
GitHub
parent
0d570caff5
commit
cde255a031
@ -1,5 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { FocusEventHandler } from 'react';
|
||||
import { FocusEventHandler, useId } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
@ -10,6 +10,7 @@ import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
const MAX_ROWS = 5;
|
||||
|
||||
export type TextAreaProps = {
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
minRows?: number;
|
||||
onChange?: (value: string) => void;
|
||||
@ -18,6 +19,20 @@ export type TextAreaProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.label`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: block;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTextArea = styled(TextareaAutosize)`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
@ -48,6 +63,7 @@ const StyledTextArea = styled(TextareaAutosize)`
|
||||
`;
|
||||
|
||||
export const TextArea = ({
|
||||
label,
|
||||
disabled,
|
||||
placeholder,
|
||||
minRows = 1,
|
||||
@ -57,6 +73,8 @@ export const TextArea = ({
|
||||
}: TextAreaProps) => {
|
||||
const computedMinRows = Math.min(minRows, MAX_ROWS);
|
||||
|
||||
const inputId = useId();
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
@ -71,18 +89,23 @@ export const TextArea = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTextArea
|
||||
placeholder={placeholder}
|
||||
maxRows={MAX_ROWS}
|
||||
minRows={computedMinRows}
|
||||
value={value}
|
||||
onChange={(event) =>
|
||||
onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value))
|
||||
}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
/>
|
||||
<StyledContainer>
|
||||
{label && <StyledLabel htmlFor={inputId}>{label}</StyledLabel>}
|
||||
|
||||
<StyledTextArea
|
||||
id={inputId}
|
||||
placeholder={placeholder}
|
||||
maxRows={MAX_ROWS}
|
||||
minRows={computedMinRows}
|
||||
value={value}
|
||||
onChange={(event) =>
|
||||
onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value))
|
||||
}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
import { userEvent, within } from '@storybook/test';
|
||||
import { TextArea, TextAreaProps } from '../TextArea';
|
||||
|
||||
type RenderProps = TextAreaProps;
|
||||
@ -37,3 +39,20 @@ export const Filled: Story = {
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Lorem Ipsum' },
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
args: { label: 'My Textarea' },
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
|
||||
const label = await canvas.findByText('My Textarea');
|
||||
|
||||
expect(label).toBeVisible();
|
||||
|
||||
await userEvent.click(label);
|
||||
|
||||
const input = await canvas.findByRole('textbox');
|
||||
|
||||
expect(input).toHaveFocus();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user