feat: add New Field Step 2 form (#2138)
Closes #2001 Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -246,6 +246,7 @@ const StyledButton = styled.button<
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
box-sizing: content-box;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@ -10,7 +10,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
|
||||
import { IconButton } from '../button/components/IconButton';
|
||||
import { IconButton, IconButtonVariant } from '../button/components/IconButton';
|
||||
import { LightIconButton } from '../button/components/LightIconButton';
|
||||
import { IconApps } from '../constants/icons';
|
||||
import { useLazyLoadIcons } from '../hooks/useLazyLoadIcons';
|
||||
@ -24,6 +24,7 @@ type IconPickerProps = {
|
||||
onClickOutside?: () => void;
|
||||
onClose?: () => void;
|
||||
onOpen?: () => void;
|
||||
variant?: IconButtonVariant;
|
||||
};
|
||||
|
||||
const StyledMenuIconItemsContainer = styled.div`
|
||||
@ -47,6 +48,7 @@ export const IconPicker = ({
|
||||
onClickOutside,
|
||||
onClose,
|
||||
onOpen,
|
||||
variant = 'secondary',
|
||||
}: IconPickerProps) => {
|
||||
const [searchString, setSearchString] = useState('');
|
||||
|
||||
@ -79,7 +81,7 @@ export const IconPicker = ({
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
Icon={selectedIconKey ? icons[selectedIconKey] : IconApps}
|
||||
variant="secondary"
|
||||
variant={variant}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
@ -119,7 +121,7 @@ export const IconPicker = ({
|
||||
setSearchString('');
|
||||
}}
|
||||
onOpen={onOpen}
|
||||
></Dropdown>
|
||||
/>
|
||||
</DropdownScope>
|
||||
);
|
||||
};
|
||||
|
||||
94
front/src/modules/ui/input/components/Select.tsx
Normal file
94
front/src/modules/ui/input/components/Select.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconChevronDown } from '@/ui/display/icon';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
|
||||
|
||||
export type SelectProps<Value extends string> = {
|
||||
dropdownScopeId: string;
|
||||
onChange: (value: Value) => void;
|
||||
options: { value: Value; label: string; Icon?: IconComponent }[];
|
||||
value?: Value;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
justify-content: space-between;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const Select = <Value extends string>({
|
||||
dropdownScopeId,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
}: SelectProps<Value>) => {
|
||||
const theme = useTheme();
|
||||
const selectedOption =
|
||||
options.find(({ value: key }) => key === value) || options[0];
|
||||
|
||||
const { closeDropdown } = useDropdown({ dropdownScopeId });
|
||||
|
||||
return (
|
||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||
<Dropdown
|
||||
dropdownMenuWidth={176}
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={
|
||||
<StyledContainer>
|
||||
<StyledLabel>
|
||||
{!!selectedOption.Icon && (
|
||||
<selectedOption.Icon
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
{selectedOption.label}
|
||||
</StyledLabel>
|
||||
<IconChevronDown
|
||||
color={theme.font.color.tertiary}
|
||||
size={theme.icon.size.md}
|
||||
/>
|
||||
</StyledContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{options.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
LeftIcon={option.Icon}
|
||||
text={option.label}
|
||||
onClick={() => {
|
||||
onChange(option.value);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
|
||||
/>
|
||||
</DropdownScope>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { Select, SelectProps } from '../Select';
|
||||
|
||||
type RenderProps = SelectProps<string>;
|
||||
|
||||
const Render = (args: RenderProps) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
const handleChange = (value: string) => {
|
||||
args.onChange?.(value);
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <Select {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof Select> = {
|
||||
title: 'UI/Input/Select',
|
||||
component: Select,
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
dropdownScopeId: 'select',
|
||||
value: 'a',
|
||||
options: [
|
||||
{ value: 'a', label: 'Option A' },
|
||||
{ value: 'b', label: 'Option B' },
|
||||
{ value: 'c', label: 'Option C' },
|
||||
],
|
||||
},
|
||||
render: Render,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Select>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Open: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const selectLabel = await canvas.getByText('Option A');
|
||||
|
||||
await userEvent.click(selectLabel);
|
||||
},
|
||||
};
|
||||
3
front/src/modules/ui/input/types/SelectHotkeyScope.ts
Normal file
3
front/src/modules/ui/input/types/SelectHotkeyScope.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum SelectHotkeyScope {
|
||||
Select = 'select',
|
||||
}
|
||||
Reference in New Issue
Block a user