-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch: New component contribution (#825)
* feat(switch): add toggle switch * feat(switch): clean up tests and storybook stories * feat(switch): update color palette, fix stories * feat(switch): update spacing and cursors * feat(switch): update colors to be accessible * feat(switch): add change event to storybook and docs * Update packages/pharos-site/static/guidelines/switch.docs.tsx Co-authored-by: Brent Swisher <[email protected]> * feat(switch): sizing and styling * docs(switch): add to site navigation * docs(switch): register new component in pharos-site * feat(switch): re-add font weight * feat(switch): fix transition timing * feat(switch): use inset border increase and update outline offset --------- Co-authored-by: Brent Swisher <[email protected]> Co-authored-by: Brent Swisher <[email protected]>
- Loading branch information
1 parent
62a6b66
commit fc902f2
Showing
15 changed files
with
741 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@ithaka/pharos-site': minor | ||
'@ithaka/pharos': minor | ||
--- | ||
|
||
Add switch component |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
nodejs 20.11.1 | ||
nodejs 20.18.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as default } from '@guidelines/switch.docs'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import PageSection from '@components/statics/PageSection.tsx'; | ||
import BestPractices from '@components/statics/BestPractices.tsx'; | ||
import { PharosSwitch, PharosHeading, PharosLink } from '@ithaka/pharos/lib/react-components'; | ||
import Canvas from '../../src/components/Canvas'; | ||
import { FC } from 'react'; | ||
|
||
const SwitchPage: FC = () => { | ||
return ( | ||
<> | ||
<PageSection | ||
title="Switch" | ||
description="Switch are stylized toggles, similar to checkboxes, to indicate a boolean value." | ||
isHeader | ||
storyBookType="forms" | ||
> | ||
<PharosSwitch> | ||
<span slot="label">I am a switch</span> | ||
</PharosSwitch> | ||
</PageSection> | ||
<PageSection | ||
topMargin | ||
title="Usage" | ||
description="The switch component is used to toggle if a control is enabled or disabled. The value of the switch is persisted at the moment the control is updated, without a separate save mechanism." | ||
> | ||
<PageSection title="Alignment" subSectionLevel={1}> | ||
<p>Labels for switches are placed to the left of the switch.</p> | ||
</PageSection> | ||
<PageSection title="Placement" subSectionLevel={1}> | ||
<p> | ||
Switches in JSTOR are often used when enabling or disabling features on the site. Their | ||
larger size and clear on/off state make them easy to use and understand. | ||
</p> | ||
</PageSection> | ||
</PageSection>{' '} | ||
<PageSection title="Best practices"> | ||
<BestPractices | ||
Do={ | ||
<ul> | ||
<li> | ||
Use switches when users need to choose "yes" or "no" per option, with no | ||
indeterminate state | ||
</li> | ||
<li> | ||
Switches should work independently from each other, unless there are switches to | ||
control groups | ||
</li> | ||
<li>Switches should always include a label</li> | ||
<li>Use switches when choices are not mutually exclusive</li> | ||
<li>The list of choices should be in a logical order</li> | ||
<li>Value of the switch should be persisted when the control is changed</li> | ||
</ul> | ||
} | ||
Dont={ | ||
<ul> | ||
<li>Don't use switches when there are no choices or a choice is non-binary</li> | ||
<li> | ||
Don't use where only one choice in a group is allowed. Consider using the radio | ||
button component instead | ||
</li> | ||
<li> | ||
Don't make switches affect other switches unless there is a clear hierarchy of | ||
controls (aka master switch and sub-switches) | ||
</li> | ||
</ul> | ||
} | ||
/> | ||
</PageSection>{' '} | ||
<PageSection title="Content guidelines"> | ||
<PageSection title="Labels" subSectionLevel={1}> | ||
<ul> | ||
<li> | ||
Labels are descriptive and succinct. They should provide further clarity for the user. | ||
</li> | ||
<li>Labels should not end in punctuation.</li> | ||
<li>Use Sentence case for labels.</li> | ||
<li> | ||
Avoid using negative language as it can be counterintuitive. For example, "I agree to | ||
the terms" instead of "I don't agree to the terms." | ||
</li> | ||
<li> | ||
Long labels may wrap to a second line, but consider rewording the label if it gets too | ||
long. | ||
</li> | ||
<li>Labels should not be truncated.</li> | ||
</ul> | ||
</PageSection> | ||
</PageSection>{' '} | ||
<PageSection title="States"> | ||
<PageSection title="Default" subSectionLevel={1}> | ||
<p>Indicates that the switch is interactable.</p> | ||
<Canvas> | ||
<PharosSwitch> | ||
<span slot="label">Switch label</span> | ||
</PharosSwitch> | ||
</Canvas> | ||
</PageSection> | ||
<PageSection title="Disabled" subSectionLevel={1}> | ||
<p>Indicates that the switch should not be interactable.</p> | ||
<Canvas> | ||
<PharosSwitch disabled> | ||
<span slot="label">Switch label</span> | ||
</PharosSwitch> | ||
</Canvas> | ||
</PageSection> | ||
<PageSection title="Checked" subSectionLevel={1}> | ||
<p>Indicates that the switch is selected and will submit a checked value of true.</p> | ||
<Canvas> | ||
<PharosSwitch checked> | ||
<span slot="label">Switch label</span> | ||
</PharosSwitch> | ||
</Canvas> | ||
</PageSection> | ||
<PageSection title="Unchecked" subSectionLevel={1}> | ||
<p> | ||
Indicates that the switch is not selected and will submit an unchecked value of false. | ||
</p> | ||
<Canvas> | ||
<PharosSwitch> | ||
<span slot="label">Switch label</span> | ||
</PharosSwitch> | ||
</Canvas> | ||
</PageSection> | ||
</PageSection> | ||
<PageSection title="Accessibility"> | ||
<PageSection subSectionLevel={1} title="Relevant WCAG guidelines"> | ||
<ul> | ||
<li> | ||
<PharosLink href="https://www.w3.org/WAI/WCAG21/Understanding/name-role-value"> | ||
4.1.2 Name, Role, Value A | ||
</PharosLink> | ||
</li> | ||
</ul> | ||
</PageSection> | ||
<PageSection subSectionLevel={1} title="Importance"> | ||
Switches help users to understand the relationship between selections in a form element or | ||
filter. A switch grouping also will typically allow users to select more than one item in | ||
the grouping. | ||
</PageSection> | ||
<PageSection subSectionLevel={1} title="Code expectations"> | ||
<ul> | ||
<li> The switch has the role "switch". </li> | ||
<li> | ||
When the switch is selected, the ARIA state is set to <code>aria-checked="true"</code>{' '} | ||
and when it is deselected <code>aria-checked="false"</code>. | ||
</li> | ||
</ul> | ||
</PageSection> | ||
<PageSection subSectionLevel={1} title="Expected actions"> | ||
<PageSection subSectionLevel={2} title="Screen reader"> | ||
<ul> | ||
<li>Reads: "item name, state (checked or unchecked), switch, group" </li> | ||
</ul> | ||
</PageSection> | ||
<PageSection subSectionLevel={2} title="Keyboard"> | ||
<ul> | ||
<li> | ||
The <kbd>Space</kbd> key can be used to select and deselect each switch when it has | ||
focus. | ||
</li> | ||
<li> | ||
Users can navigate between switch inputs by pressing <kbd>Tab</kbd> or{' '} | ||
<kbd>Shift</kbd>-<kbd>Tab</kbd>. | ||
</li> | ||
<li>Switches identified as `disabled` attribute are ignored in the tab order.</li> | ||
</ul> | ||
</PageSection> | ||
</PageSection> | ||
</PageSection> | ||
</> | ||
); | ||
}; | ||
export default SwitchPage; |
41 changes: 41 additions & 0 deletions
41
packages/pharos/src/components/switch/PharosSwitch.react.stories.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { PharosSwitch } from '../../react-components/switch/pharos-switch'; | ||
import { configureDocsPage } from '@config/docsPageConfig'; | ||
import { PharosContext } from '../../utils/PharosContext'; | ||
import { action } from '@storybook/addon-actions'; | ||
|
||
export default { | ||
title: 'Forms/Switch', | ||
component: PharosSwitch, | ||
decorators: [ | ||
(Story) => ( | ||
<PharosContext.Provider value={{ prefix: 'storybook' }}> | ||
<Story /> | ||
</PharosContext.Provider> | ||
), | ||
], | ||
parameters: { | ||
docs: { | ||
page: configureDocsPage('switch'), | ||
}, | ||
options: { selectedPanel: 'addon-controls' }, | ||
}, | ||
}; | ||
|
||
export const Base = { | ||
render: ({ disabled, checked }) => ( | ||
<PharosSwitch | ||
disabled={disabled} | ||
checked={checked} | ||
onChange={(e) => action('Change')(e.target.checked)} | ||
> | ||
<span slot="label">Toggle Switch</span> | ||
</PharosSwitch> | ||
), | ||
args: { | ||
disabled: false, | ||
checked: false, | ||
}, | ||
parameters: { | ||
options: { selectedPanel: 'addon-actions' }, | ||
}, | ||
}; |
123 changes: 123 additions & 0 deletions
123
packages/pharos/src/components/switch/pharos-switch.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
@use '../../utils/scss/mixins'; | ||
|
||
.input-wrapper { | ||
@include mixins.option-wrapper; | ||
|
||
align-items: center; | ||
column-gap: var(--pharos-spacing-1-x); | ||
} | ||
|
||
.switch__control { | ||
height: 32px; | ||
width: 70px; | ||
display: flex; | ||
align-items: center; | ||
box-sizing: border-box; | ||
cursor: pointer; | ||
border: 1px solid; | ||
border-color: var(--pharos-color-marble-gray-80); | ||
border-radius: 2rem; | ||
background-color: var(--pharos-color-marble-gray-94); | ||
transition: | ||
background-color var(--pharos-transition-duration-default) ease-out, | ||
border-color var(--pharos-transition-duration-default) ease-out, | ||
box-shadow var(--pharos-transition-duration-default) ease-out; | ||
position: relative; | ||
|
||
&:hover { | ||
border-color: var(--pharos-color-marble-gray-30); | ||
box-shadow: inset 0 0 0 1px var(--pharos-color-marble-gray-30); | ||
} | ||
|
||
&::before { | ||
position: absolute; | ||
content: ''; | ||
height: 1rem; | ||
width: 1rem; | ||
background-color: var(--pharos-color-marble-gray-30); | ||
left: 9px; | ||
transition: var(--pharos-transition-duration-default) ease-out; | ||
border-radius: 50%; | ||
} | ||
|
||
&::after { | ||
font-weight: var(--pharos-font-weight-bold); | ||
color: var(--pharos-color-marble-gray-30); | ||
content: 'OFF'; | ||
position: absolute; | ||
right: 9px; | ||
transition: color var(--pharos-transition-duration-default) ease-out; | ||
font-size: var(--pharos-font-size-small); | ||
} | ||
|
||
.switch__input:checked + .input-wrapper & { | ||
background-color: var(--pharos-color-green-97); | ||
border-color: var(--pharos-color-green-base); | ||
|
||
&:hover { | ||
box-shadow: inset 0 0 0 1px var(--pharos-color-green-base); | ||
} | ||
|
||
&::before { | ||
background-color: var(--pharos-color-green-base); | ||
left: calc(100% - 9px - 1rem); | ||
} | ||
|
||
&::after { | ||
color: var(--pharos-color-green-base); | ||
content: 'ON'; | ||
left: 9px; | ||
right: unset; | ||
} | ||
} | ||
} | ||
|
||
.switch__label { | ||
cursor: pointer; | ||
} | ||
|
||
#switch-element { | ||
@include mixins.option-input; | ||
} | ||
|
||
#switch-element:focus-visible { | ||
+ .input-wrapper .switch__control { | ||
outline: 2px solid var(--pharos-color-focus); | ||
outline-offset: 2px; | ||
border-color: var(--pharos-color-marble-gray-30); | ||
box-shadow: inset 0 0 0 1px var(--pharos-color-marble-gray-30); | ||
} | ||
} | ||
|
||
#switch-element:disabled { | ||
+ .input-wrapper .switch__label { | ||
cursor: default; | ||
} | ||
|
||
+ .input-wrapper .switch__control { | ||
cursor: default; | ||
background-color: var(--pharos-color-marble-gray-base); | ||
border-color: var(--pharos-color-marble-gray-94); | ||
|
||
&:hover { | ||
box-shadow: none; | ||
} | ||
|
||
&::before { | ||
background-color: var(--pharos-color-marble-gray-50); | ||
} | ||
|
||
&::after { | ||
color: var(--pharos-color-marble-gray-50); | ||
} | ||
} | ||
} | ||
|
||
#switch-element:focus-visible:checked { | ||
+ .input-wrapper .switch__control { | ||
outline: 2px solid var(--pharos-color-focus); | ||
outline-offset: 2px; | ||
border-color: var(--pharos-color-green-base); | ||
box-shadow: inset 0 0 0 1px var(--pharos-color-green-base); | ||
} | ||
} |
Oops, something went wrong.