3

Using Rebass/Forms in react and I cannot properly resize the Switch component using styles properly. (I also am using @emotion/styled)

I have tried using a size attribute, but that does not give the desired effect of simply changing the scale of the switch.

I have tried using the sx property and giving it a width and a height, but that only resizes the button element and not the inner div which is the "sliding dot"

I know that I could write some styling targeting the inner div itself, but I would like to find a way to give it a height and width a single time and it apply to both the button and the inner div.

<Switch
  sx={{ width: "30px", height: "15px" }}
/>

https://codesandbox.io/s/styling-rebass-switch-uu7wg

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
amaster
  • 1,915
  • 5
  • 25
  • 51

5 Answers5

4

It is not possible to do what you want 'out of the box' because the height and width are hard coded in the source

Luckily the internals of rebass are very nice, so it's possible to create your own with a little copy-pasta from the rebass source code.

import React from "react";
import { Box } from "reflexbox";

export const ResizableSwitch = ({
  checked,
  height = 24,
  width = 40,
  ...props
}) => (
  <Box
    as="button"
    type="button"
    role="switch"
    tx="forms"
    variant="switch"
    aria-checked={checked}
    {...props}
    __css={{
      appearance: "none",
      m: 0,
      p: 0,
      width,
      height,
      color: "primary",
      bg: "transparent",
      border: "1px solid",
      borderColor: "primary",
      borderRadius: 9999,
      "&[aria-checked=true]": {
        bg: "primary"
      },
      ":focus": {
        outline: "none",
        boxShadow: "0 0 0 2px"
      }
    }}
  >
    <Box
      aria-hidden
      style={{
        transform: checked ? `translateX(${width - height}px)` : "translateX(0)"
      }}
      sx={{
        mt: "-1px",
        ml: "-1px",
        width: height,
        height,
        borderRadius: 9999,
        border: "1px solid",
        borderColor: "primary",
        bg: "background",
        transitionProperty: "transform",
        transitionTimingFunction: "ease-out",
        transitionDuration: "0.1s",
        variant: "forms.switch.thumb"
      }}
    />
  </Box>
);

https://codesandbox.io/s/styling-rebass-switch-r6tmx?file=/src/App.js

lastcanal
  • 2,145
  • 14
  • 17
  • Thank you for the solution, I will just make my own rendition of the component and then be able to use it how I want to. I guess you made it 400px wide in the example just to show that you could, lol – amaster Jul 23 '20 at 19:30
1

Unfortunately, you can't. I took a deep look into the package itself and it seems like there is no fixed rule for the components written in this package. Some component do get the props and the sx. But there are components, like switch that hosts another component as a children, and no prop passed to it.

If you take a look at the implementation of switch in this page here:

export const Switch = forwardRef(({
  checked,
  ...props
}, ref) =>
  <Box
    ref={ref}
    as='button'
    type='button'
    role='switch'
    tx='forms'
    variant='switch'
    aria-checked={checked}
    {...props}
    __css={{
      appearance: 'none',
      m: 0,
      p: 0,
      width: 40,
      height: 24,
      color: 'primary',
      bg: 'transparent',
      border: '1px solid',
      borderColor: 'primary',
      borderRadius: 9999,
      '&[aria-checked=true]': {
        bg: 'primary',
      },
      ':focus': {
        outline: 'none',
        boxShadow: '0 0 0 2px'
      },
    }}>
    <Box
      aria-hidden
      style={{
        transform: checked ? 'translateX(16px)' : 'translateX(0)',
      }}
      sx={{
        mt: '-1px',
        ml: '-1px',
        width: 24,
        height: 24,
        borderRadius: 9999,
        border: '1px solid',
        borderColor: 'primary',
        bg: 'background',
        transitionProperty: 'transform',
        transitionTimingFunction: 'ease-out',
        transitionDuration: '0.1s',
        variant: 'forms.switch.thumb',
      }}
    />
  </Box>
)

There are 2 Box components (which are the base component of the package), one is a children of the other. The first Box is the area of the switch, and the child Box is the circle/button you are looking for, If you take a look on this component, you will see there is no outside variable that passed to it, so nothing can be changed - the style is already written.

