Skip to content

Commit

Permalink
moving buttongroup to context
Browse files Browse the repository at this point in the history
useButtonGoup
  • Loading branch information
dleroux committed Nov 18, 2019
1 parent b150d2e commit 153f21e
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 77 deletions.
36 changes: 34 additions & 2 deletions src/components/Button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ $partial-button-filled-pressed-box-shadow: inset 0 0 0 0 transparent,
transition-duration: duration(fast);
background: linear-gradient(to bottom, $color-light, $color-light);
border-color: $color-dark;
box-shadow: $partial-button-filled-pressed-box-shadow$color-dark;
box-shadow: $partial-button-filled-pressed-box-shadow $color-dark;
}

&:active {
background: linear-gradient(to bottom, $color-lightest, $color-lightest);
border-color: $color-dark;
box-shadow: $partial-button-filled-pressed-box-shadow$color-darkest;
box-shadow: $partial-button-filled-pressed-box-shadow $color-darkest;
}
}

Expand All @@ -71,6 +71,38 @@ $partial-button-filled-pressed-box-shadow: inset 0 0 0 0 transparent,
}
}

.globalTheming.inSegmentedGroup,
.inSegmentedGroup {
border-radius: 0;
}

.globalTheming.firstInSegmentedGroup,
.firstInSegmentedGroup {
border-top-left-radius: var(--p-border-radius-base, border-radius());
border-bottom-left-radius: var(--p-border-radius-base, border-radius());
}

.globalTheming.lastInSegmentedGroup,
.lastInSegmentedGroup {
border-top-right-radius: var(--p-border-radius-base, border-radius());
border-bottom-right-radius: var(--p-border-radius-base, border-radius());
}

.globalTheming.firstInConnectedTopGroup,
.firstInConnectedTopGroup {
border-top-left-radius: 0;
}

.globalTheming.lastInConnectedTopGroup,
.lastInConnectedTopGroup {
border-top-right-radius: 0;
}

.globalTheming.inFullWidthGroup,
.inFullWidthGroup {
@include button-full-width;
}

