Update new/edit object according to figma (#3093)

* made changes according to figma

* remove click custom in test
This commit is contained in:
brendanlaschke 2023-12-21 11:33:52 +01:00 committed by GitHub
parent 4918865132
commit b416b0f98f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 285 deletions

View File

@ -2,17 +2,20 @@ import styled from '@emotion/styled';
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
import { H2Title } from '@/ui/display/typography/components/H2Title'; import { H2Title } from '@/ui/display/typography/components/H2Title';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea'; import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
type SettingsObjectFormSectionProps = { type SettingsObjectFormSectionProps = {
disabled?: boolean; disabled?: boolean;
icon?: string;
singularName?: string; singularName?: string;
pluralName?: string; pluralName?: string;
description?: string; description?: string;
onChange?: ( onChange?: (
formValues: Partial<{ formValues: Partial<{
icon: string;
labelSingular: string; labelSingular: string;
labelPlural: string; labelPlural: string;
description: string; description: string;
@ -27,8 +30,22 @@ const StyledInputsContainer = styled.div`
width: 100%; 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 = ({ export const SettingsObjectFormSection = ({
disabled, disabled,
icon = 'IconPigMoney',
singularName = '', singularName = '',
pluralName = '', pluralName = '',
description = '', description = '',
@ -36,10 +53,20 @@ export const SettingsObjectFormSection = ({
}: SettingsObjectFormSectionProps) => ( }: SettingsObjectFormSectionProps) => (
<Section> <Section>
<H2Title <H2Title
title="Name and description" title="About"
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>
<StyledInputContainer>
<StyledLabel>Icon</StyledLabel>
<IconPicker
disabled={disabled}
selectedIconKey={icon}
onChange={(icon) => {
onChange?.({ icon: icon.iconKey });
}}
/>
</StyledInputContainer>
<TextInput <TextInput
label="Singular" label="Singular"
placeholder="Investor" placeholder="Investor"

View File

@ -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>
);
};

View File

@ -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 {};

View File

@ -7,35 +7,17 @@ import { SaveAndCancelButtons } from '@/settings/components/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 { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection'; 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 { IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsNewObject = () => { export const SettingsNewObject = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [selectedObjectType, setSelectedObjectType] =
useState<NewObjectType>('Standard');
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const { const { createObjectMetadataItem: createObject } =
activateObjectMetadataItem: activateObject, useObjectMetadataItemForSettings();
createObjectMetadataItem: createObject,
disabledObjectMetadataItems: disabledObjects,
} = useObjectMetadataItemForSettings();
const [
selectedStandardObjectMetadataIds,
setSelectedStandardObjectMetadataIds,
] = useState<Record<string, boolean>>({});
const [customFormValues, setCustomFormValues] = useState<{ const [customFormValues, setCustomFormValues] = useState<{
description?: string; description?: string;
@ -45,49 +27,28 @@ export const SettingsNewObject = () => {
}>({ icon: 'IconPigMoney', labelPlural: '', labelSingular: '' }); }>({ icon: 'IconPigMoney', labelPlural: '', labelSingular: '' });
const canSave = const canSave =
(selectedObjectType === 'Standard' && !!customFormValues.labelPlural && !!customFormValues.labelSingular;
Object.values(selectedStandardObjectMetadataIds).some(
(isSelected) => isSelected,
)) ||
(selectedObjectType === 'Custom' &&
!!customFormValues.labelPlural &&
!!customFormValues.labelSingular);
const handleSave = async () => { const handleSave = async () => {
if (selectedObjectType === 'Standard') { try {
await Promise.all( const createdObject = await createObject({
Object.entries(selectedStandardObjectMetadataIds).map( labelPlural: customFormValues.labelPlural,
([standardObjectMetadataId, isSelected]) => labelSingular: customFormValues.labelSingular,
isSelected description: customFormValues.description,
? activateObject({ id: standardObjectMetadataId }) icon: customFormValues.icon,
: undefined, });
),
navigate(
createdObject.data?.createOneObject.isActive
? `/settings/objects/${getObjectSlug(
createdObject.data.createOneObject,
)}`
: '/settings/objects',
); );
} catch (error) {
navigate('/settings/objects'); enqueueSnackBar((error as Error).message, {
} variant: 'error',
});
if (selectedObjectType === 'Custom') {
try {
const createdObject = await createObject({
labelPlural: customFormValues.labelPlural,
labelSingular: customFormValues.labelSingular,
description: customFormValues.description,
icon: customFormValues.icon,
});
navigate(
createdObject.data?.createOneObject.isActive
? `/settings/objects/${getObjectSlug(
createdObject.data.createOneObject,
)}`
: '/settings/objects',
);
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: 'error',
});
}
} }
}; };
@ -109,53 +70,18 @@ export const SettingsNewObject = () => {
onSave={handleSave} onSave={handleSave}
/> />
</SettingsHeaderContainer> </SettingsHeaderContainer>
<Section> <SettingsObjectFormSection
<H2Title icon={customFormValues.icon}
title="Object type" singularName={customFormValues.labelSingular}
description="The type of object you want to add" pluralName={customFormValues.labelPlural}
/> description={customFormValues.description}
<SettingsNewObjectType onChange={(formValues) => {
selectedType={selectedObjectType} setCustomFormValues((previousValues) => ({
onTypeSelect={setSelectedObjectType} ...previousValues,
/> ...formValues,
</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
singularName={customFormValues.labelSingular}
pluralName={customFormValues.labelPlural}
description={customFormValues.description}
onChange={(formValues) => {
setCustomFormValues((previousValues) => ({
...previousValues,
...formValues,
}));
}}
/>
</>
)}
</SettingsPageContainer> </SettingsPageContainer>
</SubMenuTopBarContainer> </SubMenuTopBarContainer>
); );

View File

@ -7,7 +7,6 @@ import { SaveAndCancelButtons } from '@/settings/components/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 { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection'; import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection';
import { SettingsObjectIconSection } from '@/settings/data-model/object-edit/SettingsObjectIconSection';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { IconArchive, IconSettings } from '@/ui/display/icon'; import { IconArchive, IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title'; import { H2Title } from '@/ui/display/typography/components/H2Title';
@ -113,19 +112,9 @@ export const SettingsObjectEdit = () => {
/> />
)} )}
</SettingsHeaderContainer> </SettingsHeaderContainer>
<SettingsObjectIconSection
disabled={!activeObjectMetadataItem.isCustom}
iconKey={formValues.icon}
label={formValues.labelPlural}
onChange={({ iconKey }) =>
setFormValues((previousFormValues) => ({
...previousFormValues,
icon: iconKey,
}))
}
/>
<SettingsObjectFormSection <SettingsObjectFormSection
disabled={!activeObjectMetadataItem.isCustom} disabled={!activeObjectMetadataItem.isCustom}
icon={formValues.icon}
singularName={formValues.labelSingular} singularName={formValues.labelSingular}
pluralName={formValues.labelPlural} pluralName={formValues.labelPlural}
description={formValues.description} description={formValues.description}

View File

@ -48,7 +48,7 @@ export const SettingsObjects = () => {
<StyledH1Title title="Objects" /> <StyledH1Title title="Objects" />
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title="New object" title="Add object"
accent="blue" accent="blue"
size="small" size="small"
onClick={() => navigate('/settings/objects/new')} onClick={() => navigate('/settings/objects/new')}

View File

@ -1,5 +1,4 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/test';
import { import {
PageDecorator, PageDecorator,
@ -31,15 +30,3 @@ export const WithStandardSelected: Story = {
await sleep(100); 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);
},
};