@tamagui/button 2.7.7 → 3.0.0-beta.643.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.
Files changed (48) hide show
  1. package/dist/cjs/Button.cjs +180 -266
  2. package/dist/cjs/Button.native.cjs +224 -0
  3. package/dist/cjs/Button.native.js +200 -271
  4. package/dist/cjs/Button.native.js.map +1 -1
  5. package/dist/cjs/index.cjs +10 -11
  6. package/dist/cjs/index.native.cjs +21 -0
  7. package/dist/cjs/index.native.js +10 -10
  8. package/dist/cjs/index.native.js.map +1 -1
  9. package/dist/esm/Button.mjs +168 -255
  10. package/dist/esm/Button.mjs.map +1 -1
  11. package/dist/esm/Button.native.js +188 -261
  12. package/dist/esm/Button.native.js.map +1 -1
  13. package/dist/esm/index.js +1 -2
  14. package/dist/esm/index.mjs +1 -2
  15. package/dist/esm/index.native.js +1 -2
  16. package/dist/jsx/Button.mjs +168 -255
  17. package/dist/jsx/Button.mjs.map +1 -1
  18. package/dist/jsx/Button.native.cjs +224 -0
  19. package/dist/jsx/Button.native.js +200 -271
  20. package/dist/jsx/Button.native.js.map +1 -1
  21. package/dist/jsx/index.js +1 -2
  22. package/dist/jsx/index.mjs +1 -2
  23. package/dist/jsx/index.native.cjs +21 -0
  24. package/dist/jsx/index.native.js +10 -10
  25. package/dist/jsx/index.native.js.map +1 -1
  26. package/package.json +11 -12
  27. package/src/Button.tsx +222 -280
  28. package/types/Button.d.ts +218 -114
  29. package/types/Button.d.ts.map +1 -1
  30. package/dist/cjs/Button.test.cjs +0 -37
  31. package/dist/cjs/Button.test.native.js +0 -40
  32. package/dist/cjs/Button.test.native.js.map +0 -1
  33. package/dist/esm/Button.test.mjs +0 -38
  34. package/dist/esm/Button.test.mjs.map +0 -1
  35. package/dist/esm/Button.test.native.js +0 -38
  36. package/dist/esm/Button.test.native.js.map +0 -1
  37. package/dist/esm/index.js.map +0 -1
  38. package/dist/esm/index.mjs.map +0 -1
  39. package/dist/esm/index.native.js.map +0 -1
  40. package/dist/jsx/Button.test.mjs +0 -38
  41. package/dist/jsx/Button.test.mjs.map +0 -1
  42. package/dist/jsx/Button.test.native.js +0 -40
  43. package/dist/jsx/Button.test.native.js.map +0 -1
  44. package/dist/jsx/index.js.map +0 -1
  45. package/dist/jsx/index.mjs.map +0 -1
  46. package/src/Button.test.tsx +0 -45
  47. package/types/Button.test.d.ts +0 -2
  48. package/types/Button.test.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA","ignoreList":[]}
