Settings Option Card component (#8456)

fixes - #8195

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
nitin
2024-11-18 14:52:33 +05:30
committed by GitHub
parent ade1c57ff4
commit 2f5dc26545
56 changed files with 931 additions and 920 deletions

View File

@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
type H2TitleProps = {
title: string;
description?: string;
addornment?: React.ReactNode;
adornment?: React.ReactNode;
};
const StyledContainer = styled.div`
@@ -33,11 +33,11 @@ const StyledDescription = styled.h3`
margin-top: ${({ theme }) => theme.spacing(3)};
`;
export const H2Title = ({ title, description, addornment }: H2TitleProps) => (
export const H2Title = ({ title, description, adornment }: H2TitleProps) => (
<StyledContainer>
<StyledTitleContainer>
<StyledTitle>{title}</StyledTitle>
{addornment}
{adornment}
</StyledTitleContainer>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledContainer>

View File

@@ -2,10 +2,10 @@ import { CalendarChannel } from '@/accounts/types/CalendarChannel';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsCalendarVisibilitySettingsCard';
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import styled from '@emotion/styled';
import { Section } from '@react-email/components';
import { Card, H2Title } from 'twenty-ui';
import { Card, H2Title, IconUserPlus } from 'twenty-ui';
import { CalendarChannelVisibility } from '~/generated-metadata/graphql';
const StyledDetailsContainer = styled.div`
@@ -63,8 +63,9 @@ export const SettingsAccountsCalendarChannelDetails = ({
title="Contact auto-creation"
description="Automatically create contacts for people you've participated in an event with."
/>
<Card>
<SettingsOptionCardContent
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconUserPlus}
title="Auto-creation"
description="Automatically create contacts for people."
checked={calendarChannel.isContactAutoCreationEnabled}

View File

@@ -1,5 +1,5 @@
import styled from '@emotion/styled';
import { Card, H2Title, Section } from 'twenty-ui';
import { Card, H2Title, IconBriefcase, IconUsers, Section } from 'twenty-ui';
import {
MessageChannel,
@@ -9,7 +9,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard';
import { SettingsAccountsMessageVisibilityCard } from '@/settings/accounts/components/SettingsAccountsMessageVisibilityCard';
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { MessageChannelVisibility } from '~/generated-metadata/graphql';
type SettingsAccountsMessageChannelDetailsProps = {
@@ -98,8 +98,9 @@ export const SettingsAccountsMessageChannelDetails = ({
/>
</Section>
<Section>
<Card>
<SettingsOptionCardContent
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconBriefcase}
title="Exclude non-professional emails"
description="Dont create contacts from/to Gmail, Outlook emails"
divider
@@ -110,7 +111,8 @@ export const SettingsAccountsMessageChannelDetails = ({
);
}}
/>
<SettingsOptionCardContent
<SettingsOptionCardContentToggle
Icon={IconUsers}
title="Exclude group emails"
description="Dont sync emails from team@ support@ noreply@..."
checked={messageChannel.excludeGroupEmails}

View File

@@ -0,0 +1,68 @@
import { useExpandedAnimation } from '@/settings/hooks/useExpandedAnimation';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import styled from '@emotion/styled';
import { AnimatePresence, motion } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { IconTool, MAIN_COLORS } from 'twenty-ui';
const StyledAdvancedWrapper = styled.div`
position: relative;
width: 100%;
`;
const StyledIconContainer = styled.div`
border-right: 1px solid ${MAIN_COLORS.yellow};
display: flex;
height: 100%;
left: ${({ theme }) => theme.spacing(-6)};
position: absolute;
top: 0;
`;
const StyledContent = styled.div`
width: 100%;
`;
const StyledIconTool = styled(IconTool)`
margin-right: ${({ theme }) => theme.spacing(0.5)};
`;
type AdvancedSettingsWrapperProps = {
children: React.ReactNode;
dimension?: 'width' | 'height';
hideIcon?: boolean;
};
export const AdvancedSettingsWrapper = ({
children,
dimension = 'height',
hideIcon = false,
}: AdvancedSettingsWrapperProps) => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const { contentRef, motionAnimationVariants } = useExpandedAnimation(
isAdvancedModeEnabled,
dimension,
);
return (
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
ref={contentRef}
initial="initial"
animate="animate"
exit="exit"
variants={motionAnimationVariants}
>
<StyledAdvancedWrapper>
{!hideIcon && (
<StyledIconContainer>
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
</StyledIconContainer>
)}
<StyledContent>{children}</StyledContent>
</StyledAdvancedWrapper>
</motion.div>
)}
</AnimatePresence>
);
};

View File

@@ -0,0 +1,103 @@
import { TextInput } from '@/ui/input/components/TextInput';
import styled from '@emotion/styled';
import { Button, IconMinus, IconPlus } from 'twenty-ui';
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';
type SettingsCounterProps = {
value: number;
onChange: (value: number) => void;
minValue?: number;
maxValue?: number;
disabled?: boolean;
};
const StyledCounterContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
margin-left: auto;
width: ${({ theme }) => theme.spacing(30)};
`;
const StyledTextInput = styled(TextInput)`
width: ${({ theme }) => theme.spacing(16)};
input {
width: ${({ theme }) => theme.spacing(16)};
height: ${({ theme }) => theme.spacing(6)};
text-align: center;
font-weight: ${({ theme }) => theme.font.weight.medium};
}
`;
const StyledControlButton = styled(Button)`
height: ${({ theme }) => theme.spacing(6)};
width: ${({ theme }) => theme.spacing(6)};
padding: 0;
justify-content: center;
svg {
height: ${({ theme }) => theme.spacing(4)};
width: ${({ theme }) => theme.spacing(4)};
}
`;
export const SettingsCounter = ({
value,
onChange,
minValue = 0,
maxValue = 100,
disabled = false,
}: SettingsCounterProps) => {
const handleIncrementCounter = () => {
if (value < maxValue) {
onChange(value + 1);
}
};
const handleDecrementCounter = () => {
if (value > minValue) {
onChange(value - 1);
}
};
const handleTextInputChange = (value: string) => {
const castedNumber = castAsNumberOrNull(value);
if (castedNumber === null) {
onChange(minValue);
return;
}
if (castedNumber < minValue) {
return;
}
if (castedNumber > maxValue) {
onChange(maxValue);
return;
}
onChange(castedNumber);
};
return (
<StyledCounterContainer>
<StyledControlButton
variant="secondary"
onClick={handleDecrementCounter}
Icon={IconMinus}
disabled={disabled}
/>
<StyledTextInput
name="counter"
fullWidth
value={value.toString()}
onChange={handleTextInputChange}
disabled={disabled}
/>
<StyledControlButton
variant="secondary"
onClick={handleIncrementCounter}
Icon={IconPlus}
disabled={disabled}
/>
</StyledCounterContainer>
);
};

View File

@@ -23,7 +23,7 @@ import {
import { useAuth } from '@/auth/hooks/useAuth';
import { billingState } from '@/client-config/states/billingState';
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation';
import { useExpandedAnimation } from '@/settings/hooks/useExpandedAnimation';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import {
@@ -69,7 +69,7 @@ const StyledIconTool = styled(IconTool)`
export const SettingsNavigationDrawerItems = () => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
const { contentRef, motionAnimationVariants } = useExpandedAnimation(
isAdvancedModeEnabled,
);
const { signOut } = useAuth();

View File

@@ -1,95 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useId } from 'react';
import { CardContent, IconComponent, Toggle } from 'twenty-ui';
type SettingsOptionCardContentProps = {
Icon?: IconComponent;
title: React.ReactNode;
description: string;
divider?: boolean;
checked: boolean;
onChange: (checked: boolean) => void;
};
const StyledCardContent = styled(CardContent)`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
cursor: pointer;
position: relative;
&:hover {
background: ${({ theme }) => theme.background.transparent.lighter};
}
`;
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
`;
const StyledIcon = styled.div`
align-items: center;
border: 2px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ theme }) => theme.background.primary};
display: flex;
height: ${({ theme }) => theme.spacing(8)};
justify-content: center;
width: ${({ theme }) => theme.spacing(8)};
min-width: ${({ theme }) => theme.icon.size.md};
`;
const StyledToggle = styled(Toggle)`
margin-left: auto;
`;
const StyledCover = styled.span`
cursor: pointer;
inset: 0;
position: absolute;
`;
export const SettingsOptionCardContent = ({
Icon,
title,
description,
divider,
checked,
onChange,
}: SettingsOptionCardContentProps) => {
const theme = useTheme();
const toggleId = useId();
return (
<StyledCardContent divider={divider}>
{Icon && (
<StyledIcon>
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.md} />
</StyledIcon>
)}
<div>
<StyledTitle>
<label htmlFor={toggleId}>
{title}
<StyledCover />
</label>
</StyledTitle>
<StyledDescription>{description}</StyledDescription>
</div>
<StyledToggle id={toggleId} value={checked} onChange={onChange} />
</StyledCardContent>
);
};

View File

@@ -0,0 +1,37 @@
import styled from '@emotion/styled';
import { CardContent } from 'twenty-ui';
type StyledCardContentProps = {
disabled?: boolean;
divider?: boolean;
};
export const StyledSettingsOptionCardContent = styled(
CardContent,
)<StyledCardContentProps>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
`;
export const StyledSettingsOptionCardIcon = styled.div`
align-items: center;
border: 2px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ theme }) => theme.background.primary};
display: flex;
height: ${({ theme }) => theme.spacing(8)};
justify-content: center;
width: ${({ theme }) => theme.spacing(8)};
min-width: ${({ theme }) => theme.icon.size.md};
`;
export const StyledSettingsOptionCardTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-bottom: ${({ theme }) => theme.spacing(1)};
`;
export const StyledSettingsOptionCardDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
`;

