import { createSignal, createContext, useContext, Show, children } from "@solidrt/core" import type { LayoutProps } from "@solidrt/core" import { createPress } from "./press" import { theme } from "./theme" import { densityScale } from "./density" import { typeStyle } from "./typography" import type { StyleProps } from "./types" // Shared selection state for a group. Created and consumed within this module, so // RadioGroup/Radio are a self-contained pair, not a cross-component dependency. type RadioContextValue = { value: () => unknown select: (value: unknown) => void disabled: () => boolean } let RadioContext = createContext() export interface RadioGroupProps { // Controlled selected value. If omitted, the group is uncontrolled. value?: unknown defaultValue?: unknown onChange?: (value: unknown) => void disabled?: boolean layout?: LayoutProps children?: any } // Owns the selection for its Radio children and shares it through context. export function RadioGroup(props: RadioGroupProps) { let [internal, setInternal] = createSignal(props.defaultValue) let value = () => (props.value !== undefined ? props.value : internal()) let select = (v: unknown) => { if (props.value === undefined) setInternal(() => v) props.onChange?.(v) } let ctx: RadioContextValue = { value, select, disabled: () => !!props.disabled, } return ( {props.children} ) } export interface RadioProps { // This option's value; selecting it makes it the group's value. value: unknown disabled?: boolean layout?: LayoutProps style?: StyleProps // A string/number renders as a themed label beside the ring; anything else // renders as-is. children?: any } const RING = 20 // A single option in a RadioGroup: a ring with an inner dot when selected. export function Radio(props: RadioProps) { // useContext throws ContextNotFoundError if a Radio is used outside a // RadioGroup (default-less context), so ctx is always present here. let ctx = useContext(RadioContext) let selected = () => ctx.value() === props.value let disabled = () => props.disabled || ctx.disabled() let ringColor = () => (selected() ? theme.color.primary : theme.color.border) // Resolved once via children(): the typeof probe and the mount sites must // share one build - reading the raw getter again would orphan native nodes. let resolved = children(() => props.children) let isText = () => typeof resolved() === "string" || typeof resolved() === "number" let ring = () => Math.round(RING * densityScale()) // Inner dot inset as a fraction of the ring, so it scales with the density. let inset = () => ring() * 0.3 // Theme-level per-component overrides merged under the instance style. let styled = () => ({ ...theme.components.radio, ...props.style }) let press = createPress({ onPress: () => ctx.select(props.value) }) return ( {resolved()} 0}> ) }