'use client'; import * as React from 'react'; import { Pressable, type GestureResponderEvent } from 'react-native'; import { useControlled } from '../../hooks/useControlled'; import { useRenderElement } from '../../use-render/useRenderElement'; import { useButton } from '../../internals/use-button/useButton'; import { useFieldControlRegistration } from '../../internals/field/useFieldControlRegistration'; import type { ZestUIComponentProps } from '../../types'; import { createChangeEventDetails, type ZestChangeEventDetails, } from '../../utils/createChangeEventDetails'; import { REASONS } from '../../utils/reasons'; import { SwitchRootContext } from './SwitchRootContext'; /** * Represents the switch itself. * Renders a ``. * * Unlike the web version there is no hidden ``: React Native has no * native form submission, so `name`/`value`/`form` props are omitted. */ export function SwitchRoot(componentProps: SwitchRoot.Props) { const { checked: checkedProp, className, defaultChecked = false, disabled: disabledProp = false, onCheckedChange, readOnly = false, render, required = false, style, ref, ...elementProps } = componentProps; const { fieldDisabled, fieldProps, markChanged, markTouched } = useFieldControlRegistration({ initialValue: defaultChecked ?? checkedProp ?? false, ownsValue: true, }); const disabled = disabledProp || fieldDisabled; const { getButtonProps } = useButton({ disabled }); const [checked, setCheckedState] = useControlled({ controlled: checkedProp, default: defaultChecked, name: 'Switch', state: 'checked', }); const state: SwitchRootState = React.useMemo( () => ({ checked, disabled, readOnly, required, }), [checked, disabled, readOnly, required], ); const element = useRenderElement(Pressable, componentProps, { state, ref, props: [ { onPress(event: GestureResponderEvent) { if (readOnly || disabled) { return; } const nextChecked = !checked; const eventDetails = createChangeEventDetails(REASONS.none, event); onCheckedChange?.(nextChecked, eventDetails); if (eventDetails.isCanceled) { return; } setCheckedState(nextChecked); markChanged(nextChecked); markTouched(nextChecked); }, accessibilityRole: 'switch' as const, accessibilityState: { checked, disabled: disabled || undefined, }, 'aria-readonly': readOnly || undefined, 'aria-required': required || undefined, ...fieldProps, }, elementProps, getButtonProps, ], }); return {element}; } export interface SwitchRootState { /** * Whether the switch is currently active. */ checked: boolean; /** * Whether the component should ignore user interaction. */ disabled: boolean; /** * Whether the user should be unable to activate or deactivate the switch. */ readOnly: boolean; /** * Whether the user must activate the switch before submitting a form. */ required: boolean; } export interface SwitchRootProps extends Omit, 'onPress'> { /** * Whether the switch is currently active. * * To render an uncontrolled switch, use the `defaultChecked` prop instead. */ checked?: boolean | undefined; /** * Whether the switch is initially active. * * To render a controlled switch, use the `checked` prop instead. * @default false */ defaultChecked?: boolean | undefined; /** * Whether the component should ignore user interaction. * @default false */ disabled?: boolean | undefined; /** * Event handler called when the switch is activated or deactivated. */ onCheckedChange?: | ((checked: boolean, eventDetails: SwitchRoot.ChangeEventDetails) => void) | undefined; /** * Whether the user should be unable to activate or deactivate the switch. * @default false */ readOnly?: boolean | undefined; /** * Whether the user must activate the switch before submitting a form. * @default false */ required?: boolean | undefined; } export type SwitchRootChangeEventReason = typeof REASONS.none; export namespace SwitchRoot { export type State = SwitchRootState; export type Props = SwitchRootProps; export type ChangeEventReason = SwitchRootChangeEventReason; export type ChangeEventDetails = ZestChangeEventDetails; }