@tamagui/toggle-group 1.10.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/Toggle.js +127 -0
- package/dist/cjs/Toggle.js.map +7 -0
- package/dist/cjs/ToggleGroup.js +342 -0
- package/dist/cjs/ToggleGroup.js.map +7 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/esm/Toggle.js +92 -0
- package/dist/esm/Toggle.js.map +7 -0
- package/dist/esm/Toggle.mjs +92 -0
- package/dist/esm/Toggle.mjs.map +7 -0
- package/dist/esm/ToggleGroup.js +313 -0
- package/dist/esm/ToggleGroup.js.map +7 -0
- package/dist/esm/ToggleGroup.mjs +313 -0
- package/dist/esm/ToggleGroup.mjs.map +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +7 -0
- package/dist/jsx/Toggle.js +88 -0
- package/dist/jsx/Toggle.js.map +7 -0
- package/dist/jsx/Toggle.mjs +88 -0
- package/dist/jsx/Toggle.mjs.map +7 -0
- package/dist/jsx/ToggleGroup.js +279 -0
- package/dist/jsx/ToggleGroup.js.map +7 -0
- package/dist/jsx/ToggleGroup.mjs +279 -0
- package/dist/jsx/ToggleGroup.mjs.map +7 -0
- package/dist/jsx/index.js +2 -0
- package/dist/jsx/index.js.map +7 -0
- package/dist/jsx/index.mjs +2 -0
- package/dist/jsx/index.mjs.map +7 -0
- package/package.json +56 -0
- package/src/Toggle.tsx +129 -0
- package/src/ToggleGroup.tsx +476 -0
- package/src/index.ts +1 -0
- package/types/Toggle.d.ts +154 -0
- package/types/Toggle.d.ts.map +1 -0
- package/types/ToggleGroup.d.ts +538 -0
- package/types/ToggleGroup.d.ts.map +1 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tamagui/toggle-group",
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"sideEffects": [
|
|
5
|
+
"*.css"
|
|
6
|
+
],
|
|
7
|
+
"source": "src/index.ts",
|
|
8
|
+
"types": "./types/index.d.ts",
|
|
9
|
+
"main": "dist/cjs",
|
|
10
|
+
"module": "dist/esm",
|
|
11
|
+
"module:jsx": "dist/jsx",
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"types",
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tamagui-build",
|
|
19
|
+
"watch": "tamagui-build --watch",
|
|
20
|
+
"lint": "../../node_modules/.bin/rome check src",
|
|
21
|
+
"lint:fix": "../../node_modules/.bin/rome check --apply-suggested src",
|
|
22
|
+
"clean": "tamagui-build clean",
|
|
23
|
+
"clean:build": "tamagui-build clean:build"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./types/index.d.ts",
|
|
29
|
+
"import": "./dist/esm/index.js",
|
|
30
|
+
"require": "./dist/cjs/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@tamagui/create-context": "1.10.0",
|
|
35
|
+
"@tamagui/focusable": "1.10.0",
|
|
36
|
+
"@tamagui/font-size": "1.10.0",
|
|
37
|
+
"@tamagui/get-size": "1.10.0",
|
|
38
|
+
"@tamagui/group": "1.10.0",
|
|
39
|
+
"@tamagui/helpers-tamagui": "1.10.0",
|
|
40
|
+
"@tamagui/roving-focus": "1.10.0",
|
|
41
|
+
"@tamagui/stacks": "1.10.0",
|
|
42
|
+
"@tamagui/use-controllable-state": "1.10.0",
|
|
43
|
+
"@tamagui/use-direction": "1.10.0",
|
|
44
|
+
"@tamagui/web": "1.10.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "*"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@tamagui/build": "1.10.0",
|
|
51
|
+
"react": "^18.2.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/Toggle.tsx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ThemeableStack } from '@tamagui/stacks'
|
|
2
|
+
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
3
|
+
import { GetProps, Theme, composeEventHandlers, isWeb, styled } from '@tamagui/web'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
/* -------------------------------------------------------------------------------------------------
|
|
7
|
+
* Toggle
|
|
8
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
9
|
+
|
|
10
|
+
const NAME = 'Toggle'
|
|
11
|
+
|
|
12
|
+
type TamaguiButtonElement = HTMLButtonElement
|
|
13
|
+
|
|
14
|
+
type ToggleElement = TamaguiButtonElement
|
|
15
|
+
|
|
16
|
+
const ToggleFrame = styled(ThemeableStack, {
|
|
17
|
+
name: NAME,
|
|
18
|
+
tag: 'button',
|
|
19
|
+
|
|
20
|
+
variants: {
|
|
21
|
+
unstyled: {
|
|
22
|
+
false: {
|
|
23
|
+
pressTheme: true,
|
|
24
|
+
backgroundColor: '$background',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
display: 'flex',
|
|
28
|
+
borderColor: '$borderColor',
|
|
29
|
+
borderWidth: '1px',
|
|
30
|
+
margin: '-1px',
|
|
31
|
+
hoverStyle: {
|
|
32
|
+
backgroundColor: '$backgroundHover',
|
|
33
|
+
},
|
|
34
|
+
pressStyle: {
|
|
35
|
+
backgroundColor: '$backgroundPress',
|
|
36
|
+
},
|
|
37
|
+
focusStyle: {
|
|
38
|
+
borderColor: '$borderColorFocus',
|
|
39
|
+
backgroundColor: '$backgroundFocus',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
active: {
|
|
44
|
+
true: {
|
|
45
|
+
zIndex: 1,
|
|
46
|
+
hoverStyle: {
|
|
47
|
+
backgroundColor: '$background',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
orientation: {
|
|
52
|
+
horizontal: {
|
|
53
|
+
flexDirection: 'row',
|
|
54
|
+
spaceDirection: 'horizontal',
|
|
55
|
+
},
|
|
56
|
+
vertical: {
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
spaceDirection: 'vertical',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
} as const,
|
|
62
|
+
defaultVariants: {
|
|
63
|
+
unstyled: false,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
type ToggleFrameProps = GetProps<typeof ToggleFrame>
|
|
68
|
+
|
|
69
|
+
type ToggleProps = ToggleFrameProps & {
|
|
70
|
+
defaultValue?: string
|
|
71
|
+
disabled?: boolean
|
|
72
|
+
pressed?: boolean
|
|
73
|
+
defaultPressed?: boolean
|
|
74
|
+
onPressedChange?(pressed: boolean): void
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const Toggle = ToggleFrame.extractable(
|
|
78
|
+
React.forwardRef<ToggleElement, ToggleProps>((props, forwardedRef) => {
|
|
79
|
+
const {
|
|
80
|
+
pressed: pressedProp,
|
|
81
|
+
defaultPressed = false,
|
|
82
|
+
onPressedChange,
|
|
83
|
+
...buttonProps
|
|
84
|
+
} = props
|
|
85
|
+
|
|
86
|
+
const [pressed = false, setPressed] = useControllableState({
|
|
87
|
+
prop: pressedProp,
|
|
88
|
+
onChange: onPressedChange,
|
|
89
|
+
defaultProp: defaultPressed,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Theme forceClassName name={pressed ? 'active' : null}>
|
|
94
|
+
<ToggleFrame
|
|
95
|
+
active={!props.unstyled ? pressed : undefined}
|
|
96
|
+
aria-pressed={pressed}
|
|
97
|
+
data-state={pressed ? 'on' : 'off'}
|
|
98
|
+
data-disabled={props.disabled ? '' : undefined}
|
|
99
|
+
{...buttonProps}
|
|
100
|
+
ref={forwardedRef}
|
|
101
|
+
onPress={composeEventHandlers(props.onPress ?? undefined, (event) => {
|
|
102
|
+
if (!props.disabled) {
|
|
103
|
+
setPressed(!pressed)
|
|
104
|
+
}
|
|
105
|
+
})}
|
|
106
|
+
// caught by onPress - no need for this
|
|
107
|
+
// {...(isWeb && {
|
|
108
|
+
// onKeyDown: composeEventHandlers(
|
|
109
|
+
// (props as React.HTMLProps<HTMLButtonElement>).onKeyDown,
|
|
110
|
+
// (event) => {
|
|
111
|
+
// if ([' ', 'Enter'].includes(event.key)) {
|
|
112
|
+
// if (!props.disabled) {
|
|
113
|
+
// setPressed(!pressed)
|
|
114
|
+
// }
|
|
115
|
+
// }
|
|
116
|
+
// }
|
|
117
|
+
// ),
|
|
118
|
+
// })}
|
|
119
|
+
/>
|
|
120
|
+
</Theme>
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
Toggle.displayName = NAME
|
|
125
|
+
|
|
126
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
127
|
+
|
|
128
|
+
export { Toggle, ToggleFrame }
|
|
129
|
+
export type { ToggleProps }
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { Scope, createContextScope } from '@tamagui/create-context'
|
|
2
|
+
import { registerFocusable } from '@tamagui/focusable'
|
|
3
|
+
import { getFontSize } from '@tamagui/font-size'
|
|
4
|
+
import { stepTokenUpOrDown } from '@tamagui/get-size'
|
|
5
|
+
import { Group, GroupProps, useGroupItem } from '@tamagui/group'
|
|
6
|
+
import { useGetThemedIcon } from '@tamagui/helpers-tamagui'
|
|
7
|
+
import { RovingFocusGroup, createRovingFocusGroupScope } from '@tamagui/roving-focus'
|
|
8
|
+
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
9
|
+
import { useDirection } from '@tamagui/use-direction'
|
|
10
|
+
import {
|
|
11
|
+
GetProps,
|
|
12
|
+
SizeTokens,
|
|
13
|
+
getVariableValue,
|
|
14
|
+
isWeb,
|
|
15
|
+
styled,
|
|
16
|
+
useTheme,
|
|
17
|
+
withStaticProperties,
|
|
18
|
+
} from '@tamagui/web'
|
|
19
|
+
import React from 'react'
|
|
20
|
+
|
|
21
|
+
import { Toggle, ToggleFrame, ToggleProps } from './Toggle'
|
|
22
|
+
|
|
23
|
+
const TOGGLE_GROUP_NAME = 'ToggleGroup'
|
|
24
|
+
const [createToggleGroupContext, createToggleGroupScope] = createContextScope(
|
|
25
|
+
TOGGLE_GROUP_NAME,
|
|
26
|
+
[createRovingFocusGroupScope]
|
|
27
|
+
)
|
|
28
|
+
/* -------------------------------------------------------------------------------------------------
|
|
29
|
+
* ToggleGroupItem
|
|
30
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
31
|
+
|
|
32
|
+
const TOGGLE_GROUP_ITEM_NAME = 'ToggleGroupItem'
|
|
33
|
+
|
|
34
|
+
type ToggleGroupItemContextValue = { disabled?: boolean }
|
|
35
|
+
|
|
36
|
+
const [createToggleGroupItemContext, createToggleGroupItemScope] =
|
|
37
|
+
createContextScope(TOGGLE_GROUP_ITEM_NAME)
|
|
38
|
+
const [ToggleGroupItemProvider, useToggleGroupItemContext] =
|
|
39
|
+
createToggleGroupContext<ToggleGroupItemContextValue>(TOGGLE_GROUP_NAME)
|
|
40
|
+
const useToggleGroupItemScope = createToggleGroupItemScope()
|
|
41
|
+
|
|
42
|
+
type ToggleGroupItemElement = ToggleGroupItemImplElement
|
|
43
|
+
|
|
44
|
+
type ToggleGroupItemProps = GetProps<typeof ToggleFrame> & {
|
|
45
|
+
value: string
|
|
46
|
+
id?: string
|
|
47
|
+
disabled?: boolean
|
|
48
|
+
size?: SizeTokens
|
|
49
|
+
/**
|
|
50
|
+
* Used to disable passing styles down to children.
|
|
51
|
+
*/
|
|
52
|
+
disablePassStyles?: boolean
|
|
53
|
+
}
|
|
54
|
+
const ToggleGroupItem = ToggleFrame.extractable(
|
|
55
|
+
React.forwardRef<ToggleGroupItemElement, ToggleGroupItemProps>(
|
|
56
|
+
(props: ScopedProps<ToggleGroupItemProps>, forwardedRef) => {
|
|
57
|
+
const valueContext = useToggleGroupValueContext(
|
|
58
|
+
TOGGLE_GROUP_ITEM_NAME,
|
|
59
|
+
props.__scopeToggleGroup
|
|
60
|
+
)
|
|
61
|
+
const context = useToggleGroupContext(
|
|
62
|
+
TOGGLE_GROUP_ITEM_NAME,
|
|
63
|
+
props.__scopeToggleGroup
|
|
64
|
+
)
|
|
65
|
+
const __scopeToggleGroup = props.__scopeToggleGroup
|
|
66
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(props.__scopeToggleGroup)
|
|
67
|
+
const pressed = valueContext.value.includes(props.value)
|
|
68
|
+
const disabled = context.disabled || props.disabled || false
|
|
69
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
70
|
+
const groupItemProps = useGroupItem({ disabled })
|
|
71
|
+
const size = props.size ?? context.size
|
|
72
|
+
|
|
73
|
+
const sizeProps: Record<string, any> = {
|
|
74
|
+
width: undefined,
|
|
75
|
+
height: undefined,
|
|
76
|
+
padding: getVariableValue(size) * 0.6,
|
|
77
|
+
}
|
|
78
|
+
if (props.orientation === 'horizontal')
|
|
79
|
+
sizeProps.height = getVariableValue(size) * 2.4
|
|
80
|
+
else sizeProps.width = getVariableValue(size) * 2.4
|
|
81
|
+
|
|
82
|
+
const iconSize = (typeof size === 'number' ? size * 0.7 : getFontSize(size)) * 1.2
|
|
83
|
+
|
|
84
|
+
const theme = useTheme()
|
|
85
|
+
const getThemedIcon = useGetThemedIcon({ size: iconSize, color: theme.color })
|
|
86
|
+
|
|
87
|
+
const childrens = React.Children.toArray(props.children)
|
|
88
|
+
const children = childrens.map((child) => {
|
|
89
|
+
if (props.disablePassStyles || !React.isValidElement(child)) {
|
|
90
|
+
return child
|
|
91
|
+
}
|
|
92
|
+
return getThemedIcon(child)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const commonProps = { ...props, pressed, disabled, ...sizeProps, children }
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<ToggleGroupItemProvider scope={__scopeToggleGroup}>
|
|
99
|
+
{context.rovingFocus ? (
|
|
100
|
+
<RovingFocusGroup.Item
|
|
101
|
+
asChild
|
|
102
|
+
{...rovingFocusGroupScope}
|
|
103
|
+
focusable={!disabled}
|
|
104
|
+
active={pressed}
|
|
105
|
+
>
|
|
106
|
+
<ToggleFrame
|
|
107
|
+
asChild
|
|
108
|
+
focusable={!disabled}
|
|
109
|
+
disabled={disabled}
|
|
110
|
+
ref={ref}
|
|
111
|
+
{...groupItemProps}
|
|
112
|
+
>
|
|
113
|
+
<ToggleGroupItemImpl {...commonProps} ref={forwardedRef} />
|
|
114
|
+
</ToggleFrame>
|
|
115
|
+
</RovingFocusGroup.Item>
|
|
116
|
+
) : (
|
|
117
|
+
<ToggleFrame asChild focusable={!disabled} disabled={disabled} ref={ref}>
|
|
118
|
+
<ToggleGroupItemImpl {...commonProps} ref={forwardedRef} />
|
|
119
|
+
</ToggleFrame>
|
|
120
|
+
)}
|
|
121
|
+
</ToggleGroupItemProvider>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
ToggleGroupItem.displayName = TOGGLE_GROUP_ITEM_NAME
|
|
127
|
+
|
|
128
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
129
|
+
|
|
130
|
+
type ToggleGroupItemImplElement = React.ElementRef<typeof Toggle>
|
|
131
|
+
type ToggleGroupItemImplProps = Omit<
|
|
132
|
+
ToggleProps,
|
|
133
|
+
'defaultPressed' | 'onPressedChange' | 'size'
|
|
134
|
+
> & {
|
|
135
|
+
/**
|
|
136
|
+
* A string value for the toggle group item. All items within a toggle group should use a unique value.
|
|
137
|
+
*/
|
|
138
|
+
value: string
|
|
139
|
+
size?: SizeTokens
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ToggleGroupItemImpl = ToggleFrame.extractable(
|
|
143
|
+
React.forwardRef<ToggleGroupItemImplElement, ToggleGroupItemImplProps>(
|
|
144
|
+
(props: ScopedProps<ToggleGroupItemImplProps>, forwardedRef) => {
|
|
145
|
+
const { __scopeToggleGroup, value, ...itemProps } = props
|
|
146
|
+
const valueContext = useToggleGroupValueContext(
|
|
147
|
+
TOGGLE_GROUP_ITEM_NAME,
|
|
148
|
+
__scopeToggleGroup
|
|
149
|
+
)
|
|
150
|
+
const singleProps = {
|
|
151
|
+
'aria-pressed': undefined,
|
|
152
|
+
}
|
|
153
|
+
const typeProps = valueContext.type === 'single' ? singleProps : undefined
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<Toggle
|
|
157
|
+
{...typeProps}
|
|
158
|
+
{...itemProps}
|
|
159
|
+
ref={forwardedRef}
|
|
160
|
+
onPressedChange={(pressed) => {
|
|
161
|
+
if (pressed) {
|
|
162
|
+
valueContext.onItemActivate(value)
|
|
163
|
+
} else {
|
|
164
|
+
valueContext.onItemDeactivate(value)
|
|
165
|
+
}
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
174
|
+
|
|
175
|
+
/* -------------------------------------------------------------------------------------------------
|
|
176
|
+
* ToggleGroup
|
|
177
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
178
|
+
|
|
179
|
+
type ScopedProps<P> = P & { __scopeToggleGroup?: Scope }
|
|
180
|
+
|
|
181
|
+
const useRovingFocusGroupScope = createRovingFocusGroupScope()
|
|
182
|
+
|
|
183
|
+
type ToggleGroupElement = ToggleGroupImplSingleElement | ToggleGroupImplMultipleElement
|
|
184
|
+
interface ToggleGroupSingleProps extends ToggleGroupImplSingleProps {
|
|
185
|
+
type: 'single'
|
|
186
|
+
}
|
|
187
|
+
interface ToggleGroupMultipleProps extends ToggleGroupImplMultipleProps {
|
|
188
|
+
type: 'multiple'
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
type ToggleGroupProps = ToggleGroupSingleProps | ToggleGroupMultipleProps
|
|
192
|
+
|
|
193
|
+
const ToggleGroup = withStaticProperties(
|
|
194
|
+
React.forwardRef<ToggleGroupElement, ToggleGroupProps>((props, forwardedRef) => {
|
|
195
|
+
const { type, ...toggleGroupProps } = props
|
|
196
|
+
|
|
197
|
+
if (!isWeb) {
|
|
198
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
if (!props.id) return
|
|
201
|
+
return registerFocusable(props.id, {
|
|
202
|
+
// TODO: would be nice to focus on the first child later - could be done with reforest
|
|
203
|
+
// for now leaving it empty
|
|
204
|
+
focus: () => {},
|
|
205
|
+
})
|
|
206
|
+
}, [props.id])
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (type === 'single') {
|
|
210
|
+
const singleProps = toggleGroupProps as ToggleGroupImplSingleProps
|
|
211
|
+
return <ToggleGroupImplSingle {...singleProps} ref={forwardedRef} />
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (type === 'multiple') {
|
|
215
|
+
const multipleProps = toggleGroupProps as ToggleGroupImplMultipleProps
|
|
216
|
+
return <ToggleGroupImplMultiple {...multipleProps} ref={forwardedRef} />
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
throw new Error(`Missing prop \`type\` expected on \`${TOGGLE_GROUP_NAME}\``)
|
|
220
|
+
}),
|
|
221
|
+
{
|
|
222
|
+
Item: ToggleGroupItem,
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
ToggleGroup.displayName = TOGGLE_GROUP_NAME
|
|
227
|
+
|
|
228
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
229
|
+
|
|
230
|
+
type ToggleGroupValueContextValue = {
|
|
231
|
+
type: 'single' | 'multiple'
|
|
232
|
+
defaultValue?: string | string[]
|
|
233
|
+
value: string[]
|
|
234
|
+
onItemActivate(value: string): void
|
|
235
|
+
onItemDeactivate(value: string): void
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const [ToggleGroupValueProvider, useToggleGroupValueContext] =
|
|
239
|
+
createToggleGroupContext<ToggleGroupValueContextValue>(TOGGLE_GROUP_NAME)
|
|
240
|
+
|
|
241
|
+
type ToggleGroupImplSingleElement = ToggleGroupImplElement
|
|
242
|
+
interface ToggleGroupImplSingleProps extends ToggleGroupImplProps {
|
|
243
|
+
/**
|
|
244
|
+
* The controlled stateful value of the item that is pressed.
|
|
245
|
+
*/
|
|
246
|
+
value?: string
|
|
247
|
+
/**
|
|
248
|
+
* The value of the item that is pressed when initially rendered. Use
|
|
249
|
+
* `defaultValue` if you do not need to control the state of a toggle group.
|
|
250
|
+
*/
|
|
251
|
+
defaultValue?: string
|
|
252
|
+
/**
|
|
253
|
+
* The callback that fires when the value of the toggle group changes.
|
|
254
|
+
*/
|
|
255
|
+
onValueChange?(value: string): void
|
|
256
|
+
/**
|
|
257
|
+
* Won't let the user turn the active item off.
|
|
258
|
+
*/
|
|
259
|
+
disableDeactivation?: boolean
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const ToggleGroupImplSingle = React.forwardRef<
|
|
263
|
+
ToggleGroupImplSingleElement,
|
|
264
|
+
ToggleGroupImplSingleProps
|
|
265
|
+
>((props: ScopedProps<ToggleGroupImplSingleProps>, forwardedRef) => {
|
|
266
|
+
const {
|
|
267
|
+
value: valueProp,
|
|
268
|
+
defaultValue,
|
|
269
|
+
onValueChange = () => {},
|
|
270
|
+
disableDeactivation = false,
|
|
271
|
+
...toggleGroupSingleProps
|
|
272
|
+
} = props
|
|
273
|
+
|
|
274
|
+
const [value, setValue] = useControllableState({
|
|
275
|
+
prop: valueProp,
|
|
276
|
+
defaultProp: defaultValue!,
|
|
277
|
+
onChange: onValueChange,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<ToggleGroupValueProvider
|
|
282
|
+
scope={props.__scopeToggleGroup}
|
|
283
|
+
type="single"
|
|
284
|
+
value={value ? [value] : []}
|
|
285
|
+
defaultValue={value}
|
|
286
|
+
onItemActivate={setValue}
|
|
287
|
+
onItemDeactivate={React.useCallback(() => disableDeactivation ? null : setValue(''), [setValue, disableDeactivation])}
|
|
288
|
+
>
|
|
289
|
+
<ToggleGroupImpl {...toggleGroupSingleProps} ref={forwardedRef} />
|
|
290
|
+
</ToggleGroupValueProvider>
|
|
291
|
+
)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
type ToggleGroupImplMultipleElement = ToggleGroupImplElement
|
|
295
|
+
interface ToggleGroupImplMultipleProps extends ToggleGroupImplProps {
|
|
296
|
+
/**
|
|
297
|
+
* The controlled stateful value of the items that are pressed.
|
|
298
|
+
*/
|
|
299
|
+
value?: string[]
|
|
300
|
+
/**
|
|
301
|
+
* The value of the items that are pressed when initially rendered. Use
|
|
302
|
+
* `defaultValue` if you do not need to control the state of a toggle group.
|
|
303
|
+
*/
|
|
304
|
+
defaultValue?: string[]
|
|
305
|
+
/**
|
|
306
|
+
* The callback that fires when the state of the toggle group changes.
|
|
307
|
+
*/
|
|
308
|
+
onValueChange?(value: string[]): void
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const ToggleGroupImplMultiple = React.forwardRef<
|
|
312
|
+
ToggleGroupImplMultipleElement,
|
|
313
|
+
ToggleGroupImplMultipleProps
|
|
314
|
+
>((props: ScopedProps<ToggleGroupImplMultipleProps>, forwardedRef) => {
|
|
315
|
+
const {
|
|
316
|
+
value: valueProp,
|
|
317
|
+
defaultValue,
|
|
318
|
+
onValueChange = () => {},
|
|
319
|
+
...toggleGroupMultipleProps
|
|
320
|
+
} = props
|
|
321
|
+
|
|
322
|
+
const [value = [], setValue] = useControllableState({
|
|
323
|
+
prop: valueProp,
|
|
324
|
+
defaultProp: defaultValue!,
|
|
325
|
+
onChange: onValueChange,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const handleButtonActivate = React.useCallback(
|
|
329
|
+
(itemValue: string) => setValue((prevValue = []) => [...prevValue, itemValue]),
|
|
330
|
+
[setValue]
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
const handleButtonDeactivate = React.useCallback(
|
|
334
|
+
(itemValue: string) =>
|
|
335
|
+
setValue((prevValue = []) => prevValue.filter((value) => value !== itemValue)),
|
|
336
|
+
[setValue]
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<ToggleGroupValueProvider
|
|
341
|
+
scope={props.__scopeToggleGroup}
|
|
342
|
+
type="multiple"
|
|
343
|
+
value={value}
|
|
344
|
+
defaultValue={value}
|
|
345
|
+
onItemActivate={handleButtonActivate}
|
|
346
|
+
onItemDeactivate={handleButtonDeactivate}
|
|
347
|
+
>
|
|
348
|
+
<ToggleGroupImpl {...toggleGroupMultipleProps} ref={forwardedRef} />
|
|
349
|
+
</ToggleGroupValueProvider>
|
|
350
|
+
)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
ToggleGroup.displayName = TOGGLE_GROUP_NAME
|
|
354
|
+
|
|
355
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
356
|
+
|
|
357
|
+
type ToggleGroupContextValue = {
|
|
358
|
+
rovingFocus: boolean
|
|
359
|
+
disabled: boolean
|
|
360
|
+
size: SizeTokens
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const [ToggleGroupContext, useToggleGroupContext] =
|
|
364
|
+
createToggleGroupContext<ToggleGroupContextValue>(TOGGLE_GROUP_NAME)
|
|
365
|
+
|
|
366
|
+
type RovingFocusGroupProps = React.ComponentPropsWithoutRef<typeof RovingFocusGroup>
|
|
367
|
+
type TamaguiElement = HTMLElement
|
|
368
|
+
type ToggleGroupImplElement = TamaguiElement
|
|
369
|
+
|
|
370
|
+
const ToggleGroupImplElementFrame = styled(Group, {
|
|
371
|
+
name: TOGGLE_GROUP_NAME,
|
|
372
|
+
variants: {
|
|
373
|
+
unstyled: {
|
|
374
|
+
false: {
|
|
375
|
+
backgroundColor: '$background',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
orientation: {
|
|
379
|
+
vertical: {
|
|
380
|
+
flexDirection: 'column',
|
|
381
|
+
spaceDirection: 'vertical',
|
|
382
|
+
},
|
|
383
|
+
horizontal: {
|
|
384
|
+
flexDirection: 'row',
|
|
385
|
+
spaceDirection: 'horizontal',
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
} as const,
|
|
389
|
+
defaultVariants: {
|
|
390
|
+
unstyled: false,
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
type ToggleGroupImplProps = GetProps<typeof ToggleGroupImplElementFrame> &
|
|
395
|
+
GroupProps & {
|
|
396
|
+
rovingFocus?: boolean
|
|
397
|
+
dir?: RovingFocusGroupProps['dir']
|
|
398
|
+
loop?: RovingFocusGroupProps['loop']
|
|
399
|
+
sizeAdjust?: number
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const ToggleGroupImpl = ToggleGroupImplElementFrame.extractable(
|
|
403
|
+
React.forwardRef<ToggleGroupImplElement, ToggleGroupImplProps>(
|
|
404
|
+
(props: ScopedProps<ToggleGroupImplProps>, forwardedRef) => {
|
|
405
|
+
const {
|
|
406
|
+
__scopeToggleGroup,
|
|
407
|
+
disabled = false,
|
|
408
|
+
orientation = 'horizontal',
|
|
409
|
+
dir,
|
|
410
|
+
rovingFocus = true,
|
|
411
|
+
loop = true,
|
|
412
|
+
unstyled = false,
|
|
413
|
+
size: sizeProp = '$true',
|
|
414
|
+
sizeAdjust = 0,
|
|
415
|
+
...toggleGroupProps
|
|
416
|
+
} = props
|
|
417
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeToggleGroup)
|
|
418
|
+
const direction = useDirection(dir)
|
|
419
|
+
const commonProps = {
|
|
420
|
+
role: 'togglegroup',
|
|
421
|
+
dir: direction,
|
|
422
|
+
...toggleGroupProps,
|
|
423
|
+
}
|
|
424
|
+
const adjustedSize = getVariableValue(
|
|
425
|
+
stepTokenUpOrDown('size', props.size, sizeAdjust)
|
|
426
|
+
)
|
|
427
|
+
const size = Math.round(adjustedSize * 0.45)
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<ToggleGroupContext
|
|
431
|
+
scope={__scopeToggleGroup}
|
|
432
|
+
rovingFocus={rovingFocus}
|
|
433
|
+
disabled={disabled}
|
|
434
|
+
size={size}
|
|
435
|
+
>
|
|
436
|
+
{rovingFocus ? (
|
|
437
|
+
<RovingFocusGroup
|
|
438
|
+
asChild
|
|
439
|
+
{...rovingFocusGroupScope}
|
|
440
|
+
orientation={orientation}
|
|
441
|
+
dir={direction}
|
|
442
|
+
loop={loop}
|
|
443
|
+
>
|
|
444
|
+
<ToggleGroupImplElementFrame
|
|
445
|
+
aria-orientation={orientation}
|
|
446
|
+
orientation={orientation}
|
|
447
|
+
axis={orientation}
|
|
448
|
+
ref={forwardedRef}
|
|
449
|
+
data-disabled={disabled ? '' : undefined}
|
|
450
|
+
unstyled={unstyled}
|
|
451
|
+
{...commonProps}
|
|
452
|
+
/>
|
|
453
|
+
</RovingFocusGroup>
|
|
454
|
+
) : (
|
|
455
|
+
<ToggleGroupImplElementFrame
|
|
456
|
+
aria-orientation={orientation}
|
|
457
|
+
ref={forwardedRef}
|
|
458
|
+
orientation={orientation}
|
|
459
|
+
data-disabled={disabled ? '' : undefined}
|
|
460
|
+
unstyled={unstyled}
|
|
461
|
+
{...commonProps}
|
|
462
|
+
/>
|
|
463
|
+
)}
|
|
464
|
+
</ToggleGroupContext>
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
export { ToggleGroup, createToggleGroupScope }
|
|
471
|
+
export type {
|
|
472
|
+
ToggleGroupItemProps,
|
|
473
|
+
ToggleGroupMultipleProps,
|
|
474
|
+
ToggleGroupSingleProps,
|
|
475
|
+
ToggleGroupProps,
|
|
476
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ToggleGroup'
|