diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md
index a4d7123a09..ac56958eae 100644
--- a/docs/6.x/docs/guides/migration.md
+++ b/docs/6.x/docs/guides/migration.md
@@ -279,3 +279,54 @@ const theme = {
style={{ fontSize: 16, color: '#1C1B1F' }}
/>
```
+
+### Switch
+
+#### Explicit operability
+
+A `Switch` now has to declare how it can be operated. Previously a switch with no `onValueChange` still rendered as an enabled, focusable control that did nothing when activated, and screen readers announced it as operable.
+
+Pass `onValueChange` to make it interactive, or mark it `readOnly` or `disabled` to render it as a state indicator. A read-only switch keeps its enabled appearance and is still announced with its on/off state, but it is neither focusable nor pressable — it is not reported as disabled.
+
+```tsx
+// Before (v5) — an enabled switch that does nothing
+
+
+// After (v6) — say which it is
+
+
+
+```
+
+This most often shows up where the switch sits inside a row that owns the press:
+
+```tsx
+// After (v6)
+
+
+ Dark Theme
+
+
+
+
+
+```
+
+#### Selected icon color
+
+The icon inside a selected switch now uses `onPrimaryContainer` instead of `primary`, matching the Material Design 3 spec and improving its contrast against the handle. If you theme `Switch` with a partial `theme.colors` override, add `onPrimaryContainer` alongside the roles you already override — omitted roles fall back to the app theme, which would leave the icon in the default palette.
+
+```diff
+ const switchTheme = {
+ colors: {
+ primary: theme.colors.tertiary,
+ onPrimary: theme.colors.onTertiary,
+ primaryContainer: theme.colors.tertiaryContainer,
++ onPrimaryContainer: theme.colors.onTertiaryContainer,
+ },
+ };
+```
+
+#### Touch target height
+
+`Switch` now reserves the 48dp minimum touch target Material Design requires, so it occupies 48dp of height instead of 40dp. Nothing painted changed size — the track is still 52×32 — but rows containing a switch may become slightly taller.
diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx
index 94afa3136c..00489864cc 100644
--- a/example/src/DrawerItems.tsx
+++ b/example/src/DrawerItems.tsx
@@ -178,7 +178,7 @@ function DrawerItems() {
Use Dynamic Theme
-
+
@@ -187,7 +187,7 @@ function DrawerItems() {
Dark Theme
-
+
@@ -196,7 +196,7 @@ function DrawerItems() {
RTL
-
+
@@ -205,7 +205,7 @@ function DrawerItems() {
Collapsed drawer *
-
+
@@ -214,7 +214,7 @@ function DrawerItems() {
Custom font *
-
+
@@ -225,7 +225,7 @@ function DrawerItems() {
{isIOS ? 'Highlight' : 'Ripple'} effect *
-
+
diff --git a/example/src/Examples/FABExample.tsx b/example/src/Examples/FABExample.tsx
index 09ce39aa14..1b8c3d61f9 100644
--- a/example/src/Examples/FABExample.tsx
+++ b/example/src/Examples/FABExample.tsx
@@ -154,7 +154,7 @@ const FABExample = () => {
title="Show FAB"
right={() => (
-
+
)}
onPress={() => setShowFab((v) => !v)}
diff --git a/example/src/Examples/SwitchExample.tsx b/example/src/Examples/SwitchExample.tsx
index 545dbf8dd3..09a3c7ec92 100644
--- a/example/src/Examples/SwitchExample.tsx
+++ b/example/src/Examples/SwitchExample.tsx
@@ -33,6 +33,7 @@ const SwitchExample = () => {
primary: theme.colors.tertiary,
onPrimary: theme.colors.onTertiary,
primaryContainer: theme.colors.tertiaryContainer,
+ onPrimaryContainer: theme.colors.onTertiaryContainer,
secondary: theme.colors.tertiary,
},
}),
@@ -88,6 +89,10 @@ const SwitchExample = () => {
/>
+
+
+
+
{
{label}
-
+
diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx
index b0ca4b4bd7..97d6feabd5 100644
--- a/src/components/Switch/Switch.tsx
+++ b/src/components/Switch/Switch.tsx
@@ -3,7 +3,9 @@ import {
Platform,
Pressable,
StyleSheet,
+ type NativeSyntheticEvent,
type StyleProp,
+ type TargetedEvent,
View,
type ViewStyle,
} from 'react-native';
@@ -15,7 +17,6 @@ import Animated, {
useAnimatedStyle,
useDerivedValue,
useSharedValue,
- withDelay,
withSequence,
withSpring,
withTiming,
@@ -33,19 +34,11 @@ import type { StateOpacityKey, ThemeProp } from '../../types';
import { isKeyboardFocusEvent } from '../../utils/isKeyboardFocusEvent';
import Icon, { type IconSource } from '../Icon';
-export type Props = {
+type SwitchBaseProps = {
/**
* Whether the switch is on.
*/
value?: boolean;
- /**
- * Called with the new value when the user toggles the switch.
- */
- onValueChange?: (value: boolean) => void;
- /**
- * Disables interaction and renders the disabled visual state.
- */
- disabled?: boolean;
/**
* Icon shown inside the handle when checked
*/
@@ -63,11 +56,51 @@ export type Props = {
'aria-label'?: string;
};
+export type Props = SwitchBaseProps & {
+ /**
+ * Called with the new value when the user toggles the switch. Required
+ * unless the switch is `readOnly` or `disabled`.
+ */
+ onValueChange?: (value: boolean) => void;
+ /**
+ * Reports state the user cannot change here. The switch keeps its enabled
+ * appearance and is still announced by a screen reader, but it is neither
+ * focusable nor pressable.
+ */
+ readOnly?: boolean;
+ /**
+ * Disables interaction and renders the disabled visual state.
+ */
+ disabled?: boolean;
+};
+
+/**
+ * `Props`, narrowed so a switch always declares how it can be operated and can
+ * never render as an enabled control that does nothing when activated.
+ *
+ * This is applied to the component's declared type rather than folded into
+ * `Props`, because the docs generator reads props off the parameter annotation
+ * and silently drops a union (a top-level one drops the whole page). So the
+ * parameter below stays annotated as the flat `Props`, and callers still get
+ * the narrowed type through this one.
+ */
+export type OperableProps = SwitchBaseProps &
+ (
+ | {
+ onValueChange: (value: boolean) => void;
+ readOnly?: boolean;
+ disabled?: boolean;
+ }
+ | { onValueChange?: never; readOnly: true; disabled?: boolean }
+ | { onValueChange?: never; readOnly?: boolean; disabled: true }
+ );
+
const {
trackWidth: TRACK_WIDTH,
trackHeight: TRACK_HEIGHT,
trackOutlineWidth: TRACK_OUTLINE_WIDTH,
stateLayerSize: STATE_LAYER_SIZE,
+ touchTargetSize: TOUCH_TARGET_SIZE,
selectedHandleSize: SELECTED_HANDLE,
unselectedHandleSize: UNSELECTED_HANDLE,
iconHandleSize: ICON_HANDLE,
@@ -86,11 +119,10 @@ const stateOpacity = stateTokens.opacity;
const { thickness: FOCUS_THICKNESS, outerOffset: FOCUS_OUTER_OFFSET } =
stateTokens.focusIndicator;
const FOCUS_RING_INSET = -(FOCUS_OUTER_OFFSET + FOCUS_THICKNESS);
-const OVERLAY_TOP = (STATE_LAYER_SIZE - TRACK_HEIGHT) / 2;
-
-// Hold-then-grow: a brief delay before snapping to PRESSED_HANDLE so a quick
-// tap doesn't flash the press-grow visual.
-const PRESS_GROW_DELAY = 100;
+// Every painted layer is smaller than the touch target and absolutely
+// positioned, so each one is centred against it.
+const centreY = (size: number) => (TOUCH_TARGET_SIZE - size) / 2;
+const TRACK_TOP = centreY(TRACK_HEIGHT);
function restingHandleSize(checked: boolean, hasIcon: boolean): number {
if (hasIcon) return ICON_HANDLE;
@@ -117,18 +149,20 @@ const CHECKED_CENTER = TRACK_WIDTH - HANDLE_PADDING - SELECTED_HANDLE / 2;
*
* ## Theming
* Customize by overriding these `theme.colors` roles:
- * - `primary` / `onPrimary`: selected track + icon / selected handle
- * - `primaryContainer`: selected handle on hover, press
+ * - `primary` / `onPrimary`: selected track / selected handle
+ * - `onPrimaryContainer`: selected icon
+ * - `primaryContainer`: selected handle on hover, focus, press
* - `surfaceContainerHighest`: unselected track + icon
* - `outline`: unselected resting handle, unselected track outline
- * - `onSurfaceVariant`: unselected handle on hover, press
+ * - `onSurfaceVariant`: unselected handle on hover, focus, press
* - `onSurface`: disabled track, handle, and icon fills
* - `surface`: disabled selected handle
* - `secondary`: focus indicator
*/
-const Switch = ({
+const Switch: (props: OperableProps) => React.JSX.Element = ({
value,
disabled,
+ readOnly,
onValueChange,
checkedIcon,
uncheckedIcon,
@@ -145,6 +179,22 @@ const Switch = ({
const checked = !!value;
const isDisabled = !!disabled;
const isEnabled = !isDisabled;
+ const isReadOnly = !!readOnly;
+ // `OperableProps` already requires one of these, but untyped callers can slip
+ // past it, so guard here too rather than rendering an enabled no-op.
+ const isMissingOperability = !onValueChange && !isReadOnly && !isDisabled;
+
+ if (isMissingOperability) {
+ console.warn(
+ 'Switch: pass `onValueChange` to make the switch operable, or set `readOnly` or `disabled` to render it as a state indicator.'
+ );
+ }
+
+ const isInteractive = !isDisabled && !isReadOnly && !isMissingOperability;
+ // Non-operable but not disabled: an explicit `readOnly`, or the fallback for
+ // a missing handler. Both render as state indicators, so both are announced
+ // that way; `aria-disabled` carries the disabled case on its own.
+ const isAnnouncedReadOnly = !isDisabled && !isInteractive;
const iconSource = checked ? checkedIcon : uncheckedIcon;
const hasIcon = iconSource !== undefined;
@@ -164,6 +214,17 @@ const Switch = ({
isDisabledSV.value = isDisabled ? 1 : 0;
}, [checked, hasIcon, isDisabled, checkedSV, hasIconSV, isDisabledSV]);
+ // A switch that stops being interactive loses its handlers, so one that was
+ // hovered, pressed, or focused at that moment would never get the matching
+ // hover-out, press-out, or blur and would keep painting that state.
+ React.useEffect(() => {
+ if (isInteractive) return;
+
+ pressedSV.value = 0;
+ hoveredSV.value = 0;
+ focusedSV.value = 0;
+ }, [isInteractive, pressedSV, hoveredSV, focusedSV]);
+
const colors = React.useMemo(() => getDefaultSwitchColors(theme), [theme]);
const reanimatedReduceMotion = reduceMotion
@@ -229,14 +290,10 @@ const Switch = ({
({ p, c, hi }) => {
const restingSize =
hi === 1 ? ICON_HANDLE : c === 1 ? SELECTED_HANDLE : UNSELECTED_HANDLE;
- if (p === 1) {
- handleSize.value = withDelay(
- PRESS_GROW_DELAY,
- withTiming(PRESSED_HANDLE, { duration: 0 })
- );
- } else {
- handleSize.value = withSpring(restingSize, springConfig);
- }
+ handleSize.value = withSpring(
+ p === 1 ? PRESSED_HANDLE : restingSize,
+ springConfig
+ );
},
[springConfig]
);
@@ -251,12 +308,7 @@ const Switch = ({
(current, prev) => {
if (current === prev) return;
const wasPressed = prev === 'pressed';
- const target =
- current === 'pressed'
- ? stateOpacity.pressed
- : current === 'hovered'
- ? stateOpacity.hovered
- : 0;
+ const target = current ? stateOpacity[current] : 0;
if (wasPressed && current !== 'pressed') {
// On release: rise to peak, then fall to the next state.
stateLayerAlpha.value = withSequence(
@@ -284,6 +336,11 @@ const Switch = ({
? colors.checkedPressedHandleColor
: colors.uncheckedPressedHandleColor;
}
+ if (focusedSV.value === 1) {
+ return isCheckedNow
+ ? colors.checkedFocusHandleColor
+ : colors.uncheckedFocusHandleColor;
+ }
if (hoveredSV.value === 1) {
return isCheckedNow
? colors.checkedHoverHandleColor
@@ -297,7 +354,7 @@ const Switch = ({
const handleAnimatedStyle = useAnimatedStyle(() => ({
width: handleSize.value,
height: handleSize.value,
- top: (STATE_LAYER_SIZE - handleSize.value) / 2,
+ top: (TOUCH_TARGET_SIZE - handleSize.value) / 2,
transform: [
{ translateX: xSign * (handleCenter.value - handleSize.value / 2) },
],
@@ -345,30 +402,40 @@ const Switch = ({
const trackOpacityValue = isDisabled ? DISABLED_TRACK_OPACITY : 1;
const iconSize = checked ? SELECTED_ICON : UNSELECTED_ICON;
- return (
-
- onValueChange?.(!checked)}
- onPressIn={() => {
+ // A non-interactive switch gets no press, hover, or focus affordances at all.
+ // It stays in the accessibility tree, so its state is still announced.
+ const interactionProps = isInteractive
+ ? {
+ onPress: () => onValueChange?.(!checked),
+ onPressIn: () => {
pressedSV.value = 1;
- }}
- onPressOut={() => {
+ },
+ onPressOut: () => {
pressedSV.value = 0;
- }}
- onHoverIn={() => {
+ },
+ onHoverIn: () => {
hoveredSV.value = 1;
- }}
- onHoverOut={() => {
+ },
+ onHoverOut: () => {
hoveredSV.value = 0;
- }}
- onFocus={(e) => {
+ },
+ onFocus: (e: NativeSyntheticEvent) => {
if (!isKeyboardFocusEvent(e)) return;
focusedSV.value = 1;
- }}
- onBlur={() => {
+ },
+ onBlur: () => {
focusedSV.value = 0;
- }}
+ },
+ }
+ : null;
+
+ return (
+
+
-
+
{/* Disabled-only: opaque `surface` backdrop. The tinted fill above
composites over it, reproducing the native math avoiding the PlatformColor alpha limitation. */}
{isDisabled ? (
@@ -413,6 +484,7 @@ const Switch = ({
/>
) : null}
+ Reanimated.getAnimatedStyle(screen.getByTestId(testID));
+
+afterEach(() => {
+ jest.restoreAllMocks();
+});
+
describe('Switch render', () => {
it('renders on', async () => {
- expect((await render()).toJSON()).toMatchSnapshot();
+ expect(
+ (await render()).toJSON()
+ ).toMatchSnapshot();
});
it('renders off', async () => {
- expect((await render()).toJSON()).toMatchSnapshot();
+ expect(
+ (
+ await render()
+ ).toJSON()
+ ).toMatchSnapshot();
});
it('renders disabled on', async () => {
@@ -26,14 +45,25 @@ describe('Switch render', () => {
it('renders with checked icon', async () => {
expect(
- (await render()).toJSON()
+ (
+ await render(
+
+ )
+ ).toJSON()
).toMatchSnapshot();
});
it('renders with per-state icons', async () => {
expect(
(
- await render()
+ await render(
+
+ )
).toJSON()
).toMatchSnapshot();
});
@@ -41,10 +71,199 @@ describe('Switch render', () => {
describe('Switch accessibility', () => {
it('has switch role', async () => {
- await render();
+ await render();
expect(screen.getByRole('switch')).toBeOnTheScreen();
});
+
+ it('exposes a touch target meeting the 48dp minimum', async () => {
+ await render();
+
+ expect(screen.getByRole('switch')).toHaveStyle({ width: 52, height: 48 });
+ });
+});
+
+describe('Switch focus state', () => {
+ const renderAndFocus = async (element: React.ReactElement) => {
+ await render(element);
+
+ await fireEvent(screen.getByTestId('switch'), 'focus');
+ await jest.runAllTimersAsync();
+ };
+
+ it('shows the focus indicator on keyboard focus', async () => {
+ await renderAndFocus(
+
+ );
+
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 1 });
+ });
+
+ it('hides the focus indicator again on blur', async () => {
+ await renderAndFocus(
+
+ );
+
+ await fireEvent(screen.getByTestId('switch'), 'blur');
+ await jest.runAllTimersAsync();
+
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 0 });
+ });
+
+ it('raises the state layer to the focused opacity', async () => {
+ await renderAndFocus(
+
+ );
+
+ expect(animatedStyle('switch-state-layer')).toMatchObject({
+ opacity: tokens.md.sys.state.opacity.focused,
+ });
+ });
+
+ it('leaves the indicator hidden for pointer focus on web', async () => {
+ jest.replaceProperty(Platform, 'OS', 'web');
+
+ await render(
+
+ );
+ await fireEvent(screen.getByTestId('switch'), 'focus', {
+ currentTarget: { matches: () => false },
+ });
+ await jest.runAllTimersAsync();
+
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 0 });
+ });
+
+ it('shows the indicator for keyboard focus on web', async () => {
+ jest.replaceProperty(Platform, 'OS', 'web');
+
+ await render(
+
+ );
+ await fireEvent(screen.getByTestId('switch'), 'focus', {
+ currentTarget: { matches: () => true },
+ });
+ await jest.runAllTimersAsync();
+
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 1 });
+ });
+
+ it('clears the focus state when the switch stops being interactive', async () => {
+ const view = await render(
+
+ );
+
+ await fireEvent(screen.getByTestId('switch'), 'focus');
+ await jest.runAllTimersAsync();
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 1 });
+
+ await view.rerender();
+ await jest.runAllTimersAsync();
+
+ expect(animatedStyle('switch-focus-ring')).toMatchObject({ opacity: 0 });
+ });
+
+ it('paints the focus handle color when selected and focused', async () => {
+ await renderAndFocus(
+
+ );
+
+ expect(animatedStyle('switch-handle-fill')).toMatchObject({
+ backgroundColor: defaultThemes.light.colors.primaryContainer,
+ });
+ });
+});
+
+describe('Switch operability', () => {
+ it('warns when it is given no way to be operated', async () => {
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
+
+ // @ts-expect-error -- the props type requires a handler, `readOnly`, or
+ // `disabled`; this covers untyped callers that get past it.
+ await render();
+
+ expect(console.warn).toHaveBeenCalledWith(
+ expect.stringContaining('onValueChange')
+ );
+ });
+
+ it('is not focusable when it has no way to be operated', async () => {
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
+
+ // @ts-expect-error -- see above.
+ await render();
+
+ expect(screen.getByRole('switch')).toHaveProp('focusable', false);
+ });
+
+ it('announces a read-only switch without making it focusable', async () => {
+ await render();
+
+ const control = screen.getByRole('switch');
+
+ expect(control).toHaveProp('focusable', false);
+ expect(control).toHaveProp('aria-readonly', true);
+ expect(control).toBeChecked();
+ expect(control).toBeEnabled();
+ });
+
+ it('announces a switch with no way to be operated as read-only', async () => {
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
+
+ // @ts-expect-error -- the fallback exists for untyped callers.
+ await render();
+
+ const control = screen.getByRole('switch');
+
+ expect(control).toHaveProp('aria-readonly', true);
+ expect(control).toBeEnabled();
+ });
+
+ it('does not announce a disabled switch as read-only', async () => {
+ await render();
+
+ const control = screen.getByRole('switch');
+
+ expect(control).toHaveProp('aria-readonly', false);
+ expect(control).toBeDisabled();
+ });
+
+ it('keeps an interactive switch focusable', async () => {
+ await render();
+
+ expect(screen.getByRole('switch')).toHaveProp('focusable', true);
+ });
+});
+
+describe('Switch press feedback', () => {
+ // MD3 resting (selected) and pressed handle sizes.
+ const RESTING = 24;
+ const PRESSED = 28;
+ const handleWidth = () => Number(animatedStyle('switch-handle').width);
+
+ it('starts growing the handle immediately on press', async () => {
+ await render();
+ expect(handleWidth()).toBe(RESTING);
+
+ await fireEvent(screen.getByTestId('switch'), 'pressIn');
+ // Two frames in -- well inside the 100ms delay the old implementation
+ // waited out before snapping, so a reintroduced delay still reads RESTING.
+ jest.advanceTimersByTime(32);
+
+ expect(handleWidth()).toBeGreaterThan(RESTING);
+ });
+
+ it('settles at the pressed size and returns on release', async () => {
+ await render();
+
+ await fireEvent(screen.getByTestId('switch'), 'pressIn');
+ await jest.runAllTimersAsync();
+ expect(handleWidth()).toBe(PRESSED);
+
+ await fireEvent(screen.getByTestId('switch'), 'pressOut');
+ await jest.runAllTimersAsync();
+ expect(handleWidth()).toBe(RESTING);
+ });
});
describe('Switch interaction', () => {
@@ -64,6 +283,19 @@ describe('Switch interaction', () => {
expect(onValueChange).toHaveBeenCalledWith(false);
});
+ it('does not toggle a read-only switch that still has a handler', async () => {
+ const user = userEvent.setup();
+ const onValueChange = jest.fn();
+ // The props type permits this pairing, so read-only has to win at runtime.
+ await render(
+
+ );
+
+ await user.press(screen.getByRole('switch'));
+
+ expect(onValueChange).not.toHaveBeenCalled();
+ });
+
it('does not fire onValueChange when disabled', async () => {
const user = userEvent.setup();
const onValueChange = jest.fn();
diff --git a/src/components/__tests__/__snapshots__/Switch.test.tsx.snap b/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
index e38a5145e1..45aed5023f 100644
--- a/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
@@ -6,7 +6,7 @@ exports[`Switch render renders disabled off 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -34,8 +34,9 @@ exports[`Switch render renders disabled off 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
- focusable={true}
+ focusable={false}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -50,7 +51,7 @@ exports[`Switch render renders disabled off 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -104,7 +105,7 @@ exports[`Switch render renders disabled off 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -139,7 +140,7 @@ exports[`Switch render renders disabled off 1`] = `
},
{
"height": 16,
- "top": 12,
+ "top": 16,
"transform": [
{
"translateX": 8,
@@ -201,10 +202,10 @@ exports[`Switch render renders disabled off 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
@@ -221,7 +222,7 @@ exports[`Switch render renders disabled on 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -249,8 +250,9 @@ exports[`Switch render renders disabled on 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
- focusable={true}
+ focusable={false}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -265,7 +267,7 @@ exports[`Switch render renders disabled on 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -299,7 +301,7 @@ exports[`Switch render renders disabled on 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -334,7 +336,7 @@ exports[`Switch render renders disabled on 1`] = `
},
{
"height": 24,
- "top": 8,
+ "top": 12,
"transform": [
{
"translateX": 24,
@@ -396,10 +398,10 @@ exports[`Switch render renders disabled on 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
@@ -416,7 +418,7 @@ exports[`Switch render renders off 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -444,6 +446,7 @@ exports[`Switch render renders off 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
focusable={true}
onBlur={[Function]}
@@ -460,7 +463,7 @@ exports[`Switch render renders off 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -514,7 +517,7 @@ exports[`Switch render renders off 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -549,7 +552,7 @@ exports[`Switch render renders off 1`] = `
},
{
"height": 16,
- "top": 12,
+ "top": 16,
"transform": [
{
"translateX": 8,
@@ -594,10 +597,10 @@ exports[`Switch render renders off 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
@@ -614,7 +617,7 @@ exports[`Switch render renders on 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -642,6 +645,7 @@ exports[`Switch render renders on 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
focusable={true}
onBlur={[Function]}
@@ -658,7 +662,7 @@ exports[`Switch render renders on 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -692,7 +696,7 @@ exports[`Switch render renders on 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -727,7 +731,7 @@ exports[`Switch render renders on 1`] = `
},
{
"height": 24,
- "top": 8,
+ "top": 12,
"transform": [
{
"translateX": 24,
@@ -772,10 +776,10 @@ exports[`Switch render renders on 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
@@ -792,7 +796,7 @@ exports[`Switch render renders with checked icon 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -820,6 +824,7 @@ exports[`Switch render renders with checked icon 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
focusable={true}
onBlur={[Function]}
@@ -836,7 +841,7 @@ exports[`Switch render renders with checked icon 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -870,7 +875,7 @@ exports[`Switch render renders with checked icon 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -905,7 +910,7 @@ exports[`Switch render renders with checked icon 1`] = `
},
{
"height": 24,
- "top": 8,
+ "top": 12,
"transform": [
{
"translateX": 24,
@@ -946,7 +951,7 @@ exports[`Switch render renders with checked icon 1`] = `
"height": 16,
"pointerEvents": "none",
"position": "absolute",
- "top": 12,
+ "top": 16,
"width": 16,
},
{
@@ -986,7 +991,7 @@ exports[`Switch render renders with checked icon 1`] = `
style={
[
{
- "color": "rgba(103, 80, 164, 1)",
+ "color": "rgba(33, 0, 93, 1)",
"fontSize": 16,
},
[
@@ -1021,10 +1026,10 @@ exports[`Switch render renders with checked icon 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
@@ -1041,7 +1046,7 @@ exports[`Switch render renders with per-state icons 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"overflow": "visible",
"width": 52,
@@ -1069,6 +1074,7 @@ exports[`Switch render renders with per-state icons 1`] = `
}
}
accessible={true}
+ aria-readonly={false}
collapsable={false}
focusable={true}
onBlur={[Function]}
@@ -1085,7 +1091,7 @@ exports[`Switch render renders with per-state icons 1`] = `
[
{
"alignItems": "center",
- "height": 40,
+ "height": 48,
"justifyContent": "center",
"width": 52,
},
@@ -1119,7 +1125,7 @@ exports[`Switch render renders with per-state icons 1`] = `
"height": 40,
"pointerEvents": "none",
"position": "absolute",
- "top": 0,
+ "top": 4,
"width": 40,
},
{
@@ -1154,7 +1160,7 @@ exports[`Switch render renders with per-state icons 1`] = `
},
{
"height": 24,
- "top": 8,
+ "top": 12,
"transform": [
{
"translateX": 24,
@@ -1195,7 +1201,7 @@ exports[`Switch render renders with per-state icons 1`] = `
"height": 16,
"pointerEvents": "none",
"position": "absolute",
- "top": 12,
+ "top": 16,
"width": 16,
},
{
@@ -1235,7 +1241,7 @@ exports[`Switch render renders with per-state icons 1`] = `
style={
[
{
- "color": "rgba(103, 80, 164, 1)",
+ "color": "rgba(33, 0, 93, 1)",
"fontSize": 16,
},
[
@@ -1270,10 +1276,10 @@ exports[`Switch render renders with per-state icons 1`] = `
"borderColor": "rgba(98, 91, 113, 1)",
"borderRadius": 9999,
"borderWidth": 3,
- "bottom": -1,
+ "bottom": 3,
"left": -5,
"right": -5,
- "top": -1,
+ "top": 3,
},
{
"opacity": 0,
diff --git a/src/index.tsx b/src/index.tsx
index 8863e2fa20..8571269cb7 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -127,7 +127,7 @@ export type { Props as RadioButtonItemProps } from './components/RadioButton/Rad
export type { Props as SearchbarProps } from './components/Searchbar';
export type { Props as SnackbarProps } from './components/Snackbar';
export type { Props as SurfaceProps } from './components/Surface';
-export type { Props as SwitchProps } from './components/Switch/Switch';
+export type { OperableProps as SwitchProps } from './components/Switch/Switch';
export type {
TextInputProps,
TextInputRenderProps,