From ca85176ac6021de93ec55ffdea7ea6c8928d6d08 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Tue, 11 Feb 2020 18:31:01 +0000 Subject: [PATCH] Forms/RadioButtonGroup: Improves semantics and simplifies CSS (#22093) * Forms/RadioButtonGroup: Improves semantics and simplifies CSS - Changes base element to radio input for improved semantics & automatic keyboard support - Simplifies CSS --- .../RadioButtonGroup/RadioButton.story.tsx | 3 +- .../Forms/RadioButtonGroup/RadioButton.tsx | 180 ++++++------------ .../RadioButtonGroup/RadioButtonGroup.tsx | 33 +++- 3 files changed, 87 insertions(+), 129 deletions(-) diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.story.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.story.tsx index 835f11e8a95..303a8557d78 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.story.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.story.tsx @@ -21,7 +21,8 @@ export const simple = () => { disabled={disabled} size={size} active={active} - onClick={() => { + id="standalone" + onChange={() => { setActive(!active); }} > diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx index f8f23488b07..692726386c3 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx @@ -8,12 +8,15 @@ export type RadioButtonSize = 'sm' | 'md'; export interface RadioButtonProps { size?: RadioButtonSize; disabled?: boolean; + name?: string; active: boolean; - onClick: () => void; + id: string; + onChange: () => void; } const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize) => { - const { padding, fontSize, height } = getPropertiesForButtonSize(theme, size); + const { fontSize, height } = getPropertiesForButtonSize(theme, size); + const horizontalPadding = theme.spacing[size] ?? theme.spacing.md; const c = theme.colors; const textColor = stv({ light: c.gray33, dark: c.gray70 }, theme.type); @@ -32,133 +35,58 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt const fakeBold = `0 0 0.65px ${textColorHover}, 0 0 0.65px ${textColorHover}`; return { - button: css` - cursor: pointer; - position: relative; - z-index: 0; - background: ${bg}; - border: ${border}; - color: ${textColor}; - font-size: ${fontSize}; - padding: ${padding}; - height: ${height}; - border-left: 0; + radio: css` + position: absolute; + top: 0; + left: -100vw; + opacity: 0; + z-index: -1000; - /* This pseudo element is responsible for rendering the lines between buttons when they are groupped */ - &:before { - content: ''; - position: absolute; - top: -1px; - left: -1px; - width: 1px; - height: calc(100% + 2px); - } - - &:hover { - border: ${borderHover}; - border-left: 0; - &:before { - /* renders line between elements */ - background: ${borderColorHover}; - } - &:first-child { - border-left: ${borderHover}; - } - &:last-child { - border-right: ${borderHover}; - } - &:first-child:before { - /* Don't render divider line on first element*/ - display: none; - } - } - - &:not(:disabled):hover { - color: ${textColorHover}; - /* The text shadow imitates font-weight:bold; - * Using font weight on hover makes the button size slighlty change which looks like a glitch - * */ + &:checked + label { + border: ${borderActive}; + color: ${textColorActive}; text-shadow: ${fakeBold}; + background: ${bgActive}; + z-index: 3; } - &:focus { - z-index: 1; + &:focus + label { ${getFocusCss(theme)}; - &:before { - background: ${borderColor}; - } - &:hover { - &:before { - background: ${borderColorHover}; - } - } + z-index: 3; } - &:disabled { + &:disabled + label { + cursor: default; background: ${bgDisabled}; color: ${textColor}; } - &:first-child { - border-top-left-radius: ${theme.border.radius.sm}; - border-bottom-left-radius: ${theme.border.radius.sm}; - border-left: ${border}; - } - &:last-child { - border-top-right-radius: ${theme.border.radius.sm}; - border-bottom-right-radius: ${theme.border.radius.sm}; - border-right: ${border}; + &:enabled + label:hover { + text-shadow: ${fakeBold}; } `, + radioLabel: css` + display: inline-block; + position: relative; + font-size: ${fontSize}; + min-height: ${fontSize}; + color: ${textColor}; + padding: calc((${height} - ${fontSize}) / 2) ${horizontalPadding} calc((${height} - ${fontSize}) / 2) + ${horizontalPadding}; + line-height: 1; + margin-left: -1px; + border-radius: ${theme.border.radius.sm}; + border: ${border}; + background: ${bg}; + cursor: pointer; + z-index: 1; - buttonActive: css` - background: ${bgActive}; - border: ${borderActive}; - border-left: 0; - color: ${textColorActive}; - text-shadow: ${fakeBold}; + user-select: none; &:hover { - border: ${borderActive}; - border-left: none; - } - - &:focus { - &:before { - background: ${borderColorActive}; - } - &:hover:before { - background: ${borderColorActive}; - } - } - - &:before, - &:hover:before { - background: ${borderColorActive}; - } - - &:first-child, - &:first-child:hover { - border-left: ${borderActive}; - } - &:last-child, - &:last-child:hover { - border-right: ${borderActive}; - } - - &:first-child { - &:before { - display: none; - } - } - - & + button:hover { - &:before { - display: none; - } - } - &:focus { - border-color: ${borderActive}; + color: ${textColorHover}; + border: ${borderHover}; + z-index: 2; } `, }; @@ -169,20 +97,28 @@ export const RadioButton: React.FC = ({ active = false, disabled = false, size = 'md', - onClick, + onChange, + id, + name = undefined, }) => { const theme = useTheme(); const styles = getRadioButtonStyles(theme, size); return ( - + <> + + + ); }; diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index 5120d215d93..048b2e1bbd2 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -1,5 +1,6 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useRef } from 'react'; import { css } from 'emotion'; +import uniqueId from 'lodash/uniqueId'; import { SelectableValue } from '@grafana/data'; import { RadioButtonSize, RadioButton } from './RadioButton'; @@ -11,6 +12,23 @@ const getRadioButtonGroupStyles = () => { flex-wrap: nowrap; position: relative; `, + radioGroup: css` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + + label { + border-radius: 0px; + + &:first-of-type { + border-radius: 2px 0px 0px 2px; + } + + &:last-of-type { + border-radius: 0px 2px 2px 0px; + } + } + `, }; }; interface RadioButtonGroupProps { @@ -30,7 +48,7 @@ export function RadioButtonGroup({ disabledOptions, size = 'md', }: RadioButtonGroupProps) { - const handleOnClick = useCallback( + const handleOnChange = useCallback( (option: SelectableValue) => { return () => { if (onChange) { @@ -40,19 +58,22 @@ export function RadioButtonGroup({ }, [onChange] ); + const groupName = useRef(uniqueId('radiogroup-')); const styles = getRadioButtonGroupStyles(); return ( -
- {options.map(o => { - const isItemDisabled = disabledOptions && o.value && disabledOptions.indexOf(o.value) > -1; +
+ {options.map((o, i) => { + const isItemDisabled = disabledOptions && o.value && disabledOptions.includes(o.value); return ( {o.label}