Skip to content

Design System: Add Switch Component #6799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 17, 2021
Merged

Conversation

samwhale
Copy link
Contributor

@samwhale samwhale commented Mar 17, 2021

Context

Add Switch component to the design system. Matches these designs: https://www.figma.com/file/bMhG3KyrJF8vIAODgmbeqT/Design-System?node-id=3398%3A94450

Summary

Adds the Switch component to the design system.

Other changes:

  • fixes bug where focusCSS was not rendering the correct focus styling

switch

Relevant Technical Choices

Based on the original Switch here: https://github.com/google/web-stories-wp/blob/main/assets/src/edit-story/components/form/switch.js

To-do

none

User-facing changes

none

Testing Instructions

  1. Go to storybook
  2. Design System -> Switch
  3. Play with the switch
    • Should be keyboard accessible like radio group
      • selection-follow-focus: arrow keys change focus and value of the Switch
      • 'tab' focuses a different radio group
    • pressing 'space' and 'enter' change the value of the switch

QA

  • This is a non-user-facing change and requires no QA

UAT

  • UAT should use the same steps as above.
  1. Go to storybook
  2. Design System -> Switch
  3. Play with the switch
    • Should be keyboard accessible like radio group
      • selection-follow-focus: arrow keys change focus and value of the Switch
      • 'tab' focuses a different radio group
    • pressing 'space' and 'enter' change the value of the switch

Reviews

Does this PR have a security-related impact?

no

Does this PR change what data or activity we track or use?

no

Does this PR have a legal-related impact?

no

Checklist

  • This PR addresses an existing issue and I have linked this PR to it in ZenHub
  • I have tested this code to the best of my abilities
  • I have verified accessibility to the best of my abilities (docs)
  • I have verified i18n and l10n (translation, right-to-left layout) to the best of my abilities
  • This PR contains automated tests (unit, integration, and/or e2e) to verify the code works as intended (docs)
  • I have added documentation where necessary
  • I have added a matching Type: XYZ label to the PR

Partially addresses #6492

* focusCSS checkes type of the first argument now
@samwhale samwhale added Type: Enhancement New feature or improvement of an existing feature Pod: Prometheus Group: Design System labels Mar 17, 2021
@samwhale samwhale self-assigned this Mar 17, 2021
@google-cla google-cla bot added the cla: yes label Mar 17, 2021
@github-actions
Copy link
Contributor

github-actions bot commented Mar 17, 2021

Size Change: +3.85 kB (0%)

Total Size: 1.65 MB

