diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index 283e33ea458..f2ed2bdabe9 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -312,7 +312,9 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'segmentedControlButton', 'switchRoot', + 'switchIndicator', 'switchThumb', + 'switchLabel', 'alert', 'alertIcon', diff --git a/packages/clerk-js/src/ui/elements/Switch.tsx b/packages/clerk-js/src/ui/elements/Switch.tsx index c768993ab16..f7e979a5036 100644 --- a/packages/clerk-js/src/ui/elements/Switch.tsx +++ b/packages/clerk-js/src/ui/elements/Switch.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useId, useState } from 'react'; +import React, { forwardRef, useState } from 'react'; import type { LocalizationKey } from '../customizables'; import { descriptors, Flex, Text } from '../customizables'; @@ -14,44 +14,49 @@ interface SwitchProps { } export const Switch = forwardRef( - ({ checked: controlledChecked, defaultChecked, onChange, disabled = false, 'aria-label': ariaLabel, label }, ref) => { + ({ checked: controlledChecked, defaultChecked, onChange, disabled = false, label }, ref) => { const [internalChecked, setInternalChecked] = useState(!!defaultChecked); const isControlled = controlledChecked !== undefined; const checked = isControlled ? controlledChecked : internalChecked; - const labelId = useId(); - const handleToggle = (_: React.MouseEvent | React.KeyboardEvent) => { + const handleChange = (e: React.ChangeEvent) => { if (disabled) return; if (!isControlled) { - setInternalChecked(!checked); - } - onChange?.(!checked); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - handleToggle(e); + setInternalChecked(e.target.checked); } + onChange?.(e.target.checked); }; return ( ({ + width: 'fit-content', + '&:has(input:focus-visible) > span': { + ...common.focusRingStyles(t), + }, + })} > - + ({ width: t.sizes.$6, height: t.sizes.$4, @@ -65,7 +70,6 @@ export const Switch = forwardRef( outline: 'none', boxSizing: 'border-box', boxShadow: '0px 0px 0px 1px rgba(0, 0, 0, 0.06) inset', - ...common.focusRing(t), })} > ( {label && ( ({ + paddingInlineStart: t.sizes.$2, cursor: disabled ? 'not-allowed' : 'pointer', userSelect: 'none', - }} + })} /> )} diff --git a/packages/clerk-js/src/ui/styledSystem/common.ts b/packages/clerk-js/src/ui/styledSystem/common.ts index b60230e10c2..471711397ab 100644 --- a/packages/clerk-js/src/ui/styledSystem/common.ts +++ b/packages/clerk-js/src/ui/styledSystem/common.ts @@ -130,15 +130,21 @@ const borderColor = (t: InternalTheme, props?: any) => { } as const; }; +const focusRingStyles = (t: InternalTheme) => { + return { + '&::-moz-focus-inner': { border: '0' }, + WebkitTapHighlightColor: 'transparent', + boxShadow: t.shadows.$focusRing.replace('{{color}}', t.colors.$neutralAlpha200), + transitionProperty: t.transitionProperty.$common, + transitionTimingFunction: t.transitionTiming.$common, + transitionDuration: t.transitionDuration.$focusRing, + } as const; +}; + const focusRing = (t: InternalTheme) => { return { '&:focus': { - '&::-moz-focus-inner': { border: '0' }, - WebkitTapHighlightColor: 'transparent', - boxShadow: t.shadows.$focusRing.replace('{{color}}', t.colors.$neutralAlpha200), - transitionProperty: t.transitionProperty.$common, - transitionTimingFunction: t.transitionTiming.$common, - transitionDuration: t.transitionDuration.$focusRing, + ...focusRingStyles(t), }, } as const; }; @@ -198,6 +204,7 @@ const visuallyHidden = () => export const common = { textVariants, borderVariants, + focusRingStyles, focusRing, disabled, borderColor, diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index 56548641cf8..159392ee94d 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -283,7 +283,9 @@ export type ElementsConfig = { segmentedControlButton: WithOptions; switchRoot: WithOptions; + switchIndicator: WithOptions; switchThumb: WithOptions; + switchLabel: WithOptions; avatarBox: WithOptions; avatarImage: WithOptions;