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