Browse Source

Refactor Radio Button

Francesco Baccetti 4 years ago
parent
commit
884f0e924d

+ 3 - 0
packages/app/.babelrc

@@ -0,0 +1,3 @@
+{
+  "plugins": ["emotion"]
+}

+ 88 - 99
packages/app/src/shared/components/RadioButton/RadioButton.style.ts

@@ -1,109 +1,98 @@
-import { StyleFn, makeStyles } from '../../utils'
-import { typography, colors, spacing } from '../../theme'
-import { CSSProperties } from 'react'
+import styled from '@emotion/styled'
+import { css } from '@emotion/core'
+import { colors } from '../../theme'
 
 
-export type RadioButtonStyleProps = {
-  selected?: boolean
-  disabled?: boolean
-  error?: boolean
-  position?: 'end' | 'start' | 'top' | 'bottom'
-}
-const container: StyleFn = (_, { position }) => ({
-  fontFamily: typography.fonts.base,
-  display: position === 'bottom' || position === 'bottom' ? 'inline-block' : 'inline-flex',
-  alignItems: 'center',
-  '&:focus': {
-    outline: 'none',
-  },
-})
+export type RadioButtonStyleProps = Partial<{
+  error: boolean
+  disabled: boolean
+  clickable: boolean
+  checked: boolean
+  position: 'end' | 'start' | 'top' | 'bottom'
+}>
 
 
-const outerDot: StyleFn = (_, { position, disabled }) => ({
-  width: spacing.xxl,
-  height: spacing.xxl,
-  borderRadius: '50%',
-  position: 'relative',
-  margin: position === 'bottom' ? `0 auto ${spacing.xs}` : position === 'top' ? `${spacing.xs} auto 0` : '',
-  '&:hover': {
-    backgroundColor: disabled ? 'none' : colors.gray[50],
-  },
-  '&:active': {
-    backgroundColor: disabled ? 'none' : colors.gray[100],
-  },
-  '&:focus': {
-    backgroundColor: disabled ? 'none' : colors.blue[100],
-    outline: 'none',
-  },
-})
+export const Input = styled.input`
+  margin: auto;
+  opacity: 0;
+`
 
 
-const dot: StyleFn = (_, { disabled, selected, error }) => {
-  return {
-    width: spacing.m,
-    height: spacing.m,
-    borderWidth: 1,
-    borderColor: disabled ? colors.gray[200] : error ? colors.error : selected ? colors.blue[500] : colors.gray[300],
-    borderStyle: 'solid',
-    borderRadius: '50%',
-    position: 'absolute',
-    top: '7px',
-    left: '7px',
-    '&:focus': {
-      borderColor: disabled ? colors.gray[200] : colors.gray[700],
-    },
-    '&:active': {
-      borderColor: disabled ? colors.gray[200] : colors.gray[700],
-    },
+const colorFromProps = ({ error, checked, disabled }: RadioButtonStyleProps) => {
+  if (error) {
+    return css`
+      background-color: ${checked ? colors.error : 'transparent'};
+      border: ${checked ? `2px solid ${colors.error}` : `1px solid ${colors.error}`};
+    `
+  } else if (disabled) {
+    return css`
+      background-color: ${checked ? colors.gray[200] : colors.gray[50]};
+      border: ${checked ? `2px solid ${colors.gray[200]}` : `1px solid ${colors.gray[200]}`};
+      background-clip: ${checked ? 'content-box' : 'unset'};
+      &::before {
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: ${checked ? colors.gray[50] : 'transparent'};
+      }
+    `
+  } else {
+    return css`
+      border: ${checked ? `2px solid ${colors.blue[500]}` : `1px solid ${colors.gray[300]}`};
+      background-color: ${checked ? colors.blue[500] : 'transparent'};
+      &:hover {
+        &::before {
+          background-color: ${checked ? colors.blue[50] : colors.gray[50]};
+        }
+      }
+      &:focus {
+        border-color: ${checked ? 'transparent' : colors.gray[700]};
+        &::before {
+          background-color: ${checked ? colors.blue[100] : colors.gray[50]};
+        }
+      }
+      &:active {
+        border-color: ${checked ? '' : colors.gray[700]};
+        &::before {
+          background-color: ${checked ? colors.blue[100] : colors.gray[100]};
+        }
+      }
+    `
   }
   }
 }
 }
 
 
