-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Components: Add next Button, ButtonGroup (#29230)
* Components: Add hooks based Button, ButtonGroup * Update package-lock.json with react-i18n * Fix prefix prop for BaseButton. Update BaseButton + Button stories * Add dependencies to tsconfig.json reference * Add ButtonGroup documentation * Update Button README * Rename prefix back to pre. Update test + snapshots * Make style diff snapshots work with CSS variables * Improve to-match-style-diff-snapshot + update snapshot tests * Update snapshots after rebase * Use i18n directly instead of react-i18n * Fix linting errors * Remove react-i18n dependency from package-lock after rebase * Update snapshot tests for interpolated components Co-authored-by: Jon Q <[email protected]>
- Loading branch information
1 parent
838e831
commit 977c770
Showing
29 changed files
with
3,342 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { contextConnect } from '@wp-g2/context'; | ||
import { cx, ui } from '@wp-g2/styles'; | ||
// eslint-disable-next-line no-restricted-imports | ||
import { Radio as ReakitRadio } from 'reakit'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Icon, chevronDown } from '@wordpress/icons'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { useButtonGroupContext } from '../button-group'; | ||
import { Elevation } from '../elevation'; | ||
import { FlexItem } from '../flex'; | ||
import { View } from '../view'; | ||
import * as styles from './styles'; | ||
import LoadingOverlay from './loading-overlay'; | ||
import { useBaseButton } from './hook'; | ||
|
||
/** | ||
* @param {import('@wp-g2/create-styles').ViewOwnProps<import('./types').Props, 'button'>} props | ||
* @param {import('react').Ref<any>} forwardedRef | ||
*/ | ||
function BaseButton( props, forwardedRef ) { | ||
const { | ||
as: asProp, | ||
children, | ||
disabled = false, | ||
elevation = 0, | ||
elevationActive, | ||
elevationFocus, | ||
elevationHover, | ||
hasCaret = false, | ||
href, | ||
icon, | ||
iconSize = 16, | ||
isActive = false, | ||
isDestructive = false, | ||
isFocused = false, | ||
isLoading = false, | ||
noWrap = true, | ||
pre, | ||
suffix, | ||
...otherProps | ||
} = useBaseButton( props ); | ||
const { buttonGroup } = useButtonGroupContext(); | ||
const buttonGroupState = buttonGroup || {}; | ||
|
||
const BaseComponent = buttonGroup ? ReakitRadio : View; | ||
const as = asProp || ( href ? 'a' : 'button' ); | ||
|
||
return ( | ||
// @ts-ignore No idea why TS is confused about this but ReakitRadio and View are definitely renderable | ||
<BaseComponent | ||
aria-busy={ isLoading } | ||
as={ as } | ||
data-active={ isActive } | ||
data-destructive={ isDestructive } | ||
data-focused={ isFocused } | ||
data-icon={ !! icon } | ||
disabled={ disabled || isLoading } | ||
href={ href } | ||
{ ...buttonGroupState } | ||
{ ...otherProps } | ||
ref={ forwardedRef } | ||
> | ||
<LoadingOverlay isLoading={ isLoading } /> | ||
{ pre && ( | ||
<FlexItem | ||
as="span" | ||
className={ cx( | ||
styles.PrefixSuffix, | ||
isLoading && styles.loading | ||
) } | ||
{ ...ui.$( 'ButtonPrefix' ) } | ||
> | ||
{ pre } | ||
</FlexItem> | ||
) } | ||
{ icon && ( | ||
<FlexItem | ||
as="span" | ||
className={ cx( | ||
styles.PrefixSuffix, | ||
isLoading && styles.loading | ||
) } | ||
{ ...ui.$( 'ButtonIcon' ) } | ||
> | ||
<Icon icon={ icon } size={ iconSize } /> | ||
</FlexItem> | ||
) } | ||
{ children && ( | ||
<FlexItem | ||
as="span" | ||
className={ cx( | ||
styles.Content, | ||
isLoading && styles.loading, | ||
noWrap && styles.noWrap | ||
) } | ||
isBlock | ||
{ ...ui.$( 'ButtonContent' ) } | ||
> | ||
{ children } | ||
</FlexItem> | ||
) } | ||
{ suffix && ( | ||
<FlexItem | ||
as="span" | ||
className={ cx( | ||
styles.PrefixSuffix, | ||
isLoading && styles.loading | ||
) } | ||
{ ...ui.$( 'ButtonSuffix' ) } | ||
> | ||
{ suffix } | ||
</FlexItem> | ||
) } | ||
{ hasCaret && ( | ||
<FlexItem | ||
as="span" | ||
className={ cx( | ||
styles.CaretWrapper, | ||
isLoading && styles.loading | ||
) } | ||
{ ...ui.$( 'ButtonCaret' ) } | ||
> | ||
<Icon icon={ chevronDown } size={ 16 } /> | ||
</FlexItem> | ||
) } | ||
<Elevation | ||
active={ elevationActive } | ||
as="span" | ||
focus={ elevationFocus } | ||
hover={ elevationHover } | ||
offset={ -1 } | ||
value={ elevation } | ||
{ ...ui.$( 'ButtonElevation' ) } | ||
/> | ||
</BaseComponent> | ||
); | ||
} | ||
|
||
/** | ||
* `BaseButton` is a primitive component used to create actionable components (e.g. `Button`). | ||
*/ | ||
const ConnectedBaseButton = contextConnect( BaseButton, 'BaseButton' ); | ||
|
||
export default ConnectedBaseButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useContextSystem } from '@wp-g2/context'; | ||
import { css, cx } from '@wp-g2/styles'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { useControlGroupContext } from '../control-group'; | ||
import { useFlex } from '../flex'; | ||
import * as styles from './styles'; | ||
|
||
/** | ||
* @param {import('@wp-g2/create-styles').ViewOwnProps<import('./types').Props, 'button'>} props | ||
*/ | ||
export function useBaseButton( props ) { | ||
const { | ||
children, | ||
className, | ||
css: cssProp, | ||
currentColor, | ||
disabled = false, | ||
elevation = 0, | ||
elevationActive, | ||
elevationFocus, | ||
elevationHover, | ||
gap = 2, | ||
hasCaret = false, | ||
href, | ||
icon, | ||
iconSize = 16, | ||
isBlock = false, | ||
isControl = false, | ||
isDestructive = false, | ||
isLoading = false, | ||
isNarrow = false, | ||
isRounded = false, | ||
isSplit = false, | ||
isSubtle = false, | ||
justify = 'center', | ||
noWrap = true, | ||
pre, | ||
size = 'medium', | ||
suffix, | ||
textAlign = 'center', | ||
...otherProps | ||
} = useContextSystem( props, 'BaseButton' ); | ||
|
||
const { className: flexClassName, ...flexProps } = useFlex( { | ||
gap, | ||
justify, | ||
} ); | ||
|
||
/** @type {import('react').ElementType} */ | ||
const as = href ? 'a' : 'button'; | ||
const { styles: controlGroupStyles } = useControlGroupContext(); | ||
const isIconOnly = !! icon && ! children; | ||
|
||
const classes = cx( | ||
flexClassName, | ||
styles.Button, | ||
isBlock && styles.block, | ||
isDestructive && styles.destructive, | ||
styles[ size ], | ||
isIconOnly && styles.icon, | ||
isSubtle && styles.subtle, | ||
isControl && styles.control, | ||
isSubtle && isControl && styles.subtleControl, | ||
controlGroupStyles, | ||
isRounded && styles.rounded, | ||
isNarrow && styles.narrow, | ||
isSplit && styles.split, | ||
currentColor && styles.currentColor, | ||
css( { textAlign } ), | ||
css( cssProp ), | ||
className | ||
); | ||
|
||
return { | ||
...flexProps, | ||
as, | ||
href, | ||
children, | ||
disabled, | ||
elevation, | ||
className: classes, | ||
elevationActive, | ||
elevationFocus, | ||
elevationHover, | ||
hasCaret, | ||
icon, | ||
isDestructive, | ||
pre, | ||
suffix, | ||
iconSize, | ||
isLoading, | ||
noWrap, | ||
...otherProps, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as BaseButton } from './component'; | ||
|
||
export * from './hook'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { Flex } from '../flex'; | ||
import { Spinner } from '../spinner'; | ||
import * as styles from './styles'; | ||
|
||
export function LoadingOverlay( { isLoading = false } ) { | ||
if ( ! isLoading ) return null; | ||
|
||
return ( | ||
<Flex | ||
aria-hidden="true" | ||
className={ styles.LoadingOverlay } | ||
justify="center" | ||
> | ||
<Spinner /> | ||
</Flex> | ||
); | ||
} | ||
|
||
export default LoadingOverlay; |
Oops, something went wrong.