@hanzogui/button 2.0.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/LICENSE +21 -0
- package/dist/cjs/Button.cjs +218 -0
- package/dist/cjs/Button.native.js +224 -0
- package/dist/cjs/Button.native.js.map +1 -0
- package/dist/cjs/Button.test.cjs +36 -0
- package/dist/cjs/Button.test.native.js +39 -0
- package/dist/cjs/Button.test.native.js.map +1 -0
- package/dist/cjs/index.cjs +18 -0
- package/dist/cjs/index.native.js +21 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/v1/Button.cjs +259 -0
- package/dist/cjs/v1/Button.native.js +265 -0
- package/dist/cjs/v1/Button.native.js.map +1 -0
- package/dist/cjs/v1/Button.test.cjs +9 -0
- package/dist/cjs/v1/Button.test.native.js +12 -0
- package/dist/cjs/v1/Button.test.native.js.map +1 -0
- package/dist/cjs/v1/index.cjs +18 -0
- package/dist/cjs/v1/index.native.js +21 -0
- package/dist/cjs/v1/index.native.js.map +1 -0
- package/dist/esm/Button.mjs +194 -0
- package/dist/esm/Button.mjs.map +1 -0
- package/dist/esm/Button.native.js +197 -0
- package/dist/esm/Button.native.js.map +1 -0
- package/dist/esm/Button.test.mjs +37 -0
- package/dist/esm/Button.test.mjs.map +1 -0
- package/dist/esm/Button.test.native.js +37 -0
- package/dist/esm/Button.test.native.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +2 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/v1/Button.mjs +231 -0
- package/dist/esm/v1/Button.mjs.map +1 -0
- package/dist/esm/v1/Button.native.js +234 -0
- package/dist/esm/v1/Button.native.js.map +1 -0
- package/dist/esm/v1/Button.test.mjs +10 -0
- package/dist/esm/v1/Button.test.mjs.map +1 -0
- package/dist/esm/v1/Button.test.native.js +10 -0
- package/dist/esm/v1/Button.test.native.js.map +1 -0
- package/dist/esm/v1/index.mjs +2 -0
- package/dist/esm/v1/index.mjs.map +1 -0
- package/dist/esm/v1/index.native.js +2 -0
- package/dist/esm/v1/index.native.js.map +1 -0
- package/dist/jsx/Button.mjs +194 -0
- package/dist/jsx/Button.mjs.map +1 -0
- package/dist/jsx/Button.native.js +224 -0
- package/dist/jsx/Button.native.js.map +1 -0
- package/dist/jsx/Button.test.mjs +37 -0
- package/dist/jsx/Button.test.mjs.map +1 -0
- package/dist/jsx/Button.test.native.js +39 -0
- package/dist/jsx/Button.test.native.js.map +1 -0
- package/dist/jsx/index.js +2 -0
- package/dist/jsx/index.js.map +1 -0
- package/dist/jsx/index.mjs +2 -0
- package/dist/jsx/index.mjs.map +1 -0
- package/dist/jsx/index.native.js +21 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/dist/jsx/v1/Button.mjs +231 -0
- package/dist/jsx/v1/Button.mjs.map +1 -0
- package/dist/jsx/v1/Button.native.js +265 -0
- package/dist/jsx/v1/Button.native.js.map +1 -0
- package/dist/jsx/v1/Button.test.mjs +10 -0
- package/dist/jsx/v1/Button.test.mjs.map +1 -0
- package/dist/jsx/v1/Button.test.native.js +12 -0
- package/dist/jsx/v1/Button.test.native.js.map +1 -0
- package/dist/jsx/v1/index.mjs +2 -0
- package/dist/jsx/v1/index.mjs.map +1 -0
- package/dist/jsx/v1/index.native.js +21 -0
- package/dist/jsx/v1/index.native.js.map +1 -0
- package/package.json +59 -0
- package/src/Button.test.tsx +45 -0
- package/src/Button.tsx +272 -0
- package/src/index.ts +1 -0
- package/src/v1/Button.test.tsx +21 -0
- package/src/v1/Button.tsx +336 -0
- package/src/v1/index.ts +1 -0
- package/types/Button.d.ts +123 -0
- package/types/Button.test.d.ts +2 -0
- package/types/index.d.ts +2 -0
- package/types/v1/Button.d.ts +302 -0
- package/types/v1/Button.test.d.ts +2 -0
- package/types/v1/index.d.ts +2 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getDefaultGuiConfig } from '@hanzogui/config-default'
|
|
2
|
+
import { createGui } from '@hanzogui/core'
|
|
3
|
+
import { describe, expect, test } from 'vitest'
|
|
4
|
+
import { Button } from './Button'
|
|
5
|
+
|
|
6
|
+
const conf = createGui(getDefaultGuiConfig())
|
|
7
|
+
|
|
8
|
+
describe('Button', () => {
|
|
9
|
+
test(`123`, () => {
|
|
10
|
+
expect(true).toBeTruthy()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// type tests for native button props (issue #3914)
|
|
14
|
+
test('accepts native button html props', () => {
|
|
15
|
+
// these should type check without errors
|
|
16
|
+
const _submitBtn = <Button type="submit">Submit</Button>
|
|
17
|
+
const _resetBtn = <Button type="reset">Reset</Button>
|
|
18
|
+
const _buttonBtn = <Button type="button">Button</Button>
|
|
19
|
+
const _formBtn = (
|
|
20
|
+
<Button
|
|
21
|
+
type="submit"
|
|
22
|
+
form="myForm"
|
|
23
|
+
formAction="/submit"
|
|
24
|
+
formMethod="post"
|
|
25
|
+
formTarget="_blank"
|
|
26
|
+
formNoValidate
|
|
27
|
+
name="submitBtn"
|
|
28
|
+
value="submit"
|
|
29
|
+
>
|
|
30
|
+
Submit
|
|
31
|
+
</Button>
|
|
32
|
+
)
|
|
33
|
+
expect(true).toBeTruthy()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// test(`Adapts to a when given accessibilityRole="link"`, async () => {
|
|
37
|
+
// const { container } = render(
|
|
38
|
+
// <GuiProvider config={conf} defaultTheme="light">
|
|
39
|
+
// <Button href="http://google.com" accessibilityRole="link" />
|
|
40
|
+
// </GuiProvider>
|
|
41
|
+
// )
|
|
42
|
+
|
|
43
|
+
// expect(container.firstChild).toMatchSnapshot()
|
|
44
|
+
// })
|
|
45
|
+
})
|
package/src/Button.tsx
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { getFontSize } from '@hanzogui/font-size'
|
|
2
|
+
import { getButtonSized } from '@hanzogui/get-button-sized'
|
|
3
|
+
import { getIcon, useCurrentColor } from '@hanzogui/helpers'
|
|
4
|
+
import { ButtonNestingContext, getElevation, themeableVariants } from '@hanzogui/stacks'
|
|
5
|
+
import { SizableText, wrapChildrenInText } from '@hanzogui/text'
|
|
6
|
+
import type { ColorTokens, GetProps, SizeTokens, Token } from '@hanzogui/web'
|
|
7
|
+
import {
|
|
8
|
+
createStyledContext,
|
|
9
|
+
getTokenValue,
|
|
10
|
+
styled,
|
|
11
|
+
useProps,
|
|
12
|
+
View,
|
|
13
|
+
withStaticProperties,
|
|
14
|
+
} from '@hanzogui/web'
|
|
15
|
+
import type { FunctionComponent, JSX } from 'react'
|
|
16
|
+
import { useContext } from 'react'
|
|
17
|
+
|
|
18
|
+
type ButtonVariant = 'outlined'
|
|
19
|
+
|
|
20
|
+
const context = createStyledContext<{
|
|
21
|
+
size?: SizeTokens
|
|
22
|
+
variant?: ButtonVariant
|
|
23
|
+
color?: ColorTokens | string
|
|
24
|
+
elevation?: SizeTokens | number
|
|
25
|
+
}>({
|
|
26
|
+
size: undefined,
|
|
27
|
+
variant: undefined,
|
|
28
|
+
color: undefined,
|
|
29
|
+
elevation: undefined,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const Frame = styled(View, {
|
|
33
|
+
context,
|
|
34
|
+
name: 'Button',
|
|
35
|
+
role: 'button',
|
|
36
|
+
render: <button type="button" />,
|
|
37
|
+
tabIndex: 0,
|
|
38
|
+
|
|
39
|
+
variants: {
|
|
40
|
+
unstyled: {
|
|
41
|
+
false: {
|
|
42
|
+
size: '$true',
|
|
43
|
+
justifyContent: 'center',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
flexWrap: 'nowrap',
|
|
46
|
+
flexDirection: 'row',
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
backgroundColor: '$background',
|
|
49
|
+
borderWidth: 1,
|
|
50
|
+
borderColor: 'transparent',
|
|
51
|
+
|
|
52
|
+
hoverStyle: {
|
|
53
|
+
backgroundColor: '$backgroundHover',
|
|
54
|
+
borderColor: '$borderColorHover',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
pressStyle: {
|
|
58
|
+
backgroundColor: '$backgroundPress',
|
|
59
|
+
borderColor: '$borderColorHover',
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
focusVisibleStyle: {
|
|
63
|
+
outlineColor: '$outlineColor',
|
|
64
|
+
outlineStyle: 'solid',
|
|
65
|
+
outlineWidth: 2,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
variant: {
|
|
71
|
+
outlined:
|
|
72
|
+
process.env.HANZO_GUI_HEADLESS === '1'
|
|
73
|
+
? {}
|
|
74
|
+
: {
|
|
75
|
+
backgroundColor: 'transparent',
|
|
76
|
+
borderWidth: 1,
|
|
77
|
+
borderColor: '$borderColor',
|
|
78
|
+
|
|
79
|
+
hoverStyle: {
|
|
80
|
+
backgroundColor: 'transparent',
|
|
81
|
+
borderColor: '$borderColorHover',
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
pressStyle: {
|
|
85
|
+
backgroundColor: 'transparent',
|
|
86
|
+
borderColor: '$borderColorPress',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
circular: themeableVariants.circular,
|
|
92
|
+
|
|
93
|
+
chromeless: themeableVariants.chromeless,
|
|
94
|
+
|
|
95
|
+
size: {
|
|
96
|
+
'...size': (val, extras) => {
|
|
97
|
+
const buttonStyle = getButtonSized(val, extras)
|
|
98
|
+
const gap = getTokenValue(val as Token)
|
|
99
|
+
return {
|
|
100
|
+
...buttonStyle,
|
|
101
|
+
gap,
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
':number': (val, extras) => {
|
|
105
|
+
const buttonStyle = getButtonSized(val, extras)
|
|
106
|
+
const gap = val * 0.4
|
|
107
|
+
return {
|
|
108
|
+
...buttonStyle,
|
|
109
|
+
gap,
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
elevation: {
|
|
115
|
+
'...size': getElevation,
|
|
116
|
+
':number': getElevation,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
disabled: {
|
|
120
|
+
true: {
|
|
121
|
+
pointerEvents: 'none',
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
'aria-disabled': true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
} as const,
|
|
127
|
+
|
|
128
|
+
defaultVariants: {
|
|
129
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const Text = styled(SizableText, {
|
|
134
|
+
context,
|
|
135
|
+
|
|
136
|
+
variants: {
|
|
137
|
+
unstyled: {
|
|
138
|
+
false: {
|
|
139
|
+
userSelect: 'none',
|
|
140
|
+
cursor: 'pointer',
|
|
141
|
+
// flexGrow 1 leads to inconsistent native style where text pushes to start of view
|
|
142
|
+
flexGrow: 0,
|
|
143
|
+
flexShrink: 1,
|
|
144
|
+
ellipsis: true,
|
|
145
|
+
color: '$color',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
} as const,
|
|
149
|
+
|
|
150
|
+
defaultVariants: {
|
|
151
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const Icon = (props: {
|
|
156
|
+
children: React.ReactNode
|
|
157
|
+
scaleIcon?: number
|
|
158
|
+
size?: SizeTokens
|
|
159
|
+
}) => {
|
|
160
|
+
const { children, scaleIcon = 1, size } = props
|
|
161
|
+
const styledContext = context.useStyledContext()
|
|
162
|
+
if (!styledContext) {
|
|
163
|
+
throw new Error('Button.Icon must be used within a Button')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sizeToken = size ?? styledContext.size
|
|
167
|
+
const iconColor = useCurrentColor(styledContext.color)
|
|
168
|
+
|
|
169
|
+
const iconSize =
|
|
170
|
+
(typeof sizeToken === 'number' ? sizeToken * 0.5 : getFontSize(sizeToken as Token)) *
|
|
171
|
+
scaleIcon
|
|
172
|
+
|
|
173
|
+
return getIcon(children, {
|
|
174
|
+
size: iconSize,
|
|
175
|
+
color: iconColor,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const ButtonContext = createStyledContext<{
|
|
180
|
+
size?: SizeTokens
|
|
181
|
+
variant?: ButtonVariant
|
|
182
|
+
color?: ColorTokens | string
|
|
183
|
+
}>({
|
|
184
|
+
size: undefined,
|
|
185
|
+
variant: undefined,
|
|
186
|
+
color: undefined,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
type IconProp = JSX.Element | FunctionComponent<{ color?: any; size?: any }> | null
|
|
190
|
+
|
|
191
|
+
type ButtonExtraProps = {
|
|
192
|
+
icon?: IconProp
|
|
193
|
+
iconAfter?: IconProp
|
|
194
|
+
scaleIcon?: number
|
|
195
|
+
iconSize?: SizeTokens
|
|
196
|
+
|
|
197
|
+
// native button html props
|
|
198
|
+
type?: 'submit' | 'reset' | 'button'
|
|
199
|
+
form?: string
|
|
200
|
+
formAction?: string
|
|
201
|
+
formEncType?: string
|
|
202
|
+
formMethod?: string
|
|
203
|
+
formNoValidate?: boolean
|
|
204
|
+
formTarget?: string
|
|
205
|
+
name?: string
|
|
206
|
+
value?: string | readonly string[] | number
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ButtonComponent = Frame.styleable<ButtonExtraProps>((propsIn, ref) => {
|
|
210
|
+
const isNested = useContext(ButtonNestingContext)
|
|
211
|
+
|
|
212
|
+
// Process props through useProps to expand shorthands (like br -> borderRadius)
|
|
213
|
+
const processedProps = useProps(propsIn, {
|
|
214
|
+
noNormalize: true,
|
|
215
|
+
noExpand: true,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const { children, iconSize, icon, iconAfter, scaleIcon = 1, ...props } = processedProps
|
|
219
|
+
|
|
220
|
+
const size = propsIn.size || (propsIn.unstyled ? undefined : '$true')
|
|
221
|
+
|
|
222
|
+
const styledContext = context.useStyledContext()
|
|
223
|
+
const iconColor = useCurrentColor(styledContext?.color)
|
|
224
|
+
const finalSize = iconSize ?? size ?? styledContext?.size
|
|
225
|
+
const iconSizeNumber =
|
|
226
|
+
(typeof finalSize === 'number' ? finalSize * 0.5 : getFontSize(finalSize as Token)) *
|
|
227
|
+
scaleIcon
|
|
228
|
+
|
|
229
|
+
const [themedIcon, themedIconAfter] = [icon, iconAfter].map((icon) => {
|
|
230
|
+
if (!icon) return null
|
|
231
|
+
return getIcon(icon, {
|
|
232
|
+
size: iconSizeNumber,
|
|
233
|
+
color: iconColor,
|
|
234
|
+
// No marginLeft or marginRight needed - spacing is handled by the gap property in Frame's size variants
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const wrappedChildren = wrapChildrenInText(
|
|
239
|
+
Text,
|
|
240
|
+
{ children },
|
|
241
|
+
{
|
|
242
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
243
|
+
size: finalSize ?? styledContext?.size,
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<ButtonNestingContext.Provider value={true}>
|
|
249
|
+
<Frame
|
|
250
|
+
ref={ref}
|
|
251
|
+
{...props}
|
|
252
|
+
{...(isNested && { render: 'span' })}
|
|
253
|
+
// Pass resolved size to circular variant when no explicit size provided
|
|
254
|
+
{...(props.circular && !propsIn.size && { size })}
|
|
255
|
+
tabIndex={0}
|
|
256
|
+
>
|
|
257
|
+
{themedIcon}
|
|
258
|
+
{wrappedChildren}
|
|
259
|
+
{themedIconAfter}
|
|
260
|
+
</Frame>
|
|
261
|
+
</ButtonNestingContext.Provider>
|
|
262
|
+
)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
export const Button = withStaticProperties(ButtonComponent, {
|
|
266
|
+
Apply: context.Provider,
|
|
267
|
+
Frame,
|
|
268
|
+
Text,
|
|
269
|
+
Icon,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
export type ButtonProps = GetProps<typeof ButtonComponent>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Button'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getDefaultGuiConfig } from '@hanzogui/config-default'
|
|
2
|
+
import { createGui } from '@hanzogui/core'
|
|
3
|
+
import { describe, expect, test } from 'vitest'
|
|
4
|
+
|
|
5
|
+
const conf = createGui(getDefaultGuiConfig())
|
|
6
|
+
|
|
7
|
+
describe('Button', () => {
|
|
8
|
+
test(`123`, () => {
|
|
9
|
+
expect(true).toBeTruthy()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// test(`Adapts to a when given accessibilityRole="link"`, async () => {
|
|
13
|
+
// const { container } = render(
|
|
14
|
+
// <GuiProvider config={conf} defaultTheme="light">
|
|
15
|
+
// <Button href="http://google.com" accessibilityRole="link" />
|
|
16
|
+
// </GuiProvider>
|
|
17
|
+
// )
|
|
18
|
+
|
|
19
|
+
// expect(container.firstChild).toMatchSnapshot()
|
|
20
|
+
// })
|
|
21
|
+
})
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { getFontSize } from '@hanzogui/font-size'
|
|
2
|
+
import { getButtonSized } from '@hanzogui/get-button-sized'
|
|
3
|
+
import { withStaticProperties } from '@hanzogui/helpers'
|
|
4
|
+
import { useGetThemedIcon } from '@hanzogui/helpers'
|
|
5
|
+
import { ButtonNestingContext, ThemeableStack } from '@hanzogui/stacks'
|
|
6
|
+
import type { TextContextStyles, TextParentStyles } from '@hanzogui/text'
|
|
7
|
+
import { SizableText, wrapChildrenInText } from '@hanzogui/text'
|
|
8
|
+
import type { FontSizeTokens, GetProps, SizeTokens, ThemeableProps } from '@hanzogui/web'
|
|
9
|
+
import { createStyledContext, getVariableValue, styled, useProps } from '@hanzogui/web'
|
|
10
|
+
import type { FunctionComponent, JSX } from 'react'
|
|
11
|
+
import { useContext } from 'react'
|
|
12
|
+
import { spacedChildren } from '@hanzogui/spacer'
|
|
13
|
+
|
|
14
|
+
type ButtonVariant = 'outlined'
|
|
15
|
+
|
|
16
|
+
export const ButtonContext = createStyledContext<
|
|
17
|
+
Partial<
|
|
18
|
+
TextContextStyles & {
|
|
19
|
+
size: SizeTokens
|
|
20
|
+
variant?: ButtonVariant
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
>({
|
|
24
|
+
// keeping these here means they work with styled() passing down color to text
|
|
25
|
+
color: undefined,
|
|
26
|
+
ellipsis: undefined,
|
|
27
|
+
fontFamily: undefined,
|
|
28
|
+
fontSize: undefined,
|
|
29
|
+
fontStyle: undefined,
|
|
30
|
+
fontWeight: undefined,
|
|
31
|
+
letterSpacing: undefined,
|
|
32
|
+
maxFontSizeMultiplier: undefined,
|
|
33
|
+
size: undefined,
|
|
34
|
+
textAlign: undefined,
|
|
35
|
+
variant: undefined,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
type ButtonIconProps = { color?: any; size?: any }
|
|
39
|
+
type IconProp =
|
|
40
|
+
| JSX.Element
|
|
41
|
+
| FunctionComponent<ButtonIconProps>
|
|
42
|
+
| ((props: ButtonIconProps) => any)
|
|
43
|
+
| null
|
|
44
|
+
|
|
45
|
+
type ButtonExtraProps = TextParentStyles &
|
|
46
|
+
ThemeableProps & {
|
|
47
|
+
/**
|
|
48
|
+
* add icon before, passes color and size automatically if Component
|
|
49
|
+
*/
|
|
50
|
+
icon?: IconProp
|
|
51
|
+
/**
|
|
52
|
+
* add icon after, passes color and size automatically if Component
|
|
53
|
+
*/
|
|
54
|
+
iconAfter?: IconProp
|
|
55
|
+
/**
|
|
56
|
+
* adjust icon relative to size
|
|
57
|
+
*
|
|
58
|
+
* @default 1
|
|
59
|
+
*/
|
|
60
|
+
scaleIcon?: number
|
|
61
|
+
/**
|
|
62
|
+
* make the spacing elements flex
|
|
63
|
+
*/
|
|
64
|
+
spaceFlex?: number | boolean
|
|
65
|
+
/**
|
|
66
|
+
* adjust internal space relative to icon size
|
|
67
|
+
*/
|
|
68
|
+
scaleSpace?: number
|
|
69
|
+
/**
|
|
70
|
+
* remove default styles
|
|
71
|
+
*/
|
|
72
|
+
unstyled?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type ButtonProps = ButtonExtraProps & GetProps<typeof ButtonFrame>
|
|
76
|
+
|
|
77
|
+
const BUTTON_NAME = 'Button'
|
|
78
|
+
|
|
79
|
+
const ButtonFrame = styled(ThemeableStack, {
|
|
80
|
+
name: BUTTON_NAME,
|
|
81
|
+
render: 'button',
|
|
82
|
+
context: ButtonContext,
|
|
83
|
+
role: 'button',
|
|
84
|
+
focusable: true,
|
|
85
|
+
|
|
86
|
+
variants: {
|
|
87
|
+
unstyled: {
|
|
88
|
+
false: {
|
|
89
|
+
size: '$true',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
flexWrap: 'nowrap',
|
|
93
|
+
flexDirection: 'row',
|
|
94
|
+
cursor: 'pointer',
|
|
95
|
+
hoverTheme: true,
|
|
96
|
+
pressTheme: true,
|
|
97
|
+
backgroundColor: '$background',
|
|
98
|
+
borderWidth: 1,
|
|
99
|
+
borderColor: 'transparent',
|
|
100
|
+
|
|
101
|
+
focusVisibleStyle: {
|
|
102
|
+
outlineColor: '$outlineColor',
|
|
103
|
+
outlineStyle: 'solid',
|
|
104
|
+
outlineWidth: 2,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
variant: {
|
|
110
|
+
outlined: {
|
|
111
|
+
backgroundColor: 'transparent',
|
|
112
|
+
borderWidth: 2,
|
|
113
|
+
borderColor: '$borderColor',
|
|
114
|
+
|
|
115
|
+
hoverStyle: {
|
|
116
|
+
backgroundColor: 'transparent',
|
|
117
|
+
borderColor: '$borderColorHover',
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
pressStyle: {
|
|
121
|
+
backgroundColor: 'transparent',
|
|
122
|
+
borderColor: '$borderColorPress',
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
focusVisibleStyle: {
|
|
126
|
+
backgroundColor: 'transparent',
|
|
127
|
+
borderColor: '$borderColorFocus',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
size: {
|
|
133
|
+
'...size': getButtonSized,
|
|
134
|
+
':number': getButtonSized,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
disabled: {
|
|
138
|
+
true: {
|
|
139
|
+
pointerEvents: 'none',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} as const,
|
|
143
|
+
|
|
144
|
+
defaultVariants: {
|
|
145
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const ButtonText = styled(SizableText, {
|
|
150
|
+
name: 'Button',
|
|
151
|
+
context: ButtonContext,
|
|
152
|
+
|
|
153
|
+
variants: {
|
|
154
|
+
unstyled: {
|
|
155
|
+
false: {
|
|
156
|
+
userSelect: 'none',
|
|
157
|
+
cursor: 'pointer',
|
|
158
|
+
// flexGrow 1 leads to inconsistent native style where text pushes to start of view
|
|
159
|
+
flexGrow: 0,
|
|
160
|
+
flexShrink: 1,
|
|
161
|
+
ellipsis: true,
|
|
162
|
+
color: '$color',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
} as const,
|
|
166
|
+
|
|
167
|
+
defaultVariants: {
|
|
168
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const ButtonIcon = (props: { children: React.ReactNode; scaleIcon?: number }) => {
|
|
173
|
+
const { children, scaleIcon = 1 } = props
|
|
174
|
+
const { size, color } = useContext(ButtonContext)
|
|
175
|
+
|
|
176
|
+
const iconSize =
|
|
177
|
+
(typeof size === 'number' ? size * 0.5 : getFontSize(size as FontSizeTokens)) *
|
|
178
|
+
scaleIcon
|
|
179
|
+
|
|
180
|
+
const getThemedIcon = useGetThemedIcon({ size: iconSize, color: color as any })
|
|
181
|
+
return getThemedIcon(children)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const ButtonComponent = ButtonFrame.styleable<ButtonExtraProps>(
|
|
185
|
+
function Button(props, ref) {
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
const { props: buttonProps } = useButton(props)
|
|
188
|
+
|
|
189
|
+
return <ButtonFrame data-disable-theme {...buttonProps} ref={ref} />
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
/**
|
|
193
|
+
* @summary A Button is a clickable element that can be used to trigger actions such as submitting forms, navigating to other pages, or performing other actions.
|
|
194
|
+
* @see — Docs https://gui.dev/ui/button
|
|
195
|
+
*/
|
|
196
|
+
const Button = withStaticProperties(ButtonComponent, {
|
|
197
|
+
Text: ButtonText,
|
|
198
|
+
Icon: ButtonIcon,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @deprecated Instead of useButton, see the Button docs for the newer and much improved Advanced customization pattern: https://gui.dev/docs/components/button
|
|
203
|
+
*/
|
|
204
|
+
function useButton<Props extends ButtonProps>(
|
|
205
|
+
{ textProps, ...propsIn }: Props,
|
|
206
|
+
{ Text = Button.Text }: { Text: any } = { Text: Button.Text }
|
|
207
|
+
) {
|
|
208
|
+
const isNested = useContext(ButtonNestingContext)
|
|
209
|
+
const propsActive = useProps(propsIn, {
|
|
210
|
+
noNormalize: true,
|
|
211
|
+
noExpand: true,
|
|
212
|
+
}) as any as ButtonProps
|
|
213
|
+
|
|
214
|
+
// careful not to destructure and re-order props, order is important
|
|
215
|
+
const {
|
|
216
|
+
icon,
|
|
217
|
+
iconAfter,
|
|
218
|
+
gap,
|
|
219
|
+
spaceFlex,
|
|
220
|
+
scaleIcon = 1,
|
|
221
|
+
scaleSpace = 0.66,
|
|
222
|
+
noTextWrap,
|
|
223
|
+
fontFamily,
|
|
224
|
+
fontSize,
|
|
225
|
+
fontWeight,
|
|
226
|
+
fontStyle,
|
|
227
|
+
letterSpacing,
|
|
228
|
+
render,
|
|
229
|
+
ellipsis,
|
|
230
|
+
maxFontSizeMultiplier,
|
|
231
|
+
|
|
232
|
+
...restProps
|
|
233
|
+
} = propsActive
|
|
234
|
+
|
|
235
|
+
const size = propsActive.size || (propsActive.unstyled ? undefined : '$true')
|
|
236
|
+
|
|
237
|
+
const color = propsActive.color as any
|
|
238
|
+
|
|
239
|
+
const iconSize =
|
|
240
|
+
(typeof size === 'number'
|
|
241
|
+
? size * 0.5
|
|
242
|
+
: getFontSize(size as FontSizeTokens, {
|
|
243
|
+
font: fontFamily?.[0] === '$' ? (fontFamily as any) : undefined,
|
|
244
|
+
})) * scaleIcon
|
|
245
|
+
|
|
246
|
+
const getThemedIcon = useGetThemedIcon({
|
|
247
|
+
size: iconSize,
|
|
248
|
+
color,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const [themedIcon, themedIconAfter] = [icon, iconAfter].map(getThemedIcon)
|
|
252
|
+
const spaceSize = gap ?? getVariableValue(iconSize) * scaleSpace
|
|
253
|
+
const contents = noTextWrap
|
|
254
|
+
? [propsIn.children]
|
|
255
|
+
: wrapChildrenInText(
|
|
256
|
+
Text,
|
|
257
|
+
{
|
|
258
|
+
children: propsIn.children,
|
|
259
|
+
fontFamily,
|
|
260
|
+
fontSize,
|
|
261
|
+
textProps,
|
|
262
|
+
fontWeight,
|
|
263
|
+
fontStyle,
|
|
264
|
+
letterSpacing,
|
|
265
|
+
ellipsis,
|
|
266
|
+
maxFontSizeMultiplier,
|
|
267
|
+
},
|
|
268
|
+
Text === ButtonText && propsActive.unstyled !== true
|
|
269
|
+
? {
|
|
270
|
+
unstyled: process.env.HANZO_GUI_HEADLESS === '1',
|
|
271
|
+
size,
|
|
272
|
+
}
|
|
273
|
+
: undefined
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const inner = spacedChildren({
|
|
277
|
+
// a bit arbitrary but scaling to font size is necessary so long as button does
|
|
278
|
+
space: spaceSize,
|
|
279
|
+
spaceFlex,
|
|
280
|
+
ensureKeys: true,
|
|
281
|
+
direction:
|
|
282
|
+
propsActive.flexDirection === 'column' ||
|
|
283
|
+
propsActive.flexDirection === 'column-reverse'
|
|
284
|
+
? 'vertical'
|
|
285
|
+
: 'horizontal',
|
|
286
|
+
// for keys to stay the same we keep indices as similar a possible
|
|
287
|
+
// so even if icons are undefined we still pass them
|
|
288
|
+
children: [themedIcon, ...contents, themedIconAfter],
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const props = {
|
|
292
|
+
size,
|
|
293
|
+
...(propsIn.disabled && {
|
|
294
|
+
// in rnw - false still has keyboard tabIndex, undefined = not actually focusable
|
|
295
|
+
focusable: undefined,
|
|
296
|
+
// even with tabIndex unset, it will keep focusVisibleStyle on web so disable it here
|
|
297
|
+
focusVisibleStyle: {
|
|
298
|
+
borderColor: '$background',
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
// fixes SSR issue + DOM nesting issue of not allowing button in button
|
|
302
|
+
render:
|
|
303
|
+
render ??
|
|
304
|
+
(isNested
|
|
305
|
+
? 'span'
|
|
306
|
+
: // defaults to <a /> when accessibilityRole = link
|
|
307
|
+
// see https://github.com/gui/gui/issues/505
|
|
308
|
+
propsActive.accessibilityRole === 'link' || propsActive.role === 'link'
|
|
309
|
+
? 'a'
|
|
310
|
+
: 'button'),
|
|
311
|
+
|
|
312
|
+
...restProps,
|
|
313
|
+
|
|
314
|
+
children: (
|
|
315
|
+
<ButtonNestingContext.Provider value={true}>{inner}</ButtonNestingContext.Provider>
|
|
316
|
+
),
|
|
317
|
+
// forces it to be a runtime pressStyle so it passes through context text colors
|
|
318
|
+
disableClassName: true,
|
|
319
|
+
} as Props
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
spaceSize,
|
|
323
|
+
isNested,
|
|
324
|
+
props,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export {
|
|
329
|
+
Button,
|
|
330
|
+
ButtonFrame,
|
|
331
|
+
ButtonIcon,
|
|
332
|
+
ButtonText,
|
|
333
|
+
// legacy
|
|
334
|
+
useButton,
|
|
335
|
+
}
|
|
336
|
+
export type { ButtonProps }
|
package/src/v1/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Button'
|