Filename Size Change
assets/js/edit-story.js 339 kB +1.44 kB (0%)
assets/js/stories-dashboard.js 264 kB +960 B (0%)
assets/js/web-stories-block.js 424 kB +1.45 kB (0%)
ℹ️ View Unchanged
Filename Size Change
assets/css/carousel-view-rtl.css 715 B 0 B
assets/css/carousel-view.css 716 B 0 B
assets/css/edit-story-rtl.css 267 B 0 B
assets/css/edit-story.css 267 B 0 B
assets/css/stories-dashboard-rtl.css 284 B 0 B
assets/css/stories-dashboard.css 284 B 0 B
assets/css/vendors~edit-story-rtl.css 702 B 0 B
assets/css/vendors~edit-story.css 702 B 0 B
assets/css/web-stories-block-rtl.css 3.25 kB 0 B
assets/css/web-stories-block.css 3.28 kB 0 B
assets/css/web-stories-embed-rtl.css 284 B 0 B
assets/css/web-stories-embed.css 284 B 0 B
assets/css/web-stories-list-styles-rtl.css 2.26 kB 0 B
assets/css/web-stories-list-styles.css 2.28 kB 0 B
assets/css/web-stories-theme-style-twentyeleven-rtl.css 102 B 0 B
assets/css/web-stories-theme-style-twentyeleven.css 102 B 0 B
assets/css/web-stories-theme-style-twentyfifteen-rtl.css 252 B 0 B
assets/css/web-stories-theme-style-twentyfifteen.css 252 B 0 B
assets/css/web-stories-theme-style-twentyfourteen-rtl.css 286 B 0 B
assets/css/web-stories-theme-style-twentyfourteen.css 286 B 0 B
assets/css/web-stories-theme-style-twentyseventeen-rtl.css 311 B 0 B
assets/css/web-stories-theme-style-twentyseventeen.css 311 B 0 B
assets/css/web-stories-theme-style-twentysixteen-rtl.css 239 B 0 B
assets/css/web-stories-theme-style-twentysixteen.css 239 B 0 B
assets/css/web-stories-theme-style-twentyten-rtl.css 144 B 0 B
assets/css/web-stories-theme-style-twentyten.css 145 B 0 B
assets/css/web-stories-theme-style-twentytwelve-rtl.css 263 B 0 B
assets/css/web-stories-theme-style-twentytwelve.css 263 B 0 B
assets/css/web-stories-theme-style-twentytwenty-rtl.css 86 B 0 B
assets/css/web-stories-theme-style-twentytwenty.css 86 B 0 B
assets/css/web-stories-theme-style-twentytwentyone-rtl.css 263 B 0 B
assets/css/web-stories-theme-style-twentytwentyone.css 265 B 0 B
assets/css/web-stories-widget.css 489 B 0 B
assets/js/carousel-view.js 3.71 kB 0 B
assets/js/chunk-fonts-********************.js 45 kB 0 B
assets/js/chunk-web-stories-template-0-********************.js 11 kB 0 B
assets/js/chunk-web-stories-template-1-********************.js 11.3 kB 0 B
assets/js/chunk-web-stories-template-2-********************.js 10.9 kB 0 B
assets/js/chunk-web-stories-template-3-********************.js 10.6 kB 0 B
assets/js/chunk-web-stories-template-4-********************.js 12.7 kB 0 B
assets/js/chunk-web-stories-template-5-********************.js 7.14 kB 0 B
assets/js/chunk-web-stories-template-6-********************.js 10.3 kB 0 B
assets/js/chunk-web-stories-template-7-********************.js 10.1 kB 0 B
assets/js/chunk-web-stories-textset-0-********************.js 5.23 kB 0 B
assets/js/chunk-web-stories-textset-1-********************.js 6.79 kB 0 B
assets/js/chunk-web-stories-textset-2-********************.js 7.81 kB 0 B
assets/js/chunk-web-stories-textset-3-********************.js 15.3 kB 0 B
assets/js/chunk-web-stories-textset-4-********************.js 4.38 kB 0 B
assets/js/chunk-web-stories-textset-5-********************.js 5.64 kB 0 B
assets/js/chunk-web-stories-textset-6-********************.js 5.43 kB 0 B
assets/js/chunk-web-stories-textset-7-********************.js 10.3 kB 0 B
assets/js/lightbox.js 985 B 0 B
assets/js/tinymce-button.js 3.48 kB 0 B
assets/js/vendors~chunk-ffmpeg-********************.js 5.61 kB 0 B
assets/js/vendors~edit-story-********************.js 61.4 kB 0 B
assets/js/vendors~edit-story~stories-dashboard-********************.js 267 kB 0 B
assets/js/web-stories-activation-notice.js 68.2 kB 0 B
assets/js/web-stories-embed.js 492 B 0 B
assets/js/web-stories-widget.js 984 B 0 B

compressed-size-action