-const BackgroundFromProps: StyleFn = (styles = {}, { disabled, selected, error }) => {
-  const key = selected ? `backgroundImage` : `backgroundColor`
-  const SELECTED_ERROR = `repeating-radial-gradient(circle, ${colors.error} 0px, ${colors.error} 3px, transparent 3px, transparent 6px, ${colors.error} 6px, ${colors.error} 8px)`
-  const SELECTED_DISABLED = `repeating-radial-gradient(circle, ${colors.gray[200]} 0px, ${colors.gray[200]} 3px, transparent 3px, transparent 6px, ${colors.gray[200]} 6px, ${colors.gray[200]} 8px)`
-  const SELECTED_DEFAULT = `repeating-radial-gradient(circle, ${colors.blue[500]} 0px, ${colors.blue[500]} 3px, transparent 3px, transparent 6px, ${colors.blue[500]} 6px, ${colors.blue[500]} 8px)`
-  const UNSELECTED_DISABLED = colors.gray[50]
-
-  const value =
-    selected && error
-      ? SELECTED_ERROR
-      : selected && disabled
-      ? SELECTED_DISABLED
-      : selected
-      ? SELECTED_DEFAULT
-      : disabled
-      ? UNSELECTED_DISABLED
-      : styles[key as keyof CSSProperties]
-  return {
-    ...styles,
-    [key]: value,
+export const StyledInput = styled.div<RadioButtonStyleProps>`
+  position: relative;
+  border-radius: 50%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-clip: content-box;
+  padding: 4px;
+  &::before {
+    content: '';
+    top: -8px;
+    bottom: -8px;
+    left: -8px;
+    right: -8px;
+    border-radius: 50%;
+    position: absolute;
+    z-index: -1;
   }
   }
-}
-
-const label: StyleFn = (_, { position }) => {
-  const key = position === 'end' ? 'margin-left' : position === 'start' ? 'margin-right' : 'margin'
-  const value =
-    key === 'margin-left' || key === 'margin-right'
-      ? spacing.xs
-      : position === 'bottom'
-      ? `0 auto ${spacing.xs}`
-      : `${spacing.xs} auto 0`
-
-  return {
-    color: colors.white,
-    [key]: value,
+  ${colorFromProps};
+  & + span {
+    color: ${(props) => (props.checked ? colors.white : '')};
   }
   }
-}
+`
 
 
-export const useCSS = ({
-  selected = false,
-  disabled = false,
-  error = false,
-  position = 'end',
-}: RadioButtonStyleProps) => {
-  const props = { selected, disabled, error, position }
-  return {
-    container: makeStyles([container])(props),
-    outterDot: makeStyles([outerDot])(props),
-    dot: makeStyles([dot, BackgroundFromProps])(props),
-    label: makeStyles([label])(props),
+export const Label = styled.label<RadioButtonStyleProps>`
+  width: min-content;
+  display: flex;
+  flex-direction: ${({ position }) => (position === 'start' || position === 'end' ? 'row' : 'column')};
+  align-items: center;
+  cursor: ${(props) => (props.clickable && !props.disabled ? 'pointer' : 'auto')};
+  & > span:nth-of-type(1) {
+    margin: 8px;
   }
   }
-}
+  & > span {
+    order: ${({ position }) => (position === 'start' || position === 'top' ? -1 : 1)};
+  }
+`

+ 28 - 20
packages/app/src/shared/components/RadioButton/RadioButton.tsx

@@ -1,27 +1,35 @@
 import React from 'react'
 import React from 'react'
-import { useCSS, RadioButtonStyleProps } from './RadioButton.style'
+import { Label, Input, StyledInput, RadioButtonStyleProps } from './RadioButton.style'
 
 
-type RadioButtonProps = {
-  label?: string
-  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void
-} & RadioButtonStyleProps
+type RadioButtonProps = Partial<{
+  label: string
+}> &
+  Omit<RadioButtonStyleProps, 'clickable'> &
+  React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
 
 
-export default function RadioButton({
-  label = '',
+const RadioButton: React.FC<RadioButtonProps> = ({
+  label,
   position = 'end',
   position = 'end',
-  disabled = false,
-  onClick = () => {},
-  ...styleProps
-}: RadioButtonProps) {
-  const styles = useCSS({ disabled, position, ...styleProps })
-
+  disabled,
+  error,
+  onClick,
+  checked,
+  ...props
+}) => {
+  const clickable = !!onClick
+  const handleClick = (e: React.MouseEvent<HTMLInputElement>) => {
+    if (onClick) {
+      onClick(e)
+    }
+  }
   return (
   return (
-    <div css={styles.container} onClick={disabled ? () => {} : onClick}>
-      {(position === 'start' || position === 'top') && <label css={styles.label}>{label}</label>}
-      <div css={styles.outterDot}>
-        <div css={styles.dot}></div>
-      </div>
-      {(position === 'end' || position === 'bottom') && <label css={styles.label}>{label}</label>}
-    </div>
+    <Label position={position} clickable={clickable} disabled={disabled}>
+      <StyledInput checked={checked} error={error} disabled={disabled}>
+        <Input type="radio" onClick={handleClick} disabled={disabled} {...props} checked={checked} />
+      </StyledInput>
+      {label && <span>{label}</span>}
+    </Label>
   )
   )
 }
 }
+
+export default RadioButton

+ 14 - 64
packages/app/src/shared/stories/09-RadioButton.stories.tsx

@@ -7,83 +7,33 @@ export default {
 }
 }
 
 
 export const Primary = () => {
 export const Primary = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton selected={isSelected} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton checked={checked} onClick={() => setChecked(!checked)} />
 }
 }
 
 
-export const SelectedDisabled = () => {
-  const [isSelected, setIsSelected] = useState(true)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton selected={isSelected} disabled={true} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
-}
-
-export const UnselectedDisabled = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton selected={isSelected} disabled={true} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
-}
+export const SelectedDisabled = () => <RadioButton checked disabled />
 
 
+export const UnselectedDisabled = () => <RadioButton disabled />
 export const Error = () => {
 export const Error = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton selected={isSelected} error={true} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton error checked={checked} onClick={() => setChecked(!checked)} />
 }
 }
 
 
 export const WithLabel = () => {
 export const WithLabel = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton label="Label" selected={isSelected} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton label="Label" checked={checked} onClick={() => setChecked(!checked)} />
 }
 }
 
 
 export const WithLabelStart = () => {
 export const WithLabelStart = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton label="Label" position="start" selected={isSelected} onClick={() => setIsSelected(!isSelected)} />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton label="Label" position="start" checked={checked} onClick={() => setChecked(!checked)} />
 }
 }
 
 
 export const WithLabelBottom = () => {
 export const WithLabelBottom = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton
-        label="A longer label than normal"
-        position="bottom"
-        selected={isSelected}
-        onClick={() => setIsSelected(!isSelected)}
-      />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton label="A longer label" position="bottom" checked={checked} onClick={() => setChecked(!checked)} />
 }
 }