1
+ {"version":3,"file":"index.native.js","names":[],"sources":["jsx/index.native.js"],"sourcesContent":["\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, \"default\"), secondTarget && __copyProps(secondTarget, mod, \"default\"));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\nvar index_exports = {};\nmodule.exports = __toCommonJS(index_exports);\n__reExport(index_exports, require(\"./Button\"), module.exports);\n//# sourceMappingURL=index.js.map\n"],"mappings":";;;;AACA,IAAI,YAAY,OAAO;AACvB,IAAI,mBAAmB,OAAO;AAC9B,IAAI,oBAAoB,OAAO;AAC/B,IAAI,eAAe,OAAO,UAAU;AACpC,IAAI,eAAe,IAAI,MAAM,QAAQ,SAAS;CAC5C,IAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,SAAS,YAAY;EAClE,KAAK,IAAI,OAAO,kBAAkB,IAAI,GACpC,IAAI,CAAC,aAAa,KAAK,IAAI,GAAG,KAAK,QAAQ,QACzC,UAAU,IAAI,KAAK;GAAE,WAAW,KAAK;GAAM,YAAY,EAAE,OAAO,iBAAiB,MAAM,GAAG,MAAM,KAAK;EAAW,CAAC;CACvH;CACA,OAAO;AACT;AACA,IAAI,cAAc,QAAQ,KAAK,kBAAkB,YAAY,QAAQ,KAAK,SAAS,GAAG,gBAAgB,YAAY,cAAc,KAAK,SAAS;AAC9I,IAAI,gBAAgB,QAAQ,YAAY,UAAU,CAAC,GAAG,cAAc,EAAE,OAAO,KAAK,CAAC,GAAG,GAAG;AACzF,IAAI,gBAAgB,CAAC;AACrB,OAAO,UAAU,aAAa,aAAa;AAC3C,WAAW,eAAe,QAAQ,oBAAU,GAAG,OAAO,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/button",
3
- "version": "2.7.7",
3
+ "version": "3.0.0-beta.643.1",
4
4
  "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
5
5
  "source": "src/index.ts",
6
6
  "files": [
@@ -35,19 +35,18 @@
35
35
  "clean:build": "tamagui-build clean:build"
36
36
  },
37
37
  "dependencies": {
38
- "@tamagui/config-default": "2.7.7",
39
- "@tamagui/core": "2.7.7",
40
- "@tamagui/get-button-sized": "2.7.7",
41
- "@tamagui/helpers": "2.7.7",
42
- "@tamagui/helpers-tamagui": "2.7.7",
43
- "@tamagui/spacer": "2.7.7",
44
- "@tamagui/stacks": "2.7.7",
45
- "@tamagui/text": "2.7.7",
46
- "@tamagui/font-size": "2.7.7",
47
- "@tamagui/web": "2.7.7"
38
+ "@tamagui/config-default": "3.0.0-beta.643.1",
39
+ "@tamagui/core": "3.0.0-beta.643.1",
40
+ "@tamagui/helpers": "3.0.0-beta.643.1",
41
+ "@tamagui/helpers-tamagui": "3.0.0-beta.643.1",
42
+ "@tamagui/spacer": "3.0.0-beta.643.1",
43
+ "@tamagui/stacks": "3.0.0-beta.643.1",
44
+ "@tamagui/text": "3.0.0-beta.643.1",
45
+ "@tamagui/font-size": "3.0.0-beta.643.1",
46
+ "@tamagui/web": "3.0.0-beta.643.1"
48
47
  },
49
48
  "devDependencies": {
50
- "@tamagui/build": "2.7.7",
49
+ "@tamagui/build": "3.0.0-beta.643.1",
51
50
  "react": ">=19",
52
51
  "vitest": "4.0.4"
53
52
  },
package/src/Button.tsx CHANGED
@@ -1,224 +1,109 @@
1
- import { getFontSize } from '@tamagui/font-size'
2
- import { getButtonSized } from '@tamagui/get-button-sized'
3
- import { getIcon, useCurrentColor } from '@tamagui/helpers-tamagui'
4
- import { ButtonNestingContext, getElevation, themeableVariants } from '@tamagui/stacks'
1
+ import { useGetThemedIcon } from '@tamagui/helpers-tamagui'
2
+ import { ButtonNestingContext } from '@tamagui/stacks'
5
3
  import type { TextContextStyles, TextParentStyles } from '@tamagui/text'
6
- import { SizableText, wrapChildrenInText } from '@tamagui/text'
7
- import type { GetProps, SizeTokens, Token } from '@tamagui/web'
4
+ import { wrapChildrenInText } from '@tamagui/text'
5
+ import type { GetProps } from '@tamagui/web'
8
6
  import {
9
7
  createStyledContext,
10
- getTokenValue,
8
+ createStyledHOC,
11
9
  styled,
10
+ Text,
12
11
  useProps,
13
12
  View,
14
13
  withStaticProperties,
15
14
  } from '@tamagui/web'
16
- import type { FunctionComponent, JSX } from 'react'
15
+ import type { FunctionComponent, JSX, ReactNode } from 'react'
17
16
  import { useContext } from 'react'
18
17
 
19
- type ButtonVariant = 'outlined'
20
-
21
- type ButtonContextStyles = TextContextStyles & {
22
- size?: SizeTokens
23
- variant?: ButtonVariant
24
- elevation?: SizeTokens | number
25
- }
26
-
27
- const context = createStyledContext<ButtonContextStyles>({
28
- size: undefined,
29
- variant: undefined,
30
- color: undefined,
31
- elevation: undefined,
32
- ellipsis: undefined,
33
- fontFamily: undefined,
34
- fontSize: undefined,
35
- fontStyle: undefined,
36
- fontWeight: undefined,
37
- letterSpacing: undefined,
38
- maxFontSizeMultiplier: undefined,
39
- textAlign: undefined,
40
- })
18
+ const buttonContextKeys = [
19
+ 'color',
20
+ 'ellipsis',
21
+ 'fontFamily',
22
+ 'fontSize',
23
+ 'fontStyle',
24
+ 'fontWeight',
25
+ 'letterSpacing',
26
+ 'maxFontSizeMultiplier',
27
+ 'textAlign',
28
+ ] as const
29
+
30
+ export const ButtonContext = createStyledContext<
31
+ TextContextStyles,
32
+ (typeof buttonContextKeys)[number]
33
+ >(
34
+ {
35
+ color: undefined,
36
+ ellipsis: undefined,
37
+ fontFamily: undefined,
38
+ fontSize: undefined,
39
+ fontStyle: undefined,
40
+ fontWeight: undefined,
41
+ letterSpacing: undefined,
42
+ maxFontSizeMultiplier: undefined,
43
+ textAlign: undefined,
44
+ },
45
+ {
46
+ keys: buttonContextKeys,
47
+ }
48
+ )
41
49
 
42
- const Frame = styled(View, {
43
- context,
44
- name: 'Button',
50
+ export const ButtonFrame = styled(View, {
51
+ context: ButtonContext,
52
+ displayName: 'ButtonFrame',
45
53
  role: 'button',
46
54
  render: <button type="button" />,
47
55
  tabIndex: 0,
56
+ alignItems: 'center',
57
+ flexDirection: 'row',
58
+ flexWrap: 'nowrap',
59
+ justifyContent: 'center',
48
60
 
49
61
  variants: {
50
- unstyled: {
51
- true: {
52
- // reset browser <button> defaults
53
- outlineWidth: 0,
54
- borderWidth: 0,
55
- backgroundColor: 'transparent',
56
- },
57
- false: {
58
- size: '$true',
59
- justifyContent: 'center',
60
- alignItems: 'center',
61
- flexWrap: 'nowrap',
62
- flexDirection: 'row',
63
- backgroundColor: '$background',
64
- borderWidth: 1,
65
- borderColor: 'transparent',
66
-
67
- '$platform-web': {
68
- cursor: 'pointer',
69
- },
70
-
71
- hoverStyle: {
72
- backgroundColor: '$backgroundHover',
73
- borderColor: '$borderColorHover',
74
- },
75
-
76
- pressStyle: {
77
- backgroundColor: '$backgroundPress',
78
- borderColor: '$borderColorHover',
79
- },
80
-
81
- focusVisibleStyle: {
82
- outlineColor: '$outlineColor',
83
- outlineStyle: 'solid',
84
- outlineWidth: 2,
85
- },
86
- },
87
- },
88
-
89
- variant: {
90
- outlined:
91
- process.env.TAMAGUI_HEADLESS === '1'
92
- ? {}
93
- : {
94
- backgroundColor: 'transparent',
95
- borderWidth: 1,
96
- borderColor: '$borderColor',
97
-
98
- hoverStyle: {
99
- backgroundColor: 'transparent',
100
- borderColor: '$borderColorHover',
101
- },
102
-
103
- pressStyle: {
104
- backgroundColor: 'transparent',
105
- borderColor: '$borderColorPress',
106
- },
107
- },
108
- },
109
-
110
- circular: themeableVariants.circular,
111
-
112
- chromeless: themeableVariants.chromeless,
113
-
114
- size: {
115
- '...size': (val, extras) => {
116
- const buttonStyle = getButtonSized(val, extras)
117
- const gap = getTokenValue(val as Token)
118
- return {
119
- ...buttonStyle,
120
- gap,
121
- }
122
- },
123
- ':number': (val, extras) => {
124
- const buttonStyle = getButtonSized(val, extras)
125
- const gap = val * 0.4
126
- return {
127
- ...buttonStyle,
128
- gap,
129
- }
130
- },
131
- },
132
-
133
- elevation: {
134
- '...size': getElevation,
135
- ':number': getElevation,
136
- },
137
-
138
62
  disabled: {
139
63
  true: {
140
64
  pointerEvents: 'none',
141
- // @ts-ignore
142
- 'aria-disabled': true,
143
65
  },
144
66
  },
145
67
  } as const,
146
-
147
- defaultVariants: {
148
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
149
- },
150
68
  })
151
69
 
152
- const Text = styled(SizableText, {
153
- context,
154
-
155
- variants: {
156
- unstyled: {
157
- false: {
158
- userSelect: 'none',
159
- // flexGrow 1 leads to inconsistent native style where text pushes to start of view
160
- flexGrow: 0,
161
- flexShrink: 1,
162
- ellipsis: true,
163
- color: '$color',
164
-
165
- '$platform-web': {
166
- cursor: 'pointer',
167
- },
168
- },
169
- },
170
- } as const,
171
-
172
- defaultVariants: {
173
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
174
- },
70
+ export const ButtonText = styled(Text, {
71
+ context: ButtonContext,
72
+ displayName: 'ButtonText',
73
+ // flexGrow 1 pushes text to the start of its parent on native
74
+ flexGrow: 0,
75
+ flexShrink: 1,
175
76
  })
176
77
 
177
- const Icon = (props: {
178
- children: React.ReactNode
78
+ export type ButtonIconProps = {
79
+ children: ReactNode
80
+ color?: TextContextStyles['color']
179
81
  scaleIcon?: number
180
- size?: SizeTokens
181
- }) => {
182
- const { children, scaleIcon = 1, size } = props
183
- const styledContext = context.useStyledContext()
184
- if (!styledContext) {
185
- throw new Error('Button.Icon must be used within a Button')
186
- }
187
-
188
- const sizeToken = size ?? styledContext.size
189
- const iconColorProp =
190
- styledContext.color === 'unset' || typeof styledContext.color === 'number'
191
- ? undefined
192
- : styledContext.color
193
- const iconColor = useCurrentColor(iconColorProp)
194
-
195
- const iconSize =
196
- (typeof sizeToken === 'number' ? sizeToken * 0.5 : getFontSize(sizeToken as Token)) *
197
- scaleIcon
82
+ size?: number
83
+ }
198
84
 
199
- return getIcon(children, {
200
- size: iconSize,
201
- color: iconColor,
85
+ export const ButtonIcon = ({ children, color, scaleIcon = 1, size }: ButtonIconProps) => {
86
+ const styledContext = ButtonContext.useStyledContext()
87
+ const iconColor = color ?? styledContext?.color
88
+ const getThemedIcon = useGetThemedIcon({
89
+ color: iconColor === 'unset' || typeof iconColor === 'number' ? undefined : iconColor,
90
+ size: size === undefined ? undefined : size * scaleIcon,
202
91
  })
203
- }
204
92
 
205
- export const ButtonContext = createStyledContext<{
206
- size?: SizeTokens
207
- variant?: ButtonVariant
208
- color?: ButtonContextStyles['color']
209
- }>({
210
- size: undefined,
211
- variant: undefined,
212
- color: undefined,
213
- })
93
+ return getThemedIcon(children)
94
+ }
214
95
 
215
- type IconProp = JSX.Element | FunctionComponent<{ color?: any; size?: any }> | null
96
+ type ButtonIconInput =
97
+ | JSX.Element
98
+ | FunctionComponent<{ color?: any; size?: any }>
99
+ | ((props: { color?: any; size?: any }) => ReactNode)
100
+ | null
216
101
 
217
- type ButtonExtraProps = TextParentStyles & {
218
- icon?: IconProp
219
- iconAfter?: IconProp
102
+ export type ButtonBehaviorProps = TextParentStyles & {
103
+ icon?: ButtonIconInput
104
+ iconAfter?: ButtonIconInput
105
+ iconSize?: number
220
106
  scaleIcon?: number
221
- iconSize?: SizeTokens
222
107
 
223
108
  // native button html props
224
109
  type?: 'submit' | 'reset' | 'button'
@@ -232,132 +117,189 @@ type ButtonExtraProps = TextParentStyles & {
232
117
  value?: string | readonly string[] | number
233
118
  }
234
119
 
235
- const ButtonComponent = Frame.styleable<ButtonExtraProps>((propsIn, ref) => {
236
- const isNested = useContext(ButtonNestingContext)
120
+ export type UseButtonOptions = {
121
+ Text?: any
122
+ iconColor?: TextContextStyles['color']
123
+ iconSize?: number
124
+ textProps?: Record<string, unknown>
125
+ }
237
126
 
238
- // Process props through useProps to expand shorthands (like br -> borderRadius)
127
+ const buttonInternalPropNames = [
128
+ 'children',
129
+ 'color',
130
+ 'disabled',
131
+ 'ellipsis',
132
+ 'fontFamily',
133
+ 'fontSize',
134
+ 'fontStyle',
135
+ 'fontWeight',
136
+ 'icon',
137
+ 'iconAfter',
138
+ 'iconSize',
139
+ 'letterSpacing',
140
+ 'maxFontSizeMultiplier',
141
+ 'noTextWrap',
142
+ 'render',
143
+ 'scaleIcon',
144
+ 'textAlign',
145
+ 'textProps',
146
+ ] as const
147
+
148
+ export function useButton<Props extends ButtonBehaviorProps>(
149
+ propsIn: Props,
150
+ {
151
+ Text = ButtonText,
152
+ iconColor: iconColorOption,
153
+ iconSize: iconSizeOption,
154
+ textProps: textPropsOption,
155
+ }: UseButtonOptions = {}
156
+ ) {
157
+ const isNested = useContext(ButtonNestingContext)
158
+ const styledContext = ButtonContext.useStyledContext()
239
159
  const processedProps = useProps(propsIn, {
240
160
  noNormalize: true,
241
161
  noExpand: true,
242
- })
162
+ }) as Props & {
163
+ accessibilityRole?: string
164
+ children?: ReactNode
165
+ color?: TextContextStyles['color']
166
+ disabled?: boolean
167
+ render?: unknown
168
+ role?: string
169
+ size?: unknown
170
+ }
243
171
 
244
172
  const {
245
173
  children,
246
- iconSize,
247
- icon,
248
- iconAfter,
249
- scaleIcon = 1,
250
- noTextWrap,
251
- textProps,
252
174
  color,
175
+ disabled,
253
176
  ellipsis,
254
177
  fontFamily,
255
178
  fontSize,
256
179
  fontStyle,
257
180
  fontWeight,
181
+ icon,
182
+ iconAfter,
183
+ iconSize,
258
184
  letterSpacing,
259
185
  maxFontSizeMultiplier,
186
+ noTextWrap,
187
+ render,
188
+ scaleIcon = 1,
260
189
  textAlign,
261
- ...props
190
+ textProps,
262
191
  } = processedProps
263
192
 
264
- const size = propsIn.size || (propsIn.unstyled ? undefined : '$true')
265
-
266
- const styledContext = context.useStyledContext()
267
- const contextColor = color ?? propsIn.color ?? styledContext?.color
268
- const iconColorProp =
269
- contextColor === 'unset' || typeof contextColor === 'number'
270
- ? undefined
271
- : contextColor
272
- const iconColor = useCurrentColor(iconColorProp)
273
- const finalSize = iconSize ?? size ?? styledContext?.size
274
- const iconSizeNumber =
275
- (typeof finalSize === 'number' ? finalSize * 0.5 : getFontSize(finalSize as Token)) *
276
- scaleIcon
193
+ // useProps resolves styles for the component running the hook. This behavior
194
+ // HOC renders a styled frame, so its frame styles must keep the raw flat
195
+ // programs for the frame's own hover/press/focus state and merge pass.
196
+ const frameProps = { ...propsIn }
197
+ for (const key of buttonInternalPropNames) {
198
+ delete (frameProps as Record<string, any>)[key]
199
+ }
277
200
 
278
- const [themedIcon, themedIconAfter] = [icon, iconAfter].map((icon) => {
279
- if (!icon) return null
280
- return getIcon(icon, {
281
- size: iconSizeNumber,
282
- color: iconColor,
283
- // No marginLeft or marginRight needed - spacing is handled by the gap property in Frame's size variants
284
- })
201
+ const contextColor = color ?? styledContext?.color
202
+ const iconColor = iconColorOption ?? contextColor
203
+ const resolvedIconSize = iconSize ?? iconSizeOption
204
+ const getThemedIcon = useGetThemedIcon({
205
+ color: iconColor === 'unset' || typeof iconColor === 'number' ? undefined : iconColor,
206
+ size: resolvedIconSize === undefined ? undefined : resolvedIconSize * scaleIcon,
207
+ })
208
+ const [themedIcon, themedIconAfter] = [icon, iconAfter].map((item) => {
209
+ return item ? getThemedIcon(item) : null
285
210
  })
286
211
 
212
+ const textContext: TextContextStyles = {
213
+ color: contextColor,
214
+ ellipsis: ellipsis ?? styledContext?.ellipsis,
215
+ fontFamily: fontFamily ?? styledContext?.fontFamily,
216
+ fontSize: fontSize ?? styledContext?.fontSize,
217
+ fontStyle: fontStyle ?? styledContext?.fontStyle,
218
+ fontWeight: fontWeight ?? styledContext?.fontWeight,
219
+ letterSpacing: letterSpacing ?? styledContext?.letterSpacing,
220
+ maxFontSizeMultiplier: maxFontSizeMultiplier ?? styledContext?.maxFontSizeMultiplier,
221
+ textAlign: textAlign ?? styledContext?.textAlign,
222
+ }
287
223
  const wrappedChildren = wrapChildrenInText(
288
224
  Text,
289
225
  {
290
226
  children,
291
- color: contextColor,
292
- ellipsis: ellipsis ?? propsIn.ellipsis ?? styledContext?.ellipsis,
293
- fontFamily: fontFamily ?? propsIn.fontFamily ?? styledContext?.fontFamily,
294
- fontSize: fontSize ?? propsIn.fontSize ?? styledContext?.fontSize,
295
- fontStyle: fontStyle ?? propsIn.fontStyle ?? styledContext?.fontStyle,
296
- fontWeight: fontWeight ?? propsIn.fontWeight ?? styledContext?.fontWeight,
297
- letterSpacing:
298
- letterSpacing ?? propsIn.letterSpacing ?? styledContext?.letterSpacing,
299
- maxFontSizeMultiplier:
300
- maxFontSizeMultiplier ??
301
- propsIn.maxFontSizeMultiplier ??
302
- styledContext?.maxFontSizeMultiplier,
303
- noTextWrap: noTextWrap ?? propsIn.noTextWrap,
304
- textAlign: textAlign ?? propsIn.textAlign ?? styledContext?.textAlign,
305
- textProps: textProps ?? propsIn.textProps,
227
+ ...textContext,
228
+ noTextWrap,
229
+ textProps,
306
230
  },
307
231
  {
308
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
309
- size: finalSize ?? styledContext?.size,
232
+ ...textPropsOption,
233
+ ...(processedProps.size === undefined ? null : { size: processedProps.size }),
310
234
  }
311
235
  )
312
-
313
- const textContext: TextContextStyles = {
314
- color: contextColor,
315
- ellipsis: ellipsis ?? propsIn.ellipsis ?? styledContext?.ellipsis,
316
- fontFamily: fontFamily ?? propsIn.fontFamily ?? styledContext?.fontFamily,
317
- fontSize: fontSize ?? propsIn.fontSize ?? styledContext?.fontSize,
318
- fontStyle: fontStyle ?? propsIn.fontStyle ?? styledContext?.fontStyle,
319
- fontWeight: fontWeight ?? propsIn.fontWeight ?? styledContext?.fontWeight,
320
- letterSpacing: letterSpacing ?? propsIn.letterSpacing ?? styledContext?.letterSpacing,
321
- maxFontSizeMultiplier:
322
- maxFontSizeMultiplier ??
323
- propsIn.maxFontSizeMultiplier ??
324
- styledContext?.maxFontSizeMultiplier,
325
- textAlign: textAlign ?? propsIn.textAlign ?? styledContext?.textAlign,
326
- }
327
-
328
- const buttonContext: ButtonContextStyles = {
329
- ...styledContext,
330
- ...textContext,
331
- size: props.size ?? propsIn.size ?? styledContext?.size,
332
- variant: props.variant ?? propsIn.variant ?? styledContext?.variant,
333
- elevation: props.elevation ?? propsIn.elevation ?? styledContext?.elevation,
334
- }
335
-
336
- return (
337
- <ButtonNestingContext.Provider value={true}>
338
- <context.Provider {...buttonContext}>
339
- <Frame
340
- ref={ref}
341
- {...props}
342
- {...(isNested && { render: 'span' })}
343
- // pass resolved size to circular variant when no explicit size provided
344
- {...(props.circular && !propsIn.size && { size })}
345
- tabIndex={0}
346
- >
236
+ const resolvedRender =
237
+ render ??
238
+ (isNested ? (
239
+ 'span'
240
+ ) : processedProps.accessibilityRole === 'link' || processedProps.role === 'link' ? (
241
+ 'a'
242
+ ) : disabled ? (
243
+ <button type="button" disabled />
244
+ ) : undefined)
245
+
246
+ const props = {
247
+ ...frameProps,
248
+ ...(disabled && {
249
+ 'aria-disabled': true,
250
+ disabled: true,
251
+ tabIndex: -1,
252
+ }),
253
+ ...(resolvedRender === undefined ? null : { render: resolvedRender }),
254
+ ...(isNested && {
255
+ // a nested Button is a presentation part, not a control: strip the
256
+ // interactive semantics so we never place a focusable role=button (with
257
+ // its own press/keyboard handlers) inside the outer native <button>. that
258
+ // nesting is invalid html, adds an extra focus stop, and bubbles
259
+ // activation to the outer control. resolvedRender already makes it a span.
260
+ role: 'none',
261
+ tabIndex: -1,
262
+ 'aria-disabled': undefined,
263
+ disabled: undefined,
264
+ onPress: undefined,
265
+ onPressIn: undefined,
266
+ onPressOut: undefined,
267
+ onLongPress: undefined,
268
+ onClick: undefined,
269
+ onKeyDown: undefined,
270
+ onKeyUp: undefined,
271
+ }),
272
+ children: (
273
+ <ButtonNestingContext.Provider value={true}>
274
+ <ButtonContext.Provider {...textContext}>
347
275
  {themedIcon}
348
276
  {wrappedChildren}
349
277
  {themedIconAfter}
350
- </Frame>
351
- </context.Provider>
352
- </ButtonNestingContext.Provider>
353
- )
354
- })
278
+ </ButtonContext.Provider>
279
+ </ButtonNestingContext.Provider>
280
+ ),
281
+ } as Props
282
+
283
+ return {
284
+ isNested,
285
+ props,
286
+ }
287
+ }
288
+
289
+ const ButtonComponent = createStyledHOC(
290
+ ButtonFrame,
291
+ function Button(props: ButtonBehaviorProps, ref) {
292
+ const { props: buttonProps } = useButton(props)
293
+
294
+ return <ButtonFrame ref={ref} {...buttonProps} />
295
+ }
296
+ )
355
297
 
356
298
  export const Button = withStaticProperties(ButtonComponent, {
357
- Apply: context.Provider,
358
- Frame,
359
- Text,
360
- Icon,
299
+ Apply: ButtonContext.Provider,
300
+ Frame: ButtonFrame,
301
+ Icon: ButtonIcon,
302
+ Text: ButtonText,
361
303
  })
362
304
 
363
305
  export type ButtonProps = GetProps<typeof ButtonComponent>