@@ -35,7 +35,7 @@ export const focusCSS = (accent, background) => css`
outline: none;
box-shadow: ${({ theme }) =>
`0px 0px 0 2px ${background || theme.colors.bg.primary}, 0px 0px 0 4px ${
accent || theme.colors.border.focus
typeof accent === 'string' ? accent : theme.colors.border.focus
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using focusCSS in a styled component, the first argument will be the props of the component. The function was written with the following type:

type (accent: string, background: string) => string;

but when using it the accent is generally an object. Adding a typecheck to make sure accent is a string makes it so that we don't try to style using an object.

Comment on lines +167 to +170
useKeyDownEffect(radioGroupRef, ['space', 'enter'], handleKeyDown, [
handleKeyDown,
value,
]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch gets the accessibility from being a radio group. Maybe we shouldn't add this here and instead add it where we need it (in the editor) since it adds extra behavior that may not be wanted by all consumers of this component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like having the useKeyDownEffect here if we're expecting switches to have the same behavior across the board. maybe i am reading this comment wrong.

Copy link
Contributor Author

@samwhale samwhale Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm leaning into removing it tbh (and then adding it back in in the editor). This keydown effect adds extra functionality than what the browser gives us.

If this component were ever used in the future but they didn't want space and enter to change the value, they'd have to come into this component and change it first rather than the base component displaying the 'normal' browser interactions

Comment on lines 153 to 158
const handleChange = useCallback(
(evt) => {
onChange(evt, evt.target.value === 'true');
},
[onChange]
);
Copy link
Contributor Author

@samwhale samwhale Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm making the values of the radio buttons the booleans true and false. However, when they are rendered by react as html elements their values become the strings "true" and "false".

The value on the event is a string, so we need a way to give the boolean value to the onChange event handler.

I don't love this api, but I couldn't see any other way to do this that also exposed the event. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I think I'm not used to making the values of a radio either true or false. I feel like there are aspects of this that make it feel between a checkbox and a radio because in a sense we're trying to map checked/unchecked to two radio buttons.

I can't think of a better way to do it as a radio tho 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be any benefit to making it a checkbox? I feel like the arrow keys toggling it might make it weird at that point tho. hmph 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me a switch is a non-boolean checkbox (meaning the 2 values could be 1 and 2 or tacos and enchiladas rather than just a true/false state - but only because of values tied to accessibility), which makes it into a 2 input radio group. I think "traditional" radio input values aren't true/false and also expect more than 2 options so the string values make more sense. Given that a switch will always have 2 values and here making them booleans makes the component less tied to wherever it's getting used, it makes sense. At this point, i think the underlying HTML for this should just be what make the most sense semantically and for keyboard interaction. Which I think is what you have. That's my 2 cents!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're better off keeping it as a radio so that we can always only have one value selected. I updated the values to only call onChange with booleans though. Internally their values will be "ON" and "OFF"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I respect your decision to not make it "TACOS" & "ENCHILADAS" internally

*/
export const Switch = forwardRef(function (
{
className = '',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't add a mousetrap class here because we may want to use this apart from having that class.

Copy link
Contributor

@maxyinger maxyinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good!

I think the only slight functional issue I was seeing was that it wasn't updating the checked input properly in the rendered output.

Other than that I agree that the onChange is a little weird cus it's like mapping a toggle to a radio, but not sure of a better solution 🤷 . Maybe some ppl better than I with forms could shed some light

Comment on lines 153 to 158
const handleChange = useCallback(
(evt) => {
onChange(evt, evt.target.value === 'true');
},
[onChange]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I think I'm not used to making the values of a radio either true or false. I feel like there are aspects of this that make it feel between a checkbox and a radio because in a sense we're trying to map checked/unchecked to two radio buttons.

I can't think of a better way to do it as a radio tho 🤷

Comment on lines 153 to 158
const handleChange = useCallback(
(evt) => {
onChange(evt, evt.target.value === 'true');
},
[onChange]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be any benefit to making it a checkbox? I feel like the arrow keys toggling it might make it weird at that point tho. hmph 🤔

>
{onLabel}
<HiddenRadioButton
checked={value}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks fine to me, but for some reason when I inspect the markup, the checked attribute isn't switching between the selected radio button:

Kapture.2021-03-17.at.09.15.10.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some investigation and chatted with max about it. Here's our findings on this interesting looking bug:

There’s some really interesting talk about this online. It looks like it’s very common with the way people implement checkboxes:
facebook/react#6321 (comment)
facebook/react#3005 (comment)

It's funny - really popular libraries do this too like Material UI
material


So if we look at the values in the console, we see that checked is getting changed but it’s just not displayed in the tree
radios

This also happens to our other radio buttons (in design system and in the animation panel). We can fix this by adding a key that changes when the value is updated. This would invalidate the mounted component and re-mount a new one.

At the end of the day - in Javascript we use the checked property to determine the state of the checkbox. It is being updated correctly in javascript, but visually in the dom tree it is confusing 😕

Copy link
Contributor

@BrittanyIRL BrittanyIRL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, the notes you've added are real nice, thank you!

I think something goofy is happening with the text color on RTL?

rtl text color

`}

/* add focus styling on the slider when the hidden input is focused */
:focus-within ~ span {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 153 to 158
const handleChange = useCallback(
(evt) => {
onChange(evt, evt.target.value === 'true');
},
[onChange]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me a switch is a non-boolean checkbox (meaning the 2 values could be 1 and 2 or tacos and enchiladas rather than just a true/false state - but only because of values tied to accessibility), which makes it into a 2 input radio group. I think "traditional" radio input values aren't true/false and also expect more than 2 options so the string values make more sense. Given that a switch will always have 2 values and here making them booleans makes the component less tied to wherever it's getting used, it makes sense. At this point, i think the underlying HTML for this should just be what make the most sense semantically and for keyboard interaction. Which I think is what you have. That's my 2 cents!

Comment on lines +167 to +170
useKeyDownEffect(radioGroupRef, ['space', 'enter'], handleKeyDown, [
handleKeyDown,
value,
]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like having the useKeyDownEffect here if we're expecting switches to have the same behavior across the board. maybe i am reading this comment wrong.

* remove `mousetrap` class
@samwhale
Copy link
Contributor Author

samwhale commented Mar 17, 2021

I think something goofy is happening with the text color on RTL?

rtl text color

Wanna see something weird? Check this out:
I added the switch to the editor to see the styles there and it just works ??? 😱
switch-wat

I don't get why it's different in storybook vs the editor when in rtl ....

Copy link
Contributor

@BrittanyIRL BrittanyIRL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the RTL color issue I noticed is just storybook 🤷‍♀️

@samwhale
Copy link
Contributor Author

Added a bug here for tracking: #6809. I won't try to fix the storybook bug here since it's out of scope 😞

@samwhale samwhale merged commit 64944fc into main Mar 17, 2021
@samwhale samwhale deleted the feature/6492-switch-component branch March 17, 2021 21:04
@samwhale samwhale linked an issue Mar 18, 2021 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Group: Design System Type: Enhancement New feature or improvement of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Redesign: Generic switch component
3 participants