-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(switch): add new package (#2625)
* feat(switch): add package * feat(tokens): add new icon size, adjust dark colors * chore(box): add _checked_hover selector for switch * chore(theme): add new icon size to theme shape * feat(internal-docs): new tokens and components * test(switch): add tests * docs(checkbox, radio, chat-log): small improvements * chore(switch): small type fixes * docs(switch): add docs page * chore(tokens): typo * chore(switch): pr feedback * fix: tidy up some markup and accessibility issues * chore(switch): feedback from pr * chore(switch): small fixes * chore(switch): a11y violation in story Co-authored-by: Si Taggart <me@simontaggart.com>
- Loading branch information
Showing
41 changed files
with
1,035 additions
and
13 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 @@ | ||
--- | ||
'@twilio-paste/label': minor | ||
'@twilio-paste/core': minor | ||
--- | ||
|
||
[Label] add the ability to use the label as a div HTML element |
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 @@ | ||
--- | ||
'@twilio-paste/switch': major | ||
'@twilio-paste/core': minor | ||
--- | ||
|
||
[Switch] add Switch package |
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 @@ | ||
--- | ||
'@twilio-paste/core': patch | ||
'@twilio-paste/theme': patch | ||
--- | ||
|
||
[Theme] add new icon size (05) to theme shape |
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 @@ | ||
--- | ||
'@twilio-paste/core': patch | ||
'@twilio-paste/box': patch | ||
--- | ||
|
||
[Box] add a `_checked_hover` pseudo selector style prop for use in switch package |
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 @@ | ||
--- | ||
'@twilio-paste/core': minor | ||
'@twilio-paste/design-tokens': minor | ||
--- | ||
|
||
[Design tokens] add new icon size and line height tokens (05), adust dark theme background tokens |
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,13 @@ | ||
# Adding New Design Tokens | ||
|
||
Tokens live in `packages/paste-design-tokens/tokens/global`. Dark theme tokens live in `packages/paste-design-tokens/tokens/dark/global`. | ||
|
||
After adding a new token, bump the package and core up as a minor by running `yarn changeset`. | ||
|
||
## Colors | ||
|
||
When adding new color tokens, make sure the value points to an alias (e.g. `red-60`). | ||
|
||
## Icon sizes | ||
|
||
Make sure the new icon size points to a line height. You may have to add a new token to fit your needs. You'll also have to add the new line height to the alias file. You'll also need to add the new icon size to the `packages/paste-theme/src/generateThemeFromTokens` file and the `packages/paste-theme/src/types/GenericThemeShape` file. That will require a re-build and a snapshot update for the theme shape. |
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
102 changes: 102 additions & 0 deletions
102
packages/paste-core/components/switch/__tests__/index.spec.tsx
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,102 @@ | ||
import * as React from 'react'; | ||
import {render} from '@testing-library/react'; | ||
import {CustomizationProvider} from '@twilio-paste/customization'; | ||
import {Default, Disabled, On, Required} from '../stories/index.stories'; | ||
|
||
describe('Switch', () => { | ||
it('should render as role="switch"', () => { | ||
const {getByRole} = render(<Default />); | ||
expect(getByRole('switch')).not.toBeNull(); | ||
}); | ||
it('should render as disabled', () => { | ||
const {getByRole} = render(<Disabled />); | ||
expect(getByRole('switch').getAttribute('aria-disabled')).toBe('true'); | ||
}); | ||
it('should default to aria-checked="false"', () => { | ||
const {getByRole} = render(<Default />); | ||
expect(getByRole('switch').getAttribute('aria-checked')).toBe('false'); | ||
}); | ||
it('should add aria-checked when switch is "on"', () => { | ||
const {getByRole} = render(<On />); | ||
expect(getByRole('switch').getAttribute('aria-checked')).toBe('true'); | ||
}); | ||
it('should set aria-labelledby to the label id', () => { | ||
const {getByRole, container} = render(<Default />); | ||
const labelId = getByRole('switch').getAttribute('aria-labelledby'); | ||
expect(container.querySelector('[data-paste-element="SWITCH_CONTAINER_LABEL"]')?.getAttribute('id')).toEqual( | ||
labelId | ||
); | ||
}); | ||
it('should set aria-describedby to the help text id when past', () => { | ||
const {getByRole, container} = render(<Default />); | ||
const describedbyId = getByRole('switch').getAttribute('aria-describedby'); | ||
expect(container.querySelector('[data-paste-element="SWITCH_CONTAINER_HELP_TEXT"]')?.getAttribute('id')).toEqual( | ||
describedbyId | ||
); | ||
}); | ||
}); | ||
|
||
describe('Switch customization', () => { | ||
it('should set an element data attribute on Switch', () => { | ||
const {getByRole, container} = render(<Required />); | ||
expect(getByRole('switch').dataset.pasteElement).toEqual('SWITCH'); | ||
expect(getByRole('switch').firstChild?.firstChild).toHaveAttribute('data-paste-element', 'SWITCH_KNOB'); | ||
expect(getByRole('switch').querySelector('[data-paste-element="SWITCH_ICON"]')).toBeInTheDocument(); | ||
expect(container.querySelector('[data-paste-element="SWITCH_CONTAINER_HELP_TEXT"]')).toBeInTheDocument(); | ||
expect(container.querySelector('[data-paste-element="SWITCH_CONTAINER_LABEL"]')).toBeInTheDocument(); | ||
}); | ||
it('should set custom element data attributes on Switch', () => { | ||
const {getByRole, container} = render(<Required element="MY_SWITCH" />); | ||
expect(getByRole('switch').dataset.pasteElement).toEqual('MY_SWITCH'); | ||
expect(getByRole('switch').firstChild?.firstChild).toHaveAttribute('data-paste-element', 'MY_SWITCH_KNOB'); | ||
expect(getByRole('switch').querySelector('[data-paste-element="MY_SWITCH_ICON"]')).toBeInTheDocument(); | ||
expect(container.querySelector('[data-paste-element="MY_SWITCH_HELP_TEXT"]')).toBeInTheDocument(); | ||
expect(container.querySelector('[data-paste-element="MY_SWITCH_LABEL"]')).toBeInTheDocument(); | ||
}); | ||
it('should add custom styling to Switch', () => { | ||
const {getByRole, container} = render( | ||
<CustomizationProvider | ||
theme={TestTheme} | ||
elements={{ | ||
SWITCH: {height: '30px', width: '52'}, | ||
SWITCH_KNOB: {height: '26px', width: '26px'}, | ||
SWITCH_CONTAINER_HELP_TEXT: {backgroundColor: 'colorBackgroundAvailable'}, | ||
SWITCH_CONTAINER_LABEL: {backgroundColor: 'colorBackgroundBrandStrong'}, | ||
}} | ||
> | ||
<Required /> | ||
</CustomizationProvider> | ||
); | ||
const theSwitch = getByRole('switch'); | ||
const switchKnob = getByRole('switch').firstChild?.firstChild; | ||
const switchLabel = container.querySelector('[data-paste-element="SWITCH_CONTAINER_LABEL"]'); | ||
const switchHelpText = container.querySelector('[data-paste-element="SWITCH_CONTAINER_HELP_TEXT"]'); | ||
expect(theSwitch).toHaveStyleRule('height', '30px'); | ||
expect(switchKnob).toHaveStyleRule('height', '26px'); | ||
expect(switchLabel).toHaveStyleRule('background-color', 'rgb(3, 11, 93)'); | ||
expect(switchHelpText).toHaveStyleRule('background-color', 'rgb(20, 176, 83)'); | ||
}); | ||
it('should add custom styling to a custom named Switch', () => { | ||
const {getByRole, container} = render( | ||
<CustomizationProvider | ||
theme={TestTheme} | ||
elements={{ | ||
MY_SWITCH: {height: '30px', width: '52'}, | ||
MY_SWITCH_KNOB: {height: '26px', width: '26px'}, | ||
MY_SWITCH_HELP_TEXT: {backgroundColor: 'colorBackgroundAvailable'}, | ||
MY_SWITCH_LABEL: {backgroundColor: 'colorBackgroundBrandStrong'}, | ||
}} | ||
> | ||
<Required element="MY_SWITCH" /> | ||
</CustomizationProvider> | ||
); | ||
const toggle = getByRole('switch'); | ||
const toggleKnob = getByRole('switch').firstChild?.firstChild; | ||
const toggleLabel = container.querySelector('[data-paste-element="MY_SWITCH_LABEL"]'); | ||
const toggleHelpText = container.querySelector('[data-paste-element="MY_SWITCH_HELP_TEXT"]'); | ||
expect(toggle).toHaveStyleRule('height', '30px'); | ||
expect(toggleKnob).toHaveStyleRule('height', '26px'); | ||
expect(toggleLabel).toHaveStyleRule('background-color', 'rgb(3, 11, 93)'); | ||
expect(toggleHelpText).toHaveStyleRule('background-color', 'rgb(20, 176, 83)'); | ||
}); | ||
}); |
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,3 @@ | ||
const {build} = require('../../../../tools/build/esbuild'); | ||
|
||
build(require('./package.json')); |
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,57 @@ | ||
{ | ||
"name": "@twilio-paste/switch", | ||
"version": "0.0.0", | ||
"category": "interaction", | ||
"status": "production", | ||
"description": "A switch is an interactive binary control.", | ||
"author": "Twilio Inc.", | ||
"license": "MIT", | ||
"main:dev": "src/index.tsx", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "yarn clean && NODE_ENV=production node build.js && tsc", | ||
"build:js": "NODE_ENV=development node build.js", | ||
"build:props": "typedoc --tsconfig ./tsconfig.json --json ./dist/prop-types.json", | ||
"clean": "rm -rf ./dist", | ||
"tsc": "tsc" | ||
}, | ||
"peerDependencies": { | ||
"@twilio-paste/box": "^7.0.0", | ||
"@twilio-paste/design-tokens": "^8.0.0", | ||
"@twilio-paste/help-text": "^10.0.0", | ||
"@twilio-paste/icons": "^9.0.0", | ||
"@twilio-paste/label": "^10.0.0", | ||
"@twilio-paste/media-object": "^7.0.0", | ||
"@twilio-paste/style-props": "^6.0.0", | ||
"@twilio-paste/text": "^7.0.0", | ||
"@twilio-paste/theme": "^8.0.0", | ||
"@twilio-paste/uid-library": "^0.2.5", | ||
"prop-types": "^15.7.2", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2" | ||
}, | ||
"devDependencies": { | ||
"@twilio-paste/box": "^7.0.0", | ||
"@twilio-paste/design-tokens": "^8.0.0", | ||
"@twilio-paste/help-text": "^10.0.0", | ||
"@twilio-paste/icons": "^9.0.0", | ||
"@twilio-paste/label": "^10.0.0", | ||
"@twilio-paste/media-object": "^7.0.0", | ||
"@twilio-paste/style-props": "^6.0.0", | ||
"@twilio-paste/text": "^7.0.0", | ||
"@twilio-paste/theme": "^8.0.0", | ||
"@twilio-paste/uid-library": "^0.2.5", | ||
"prop-types": "^15.7.2", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2" | ||
} | ||
} |
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,105 @@ | ||
import * as React from 'react'; | ||
import * as PropTypes from 'prop-types'; | ||
import type {SwitchProps} from './types'; | ||
import {SWITCH_HEIGHT, SWITCH_WIDTH} from './constants'; | ||
import {SwitchKnob} from './SwitchKnob'; | ||
import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; | ||
import type {BoxStyleProps} from '@twilio-paste/box'; | ||
|
||
const styles: BoxStyleProps = { | ||
backgroundColor: 'colorBackgroundStronger', | ||
_checked_hover: { | ||
backgroundColor: 'colorBackgroundPrimary', | ||
color: 'colorTextLink', | ||
}, | ||
_checked: { | ||
backgroundColor: 'colorBackgroundPrimaryStronger', | ||
color: 'colorTextLinkStronger', | ||
}, | ||
_hover: { | ||
backgroundColor: 'colorBackgroundStrongest', | ||
cursor: 'pointer', | ||
}, | ||
_disabled: { | ||
backgroundColor: 'colorBackgroundStrong', | ||
color: 'colorTextIcon', | ||
cursor: 'not-allowed', | ||
}, | ||
_focus: {boxShadow: 'shadowFocus'}, | ||
}; | ||
|
||
const Switch = React.forwardRef<HTMLDivElement, SwitchProps>( | ||
({element = 'SWITCH', id, labelId, helpTextId, disabled = false, on = false, onClick, ...props}, ref) => { | ||
const [switchIsOn, setSwitchIsOn] = React.useState(on); | ||
const [isHovering, setIsHovering] = React.useState(false); | ||
|
||
const handleClick = React.useCallback((): void => { | ||
if (!disabled) { | ||
setSwitchIsOn(!switchIsOn); | ||
if (onClick) onClick(); | ||
} | ||
}, [onClick, disabled, switchIsOn]); | ||
|
||
const handleKeyDown = React.useCallback((event: React.KeyboardEvent): void => { | ||
if (event.key === ' ' || event.key === 'Enter') setSwitchIsOn((prev) => !prev); | ||
}, []); | ||
|
||
return ( | ||
<Box | ||
{...safelySpreadBoxProps(props)} | ||
{...styles} | ||
as="div" | ||
role="switch" | ||
aria-checked={switchIsOn} | ||
aria-disabled={disabled} | ||
aria-labelledby={labelId} | ||
aria-describedby={helpTextId} | ||
element={element} | ||
id={id} | ||
ref={ref} | ||
tabIndex={0} | ||
outline="none" | ||
position="relative" | ||
display="inline-block" | ||
boxSizing="content-box" | ||
height={SWITCH_HEIGHT} | ||
width={SWITCH_WIDTH} | ||
overflow="hidden" | ||
padding="space10" | ||
borderColor="colorBorder" | ||
borderWidth="borderWidth10" | ||
borderRadius="borderRadiusPill" | ||
transition="background-color .2s ease-in-out, box-shadow .2s ease-in-out" | ||
onClick={handleClick} | ||
onKeyDown={handleKeyDown} | ||
onMouseEnter={() => { | ||
setIsHovering(true); | ||
}} | ||
onMouseLeave={() => { | ||
setIsHovering(false); | ||
}} | ||
> | ||
<SwitchKnob | ||
element={element} | ||
disabled={disabled} | ||
switchIsOn={switchIsOn} | ||
isHovering={isHovering} | ||
height={SWITCH_HEIGHT} | ||
/> | ||
</Box> | ||
); | ||
} | ||
); | ||
|
||
Switch.displayName = 'Switch'; | ||
|
||
Switch.propTypes = { | ||
disabled: PropTypes.bool, | ||
element: PropTypes.string, | ||
labelId: PropTypes.string, | ||
id: PropTypes.string, | ||
on: PropTypes.bool, | ||
onClick: PropTypes.func, | ||
}; | ||
|
||
export {Switch}; |
Oops, something went wrong.