Fix noninteractive toggle (#8383)

- Use a label to make the whole card interactive
- Disallow the Toggle component to shrink; it used to on mobile devices

A focus indicator is missing for the Toggle component. We'll have to add
one.
This commit is contained in:
Baptiste Devessier 2024-11-08 12:42:15 +01:00 committed by GitHub
parent 8b5e90aca9
commit f3e3c186dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 50 deletions

View File

@ -5,7 +5,7 @@ import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent'; import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
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, Toggle } from 'twenty-ui'; import { Card, H2Title } from 'twenty-ui';
import { CalendarChannelVisibility } from '~/generated-metadata/graphql'; import { CalendarChannelVisibility } from '~/generated-metadata/graphql';
const StyledDetailsContainer = styled.div` const StyledDetailsContainer = styled.div`
@ -21,10 +21,6 @@ type SettingsAccountsCalendarChannelDetailsProps = {
>; >;
}; };
const StyledToggle = styled(Toggle)`
margin-left: auto;
`;
export const SettingsAccountsCalendarChannelDetails = ({ export const SettingsAccountsCalendarChannelDetails = ({
calendarChannel, calendarChannel,
}: SettingsAccountsCalendarChannelDetailsProps) => { }: SettingsAccountsCalendarChannelDetailsProps) => {
@ -71,16 +67,13 @@ export const SettingsAccountsCalendarChannelDetails = ({
<SettingsOptionCardContent <SettingsOptionCardContent
title="Auto-creation" title="Auto-creation"
description="Automatically create contacts for people." description="Automatically create contacts for people."
onClick={() => checked={calendarChannel.isContactAutoCreationEnabled}
onChange={() => {
handleContactAutoCreationToggle( handleContactAutoCreationToggle(
!calendarChannel.isContactAutoCreationEnabled, !calendarChannel.isContactAutoCreationEnabled,
) );
} }}
>
<StyledToggle
value={calendarChannel.isContactAutoCreationEnabled}
/> />
</SettingsOptionCardContent>
</Card> </Card>
</Section> </Section>
</StyledDetailsContainer> </StyledDetailsContainer>

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Card, H2Title, Section, Toggle } from 'twenty-ui'; import { Card, H2Title, Section } from 'twenty-ui';
import { import {
MessageChannel, MessageChannel,
@ -30,10 +30,6 @@ const StyledDetailsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(6)}; gap: ${({ theme }) => theme.spacing(6)};
`; `;
const StyledToggle = styled(Toggle)`
margin-left: auto;
`;
export const SettingsAccountsMessageChannelDetails = ({ export const SettingsAccountsMessageChannelDetails = ({
messageChannel, messageChannel,
}: SettingsAccountsMessageChannelDetailsProps) => { }: SettingsAccountsMessageChannelDetailsProps) => {
@ -107,25 +103,23 @@ export const SettingsAccountsMessageChannelDetails = ({
title="Exclude non-professional emails" title="Exclude non-professional emails"
description="Dont create contacts from/to Gmail, Outlook emails" description="Dont create contacts from/to Gmail, Outlook emails"
divider divider
onClick={() => checked={messageChannel.excludeNonProfessionalEmails}
onChange={() => {
handleIsNonProfessionalEmailExcludedToggle( handleIsNonProfessionalEmailExcludedToggle(
!messageChannel.excludeNonProfessionalEmails, !messageChannel.excludeNonProfessionalEmails,
) );
} }}
> />
<StyledToggle value={messageChannel.excludeNonProfessionalEmails} />
</SettingsOptionCardContent>
<SettingsOptionCardContent <SettingsOptionCardContent
title="Exclude group emails" title="Exclude group emails"
description="Dont sync emails from team@ support@ noreply@..." description="Dont sync emails from team@ support@ noreply@..."
onClick={() => checked={messageChannel.excludeGroupEmails}
onChange={() =>
handleIsGroupEmailExcludedToggle( handleIsGroupEmailExcludedToggle(
!messageChannel.excludeGroupEmails, !messageChannel.excludeGroupEmails,
) )
} }
> />
<StyledToggle value={messageChannel.excludeGroupEmails} />
</SettingsOptionCardContent>
</Card> </Card>
</Section> </Section>
</StyledDetailsContainer> </StyledDetailsContainer>

View File

@ -1,16 +1,16 @@
import styled from '@emotion/styled';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent, CardContent } from 'twenty-ui'; import { useId } from 'react';
import { ReactNode } from 'react'; import { CardContent, IconComponent, Toggle } from 'twenty-ui';
type SettingsOptionCardContentProps = { type SettingsOptionCardContentProps = {
Icon?: IconComponent; Icon?: IconComponent;
title: string; title: React.ReactNode;
description: string; description: string;
onClick: () => void;
children: ReactNode;
divider?: boolean; divider?: boolean;
checked: boolean;
onChange: (checked: boolean) => void;
}; };
const StyledCardContent = styled(CardContent)` const StyledCardContent = styled(CardContent)`
@ -18,6 +18,7 @@ const StyledCardContent = styled(CardContent)`
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(4)}; gap: ${({ theme }) => theme.spacing(4)};
cursor: pointer; cursor: pointer;
position: relative;
&:hover { &:hover {
background: ${({ theme }) => theme.background.transparent.lighter}; background: ${({ theme }) => theme.background.transparent.lighter};
@ -47,28 +48,48 @@ const StyledIcon = styled.div`
min-width: ${({ theme }) => theme.icon.size.md}; 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 = ({ export const SettingsOptionCardContent = ({
Icon, Icon,
title, title,
description, description,
onClick,
children,
divider, divider,
checked,
onChange,
}: SettingsOptionCardContentProps) => { }: SettingsOptionCardContentProps) => {
const theme = useTheme(); const theme = useTheme();
const toggleId = useId();
return ( return (
<StyledCardContent onClick={onClick} divider={divider}> <StyledCardContent divider={divider}>
{Icon && ( {Icon && (
<StyledIcon> <StyledIcon>
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.md} /> <Icon size={theme.icon.size.md} stroke={theme.icon.stroke.md} />
</StyledIcon> </StyledIcon>
)} )}
<div> <div>
<StyledTitle>{title}</StyledTitle> <StyledTitle>
<label htmlFor={toggleId}>
{title}
<StyledCover />
</label>
</StyledTitle>
<StyledDescription>{description}</StyledDescription> <StyledDescription>{description}</StyledDescription>
</div> </div>
{children}
<StyledToggle id={toggleId} value={checked} onChange={onChange} />
</StyledCardContent> </StyledCardContent>
); );
}; };

View File

@ -2,21 +2,21 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent'; import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
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 styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { Card, IconLink, Toggle } from 'twenty-ui'; import { Card, IconLink, isDefined } from 'twenty-ui';
import { useUpdateWorkspaceMutation } from '~/generated/graphql'; import { useUpdateWorkspaceMutation } from '~/generated/graphql';
const StyledToggle = styled(Toggle)`
margin-left: auto;
`;
export const SettingsSecurityOptionsList = () => { export const SettingsSecurityOptionsList = () => {
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const [currentWorkspace, setCurrentWorkspace] = useRecoilState( const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
currentWorkspaceState, currentWorkspaceState,
); );
if (!isDefined(currentWorkspace)) {
throw new Error(
'The current workspace must be defined to edit its security options.',
);
}
const [updateWorkspace] = useUpdateWorkspaceMutation(); const [updateWorkspace] = useUpdateWorkspaceMutation();
@ -49,12 +49,11 @@ export const SettingsSecurityOptionsList = () => {
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."
onClick={() => checked={currentWorkspace.isPublicInviteLinkEnabled}
handleChange(!currentWorkspace?.isPublicInviteLinkEnabled) onChange={() =>
handleChange(!currentWorkspace.isPublicInviteLinkEnabled)
} }
> />
<StyledToggle value={currentWorkspace?.isPublicInviteLinkEnabled} />
</SettingsOptionCardContent>
</Card> </Card>
); );
}; };

View File

@ -12,6 +12,7 @@ type ContainerProps = {
}; };
const StyledContainer = styled.label<ContainerProps>` const StyledContainer = styled.label<ContainerProps>`
flex-shrink: 0;
align-items: center; align-items: center;
background-color: ${({ theme, isOn, color }) => background-color: ${({ theme, isOn, color }) =>
isOn ? (color ?? theme.color.blue) : theme.background.transparent.medium}; isOn ? (color ?? theme.color.blue) : theme.background.transparent.medium};