@tamagui/radio-group 1.96.0 → 1.97.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/RadioGroup.js +23 -248
- package/dist/cjs/RadioGroup.js.map +1 -1
- package/dist/cjs/RadioGroup.native.js +25 -336
- package/dist/cjs/RadioGroup.native.js.map +2 -2
- package/dist/cjs/RadioGroupStyledContext.js +25 -0
- package/dist/cjs/RadioGroupStyledContext.js.map +6 -0
- package/dist/cjs/RadioGroupStyledContext.native.js +29 -0
- package/dist/cjs/RadioGroupStyledContext.native.js.map +6 -0
- package/dist/cjs/createRadioGroup.js +107 -0
- package/dist/cjs/createRadioGroup.js.map +6 -0
- package/dist/cjs/createRadioGroup.native.js +171 -0
- package/dist/cjs/createRadioGroup.native.js.map +6 -0
- package/dist/cjs/index.js +15 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +19 -2
- package/dist/cjs/index.native.js.map +2 -2
- package/dist/esm/RadioGroup.js +20 -248
- package/dist/esm/RadioGroup.js.map +1 -1
- package/dist/esm/RadioGroup.mjs +19 -240
- package/dist/esm/RadioGroup.native.js +20 -334
- package/dist/esm/RadioGroup.native.js.map +2 -2
- package/dist/esm/RadioGroupStyledContext.js +9 -0
- package/dist/esm/RadioGroupStyledContext.js.map +6 -0
- package/dist/esm/RadioGroupStyledContext.mjs +6 -0
- package/dist/esm/RadioGroupStyledContext.native.js +9 -0
- package/dist/esm/RadioGroupStyledContext.native.js.map +6 -0
- package/dist/esm/createRadioGroup.js +104 -0
- package/dist/esm/createRadioGroup.js.map +6 -0
- package/dist/esm/createRadioGroup.mjs +131 -0
- package/dist/esm/createRadioGroup.native.js +155 -0
- package/dist/esm/createRadioGroup.native.js.map +6 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +11 -1
- package/dist/esm/index.native.js +12 -0
- package/dist/esm/index.native.js.map +2 -2
- package/dist/jsx/RadioGroup.js +20 -248
- package/dist/jsx/RadioGroup.js.map +1 -1
- package/dist/jsx/RadioGroup.mjs +19 -240
- package/dist/jsx/RadioGroup.native.js +20 -334
- package/dist/jsx/RadioGroup.native.js.map +2 -2
- package/dist/jsx/RadioGroupStyledContext.js +9 -0
- package/dist/jsx/RadioGroupStyledContext.js.map +6 -0
- package/dist/jsx/RadioGroupStyledContext.mjs +6 -0
- package/dist/jsx/RadioGroupStyledContext.native.js +9 -0
- package/dist/jsx/RadioGroupStyledContext.native.js.map +6 -0
- package/dist/jsx/createRadioGroup.js +104 -0
- package/dist/jsx/createRadioGroup.js.map +6 -0
- package/dist/jsx/createRadioGroup.mjs +131 -0
- package/dist/jsx/createRadioGroup.native.js +155 -0
- package/dist/jsx/createRadioGroup.native.js.map +6 -0
- package/dist/jsx/index.js +16 -0
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +11 -1
- package/dist/jsx/index.native.js +12 -0
- package/dist/jsx/index.native.js.map +2 -2
- package/package.json +14 -14
- package/src/RadioGroup.tsx +22 -432
- package/src/RadioGroupStyledContext.tsx +7 -0
- package/src/createRadioGroup.tsx +201 -0
- package/src/index.ts +15 -0
- package/types/RadioGroup.d.ts +7 -224
- package/types/RadioGroup.d.ts.map +1 -1
- package/types/RadioGroupStyledContext.d.ts +6 -0
- package/types/RadioGroupStyledContext.d.ts.map +1 -0
- package/types/createRadioGroup.d.ts +979 -0
- package/types/createRadioGroup.d.ts.map +1 -0
- package/types/index.d.ts +949 -0
- package/types/index.d.ts.map +1 -1
package/src/RadioGroup.tsx
CHANGED
|
@@ -1,120 +1,10 @@
|
|
|
1
|
-
// forked from Radix UI
|
|
2
|
-
// https://github.com/radix-ui/primitives/blob/main/packages/react/radio-group/src/RadioGroup.tsx
|
|
3
|
-
|
|
4
|
-
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
5
|
-
import { isWeb } from '@tamagui/constants'
|
|
6
|
-
import type { GetProps } from '@tamagui/core'
|
|
7
1
|
import { getVariableValue, styled } from '@tamagui/core'
|
|
8
|
-
import type { Scope } from '@tamagui/create-context'
|
|
9
|
-
import { createContextScope } from '@tamagui/create-context'
|
|
10
|
-
import { registerFocusable } from '@tamagui/focusable'
|
|
11
2
|
import { getSize } from '@tamagui/get-token'
|
|
12
|
-
import { composeEventHandlers, withStaticProperties } from '@tamagui/helpers'
|
|
13
|
-
import { useLabelContext } from '@tamagui/label'
|
|
14
|
-
import { RovingFocusGroup } from '@tamagui/roving-focus'
|
|
15
3
|
import { ThemeableStack } from '@tamagui/stacks'
|
|
16
|
-
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
17
|
-
import { usePrevious } from '@tamagui/use-previous'
|
|
18
|
-
import * as React from 'react'
|
|
19
|
-
import type { View } from 'react-native'
|
|
20
|
-
|
|
21
|
-
const RADIO_GROUP_NAME = 'RadioGroup'
|
|
22
|
-
|
|
23
|
-
const ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
|
|
24
|
-
|
|
25
|
-
const [createRadioGroupContext, createRadioGroupScope] =
|
|
26
|
-
createContextScope(RADIO_GROUP_NAME)
|
|
27
|
-
type RadioGroupContextValue = {
|
|
28
|
-
value?: string
|
|
29
|
-
disabled?: boolean
|
|
30
|
-
required?: boolean
|
|
31
|
-
onChange?: (value: string) => void
|
|
32
|
-
name?: string
|
|
33
|
-
native?: boolean
|
|
34
|
-
accentColor?: string
|
|
35
|
-
}
|
|
36
|
-
const [RadioGroupProvider, useRadioGroupContext] =
|
|
37
|
-
createRadioGroupContext<RadioGroupContextValue>(RADIO_GROUP_NAME)
|
|
38
|
-
|
|
39
|
-
const getState = (checked: boolean) => {
|
|
40
|
-
return checked ? 'checked' : 'unchecked'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/* -------------------------------------------------------------------------
|
|
44
|
-
* RadioIndicator
|
|
45
|
-
* ------------------------------------------------------------------------ */
|
|
46
|
-
|
|
47
|
-
const RADIO_GROUP_INDICATOR_NAME = 'RadioGroupIndicator'
|
|
48
|
-
|
|
49
|
-
const RadioIndicatorFrame = styled(ThemeableStack, {
|
|
50
|
-
name: RADIO_GROUP_INDICATOR_NAME,
|
|
51
|
-
|
|
52
|
-
variants: {
|
|
53
|
-
unstyled: {
|
|
54
|
-
false: {
|
|
55
|
-
width: '33%',
|
|
56
|
-
height: '33%',
|
|
57
|
-
borderRadius: 1000,
|
|
58
|
-
backgroundColor: '$color',
|
|
59
|
-
pressTheme: true,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
} as const,
|
|
63
|
-
|
|
64
|
-
defaultVariants: {
|
|
65
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1' ? true : false,
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
type RadioIndicatorProps = GetProps<typeof RadioIndicatorFrame> & {
|
|
70
|
-
forceMount?: boolean
|
|
71
|
-
unstyled?: boolean
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
type RadioIndicatorElement = TamaguiElement
|
|
75
|
-
|
|
76
|
-
const RadioIndicator = RadioIndicatorFrame.extractable(
|
|
77
|
-
React.forwardRef<RadioIndicatorElement, RadioIndicatorProps>(
|
|
78
|
-
(props: ScopedRadioGroupItemProps<RadioIndicatorProps>, forwardedRef) => {
|
|
79
|
-
const { __scopeRadioGroupItem, forceMount, disabled, ...indicatorProps } = props
|
|
80
|
-
const { checked } = useRadioGroupItemContext(
|
|
81
|
-
RADIO_GROUP_INDICATOR_NAME,
|
|
82
|
-
__scopeRadioGroupItem
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
if (forceMount || checked) {
|
|
86
|
-
return (
|
|
87
|
-
<RadioIndicatorFrame
|
|
88
|
-
data-state={getState(checked)}
|
|
89
|
-
data-disabled={disabled ? '' : undefined}
|
|
90
|
-
{...indicatorProps}
|
|
91
|
-
ref={forwardedRef}
|
|
92
|
-
/>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return null
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
RadioIndicator.displayName = RADIO_GROUP_INDICATOR_NAME
|
|
102
|
-
|
|
103
|
-
/* -------------------------------------------------------------------------
|
|
104
|
-
* RadioGroupItem
|
|
105
|
-
* ------------------------------------------------------------------------ */
|
|
106
4
|
|
|
107
5
|
const RADIO_GROUP_ITEM_NAME = 'RadioGroupItem'
|
|
108
6
|
|
|
109
|
-
|
|
110
|
-
checked: boolean
|
|
111
|
-
disabled?: boolean
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const [RadioGroupItemProvider, useRadioGroupItemContext] =
|
|
115
|
-
createRadioGroupContext<RadioGroupItemContextValue>(RADIO_GROUP_NAME)
|
|
116
|
-
|
|
117
|
-
const RadioGroupItemFrame = styled(ThemeableStack, {
|
|
7
|
+
export const RadioGroupItemFrame = styled(ThemeableStack, {
|
|
118
8
|
name: RADIO_GROUP_ITEM_NAME,
|
|
119
9
|
tag: 'button',
|
|
120
10
|
|
|
@@ -189,260 +79,35 @@ const RadioGroupItemFrame = styled(ThemeableStack, {
|
|
|
189
79
|
} as const,
|
|
190
80
|
|
|
191
81
|
defaultVariants: {
|
|
192
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1'
|
|
82
|
+
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
193
83
|
},
|
|
194
84
|
})
|
|
195
85
|
|
|
196
|
-
|
|
197
|
-
value: string
|
|
198
|
-
id?: string
|
|
199
|
-
labelledBy?: string
|
|
200
|
-
disabled?: boolean
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
type RadioGroupItemElement = HTMLButtonElement
|
|
204
|
-
|
|
205
|
-
type ScopedRadioGroupItemProps<P> = P & {
|
|
206
|
-
__scopeRadioGroupItem?: Scope
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const RadioGroupItem = RadioGroupItemFrame.extractable(
|
|
210
|
-
React.forwardRef<RadioGroupItemElement, RadioGroupItemProps>(
|
|
211
|
-
(props: ScopedProps<RadioGroupItemProps>, forwardedRef) => {
|
|
212
|
-
const {
|
|
213
|
-
__scopeRadioGroup,
|
|
214
|
-
value,
|
|
215
|
-
labelledBy: ariaLabelledby,
|
|
216
|
-
disabled: itemDisabled,
|
|
217
|
-
...itemProps
|
|
218
|
-
} = props
|
|
219
|
-
const {
|
|
220
|
-
value: groupValue,
|
|
221
|
-
disabled,
|
|
222
|
-
required,
|
|
223
|
-
onChange,
|
|
224
|
-
name,
|
|
225
|
-
native,
|
|
226
|
-
accentColor,
|
|
227
|
-
} = useRadioGroupContext(RADIO_GROUP_ITEM_NAME, __scopeRadioGroup)
|
|
228
|
-
const [button, setButton] = React.useState<HTMLButtonElement | null>(null)
|
|
229
|
-
const hasConsumerStoppedPropagationRef = React.useRef(false)
|
|
230
|
-
const ref = React.useRef<HTMLButtonElement>(null)
|
|
231
|
-
const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node), ref)
|
|
232
|
-
const isArrowKeyPressedRef = React.useRef(false)
|
|
233
|
-
|
|
234
|
-
const isFormControl = isWeb
|
|
235
|
-
? button
|
|
236
|
-
? Boolean(button.closest('form'))
|
|
237
|
-
: true
|
|
238
|
-
: false
|
|
239
|
-
|
|
240
|
-
const checked = groupValue === value
|
|
241
|
-
|
|
242
|
-
const labelId = useLabelContext(button)
|
|
243
|
-
const labelledBy = ariaLabelledby || labelId
|
|
244
|
-
|
|
245
|
-
React.useEffect(() => {
|
|
246
|
-
if (isWeb) {
|
|
247
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
248
|
-
if (ARROW_KEYS.includes(event.key)) {
|
|
249
|
-
isArrowKeyPressedRef.current = true
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const handleKeyUp = () => {
|
|
253
|
-
isArrowKeyPressedRef.current = false
|
|
254
|
-
}
|
|
255
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
256
|
-
document.addEventListener('keyup', handleKeyUp)
|
|
257
|
-
return () => {
|
|
258
|
-
document.removeEventListener('keydown', handleKeyDown)
|
|
259
|
-
document.removeEventListener('keyup', handleKeyUp)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}, [])
|
|
263
|
-
|
|
264
|
-
if (process.env.TAMAGUI_TARGET === 'native') {
|
|
265
|
-
React.useEffect(() => {
|
|
266
|
-
if (!props.id) return
|
|
267
|
-
if (disabled) return
|
|
268
|
-
|
|
269
|
-
return registerFocusable(props.id, {
|
|
270
|
-
focusAndSelect: () => {
|
|
271
|
-
onChange?.(value)
|
|
272
|
-
},
|
|
273
|
-
focus: () => {},
|
|
274
|
-
})
|
|
275
|
-
}, [props.id, value, disabled])
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const isDisabled = disabled || itemDisabled
|
|
279
|
-
|
|
280
|
-
return (
|
|
281
|
-
<RadioGroupItemProvider checked={checked} scope={__scopeRadioGroup}>
|
|
282
|
-
{isWeb && native ? (
|
|
283
|
-
<BubbleInput
|
|
284
|
-
control={button}
|
|
285
|
-
bubbles={!hasConsumerStoppedPropagationRef.current}
|
|
286
|
-
name={name}
|
|
287
|
-
value={value}
|
|
288
|
-
checked={checked}
|
|
289
|
-
required={required}
|
|
290
|
-
disabled={isDisabled}
|
|
291
|
-
id={props.id}
|
|
292
|
-
accentColor={accentColor}
|
|
293
|
-
/>
|
|
294
|
-
) : (
|
|
295
|
-
<>
|
|
296
|
-
<RovingFocusGroup.Item
|
|
297
|
-
__scopeRovingFocusGroup={RADIO_GROUP_NAME}
|
|
298
|
-
asChild="except-style"
|
|
299
|
-
focusable={!isDisabled}
|
|
300
|
-
active={checked}
|
|
301
|
-
>
|
|
302
|
-
<RadioGroupItemFrame
|
|
303
|
-
// theme={checked ? 'active' : undefined}
|
|
304
|
-
data-state={getState(checked)}
|
|
305
|
-
data-disabled={isDisabled ? '' : undefined}
|
|
306
|
-
role="radio"
|
|
307
|
-
aria-labelledby={labelledBy}
|
|
308
|
-
aria-checked={checked}
|
|
309
|
-
aria-required={required}
|
|
310
|
-
disabled={isDisabled}
|
|
311
|
-
ref={composedRefs}
|
|
312
|
-
{...(isWeb && {
|
|
313
|
-
type: 'button',
|
|
314
|
-
value: value,
|
|
315
|
-
})}
|
|
316
|
-
// allow them to override all but the handlers that already compose:
|
|
317
|
-
{...itemProps}
|
|
318
|
-
onPress={composeEventHandlers(props.onPress as any, (event) => {
|
|
319
|
-
if (!checked) {
|
|
320
|
-
onChange?.(value)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (isFormControl) {
|
|
324
|
-
hasConsumerStoppedPropagationRef.current =
|
|
325
|
-
event.isPropagationStopped()
|
|
326
|
-
// if radio is in a form, stop propagation from the button so that we only propagate
|
|
327
|
-
// one click event (from the input). We propagate changes from an input so that native
|
|
328
|
-
// form validation works and form events reflect radio updates.
|
|
329
|
-
if (!hasConsumerStoppedPropagationRef.current)
|
|
330
|
-
event.stopPropagation()
|
|
331
|
-
}
|
|
332
|
-
})}
|
|
333
|
-
{...(isWeb && {
|
|
334
|
-
onKeyDown: composeEventHandlers(
|
|
335
|
-
(props as React.HTMLProps<HTMLButtonElement>).onKeyDown,
|
|
336
|
-
(event) => {
|
|
337
|
-
// According to WAI ARIA, Checkboxes don't activate on enter keypress
|
|
338
|
-
if (event.key === 'Enter') event.preventDefault()
|
|
339
|
-
}
|
|
340
|
-
),
|
|
341
|
-
onFocus: composeEventHandlers(itemProps.onFocus, () => {
|
|
342
|
-
/**
|
|
343
|
-
* Our `RovingFocusGroup` will focus the radio when navigating with arrow keys
|
|
344
|
-
* and we need to "check" it in that case. We click it to "check" it (instead
|
|
345
|
-
* of updating `context.value`) so that the radio change event fires.
|
|
346
|
-
*/
|
|
347
|
-
if (isArrowKeyPressedRef.current) {
|
|
348
|
-
;(ref.current as HTMLButtonElement)?.click()
|
|
349
|
-
}
|
|
350
|
-
}),
|
|
351
|
-
})}
|
|
352
|
-
/>
|
|
353
|
-
</RovingFocusGroup.Item>
|
|
354
|
-
{isFormControl && (
|
|
355
|
-
<BubbleInput
|
|
356
|
-
isHidden
|
|
357
|
-
control={button}
|
|
358
|
-
bubbles={!hasConsumerStoppedPropagationRef.current}
|
|
359
|
-
name={name}
|
|
360
|
-
value={value}
|
|
361
|
-
checked={checked}
|
|
362
|
-
required={required}
|
|
363
|
-
disabled={isDisabled}
|
|
364
|
-
/>
|
|
365
|
-
)}
|
|
366
|
-
</>
|
|
367
|
-
)}
|
|
368
|
-
</RadioGroupItemProvider>
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
/* -------------------------------------------------------------------------
|
|
375
|
-
* BubbleInput
|
|
376
|
-
* ------------------------------------------------------------------------ */
|
|
377
|
-
|
|
378
|
-
interface BubbleInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'checked'> {
|
|
379
|
-
checked: boolean
|
|
380
|
-
control: HTMLElement | null
|
|
381
|
-
bubbles: boolean
|
|
382
|
-
isHidden?: boolean
|
|
383
|
-
accentColor?: string
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const BubbleInput = (props: BubbleInputProps) => {
|
|
387
|
-
const { checked, bubbles = true, control, isHidden, accentColor, ...inputProps } = props
|
|
388
|
-
const ref = React.useRef<HTMLInputElement>(null)
|
|
389
|
-
const prevChecked = usePrevious(checked)
|
|
390
|
-
|
|
391
|
-
// Bubble checked change to parents (e.g form change event)
|
|
392
|
-
React.useEffect(() => {
|
|
393
|
-
const input = ref.current!
|
|
394
|
-
const inputProto = window.HTMLInputElement.prototype
|
|
395
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
396
|
-
inputProto,
|
|
397
|
-
'checked'
|
|
398
|
-
) as PropertyDescriptor
|
|
399
|
-
const setChecked = descriptor.set
|
|
400
|
-
if (prevChecked !== checked && setChecked) {
|
|
401
|
-
const event = new Event('click', { bubbles })
|
|
402
|
-
setChecked.call(input, checked)
|
|
403
|
-
input.dispatchEvent(event)
|
|
404
|
-
}
|
|
405
|
-
}, [prevChecked, checked, bubbles])
|
|
406
|
-
|
|
407
|
-
return (
|
|
408
|
-
<input
|
|
409
|
-
type="radio"
|
|
410
|
-
defaultChecked={checked}
|
|
411
|
-
{...inputProps}
|
|
412
|
-
tabIndex={-1}
|
|
413
|
-
ref={ref}
|
|
414
|
-
aria-hidden={isHidden}
|
|
415
|
-
style={{
|
|
416
|
-
...(isHidden
|
|
417
|
-
? {
|
|
418
|
-
// ...controlSize,
|
|
419
|
-
position: 'absolute',
|
|
420
|
-
pointerEvents: 'none',
|
|
421
|
-
opacity: 0,
|
|
422
|
-
margin: 0,
|
|
423
|
-
}
|
|
424
|
-
: {
|
|
425
|
-
appearance: 'auto',
|
|
426
|
-
accentColor,
|
|
427
|
-
}),
|
|
428
|
-
|
|
429
|
-
...props.style,
|
|
430
|
-
}}
|
|
431
|
-
/>
|
|
432
|
-
)
|
|
433
|
-
}
|
|
86
|
+
const RADIO_GROUP_INDICATOR_NAME = 'RadioGroupIndicator'
|
|
434
87
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
* ----------------------------------------------------------------------- */
|
|
88
|
+
export const RadioGroupIndicatorFrame = styled(ThemeableStack, {
|
|
89
|
+
name: RADIO_GROUP_INDICATOR_NAME,
|
|
438
90
|
|
|
439
|
-
|
|
91
|
+
variants: {
|
|
92
|
+
unstyled: {
|
|
93
|
+
false: {
|
|
94
|
+
width: '33%',
|
|
95
|
+
height: '33%',
|
|
96
|
+
borderRadius: 1000,
|
|
97
|
+
backgroundColor: '$color',
|
|
98
|
+
pressTheme: true,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
} as const,
|
|
440
102
|
|
|
441
|
-
|
|
103
|
+
defaultVariants: {
|
|
104
|
+
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
105
|
+
},
|
|
106
|
+
})
|
|
442
107
|
|
|
443
|
-
|
|
108
|
+
const RADIO_GROUP_NAME = 'RadioGroup'
|
|
444
109
|
|
|
445
|
-
const RadioGroupFrame = styled(ThemeableStack, {
|
|
110
|
+
export const RadioGroupFrame = styled(ThemeableStack, {
|
|
446
111
|
name: RADIO_GROUP_NAME,
|
|
447
112
|
|
|
448
113
|
variants: {
|
|
@@ -458,78 +123,3 @@ const RadioGroupFrame = styled(ThemeableStack, {
|
|
|
458
123
|
},
|
|
459
124
|
} as const,
|
|
460
125
|
})
|
|
461
|
-
|
|
462
|
-
type RadioGroupProps = GetProps<typeof RadioGroupFrame> & {
|
|
463
|
-
value?: string
|
|
464
|
-
defaultValue?: string
|
|
465
|
-
onValueChange?: (value: string) => void
|
|
466
|
-
required?: boolean
|
|
467
|
-
disabled?: boolean
|
|
468
|
-
name?: string
|
|
469
|
-
native?: boolean
|
|
470
|
-
accentColor?: string
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const RadioGroup = withStaticProperties(
|
|
474
|
-
RadioGroupFrame.extractable(
|
|
475
|
-
React.forwardRef<RadioGroupElement, RadioGroupProps>(
|
|
476
|
-
(props: ScopedProps<RadioGroupProps>, forwardedRef) => {
|
|
477
|
-
const {
|
|
478
|
-
__scopeRadioGroup,
|
|
479
|
-
value: valueProp,
|
|
480
|
-
defaultValue,
|
|
481
|
-
onValueChange,
|
|
482
|
-
disabled = false,
|
|
483
|
-
required = false,
|
|
484
|
-
name,
|
|
485
|
-
orientation,
|
|
486
|
-
native,
|
|
487
|
-
accentColor,
|
|
488
|
-
...radioGroupProps
|
|
489
|
-
} = props
|
|
490
|
-
const [value, setValue] = useControllableState({
|
|
491
|
-
prop: valueProp,
|
|
492
|
-
defaultProp: defaultValue!,
|
|
493
|
-
onChange: onValueChange,
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
return (
|
|
497
|
-
<RadioGroupProvider
|
|
498
|
-
scope={__scopeRadioGroup}
|
|
499
|
-
value={value}
|
|
500
|
-
required={required}
|
|
501
|
-
onChange={setValue}
|
|
502
|
-
disabled={disabled}
|
|
503
|
-
name={name}
|
|
504
|
-
native={native}
|
|
505
|
-
accentColor={accentColor}
|
|
506
|
-
>
|
|
507
|
-
<RovingFocusGroup
|
|
508
|
-
__scopeRovingFocusGroup={RADIO_GROUP_NAME}
|
|
509
|
-
orientation={orientation as any}
|
|
510
|
-
loop={true}
|
|
511
|
-
>
|
|
512
|
-
<RadioGroupFrame
|
|
513
|
-
role="radiogroup"
|
|
514
|
-
aria-orientation={orientation}
|
|
515
|
-
ref={forwardedRef}
|
|
516
|
-
orientation={orientation}
|
|
517
|
-
data-disabled={disabled ? '' : undefined}
|
|
518
|
-
{...radioGroupProps}
|
|
519
|
-
/>
|
|
520
|
-
</RovingFocusGroup>
|
|
521
|
-
</RadioGroupProvider>
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
)
|
|
525
|
-
),
|
|
526
|
-
{
|
|
527
|
-
Indicator: RadioIndicator,
|
|
528
|
-
Item: RadioGroupItem,
|
|
529
|
-
}
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
RadioGroup.displayName = RADIO_GROUP_NAME
|
|
533
|
-
|
|
534
|
-
export { createRadioGroupScope, RadioGroup }
|
|
535
|
-
export type { RadioGroupProps, RadioGroupItemProps }
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RadioGroupContextValue,
|
|
3
|
+
RadioGroupItemContextValue,
|
|
4
|
+
} from '@tamagui/radio-headless'
|
|
5
|
+
import type { GetProps } from '@tamagui/core'
|
|
6
|
+
import { isWeb, withStaticProperties } from '@tamagui/core'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
RadioGroupFrame,
|
|
10
|
+
RadioGroupIndicatorFrame,
|
|
11
|
+
RadioGroupItemFrame,
|
|
12
|
+
} from './RadioGroup'
|
|
13
|
+
|
|
14
|
+
const ensureContext = (x: any) => {
|
|
15
|
+
if (!x.context) {
|
|
16
|
+
x.context = RadioGroupContext
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type RadioIndicatorProps = GetProps<typeof RadioGroupIndicatorFrame> & {
|
|
21
|
+
forceMount?: boolean
|
|
22
|
+
unstyled?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
useRadioGroup,
|
|
27
|
+
useRadioGroupItem,
|
|
28
|
+
useRadioGroupItemIndicator,
|
|
29
|
+
} from '@tamagui/radio-headless'
|
|
30
|
+
import { RovingFocusGroup } from '@tamagui/roving-focus'
|
|
31
|
+
import { createContext } from 'react'
|
|
32
|
+
const RadioGroupContext = createContext<RadioGroupContextValue>({})
|
|
33
|
+
const RadioGroupItemContext = createContext<RadioGroupItemContextValue>({
|
|
34
|
+
checked: false,
|
|
35
|
+
disabled: false,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export type RadioGroupItemProps = GetProps<typeof RadioGroupItemFrame> & {
|
|
39
|
+
value: string
|
|
40
|
+
id?: string
|
|
41
|
+
labelledBy?: string
|
|
42
|
+
disabled?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RadioGroupProps = GetProps<typeof RadioGroupFrame> & {
|
|
46
|
+
value?: string
|
|
47
|
+
defaultValue?: string
|
|
48
|
+
onValueChange?: (value: string) => void
|
|
49
|
+
required?: boolean
|
|
50
|
+
disabled?: boolean
|
|
51
|
+
name?: string
|
|
52
|
+
native?: boolean
|
|
53
|
+
accentColor?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type RadioGroupComponent = (props: RadioGroupProps) => any
|
|
57
|
+
|
|
58
|
+
type RadioGroupIndicatorComponent = (props: RadioIndicatorProps) => any
|
|
59
|
+
|
|
60
|
+
type RadioGroupItemComponent = (props: RadioGroupItemProps) => any
|
|
61
|
+
|
|
62
|
+
export function createRadioGroup<
|
|
63
|
+
F extends RadioGroupComponent,
|
|
64
|
+
D extends RadioGroupIndicatorComponent,
|
|
65
|
+
I extends RadioGroupItemComponent,
|
|
66
|
+
>(createProps: { disableActiveTheme?: boolean; Frame?: F; Indicator?: D; Item?: I }) {
|
|
67
|
+
const {
|
|
68
|
+
disableActiveTheme,
|
|
69
|
+
Frame = RadioGroupFrame,
|
|
70
|
+
Indicator = RadioGroupIndicatorFrame,
|
|
71
|
+
Item = RadioGroupItemFrame,
|
|
72
|
+
} = createProps as any as {
|
|
73
|
+
disableActiveTheme?: boolean
|
|
74
|
+
Frame: typeof RadioGroupFrame
|
|
75
|
+
Indicator: typeof RadioGroupIndicatorFrame
|
|
76
|
+
Item: typeof RadioGroupItemFrame
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ensureContext(Frame)
|
|
80
|
+
ensureContext(Indicator)
|
|
81
|
+
ensureContext(Item)
|
|
82
|
+
|
|
83
|
+
type RadioGroupProps = GetProps<typeof RadioGroupFrame> & {
|
|
84
|
+
value?: string
|
|
85
|
+
defaultValue?: string
|
|
86
|
+
onValueChange?: (value: string) => void
|
|
87
|
+
required?: boolean
|
|
88
|
+
disabled?: boolean
|
|
89
|
+
name?: string
|
|
90
|
+
native?: boolean
|
|
91
|
+
accentColor?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const RadioGroupImp = Frame.styleable<RadioGroupProps>((props, ref) => {
|
|
95
|
+
const {
|
|
96
|
+
value,
|
|
97
|
+
defaultValue,
|
|
98
|
+
onValueChange,
|
|
99
|
+
required = false,
|
|
100
|
+
disabled = false,
|
|
101
|
+
name,
|
|
102
|
+
native,
|
|
103
|
+
accentColor,
|
|
104
|
+
orientation = 'vertical',
|
|
105
|
+
...rest
|
|
106
|
+
} = props
|
|
107
|
+
|
|
108
|
+
const { providerValue, frameAttrs, rovingFocusGroupAttrs } = useRadioGroup({
|
|
109
|
+
orientation,
|
|
110
|
+
name,
|
|
111
|
+
defaultValue,
|
|
112
|
+
value,
|
|
113
|
+
onValueChange,
|
|
114
|
+
required,
|
|
115
|
+
disabled,
|
|
116
|
+
native,
|
|
117
|
+
accentColor,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<RadioGroupContext.Provider value={providerValue}>
|
|
122
|
+
<RovingFocusGroup {...rovingFocusGroupAttrs}>
|
|
123
|
+
<RadioGroupFrame {...frameAttrs} ref={ref} {...rest} />
|
|
124
|
+
</RovingFocusGroup>
|
|
125
|
+
</RadioGroupContext.Provider>
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const RadioGroupItemImp = Item.styleable<RadioGroupItemProps>((props, ref) => {
|
|
130
|
+
const {
|
|
131
|
+
value,
|
|
132
|
+
labelledBy,
|
|
133
|
+
onPress,
|
|
134
|
+
//@ts-expect-error
|
|
135
|
+
onKeyDown,
|
|
136
|
+
disabled,
|
|
137
|
+
id,
|
|
138
|
+
...rest
|
|
139
|
+
} = props
|
|
140
|
+
|
|
141
|
+
const {
|
|
142
|
+
providerValue,
|
|
143
|
+
bubbleInput,
|
|
144
|
+
rovingFocusGroupAttrs,
|
|
145
|
+
frameAttrs,
|
|
146
|
+
isFormControl,
|
|
147
|
+
native,
|
|
148
|
+
} = useRadioGroupItem({
|
|
149
|
+
radioGroupContext: RadioGroupContext,
|
|
150
|
+
value,
|
|
151
|
+
id,
|
|
152
|
+
labelledBy,
|
|
153
|
+
disabled,
|
|
154
|
+
onPress: onPress!,
|
|
155
|
+
onKeyDown,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<RadioGroupItemContext.Provider value={providerValue}>
|
|
160
|
+
{isWeb && native ? (
|
|
161
|
+
bubbleInput
|
|
162
|
+
) : (
|
|
163
|
+
<>
|
|
164
|
+
<RovingFocusGroup.Item {...rovingFocusGroupAttrs}>
|
|
165
|
+
<RadioGroupItemFrame {...frameAttrs} ref={ref} {...rest} />
|
|
166
|
+
</RovingFocusGroup.Item>
|
|
167
|
+
{isFormControl && bubbleInput}
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
</RadioGroupItemContext.Provider>
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
RadioGroupItemImp.displayName = 'RadioGroupItem'
|
|
175
|
+
|
|
176
|
+
const RadioIndicator = Indicator.styleable<RadioIndicatorProps>(
|
|
177
|
+
(props: RadioIndicatorProps, forwardedRef) => {
|
|
178
|
+
const { forceMount, disabled, ...indicatorProps } = props
|
|
179
|
+
const { checked, ...useIndicatorRest } = useRadioGroupItemIndicator({
|
|
180
|
+
radioGroupItemContext: RadioGroupItemContext,
|
|
181
|
+
disabled,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (forceMount || checked) {
|
|
185
|
+
return <Indicator {...useIndicatorRest} ref={forwardedRef} {...indicatorProps} />
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
RadioIndicator.displayName = 'RadioIndicator'
|
|
193
|
+
|
|
194
|
+
const RadioGroup = withStaticProperties(RadioGroupImp, {
|
|
195
|
+
Item: RadioGroupItemImp,
|
|
196
|
+
Indicator: RadioIndicator,
|
|
197
|
+
})
|
|
198
|
+
RadioGroup.displayName = 'RadioGroup'
|
|
199
|
+
|
|
200
|
+
return RadioGroup
|
|
201
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RadioGroupFrame,
|
|
3
|
+
RadioGroupIndicatorFrame,
|
|
4
|
+
RadioGroupItemFrame,
|
|
5
|
+
} from './RadioGroup'
|
|
6
|
+
import { createRadioGroup } from './createRadioGroup'
|
|
7
|
+
|
|
8
|
+
export * from './createRadioGroup'
|
|
1
9
|
export * from './RadioGroup'
|
|
10
|
+
export * from './RadioGroupStyledContext'
|
|
11
|
+
|
|
12
|
+
export const RadioGroup = createRadioGroup({
|
|
13
|
+
Frame: RadioGroupFrame,
|
|
14
|
+
Indicator: RadioGroupIndicatorFrame,
|
|
15
|
+
Item: RadioGroupItemFrame,
|
|
16
|
+
})
|