@@ -10,11 +10,11 @@ import {
1010 filterBaseProps ,
1111 OUTER_STYLES ,
1212 OuterStyleProps ,
13- Styles ,
1413 tasty ,
1514} from '../../../tasty' ;
1615import { mergeProps } from '../../../utils/react' ;
1716import { useFocus } from '../../../utils/react/interactions' ;
17+ import { CubeItemBaseProps , ItemBase } from '../../content/ItemBase/ItemBase' ;
1818import { INLINE_LABEL_STYLES , useFieldProps , useFormProps } from '../../form' ;
1919import { HiddenInput } from '../../HiddenInput' ;
2020
@@ -49,64 +49,6 @@ const RadioWrapperElement = tasty({
4949 checked : 1 ,
5050 } ,
5151 flexGrow : 1 ,
52-
53- Input : {
54- radius : {
55- '' : 'round' ,
56- button : true ,
57- 'button & solid' : 0 ,
58- 'button & solid & :first-child' : '1r 0 0 1r' ,
59- 'button & solid & :last-child' : '0 1r 1r 0' ,
60- } ,
61- margin : {
62- '' : 'initial' ,
63- 'button & solid' : '-1bw right' ,
64- 'button & solid & :last-child' : 0 ,
65- } ,
66- } ,
67- } ,
68- } ) ;
69-
70- const RadioButtonElement = tasty ( {
71- styles : {
72- display : 'grid' ,
73- flow : 'column' ,
74- placeItems : 'center' ,
75- gap : '.75x' ,
76- fill : {
77- '' : '#white' ,
78- hovered : '#purple-text.04' ,
79- checked : '#white' ,
80- disabled : '#light' ,
81- } ,
82- color : {
83- '' : '#dark.85' ,
84- invalid : '#danger-text' ,
85- checked : '#purple-text' ,
86- disabled : '#dark-04' ,
87- 'disabled & checked' : '#dark-02' ,
88- } ,
89- preset : 't3m' ,
90- border : {
91- '' : '#dark-05' ,
92- checked : '#purple' ,
93- 'invalid & checked' : '#danger-text' ,
94- disabled : '#dark-05' ,
95- 'disabled & checked' : '#dark-03' ,
96- } ,
97- padding : '(.75x - 1bw) (1.25x - 1bw)' ,
98- cursor : 'pointer' ,
99- opacity : {
100- '' : 1 ,
101- disabled : 0.5 ,
102- } ,
103- outline : {
104- '' : '#purple-text.0' ,
105- focused : '1bw #purple-text' ,
106- } ,
107- outlineOffset : 1 ,
108- transition : 'theme' ,
109- whiteSpace : 'nowrap' ,
11052 } ,
11153} ) ;
11254
@@ -161,12 +103,26 @@ const RadioLabelElement = tasty({
161103export interface CubeRadioProps
162104 extends BaseProps ,
163105 AriaRadioProps ,
164- FieldBaseProps ,
106+ Omit < FieldBaseProps , 'tooltip' > ,
165107 OuterStyleProps {
166- inputStyles ?: Styles ;
167108 /* The visual type of the radio button */
168109 type ?: 'button' | 'radio' ;
110+ buttonType ?: Exclude < CubeItemBaseProps [ 'type' ] , 'secondary' > ;
169111 value ?: string ;
112+ /* Whether the radio is invalid */
113+ isInvalid ?: boolean ;
114+ /* Size of the button (for button type only) */
115+ size ?: CubeItemBaseProps [ 'size' ] ;
116+ /* Icon to display (for button type only) */
117+ icon ?: CubeItemBaseProps [ 'icon' ] ;
118+ /* Icon to display on the right (for button type only) */
119+ rightIcon ?: CubeItemBaseProps [ 'rightIcon' ] ;
120+ /* Description text (for button type only) */
121+ description ?: CubeItemBaseProps [ 'description' ] ;
122+ /* Tooltip configuration (for button type only) */
123+ tooltip ?: CubeItemBaseProps [ 'tooltip' ] ;
124+ /* Keyboard shortcut (for button type only) */
125+ hotkeys ?: CubeItemBaseProps [ 'hotkeys' ] ;
170126}
171127
172128function Radio ( props : CubeRadioProps , ref ) {
@@ -177,27 +133,29 @@ function Radio(props: CubeRadioProps, ref) {
177133 let {
178134 qa,
179135 isDisabled,
180- validationState ,
136+ isInvalid ,
181137 children,
182138 label,
183139 autoFocus,
184140 labelStyles,
185141 labelProps,
186- inputStyles,
187- type = 'radio' ,
142+ type,
143+ buttonType,
144+ size,
145+ icon,
146+ rightIcon,
147+ description,
148+ tooltip,
149+ hotkeys,
188150 'aria-label' : ariaLabel ,
189151 form,
190152 ...otherProps
191153 } = props ;
192154
193- let isButton = type === 'button' ;
194-
195155 label = label || children ;
196156
197157 let styles = extractStyles ( otherProps , OUTER_STYLES ) ;
198158
199- const RadioElement = isButton ? RadioButtonElement : RadioNormalElement ;
200-
201159 labelStyles = {
202160 ...INLINE_LABEL_STYLES ,
203161 ...labelStyles ,
@@ -207,14 +165,58 @@ function Radio(props: CubeRadioProps, ref) {
207165
208166 let state = radioGroupProps && radioGroupProps . state ;
209167 let name = radioGroupProps && radioGroupProps . name ;
210- let isSolid = ( radioGroupProps && radioGroupProps . isSolid ) || false ;
168+ let contextSize = radioGroupProps ?. size ;
169+ let contextButtonType = radioGroupProps ?. buttonType ;
170+ let contextType = radioGroupProps ?. type ;
171+ let contextIsDisabled = radioGroupProps ?. isDisabled ;
211172
212173 if ( ! state ) {
213174 throw new Error ( 'CubeUI: The Radio button is used outside the RadioGroup.' ) ;
214175 }
215176
216- let { isFocused, focusProps } = useFocus ( { isDisabled } , true ) ;
217- let { hoverProps, isHovered } = useHover ( { isDisabled } ) ;
177+ // Determine effective type from props or context
178+ let effectiveType = type ?? contextType ?? 'radio' ;
179+ let isButton = effectiveType === 'button' || effectiveType === 'tabs' ;
180+
181+ // Determine effective size with priority: prop > context > default
182+ let effectiveSize = size ?? contextSize ?? 'medium' ;
183+
184+ // Apply size mapping for tabs mode button radios
185+ if ( effectiveType === 'tabs' && isButton ) {
186+ if ( effectiveSize === 'small' || effectiveSize === 'medium' ) {
187+ effectiveSize = 'xsmall' ;
188+ } else if ( effectiveSize === 'large' ) {
189+ effectiveSize = 'medium' ;
190+ } else if ( effectiveSize === 'xlarge' ) {
191+ effectiveSize = 'large' ;
192+ }
193+ // 'xsmall' stays 'xsmall', 'inline' stays 'inline'
194+ }
195+
196+ // Determine effective button type
197+ // In tabs mode, always use 'neutral' and ignore buttonType prop
198+ let effectiveButtonType ;
199+ if ( effectiveType === 'tabs' ) {
200+ effectiveButtonType = 'neutral' ; // Force neutral for tabs, ignore buttonType
201+ } else {
202+ const baseButtonType = buttonType ?? contextButtonType ?? 'outline' ;
203+ // When buttonType is 'primary', use 'secondary' for non-selected and 'primary' for selected
204+ if ( baseButtonType === 'primary' ) {
205+ effectiveButtonType =
206+ state . selectedValue === props . value ? 'primary' : 'secondary' ;
207+ } else {
208+ effectiveButtonType = baseButtonType ;
209+ }
210+ }
211+
212+ // Use context isDisabled if prop isDisabled is not explicitly set
213+ let effectiveIsDisabled = isDisabled ?? contextIsDisabled ?? false ;
214+
215+ let { isFocused, focusProps } = useFocus (
216+ { isDisabled : effectiveIsDisabled } ,
217+ true ,
218+ ) ;
219+ let { hoverProps, isHovered } = useHover ( { isDisabled : effectiveIsDisabled } ) ;
218220
219221 let inputRef = useRef ( null ) ;
220222 let domRef = useFocusableRef ( ref , inputRef ) ;
@@ -227,7 +229,7 @@ function Radio(props: CubeRadioProps, ref) {
227229 {
228230 name,
229231 ...props ,
230- isDisabled,
232+ isDisabled : effectiveIsDisabled ,
231233 } ,
232234 state ,
233235 inputRef ,
@@ -236,25 +238,66 @@ function Radio(props: CubeRadioProps, ref) {
236238 const mods = useMemo (
237239 ( ) => ( {
238240 checked : isRadioSelected ,
239- invalid : validationState === 'invalid' ,
240- valid : validationState === 'valid' ,
241+ invalid : ! ! isInvalid ,
241242 disabled : isRadioDisabled ,
242243 hovered : isHovered ,
243244 button : isButton ,
244245 focused : isFocused ,
245- solid : isSolid ,
246+ tabs : effectiveType === 'tabs' ,
246247 } ) ,
247248 [
248249 isRadioSelected ,
249- validationState ,
250+ isInvalid ,
250251 isRadioDisabled ,
251252 isHovered ,
252253 isButton ,
253254 isFocused ,
254- isSolid ,
255+ effectiveType ,
255256 ] ,
256257 ) ;
257258
259+ // Render button type using ItemBase
260+ if ( isButton ) {
261+ return (
262+ < ItemBase
263+ ref = { domRef }
264+ as = "label"
265+ qa = { qa || 'Radio' }
266+ type = { effectiveButtonType }
267+ theme = { isInvalid ? 'danger' : 'default' }
268+ size = { effectiveSize }
269+ icon = { icon }
270+ rightIcon = { rightIcon }
271+ description = { description }
272+ tooltip = { tooltip }
273+ hotkeys = { hotkeys }
274+ isSelected = { isRadioSelected }
275+ isDisabled = { isRadioDisabled }
276+ mods = { mods }
277+ preset = "t3m"
278+ styles = { {
279+ preset : 't3m' ,
280+ lineHeight : '1fs' ,
281+ ...( isRadioSelected && effectiveType === 'tabs'
282+ ? { fill : '#white' , shadow : '0 0 .5x #shadow' }
283+ : { } ) ,
284+ ...styles ,
285+ } }
286+ { ...mergeProps ( hoverProps , focusProps ) }
287+ >
288+ < HiddenInput
289+ data-qa = { qa || 'Radio' }
290+ aria-label = { ariaLabel }
291+ { ...inputProps }
292+ ref = { inputRef }
293+ mods = { { button : isButton , disabled : isRadioDisabled } }
294+ />
295+ { label }
296+ </ ItemBase >
297+ ) ;
298+ }
299+
300+ // Render classic radio type
258301 return (
259302 < RadioWrapperElement
260303 styles = { styles }
@@ -270,15 +313,10 @@ function Radio(props: CubeRadioProps, ref) {
270313 ref = { inputRef }
271314 mods = { { button : isButton } }
272315 />
273- < RadioElement
274- data-element = "Input"
275- mods = { mods }
276- data-type = { type }
277- styles = { inputStyles }
278- >
279- { ! isButton ? RadioCircleElement : children }
280- </ RadioElement >
281- { label && ! isButton && (
316+ < RadioNormalElement data-element = "Input" mods = { mods } data-type = { type } >
317+ { RadioCircleElement }
318+ </ RadioNormalElement >
319+ { label && (
282320 < RadioLabelElement
283321 mods = { mods }
284322 styles = { labelStyles }
@@ -308,20 +346,20 @@ const _Radio = forwardRef(Radio);
308346 */
309347const _RadioButton = forwardRef ( RadioButton ) ;
310348
311- const ButtonGroup = tasty ( RadioGroup , {
312- isSolid : true ,
349+ const Tabs = tasty ( RadioGroup , {
350+ type : 'tabs' ,
313351} ) ;
314352
315353const __Radio = Object . assign (
316354 _Radio as typeof _Radio & {
317355 Group : typeof RadioGroup ;
318356 Button : typeof _RadioButton ;
319- ButtonGroup : typeof ButtonGroup ;
357+ Tabs : typeof Tabs ;
320358 } ,
321359 {
322360 Group : RadioGroup ,
323361 Button : _RadioButton ,
324- ButtonGroup ,
362+ Tabs ,
325363 } ,
326364) ;
327365
0 commit comments