View File

@@ -0,0 +1,57 @@
import { SettingsCounter } from '@/settings/components/SettingsCounter';
import {
StyledSettingsOptionCardContent,
StyledSettingsOptionCardDescription,
StyledSettingsOptionCardIcon,
StyledSettingsOptionCardTitle,
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import { IconComponent } from 'twenty-ui';
type SettingsOptionCardContentCounterProps = {
Icon?: IconComponent;
title: React.ReactNode;
description?: string;
divider?: boolean;
disabled?: boolean;
value: number;
onChange: (value: number) => void;
minValue?: number;
maxValue?: number;
};
export const SettingsOptionCardContentCounter = ({
Icon,
title,
description,
divider,
disabled = false,
value,
onChange,
minValue,
maxValue,
}: SettingsOptionCardContentCounterProps) => {
return (
<StyledSettingsOptionCardContent divider={divider} disabled={disabled}>
{Icon && (
<StyledSettingsOptionCardIcon>
<SettingsOptionIconCustomizer Icon={Icon} />
</StyledSettingsOptionCardIcon>
)}
<div>
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
{description && (
<StyledSettingsOptionCardDescription>
{description}
</StyledSettingsOptionCardDescription>
)}
</div>
<SettingsCounter
value={value}
onChange={onChange}
minValue={minValue}
maxValue={maxValue}
/>
</StyledSettingsOptionCardContent>
);
};

View File

@@ -0,0 +1,75 @@
import {
StyledSettingsOptionCardContent,
StyledSettingsOptionCardDescription,
StyledSettingsOptionCardIcon,
StyledSettingsOptionCardTitle,
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import { Select } from '@/ui/input/components/Select';
import styled from '@emotion/styled';
import { IconComponent } from 'twenty-ui';
const StyledSettingsOptionCardSelect = styled(Select)`
margin-left: auto;
width: 120px;
`;
type SelectValue = string | number | boolean | null;
type SettingsOptionCardContentSelectProps<Value extends SelectValue> = {
Icon?: IconComponent;
title: React.ReactNode;
description?: string;
divider?: boolean;
disabled?: boolean;
value: Value;
onChange: (value: SelectValue) => void;
options: {
value: Value;
label: string;
Icon?: IconComponent;
}[];
selectClassName?: string;
dropdownId: string;
fullWidth?: boolean;
};
export const SettingsOptionCardContentSelect = <Value extends SelectValue>({
Icon,
title,
description,
divider,
disabled = false,
value,
onChange,
options,
selectClassName,
dropdownId,
fullWidth,
}: SettingsOptionCardContentSelectProps<Value>) => {
return (
<StyledSettingsOptionCardContent divider={divider} disabled={disabled}>
{Icon && (
<StyledSettingsOptionCardIcon>
<SettingsOptionIconCustomizer Icon={Icon} />
</StyledSettingsOptionCardIcon>
)}
<div>
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
<StyledSettingsOptionCardDescription>
{description}
</StyledSettingsOptionCardDescription>
</div>
<StyledSettingsOptionCardSelect
className={selectClassName}
dropdownWidth={fullWidth ? 'auto' : 120}
disabled={disabled}
dropdownId={dropdownId}
value={value}
onChange={onChange}
options={options}
selectSizeVariant="small"
/>
</StyledSettingsOptionCardContent>
);
};

View File

@@ -0,0 +1,89 @@
import {
StyledSettingsOptionCardContent,
StyledSettingsOptionCardDescription,
StyledSettingsOptionCardIcon,
StyledSettingsOptionCardTitle,
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useId } from 'react';
import { IconComponent, Toggle } from 'twenty-ui';
const StyledSettingsOptionCardToggleContent = styled(
StyledSettingsOptionCardContent,
)`
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
position: relative;
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
&:hover {
background: ${({ theme }) => theme.background.transparent.lighter};
}
`;
const StyledSettingsOptionCardToggleButton = styled(Toggle)`
margin-left: auto;
`;
const StyledSettingsOptionCardToggleCover = styled.span`
cursor: pointer;
inset: 0;
position: absolute;
`;
type SettingsOptionCardContentToggleProps = {
Icon?: IconComponent;
title: React.ReactNode;
description?: string;
divider?: boolean;
disabled?: boolean;
advancedMode?: boolean;
checked: boolean;
onChange: (checked: boolean) => void;
};
export const SettingsOptionCardContentToggle = ({
Icon,
title,
description,
divider,
disabled = false,
advancedMode = false,
checked,
onChange,
}: SettingsOptionCardContentToggleProps) => {
const theme = useTheme();
const toggleId = useId();
return (
<StyledSettingsOptionCardToggleContent
divider={divider}
disabled={disabled}
>
{Icon && (
<StyledSettingsOptionCardIcon>
<SettingsOptionIconCustomizer Icon={Icon} />
</StyledSettingsOptionCardIcon>
)}
<div>
<StyledSettingsOptionCardTitle>
<label htmlFor={toggleId}>
{title}
<StyledSettingsOptionCardToggleCover />
</label>
</StyledSettingsOptionCardTitle>
<StyledSettingsOptionCardDescription>
{description}
</StyledSettingsOptionCardDescription>
</div>
<StyledSettingsOptionCardToggleButton
id={toggleId}
value={checked}
onChange={onChange}
disabled={disabled}
color={advancedMode ? theme.color.yellow : theme.color.blue}
/>
</StyledSettingsOptionCardToggleContent>
);
};

View File

@@ -0,0 +1,33 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent } from 'twenty-ui';
type SettingsOptionIconCustomizerProps = {
Icon: IconComponent;
zoom?: number;
rotate?: number;
};
const StyledIconCustomizer = styled.div<{ zoom: number; rotate: number }>`
display: inline-flex;
align-items: center;
justify-content: center;
transform: scale(${({ zoom }) => zoom}) rotate(${({ rotate }) => rotate}deg);
`;
export const SettingsOptionIconCustomizer = ({
Icon,
zoom = 1,
rotate = -4,
}: SettingsOptionIconCustomizerProps) => {
const theme = useTheme();
return (
<StyledIconCustomizer zoom={zoom} rotate={rotate}>
<Icon
size={theme.icon.size.xl}
color={theme.IllustrationIcon.color.grey}
stroke={theme.icon.stroke.md}
/>
</StyledIconCustomizer>
);
};

View File

@@ -1,11 +1,10 @@
import styled from '@emotion/styled';
import { Controller, useFormContext } from 'react-hook-form';
import { IconCheck, IconX, CardContent } from 'twenty-ui';
import { IconCheck, IconX } from 'twenty-ui';
import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
import { Select } from '@/ui/input/components/Select';
import { isDefined } from '~/utils/isDefined';
export const settingsDataModelFieldBooleanFormSchema = z.object({
@@ -21,18 +20,6 @@ type SettingsDataModelFieldBooleanFormProps = {
fieldMetadataItem: Pick<FieldMetadataItem, 'defaultValue'>;
};
const StyledContainer = styled(CardContent)`
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
display: block;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: 6px;
`;
export const SettingsDataModelFieldBooleanForm = ({
className,
fieldMetadataItem,
@@ -45,37 +32,36 @@ export const SettingsDataModelFieldBooleanForm = ({
});
return (
<StyledContainer>
<StyledLabel>Default Value</StyledLabel>
<Controller
name="defaultValue"
control={control}
defaultValue={initialDefaultValue}
render={({ field: { onChange, value } }) => (
<Select
className={className}
fullWidth
// TODO: temporary fix - disabling edition because after editing the defaultValue,
// newly created records are not taking into account the updated defaultValue properly.
disabled={isEditMode}
dropdownId="object-field-default-value-select"
value={value}
onChange={onChange}
options={[
{
value: true,
label: 'True',
Icon: IconCheck,
},
{
value: false,
label: 'False',
Icon: IconX,
},
]}
/>
)}
/>
</StyledContainer>
<Controller
name="defaultValue"
control={control}
defaultValue={initialDefaultValue}
render={({ field: { onChange, value } }) => (
<SettingsOptionCardContentSelect
Icon={IconCheck}
title="Default Value"
description="Select the default value for this boolean field"
value={value}
onChange={onChange}
selectClassName={className}
// TODO: temporary fix - disabling edition because after editing the defaultValue,
// newly created records are not taking into account the updated defaultValue properly.
disabled={isEditMode}
dropdownId="object-field-default-value-select-boolean"
options={[
{
value: true,
label: 'True',
Icon: IconCheck,
},
{
value: false,
label: 'False',
Icon: IconX,
},
]}
/>
)}
/>
);
};

View File

@@ -1,91 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { createPortal } from 'react-dom';
import {
AppTooltip,
IconComponent,
IconInfoCircle,
Toggle,
TooltipDelay,
} from 'twenty-ui';
const StyledContainer = styled.div<{ disabled?: boolean }>`
align-items: center;
background-color: ${({ theme }) => theme.background.transparent.lighter};
border: 1px solid ${({ theme }) => theme.border.color.medium};
box-sizing: border-box;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ disabled, theme }) =>
disabled ? theme.font.color.tertiary : theme.font.color.primary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
height: ${({ theme }) => theme.spacing(8)};
justify-content: space-between;
padding: 0 ${({ theme }) => theme.spacing(2)};
`;
const StyledGroup = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
interface SettingsDataModelFieldToggleProps {
disabled?: boolean;
Icon?: IconComponent;
label: string;
tooltip?: string;
value?: boolean;
onChange: (value: boolean) => void;
}
export const SettingsDataModelFieldToggle = ({
disabled,
Icon,
label,
tooltip,
value,
onChange,
}: SettingsDataModelFieldToggleProps) => {
const theme = useTheme();
const infoCircleElementId = `info-circle-id-${Math.random().toString(36).slice(2)}`;
return (
<StyledContainer>
<StyledGroup>
{Icon && (
<Icon color={theme.font.color.tertiary} size={theme.icon.size.md} />
)}
{label}
</StyledGroup>
<StyledGroup>
{tooltip && (
<IconInfoCircle
id={infoCircleElementId}
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
)}
{tooltip &&
createPortal(
<AppTooltip
anchorSelect={`#${infoCircleElementId}`}
content={tooltip}
offset={5}
noArrow
place="bottom"
positionStrategy="absolute"
delay={TooltipDelay.shortDelay}
/>,
document.body,
)}
<Toggle
disabled={disabled}
value={value}
onChange={onChange}
toggleSize="small"
/>
</StyledGroup>
</StyledContainer>
);
};

View File

@@ -3,10 +3,10 @@ import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { currencyFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/currencyFieldDefaultValueSchema';
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
import { Select } from '@/ui/input/components/Select';
import { CardContent } from 'twenty-ui';
import { IconCurrencyDollar } from 'twenty-ui';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
export const settingsDataModelFieldCurrencyFormSchema = z.object({
@@ -41,7 +41,7 @@ export const SettingsDataModelFieldCurrencyForm = ({
useCurrencySettingsFormInitialValues({ fieldMetadataItem });
return (
<CardContent>
<>
<Controller
name="defaultValue.amountMicros"
control={control}
@@ -53,17 +53,19 @@ export const SettingsDataModelFieldCurrencyForm = ({
control={control}
defaultValue={initialCurrencyCodeValue}
render={({ field: { onChange, value } }) => (
<Select
fullWidth
disabled={disabled}
label="Default Unit"
dropdownId="currency-unit-select"
<SettingsOptionCardContentSelect
Icon={IconCurrencyDollar}
title="Default Value"
description="Choose the default currency that will apply"
value={value}
options={OPTIONS}
onChange={onChange}
disabled={disabled}
fullWidth
dropdownId="object-field-default-value-select-currency"
options={OPTIONS}
/>
)}
/>
</CardContent>
</>
);
};

View File

@@ -2,10 +2,9 @@ import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle';
import { SettingsDataModelFieldToggle } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
import { IconClockShare, CardContent } from 'twenty-ui';
import { IconSlash } from 'twenty-ui';
export const settingsDataModelFieldDateFormSchema = z.object({
settings: z
@@ -36,27 +35,19 @@ export const SettingsDataModelFieldDateForm = ({
});
return (
<CardContent>
<Controller
name="settings.displayAsRelativeDate"
control={control}
defaultValue={initialDisplayAsRelativeDateValue}
render={({ field: { onChange, value } }) => (
<>
<StyledFormCardTitle>Options</StyledFormCardTitle>
<SettingsDataModelFieldToggle
label="Display as relative date"
Icon={IconClockShare}
onChange={onChange}
value={value}
disabled={disabled}
tooltip={
'Show dates in a human-friendly format. Example: "13 mins ago" instead of "Jul 30, 2024 7:11pm"'
}
/>
</>
)}
/>
</CardContent>
<Controller
name="settings.displayAsRelativeDate"
control={control}
defaultValue={initialDisplayAsRelativeDateValue}
render={({ field: { onChange, value } }) => (
<SettingsOptionCardContentToggle
Icon={IconSlash}
title="Display as relative date"
checked={value ?? false}
disabled={disabled}
onChange={onChange}
/>
)}
/>
);
};

View File

@@ -1,167 +0,0 @@
import styled from '@emotion/styled';
import { TextInput } from '@/ui/input/components/TextInput';
import { Button, IconInfoCircle, IconMinus, IconPlus } from 'twenty-ui';
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';
type SettingsDataModelFieldNumberDecimalsInputProps = {
value: number;
onChange: (value: number) => void;
disabled?: boolean;
};
const StyledCounterContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.noisy};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: 4px;
display: flex;
flex-direction: column;
flex: 1;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
`;
const StyledExampleText = styled.div`
color: ${({ theme }) => theme.font.color.primary};
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
const StyledCounterControlsIcons = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledCounterInnerContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(2)};
height: 24px;
`;
const StyledTextInput = styled(TextInput)`
width: ${({ theme }) => theme.spacing(16)};
input {
width: ${({ theme }) => theme.spacing(16)};
height: ${({ theme }) => theme.spacing(6)};
text-align: center;
font-weight: ${({ theme }) => theme.font.weight.medium};
background: ${({ theme }) => theme.background.noisy};
}
input ~ div {
padding-right: ${({ theme }) => theme.spacing(0)};
border-radius: ${({ theme }) => theme.spacing(1)};
background: ${({ theme }) => theme.background.noisy};
}
`;
const StyledTitle = styled.div`
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)};
`;
const StyledControlButton = styled(Button)`
height: ${({ theme }) => theme.spacing(6)};
width: ${({ theme }) => theme.spacing(6)};
padding: 0;
justify-content: center;
svg {
height: ${({ theme }) => theme.spacing(4)};
width: ${({ theme }) => theme.spacing(4)};
}
`;
const StyledInfoButton = styled(Button)`
height: ${({ theme }) => theme.spacing(6)};
width: ${({ theme }) => theme.spacing(6)};
padding: 0;
justify-content: center;
svg {
color: ${({ theme }) => theme.font.color.extraLight};
height: ${({ theme }) => theme.spacing(4)};
width: ${({ theme }) => theme.spacing(4)};
}
`;
const MIN_VALUE = 0;
const MAX_VALUE = 100;
export const SettingsDataModelFieldNumberDecimalsInput = ({
value,
onChange,
disabled,
}: SettingsDataModelFieldNumberDecimalsInputProps) => {
const exampleValue = (1000).toFixed(value);
const handleIncrementCounter = () => {
if (value < MAX_VALUE) {
const newValue = value + 1;
onChange(newValue);
}
};
const handleDecrementCounter = () => {
if (value > MIN_VALUE) {
const newValue = value - 1;
onChange(newValue);
}
};
const handleTextInputChange = (value: string) => {
const castedNumber = castAsNumberOrNull(value);
if (castedNumber === null) {
onChange(MIN_VALUE);
return;
}
if (castedNumber < MIN_VALUE) {
return;
}
if (castedNumber > MAX_VALUE) {
onChange(MAX_VALUE);
return;
}
onChange(castedNumber);
};
return (
<>
<StyledTitle>Number of decimals</StyledTitle>
<StyledCounterContainer>
<StyledCounterInnerContainer>
<StyledExampleText>Example: {exampleValue}</StyledExampleText>
<StyledCounterControlsIcons>
<StyledInfoButton variant="tertiary" Icon={IconInfoCircle} />
<StyledControlButton
variant="secondary"
onClick={handleDecrementCounter}
Icon={IconMinus}
disabled={disabled}
/>
<StyledTextInput
name="decimals"
fullWidth
value={value.toString()}
onChange={(value) => handleTextInputChange(value)}
disabled={disabled}
/>
<StyledControlButton
variant="secondary"
onClick={handleIncrementCounter}
Icon={IconPlus}
disabled={disabled}
/>
</StyledCounterControlsIcons>
</StyledCounterInnerContainer>
</StyledCounterContainer>
</>
);
};

View File

@@ -3,10 +3,9 @@ import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema';
import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput';
import { Select } from '@/ui/input/components/Select';
import styled from '@emotion/styled';
import { CardContent, IconNumber9, IconPercentage } from 'twenty-ui';
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
import { IconDecimal, IconEye, IconNumber9, IconPercentage } from 'twenty-ui';
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';
export const settingsDataModelFieldNumberFormSchema = z.object({
@@ -17,13 +16,6 @@ export type SettingsDataModelFieldNumberFormValues = z.infer<
typeof settingsDataModelFieldNumberFormSchema
>;
const StyledFormCardTitle = styled.div`
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)};
`;
type SettingsDataModelFieldNumberFormProps = {
disabled?: boolean;
fieldMetadataItem: Pick<
@@ -39,52 +31,54 @@ export const SettingsDataModelFieldNumberForm = ({
const { control } = useFormContext<SettingsDataModelFieldNumberFormValues>();
return (
<CardContent>
<Controller
name="settings"
defaultValue={{
decimals:
fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
type: fieldMetadataItem?.settings?.type || 'number',
}}
control={control}
render={({ field: { onChange, value } }) => {
const count = value?.decimals ?? 0;
const type = value?.type ?? 'number';
<Controller
name="settings"
defaultValue={{
decimals:
fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
type: fieldMetadataItem?.settings?.type ?? 'number',
}}
control={control}
render={({ field: { onChange, value } }) => {
const count = value?.decimals ?? 0;
const type = value?.type ?? 'number';
return (
<>
<StyledFormCardTitle>Type</StyledFormCardTitle>
<Select
disabled={disabled}
dropdownId="selectNumberTypes"
options={[
{
label: 'Number',
value: 'number',
Icon: IconNumber9,
},
{
label: 'Percentage',
value: 'percentage',
Icon: IconPercentage,
},
]}
value={type}
onChange={(value) => onChange({ type: value, decimals: count })}
withSearchInput={false}
dropdownWidthAuto={true}
/>
<br />
<SettingsDataModelFieldNumberDecimalsInput
value={count}
onChange={(value) => onChange({ type: type, decimals: value })}
disabled={disabled}
/>
</>
);
}}
/>
</CardContent>
return (
<>
<SettingsOptionCardContentSelect
Icon={IconEye}
dropdownId="number-type"
title="Number type"
description="The number type you want to use, e.g. percentage"
value={type}
onChange={(value) => onChange({ type: value, decimals: count })}
disabled={disabled}
options={[
{
Icon: IconNumber9,
label: 'Number',
value: 'number',
},
{
Icon: IconPercentage,
label: 'Percentage',
value: 'percentage',
},
]}
/>
<SettingsOptionCardContentCounter
Icon={IconDecimal}
title="Number of decimals"
description={`Example: ${(1000).toFixed(count)}`}
value={count}
onChange={(value) => onChange({ type: type, decimals: value })}
disabled={disabled}
minValue={0}
maxValue={100} // needs to be changed
/>
</>
);
}}
/>
);
};

View File

@@ -28,9 +28,8 @@ import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { toSpliced } from '~/utils/array/toSpliced';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
import { EXPANDED_WIDTH_ANIMATION_VARIANTS } from '@/settings/constants/ExpandedWidthAnimationVariants';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { AnimatePresence, motion } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow';
@@ -251,26 +250,14 @@ export const SettingsDataModelFieldSelectForm = ({
<>
<StyledContainer>
<StyledLabelContainer>
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
initial="initial"
animate="animate"
exit="exit"
variants={EXPANDED_WIDTH_ANIMATION_VARIANTS}
>
<StyledApiKeyContainer>
<StyledIconContainer>
<StyledIconTool
size={12}
color={MAIN_COLORS.yellow}
/>
</StyledIconContainer>
<StyledApiKey>API values</StyledApiKey>
</StyledApiKeyContainer>
</motion.div>
)}
</AnimatePresence>
<AdvancedSettingsWrapper dimension="width" hideIcon={true}>
<StyledApiKeyContainer>
<StyledIconContainer>
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
</StyledIconContainer>
<StyledApiKey>API values</StyledApiKey>
</StyledApiKeyContainer>
</AdvancedSettingsWrapper>
<StyledOptionsLabel
isAdvancedModeEnabled={isAdvancedModeEnabled}
>

View File

@@ -1,3 +1,11 @@
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { OPTION_VALUE_MAXIMUM_LENGTH } from '@/settings/data-model/constants/OptionValueMaximumLength';
import { TextInput } from '@/ui/input/components/TextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useMemo } from 'react';
@@ -14,18 +22,6 @@ import {
MenuItemSelectColor,
} from 'twenty-ui';
import { v4 } from 'uuid';
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { EXPANDED_WIDTH_ANIMATION_VARIANTS } from '@/settings/constants/ExpandedWidthAnimationVariants';
import { OPTION_VALUE_MAXIMUM_LENGTH } from '@/settings/data-model/constants/OptionValueMaximumLength';
import { TextInput } from '@/ui/input/components/TextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { AnimatePresence, motion } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
type SettingsDataModelFieldSelectFormOptionRowProps = {
@@ -83,7 +79,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
option,
isNewRow,
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const theme = useTheme();
const dropdownIds = useMemo(() => {
@@ -111,28 +106,19 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
stroke={theme.icon.stroke.sm}
color={theme.font.color.extraLight}
/>
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
initial="initial"
animate="animate"
exit="exit"
variants={EXPANDED_WIDTH_ANIMATION_VARIANTS}
>
<StyledOptionInput
value={option.value}
onChange={(input) =>
onChange({
...option,
value: computeOptionValueFromLabel(input),
})
}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
/>
</motion.div>
)}
</AnimatePresence>
<AdvancedSettingsWrapper dimension="width" hideIcon={true}>
<StyledOptionInput
value={option.value}
onChange={(input) =>
onChange({
...option,
value: computeOptionValueFromLabel(input),
})
}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
/>
</AdvancedSettingsWrapper>
<Dropdown
dropdownId={dropdownIds.color}
dropdownPlacement="bottom-start"

View File

@@ -1,23 +1,20 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { OBJECT_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/ObjectNameMaximumLength';
import { SyncObjectLabelAndNameToggle } from '@/settings/data-model/objects/forms/components/SyncObjectLabelAndNameToggle';
import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, motion } from 'framer-motion';
import { plural } from 'pluralize';
import { Controller, useFormContext } from 'react-hook-form';
import { useRecoilValue } from 'recoil';
import {
AppTooltip,
Card,
IconInfoCircle,
IconTool,
MAIN_COLORS,
IconRefresh,
TooltipDelay,
} from 'twenty-ui';
import { z } from 'zod';
@@ -83,18 +80,6 @@ const StyledAdvancedSettingsContainer = styled.div`
width: 100%;
`;
const StyledIconToolContainer = styled.div`
border-right: 1px solid ${MAIN_COLORS.yellow};
display: flex;
left: ${({ theme }) => theme.spacing(-6)};
position: absolute;
height: 100%;
`;
const StyledIconTool = styled(IconTool)`
margin-right: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
@@ -115,10 +100,6 @@ export const SettingsDataModelObjectAboutForm = ({
const { control, watch, setValue } =
useFormContext<SettingsDataModelObjectAboutFormValues>();
const theme = useTheme();
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
isAdvancedModeEnabled,
);
const isLabelSyncedWithName = watch(IS_LABEL_SYNCED_WITH_NAME_LABEL);
const labelSingular = watch('labelSingular');
@@ -235,122 +216,111 @@ export const SettingsDataModelObjectAboutForm = ({
/>
)}
/>
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
ref={contentRef}
initial="initial"
animate="animate"
exit="exit"
variants={motionAnimationVariants}
>
<StyledAdvancedSettingsOuterContainer>
<StyledAdvancedSettingsContainer>
<StyledIconToolContainer>
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
</StyledIconToolContainer>
<StyledAdvancedSettingsSectionInputWrapper>
{[
{
label: 'API Name (Singular)',
fieldName: 'nameSingular' as const,
placeholder: 'listing',
defaultValue: objectMetadataItem?.nameSingular,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
{
label: 'API Name (Plural)',
fieldName: 'namePlural' as const,
placeholder: 'listings',
defaultValue: objectMetadataItem?.namePlural,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
].map(
({
defaultValue,
fieldName,
label,
placeholder,
disabled,
tooltip,
}) => (
<StyledInputContainer
key={`object-${fieldName}-text-input`}
>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => (
<>
<TextInput
label={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
onBlur={onBlur}
RightIcon={() =>
tooltip && (
<>
<IconInfoCircle
id={infoCircleElementId + fieldName}
size={theme.icon.size.md}
color={theme.font.color.tertiary}
style={{ outline: 'none' }}
/>
<AppTooltip
anchorSelect={`#${infoCircleElementId}${fieldName}`}
content={tooltip}
offset={5}
noArrow
place="bottom"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</>
)
}
/>
</>
)}
/>
</StyledInputContainer>
),
)}
<Controller
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
control={control}
defaultValue={
objectMetadataItem?.isLabelSyncedWithName ?? true
}
render={({ field: { onChange, value } }) => (
<SyncObjectLabelAndNameToggle
value={value ?? true}
disabled={!objectMetadataItem?.isCustom}
onChange={(value) => {
onChange(value);
if (value === true) {
fillNamePluralFromLabelPlural(labelPlural);
fillNameSingularFromLabelSingular(labelSingular);
}
onBlur?.();
}}
/>
)}
/>
</StyledAdvancedSettingsSectionInputWrapper>
</StyledAdvancedSettingsContainer>
</StyledAdvancedSettingsOuterContainer>
</motion.div>
)}
</AnimatePresence>
<StyledAdvancedSettingsOuterContainer>
<AdvancedSettingsWrapper>
<StyledAdvancedSettingsContainer>
<StyledAdvancedSettingsSectionInputWrapper>
{[
{
label: 'API Name (Singular)',
fieldName: 'nameSingular' as const,
placeholder: 'listing',
defaultValue: objectMetadataItem?.nameSingular,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
{
label: 'API Name (Plural)',
fieldName: 'namePlural' as const,
placeholder: 'listings',
defaultValue: objectMetadataItem?.namePlural,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
].map(
({
defaultValue,
fieldName,
label,
placeholder,
disabled,
tooltip,
}) => (
<StyledInputContainer key={`object-${fieldName}-text-input`}>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => (
<>
<TextInput
label={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
onBlur={onBlur}
RightIcon={() =>
tooltip && (
<>
<IconInfoCircle
id={infoCircleElementId + fieldName}
size={theme.icon.size.md}
color={theme.font.color.tertiary}
style={{ outline: 'none' }}
/>
<AppTooltip
anchorSelect={`#${infoCircleElementId}${fieldName}`}
content={tooltip}
offset={5}
noArrow
place="bottom"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</>
)
}
/>
</>
)}
/>
</StyledInputContainer>
),
)}
<Controller
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
control={control}
defaultValue={objectMetadataItem?.isLabelSyncedWithName ?? true}
render={({ field: { onChange, value } }) => (
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconRefresh}
title="Synchronize Objects Labels and API Names"
description="Should changing an object's label also change the API?"
checked={value ?? true}
disabled={!objectMetadataItem?.isCustom}
advancedMode
onChange={(value) => {
onChange(value);
if (value === true) {
fillNamePluralFromLabelPlural(labelPlural);
fillNameSingularFromLabelSingular(labelSingular);
}
onBlur?.();
}}
/>
</Card>
)}
/>
</StyledAdvancedSettingsSectionInputWrapper>
</StyledAdvancedSettingsContainer>
</AdvancedSettingsWrapper>
</StyledAdvancedSettingsOuterContainer>
</>
);
};

View File

@@ -1,80 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconRefresh, MAIN_COLORS, Toggle } from 'twenty-ui';
const StyledToggleContainer = styled.div`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
justify-content: space-between;
padding: ${({ theme }) => theme.spacing(4)};
background: ${({ theme }) => theme.background.secondary};
`;
const StyledIconRefreshContainer = styled.div`
border: 2px solid ${({ theme }) => theme.border.color.medium};
border-radius: 3px;
margin-right: ${({ theme }) => theme.spacing(3)};
width: ${({ theme }) => theme.spacing(8)};
height: ${({ theme }) => theme.spacing(8)};
display: flex;
align-items: center;
justify-content: center;
`;
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
`;
const StyledTitle = styled.h2`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: 0;
`;
const StyledDescription = styled.h3`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin: 0;
margin-top: ${({ theme }) => theme.spacing(1)};
`;
type SyncObjectLabelAndNameToggleProps = {
value: boolean;
onChange: (value: boolean) => void;
disabled?: boolean;
};
export const SyncObjectLabelAndNameToggle = ({
value,
onChange,
disabled,
}: SyncObjectLabelAndNameToggleProps) => {
const theme = useTheme();
return (
<StyledToggleContainer>
<StyledTitleContainer>
<StyledIconRefreshContainer>
<IconRefresh size={22.5} color={theme.font.color.tertiary} />
</StyledIconRefreshContainer>
<div>
<StyledTitle>Synchronize Objects Labels and API Names</StyledTitle>
<StyledDescription>
Should changing an object's label also change the API?
</StyledDescription>
</div>
</StyledTitleContainer>
<Toggle
onChange={onChange}
color={MAIN_COLORS.yellow}
value={value}
disabled={disabled}
/>
</StyledToggleContainer>
);
};

View File

@@ -0,0 +1,63 @@
import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations';
import { useEffect, useRef, useState } from 'react';
import { isDefined } from 'twenty-ui';
type AnimationDimension = 'width' | 'height';
const getTransitionValues = (dimension: AnimationDimension) => ({
transition: {
opacity: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.opacity },
[dimension]: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.size },
},
});
const commonStyles = (dimension: AnimationDimension) => ({
opacity: 0,
[dimension]: 0,
...getTransitionValues(dimension),
});
const advancedSectionAnimationConfig = (
isExpanded: boolean,
dimension: AnimationDimension,
measuredValue?: number,
) => ({
initial: {
...commonStyles(dimension),
},
animate: {
opacity: 1,
[dimension]: isExpanded
? dimension === 'width'
? '100%'
: measuredValue
: 0,
...getTransitionValues(dimension),
},
exit: {
...commonStyles(dimension),
},
});
export const useExpandedAnimation = (
isExpanded: boolean,
dimension: AnimationDimension = 'height',
) => {
const contentRef = useRef<HTMLDivElement>(null);
const [measuredValue, setMeasuredValue] = useState(0);
useEffect(() => {
if (dimension === 'height' && isDefined(contentRef.current)) {
setMeasuredValue(contentRef.current.scrollHeight);
}
}, [isExpanded, dimension]);
return {
contentRef,
motionAnimationVariants: advancedSectionAnimationConfig(
isExpanded,
dimension,
dimension === 'height' ? measuredValue : undefined,
),
};
};

View File

@@ -1,52 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import { isDefined } from 'twenty-ui';
const transitionValues = {
transition: {
opacity: { duration: 0.2 },
height: { duration: 0.4 },
},
};
const commonStyles = {
opacity: 0,
height: 0,
...transitionValues,
};
const advancedSectionAnimationConfig = (
isExpanded: boolean,
measuredHeight: number,
) => ({
initial: {
...commonStyles,
},
animate: {
opacity: 1,
height: isExpanded ? measuredHeight : 0,
...transitionValues,
},
exit: {
...commonStyles,
},
});
export const useExpandedHeightAnimation = (isExpanded: boolean) => {
const contentRef = useRef<HTMLDivElement>(null);
const [measuredHeight, setMeasuredHeight] = useState(0);
useEffect(() => {
if (isDefined(contentRef.current)) {
setMeasuredHeight(contentRef.current.scrollHeight);
}
}, [isExpanded]);
return {
contentRef,
measuredHeight,
motionAnimationVariants: advancedSectionAnimationConfig(
isExpanded,
measuredHeight,
),
};
};

View File

@@ -1,5 +1,5 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRecoilState } from 'recoil';
@@ -44,12 +44,13 @@ export const SettingsSecurityOptionsList = () => {
};
return (
<Card>
<SettingsOptionCardContent
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconLink}
title="Invite by Link"
description="Allow the invitation of new users by sharing an invite link."
checked={currentWorkspace.isPublicInviteLinkEnabled}
advancedMode
onChange={() =>
handleChange(!currentWorkspace.isPublicInviteLinkEnabled)
}

View File

@@ -12,21 +12,24 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
import { isDefined } from '~/utils/isDefined';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
export type SelectOption<Value extends string | number | null> = {
export type SelectOption<Value extends string | number | boolean | null> = {
value: Value;
label: string;
Icon?: IconComponent;
};
export type SelectSizeVariant = 'small' | 'default';
type CallToActionButton = {
text: string;
onClick: (event: MouseEvent<HTMLDivElement>) => void;
Icon?: IconComponent;
};
export type SelectProps<Value extends string | number | null> = {
export type SelectProps<Value extends string | number | boolean | null> = {
className?: string;
disabled?: boolean;
selectSizeVariant?: SelectSizeVariant;
disableBlur?: boolean;
dropdownId: string;
dropdownWidth?: `${string}px` | 'auto' | number;
@@ -54,9 +57,10 @@ const StyledLabel = styled.span`
margin-bottom: ${({ theme }) => theme.spacing(1)};
`;
export const Select = <Value extends string | number | null>({
export const Select = <Value extends string | number | boolean | null>({
className,
disabled: disabledFromProps,
selectSizeVariant,
disableBlur = false,
dropdownId,
dropdownWidth = 176,
@@ -115,6 +119,7 @@ export const Select = <Value extends string | number | null>({
<SelectControl
selectedOption={selectedOption}
isDisabled={isDisabled}
selectSizeVariant={selectSizeVariant}
/>
) : (
<Dropdown
@@ -125,6 +130,7 @@ export const Select = <Value extends string | number | null>({
<SelectControl
selectedOption={selectedOption}
isDisabled={isDisabled}
selectSizeVariant={selectSizeVariant}
/>
}
disableBlur={disableBlur}
@@ -144,7 +150,7 @@ export const Select = <Value extends string | number | null>({
<DropdownMenuItemsContainer hasMaxHeight>
{filteredOptions.map((option) => (
<MenuItem
key={option.value}
key={`${option.value}-${option.label}`}
LeftIcon={option.Icon}
text={option.label}
onClick={() => {

View File

@@ -1,4 +1,4 @@
import { SelectOption } from '@/ui/input/components/Select';
import { SelectOption, SelectSizeVariant } from '@/ui/input/components/Select';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
@@ -10,6 +10,7 @@ import {
const StyledControlContainer = styled.div<{
disabled?: boolean;
hasIcon: boolean;
selectSizeVariant?: SelectSizeVariant;
}>`
display: grid;
grid-template-columns: ${({ hasIcon }) =>
@@ -17,7 +18,8 @@ const StyledControlContainer = styled.div<{
align-items: center;
gap: ${({ theme }) => theme.spacing(1)};
box-sizing: border-box;
height: ${({ theme }) => theme.spacing(8)};
height: ${({ selectSizeVariant, theme }) =>
selectSizeVariant === 'small' ? theme.spacing(6) : theme.spacing(8)};
max-width: 100%;
padding: 0 ${({ theme }) => theme.spacing(2)};
background-color: ${({ theme }) => theme.background.transparent.lighter};
@@ -37,13 +39,15 @@ const StyledIconChevronDown = styled(IconChevronDown)<{
`;
type SelectControlProps = {
selectedOption: SelectOption<string | number | null>;
selectedOption: SelectOption<string | number | boolean | null>;
isDisabled?: boolean;
selectSizeVariant?: SelectSizeVariant;
};
export const SelectControl = ({
selectedOption,
isDisabled,
selectSizeVariant,
}: SelectControlProps) => {
const theme = useTheme();
@@ -51,6 +55,7 @@ export const SelectControl = ({
<StyledControlContainer
disabled={isDisabled}
hasIcon={isDefined(selectedOption.Icon)}
selectSizeVariant={selectSizeVariant}
>
{isDefined(selectedOption.Icon) ? (
<selectedOption.Icon

View File

@@ -5,11 +5,11 @@ import { ComponentDecorator, IconPlus } from 'twenty-ui';
import { Select, SelectProps } from '../Select';
type RenderProps = SelectProps<string | number | null>;
type RenderProps = SelectProps<string | number | boolean | null>;
const Render = (args: RenderProps) => {
const [value, setValue] = useState(args.value);
const handleChange = (value: string | number | null) => {
const handleChange = (value: string | number | boolean | null) => {
args.onChange?.(value);
setValue(value);
};

View File

@@ -32,7 +32,7 @@ export const SettingsWorkspace = () => (
<Section>
<H2Title
title="Support"
addornment={<ToggleImpersonate />}
adornment={<ToggleImpersonate />}
description="Grant Twenty support temporary access to your workspace so we can troubleshoot problems or recover content on your behalf. You can revoke access at any time."
/>
</Section>

View File

@@ -1,5 +1,7 @@
import { H2Title, Section, IconLock, Tag } from 'twenty-ui';
import styled from '@emotion/styled';
import { H2Title, IconLock, Section, Tag } from 'twenty-ui';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SettingsSSOIdentitiesProvidersListCard';
@@ -8,6 +10,21 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
const StyledContainer = styled.div`
width: 100%;
`;
const StyledMainContent = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(10)};
min-height: 200px;
`;
const StyledSSOSection = styled(Section)`
flex-shrink: 0;
`;
export const SettingsSecurity = () => {
return (
<SubMenuTopBarContainer
@@ -22,28 +39,34 @@ export const SettingsSecurity = () => {
]}
>
<SettingsPageContainer>
<Section>
<H2Title
title="SSO"
description="Configure an SSO connection"
addornment={
<Tag
text={'Enterprise'}
color={'transparent'}
Icon={IconLock}
variant={'border'}
/>
}
/>
<SettingsSSOIdentitiesProvidersListCard />
</Section>
<Section>
<H2Title
title="Other"
description="Customize your workspace security"
/>
<SettingsSecurityOptionsList />
</Section>
<StyledMainContent>
<StyledSSOSection>
<H2Title
title="SSO"
description="Configure an SSO connection"
adornment={
<Tag
text={'Enterprise'}
color={'transparent'}
Icon={IconLock}
variant={'border'}
/>
}
/>
<SettingsSSOIdentitiesProvidersListCard />
</StyledSSOSection>
<Section>
<AdvancedSettingsWrapper>
<StyledContainer>
<H2Title
title="Other"
description="Customize your workspace security"
/>
<SettingsSecurityOptionsList />
</StyledContainer>
</AdvancedSettingsWrapper>
</Section>
</StyledMainContent>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);

View File

@@ -11,8 +11,8 @@ export const IllustrationIconArray = (props: IllustrationIconArrayProps) => {
return (
<IllustrationIconWrapper>
<IllustrationIconArrayRaw
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
height={size}
width={size}
/>

View File

@@ -13,8 +13,8 @@ export const IllustrationIconCalendarEvent = (
return (
<IllustrationIconWrapper>
<IllustrationIconCalendarEventRaw
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
height={size}
width={size}
/>

View File

@@ -16,8 +16,8 @@ export const IllustrationIconCalendarTime = (
<IllustrationIconCalendarTimeRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -17,8 +17,8 @@ export const IllustrationIconCurrency = (
<IllustrationIconCurrencyRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconJson = (props: IllustrationIconJsonProps) => {
<IllustrationIconJsonRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconLink = (props: IllustrationIconLinkProps) => {
<IllustrationIconLinkRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -14,8 +14,8 @@ export const IllustrationIconMail = (props: IllustrationIconMailProps) => {
<IllustrationIconMailRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -16,8 +16,8 @@ export const IllustrationIconManyToMany = (
<IllustrationIconManyToManyRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -14,8 +14,8 @@ export const IllustrationIconMap = (props: IllustrationIconMapProps) => {
<IllustrationIconMapRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -16,8 +16,8 @@ export const IllustrationIconNumbers = (
<IllustrationIconNumbersRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -17,8 +17,8 @@ export const IllustrationIconOneToMany = (
<IllustrationIconOneToManyRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -17,8 +17,8 @@ export const IllustrationIconOneToOne = (
<IllustrationIconOneToOneRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -16,8 +16,8 @@ export const IllustrationIconPhone = (props: IllustrationIconPhoneProps) => {
<IllustrationIconPhoneRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -18,8 +18,8 @@ export const IllustrationIconSetting = (
<IllustrationIconSettingRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -16,8 +16,8 @@ export const IllustrationIconStar = (props: IllustrationIconStarProps) => {
<IllustrationIconStarRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconTag = (props: IllustrationIconTagProps) => {
<IllustrationIconTagRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconTags = (props: IllustrationIconTagsProps) => {
<IllustrationIconTagsRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconText = (props: IllustrationIconTextProps) => {
<IllustrationIconTextRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconToggle = (props: IllustrationIconToggleProps) => {
<IllustrationIconToggleRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconUid = (props: IllustrationIconUidProps) => {
<IllustrationIconUidRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -15,8 +15,8 @@ export const IllustrationIconUser = (props: IllustrationIconUserProps) => {
<IllustrationIconUserRaw
height={size}
width={size}
fill={fill}
color={color}
fill={fill.blue}
color={color.blue}
/>
</IllustrationIconWrapper>
);

View File

@@ -110,6 +110,7 @@ export {
IconCurrencyZloty,
IconDatabase,
IconDatabaseExport,
IconDecimal,
IconDeviceFloppy,
IconDoorEnter,
IconDotsVertical,
@@ -212,6 +213,7 @@ export {
IconSend,
IconSettings,
IconSettingsAutomation,
IconSlash,
IconSortDescending,
IconSparkles,
IconSql,
@@ -232,6 +234,7 @@ export {
IconUpload,
IconUser,
IconUserCircle,
IconUserPlus,
IconUsers,
IconVariablePlus,
IconVideo,

View File

@@ -24,6 +24,7 @@ export * from './icon/components/IllustrationIconCalendarEvent';
export * from './icon/components/IllustrationIconCalendarTime';
export * from './icon/components/IllustrationIconCurrency';
export * from './icon/components/IllustrationIconJson';
export * from './icon/components/IllustrationIconLink';
export * from './icon/components/IllustrationIconMail';
export * from './icon/components/IllustrationIconManyToMany';
export * from './icon/components/IllustrationIconMap';
@@ -40,7 +41,6 @@ export * from './icon/components/IllustrationIconToggle';
export * from './icon/components/IllustrationIconUid';
export * from './icon/components/IllustrationIconUser';
export * from './icon/components/IllustrationIconWrapper';
export * from './icon/components/llustrationIconLink';
export * from './icon/components/TablerIcons';
export * from './icon/hooks/useIcons';
export * from './icon/providers/IconsProvider';

View File

@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
type H2TitleProps = {
title: string;
description?: string;
addornment?: React.ReactNode;
adornment?: React.ReactNode;
className?: string;
};
@@ -37,13 +37,13 @@ const StyledDescription = styled.h3`
export const H2Title = ({
title,
description,
addornment,
adornment,
className,
}: H2TitleProps) => (
<StyledContainer className={className}>
<StyledTitleContainer>
<StyledTitle>{title}</StyledTitle>
{addornment}
{adornment}
</StyledTitleContainer>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledContainer>

View File

@@ -1,6 +1,13 @@
import { GRAY_SCALE } from '@ui/theme/constants/GrayScale';
import { COLOR } from './Colors';
export const ILLUSTRATION_ICON_DARK = {
color: COLOR.blue50,
fill: COLOR.blue70,
color: {
blue: COLOR.blue50,
grey: GRAY_SCALE.gray50,
},
fill: {
blue: COLOR.blue70,
grey: GRAY_SCALE.gray70,
},
};

View File

@@ -1,6 +1,13 @@
import { GRAY_SCALE } from '@ui/theme/constants/GrayScale';
import { COLOR } from './Colors';
export const ILLUSTRATION_ICON_LIGHT = {
color: COLOR.blue40,
fill: COLOR.blue20,
color: {
blue: COLOR.blue40,
grey: GRAY_SCALE.gray40,
},
fill: {
blue: COLOR.blue20,
grey: GRAY_SCALE.gray20,
},
};