|
1 | 1 | import React, {forwardRef} from 'react' |
2 | | -import Box from '../Box' |
3 | | -import styled from 'styled-components' |
4 | | -import sx, {merge, SxProp} from '../sx' |
5 | | -import {useTheme, Theme} from '../ThemeProvider' |
6 | | -import {VariantType, ButtonProps} from './types' |
| 2 | +import {ButtonProps} from './types' |
| 3 | +import ButtonBase from './button-base' |
7 | 4 |
|
8 | | -const TEXT_ROW_HEIGHT = '20px' // custom value off the scale |
9 | | - |
10 | | -const getVariantStyles = (variant: VariantType = 'default', theme?: Theme) => { |
11 | | - const style = { |
12 | | - default: { |
13 | | - color: 'btn.text', |
14 | | - backgroundColor: 'btn.bg', |
15 | | - boxShadow: `${theme?.shadows.btn.shadow}, ${theme?.shadows.btn.insetShadow}`, |
16 | | - '&:hover:not([disabled])': { |
17 | | - backgroundColor: 'btn.hoverBg' |
18 | | - }, |
19 | | - // focus must come before :active so that the active box shadow overrides |
20 | | - '&:focus:not([disabled])': { |
21 | | - boxShadow: `${theme?.shadows.btn.focusShadow}` |
22 | | - }, |
23 | | - '&:active:not([disabled])': { |
24 | | - backgroundColor: 'btn.selectedBg', |
25 | | - boxShadow: `${theme?.shadows.btn.shadowActive}` |
26 | | - }, |
27 | | - '&:disabled': { |
28 | | - color: 'primer.fg.disabled', |
29 | | - backgroundColor: 'btn.disabledBg' |
30 | | - } |
31 | | - }, |
32 | | - primary: { |
33 | | - color: 'btn.primary.text', |
34 | | - backgroundColor: 'btn.primary.bg', |
35 | | - borderColor: 'border.subtle', |
36 | | - boxShadow: `${theme?.shadows.btn.primary.shadow}`, |
37 | | - '&:hover:not([disabled])': { |
38 | | - color: 'btn.primary.hoverText', |
39 | | - backgroundColor: 'btn.primary.hoverBg' |
40 | | - }, |
41 | | - // focus must come before :active so that the active box shadow overrides |
42 | | - '&:focus:not([disabled])': { |
43 | | - boxShadow: `${theme?.shadows.btn.primary.focusShadow}` |
44 | | - }, |
45 | | - '&:active:not([disabled])': { |
46 | | - backgroundColor: 'btn.primary.selectedBg', |
47 | | - boxShadow: `${theme?.shadows.btn.primary.selectedShadow}` |
48 | | - }, |
49 | | - '&:disabled': { |
50 | | - color: 'btn.primary.disabledText', |
51 | | - backgroundColor: 'btn.primary.disabledBg' |
52 | | - }, |
53 | | - '[data-component="ButtonCounter"]': { |
54 | | - backgroundColor: 'btn.primary.counterBg', |
55 | | - color: 'btn.primary.text' |
56 | | - } |
57 | | - }, |
58 | | - danger: { |
59 | | - color: 'btn.danger.text', |
60 | | - backgroundColor: 'btn.bg', |
61 | | - boxShadow: `${theme?.shadows.btn.shadow}`, |
62 | | - '&:hover:not([disabled])': { |
63 | | - color: 'btn.danger.hoverText', |
64 | | - backgroundColor: 'btn.danger.hoverBg', |
65 | | - borderColor: 'btn.danger.hoverBorder', |
66 | | - boxShadow: `${theme?.shadows.btn.danger.hoverShadow}`, |
67 | | - '[data-component="ButtonCounter"]': { |
68 | | - backgroundColor: 'btn.danger.hoverCounterBg', |
69 | | - color: 'btn.danger.hoverText' |
70 | | - } |
71 | | - }, |
72 | | - // focus must come before :active so that the active box shadow overrides |
73 | | - '&:focus:not([disabled])': { |
74 | | - borderColor: 'btn.danger.focusBorder', |
75 | | - boxShadow: `${theme?.shadows.btn.danger.focusShadow}` |
76 | | - }, |
77 | | - '&:active:not([disabled])': { |
78 | | - color: 'btn.danger.selectedText', |
79 | | - backgroundColor: 'btn.danger.selectedBg', |
80 | | - boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`, |
81 | | - borderColor: 'btn.danger.selectedBorder' |
82 | | - }, |
83 | | - '&:disabled': { |
84 | | - color: 'btn.danger.disabledText', |
85 | | - backgroundColor: 'btn.danger.disabledBg', |
86 | | - borderColor: 'btn.danger.disabledBorder', |
87 | | - '[data-component="ButtonCounter"]': { |
88 | | - backgroundColor: 'btn.danger.disabledCounterBg' |
89 | | - } |
90 | | - }, |
91 | | - '[data-component="ButtonCounter"]': { |
92 | | - color: 'btn.danger.text', |
93 | | - backgroundColor: 'btn.danger.counterBg' |
94 | | - } |
95 | | - }, |
96 | | - invisible: { |
97 | | - color: 'accent.fg', |
98 | | - backgroundColor: 'transparent', |
99 | | - border: '0', |
100 | | - boxShadow: 'none', |
101 | | - '&:hover:not([disabled])': { |
102 | | - backgroundColor: 'btn.hoverBg' |
103 | | - }, |
104 | | - // focus must come before :active so that the active box shadow overrides |
105 | | - '&:focus:not([disabled])': { |
106 | | - boxShadow: `${theme?.shadows.btn.focusShadow}` |
107 | | - }, |
108 | | - '&:active:not([disabled])': { |
109 | | - backgroundColor: 'btn.selectedBg' |
110 | | - }, |
111 | | - '&:disabled': { |
112 | | - color: 'primer.fg.disabled' |
113 | | - } |
114 | | - }, |
115 | | - outline: { |
116 | | - color: 'btn.outline.text', |
117 | | - boxShadow: `${theme?.shadows.btn.shadow}`, |
118 | | - |
119 | | - '&:hover': { |
120 | | - color: 'btn.outline.hoverText', |
121 | | - backgroundColor: 'btn.outline.hoverBg', |
122 | | - borderColor: 'outline.hoverBorder', |
123 | | - boxShadow: `${theme?.shadows.btn.outline.hoverShadow}`, |
124 | | - '[data-component="ButtonCounter"]': { |
125 | | - backgroundColor: 'btn.outline.hoverCounterBg', |
126 | | - color: 'btn.outline.hoverText' |
127 | | - } |
128 | | - }, |
129 | | - // focus must come before :active so that the active box shadow overrides |
130 | | - '&:focus': { |
131 | | - borderColor: 'btn.outline.focusBorder', |
132 | | - boxShadow: `${theme?.shadows.btn.outline.focusShadow}` |
133 | | - }, |
134 | | - |
135 | | - '&:active:not([disabled])': { |
136 | | - color: 'btn.outline.selectedText', |
137 | | - backgroundColor: 'btn.outline.selectedBg', |
138 | | - boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`, |
139 | | - borderColor: 'btn.outline.selectedBorder' |
140 | | - }, |
141 | | - |
142 | | - '&:disabled': { |
143 | | - color: 'btn.outline.disabledText', |
144 | | - backgroundColor: 'btn.outline.disabledBg', |
145 | | - borderColor: 'btn.border', |
146 | | - '[data-component="ButtonCounter"]': { |
147 | | - backgroundColor: 'btn.outline.disabledCounterBg' |
148 | | - } |
149 | | - }, |
150 | | - '[data-component="ButtonCounter"]': { |
151 | | - backgroundColor: 'btn.outline.counterBg', |
152 | | - color: 'btn.outline.text' |
153 | | - } |
154 | | - } |
155 | | - } |
156 | | - return style[variant] |
157 | | -} |
158 | | - |
159 | | -const getSizeStyles = (size = 'medium', variant: VariantType = 'default', iconOnly: boolean) => { |
160 | | - let paddingY, paddingX, fontSize |
161 | | - switch (size) { |
162 | | - case 'small': |
163 | | - paddingY = 3 |
164 | | - paddingX = 12 |
165 | | - fontSize = 0 |
166 | | - break |
167 | | - case 'large': |
168 | | - paddingY = 9 |
169 | | - paddingX = 20 |
170 | | - fontSize = 2 |
171 | | - break |
172 | | - case 'medium': |
173 | | - default: |
174 | | - paddingY = 5 |
175 | | - paddingX = 16 |
176 | | - fontSize = 1 |
177 | | - } |
178 | | - if (iconOnly) { |
179 | | - paddingX = paddingY + 2 |
180 | | - } |
181 | | - if (variant === 'invisible') { |
182 | | - paddingY = paddingY + 1 |
183 | | - } |
184 | | - return { |
185 | | - paddingY: `${paddingY}px`, |
186 | | - paddingX: `${paddingX}px`, |
187 | | - fontSize, |
188 | | - '[data-component="ButtonCounter"]': { |
189 | | - fontSize |
190 | | - } |
191 | | - } |
192 | | -} |
193 | | - |
194 | | -const ButtonBase = styled.button<SxProp>(sx) |
195 | | - |
196 | | -const Button = forwardRef<HTMLButtonElement, ButtonProps>( |
197 | | - ({children, sx: sxProp = {}, ...props}, forwardedRef): JSX.Element => { |
198 | | - const { |
199 | | - icon: Icon, |
200 | | - leadingIcon: LeadingIcon, |
201 | | - trailingIcon: TrailingIcon, |
202 | | - variant = 'default', |
203 | | - size = 'medium' |
204 | | - } = props |
205 | | - const iconOnly = !!Icon |
206 | | - const {theme} = useTheme() |
207 | | - |
208 | | - const styles = { |
209 | | - borderRadius: '2', |
210 | | - border: '1px solid', |
211 | | - borderColor: theme?.colors.btn.border, |
212 | | - display: 'grid', |
213 | | - gridTemplateAreas: '"leadingIcon text trailingIcon"', |
214 | | - fontWeight: 'bold', |
215 | | - lineHeight: TEXT_ROW_HEIGHT, |
216 | | - whiteSpace: 'nowrap', |
217 | | - verticalAlign: 'middle', |
218 | | - cursor: 'pointer', |
219 | | - appearance: 'none', |
220 | | - userSelect: 'none', |
221 | | - textDecoration: 'none', |
222 | | - textAlign: 'center', |
223 | | - '& > :not(:last-child)': { |
224 | | - mr: '2' |
225 | | - }, |
226 | | - '&:focus': { |
227 | | - outline: 'none' |
228 | | - }, |
229 | | - '&:disabled': { |
230 | | - cursor: 'default' |
231 | | - }, |
232 | | - '&:disabled svg': { |
233 | | - opacity: '0.6' |
234 | | - }, |
235 | | - '[data-component="leadingIcon"]': { |
236 | | - gridArea: 'leadingIcon' |
237 | | - }, |
238 | | - '[data-component="text"]': { |
239 | | - gridArea: 'text' |
240 | | - }, |
241 | | - '[data-component="trailingIcon"]': { |
242 | | - gridArea: 'trailingIcon' |
243 | | - } |
244 | | - } |
245 | | - const iconWrapStyles = { |
246 | | - display: 'inline-block' |
247 | | - } |
248 | | - const sxStyles = merge.all([ |
249 | | - styles, |
250 | | - getSizeStyles(size, variant, iconOnly), |
251 | | - getVariantStyles(variant, theme), |
252 | | - sxProp as SxProp |
253 | | - ]) |
254 | | - return ( |
255 | | - <ButtonBase sx={sxStyles} ref={forwardedRef} {...props}> |
256 | | - {LeadingIcon && ( |
257 | | - <Box as="span" data-component="leadingIcon" sx={iconWrapStyles} aria-hidden={!iconOnly}> |
258 | | - <LeadingIcon /> |
259 | | - </Box> |
260 | | - )} |
261 | | - <span data-component="text" hidden={Icon ? true : false}> |
262 | | - {children} |
263 | | - </span> |
264 | | - {Icon && ( |
265 | | - <Box data-component="icon-only" as="span" sx={{display: 'inline-block'}} aria-hidden={!iconOnly}> |
266 | | - <Icon /> |
267 | | - </Box> |
268 | | - )} |
269 | | - {TrailingIcon && ( |
270 | | - <Box as="span" data-component="trailingIcon" sx={{...iconWrapStyles, ml: 2}} aria-hidden={!iconOnly}> |
271 | | - <TrailingIcon /> |
272 | | - </Box> |
273 | | - )} |
274 | | - </ButtonBase> |
275 | | - ) |
276 | | - } |
277 | | -) |
| 5 | +const Button = forwardRef<HTMLButtonElement, ButtonProps>(({children, ...props}, forwardedRef): JSX.Element => { |
| 6 | + return ( |
| 7 | + <ButtonBase ref={forwardedRef} {...props} as="button"> |
| 8 | + {children} |
| 9 | + </ButtonBase> |
| 10 | + ) |
| 11 | +}) |
278 | 12 |
|
279 | 13 | Button.displayName = 'Button' |
280 | 14 |
|
281 | | -Object.assign(Button, {}) |
282 | | - |
283 | 15 | export {Button} |
0 commit comments