This is the Button/Circle component:

    <Box
      aria-hidden
      style={{
        transform: checked ? 'translateX(16px)' : 'translateX(0)',
      }}
      sx={{
        mt: '-1px',
        ml: '-1px',
        width: 24,
        height: 24,
        borderRadius: 9999,
        border: '1px solid',
        borderColor: 'primary',
        bg: 'background',
        transitionProperty: 'transform',
        transitionTimingFunction: 'ease-out',
        transitionDuration: '0.1s',
        variant: 'forms.switch.thumb',
      }}
    />

If you are still willing to use that package, you can overcome this by overwriting the css, giving the component a className and apply styling to its children.

Plus, you can open an issue or suggest a fix on the package github repository.

SomoKRoceS
  • 2,934
  • 2
  • 19
  • 30
1

Looking at Switch source code it seems no properties are propagated to the inner <div>... would you open an issue?

In the mean while you can set css properties for children and/or based on attributes:

.myswitch {
  width: 30px !important;
  height: 15px !important;
  background: gray !important;
}

.myswitch[aria-checked="true"] {
  background: red !important;
}

.myswitch div {
  width: 15px;
  height: 15px;
  background: red;
}

then:

<Switch className="myswitch" />

https://codesandbox.io/s/styling-rebass-switch-o0j8t

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
  • 1
    Hi @amaster , can I ask you some feedback about my answer? – Daniele Ricci Jul 20 '20 at 12:47
  • It is a viable solution but is hard coded CSS :( when I am trying to use a theme file and do some tweaking within components. So not the answer I am looking for, but a good answer nonetheless. – amaster Jul 20 '20 at 19:17
1

You could use CSS transform scale to scale down/up an element and it's children. As, you're using emotion, here's something that goes along with it.

scale documentation

CodeSandbox: https://codesandbox.io/s/styling-rebass-switch-5fqku?file=/src/App.js

import React, { useState } from "react";
import styled from "@emotion/styled";
import { Label, Checkbox, Switch } from "@rebass/forms";

const Title = styled.h1`
  text-align: center;
`;

const FormLabel = styled(Label)`
  align-items: center;
`;

const Control = styled.div`
  width: 40px;
`;

const Toggle = styled(Switch)`
  transform: scale(.7)
`;


export default function App() {
  const [switched, setSwitched] = useState(false);
  const toggleSwitch = () => {
    setSwitched(!switched);
  };
  return (
    <div className="App">
      <Title>How to Style Rebass/Forms Switch</Title>
      <FormLabel sx={{ padding: "10px" }}>
        <Control>
          <Checkbox size="16px" sx={{ marginLeft: "10px" }} />
        </Control>
        CheckBox
      </FormLabel>
      <FormLabel sx={{ padding: "10px" }}>
        <Control>
          <Toggle
            checked={switched}
            onClick={() => toggleSwitch()}
          />
        </Control>
        Switch
      </FormLabel>
    </div>
  );
}
sudo bangbang
  • 27,127
  • 11
  • 75
  • 77
1

https://codesandbox.io/s/styling-rebass-switch-zto4z?file=/src/styles.css:37-1020

button.switch,
button.switch:hover,
button.switch:focus {
  outline: none;
  border: 1px solid grey;
  box-shadow: none;
}

button.switch > div {
  content: "";
  width: 14px;
  height: 14px;
  background-color: #9fa2ab;
}

button.switch > div:after {
  content: "";
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  left: -2px;
  top: -3px;
  transition: left 0.3s ease, background-color 0.3s ease, box-shadow 0.1s ease,
    transform 0.1s ease;
  background-color: #5b5c60;
  -webkit-box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
    0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
  box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
    0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}

button.switch[aria-checked="true"] {
  background-color: #d0f35e;
}
button.switch[aria-checked="true"] > div:after {
  background-color: #86af00;
}
button.switch[aria-checked="false"] {
  background-color: #ffffff;
  left: 18px;
}

Add class switch

<Switch
            
            className="switch"
            sx={{ width: "30px", height: "15px" }}
            checked={switched}
            onClick={() => toggleSwitch()}
          />
Velu S Gautam
  • 822
  • 9
  • 18