diff --git a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx index 39bafac99582..cd45fdca1fba 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx @@ -1,8 +1,6 @@ import styled from '@emotion/styled'; import { motion } from 'framer-motion'; -import { useEffect, useState } from 'react'; - -import { isDefined } from '~/utils/isDefined'; +import { VisibilityHiddenInput } from 'twenty-ui'; export type ToggleSize = 'small' | 'medium'; @@ -10,10 +8,10 @@ type ContainerProps = { isOn: boolean; color?: string; toggleSize: ToggleSize; - disabled?: boolean; + 'data-disabled'?: boolean; }; -const StyledContainer = styled.div` +const StyledContainer = styled.label` align-items: center; background-color: ${({ theme, isOn, color }) => isOn ? (color ?? theme.color.blue) : theme.background.transparent.medium}; @@ -23,13 +21,15 @@ const StyledContainer = styled.div` height: ${({ toggleSize }) => (toggleSize === 'small' ? 16 : 20)}px; transition: background-color 0.3s ease; width: ${({ toggleSize }) => (toggleSize === 'small' ? 24 : 32)}px; - opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; - pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')}; + opacity: ${({ 'data-disabled': disabled }) => (disabled ? 0.5 : 1)}; + pointer-events: ${({ 'data-disabled': disabled }) => + disabled ? 'none' : 'auto'}; `; -const StyledCircle = styled(motion.div)<{ +const StyledCircle = styled(motion.span)<{ size: ToggleSize; }>` + display: block; background-color: ${({ theme }) => theme.background.primary}; border-radius: 50%; height: ${({ size }) => (size === 'small' ? 12 : 16)}px; @@ -37,6 +37,7 @@ const StyledCircle = styled(motion.div)<{ `; export type ToggleProps = { + id?: string; value?: boolean; onChange?: (value: boolean) => void; color?: string; @@ -46,49 +47,39 @@ export type ToggleProps = { }; export const Toggle = ({ - value, + id, + value = false, onChange, color, toggleSize = 'medium', className, disabled, }: ToggleProps) => { - const [isOn, setIsOn] = useState(value ?? false); - const circleVariants = { on: { x: toggleSize === 'small' ? 10 : 14 }, off: { x: 2 }, }; - const handleChange = () => { - setIsOn(!isOn); - - if (isDefined(onChange)) { - onChange(!isOn); - } - }; - - useEffect(() => { - setIsOn((isOn) => { - if (value !== isOn) { - return value ?? false; - } - - return isOn; - }); - }, [value, setIsOn]); - return ( + { + onChange?.(event.target.checked); + }} + /> + diff --git a/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx index e08f60031bf1..9a74cca8148c 100644 --- a/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx @@ -1,6 +1,7 @@ import { Toggle } from '@/ui/input/components/Toggle'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import styled from '@emotion/styled'; +import { useId } from 'react'; import { useRecoilState } from 'recoil'; import { IconTool, MAIN_COLORS } from 'twenty-ui'; @@ -12,7 +13,7 @@ const StyledContainer = styled.div` position: relative; `; -const StyledText = styled.span` +const StyledLabel = styled.label` color: ${({ theme }) => theme.font.color.secondary}; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme }) => theme.font.weight.medium}; @@ -41,6 +42,7 @@ export const AdvancedSettingsToggle = () => { const [isAdvancedModeEnabled, setIsAdvancedModeEnabled] = useRecoilState( isAdvancedModeEnabledState, ); + const inputId = useId(); const onChange = (newValue: boolean) => { setIsAdvancedModeEnabled(newValue); @@ -52,8 +54,10 @@ export const AdvancedSettingsToggle = () => { - Advanced: + Advanced: + { + return {children}; +}; diff --git a/packages/twenty-ui/src/accessibility/components/VisibilityHiddenInput.tsx b/packages/twenty-ui/src/accessibility/components/VisibilityHiddenInput.tsx new file mode 100644 index 000000000000..01258484db19 --- /dev/null +++ b/packages/twenty-ui/src/accessibility/components/VisibilityHiddenInput.tsx @@ -0,0 +1,7 @@ +import styled from '@emotion/styled'; +import { VISIBILITY_HIDDEN } from '@ui/accessibility/utils/visibility-hidden'; + +// eslint-disable-next-line @nx/workspace-styled-components-prefixed-with-styled +export const VisibilityHiddenInput = styled.input` + ${VISIBILITY_HIDDEN} +`; diff --git a/packages/twenty-ui/src/accessibility/index.ts b/packages/twenty-ui/src/accessibility/index.ts new file mode 100644 index 000000000000..70d4702dd067 --- /dev/null +++ b/packages/twenty-ui/src/accessibility/index.ts @@ -0,0 +1,3 @@ +export * from './components/VisibilityHidden'; +export * from './components/VisibilityHiddenInput'; +export * from './utils/visibility-hidden'; diff --git a/packages/twenty-ui/src/accessibility/utils/visibility-hidden.ts b/packages/twenty-ui/src/accessibility/utils/visibility-hidden.ts new file mode 100644 index 000000000000..c00ebfe9060b --- /dev/null +++ b/packages/twenty-ui/src/accessibility/utils/visibility-hidden.ts @@ -0,0 +1,13 @@ +import { css } from '@emotion/react'; + +export const VISIBILITY_HIDDEN = css` + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +`; diff --git a/packages/twenty-ui/src/index.ts b/packages/twenty-ui/src/index.ts index bc26e1a7c694..7518172f6bb3 100644 --- a/packages/twenty-ui/src/index.ts +++ b/packages/twenty-ui/src/index.ts @@ -1,3 +1,4 @@ +export * from './accessibility'; export * from './components'; export * from './display'; export * from './layout';