.Content {
@include text-style-button;
position: relative;
Expand Down
27 changes: 26 additions & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, {useRef} from 'react';
import {CaretDownMinor} from '@shopify/polaris-icons';
import {classNames, variationName} from '../../utilities/css';
import {handleMouseUpByBlurring} from '../../utilities/focus';
import {useUniqueId} from '../../utilities/unique-id';
import {useFeatures} from '../../utilities/features';
import {useI18n} from '../../utilities/i18n';
import {UnstyledLink} from '../UnstyledLink';
import {useButtonGroup} from '../ButtonGroup';
import {Icon} from '../Icon';
import {IconProps} from '../../types';
import {Spinner} from '../Spinner';
Expand Down Expand Up @@ -90,7 +92,7 @@ export interface ButtonProps {
const DEFAULT_SIZE = 'medium';

export function Button({
id,
id: idProp,
url,
disabled,
loading,
Expand Down Expand Up @@ -124,6 +126,7 @@ export function Button({
}: ButtonProps) {
const {unstableGlobalTheming = false} = useFeatures();
const hasGivenDeprecationWarning = useRef(false);
const id = useUniqueId('Button', idProp);

if (ariaPressed && !hasGivenDeprecationWarning.current) {
// eslint-disable-next-line no-console
Expand All @@ -135,6 +138,27 @@ export function Button({

const i18n = useI18n();

const buttonGroupContext = useButtonGroup();
let buttonGroupClassName;

if (buttonGroupContext) {
!buttonGroupContext.buttons.includes(id) &&
buttonGroupContext.setButtons([...buttonGroupContext.buttons, id]);

const {segmented, fullWidth, connectedTop} = buttonGroupContext;
const lastInGroup = buttonGroupContext.buttons[0] === id;
const firstInGroup = [...buttonGroupContext.buttons].pop() === id;

buttonGroupClassName = classNames(
segmented && styles.inSegmentedGroup,
segmented && lastInGroup && styles.lastInSegmentedGroup,
segmented && firstInGroup && styles.firstInSegmentedGroup,
fullWidth && styles.inFullWidthGroup,
connectedTop && firstInGroup && styles.firstInConnectedTopGroup,
connectedTop && lastInGroup && styles.lastInConnectedTopGroup,
);
}

const isDisabled = disabled || loading;

const className = classNames(
Expand All @@ -152,6 +176,7 @@ export function Button({
textAlign && styles[variationName('textAlign', textAlign)],
fullWidth && styles.fullWidth,
icon && children == null && styles.iconOnly,
buttonGroupClassName,
);

const disclosureIconMarkup = disclosure ? (
Expand Down
45 changes: 8 additions & 37 deletions src/components/ButtonGroup/ButtonGroup.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ $item-spacing: spacing(tight);
margin-top: 0;
margin-left: 0;

// This is a violation of our component model, but it’s the cleanest
// way to remove the border radii on connected elements.
.Item {
position: relative;
z-index: z-index(item, $stacking-order);
Expand All @@ -48,51 +46,24 @@ $item-spacing: spacing(tight);
&:not(:first-child) {
margin-left: -(border-width());
}

// stylelint-disable-next-line selector-max-combinators
> * {
border-radius: 0;
}

// stylelint-disable-next-line selector-max-combinators
&:first-child > * {
border-top-left-radius: border-radius();
border-bottom-left-radius: border-radius();
}

// stylelint-disable-next-line selector-max-combinators
&:last-child > * {
border-top-right-radius: border-radius();
border-bottom-right-radius: border-radius();
}
}

.Item-focused {
z-index: z-index(focused, $stacking-order);
}
}

.fullWidth {
.Item {
flex: 1 1 auto;

// stylelint-disable-next-line selector-max-combinators
> * {
@include button-full-width;
&.globalTheming {
.Item {
// stylelint-disable-next-line selector-max-class, selector-max-specificity
&:not(:first-child) {
margin-left: spacing(extra-tight);
}
}
}
}

.connectedTop {
.fullWidth {
.Item {
// stylelint-disable-next-line selector-max-combinators
&:first-child > * {
border-top-left-radius: 0;
}

// stylelint-disable-next-line selector-max-combinators
&:last-child > * {
border-top-right-radius: 0;
}
flex: 1 1 auto;
}
}
41 changes: 39 additions & 2 deletions src/components/ButtonGroup/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import React, {useState, useContext} from 'react';
import {classNames} from '../../utilities/css';
import {useFeatures} from '../../utilities/features';
import {elementChildren} from '../../utilities/components';
import {Item} from './components';
import styles from './ButtonGroup.scss';
Expand All @@ -15,22 +16,58 @@ export interface ButtonGroupProps {
children?: React.ReactNode;
}

interface ButtonGroupContextType {
buttons: string[];
connectedTop?: boolean;
fullWidth?: boolean;
segmented?: boolean;
setButtons(value: string[]): void;
}

export const ButtonGroupContext = React.createContext<
ButtonGroupContextType | undefined
>(undefined);

export function useButtonGroup() {
return useContext(ButtonGroupContext);
}

export function ButtonGroup({
children,
segmented,
fullWidth,
connectedTop,
}: ButtonGroupProps) {
const [buttons, setButtons] = useState([] as string[]);
const {unstableGlobalTheming = false} = useFeatures();

const className = classNames(
styles.ButtonGroup,
unstableGlobalTheming && styles.globalTheming,
segmented && styles.segmented,
fullWidth && styles.fullWidth,
connectedTop && styles.connectedTop,
);

const context = {
buttons,
segmented,
setButtons,
fullWidth,
connectedTop,
};

const contents = elementChildren(children).map((child, index) => (
<Item button={child} key={index} />
));

return <div className={className}>{contents}</div>;
const buttonGroup = <div className={className}>{contents}</div>;

return context ? (
<ButtonGroupContext.Provider value={context}>
{buttonGroup}
</ButtonGroupContext.Provider>
) : (
buttonGroup
);
}
54 changes: 20 additions & 34 deletions src/components/ButtonGroup/components/Item/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useState} from 'react';

import {classNames} from '../../../../utilities/css';
import {ButtonProps} from '../../../Button';
Expand All @@ -9,39 +9,25 @@ export interface ItemProps {
button: React.ReactElement<ButtonProps>;
}

interface State {
focused: boolean;
}

export class Item extends React.PureComponent<ItemProps, State> {
state: State = {focused: false};

render() {
const {button} = this.props;
const {focused} = this.state;

const className = classNames(
styles.Item,
focused && styles['Item-focused'],
button.props.plain && styles['Item-plain'],
);

return (
<div
className={className}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
>
{button}
</div>
);
}

private handleFocus = () => {
this.setState({focused: true});
export const Item = React.memo(function Item({button}: ItemProps) {
const [focused, setFocused] = useState(false);
const handleFocus = () => {
setFocused(true);
};

private handleBlur = () => {
this.setState({focused: false});
const handleBlur = () => {
setFocused(false);
};
}

const className = classNames(
styles.Item,
focused && styles['Item-focused'],
button.props.plain && styles['Item-plain'],
);

return (
<div className={className} onFocus={handleFocus} onBlur={handleBlur}>
{button}
</div>
);
});
2 changes: 1 addition & 1 deletion src/components/ButtonGroup/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {ButtonGroup, ButtonGroupProps} from './ButtonGroup';
export {ButtonGroup, ButtonGroupProps, useButtonGroup} from './ButtonGroup';

0 comments on commit 153f21e

Please sign in to comment.