mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-24 20:42:05 +03:00
Update new/edit object according to figma (#3093)
* made changes according to figma * remove click custom in test
This commit is contained in:
parent
4918865132
commit
b416b0f98f
@ -2,17 +2,20 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
|
||||
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 SettingsObjectFormSectionProps = {
|
||||
disabled?: boolean;
|
||||
icon?: string;
|
||||
singularName?: string;
|
||||
pluralName?: string;
|
||||
description?: string;
|
||||
onChange?: (
|
||||
formValues: Partial<{
|
||||
icon: string;
|
||||
labelSingular: string;
|
||||
labelPlural: string;
|
||||
description: string;
|
||||
@ -27,8 +30,22 @@ const StyledInputsContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFormSection = ({
|
||||
disabled,
|
||||
icon = 'IconPigMoney',
|
||||
singularName = '',
|
||||
pluralName = '',
|
||||
description = '',
|
||||
@ -36,10 +53,20 @@ export const SettingsObjectFormSection = ({
|
||||
}: SettingsObjectFormSectionProps) => (
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Name and description"
|
||||
title="About"
|
||||
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<StyledInputContainer>
|
||||
<StyledLabel>Icon</StyledLabel>
|
||||
<IconPicker
|
||||
disabled={disabled}
|
||||
selectedIconKey={icon}
|
||||
onChange={(icon) => {
|
||||
onChange?.({ icon: icon.iconKey });
|
||||
}}
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
<TextInput
|
||||
label="Singular"
|
||||
placeholder="Investor"
|
||||
|
@ -1,70 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconBox, IconDatabase, IconFileCheck } from '@/ui/display/icon';
|
||||
|
||||
import { SettingsObjectTypeCard } from './SettingsObjectTypeCard';
|
||||
|
||||
export type NewObjectType = 'Standard' | 'Custom' | 'Remote';
|
||||
|
||||
type SettingsNewObjectTypeProps = {
|
||||
selectedType?: NewObjectType;
|
||||
onTypeSelect?: (type: NewObjectType) => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const SettingsNewObjectType = ({
|
||||
selectedType,
|
||||
onTypeSelect,
|
||||
}: SettingsNewObjectTypeProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledContainer>
|
||||
<SettingsObjectTypeCard
|
||||
title={'Standard'}
|
||||
color="blue"
|
||||
selected={selectedType === 'Standard'}
|
||||
prefixIcon={
|
||||
<IconFileCheck
|
||||
size={theme.icon.size.lg}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
}
|
||||
onClick={() => onTypeSelect?.('Standard')}
|
||||
/>
|
||||
<SettingsObjectTypeCard
|
||||
title="Custom"
|
||||
color="orange"
|
||||
selected={selectedType === 'Custom'}
|
||||
prefixIcon={
|
||||
<IconBox
|
||||
size={theme.icon.size.lg}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
}
|
||||
onClick={() => onTypeSelect?.('Custom')}
|
||||
/>
|
||||
<SettingsObjectTypeCard
|
||||
title="Remote"
|
||||
soon
|
||||
disabled
|
||||
color="green"
|
||||
selected={selectedType === 'Remote'}
|
||||
prefixIcon={
|
||||
<IconDatabase
|
||||
size={theme.icon.size.lg}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -1,81 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconCheck } from '@/ui/display/icon';
|
||||
import { SoonPill } from '@/ui/display/pill/components/SoonPill';
|
||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
|
||||
const StyledObjectTypeCard = styled.div<SettingsObjectTypeCardProps>`
|
||||
${({ theme, disabled, selected }) => `
|
||||
background: ${theme.background.transparent.primary};
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${theme.font.family};
|
||||
font-weight: 500;
|
||||
border-style: solid;
|
||||
border-width: '1px';
|
||||
padding: ${theme.spacing(3)};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
gap: ${theme.spacing(2)};
|
||||
border-color: ${
|
||||
selected ? theme.border.color.inverted : theme.border.color.medium
|
||||
};
|
||||
color: ${theme.font.color.primary};
|
||||
align-items: center;
|
||||
width: 140px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledTag = styled(Tag)`
|
||||
box-sizing: border-box;
|
||||
height: ${({ theme }) => theme.spacing(5)};
|
||||
`;
|
||||
|
||||
const StyledIconCheck = styled(IconCheck)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const StyledSoonPill = styled(SoonPill)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
type SettingsObjectTypeCardProps = {
|
||||
prefixIcon?: React.ReactNode;
|
||||
title: string;
|
||||
soon?: boolean;
|
||||
disabled?: boolean;
|
||||
color: ThemeColor;
|
||||
selected: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const SettingsObjectTypeCard = ({
|
||||
prefixIcon,
|
||||
title,
|
||||
soon = false,
|
||||
selected,
|
||||
disabled = false,
|
||||
color,
|
||||
onClick,
|
||||
}: SettingsObjectTypeCardProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledObjectTypeCard
|
||||
title={title}
|
||||
soon={soon}
|
||||
disabled={disabled}
|
||||
color={color}
|
||||
selected={selected}
|
||||
onClick={onClick}
|
||||
>
|
||||
{prefixIcon}
|
||||
<StyledTag color={color} text={title} />
|
||||
{soon && <StyledSoonPill />}
|
||||
{!disabled && selected && <StyledIconCheck size={theme.icon.size.md} />}
|
||||
</StyledObjectTypeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export {};
|
@ -7,35 +7,17 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection';
|
||||
import { SettingsAvailableStandardObjectsSection } from '@/settings/data-model/new-object/components/SettingsAvailableStandardObjectsSection';
|
||||
import {
|
||||
NewObjectType,
|
||||
SettingsNewObjectType,
|
||||
} from '@/settings/data-model/new-object/components/SettingsNewObjectType';
|
||||
import { SettingsObjectIconSection } from '@/settings/data-model/object-edit/SettingsObjectIconSection';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
|
||||
export const SettingsNewObject = () => {
|
||||
const navigate = useNavigate();
|
||||
const [selectedObjectType, setSelectedObjectType] =
|
||||
useState<NewObjectType>('Standard');
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const {
|
||||
activateObjectMetadataItem: activateObject,
|
||||
createObjectMetadataItem: createObject,
|
||||
disabledObjectMetadataItems: disabledObjects,
|
||||
} = useObjectMetadataItemForSettings();
|
||||
|
||||
const [
|
||||
selectedStandardObjectMetadataIds,
|
||||
setSelectedStandardObjectMetadataIds,
|
||||
] = useState<Record<string, boolean>>({});
|
||||
const { createObjectMetadataItem: createObject } =
|
||||
useObjectMetadataItemForSettings();
|
||||
|
||||
const [customFormValues, setCustomFormValues] = useState<{
|
||||
description?: string;
|
||||
@ -45,29 +27,9 @@ export const SettingsNewObject = () => {
|
||||
}>({ icon: 'IconPigMoney', labelPlural: '', labelSingular: '' });
|
||||
|
||||
const canSave =
|
||||
(selectedObjectType === 'Standard' &&
|
||||
Object.values(selectedStandardObjectMetadataIds).some(
|
||||
(isSelected) => isSelected,
|
||||
)) ||
|
||||
(selectedObjectType === 'Custom' &&
|
||||
!!customFormValues.labelPlural &&
|
||||
!!customFormValues.labelSingular);
|
||||
!!customFormValues.labelPlural && !!customFormValues.labelSingular;
|
||||
|
||||
const handleSave = async () => {
|
||||
if (selectedObjectType === 'Standard') {
|
||||
await Promise.all(
|
||||
Object.entries(selectedStandardObjectMetadataIds).map(
|
||||
([standardObjectMetadataId, isSelected]) =>
|
||||
isSelected
|
||||
? activateObject({ id: standardObjectMetadataId })
|
||||
: undefined,
|
||||
),
|
||||
);
|
||||
|
||||
navigate('/settings/objects');
|
||||
}
|
||||
|
||||
if (selectedObjectType === 'Custom') {
|
||||
try {
|
||||
const createdObject = await createObject({
|
||||
labelPlural: customFormValues.labelPlural,
|
||||
@ -88,7 +50,6 @@ export const SettingsNewObject = () => {
|
||||
variant: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -109,41 +70,8 @@ export const SettingsNewObject = () => {
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Object type"
|
||||
description="The type of object you want to add"
|
||||
/>
|
||||
<SettingsNewObjectType
|
||||
selectedType={selectedObjectType}
|
||||
onTypeSelect={setSelectedObjectType}
|
||||
/>
|
||||
</Section>
|
||||
{selectedObjectType === 'Standard' && (
|
||||
<SettingsAvailableStandardObjectsSection
|
||||
objectItems={disabledObjects.filter(({ isCustom }) => !isCustom)}
|
||||
onChange={(selectedIds) =>
|
||||
setSelectedStandardObjectMetadataIds((previousSelectedIds) => ({
|
||||
...previousSelectedIds,
|
||||
...selectedIds,
|
||||
}))
|
||||
}
|
||||
selectedIds={selectedStandardObjectMetadataIds}
|
||||
/>
|
||||
)}
|
||||
{selectedObjectType === 'Custom' && (
|
||||
<>
|
||||
<SettingsObjectIconSection
|
||||
label={customFormValues.labelPlural}
|
||||
iconKey={customFormValues.icon}
|
||||
onChange={({ iconKey }) => {
|
||||
setCustomFormValues((previousValues) => ({
|
||||
...previousValues,
|
||||
icon: iconKey,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<SettingsObjectFormSection
|
||||
icon={customFormValues.icon}
|
||||
singularName={customFormValues.labelSingular}
|
||||
pluralName={customFormValues.labelPlural}
|
||||
description={customFormValues.description}
|
||||
@ -154,8 +82,6 @@ export const SettingsNewObject = () => {
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
|
@ -7,7 +7,6 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection';
|
||||
import { SettingsObjectIconSection } from '@/settings/data-model/object-edit/SettingsObjectIconSection';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconArchive, IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
@ -113,19 +112,9 @@ export const SettingsObjectEdit = () => {
|
||||
/>
|
||||
)}
|
||||
</SettingsHeaderContainer>
|
||||
<SettingsObjectIconSection
|
||||
disabled={!activeObjectMetadataItem.isCustom}
|
||||
iconKey={formValues.icon}
|
||||
label={formValues.labelPlural}
|
||||
onChange={({ iconKey }) =>
|
||||
setFormValues((previousFormValues) => ({
|
||||
...previousFormValues,
|
||||
icon: iconKey,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<SettingsObjectFormSection
|
||||
disabled={!activeObjectMetadataItem.isCustom}
|
||||
icon={formValues.icon}
|
||||
singularName={formValues.labelSingular}
|
||||
pluralName={formValues.labelPlural}
|
||||
description={formValues.description}
|
||||
|
@ -48,7 +48,7 @@ export const SettingsObjects = () => {
|
||||
<StyledH1Title title="Objects" />
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New object"
|
||||
title="Add object"
|
||||
accent="blue"
|
||||
size="small"
|
||||
onClick={() => navigate('/settings/objects/new')}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/test';
|
||||
|
||||
import {
|
||||
PageDecorator,
|
||||
@ -31,15 +30,3 @@ export const WithStandardSelected: Story = {
|
||||
await sleep(100);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomSelected: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
const customButtonElement = canvas.getByText('Custom');
|
||||
|
||||
await userEvent.click(customButtonElement);
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user