feat: add New Field Step 2 form (#2138)
Closes #2001 Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,64 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||||
|
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||||
|
import { TextArea } from '@/ui/input/components/TextArea';
|
||||||
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
|
|
||||||
|
type SettingsObjectFieldFormSectionProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
iconKey?: string;
|
||||||
|
onChange?: (
|
||||||
|
formValues: Partial<{
|
||||||
|
iconKey: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}>,
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledInputsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsObjectFieldFormSection = ({
|
||||||
|
disabled,
|
||||||
|
name = '',
|
||||||
|
description = '',
|
||||||
|
iconKey = 'IconUsers',
|
||||||
|
onChange,
|
||||||
|
}: SettingsObjectFieldFormSectionProps) => (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Name and description"
|
||||||
|
description="The name and description of this field"
|
||||||
|
/>
|
||||||
|
<StyledInputsContainer>
|
||||||
|
<IconPicker
|
||||||
|
selectedIconKey={iconKey}
|
||||||
|
onChange={(value) => onChange?.({ iconKey: value.iconKey })}
|
||||||
|
variant="primary"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Employees"
|
||||||
|
value={name}
|
||||||
|
onChange={(value) => onChange?.({ name: value })}
|
||||||
|
disabled={disabled}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</StyledInputsContainer>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Write a description"
|
||||||
|
minRows={4}
|
||||||
|
value={description}
|
||||||
|
onChange={(value) => onChange?.({ description: value })}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||||
|
import { Select } from '@/ui/input/components/Select';
|
||||||
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
|
|
||||||
|
import { dataTypes } from '../constants/dataTypes';
|
||||||
|
import { ObjectFieldDataType } from '../types/ObjectFieldDataType';
|
||||||
|
|
||||||
|
type SettingsObjectFieldTypeSelectSectionProps = {
|
||||||
|
type: ObjectFieldDataType;
|
||||||
|
onChange: (value: ObjectFieldDataType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsObjectFieldTypeSelectSection = ({
|
||||||
|
type,
|
||||||
|
onChange,
|
||||||
|
}: SettingsObjectFieldTypeSelectSectionProps) => (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Type and values"
|
||||||
|
description="The field's type and values."
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
dropdownScopeId="object-field-type-select"
|
||||||
|
value={type}
|
||||||
|
onChange={onChange}
|
||||||
|
options={Object.entries(dataTypes).map(([key, dataType]) => ({
|
||||||
|
value: key as ObjectFieldDataType,
|
||||||
|
...dataType,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
@ -26,10 +26,6 @@ const StyledInputsContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTextInput = styled(TextInput)`
|
|
||||||
flex: 1 0 auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsObjectFormSection = ({
|
export const SettingsObjectFormSection = ({
|
||||||
disabled,
|
disabled,
|
||||||
singularName = '',
|
singularName = '',
|
||||||
@ -43,19 +39,21 @@ export const SettingsObjectFormSection = ({
|
|||||||
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
|
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
|
||||||
/>
|
/>
|
||||||
<StyledInputsContainer>
|
<StyledInputsContainer>
|
||||||
<StyledTextInput
|
<TextInput
|
||||||
label="Singular"
|
label="Singular"
|
||||||
placeholder="Investor"
|
placeholder="Investor"
|
||||||
value={singularName}
|
value={singularName}
|
||||||
onChange={(value) => onChange?.({ singularName: value })}
|
onChange={(value) => onChange?.({ singularName: value })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<StyledTextInput
|
<TextInput
|
||||||
label="Plural"
|
label="Plural"
|
||||||
placeholder="Investors"
|
placeholder="Investors"
|
||||||
value={pluralName}
|
value={pluralName}
|
||||||
onChange={(value) => onChange?.({ pluralName: value })}
|
onChange={(value) => onChange?.({ pluralName: value })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</StyledInputsContainer>
|
</StyledInputsContainer>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import { SettingsObjectFieldFormSection } from '../SettingsObjectFieldFormSection';
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsObjectFieldFormSection> = {
|
||||||
|
title: 'Modules/Settings/DataModel/SettingsObjectFieldFormSection',
|
||||||
|
component: SettingsObjectFieldFormSection,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsObjectFieldFormSection>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const WithDefaultValues: Story = {
|
||||||
|
args: {
|
||||||
|
iconKey: 'IconLink',
|
||||||
|
name: 'URL',
|
||||||
|
description: 'Lorem ipsum',
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { userEvent, within } from '@storybook/testing-library';
|
||||||
|
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import { SettingsObjectFieldTypeSelectSection } from '../SettingsObjectFieldTypeSelectSection';
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsObjectFieldTypeSelectSection> = {
|
||||||
|
title: 'Modules/Settings/DataModel/SettingsObjectFieldTypeSelectSection',
|
||||||
|
component: SettingsObjectFieldTypeSelectSection,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
args: { type: 'number' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsObjectFieldTypeSelectSection>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const WithOpenSelect: Story = {
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const selectLabel = canvas.getByText('Number');
|
||||||
|
|
||||||
|
await userEvent.click(selectLabel);
|
||||||
|
},
|
||||||
|
};
|
||||||
25
front/src/modules/settings/data-model/constants/dataTypes.ts
Normal file
25
front/src/modules/settings/data-model/constants/dataTypes.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
IconCheck,
|
||||||
|
IconLink,
|
||||||
|
IconNumbers,
|
||||||
|
IconPlug,
|
||||||
|
IconSocial,
|
||||||
|
IconTextSize,
|
||||||
|
IconUserCircle,
|
||||||
|
} from '@/ui/display/icon';
|
||||||
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
|
|
||||||
|
import { ObjectFieldDataType } from '../types/ObjectFieldDataType';
|
||||||
|
|
||||||
|
export const dataTypes: Record<
|
||||||
|
ObjectFieldDataType,
|
||||||
|
{ label: string; Icon: IconComponent }
|
||||||
|
> = {
|
||||||
|
number: { label: 'Number', Icon: IconNumbers },
|
||||||
|
text: { label: 'Text', Icon: IconTextSize },
|
||||||
|
link: { label: 'Link', Icon: IconLink },
|
||||||
|
teammate: { label: 'Team member', Icon: IconUserCircle },
|
||||||
|
boolean: { label: 'True/False', Icon: IconCheck },
|
||||||
|
relation: { label: 'Relation', Icon: IconPlug },
|
||||||
|
social: { label: 'Social', Icon: IconSocial },
|
||||||
|
};
|
||||||
@ -1,16 +1,7 @@
|
|||||||
import { css, useTheme } from '@emotion/react';
|
import { css, useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import {
|
import { dataTypes } from '../../constants/dataTypes';
|
||||||
IconCheck,
|
|
||||||
IconLink,
|
|
||||||
IconNumbers,
|
|
||||||
IconPlug,
|
|
||||||
IconSocial,
|
|
||||||
IconUserCircle,
|
|
||||||
} from '@/ui/display/icon';
|
|
||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
|
||||||
|
|
||||||
import { ObjectFieldDataType } from '../../types/ObjectFieldDataType';
|
import { ObjectFieldDataType } from '../../types/ObjectFieldDataType';
|
||||||
|
|
||||||
const StyledDataType = styled.div<{ value: ObjectFieldDataType }>`
|
const StyledDataType = styled.div<{ value: ObjectFieldDataType }>`
|
||||||
@ -32,18 +23,6 @@ const StyledDataType = styled.div<{ value: ObjectFieldDataType }>`
|
|||||||
: ''}
|
: ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const dataTypes: Record<
|
|
||||||
ObjectFieldDataType,
|
|
||||||
{ label: string; Icon: IconComponent }
|
|
||||||
> = {
|
|
||||||
boolean: { label: 'True/False', Icon: IconCheck },
|
|
||||||
number: { label: 'Number', Icon: IconNumbers },
|
|
||||||
relation: { label: 'Relation', Icon: IconPlug },
|
|
||||||
social: { label: 'Social', Icon: IconSocial },
|
|
||||||
teammate: { label: 'Teammate', Icon: IconUserCircle },
|
|
||||||
text: { label: 'Text', Icon: IconLink },
|
|
||||||
};
|
|
||||||
|
|
||||||
type SettingsObjectFieldDataTypeProps = {
|
type SettingsObjectFieldDataTypeProps = {
|
||||||
value: ObjectFieldDataType;
|
value: ObjectFieldDataType;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export type ObjectFieldDataType =
|
export type ObjectFieldDataType =
|
||||||
| 'boolean'
|
| 'boolean'
|
||||||
|
| 'link'
|
||||||
| 'number'
|
| 'number'
|
||||||
| 'relation'
|
| 'relation'
|
||||||
| 'social'
|
| 'social'
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export {
|
|||||||
IconTag,
|
IconTag,
|
||||||
IconTarget,
|
IconTarget,
|
||||||
IconTargetArrow,
|
IconTargetArrow,
|
||||||
|
IconTextSize,
|
||||||
IconTimelineEvent,
|
IconTimelineEvent,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
IconUpload,
|
IconUpload,
|
||||||
|
|||||||
@ -246,6 +246,7 @@ const StyledButton = styled.button<
|
|||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
box-sizing: content-box;
|
||||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
|||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
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 { LightIconButton } from '../button/components/LightIconButton';
|
||||||
import { IconApps } from '../constants/icons';
|
import { IconApps } from '../constants/icons';
|
||||||
import { useLazyLoadIcons } from '../hooks/useLazyLoadIcons';
|
import { useLazyLoadIcons } from '../hooks/useLazyLoadIcons';
|
||||||
@ -24,6 +24,7 @@ type IconPickerProps = {
|
|||||||
onClickOutside?: () => void;
|
onClickOutside?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
|
variant?: IconButtonVariant;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledMenuIconItemsContainer = styled.div`
|
const StyledMenuIconItemsContainer = styled.div`
|
||||||
@ -47,6 +48,7 @@ export const IconPicker = ({
|
|||||||
onClickOutside,
|
onClickOutside,
|
||||||
onClose,
|
onClose,
|
||||||
onOpen,
|
onOpen,
|
||||||
|
variant = 'secondary',
|
||||||
}: IconPickerProps) => {
|
}: IconPickerProps) => {
|
||||||
const [searchString, setSearchString] = useState('');
|
const [searchString, setSearchString] = useState('');
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ export const IconPicker = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
Icon={selectedIconKey ? icons[selectedIconKey] : IconApps}
|
Icon={selectedIconKey ? icons[selectedIconKey] : IconApps}
|
||||||
variant="secondary"
|
variant={variant}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
@ -119,7 +121,7 @@ export const IconPicker = ({
|
|||||||
setSearchString('');
|
setSearchString('');
|
||||||
}}
|
}}
|
||||||
onOpen={onOpen}
|
onOpen={onOpen}
|
||||||
></Dropdown>
|
/>
|
||||||
</DropdownScope>
|
</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',
|
||||||
|
}
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
|
import { SettingsObjectFieldFormSection } from '@/settings/data-model/components/SettingsObjectFieldFormSection';
|
||||||
|
import { SettingsObjectFieldTypeSelectSection } from '@/settings/data-model/components/SettingsObjectFieldTypeSelectSection';
|
||||||
import { activeObjectItems } from '@/settings/data-model/constants/mockObjects';
|
import { activeObjectItems } from '@/settings/data-model/constants/mockObjects';
|
||||||
|
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { IconSettings } from '@/ui/display/icon';
|
import { IconSettings } from '@/ui/display/icon';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
@ -21,6 +24,16 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
if (!activeObject) navigate(AppPath.NotFound);
|
if (!activeObject) navigate(AppPath.NotFound);
|
||||||
}, [activeObject, navigate]);
|
}, [activeObject, navigate]);
|
||||||
|
|
||||||
|
const [formValues, setFormValues] = useState<
|
||||||
|
Partial<{
|
||||||
|
iconKey: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}> & { type: ObjectFieldDataType }
|
||||||
|
>({ type: 'number' });
|
||||||
|
|
||||||
|
const canSave = !!formValues.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
@ -36,13 +49,30 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SaveAndCancelButtons
|
<SaveAndCancelButtons
|
||||||
isSaveDisabled
|
isSaveDisabled={!canSave}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
navigate('/settings/objects');
|
navigate('/settings/objects');
|
||||||
}}
|
}}
|
||||||
onSave={() => {}}
|
onSave={() => undefined}
|
||||||
/>
|
/>
|
||||||
</SettingsHeaderContainer>
|
</SettingsHeaderContainer>
|
||||||
|
<SettingsObjectFieldFormSection
|
||||||
|
iconKey={formValues.iconKey}
|
||||||
|
name={formValues.name}
|
||||||
|
description={formValues.description}
|
||||||
|
onChange={(values) =>
|
||||||
|
setFormValues((previousValues) => ({
|
||||||
|
...previousValues,
|
||||||
|
...values,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SettingsObjectFieldTypeSelectSection
|
||||||
|
type={formValues.type}
|
||||||
|
onChange={(type) =>
|
||||||
|
setFormValues((previousValues) => ({ ...previousValues, type }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user