From 414facc9c2419ee67f1b818f534ea7c353d75493 Mon Sep 17 00:00:00 2001 From: Nandor_Czegledi Date: Wed, 25 Feb 2026 11:48:15 +0100 Subject: [PATCH] feat(ui-radio-input): migrate toggle variant to the new theming system --- docs/guides/upgrade-guide.md | 48 ++++++++++++++ .../ui-radio-input/src/RadioInput/styles.ts | 64 ++++++++++++++++--- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/docs/guides/upgrade-guide.md b/docs/guides/upgrade-guide.md index 54fde026e2..ec970d0a36 100644 --- a/docs/guides/upgrade-guide.md +++ b/docs/guides/upgrade-guide.md @@ -491,6 +491,54 @@ type: embed - Setting `readonly` does not set the low level `` to disabled, but to `readonly`. This also means that the input is still focusable when `readonly` - Its DOM structure has been significantly simplified +```js +--- +type: embed +--- + + +``` + ### RadioInputGroup `error` or `success` messages are no longer displayed when the component is `readOnly` or `disabled`. diff --git a/packages/ui-radio-input/src/RadioInput/styles.ts b/packages/ui-radio-input/src/RadioInput/styles.ts index 4726457d53..9c2f9e50d6 100644 --- a/packages/ui-radio-input/src/RadioInput/styles.ts +++ b/packages/ui-radio-input/src/RadioInput/styles.ts @@ -24,6 +24,7 @@ import type { RadioInputProps, RadioInputStyle } from './props' import type { NewComponentTypes, SharedTokens } from '@instructure/ui-themes' +import { boxShadowObjectsToCSSString } from '@instructure/ui-themes' import { calcFocusOutlineStyles } from '@instructure/emotion' type StyleParams = { @@ -51,8 +52,7 @@ const generateStyle = ( params: StyleParams, sharedTokens: SharedTokens ): RadioInputStyle => { - const { disabled, inline, hovered, size, readOnly } = params - const variant = 'simple' // TODO read from params when the toggle variant is ready + const { disabled, inline, hovered, size, readOnly, variant, context } = params // 4*2 states: base, hover, disabled, readonly X none/selected const insetSizes = { @@ -114,6 +114,17 @@ const generateStyle = ( const inputColors = getInputColors() + const toggleContextBackgrounds: Record< + NonNullable, + string + > = { + success: componentTheme.toggleBackgroundSuccess, + warning: componentTheme.toggleBackgroundWarning, + danger: componentTheme.toggleBackgroundDanger, + off: componentTheme.toggleBackgroundOff + } + const toggleBackground = toggleContextBackgrounds[context ?? 'success'] + const focusOutline = calcFocusOutlineStyles(sharedTokens.focusOutline) focusOutline.transition += ', box-shadow 0.2s' @@ -157,6 +168,23 @@ const generateStyle = ( width: componentTheme.controlSizeLg, height: componentTheme.controlSizeLg } + }, + toggle: { + base: { + all: 'initial', + boxSizing: 'border-box', + // Visually hidden but accessible (screen reader, focus, keyboard) + position: 'absolute', + opacity: 0.0001 /* selenium cannot find fully transparent elements */, + width: '1px', + height: '1px', + overflow: 'hidden', + margin: 0, + padding: 0 + }, + small: {}, + medium: {}, + large: {} } } @@ -175,9 +203,10 @@ const generateStyle = ( fontSize: componentTheme.fontSizeLg, lineHeight: componentTheme.lineHeightLg } - } - /*toggle: { + }, + toggle: { base: { + flex: '0 0 auto', position: 'relative', zIndex: 1, textTransform: 'uppercase', @@ -187,18 +216,33 @@ const generateStyle = ( lineHeight: 1, display: 'flex', alignItems: 'center', + justifyContent: 'center', minWidth: '0.0625rem', - - [getInputStateSelector('checked')]: { - color: componentTheme.toggleHandleText + borderRadius: componentTheme.toggleBorderRadius, + borderWidth: componentTheme.toggleBorderWidth, + borderStyle: 'solid', + color: disabled ? componentTheme.labelDisabledColor : toggleBackground, + borderColor: disabled + ? componentTheme.borderDisabledColor + : toggleBackground, + background: 'transparent', + boxShadow: boxShadowObjectsToCSSString(componentTheme.toggleShadow), + 'input:checked + &': { + color: disabled + ? componentTheme.labelDisabledColor + : componentTheme.toggleHandleText, + background: disabled + ? componentTheme.backgroundDisabledColor + : toggleBackground }, - [getInputStateSelector('focus')]: { + 'input:focus-visible + &': { textDecoration: 'underline' } }, small: { fontSize: componentTheme.toggleSmallFontSize, height: componentTheme.toggleSmallHeight, + padding: '0 0.5rem', svg: { fontSize: `calc(${componentTheme.toggleSmallFontSize} + 0.375rem)` } @@ -206,6 +250,7 @@ const generateStyle = ( medium: { fontSize: componentTheme.toggleMediumFontSize, height: componentTheme.toggleMediumHeight, + padding: '0 0.875rem', svg: { fontSize: `calc(${componentTheme.toggleMediumFontSize} + 0.375rem)` } @@ -213,11 +258,12 @@ const generateStyle = ( large: { fontSize: componentTheme.toggleLargeFontSize, height: componentTheme.toggleLargeHeight, + padding: '0 1rem', svg: { fontSize: `calc(${componentTheme.toggleLargeFontSize} + 0.375rem)` } } - }*/ + } } return {