@tamagui/button 3.0.0-beta.807.1 → 3.0.0-beta.831.1
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/Button.cjs +18 -103
- package/dist/cjs/Button.native.cjs +18 -126
- package/dist/cjs/Button.native.js +18 -126
- package/dist/cjs/Button.native.js.map +1 -1
- package/dist/esm/Button.mjs +21 -104
- package/dist/esm/Button.mjs.map +1 -1
- package/dist/esm/Button.native.js +21 -127
- package/dist/esm/Button.native.js.map +1 -1
- package/dist/jsx/Button.mjs +21 -104
- package/dist/jsx/Button.mjs.map +1 -1
- package/dist/jsx/Button.native.cjs +18 -126
- package/dist/jsx/Button.native.js +18 -126
- package/dist/jsx/Button.native.js.map +1 -1
- package/package.json +11 -11
- package/src/Button.tsx +68 -190
- package/types/Button.d.ts +38 -275
- package/types/Button.d.ts.map +1 -1
package/src/Button.tsx
CHANGED
|
@@ -1,57 +1,14 @@
|
|
|
1
1
|
import { useGetThemedIcon } from '@tamagui/helpers-tamagui'
|
|
2
2
|
import { ButtonNestingContext } from '@tamagui/stacks'
|
|
3
|
-
import type {
|
|
4
|
-
import { wrapChildrenInText } from '@tamagui/text'
|
|
5
|
-
import type { GetProps } from '@tamagui/web'
|
|
6
|
-
import {
|
|
7
|
-
createStyledContext,
|
|
8
|
-
createStyledHOC,
|
|
9
|
-
isVariable,
|
|
10
|
-
styled,
|
|
11
|
-
Text,
|
|
12
|
-
useProps,
|
|
13
|
-
View,
|
|
14
|
-
withStaticProperties,
|
|
15
|
-
} from '@tamagui/web'
|
|
3
|
+
import type { TextParentStyles } from '@tamagui/text'
|
|
4
|
+
import { textParentProps, wrapChildrenInText } from '@tamagui/text'
|
|
5
|
+
import type { GetProps, TamaguiComponentPropsBaseBase } from '@tamagui/web'
|
|
6
|
+
import { splitStyleProps, styled, Text, View } from '@tamagui/web'
|
|
16
7
|
import type { FunctionComponent, JSX, ReactNode } from 'react'
|
|
17
8
|
import { useContext } from 'react'
|
|
18
|
-
import type { OpaqueColorValue } from 'react-native'
|
|
19
|
-
|
|
20
|
-
const buttonContextKeys = [
|
|
21
|
-
'color',
|
|
22
|
-
'ellipsis',
|
|
23
|
-
'fontFamily',
|
|
24
|
-
'fontSize',
|
|
25
|
-
'fontStyle',
|
|
26
|
-
'fontWeight',
|
|
27
|
-
'letterSpacing',
|
|
28
|
-
'maxFontSizeMultiplier',
|
|
29
|
-
'textAlign',
|
|
30
|
-
] as const
|
|
31
|
-
|
|
32
|
-
export const ButtonContext = createStyledContext<
|
|
33
|
-
TextContextStyles,
|
|
34
|
-
(typeof buttonContextKeys)[number]
|
|
35
|
-
>(
|
|
36
|
-
{
|
|
37
|
-
color: undefined,
|
|
38
|
-
ellipsis: undefined,
|
|
39
|
-
fontFamily: undefined,
|
|
40
|
-
fontSize: undefined,
|
|
41
|
-
fontStyle: undefined,
|
|
42
|
-
fontWeight: undefined,
|
|
43
|
-
letterSpacing: undefined,
|
|
44
|
-
maxFontSizeMultiplier: undefined,
|
|
45
|
-
textAlign: undefined,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
keys: buttonContextKeys,
|
|
49
|
-
}
|
|
50
|
-
)
|
|
51
9
|
|
|
52
10
|
export const ButtonFrame = styled(View, {
|
|
53
|
-
|
|
54
|
-
displayName: 'ButtonFrame',
|
|
11
|
+
// role is the cross-platform one; render only lands on web
|
|
55
12
|
role: 'button',
|
|
56
13
|
render: <button type="button" />,
|
|
57
14
|
tabIndex: 0,
|
|
@@ -70,8 +27,6 @@ export const ButtonFrame = styled(View, {
|
|
|
70
27
|
})
|
|
71
28
|
|
|
72
29
|
export const ButtonText = styled(Text, {
|
|
73
|
-
context: ButtonContext,
|
|
74
|
-
displayName: 'ButtonText',
|
|
75
30
|
// flexGrow 1 pushes text to the start of its parent on native
|
|
76
31
|
flexGrow: 0,
|
|
77
32
|
flexShrink: 1,
|
|
@@ -79,30 +34,14 @@ export const ButtonText = styled(Text, {
|
|
|
79
34
|
|
|
80
35
|
export type ButtonIconProps = {
|
|
81
36
|
children: ReactNode
|
|
82
|
-
color?:
|
|
37
|
+
color?: string
|
|
83
38
|
scaleIcon?: number
|
|
84
39
|
size?: number
|
|
85
40
|
}
|
|
86
41
|
|
|
87
|
-
// react-native opaque colors (PlatformColor, DynamicColorIOS) are plain
|
|
88
|
-
// objects and remain valid single icon colors, unlike conditional flat objects
|
|
89
|
-
const isOpaqueColor = (value: unknown): value is OpaqueColorValue =>
|
|
90
|
-
typeof value === 'object' &&
|
|
91
|
-
value !== null &&
|
|
92
|
-
('dynamic' in value || 'semantic' in value || 'resource_paths' in value)
|
|
93
|
-
|
|
94
42
|
export const ButtonIcon = ({ children, color, scaleIcon = 1, size }: ButtonIconProps) => {
|
|
95
|
-
const styledContext = ButtonContext.useStyledContext()
|
|
96
|
-
const iconColor = color ?? styledContext?.color
|
|
97
43
|
const getThemedIcon = useGetThemedIcon({
|
|
98
|
-
|
|
99
|
-
// objects) fall back to the theme color
|
|
100
|
-
color:
|
|
101
|
-
(typeof iconColor === 'string' && iconColor !== 'unset') ||
|
|
102
|
-
isVariable(iconColor) ||
|
|
103
|
-
isOpaqueColor(iconColor)
|
|
104
|
-
? iconColor
|
|
105
|
-
: undefined,
|
|
44
|
+
color,
|
|
106
45
|
size: size === undefined ? undefined : size * scaleIcon,
|
|
107
46
|
})
|
|
108
47
|
|
|
@@ -115,13 +54,23 @@ type ButtonIconInput =
|
|
|
115
54
|
| ((props: { color?: any; size?: any }) => ReactNode)
|
|
116
55
|
| null
|
|
117
56
|
|
|
118
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The props `useButton` reads and replaces. It hands the frame everything else
|
|
59
|
+
* untouched, so this is exactly what its result omits.
|
|
60
|
+
*/
|
|
61
|
+
type ButtonConsumedProps = {
|
|
62
|
+
children?: ReactNode
|
|
63
|
+
disabled?: boolean
|
|
64
|
+
render?: TamaguiComponentPropsBaseBase['render']
|
|
65
|
+
|
|
119
66
|
icon?: ButtonIconInput
|
|
120
67
|
iconAfter?: ButtonIconInput
|
|
121
68
|
iconSize?: number
|
|
122
69
|
scaleIcon?: number
|
|
70
|
+
}
|
|
123
71
|
|
|
124
|
-
|
|
72
|
+
/** passed straight through to the rendered `<button>` */
|
|
73
|
+
type ButtonHTMLProps = {
|
|
125
74
|
type?: 'submit' | 'reset' | 'button'
|
|
126
75
|
form?: string
|
|
127
76
|
formAction?: string
|
|
@@ -133,140 +82,87 @@ export type ButtonBehaviorProps = TextParentStyles & {
|
|
|
133
82
|
value?: string | readonly string[] | number
|
|
134
83
|
}
|
|
135
84
|
|
|
85
|
+
export type ButtonBehaviorProps = TextParentStyles & ButtonConsumedProps & ButtonHTMLProps
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What `useButton` returns: the caller's props minus the ones it consumed, plus
|
|
89
|
+
* the ones it decides. Spelled out rather than cast, so a skin that spreads the
|
|
90
|
+
* result onto a frame is type-checked on exactly what it will receive.
|
|
91
|
+
*/
|
|
92
|
+
export type UseButtonProps<Props> = Omit<
|
|
93
|
+
Omit<Props, keyof TextParentStyles | keyof typeof textParentProps>,
|
|
94
|
+
keyof ButtonConsumedProps
|
|
95
|
+
> & {
|
|
96
|
+
children: ReactNode
|
|
97
|
+
'aria-disabled'?: boolean
|
|
98
|
+
disabled?: boolean
|
|
99
|
+
render?: TamaguiComponentPropsBaseBase['render']
|
|
100
|
+
tabIndex?: number
|
|
101
|
+
}
|
|
102
|
+
|
|
136
103
|
export type UseButtonOptions = {
|
|
137
104
|
Text?: any
|
|
138
|
-
iconColor?:
|
|
105
|
+
iconColor?: string
|
|
139
106
|
iconSize?: number
|
|
140
107
|
textProps?: Record<string, unknown>
|
|
141
108
|
}
|
|
142
109
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
'fontFamily',
|
|
149
|
-
'fontSize',
|
|
150
|
-
'fontStyle',
|
|
151
|
-
'fontWeight',
|
|
152
|
-
'icon',
|
|
153
|
-
'iconAfter',
|
|
154
|
-
'iconSize',
|
|
155
|
-
'letterSpacing',
|
|
156
|
-
'maxFontSizeMultiplier',
|
|
157
|
-
'noTextWrap',
|
|
158
|
-
'render',
|
|
159
|
-
'scaleIcon',
|
|
160
|
-
'textAlign',
|
|
161
|
-
'textProps',
|
|
162
|
-
] as const
|
|
163
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Button behavior: icon theming, wrapping bare children in a text, and the html
|
|
112
|
+
* nesting rules. Flat text styles are partitioned in one pass and handed to the
|
|
113
|
+
* wrapped text, with no style resolution hook or text context.
|
|
114
|
+
*/
|
|
164
115
|
export function useButton<Props extends ButtonBehaviorProps>(
|
|
165
116
|
propsIn: Props,
|
|
166
117
|
{
|
|
167
118
|
Text = ButtonText,
|
|
168
|
-
iconColor
|
|
119
|
+
iconColor,
|
|
169
120
|
iconSize: iconSizeOption,
|
|
170
121
|
textProps: textPropsOption,
|
|
171
122
|
}: UseButtonOptions = {}
|
|
172
|
-
) {
|
|
123
|
+
): { isNested: boolean; props: UseButtonProps<Props> } {
|
|
173
124
|
const isNested = useContext(ButtonNestingContext)
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}) as Props & {
|
|
179
|
-
accessibilityRole?: string
|
|
180
|
-
children?: ReactNode
|
|
181
|
-
color?: TextContextStyles['color']
|
|
182
|
-
disabled?: boolean
|
|
183
|
-
render?: unknown
|
|
184
|
-
role?: string
|
|
185
|
-
size?: unknown
|
|
186
|
-
}
|
|
125
|
+
const [wrappedTextProps, buttonProps] = splitStyleProps(propsIn, {
|
|
126
|
+
expandShorthands: true,
|
|
127
|
+
filter: textParentProps,
|
|
128
|
+
})
|
|
187
129
|
|
|
188
130
|
const {
|
|
189
131
|
children,
|
|
190
|
-
color,
|
|
191
132
|
disabled,
|
|
192
|
-
ellipsis,
|
|
193
|
-
fontFamily,
|
|
194
|
-
fontSize,
|
|
195
|
-
fontStyle,
|
|
196
|
-
fontWeight,
|
|
197
133
|
icon,
|
|
198
134
|
iconAfter,
|
|
199
135
|
iconSize,
|
|
200
|
-
letterSpacing,
|
|
201
|
-
maxFontSizeMultiplier,
|
|
202
|
-
noTextWrap,
|
|
203
136
|
render,
|
|
204
137
|
scaleIcon = 1,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} =
|
|
208
|
-
|
|
209
|
-
// useProps resolves styles for the component running the hook. This behavior
|
|
210
|
-
// HOC renders a styled frame, so its frame styles must keep the raw flat
|
|
211
|
-
// programs for the frame's own hover/press/focus state and merge pass.
|
|
212
|
-
const frameProps = { ...propsIn }
|
|
213
|
-
for (const key of buttonInternalPropNames) {
|
|
214
|
-
delete (frameProps as Record<string, any>)[key]
|
|
215
|
-
}
|
|
138
|
+
...frameProps
|
|
139
|
+
} = buttonProps
|
|
140
|
+
const { noTextWrap, textProps, ...textStyleProps } = wrappedTextProps
|
|
216
141
|
|
|
217
|
-
const contextColor = color ?? styledContext?.color
|
|
218
|
-
const iconColor = iconColorOption ?? contextColor
|
|
219
142
|
const resolvedIconSize = iconSize ?? iconSizeOption
|
|
220
143
|
const getThemedIcon = useGetThemedIcon({
|
|
221
|
-
|
|
222
|
-
// objects) fall back to the theme color
|
|
223
|
-
color:
|
|
224
|
-
(typeof iconColor === 'string' && iconColor !== 'unset') ||
|
|
225
|
-
isVariable(iconColor) ||
|
|
226
|
-
isOpaqueColor(iconColor)
|
|
227
|
-
? iconColor
|
|
228
|
-
: undefined,
|
|
144
|
+
color: iconColor,
|
|
229
145
|
size: resolvedIconSize === undefined ? undefined : resolvedIconSize * scaleIcon,
|
|
230
146
|
})
|
|
231
147
|
const [themedIcon, themedIconAfter] = [icon, iconAfter].map((item) => {
|
|
232
148
|
return item ? getThemedIcon(item) : null
|
|
233
149
|
})
|
|
234
150
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
fontSize: fontSize ?? styledContext?.fontSize,
|
|
240
|
-
fontStyle: fontStyle ?? styledContext?.fontStyle,
|
|
241
|
-
fontWeight: fontWeight ?? styledContext?.fontWeight,
|
|
242
|
-
letterSpacing: letterSpacing ?? styledContext?.letterSpacing,
|
|
243
|
-
maxFontSizeMultiplier: maxFontSizeMultiplier ?? styledContext?.maxFontSizeMultiplier,
|
|
244
|
-
textAlign: textAlign ?? styledContext?.textAlign,
|
|
245
|
-
}
|
|
151
|
+
// adjacent text children join into one Text: `<Button>hi {name}</Button>` is
|
|
152
|
+
// two children, and wrapping them separately lays them out with a gap,
|
|
153
|
+
// ellipsizes per fragment, reads as fragments to assistive tech, and throws
|
|
154
|
+
// "Text strings must be rendered within a <Text>" for a bare number on native
|
|
246
155
|
const wrappedChildren = wrapChildrenInText(
|
|
247
156
|
Text,
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
...textContext,
|
|
251
|
-
noTextWrap,
|
|
252
|
-
textProps,
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
...textPropsOption,
|
|
256
|
-
...(processedProps.size === undefined ? null : { size: processedProps.size }),
|
|
257
|
-
}
|
|
157
|
+
{ children, noTextWrap, textProps },
|
|
158
|
+
{ ...textPropsOption, ...textStyleProps }
|
|
258
159
|
)
|
|
160
|
+
|
|
259
161
|
const resolvedRender =
|
|
260
162
|
render ??
|
|
261
|
-
(isNested ?
|
|
262
|
-
'span'
|
|
263
|
-
) : processedProps.accessibilityRole === 'link' || processedProps.role === 'link' ? (
|
|
264
|
-
'a'
|
|
265
|
-
) : disabled ? (
|
|
266
|
-
<button type="button" disabled />
|
|
267
|
-
) : undefined)
|
|
163
|
+
(isNested ? 'span' : disabled ? <button type="button" disabled /> : undefined)
|
|
268
164
|
|
|
269
|
-
const
|
|
165
|
+
const resolvedProps = {
|
|
270
166
|
...frameProps,
|
|
271
167
|
...(disabled && {
|
|
272
168
|
'aria-disabled': true,
|
|
@@ -294,35 +190,17 @@ export function useButton<Props extends ButtonBehaviorProps>(
|
|
|
294
190
|
}),
|
|
295
191
|
children: (
|
|
296
192
|
<ButtonNestingContext.Provider value={true}>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
{themedIconAfter}
|
|
301
|
-
</ButtonContext.Provider>
|
|
193
|
+
{themedIcon}
|
|
194
|
+
{wrappedChildren}
|
|
195
|
+
{themedIconAfter}
|
|
302
196
|
</ButtonNestingContext.Provider>
|
|
303
197
|
),
|
|
304
|
-
}
|
|
198
|
+
}
|
|
305
199
|
|
|
306
200
|
return {
|
|
307
201
|
isNested,
|
|
308
|
-
props,
|
|
202
|
+
props: resolvedProps,
|
|
309
203
|
}
|
|
310
204
|
}
|
|
311
205
|
|
|
312
|
-
|
|
313
|
-
ButtonFrame,
|
|
314
|
-
function Button(props: ButtonBehaviorProps, ref) {
|
|
315
|
-
const { props: buttonProps } = useButton(props)
|
|
316
|
-
|
|
317
|
-
return <ButtonFrame ref={ref} {...buttonProps} />
|
|
318
|
-
}
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
export const Button = withStaticProperties(ButtonComponent, {
|
|
322
|
-
Apply: ButtonContext.Provider,
|
|
323
|
-
Frame: ButtonFrame,
|
|
324
|
-
Icon: ButtonIcon,
|
|
325
|
-
Text: ButtonText,
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
export type ButtonProps = GetProps<typeof ButtonComponent>
|
|
206
|
+
export type ButtonFrameProps = GetProps<typeof ButtonFrame>
|