feat: reorder select field options (#2766)

Closes #2703
This commit is contained in:
Thaïs
2023-11-29 16:42:36 +01:00
committed by GitHub
parent 04c7c1a334
commit d50cf5291a
6 changed files with 123 additions and 45 deletions

View File

@ -1,7 +1,11 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DropResult } from '@hello-pangea/dnd';
import { v4 } from 'uuid';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors'; import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors';
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
@ -18,6 +22,7 @@ type SettingsObjectFieldSelectFormProps = {
const StyledContainer = styled.div` const StyledContainer = styled.div`
padding: ${({ theme }) => theme.spacing(4)}; padding: ${({ theme }) => theme.spacing(4)};
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
`; `;
const StyledLabel = styled.span` const StyledLabel = styled.span`
@ -30,12 +35,6 @@ const StyledLabel = styled.span`
text-transform: uppercase; text-transform: uppercase;
`; `;
const StyledRows = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledButton = styled(Button)` const StyledButton = styled(Button)`
border-bottom: 0; border-bottom: 0;
border-left: 0; border-left: 0;
@ -57,39 +56,66 @@ export const SettingsObjectFieldSelectForm = ({
onChange, onChange,
values, values,
}: SettingsObjectFieldSelectFormProps) => { }: SettingsObjectFieldSelectFormProps) => {
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;
const nextOptions = [...values];
const [movedOption] = nextOptions.splice(result.source.index, 1);
nextOptions.splice(result.destination.index, 0, movedOption);
onChange(nextOptions);
};
return ( return (
<> <>
<StyledContainer> <StyledContainer>
<StyledLabel>Options</StyledLabel> <StyledLabel>Options</StyledLabel>
<StyledRows> <DraggableList
{values.map((option, index) => ( onDragEnd={handleDragEnd}
<SettingsObjectFieldSelectFormOptionRow draggableItems={
key={index} <>
isDefault={option.isDefault} {values.map((option, index) => (
onChange={(nextOption) => { <DraggableItem
const hasDefaultOptionChanged = key={option.value}
!option.isDefault && nextOption.isDefault; draggableId={option.value}
const nextOptions = hasDefaultOptionChanged index={index}
? values.map((value) => ({ ...value, isDefault: false })) isDragDisabled={values.length === 1}
: [...values]; itemComponent={
<SettingsObjectFieldSelectFormOptionRow
key={option.value}
isDefault={option.isDefault}
onChange={(nextOption) => {
const hasDefaultOptionChanged =
!option.isDefault && nextOption.isDefault;
const nextOptions = hasDefaultOptionChanged
? values.map((value) => ({
...value,
isDefault: false,
}))
: [...values];
nextOptions.splice(index, 1, nextOption); nextOptions.splice(index, 1, nextOption);
onChange(nextOptions); onChange(nextOptions);
}} }}
onRemove={ onRemove={
values.length > 1 values.length > 1
? () => { ? () => {
const nextOptions = [...values]; const nextOptions = [...values];
nextOptions.splice(index, 1); nextOptions.splice(index, 1);
onChange(nextOptions); onChange(nextOptions);
} }
: undefined : undefined
} }
option={option} option={option}
/> />
))} }
</StyledRows> />
))}
</>
}
/>
</StyledContainer> </StyledContainer>
<StyledButton <StyledButton
title="Add option" title="Add option"
@ -101,6 +127,7 @@ export const SettingsObjectFieldSelectForm = ({
{ {
color: getNextColor(values[values.length - 1].color), color: getNextColor(values[values.length - 1].color),
label: `Option ${values.length + 1}`, label: `Option ${values.length + 1}`,
value: v4(),
}, },
]) ])
} }

View File

@ -1,4 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -6,6 +7,7 @@ import { ColorSample } from '@/ui/display/color/components/ColorSample';
import { import {
IconCheck, IconCheck,
IconDotsVertical, IconDotsVertical,
IconGripVertical,
IconTrash, IconTrash,
IconX, IconX,
} from '@/ui/display/icon'; } from '@/ui/display/icon';
@ -23,6 +25,7 @@ import { mainColorNames } from '@/ui/theme/constants/colors';
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
type SettingsObjectFieldSelectFormOptionRowProps = { type SettingsObjectFieldSelectFormOptionRowProps = {
className?: string;
isDefault?: boolean; isDefault?: boolean;
onChange: (value: SettingsObjectFieldSelectFormOption) => void; onChange: (value: SettingsObjectFieldSelectFormOption) => void;
onRemove?: () => void; onRemove?: () => void;
@ -33,11 +36,12 @@ const StyledRow = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
height: ${({ theme }) => theme.spacing(6)}; height: ${({ theme }) => theme.spacing(6)};
padding: ${({ theme }) => theme.spacing(1)} 0; padding: ${({ theme }) => theme.spacing(1.5)} 0;
`; `;
const StyledColorSample = styled(ColorSample)` const StyledColorSample = styled(ColorSample)`
cursor: pointer; cursor: pointer;
margin-left: 9px;
margin-right: 14px; margin-right: 14px;
`; `;
@ -51,11 +55,14 @@ const StyledOptionInput = styled(TextInput)`
`; `;
export const SettingsObjectFieldSelectFormOptionRow = ({ export const SettingsObjectFieldSelectFormOptionRow = ({
className,
isDefault, isDefault,
onChange, onChange,
onRemove, onRemove,
option, option,
}: SettingsObjectFieldSelectFormOptionRowProps) => { }: SettingsObjectFieldSelectFormOptionRowProps) => {
const theme = useTheme();
const dropdownScopeIds = useMemo(() => { const dropdownScopeIds = useMemo(() => {
const baseScopeId = `select-field-option-row-${v4()}`; const baseScopeId = `select-field-option-row-${v4()}`;
return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` }; return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` };
@ -69,7 +76,12 @@ export const SettingsObjectFieldSelectFormOptionRow = ({
}); });
return ( return (
<StyledRow> <StyledRow className={className}>
<IconGripVertical
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={theme.font.color.extraLight}
/>
<DropdownScope dropdownScopeId={dropdownScopeIds.color}> <DropdownScope dropdownScopeId={dropdownScopeIds.color}>
<Dropdown <Dropdown
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"

View File

@ -15,6 +15,7 @@ import {
mockedPeopleMetadata, mockedPeopleMetadata,
} from '~/testing/mock-data/metadata'; } from '~/testing/mock-data/metadata';
import { fieldMetadataFormDefaultValues } from '../../hooks/useFieldMetadataForm';
import { import {
SettingsObjectFieldTypeSelectSection, SettingsObjectFieldTypeSelectSection,
SettingsObjectFieldTypeSelectSectionFormValues, SettingsObjectFieldTypeSelectSectionFormValues,
@ -41,11 +42,10 @@ const meta: Meta<typeof SettingsObjectFieldTypeSelectSection> = {
args: { args: {
fieldMetadata: fieldMetadataWithoutId, fieldMetadata: fieldMetadataWithoutId,
objectMetadataId: mockedCompaniesMetadata.node.id, objectMetadataId: mockedCompaniesMetadata.node.id,
values: { values: fieldMetadataFormDefaultValues,
type: FieldMetadataType.Text,
} as SettingsObjectFieldTypeSelectSectionFormValues,
}, },
parameters: { parameters: {
container: { width: 512 },
msw: graphqlMocks, msw: graphqlMocks,
}, },
}; };
@ -92,6 +92,7 @@ export const WithRelationForm: Story = {
)?.node, )?.node,
relationFieldMetadata, relationFieldMetadata,
values: { values: {
...fieldMetadataFormDefaultValues,
type: FieldMetadataType.Relation, type: FieldMetadataType.Relation,
relation: { relation: {
field: relationFieldMetadata, field: relationFieldMetadata,
@ -101,3 +102,38 @@ export const WithRelationForm: Story = {
} as unknown as SettingsObjectFieldTypeSelectSectionFormValues, } as unknown as SettingsObjectFieldTypeSelectSectionFormValues,
}, },
}; };
export const WithSelectForm: Story = {
args: {
fieldMetadata: { label: 'Industry', icon: 'IconBuildingFactory2' },
values: {
...fieldMetadataFormDefaultValues,
type: FieldMetadataType.Enum,
select: [
{
color: 'pink',
isDefault: true,
label: '💊 Health',
value: 'HEALTH',
},
{
color: 'purple',
label: '🏭 Industry',
value: 'INDUSTRY',
},
{ color: 'sky', label: '🤖 SaaS', value: 'SAAS' },
{
color: 'turquoise',
label: '🌿 Green tech',
value: 'GREEN_TECH',
},
{
color: 'yellow',
label: '🚲 Mobility',
value: 'MOBILITY',
},
{ color: 'green', label: '🌏 NGO', value: 'NGO' },
],
},
},
};

View File

@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { DeepPartial } from 'react-hook-form'; import { DeepPartial } from 'react-hook-form';
import { v4 } from 'uuid';
import { z } from 'zod'; import { z } from 'zod';
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema'; import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
@ -20,7 +21,7 @@ type FormValues = {
select: SettingsObjectFieldTypeSelectSectionFormValues['select']; select: SettingsObjectFieldTypeSelectSectionFormValues['select'];
}; };
const defaultValues: FormValues = { export const fieldMetadataFormDefaultValues: FormValues = {
icon: 'IconUsers', icon: 'IconUsers',
label: '', label: '',
type: FieldMetadataType.Text, type: FieldMetadataType.Text,
@ -29,7 +30,7 @@ const defaultValues: FormValues = {
objectMetadataId: '', objectMetadataId: '',
field: { label: '' }, field: { label: '' },
}, },
select: [{ color: 'green', label: 'Option 1' }], select: [{ color: 'green', label: 'Option 1', value: v4() }],
}; };
const fieldSchema = z.object({ const fieldSchema = z.object({
@ -96,9 +97,12 @@ type PartialFormValues = Partial<Omit<FormValues, 'relation'>> &
export const useFieldMetadataForm = () => { export const useFieldMetadataForm = () => {
const [isInitialized, setIsInitialized] = useState(false); const [isInitialized, setIsInitialized] = useState(false);
const [initialFormValues, setInitialFormValues] = const [initialFormValues, setInitialFormValues] = useState<FormValues>(
useState<FormValues>(defaultValues); fieldMetadataFormDefaultValues,
const [formValues, setFormValues] = useState<FormValues>(defaultValues); );
const [formValues, setFormValues] = useState<FormValues>(
fieldMetadataFormDefaultValues,
);
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false); const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false); const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false); const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);

View File

@ -4,5 +4,5 @@ export type SettingsObjectFieldSelectFormOption = {
color: ThemeColor; color: ThemeColor;
isDefault?: boolean; isDefault?: boolean;
label: string; label: string;
value?: string; value: string;
}; };

View File

@ -812,7 +812,6 @@ export {
IconBrandTabler, IconBrandTabler,
IconBrandTailwind, IconBrandTailwind,
IconBrandTaobao, IconBrandTaobao,
IconBrandTeams,
IconBrandTed, IconBrandTed,
IconBrandTelegram, IconBrandTelegram,
IconBrandTerraform, IconBrandTerraform,