|
1 | | -import React, { ReactElement } from 'react'; |
| 1 | +import React, { ReactElement, ReactNode } from 'react'; |
2 | 2 | import styled from 'styled-components'; |
3 | 3 | import { variant } from 'styled-system'; |
4 | | -import { Button as BaseButton, ButtonProps as BaseButtonProps } from 'react-aria-components'; |
| 4 | +import { Button as BaseButton, ButtonProps as BaseButtonProps, ButtonRenderProps } from 'react-aria-components'; |
5 | 5 | import { getSemanticValue } from '../../../essentials/experimental/cssVariables'; |
6 | 6 | import { get } from '../../../utils/experimental/themeGet'; |
7 | 7 | import { textStyles } from '../Text/Text'; |
@@ -74,14 +74,10 @@ const emphasisStyles = variant<Record<string, unknown>, Emphasis>({ |
74 | 74 | const ButtonStyled = styled(BaseButton)<{ $emphasis: Emphasis }>` |
75 | 75 | position: relative; |
76 | 76 |
|
77 | | - display: inline-flex; |
78 | | - align-items: center; |
79 | | - justify-content: center; |
80 | | - gap: ${get('space.2')}; |
81 | 77 | border: none; |
82 | 78 | outline: none; |
83 | 79 | border-radius: ${get('radii.4')}; |
84 | | - padding: ${get('space.4')} ${get('space.6')}; |
| 80 | + padding: 0; |
85 | 81 |
|
86 | 82 | cursor: pointer; |
87 | 83 |
|
@@ -120,20 +116,52 @@ const ButtonStyled = styled(BaseButton)<{ $emphasis: Emphasis }>` |
120 | 116 | ${emphasisStyles}; |
121 | 117 | `; |
122 | 118 |
|
| 119 | +const ChildrenContainer = styled.span<{ isLoading: boolean }>` |
| 120 | + display: inline-flex; |
| 121 | + align-items: center; |
| 122 | + justify-content: center; |
| 123 | + gap: ${get('space.2')}; |
| 124 | + padding: ${get('space.4')} ${get('space.6')}; |
| 125 | +
|
| 126 | + opacity: ${({ isLoading }) => (isLoading ? 0 : 1)}; |
| 127 | + visibility: ${({ isLoading }) => (isLoading ? 'hidden' : 'visible')}; |
| 128 | + transition: opacity ease 200ms; |
| 129 | +`; |
| 130 | + |
| 131 | +const SpinnerContainer = styled.span` |
| 132 | + position: absolute; |
| 133 | + display: flex; |
| 134 | + justify-content: center; |
| 135 | + align-items: center; |
| 136 | + top: 50%; |
| 137 | + left: 50%; |
| 138 | + transform: translate(-50%, -50%); |
| 139 | + pointer-events: none; |
| 140 | +`; |
| 141 | + |
123 | 142 | const spinnerColor: Record<Emphasis, string> = { |
124 | 143 | primary: getSemanticValue('on-accent'), |
125 | 144 | secondary: getSemanticValue('on-surface'), |
126 | 145 | textButton: getSemanticValue('on-surface') |
127 | 146 | }; |
128 | 147 |
|
129 | 148 | function Button({ children, emphasis = 'primary', isLoading = false, ...restProps }: ButtonProps): ReactElement { |
| 149 | + const renderContent = (props: ButtonRenderProps & { defaultChildren: ReactNode }) => ( |
| 150 | + <> |
| 151 | + <ChildrenContainer isLoading={isLoading}> |
| 152 | + {typeof children === 'function' ? children(props) : children} |
| 153 | + </ChildrenContainer> |
| 154 | + {isLoading && ( |
| 155 | + <SpinnerContainer> |
| 156 | + <InlineSpinner data-testid="button-spinner" color={spinnerColor[emphasis]} size="medium" /> |
| 157 | + </SpinnerContainer> |
| 158 | + )} |
| 159 | + </> |
| 160 | + ); |
| 161 | + |
130 | 162 | return ( |
131 | 163 | <ButtonStyled data-testid="button-container" isPending={isLoading} $emphasis={emphasis} {...restProps}> |
132 | | - {isLoading ? ( |
133 | | - <InlineSpinner data-testid="button-spinner" color={spinnerColor[emphasis]} size="medium" /> |
134 | | - ) : ( |
135 | | - children |
136 | | - )} |
| 164 | + {renderContent} |
137 | 165 | </ButtonStyled> |
138 | 166 | ); |
139 | 167 | } |
|
0 commit comments