Add SettingsCard for Config Data Type and Accounts Settings (#7093)

https://github.com/twentyhq/twenty/issues/6950
Add new Settings Card for Config Data Type and accounts Settings
Before:
<img width="707" alt="Screenshot 2024-09-11 at 17 43 16"
src="https://github.com/user-attachments/assets/63ff9373-fa86-4b22-8e8b-21483039c3be">
After:
<img width="755" alt="Screenshot 2024-09-17 at 14 15 18"
src="https://github.com/user-attachments/assets/213c24a1-dc1c-4ffb-8890-7c1f63ed376c">
<img width="755" alt="Screenshot 2024-09-17 at 14 15 38"
src="https://github.com/user-attachments/assets/0fc12d19-b92a-493d-80fa-0064cf491fbc">
This commit is contained in:
Ana Sofia Marin Alexandre
2024-09-18 18:32:41 +02:00
committed by GitHub
parent b1cb8998f8
commit cac3e116a3
17 changed files with 207 additions and 73 deletions

View File

@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import { H2Title, IconCalendarEvent, IconMailCog } from 'twenty-ui';
import { SettingsNavigationCard } from '@/settings/components/SettingsNavigationCard';
import { SettingsCard } from '@/settings/components/SettingsCard';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Section } from '@/ui/layout/section/components/Section';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { useTheme } from '@emotion/react';
const StyledCardsContainer = styled.div`
display: flex;
@ -14,6 +15,7 @@ const StyledCardsContainer = styled.div`
`;
export const SettingsAccountsSettingsSection = () => {
const theme = useTheme();
return (
<Section>
<H2Title
@ -22,16 +24,30 @@ export const SettingsAccountsSettingsSection = () => {
/>
<StyledCardsContainer>
<UndecoratedLink to={getSettingsPagePath(SettingsPath.AccountsEmails)}>
<SettingsNavigationCard Icon={IconMailCog} title="Emails">
Set email visibility, manage your blocklist and more.
</SettingsNavigationCard>
<SettingsCard
Icon={
<IconMailCog
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
/>
}
title="Emails"
description="Set email visibility, manage your blocklist and more."
/>
</UndecoratedLink>
<UndecoratedLink
to={getSettingsPagePath(SettingsPath.AccountsCalendars)}
>
<SettingsNavigationCard Icon={IconCalendarEvent} title="Calendar">
Configure and customize your calendar preferences.
</SettingsNavigationCard>
<SettingsCard
Icon={
<IconCalendarEvent
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
/>
}
title="Calendar"
description="Configure and customize your calendar preferences."
/>
</UndecoratedLink>
</StyledCardsContainer>
</Section>

View File

@ -1,16 +1,16 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconChevronRight, IconComponent, Pill } from 'twenty-ui';
import { IconChevronRight, Pill } from 'twenty-ui';
import { Card } from '@/ui/layout/card/components/Card';
import { CardContent } from '@/ui/layout/card/components/CardContent';
import { ReactNode } from 'react';
type SettingsNavigationCardProps = {
children: ReactNode;
type SettingsCardProps = {
description?: string;
disabled?: boolean;
soon?: boolean;
Icon: IconComponent;
Icon: ReactNode;
onClick?: () => void;
title: string;
className?: string;
@ -24,19 +24,23 @@ const StyledCard = styled(Card)<{
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
cursor: ${({ disabled, onClick }) =>
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
width: 100%;
& :hover {
background-color: ${({ theme }) => theme.background.quaternary};
}
`;
const StyledCardContent = styled(CardContent)`
const StyledCardContent = styled(CardContent)<object>`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(4, 3)};
padding: ${({ theme }) => theme.spacing(2, 2)};
`;
const StyledHeader = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(3)};
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTitle = styled.div<{ disabled?: boolean }>`
@ -54,18 +58,27 @@ const StyledIconChevronRight = styled(IconChevronRight)`
`;
const StyledDescription = styled.div`
padding-left: ${({ theme }) => theme.spacing(8)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(7)};
`;
export const SettingsNavigationCard = ({
children,
const StyledIconContainer = styled.div`
align-items: center;
display: flex;
height: 24px;
justify-content: center;
width: 24px;
`;
export const SettingsCard = ({
description,
soon,
disabled = soon,
Icon,
onClick,
title,
className,
}: SettingsNavigationCardProps) => {
}: SettingsCardProps) => {
const theme = useTheme();
return (
@ -73,17 +86,18 @@ export const SettingsNavigationCard = ({
disabled={disabled}
onClick={disabled ? undefined : onClick}
className={className}
rounded={true}
>
<StyledCardContent>
<StyledHeader>
<Icon size={theme.icon.size.lg} stroke={theme.icon.stroke.sm} />
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledTitle disabled={disabled}>
{title}
{soon && <Pill label="Soon" />}
</StyledTitle>
<StyledIconChevronRight size={theme.icon.size.sm} />
</StyledHeader>
<StyledDescription>{children}</StyledDescription>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledCardContent>
</StyledCard>
);

View File

@ -0,0 +1,24 @@
import { SettingsCard } from '@/settings/components/SettingsCard';
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { ComponentDecorator, IconMailCog } from 'twenty-ui';
const meta: Meta<typeof SettingsCard> = {
title: 'Modules/Settings/SettingsCard',
component: SettingsCard,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof SettingsCard>;
export const Default: Story = {
args: {
onClick: () => {},
Icon: React.createElement(IconMailCog),
title: 'Settings Card',
},
argTypes: {
className: { control: 'false' },
Icon: { control: 'false' },
},
};

View File

@ -1,9 +1,8 @@
import {
IconComponent,
IconRelationManyToMany,
IconRelationManyToOne,
IconRelationOneToMany,
IconRelationOneToOne,
IllustrationIconManyToMany,
IllustrationIconOneToMany,
IllustrationIconOneToOne,
} from 'twenty-ui';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
@ -22,24 +21,24 @@ export const RELATION_TYPES: Record<
> = {
[RelationDefinitionType.OneToMany]: {
label: 'Has many',
Icon: IconRelationOneToMany,
Icon: IllustrationIconOneToMany,
imageSrc: OneToManySvg,
},
[RelationDefinitionType.OneToOne]: {
label: 'Has one',
Icon: IconRelationOneToOne,
Icon: IllustrationIconOneToOne,
imageSrc: OneToOneSvg,
},
[RelationDefinitionType.ManyToOne]: {
label: 'Belongs to one',
Icon: IconRelationManyToOne,
Icon: IllustrationIconOneToMany,
imageSrc: OneToManySvg,
isImageFlipped: true,
},
// Not supported yet
[RelationDefinitionType.ManyToMany]: {
label: 'Belongs to many',
Icon: IconRelationManyToMany,
Icon: IllustrationIconManyToMany,
imageSrc: OneToManySvg,
isImageFlipped: true,
},

View File

@ -1,6 +1,6 @@
import {
IconBracketsContain,
IconComponent,
IllustrationIconArray,
IllustrationIconCalendarEvent,
IllustrationIconCalendarTime,
IllustrationIconCurrency,
@ -186,7 +186,7 @@ export const SETTINGS_FIELD_TYPE_CONFIGS = {
},
[FieldMetadataType.Array]: {
label: 'Array',
Icon: IconBracketsContain,
Icon: IllustrationIconArray,
category: 'Basic',
exampleValue: ['value1', 'value2'],
},

View File

@ -1,8 +1,5 @@
import styled from '@emotion/styled';
import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsCard } from '@/settings/components/SettingsCard';
import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories';
import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions';
import {
@ -13,12 +10,14 @@ import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/field
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Section } from '@react-email/components';
import { useState } from 'react';
import { H2Title, IconChevronRight, IconSearch } from 'twenty-ui';
import { Controller, useFormContext } from 'react-hook-form';
import { H2Title, IconSearch } from 'twenty-ui';
import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const settingsDataModelFieldTypeFormSchema = z.object({
@ -51,13 +50,6 @@ const StyledTypeSelectContainer = styled.div`
width: 100%;
`;
const StyledButton = styled(Button)<{ isActive: boolean }>`
background: ${({ theme, isActive }) =>
isActive ? theme.background.quaternary : theme.background.secondary};
height: 40px;
width: 100%;
border-radius: ${({ theme }) => theme.border.radius.md};
`;
const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
@ -66,20 +58,13 @@ const StyledContainer = styled.div`
width: 100%;
`;
const StyledButtonContainer = styled.div`
const StyledCardContainer = styled.div`
display: flex;
position: relative;
width: calc(50% - ${({ theme }) => theme.spacing(1)});
`;
const StyledRightChevron = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.secondary};
position: absolute;
right: ${({ theme }) => theme.spacing(2)};
top: 50%;
transform: translateY(-50%);
`;
const StyledSearchInput = styled(TextInput)`
width: 100%;
`;
@ -90,9 +75,9 @@ export const SettingsDataModelFieldTypeSelect = ({
fieldMetadataItem,
onFieldTypeSelect,
}: SettingsDataModelFieldTypeSelectProps) => {
const theme = useTheme();
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();
const [searchQuery, setSearchQuery] = useState('');
const theme = useTheme();
const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig>(
SETTINGS_FIELD_TYPE_CONFIGS,
).filter(
@ -136,7 +121,7 @@ export const SettingsDataModelFieldTypeSelect = ({
? (fieldMetadataItem.type as SettingsSupportedFieldType)
: FieldMetadataType.Text
}
render={({ field: { onChange, value } }) => (
render={({ field: { onChange } }) => (
<StyledTypeSelectContainer className={className}>
<Section>
<StyledSearchInput
@ -158,8 +143,8 @@ export const SettingsDataModelFieldTypeSelect = ({
{fieldTypeConfigs
.filter(([, config]) => config.category === category)
.map(([key, config]) => (
<StyledButtonContainer>
<StyledButton
<StyledCardContainer>
<SettingsCard
key={key}
onClick={() => {
onChange(key as SettingsSupportedFieldType);
@ -168,13 +153,15 @@ export const SettingsDataModelFieldTypeSelect = ({
);
onFieldTypeSelect();
}}
Icon={
<config.Icon
size={theme.icon.size.xl}
stroke={theme.icon.stroke.sm}
/>
}
title={config.label}
Icon={config.Icon}
size="small"
isActive={value === key}
/>
<StyledRightChevron size={theme.icon.size.md} />
</StyledButtonContainer>
</StyledCardContainer>
))}
</StyledContainer>
</Section>

View File

@ -1,6 +1,6 @@
import { Link } from 'react-router-dom';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { IconComponent, IconTwentyStar } from 'twenty-ui';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
@ -23,10 +23,9 @@ const StyledDataType = styled.div<{
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(1)};
gap: ${({ theme }) => theme.spacing(2)};
height: 20px;
overflow: hidden;
padding: 0 ${({ theme }) => theme.spacing(2)};
text-decoration: none;
${({ to }) =>
@ -36,11 +35,11 @@ const StyledDataType = styled.div<{
`
: ''}
${({ theme, value }) =>
${({ value, theme }) =>
value === FieldMetadataType.Relation
? css`
border-color: ${theme.tag.background.purple};
color: ${theme.color.purple};
color: ${theme.font.color.secondary};
text-decoration: underline;
`
: ''}
`;