mirror of
https://github.com/lingble/twenty.git
synced 2025-10-29 11:52:28 +00:00
Settings Option Card component (#8456)
fixes - #8195 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
type H2TitleProps = {
|
type H2TitleProps = {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
addornment?: React.ReactNode;
|
adornment?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@@ -33,11 +33,11 @@ const StyledDescription = styled.h3`
|
|||||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const H2Title = ({ title, description, addornment }: H2TitleProps) => (
|
export const H2Title = ({ title, description, adornment }: H2TitleProps) => (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledTitleContainer>
|
<StyledTitleContainer>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
{addornment}
|
{adornment}
|
||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
{description && <StyledDescription>{description}</StyledDescription>}
|
{description && <StyledDescription>{description}</StyledDescription>}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsCalendarVisibilitySettingsCard';
|
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 styled from '@emotion/styled';
|
||||||
import { Section } from '@react-email/components';
|
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';
|
import { CalendarChannelVisibility } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledDetailsContainer = styled.div`
|
const StyledDetailsContainer = styled.div`
|
||||||
@@ -63,8 +63,9 @@ export const SettingsAccountsCalendarChannelDetails = ({
|
|||||||
title="Contact auto-creation"
|
title="Contact auto-creation"
|
||||||
description="Automatically create contacts for people you've participated in an event with."
|
description="Automatically create contacts for people you've participated in an event with."
|
||||||
/>
|
/>
|
||||||
<Card>
|
<Card rounded>
|
||||||
<SettingsOptionCardContent
|
<SettingsOptionCardContentToggle
|
||||||
|
Icon={IconUserPlus}
|
||||||
title="Auto-creation"
|
title="Auto-creation"
|
||||||
description="Automatically create contacts for people."
|
description="Automatically create contacts for people."
|
||||||
checked={calendarChannel.isContactAutoCreationEnabled}
|
checked={calendarChannel.isContactAutoCreationEnabled}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Card, H2Title, Section } from 'twenty-ui';
|
import { Card, H2Title, IconBriefcase, IconUsers, Section } from 'twenty-ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MessageChannel,
|
MessageChannel,
|
||||||
@@ -9,7 +9,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard';
|
import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard';
|
||||||
import { SettingsAccountsMessageVisibilityCard } from '@/settings/accounts/components/SettingsAccountsMessageVisibilityCard';
|
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';
|
import { MessageChannelVisibility } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type SettingsAccountsMessageChannelDetailsProps = {
|
type SettingsAccountsMessageChannelDetailsProps = {
|
||||||
@@ -98,8 +98,9 @@ export const SettingsAccountsMessageChannelDetails = ({
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section>
|
<Section>
|
||||||
<Card>
|
<Card rounded>
|
||||||
<SettingsOptionCardContent
|
<SettingsOptionCardContentToggle
|
||||||
|
Icon={IconBriefcase}
|
||||||
title="Exclude non-professional emails"
|
title="Exclude non-professional emails"
|
||||||
description="Don’t create contacts from/to Gmail, Outlook emails"
|
description="Don’t create contacts from/to Gmail, Outlook emails"
|
||||||
divider
|
divider
|
||||||
@@ -110,7 +111,8 @@ export const SettingsAccountsMessageChannelDetails = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SettingsOptionCardContent
|
<SettingsOptionCardContentToggle
|
||||||
|
Icon={IconUsers}
|
||||||
title="Exclude group emails"
|
title="Exclude group emails"
|
||||||
description="Don’t sync emails from team@ support@ noreply@..."
|
description="Don’t sync emails from team@ support@ noreply@..."
|
||||||
checked={messageChannel.excludeGroupEmails}
|
checked={messageChannel.excludeGroupEmails}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
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 { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import {
|
import {
|
||||||
@@ -69,7 +69,7 @@ const StyledIconTool = styled(IconTool)`
|
|||||||
|
|
||||||
export const SettingsNavigationDrawerItems = () => {
|
export const SettingsNavigationDrawerItems = () => {
|
||||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
||||||
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
|
const { contentRef, motionAnimationVariants } = useExpandedAnimation(
|
||||||
isAdvancedModeEnabled,
|
isAdvancedModeEnabled,
|
||||||
);
|
);
|
||||||
const { signOut } = useAuth();
|
const { signOut } = useAuth();
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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};
|
||||||
|
`;
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
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 { z } from 'zod';
|
||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
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 { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const settingsDataModelFieldBooleanFormSchema = z.object({
|
export const settingsDataModelFieldBooleanFormSchema = z.object({
|
||||||
@@ -21,18 +20,6 @@ type SettingsDataModelFieldBooleanFormProps = {
|
|||||||
fieldMetadataItem: Pick<FieldMetadataItem, 'defaultValue'>;
|
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 = ({
|
export const SettingsDataModelFieldBooleanForm = ({
|
||||||
className,
|
className,
|
||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
@@ -45,37 +32,36 @@ export const SettingsDataModelFieldBooleanForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<Controller
|
||||||
<StyledLabel>Default Value</StyledLabel>
|
name="defaultValue"
|
||||||
<Controller
|
control={control}
|
||||||
name="defaultValue"
|
defaultValue={initialDefaultValue}
|
||||||
control={control}
|
render={({ field: { onChange, value } }) => (
|
||||||
defaultValue={initialDefaultValue}
|
<SettingsOptionCardContentSelect
|
||||||
render={({ field: { onChange, value } }) => (
|
Icon={IconCheck}
|
||||||
<Select
|
title="Default Value"
|
||||||
className={className}
|
description="Select the default value for this boolean field"
|
||||||
fullWidth
|
value={value}
|
||||||
// TODO: temporary fix - disabling edition because after editing the defaultValue,
|
onChange={onChange}
|
||||||
// newly created records are not taking into account the updated defaultValue properly.
|
selectClassName={className}
|
||||||
disabled={isEditMode}
|
// TODO: temporary fix - disabling edition because after editing the defaultValue,
|
||||||
dropdownId="object-field-default-value-select"
|
// newly created records are not taking into account the updated defaultValue properly.
|
||||||
value={value}
|
disabled={isEditMode}
|
||||||
onChange={onChange}
|
dropdownId="object-field-default-value-select-boolean"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: true,
|
value: true,
|
||||||
label: 'True',
|
label: 'True',
|
||||||
Icon: IconCheck,
|
Icon: IconCheck,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: false,
|
value: false,
|
||||||
label: 'False',
|
label: 'False',
|
||||||
Icon: IconX,
|
Icon: IconX,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -3,10 +3,10 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { currencyFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/currencyFieldDefaultValueSchema';
|
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 { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||||
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { IconCurrencyDollar } from 'twenty-ui';
|
||||||
import { CardContent } from 'twenty-ui';
|
|
||||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||||
|
|
||||||
export const settingsDataModelFieldCurrencyFormSchema = z.object({
|
export const settingsDataModelFieldCurrencyFormSchema = z.object({
|
||||||
@@ -41,7 +41,7 @@ export const SettingsDataModelFieldCurrencyForm = ({
|
|||||||
useCurrencySettingsFormInitialValues({ fieldMetadataItem });
|
useCurrencySettingsFormInitialValues({ fieldMetadataItem });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
name="defaultValue.amountMicros"
|
name="defaultValue.amountMicros"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -53,17 +53,19 @@ export const SettingsDataModelFieldCurrencyForm = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={initialCurrencyCodeValue}
|
defaultValue={initialCurrencyCodeValue}
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<Select
|
<SettingsOptionCardContentSelect
|
||||||
fullWidth
|
Icon={IconCurrencyDollar}
|
||||||
disabled={disabled}
|
title="Default Value"
|
||||||
label="Default Unit"
|
description="Choose the default currency that will apply"
|
||||||
dropdownId="currency-unit-select"
|
|
||||||
value={value}
|
value={value}
|
||||||
options={OPTIONS}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
fullWidth
|
||||||
|
dropdownId="object-field-default-value-select-currency"
|
||||||
|
options={OPTIONS}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle';
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { SettingsDataModelFieldToggle } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle';
|
|
||||||
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
|
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({
|
export const settingsDataModelFieldDateFormSchema = z.object({
|
||||||
settings: z
|
settings: z
|
||||||
@@ -36,27 +35,19 @@ export const SettingsDataModelFieldDateForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<Controller
|
||||||
<Controller
|
name="settings.displayAsRelativeDate"
|
||||||
name="settings.displayAsRelativeDate"
|
control={control}
|
||||||
control={control}
|
defaultValue={initialDisplayAsRelativeDateValue}
|
||||||
defaultValue={initialDisplayAsRelativeDateValue}
|
render={({ field: { onChange, value } }) => (
|
||||||
render={({ field: { onChange, value } }) => (
|
<SettingsOptionCardContentToggle
|
||||||
<>
|
Icon={IconSlash}
|
||||||
<StyledFormCardTitle>Options</StyledFormCardTitle>
|
title="Display as relative date"
|
||||||
<SettingsDataModelFieldToggle
|
checked={value ?? false}
|
||||||
label="Display as relative date"
|
disabled={disabled}
|
||||||
Icon={IconClockShare}
|
onChange={onChange}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -3,10 +3,9 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema';
|
import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema';
|
||||||
import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput';
|
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||||
import styled from '@emotion/styled';
|
import { IconDecimal, IconEye, IconNumber9, IconPercentage } from 'twenty-ui';
|
||||||
import { CardContent, IconNumber9, IconPercentage } from 'twenty-ui';
|
|
||||||
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';
|
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';
|
||||||
|
|
||||||
export const settingsDataModelFieldNumberFormSchema = z.object({
|
export const settingsDataModelFieldNumberFormSchema = z.object({
|
||||||
@@ -17,13 +16,6 @@ export type SettingsDataModelFieldNumberFormValues = z.infer<
|
|||||||
typeof settingsDataModelFieldNumberFormSchema
|
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 = {
|
type SettingsDataModelFieldNumberFormProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
fieldMetadataItem: Pick<
|
fieldMetadataItem: Pick<
|
||||||
@@ -39,52 +31,54 @@ export const SettingsDataModelFieldNumberForm = ({
|
|||||||
const { control } = useFormContext<SettingsDataModelFieldNumberFormValues>();
|
const { control } = useFormContext<SettingsDataModelFieldNumberFormValues>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<Controller
|
||||||
<Controller
|
name="settings"
|
||||||
name="settings"
|
defaultValue={{
|
||||||
defaultValue={{
|
decimals:
|
||||||
decimals:
|
fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
|
||||||
fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
|
type: fieldMetadataItem?.settings?.type ?? 'number',
|
||||||
type: fieldMetadataItem?.settings?.type || 'number',
|
}}
|
||||||
}}
|
control={control}
|
||||||
control={control}
|
render={({ field: { onChange, value } }) => {
|
||||||
render={({ field: { onChange, value } }) => {
|
const count = value?.decimals ?? 0;
|
||||||
const count = value?.decimals ?? 0;
|
const type = value?.type ?? 'number';
|
||||||
const type = value?.type ?? 'number';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledFormCardTitle>Type</StyledFormCardTitle>
|
<SettingsOptionCardContentSelect
|
||||||
<Select
|
Icon={IconEye}
|
||||||
disabled={disabled}
|
dropdownId="number-type"
|
||||||
dropdownId="selectNumberTypes"
|
title="Number type"
|
||||||
options={[
|
description="The number type you want to use, e.g. percentage"
|
||||||
{
|
value={type}
|
||||||
label: 'Number',
|
onChange={(value) => onChange({ type: value, decimals: count })}
|
||||||
value: 'number',
|
disabled={disabled}
|
||||||
Icon: IconNumber9,
|
options={[
|
||||||
},
|
{
|
||||||
{
|
Icon: IconNumber9,
|
||||||
label: 'Percentage',
|
label: 'Number',
|
||||||
value: 'percentage',
|
value: 'number',
|
||||||
Icon: IconPercentage,
|
},
|
||||||
},
|
{
|
||||||
]}
|
Icon: IconPercentage,
|
||||||
value={type}
|
label: 'Percentage',
|
||||||
onChange={(value) => onChange({ type: value, decimals: count })}
|
value: 'percentage',
|
||||||
withSearchInput={false}
|
},
|
||||||
dropdownWidthAuto={true}
|
]}
|
||||||
/>
|
/>
|
||||||
<br />
|
<SettingsOptionCardContentCounter
|
||||||
<SettingsDataModelFieldNumberDecimalsInput
|
Icon={IconDecimal}
|
||||||
value={count}
|
title="Number of decimals"
|
||||||
onChange={(value) => onChange({ type: type, decimals: value })}
|
description={`Example: ${(1000).toFixed(count)}`}
|
||||||
disabled={disabled}
|
value={count}
|
||||||
/>
|
onChange={(value) => onChange({ type: type, decimals: value })}
|
||||||
</>
|
disabled={disabled}
|
||||||
);
|
minValue={0}
|
||||||
}}
|
maxValue={100} // needs to be changed
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
|||||||
import { toSpliced } from '~/utils/array/toSpliced';
|
import { toSpliced } from '~/utils/array/toSpliced';
|
||||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
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 { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow';
|
import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow';
|
||||||
|
|
||||||
@@ -251,26 +250,14 @@ export const SettingsDataModelFieldSelectForm = ({
|
|||||||
<>
|
<>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledLabelContainer>
|
<StyledLabelContainer>
|
||||||
<AnimatePresence>
|
<AdvancedSettingsWrapper dimension="width" hideIcon={true}>
|
||||||
{isAdvancedModeEnabled && (
|
<StyledApiKeyContainer>
|
||||||
<motion.div
|
<StyledIconContainer>
|
||||||
initial="initial"
|
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
|
||||||
animate="animate"
|
</StyledIconContainer>
|
||||||
exit="exit"
|
<StyledApiKey>API values</StyledApiKey>
|
||||||
variants={EXPANDED_WIDTH_ANIMATION_VARIANTS}
|
</StyledApiKeyContainer>
|
||||||
>
|
</AdvancedSettingsWrapper>
|
||||||
<StyledApiKeyContainer>
|
|
||||||
<StyledIconContainer>
|
|
||||||
<StyledIconTool
|
|
||||||
size={12}
|
|
||||||
color={MAIN_COLORS.yellow}
|
|
||||||
/>
|
|
||||||
</StyledIconContainer>
|
|
||||||
<StyledApiKey>API values</StyledApiKey>
|
|
||||||
</StyledApiKeyContainer>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
<StyledOptionsLabel
|
<StyledOptionsLabel
|
||||||
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -14,18 +22,6 @@ import {
|
|||||||
MenuItemSelectColor,
|
MenuItemSelectColor,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { v4 } from 'uuid';
|
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';
|
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||||
|
|
||||||
type SettingsDataModelFieldSelectFormOptionRowProps = {
|
type SettingsDataModelFieldSelectFormOptionRowProps = {
|
||||||
@@ -83,7 +79,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
|||||||
option,
|
option,
|
||||||
isNewRow,
|
isNewRow,
|
||||||
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
||||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const dropdownIds = useMemo(() => {
|
const dropdownIds = useMemo(() => {
|
||||||
@@ -111,28 +106,19 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
|||||||
stroke={theme.icon.stroke.sm}
|
stroke={theme.icon.stroke.sm}
|
||||||
color={theme.font.color.extraLight}
|
color={theme.font.color.extraLight}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AdvancedSettingsWrapper dimension="width" hideIcon={true}>
|
||||||
{isAdvancedModeEnabled && (
|
<StyledOptionInput
|
||||||
<motion.div
|
value={option.value}
|
||||||
initial="initial"
|
onChange={(input) =>
|
||||||
animate="animate"
|
onChange({
|
||||||
exit="exit"
|
...option,
|
||||||
variants={EXPANDED_WIDTH_ANIMATION_VARIANTS}
|
value: computeOptionValueFromLabel(input),
|
||||||
>
|
})
|
||||||
<StyledOptionInput
|
}
|
||||||
value={option.value}
|
RightIcon={isDefault ? IconCheck : undefined}
|
||||||
onChange={(input) =>
|
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
|
||||||
onChange({
|
/>
|
||||||
...option,
|
</AdvancedSettingsWrapper>
|
||||||
value: computeOptionValueFromLabel(input),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
RightIcon={isDefault ? IconCheck : undefined}
|
|
||||||
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownIds.color}
|
dropdownId={dropdownIds.color}
|
||||||
dropdownPlacement="bottom-start"
|
dropdownPlacement="bottom-start"
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
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 { 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 { 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 { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { plural } from 'pluralize';
|
import { plural } from 'pluralize';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import {
|
import {
|
||||||
AppTooltip,
|
AppTooltip,
|
||||||
|
Card,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconTool,
|
IconRefresh,
|
||||||
MAIN_COLORS,
|
|
||||||
TooltipDelay,
|
TooltipDelay,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -83,18 +80,6 @@ const StyledAdvancedSettingsContainer = styled.div`
|
|||||||
width: 100%;
|
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`
|
const StyledLabel = styled.span`
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
font-size: ${({ theme }) => theme.font.size.xs};
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
@@ -115,10 +100,6 @@ export const SettingsDataModelObjectAboutForm = ({
|
|||||||
const { control, watch, setValue } =
|
const { control, watch, setValue } =
|
||||||
useFormContext<SettingsDataModelObjectAboutFormValues>();
|
useFormContext<SettingsDataModelObjectAboutFormValues>();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
|
||||||
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
|
|
||||||
isAdvancedModeEnabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isLabelSyncedWithName = watch(IS_LABEL_SYNCED_WITH_NAME_LABEL);
|
const isLabelSyncedWithName = watch(IS_LABEL_SYNCED_WITH_NAME_LABEL);
|
||||||
const labelSingular = watch('labelSingular');
|
const labelSingular = watch('labelSingular');
|
||||||
@@ -235,122 +216,111 @@ export const SettingsDataModelObjectAboutForm = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<StyledAdvancedSettingsOuterContainer>
|
||||||
{isAdvancedModeEnabled && (
|
<AdvancedSettingsWrapper>
|
||||||
<motion.div
|
<StyledAdvancedSettingsContainer>
|
||||||
ref={contentRef}
|
<StyledAdvancedSettingsSectionInputWrapper>
|
||||||
initial="initial"
|
{[
|
||||||
animate="animate"
|
{
|
||||||
exit="exit"
|
label: 'API Name (Singular)',
|
||||||
variants={motionAnimationVariants}
|
fieldName: 'nameSingular' as const,
|
||||||
>
|
placeholder: 'listing',
|
||||||
<StyledAdvancedSettingsOuterContainer>
|
defaultValue: objectMetadataItem?.nameSingular,
|
||||||
<StyledAdvancedSettingsContainer>
|
disabled:
|
||||||
<StyledIconToolContainer>
|
disabled || disableNameEdit || isLabelSyncedWithName,
|
||||||
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
|
tooltip: apiNameTooltipText,
|
||||||
</StyledIconToolContainer>
|
},
|
||||||
<StyledAdvancedSettingsSectionInputWrapper>
|
{
|
||||||
{[
|
label: 'API Name (Plural)',
|
||||||
{
|
fieldName: 'namePlural' as const,
|
||||||
label: 'API Name (Singular)',
|
placeholder: 'listings',
|
||||||
fieldName: 'nameSingular' as const,
|
defaultValue: objectMetadataItem?.namePlural,
|
||||||
placeholder: 'listing',
|
disabled:
|
||||||
defaultValue: objectMetadataItem?.nameSingular,
|
disabled || disableNameEdit || isLabelSyncedWithName,
|
||||||
disabled:
|
tooltip: apiNameTooltipText,
|
||||||
disabled || disableNameEdit || isLabelSyncedWithName,
|
},
|
||||||
tooltip: apiNameTooltipText,
|
].map(
|
||||||
},
|
({
|
||||||
{
|
defaultValue,
|
||||||
label: 'API Name (Plural)',
|
fieldName,
|
||||||
fieldName: 'namePlural' as const,
|
label,
|
||||||
placeholder: 'listings',
|
placeholder,
|
||||||
defaultValue: objectMetadataItem?.namePlural,
|
disabled,
|
||||||
disabled:
|
tooltip,
|
||||||
disabled || disableNameEdit || isLabelSyncedWithName,
|
}) => (
|
||||||
tooltip: apiNameTooltipText,
|
<StyledInputContainer key={`object-${fieldName}-text-input`}>
|
||||||
},
|
<Controller
|
||||||
].map(
|
name={fieldName}
|
||||||
({
|
control={control}
|
||||||
defaultValue,
|
defaultValue={defaultValue}
|
||||||
fieldName,
|
render={({ field: { onChange, value } }) => (
|
||||||
label,
|
<>
|
||||||
placeholder,
|
<TextInput
|
||||||
disabled,
|
label={label}
|
||||||
tooltip,
|
placeholder={placeholder}
|
||||||
}) => (
|
value={value}
|
||||||
<StyledInputContainer
|
onChange={onChange}
|
||||||
key={`object-${fieldName}-text-input`}
|
disabled={disabled}
|
||||||
>
|
fullWidth
|
||||||
<Controller
|
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
|
||||||
name={fieldName}
|
onBlur={onBlur}
|
||||||
control={control}
|
RightIcon={() =>
|
||||||
defaultValue={defaultValue}
|
tooltip && (
|
||||||
render={({ field: { onChange, value } }) => (
|
<>
|
||||||
<>
|
<IconInfoCircle
|
||||||
<TextInput
|
id={infoCircleElementId + fieldName}
|
||||||
label={label}
|
size={theme.icon.size.md}
|
||||||
placeholder={placeholder}
|
color={theme.font.color.tertiary}
|
||||||
value={value}
|
style={{ outline: 'none' }}
|
||||||
onChange={onChange}
|
/>
|
||||||
disabled={disabled}
|
<AppTooltip
|
||||||
fullWidth
|
anchorSelect={`#${infoCircleElementId}${fieldName}`}
|
||||||
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
|
content={tooltip}
|
||||||
onBlur={onBlur}
|
offset={5}
|
||||||
RightIcon={() =>
|
noArrow
|
||||||
tooltip && (
|
place="bottom"
|
||||||
<>
|
positionStrategy="fixed"
|
||||||
<IconInfoCircle
|
delay={TooltipDelay.shortDelay}
|
||||||
id={infoCircleElementId + fieldName}
|
/>
|
||||||
size={theme.icon.size.md}
|
</>
|
||||||
color={theme.font.color.tertiary}
|
)
|
||||||
style={{ outline: 'none' }}
|
}
|
||||||
/>
|
/>
|
||||||
<AppTooltip
|
</>
|
||||||
anchorSelect={`#${infoCircleElementId}${fieldName}`}
|
)}
|
||||||
content={tooltip}
|
/>
|
||||||
offset={5}
|
</StyledInputContainer>
|
||||||
noArrow
|
),
|
||||||
place="bottom"
|
)}
|
||||||
positionStrategy="fixed"
|
<Controller
|
||||||
delay={TooltipDelay.shortDelay}
|
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?"
|
||||||
</StyledInputContainer>
|
checked={value ?? true}
|
||||||
),
|
disabled={!objectMetadataItem?.isCustom}
|
||||||
)}
|
advancedMode
|
||||||
<Controller
|
onChange={(value) => {
|
||||||
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
|
onChange(value);
|
||||||
control={control}
|
if (value === true) {
|
||||||
defaultValue={
|
fillNamePluralFromLabelPlural(labelPlural);
|
||||||
objectMetadataItem?.isLabelSyncedWithName ?? true
|
fillNameSingularFromLabelSingular(labelSingular);
|
||||||
}
|
}
|
||||||
render={({ field: { onChange, value } }) => (
|
onBlur?.();
|
||||||
<SyncObjectLabelAndNameToggle
|
}}
|
||||||
value={value ?? true}
|
/>
|
||||||
disabled={!objectMetadataItem?.isCustom}
|
</Card>
|
||||||
onChange={(value) => {
|
)}
|
||||||
onChange(value);
|
/>
|
||||||
if (value === true) {
|
</StyledAdvancedSettingsSectionInputWrapper>
|
||||||
fillNamePluralFromLabelPlural(labelPlural);
|
</StyledAdvancedSettingsContainer>
|
||||||
fillNameSingularFromLabelSingular(labelSingular);
|
</AdvancedSettingsWrapper>
|
||||||
}
|
</StyledAdvancedSettingsOuterContainer>
|
||||||
onBlur?.();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</StyledAdvancedSettingsSectionInputWrapper>
|
|
||||||
</StyledAdvancedSettingsContainer>
|
|
||||||
</StyledAdvancedSettingsOuterContainer>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
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 { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
@@ -44,12 +44,13 @@ export const SettingsSecurityOptionsList = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card rounded>
|
||||||
<SettingsOptionCardContent
|
<SettingsOptionCardContentToggle
|
||||||
Icon={IconLink}
|
Icon={IconLink}
|
||||||
title="Invite by Link"
|
title="Invite by Link"
|
||||||
description="Allow the invitation of new users by sharing an invite link."
|
description="Allow the invitation of new users by sharing an invite link."
|
||||||
checked={currentWorkspace.isPublicInviteLinkEnabled}
|
checked={currentWorkspace.isPublicInviteLinkEnabled}
|
||||||
|
advancedMode
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
handleChange(!currentWorkspace.isPublicInviteLinkEnabled)
|
handleChange(!currentWorkspace.isPublicInviteLinkEnabled)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,21 +12,24 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
|
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
|
||||||
|
|
||||||
export type SelectOption<Value extends string | number | null> = {
|
export type SelectOption<Value extends string | number | boolean | null> = {
|
||||||
value: Value;
|
value: Value;
|
||||||
label: string;
|
label: string;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SelectSizeVariant = 'small' | 'default';
|
||||||
|
|
||||||
type CallToActionButton = {
|
type CallToActionButton = {
|
||||||
text: string;
|
text: string;
|
||||||
onClick: (event: MouseEvent<HTMLDivElement>) => void;
|
onClick: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectProps<Value extends string | number | null> = {
|
export type SelectProps<Value extends string | number | boolean | null> = {
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
selectSizeVariant?: SelectSizeVariant;
|
||||||
disableBlur?: boolean;
|
disableBlur?: boolean;
|
||||||
dropdownId: string;
|
dropdownId: string;
|
||||||
dropdownWidth?: `${string}px` | 'auto' | number;
|
dropdownWidth?: `${string}px` | 'auto' | number;
|
||||||
@@ -54,9 +57,10 @@ const StyledLabel = styled.span`
|
|||||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Select = <Value extends string | number | null>({
|
export const Select = <Value extends string | number | boolean | null>({
|
||||||
className,
|
className,
|
||||||
disabled: disabledFromProps,
|
disabled: disabledFromProps,
|
||||||
|
selectSizeVariant,
|
||||||
disableBlur = false,
|
disableBlur = false,
|
||||||
dropdownId,
|
dropdownId,
|
||||||
dropdownWidth = 176,
|
dropdownWidth = 176,
|
||||||
@@ -115,6 +119,7 @@ export const Select = <Value extends string | number | null>({
|
|||||||
<SelectControl
|
<SelectControl
|
||||||
selectedOption={selectedOption}
|
selectedOption={selectedOption}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
selectSizeVariant={selectSizeVariant}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -125,6 +130,7 @@ export const Select = <Value extends string | number | null>({
|
|||||||
<SelectControl
|
<SelectControl
|
||||||
selectedOption={selectedOption}
|
selectedOption={selectedOption}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
selectSizeVariant={selectSizeVariant}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disableBlur={disableBlur}
|
disableBlur={disableBlur}
|
||||||
@@ -144,7 +150,7 @@ export const Select = <Value extends string | number | null>({
|
|||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
{filteredOptions.map((option) => (
|
{filteredOptions.map((option) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={option.value}
|
key={`${option.value}-${option.label}`}
|
||||||
LeftIcon={option.Icon}
|
LeftIcon={option.Icon}
|
||||||
text={option.label}
|
text={option.label}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -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 { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
const StyledControlContainer = styled.div<{
|
const StyledControlContainer = styled.div<{
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hasIcon: boolean;
|
hasIcon: boolean;
|
||||||
|
selectSizeVariant?: SelectSizeVariant;
|
||||||
}>`
|
}>`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: ${({ hasIcon }) =>
|
grid-template-columns: ${({ hasIcon }) =>
|
||||||
@@ -17,7 +18,8 @@ const StyledControlContainer = styled.div<{
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: ${({ theme }) => theme.spacing(8)};
|
height: ${({ selectSizeVariant, theme }) =>
|
||||||
|
selectSizeVariant === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
@@ -37,13 +39,15 @@ const StyledIconChevronDown = styled(IconChevronDown)<{
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type SelectControlProps = {
|
type SelectControlProps = {
|
||||||
selectedOption: SelectOption<string | number | null>;
|
selectedOption: SelectOption<string | number | boolean | null>;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
selectSizeVariant?: SelectSizeVariant;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectControl = ({
|
export const SelectControl = ({
|
||||||
selectedOption,
|
selectedOption,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
selectSizeVariant,
|
||||||
}: SelectControlProps) => {
|
}: SelectControlProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -51,6 +55,7 @@ export const SelectControl = ({
|
|||||||
<StyledControlContainer
|
<StyledControlContainer
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
hasIcon={isDefined(selectedOption.Icon)}
|
hasIcon={isDefined(selectedOption.Icon)}
|
||||||
|
selectSizeVariant={selectSizeVariant}
|
||||||
>
|
>
|
||||||
{isDefined(selectedOption.Icon) ? (
|
{isDefined(selectedOption.Icon) ? (
|
||||||
<selectedOption.Icon
|
<selectedOption.Icon
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { ComponentDecorator, IconPlus } from 'twenty-ui';
|
|||||||
|
|
||||||
import { Select, SelectProps } from '../Select';
|
import { Select, SelectProps } from '../Select';
|
||||||
|
|
||||||
type RenderProps = SelectProps<string | number | null>;
|
type RenderProps = SelectProps<string | number | boolean | null>;
|
||||||
|
|
||||||
const Render = (args: RenderProps) => {
|
const Render = (args: RenderProps) => {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
const handleChange = (value: string | number | null) => {
|
const handleChange = (value: string | number | boolean | null) => {
|
||||||
args.onChange?.(value);
|
args.onChange?.(value);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const SettingsWorkspace = () => (
|
|||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
title="Support"
|
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."
|
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>
|
</Section>
|
||||||
|
|||||||
@@ -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 { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
|
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
|
||||||
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SettingsSSOIdentitiesProvidersListCard';
|
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SettingsSSOIdentitiesProvidersListCard';
|
||||||
@@ -8,6 +10,21 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
|||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
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 = () => {
|
export const SettingsSecurity = () => {
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<SubMenuTopBarContainer
|
||||||
@@ -22,28 +39,34 @@ export const SettingsSecurity = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<Section>
|
<StyledMainContent>
|
||||||
<H2Title
|
<StyledSSOSection>
|
||||||
title="SSO"
|
<H2Title
|
||||||
description="Configure an SSO connection"
|
title="SSO"
|
||||||
addornment={
|
description="Configure an SSO connection"
|
||||||
<Tag
|
adornment={
|
||||||
text={'Enterprise'}
|
<Tag
|
||||||
color={'transparent'}
|
text={'Enterprise'}
|
||||||
Icon={IconLock}
|
color={'transparent'}
|
||||||
variant={'border'}
|
Icon={IconLock}
|
||||||
/>
|
variant={'border'}
|
||||||
}
|
/>
|
||||||
/>
|
}
|
||||||
<SettingsSSOIdentitiesProvidersListCard />
|
/>
|
||||||
</Section>
|
<SettingsSSOIdentitiesProvidersListCard />
|
||||||
<Section>
|
</StyledSSOSection>
|
||||||
<H2Title
|
<Section>
|
||||||
title="Other"
|
<AdvancedSettingsWrapper>
|
||||||
description="Customize your workspace security"
|
<StyledContainer>
|
||||||
/>
|
<H2Title
|
||||||
<SettingsSecurityOptionsList />
|
title="Other"
|
||||||
</Section>
|
description="Customize your workspace security"
|
||||||
|
/>
|
||||||
|
<SettingsSecurityOptionsList />
|
||||||
|
</StyledContainer>
|
||||||
|
</AdvancedSettingsWrapper>
|
||||||
|
</Section>
|
||||||
|
</StyledMainContent>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export const IllustrationIconArray = (props: IllustrationIconArrayProps) => {
|
|||||||
return (
|
return (
|
||||||
<IllustrationIconWrapper>
|
<IllustrationIconWrapper>
|
||||||
<IllustrationIconArrayRaw
|
<IllustrationIconArrayRaw
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export const IllustrationIconCalendarEvent = (
|
|||||||
return (
|
return (
|
||||||
<IllustrationIconWrapper>
|
<IllustrationIconWrapper>
|
||||||
<IllustrationIconCalendarEventRaw
|
<IllustrationIconCalendarEventRaw
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const IllustrationIconCalendarTime = (
|
|||||||
<IllustrationIconCalendarTimeRaw
|
<IllustrationIconCalendarTimeRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const IllustrationIconCurrency = (
|
|||||||
<IllustrationIconCurrencyRaw
|
<IllustrationIconCurrencyRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconJson = (props: IllustrationIconJsonProps) => {
|
|||||||
<IllustrationIconJsonRaw
|
<IllustrationIconJsonRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconLink = (props: IllustrationIconLinkProps) => {
|
|||||||
<IllustrationIconLinkRaw
|
<IllustrationIconLinkRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
@@ -14,8 +14,8 @@ export const IllustrationIconMail = (props: IllustrationIconMailProps) => {
|
|||||||
<IllustrationIconMailRaw
|
<IllustrationIconMailRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const IllustrationIconManyToMany = (
|
|||||||
<IllustrationIconManyToManyRaw
|
<IllustrationIconManyToManyRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export const IllustrationIconMap = (props: IllustrationIconMapProps) => {
|
|||||||
<IllustrationIconMapRaw
|
<IllustrationIconMapRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const IllustrationIconNumbers = (
|
|||||||
<IllustrationIconNumbersRaw
|
<IllustrationIconNumbersRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const IllustrationIconOneToMany = (
|
|||||||
<IllustrationIconOneToManyRaw
|
<IllustrationIconOneToManyRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const IllustrationIconOneToOne = (
|
|||||||
<IllustrationIconOneToOneRaw
|
<IllustrationIconOneToOneRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const IllustrationIconPhone = (props: IllustrationIconPhoneProps) => {
|
|||||||
<IllustrationIconPhoneRaw
|
<IllustrationIconPhoneRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export const IllustrationIconSetting = (
|
|||||||
<IllustrationIconSettingRaw
|
<IllustrationIconSettingRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const IllustrationIconStar = (props: IllustrationIconStarProps) => {
|
|||||||
<IllustrationIconStarRaw
|
<IllustrationIconStarRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconTag = (props: IllustrationIconTagProps) => {
|
|||||||
<IllustrationIconTagRaw
|
<IllustrationIconTagRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconTags = (props: IllustrationIconTagsProps) => {
|
|||||||
<IllustrationIconTagsRaw
|
<IllustrationIconTagsRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconText = (props: IllustrationIconTextProps) => {
|
|||||||
<IllustrationIconTextRaw
|
<IllustrationIconTextRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconToggle = (props: IllustrationIconToggleProps) => {
|
|||||||
<IllustrationIconToggleRaw
|
<IllustrationIconToggleRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconUid = (props: IllustrationIconUidProps) => {
|
|||||||
<IllustrationIconUidRaw
|
<IllustrationIconUidRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const IllustrationIconUser = (props: IllustrationIconUserProps) => {
|
|||||||
<IllustrationIconUserRaw
|
<IllustrationIconUserRaw
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
fill={fill}
|
fill={fill.blue}
|
||||||
color={color}
|
color={color.blue}
|
||||||
/>
|
/>
|
||||||
</IllustrationIconWrapper>
|
</IllustrationIconWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export {
|
|||||||
IconCurrencyZloty,
|
IconCurrencyZloty,
|
||||||
IconDatabase,
|
IconDatabase,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
|
IconDecimal,
|
||||||
IconDeviceFloppy,
|
IconDeviceFloppy,
|
||||||
IconDoorEnter,
|
IconDoorEnter,
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
@@ -212,6 +213,7 @@ export {
|
|||||||
IconSend,
|
IconSend,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconSettingsAutomation,
|
IconSettingsAutomation,
|
||||||
|
IconSlash,
|
||||||
IconSortDescending,
|
IconSortDescending,
|
||||||
IconSparkles,
|
IconSparkles,
|
||||||
IconSql,
|
IconSql,
|
||||||
@@ -232,6 +234,7 @@ export {
|
|||||||
IconUpload,
|
IconUpload,
|
||||||
IconUser,
|
IconUser,
|
||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
|
IconUserPlus,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
IconVariablePlus,
|
IconVariablePlus,
|
||||||
IconVideo,
|
IconVideo,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export * from './icon/components/IllustrationIconCalendarEvent';
|
|||||||
export * from './icon/components/IllustrationIconCalendarTime';
|
export * from './icon/components/IllustrationIconCalendarTime';
|
||||||
export * from './icon/components/IllustrationIconCurrency';
|
export * from './icon/components/IllustrationIconCurrency';
|
||||||
export * from './icon/components/IllustrationIconJson';
|
export * from './icon/components/IllustrationIconJson';
|
||||||
|
export * from './icon/components/IllustrationIconLink';
|
||||||
export * from './icon/components/IllustrationIconMail';
|
export * from './icon/components/IllustrationIconMail';
|
||||||
export * from './icon/components/IllustrationIconManyToMany';
|
export * from './icon/components/IllustrationIconManyToMany';
|
||||||
export * from './icon/components/IllustrationIconMap';
|
export * from './icon/components/IllustrationIconMap';
|
||||||
@@ -40,7 +41,6 @@ export * from './icon/components/IllustrationIconToggle';
|
|||||||
export * from './icon/components/IllustrationIconUid';
|
export * from './icon/components/IllustrationIconUid';
|
||||||
export * from './icon/components/IllustrationIconUser';
|
export * from './icon/components/IllustrationIconUser';
|
||||||
export * from './icon/components/IllustrationIconWrapper';
|
export * from './icon/components/IllustrationIconWrapper';
|
||||||
export * from './icon/components/llustrationIconLink';
|
|
||||||
export * from './icon/components/TablerIcons';
|
export * from './icon/components/TablerIcons';
|
||||||
export * from './icon/hooks/useIcons';
|
export * from './icon/hooks/useIcons';
|
||||||
export * from './icon/providers/IconsProvider';
|
export * from './icon/providers/IconsProvider';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
type H2TitleProps = {
|
type H2TitleProps = {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
addornment?: React.ReactNode;
|
adornment?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,13 +37,13 @@ const StyledDescription = styled.h3`
|
|||||||
export const H2Title = ({
|
export const H2Title = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
addornment,
|
adornment,
|
||||||
className,
|
className,
|
||||||
}: H2TitleProps) => (
|
}: H2TitleProps) => (
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
<StyledTitleContainer>
|
<StyledTitleContainer>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
{addornment}
|
{adornment}
|
||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
{description && <StyledDescription>{description}</StyledDescription>}
|
{description && <StyledDescription>{description}</StyledDescription>}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import { GRAY_SCALE } from '@ui/theme/constants/GrayScale';
|
||||||
import { COLOR } from './Colors';
|
import { COLOR } from './Colors';
|
||||||
|
|
||||||
export const ILLUSTRATION_ICON_DARK = {
|
export const ILLUSTRATION_ICON_DARK = {
|
||||||
color: COLOR.blue50,
|
color: {
|
||||||
fill: COLOR.blue70,
|
blue: COLOR.blue50,
|
||||||
|
grey: GRAY_SCALE.gray50,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
blue: COLOR.blue70,
|
||||||
|
grey: GRAY_SCALE.gray70,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import { GRAY_SCALE } from '@ui/theme/constants/GrayScale';
|
||||||
import { COLOR } from './Colors';
|
import { COLOR } from './Colors';
|
||||||
|
|
||||||
export const ILLUSTRATION_ICON_LIGHT = {
|
export const ILLUSTRATION_ICON_LIGHT = {
|
||||||
color: COLOR.blue40,
|
color: {
|
||||||
fill: COLOR.blue20,
|
blue: COLOR.blue40,
|
||||||
|
grey: GRAY_SCALE.gray40,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
blue: COLOR.blue20,
|
||||||
|
grey: GRAY_SCALE.gray20,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user