diff --git a/.changeset/item-selected-disabled.md b/.changeset/item-selected-disabled.md new file mode 100644 index 000000000..02c829672 --- /dev/null +++ b/.changeset/item-selected-disabled.md @@ -0,0 +1,5 @@ +--- +'@cube-dev/ui-kit': patch +--- + +`Item` themes: a disabled item now keeps showing whether it is selected. The `disabled` entry in every `outline`, `outline-2` and `clear` variant used to override `selected` outright, so a disabled segmented control — `RadioGroup type="button"` most visibly — rendered every option identically with no sign of which one was active. Each of those variants gains a `selected & disabled` state that paints the brand-tinted `accent-disabled-surface` chip and its paired label instead of the neutral one, across all six themes (`special` and `current` stay in their own white-alpha / `currentcolor` registers). `type="item"` rows are unchanged — they already keep brand identity in their disabled label and pair selection with a checkmark. diff --git a/.changeset/radio-button-item-props.md b/.changeset/radio-button-item-props.md new file mode 100644 index 000000000..88e8c11b9 --- /dev/null +++ b/.changeset/radio-button-item-props.md @@ -0,0 +1,5 @@ +--- +'@cube-dev/ui-kit': minor +--- + +`Radio` / `Radio.Button`: a button- or tabs-type radio now accepts the full content API of the `Item` it renders, matching `ItemButton`. Newly forwarded: `descriptionPlacement`, `descriptionProps`, `keyboardShortcutProps`, `isLoading`, `loadingSlot`, `highlight`, `highlightCaseSensitive`, `highlightStyles`, `level` and `labelRef`. `description` now has a grid area to land in, so it renders in both `inline` and `block` placement, and container style props (`padding`, `gap`, `fill`, `preset`, …) apply to button-type radios instead of being dropped. A loading button radio is also disabled, so it no longer takes clicks or arrow-key selection — pass an explicit `isDisabled={false}` to opt out. diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 84dcf77ba..7e1c36bc4 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1,3 +1,62 @@ + + + diff --git a/src/components/fields/RadioGroup/Radio.tsx b/src/components/fields/RadioGroup/Radio.tsx index 981e93033..1d98532cb 100644 --- a/src/components/fields/RadioGroup/Radio.tsx +++ b/src/components/fields/RadioGroup/Radio.tsx @@ -1,9 +1,10 @@ import { useFocusableRef } from '@react-spectrum/utils'; import { BaseProps, + CONTAINER_STYLES, + ContainerStyleProps, filterBaseProps, OUTER_STYLES, - OuterStyleProps, tasty, } from '@tenphi/tasty'; import { forwardRef, useMemo, useRef } from 'react'; @@ -38,8 +39,20 @@ const RadioButtonElement = tasty(Item, { preset: 't3m', lineHeight: '1em', flexGrow: 1, - gridTemplate: - '"icon prefix label suffix rightIcon actions" auto / max-content max-content max-content max-content max-content max-content', + // Same areas as `Item`, but every column is `max-content` — a radio button + // is sized by its content, not stretched like a list item. The + // `description` rows have to be repeated here because a plain string would + // replace `Item`'s whole state map and leave the description without a + // grid area to land in. + gridTemplate: { + '': '"icon prefix label suffix rightIcon actions" auto / max-content max-content max-content max-content max-content max-content', + 'description=inline': + '"icon prefix description suffix rightIcon actions" auto / max-content max-content max-content max-content max-content max-content', + 'description=inline & has-label': + '"icon prefix label suffix rightIcon actions" auto "icon prefix description suffix rightIcon actions" auto / max-content max-content max-content max-content max-content max-content', + 'description=block': + '"icon prefix label suffix rightIcon actions" auto "description description description description description description" auto / max-content max-content max-content max-content max-content max-content', + }, placeContent: 'center', shadow: { '': false, @@ -161,11 +174,61 @@ const RadioLabelElement = tasty({ styles: INLINE_LABEL_STYLES, }); +/** + * `Item` props a button-type radio hands straight to the `Item` it renders, so + * `Radio.Button` accepts the same content and presentation API as an + * `ItemButton`. Kept as a runtime list so a new `Item` prop only has to be + * added in one place. + * + * Deliberately absent: + * - `size`, `type`, `theme`, `isSelected`, `isDisabled`, `mods`, `styles` — + * owned by the radio (they come from the prop/context/validation resolution + * below). + * - `shape` — the radius is owned by the button-group layout below, which keeps + * only the outer-side radius on the first/last radio so the group reads as one + * continuous control. Forwarding `shape` would have no effect. + * - `htmlType`, `as`, `insideWrapper`, `showActions` — internal to `Item` / + * `ItemButton`. + * - `actions`, `autoHideActions`, `preserveActionsSpace`, + * `disableActionsFocus` — a button radio renders as a `