UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

173 lines 7.37 kB
import { Shade, createComponent } from '@furystack/shades'; import { cssVariableTheme } from '../../services/css-variable-theme.js'; import { ThemeProviderService } from '../../services/theme-provider-service.js'; import { FormContextToken } from '../form.js'; export const Radio = Shade({ customElementName: 'shade-radio', css: { display: 'inline-flex', fontFamily: cssVariableTheme.typography.fontFamily, alignItems: 'center', '& label': { display: 'inline-flex', alignItems: 'center', gap: cssVariableTheme.spacing.sm, cursor: 'pointer', fontSize: cssVariableTheme.typography.fontSize.sm, color: cssVariableTheme.text.primary, userSelect: 'none', webkitUserSelect: 'none', }, '& .radio-control': { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '20px', height: '20px', flexShrink: '0', }, '& input[type="radio"]': { appearance: 'none', webkitAppearance: 'none', width: '20px', height: '20px', margin: '0', border: `2px solid ${cssVariableTheme.text.secondary}`, borderRadius: cssVariableTheme.shape.borderRadius.full, backgroundColor: 'transparent', cursor: 'pointer', transition: `all ${cssVariableTheme.transitions.duration.fast} ${cssVariableTheme.transitions.easing.default}`, outline: 'none', }, '& input[type="radio"]:hover:not(:disabled)': { borderColor: 'var(--radio-color)', }, '& input[type="radio"]:focus-visible': { outline: 'none', boxShadow: cssVariableTheme.action.focusRing, }, '& input[type="radio"]:checked': { borderColor: 'var(--radio-color)', }, '& input[type="radio"]:checked::after': { content: '""', position: 'absolute', left: '5px', top: '5px', width: '10px', height: '10px', borderRadius: cssVariableTheme.shape.borderRadius.full, backgroundColor: 'var(--radio-color)', pointerEvents: 'none', }, '&[data-disabled] label': { color: cssVariableTheme.text.disabled, cursor: 'not-allowed', }, '&[data-disabled] input[type="radio"]': { opacity: cssVariableTheme.action.disabledOpacity, cursor: 'not-allowed', }, // Size: small '&[data-size="small"] label': { fontSize: cssVariableTheme.typography.fontSize.xs, }, '&[data-size="small"] .radio-control': { width: '16px', height: '16px', }, '&[data-size="small"] input[type="radio"]': { width: '16px', height: '16px', }, '&[data-size="small"] input[type="radio"]:checked::after': { left: '4px', top: '4px', width: '8px', height: '8px', }, // Size: large '&[data-size="large"] label': { fontSize: cssVariableTheme.typography.fontSize.md, }, '&[data-size="large"] .radio-control': { width: '24px', height: '24px', }, '&[data-size="large"] input[type="radio"]': { width: '24px', height: '24px', }, '&[data-size="large"] input[type="radio"]:checked::after': { left: '6px', top: '6px', width: '12px', height: '12px', }, }, render: ({ props, injector, useDisposable, useHostProps, useRef }) => { const inputRef = useRef('formInput'); useDisposable('form-registration', () => { const formService = injector.get(FormContextToken); queueMicrotask(() => { if (inputRef.current && formService) { formService.inputs.add(inputRef.current); } // Read group-level overrides from parent RadioGroup const group = inputRef.current?.closest('shade-radio-group'); if (group && inputRef.current) { const groupName = group.getAttribute('data-group-name'); if (groupName) inputRef.current.name = groupName; const groupDisabled = group.hasAttribute('data-disabled'); if (groupDisabled) inputRef.current.disabled = true; const groupValue = group.getAttribute('data-group-value'); if (groupValue !== null) { inputRef.current.checked = props.value === groupValue; } else { const groupDefaultValue = group.getAttribute('data-group-default-value'); if (groupDefaultValue !== null && props.checked === undefined) { inputRef.current.checked = props.value === groupDefaultValue; } } } }); return { [Symbol.dispose]: () => { if (inputRef.current && formService) formService.inputs.delete(inputRef.current); }, }; }); const themeProvider = injector.get(ThemeProviderService); // Read group-level overrides from parent RadioGroup (inputRef is set after first mount) const group = inputRef.current ? inputRef.current.closest('shade-radio-group') : null; const groupName = group?.getAttribute('data-group-name'); const groupDisabled = group?.hasAttribute('data-disabled') ?? false; const groupValue = group?.getAttribute('data-group-value'); const groupDefaultValue = group?.getAttribute('data-group-default-value'); const effectiveName = groupName ?? props.name; const isDisabled = props.disabled || groupDisabled; let isChecked = props.checked; if (groupValue !== undefined && groupValue !== null) { isChecked = props.value === groupValue; } else if (groupDefaultValue !== undefined && groupDefaultValue !== null && isChecked === undefined) { isChecked = props.value === groupDefaultValue; } const color = themeProvider.theme.palette[props.color || 'primary'].main; useHostProps({ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined, 'data-disabled': isDisabled ? '' : undefined, style: { '--radio-color': color }, }); return (createComponent("label", { ...props.labelProps }, createComponent("span", { className: "radio-control" }, createComponent("input", { ref: inputRef, type: "radio", value: props.value, checked: isChecked, disabled: isDisabled, name: effectiveName })), props.labelTitle ? createComponent("span", { className: "radio-label" }, props.labelTitle) : null)); }, }); //# sourceMappingURL=radio.js.map