wave icon indicating copy to clipboard operation
wave copied to clipboard

New ButtonGroup component

Open phllipo opened this issue 3 years ago • 5 comments

We recently needed a group of buttons to be able to select different weekdays. Screenshot 2021-06-02 at 11 21 08 Screenshot 2021-06-02 at 11 21 00

So I went ahead and created this using the Button and Box component and styling them to our needs.

Styling code:

const ButtonGroup = styled(Box)`
    border-radius: 4px;
    overflow: hidden;
    border: ${props =>
        props.error ? `1px solid ${Colors.NEGATIVE_ORANGE_900}` : `1px solid ${Colors.AUTHENTIC_BLUE_200}`};
`;

const ButtonGroupItem = styled(Button)`
    background: ${props => (props.selected ? Colors.ACTION_BLUE_900 : Colors.WHITE)};
    border-top: none;
    border-bottom: none;
    border-left: none;
    border-right: ${props =>
          props.selected ? `1px solid ${Colors.ACTION_BLUE_900}` : `1px solid ${Colors.AUTHENTIC_BLUE_200}`};

    padding: 0 8px;
    min-width: 0;
    border-radius: 0;

    :nth-child(1) {
        border-left: ${props =>
           props.selected ? `1px solid ${Colors.ACTION_BLUE_900}` : `1px solid ${Colors.AUTHENTIC_BLUE_200}`};
    }

    &:hover {
        background: ${props => (props.selected ? Colors.ACTION_BLUE_1000 : Colors.AUTHENTIC_BLUE_50)};
    }
`;

JSX code:

<ButtonGroup mt={3} error={error && !rule.daysOfWeek.length} display="flex">
    {days.map((day, index) => (
        <ButtonGroupItem
            type="button"
            key={`time-restriction-day-${index}`}
            variant={rule.daysOfWeek.includes(day.value) ? 'primary' : 'secondary'}
            selected={rule.daysOfWeek.includes(day.value)}
            size="small"
            onClick={() => handleDayChange(day.value)}
        >
            {day.label}
        </ButtonGroupItem>
    ))}
</ButtonGroup>

It feels a little bit hacky overriding a bunch of the styles, but it seemed like the best approach to have the elements look like all other buttons. What do you think about this solution and do you see this as something worth adding to wave?

phllipo avatar Jun 02 '21 09:06 phllipo

Cool! We are using a hardcoded version of this UI-element already in GAME. And if I'm not mistaking @jonotrujillo implemented it. It is not looking 100% like in the design, but maybe he has somethings that could help you.

rafael-sepeda avatar Jun 02 '21 09:06 rafael-sepeda

It's quite similar. The one I implemented is looking like this:

Screen Shot 2021-06-02 at 17 21 49

In my case, I implemented the options as checkboxes as I felt this was the closest native behaviour:

export interface WeekdaySelectorProp {
  error?: boolean;
}

export const WeekdaySelector = styled(Box)<WeekdaySelectorProp>`
  box-shadow: inset 0 0 0 0.0625rem
    ${({ error }) => (error ? Colors.NEGATIVE_ORANGE_900 : 'transparent')};
  border-radius: 4px;
  border: 1px solid
    ${({ error }) =>
      error ? Colors.NEGATIVE_ORANGE_900 : Colors.AUTHENTIC_BLUE_200};
  display: flex;
  margin: calc(${Spaces[1]} - 1px) 0;
  overflow: hidden;
`;

const OptionLabel = styled.label<{
  inverted?: boolean;
}>`
  background-color: ${({ inverted }) =>
    inverted ? Colors.ACTION_BLUE_900 : 'transparent'};
  border-right: 1px solid ${Colors.AUTHENTIC_BLUE_200};
  color: ${({ inverted }) =>
    inverted ? Colors.WHITE : Colors.AUTHENTIC_BLUE_1100};
  cursor: pointer;
  flex: 1;
  overflow: hidden;
  padding: 0 ${Spaces[1]};
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;

  &:last-child {
    border-right: 0;
  }
`;

const OptionText = styled(Text)`
  font-size: 14px;
  font-weight: 600;
  line-height: ${Spaces[4]};
`;

export interface WeekdaySelectorOptionProps {
  checked?: boolean;
  label?: string;
  onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
}

export const WeekdaySelectorOption: VFC<WeekdaySelectorOptionProps> = ({
  checked,
  label,
  onChange,
}) => (
  <OptionLabel inverted={checked}>
    <input type="checkbox" checked={checked} onChange={onChange} hidden></input>
    <OptionText inverted={checked}>{label}</OptionText>
  </OptionLabel>
);

And used like this:

<WeekdaySelector error={hasError}>
  <WeekdaySelectorOption
     checked={modayChecked}
     onChange={(event) => onChange(event.target.checked)}
     label="Tuesday"
  />
  <WeekdaySelectorOption
    checked={tuesdayChecked}
    onChange={(event) => onChange(event.target.checked)}
    label="Tuesday"
   />
  ...
</WekdaySelector>

jonotrujillo avatar Jun 02 '21 15:06 jonotrujillo

@phllipo: what is the use case to have variant and selected separately in ButtonGroupItem?

jonotrujillo avatar Jun 02 '21 15:06 jonotrujillo

@jonotrujillo oh that's also an interesting approach, wouldn't have thought of using checkbox, but from an API perspective it makes a lot of sense.

Initially I used the variant to have the usual button behaviour (colors, hover etc.) and then the selected for my styled component, but I probably overwrote most of the variant styling, so I can probably get rid of the variant.

phllipo avatar Jun 03 '21 07:06 phllipo

Good point, I can also see something a little more compact when it comes to the selected button (checkboxes) here: https://baseweb.design/components/button-group/#checkbox-mode. Maybe we can make the checkbox-style "you can select multiple buttons" as one of more use cases. I can also imagine having two buttons in the table in the "actions" column for example, which both need to function as buttons.

snapsnapturtle avatar Jun 08 '21 08:06 snapsnapturtle