-
 export const WithLabelTop = () => {
 export const WithLabelTop = () => {
-  const [isSelected, setIsSelected] = useState(false)
-  return (
-    <div style={{ backgroundColor: 'black', padding: '50px 20px' }}>
-      <RadioButton
-        label="A longer label than normal"
-        position="top"
-        selected={isSelected}
-        onClick={() => setIsSelected(!isSelected)}
-      />
-    </div>
-  )
+  const [checked, setChecked] = useState(false)
+  return <RadioButton label="A longer label" position="top" checked={checked} onClick={() => setChecked(!checked)} />
 }
 }

+ 0 - 0
packages/app/src/shared/stories/12-Link.stories.tsx → packages/app/src/shared/stories/13-Link.stories.tsx


+ 0 - 0
packages/app/src/shared/stories/13-Sidenav.stories.tsx → packages/app/src/shared/stories/15-Sidenav.stories.tsx


+ 0 - 0
packages/app/src/shared/stories/15-VideoPlayer.stories.tsx → packages/app/src/shared/stories/16-VideoPlayer.stories.tsx


+ 0 - 0
packages/app/src/shared/stories/16-VideoPreview.stories.tsx → packages/app/src/shared/stories/18-VideoPreview.stories.tsx


+ 0 - 0
packages/app/src/shared/stories/18-InfiniteVideoGrid.stories.tsx → packages/app/src/shared/stories/19-InfiniteVideoGrid.stories.tsx