feat: add New Object Custom form (#2105)
* feat: add New Object Custom form Closes #1808 * fix: fix lint error
This commit is contained in:
@ -17,6 +17,7 @@ import { DropdownMenuSkeletonItem } from '../relation-picker/components/skeleton
|
||||
import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
|
||||
|
||||
type IconPickerProps = {
|
||||
disabled?: boolean;
|
||||
onChange: (params: { iconKey: string; Icon: IconComponent }) => void;
|
||||
selectedIconKey?: string;
|
||||
onClickOutside?: () => void;
|
||||
@ -39,6 +40,7 @@ const convertIconKeyToLabel = (iconKey: string) =>
|
||||
iconKey.replace(/[A-Z]/g, (letter) => ` ${letter}`).trim();
|
||||
|
||||
export const IconPicker = ({
|
||||
disabled,
|
||||
onChange,
|
||||
selectedIconKey,
|
||||
onClickOutside,
|
||||
@ -81,6 +83,7 @@ export const IconPicker = ({
|
||||
dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }}
|
||||
clickableComponent={
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
Icon={selectedIconKey ? icons[selectedIconKey] : IconApps}
|
||||
variant="secondary"
|
||||
/>
|
||||
@ -100,6 +103,7 @@ export const IconPicker = ({
|
||||
<StyledMenuIconItemsContainer>
|
||||
{iconKeys.map((iconKey) => (
|
||||
<StyledLightIconButton
|
||||
key={iconKey}
|
||||
aria-label={convertIconKeyToLabel(iconKey)}
|
||||
isSelected={selectedIconKey === iconKey}
|
||||
size="medium"
|
||||
|
||||
63
front/src/modules/ui/input/components/TextArea.tsx
Normal file
63
front/src/modules/ui/input/components/TextArea.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const MAX_ROWS = 5;
|
||||
|
||||
export type TextAreaProps = {
|
||||
disabled?: boolean;
|
||||
minRows?: number;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
const StyledTextArea = styled(TextareaAutosize)`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-family: inherit;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
line-height: 16px;
|
||||
overflow: auto;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
resize: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TextArea = ({
|
||||
disabled,
|
||||
placeholder,
|
||||
minRows = 1,
|
||||
value = '',
|
||||
onChange,
|
||||
}: TextAreaProps) => {
|
||||
const computedMinRows = Math.min(minRows, MAX_ROWS);
|
||||
|
||||
return (
|
||||
<StyledTextArea
|
||||
placeholder={placeholder}
|
||||
maxRows={MAX_ROWS}
|
||||
minRows={computedMinRows}
|
||||
value={value}
|
||||
onChange={(event) => onChange?.(event.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -19,7 +19,7 @@ import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
|
||||
type TextInputComponentProps = Omit<
|
||||
export type TextInputComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange'
|
||||
> & {
|
||||
@ -53,9 +53,10 @@ const StyledInputContainer = styled.div`
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
background-color: ${({ theme }) => theme.background.tertiary};
|
||||
border: none;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-right: none;
|
||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
@ -74,6 +75,10 @@ const StyledInput = styled.input<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledErrorHelper = styled.div`
|
||||
@ -84,8 +89,10 @@ const StyledErrorHelper = styled.div`
|
||||
|
||||
const StyledTrailingIconContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.tertiary};
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-left: none;
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { TextArea, TextAreaProps } from '../TextArea';
|
||||
|
||||
type RenderProps = TextAreaProps;
|
||||
|
||||
const Render = (args: RenderProps) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
const handleChange = (text: string) => {
|
||||
args.onChange?.(text);
|
||||
setValue(text);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextArea {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof TextArea> = {
|
||||
title: 'UI/Input/TextArea',
|
||||
component: TextArea,
|
||||
decorators: [ComponentDecorator],
|
||||
args: { minRows: 4, placeholder: 'Lorem Ipsum' },
|
||||
render: Render,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TextArea>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Filled: Story = {
|
||||
args: { value: 'Lorem Ipsum' },
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Lorem Ipsum' },
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { TextInput, TextInputComponentProps } from '../TextInput';
|
||||
|
||||
type RenderProps = TextInputComponentProps;
|
||||
|
||||
const Render = (args: RenderProps) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
const handleChange = (text: string) => {
|
||||
args.onChange?.(text);
|
||||
setValue(text);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextInput {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof TextInput> = {
|
||||
title: 'UI/Input/TextInput',
|
||||
component: TextInput,
|
||||
decorators: [ComponentDecorator],
|
||||
args: { placeholder: 'Tim' },
|
||||
render: Render,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TextInput>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Filled: Story = {
|
||||
args: { value: 'Tim' },
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Tim' },
|
||||
};
|
||||
Reference in New Issue
Block a user