-
Notifications
You must be signed in to change notification settings - Fork 51
feat(Checkbox): add component #1405
Changes from all commits
72a2e76
e459ebb
209167a
6af18cb
e78f34a
3e8727b
4116963
a99363e
3ce9692
489db2c
3d13c72
b57a98b
75b62ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { useBooleanKnob, useStringKnob } from '@stardust-ui/docs-components' | ||
| import { Checkbox } from '@stardust-ui/react' | ||
| import * as React from 'react' | ||
|
|
||
| const CheckboxPlayground: React.FunctionComponent = () => { | ||
| const [checked, setChecked] = useBooleanKnob({ name: 'checked' }) | ||
| const [disabled] = useBooleanKnob({ name: 'disabled' }) | ||
| const [toggle] = useBooleanKnob({ name: 'toggle' }) | ||
|
|
||
| const [label] = useStringKnob({ | ||
| name: 'label', | ||
| initialValue: 'Make profile visible', | ||
| }) | ||
|
|
||
| return ( | ||
| <Checkbox | ||
| checked={checked} | ||
| disabled={disabled} | ||
| label={label} | ||
| toggle={toggle} | ||
| onChange={(e, data) => setChecked(data.checked)} | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| export default CheckboxPlayground |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { Checkbox } from '@stardust-ui/react' | ||
|
|
||
| export const config: ScreenerTestsConfig = { | ||
| themes: ['base', 'teams'], | ||
| steps: [builder => builder.click(`.${Checkbox.className}`).snapshot('Can be checked')], | ||
| } | ||
|
|
||
| export default config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { Checkbox } from '@stardust-ui/react' | ||
| import * as React from 'react' | ||
|
|
||
| const CheckboxExample = () => <Checkbox label="Make my profile visible" /> | ||
|
|
||
| export default CheckboxExample |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { Checkbox } from '@stardust-ui/react' | ||
| import * as React from 'react' | ||
|
|
||
| const CheckboxExampleDisabled = () => ( | ||
| <> | ||
| <Checkbox disabled label="Disabled" /> | ||
| <Checkbox disabled checked label="Disabled & Checked" /> | ||
| </> | ||
| ) | ||
|
|
||
| export default CheckboxExampleDisabled |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { Checkbox } from '@stardust-ui/react' | ||
|
|
||
| export const config: ScreenerTestsConfig = { | ||
| themes: ['base', 'teams'], | ||
| steps: [builder => builder.click(`.${Checkbox.className}`).snapshot('Can be checked')], | ||
| } | ||
|
|
||
| export default config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { Checkbox } from '@stardust-ui/react' | ||
| import * as React from 'react' | ||
|
|
||
| const CheckboxExampleToggle = () => ( | ||
| <> | ||
| <Checkbox label="Subscribe to weekly newsletter" toggle /> | ||
| <Checkbox disabled checked label="Subscribe to weekly newsletter" toggle /> | ||
| </> | ||
| ) | ||
|
|
||
| export default CheckboxExampleToggle |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import * as React from 'react' | ||
|
|
||
| import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample' | ||
| import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection' | ||
|
|
||
| const Types = () => ( | ||
| <ExampleSection title="Types"> | ||
| <ComponentExample | ||
| title="Checkbox" | ||
| description="A standard checkbox." | ||
| examplePath="components/Checkbox/Types/CheckboxExample" | ||
| /> | ||
| <ComponentExample | ||
| title="Disabled" | ||
| description="A checkbox can be read-only and unable to change states." | ||
| examplePath="components/Checkbox/Types/CheckboxExampleDisabled" | ||
| /> | ||
| <ComponentExample | ||
| title="Toggle" | ||
| description="A checkbox can be formatted to show an on or off choice." | ||
| examplePath="components/Checkbox/Types/CheckboxExampleToggle" | ||
| /> | ||
| </ExampleSection> | ||
| ) | ||
|
|
||
| export default Types |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import * as React from 'react' | ||
| import Types from './Types' | ||
|
|
||
| const CheckboxExamples = () => ( | ||
| <> | ||
| <Types /> | ||
| </> | ||
| ) | ||
|
|
||
| export default CheckboxExamples |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| import * as customPropTypes from '@stardust-ui/react-proptypes' | ||
| import * as _ from 'lodash' | ||
| import * as React from 'react' | ||
| import * as PropTypes from 'prop-types' | ||
|
|
||
| import { | ||
| applyAccessibilityKeyHandlers, | ||
| AutoControlledComponent, | ||
| createShorthandFactory, | ||
| ChildrenComponentProps, | ||
| commonPropTypes, | ||
| isFromKeyboard, | ||
| UIComponentProps, | ||
| } from '../../lib' | ||
| import { ComponentEventHandler, WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types' | ||
| import Icon from '../Icon/Icon' | ||
| import Text from '../Text/Text' | ||
| import { Accessibility } from '../../lib/accessibility/types' | ||
| import { checkboxBehavior } from '../../lib/accessibility' | ||
|
|
||
| export interface CheckboxProps extends UIComponentProps, ChildrenComponentProps { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add conformat test for the new component.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added, have to add an additional handler for |
||
| /** | ||
| * Accessibility behavior if overridden by the user. | ||
| * @default checkboxBehavior | ||
| */ | ||
| accessibility?: Accessibility | ||
|
|
||
| /** Initial checked value. */ | ||
| defaultChecked?: boolean | ||
|
|
||
| /** Whether or not item is checked. */ | ||
| checked?: boolean | ||
|
|
||
| /** An item can appear disabled and be unable to change states. */ | ||
| disabled?: boolean | ||
|
|
||
| /** The item indicator can be user-defined icon. */ | ||
| icon?: ShorthandValue | ||
|
|
||
| /** The label of the item. */ | ||
| label?: ShorthandValue | ||
|
|
||
| /** | ||
| * Called after item checked state is changed. | ||
| * @param {SyntheticEvent} event - React's original SyntheticEvent. | ||
| * @param {object} data - All props. | ||
| */ | ||
| onChange?: ComponentEventHandler<CheckboxProps> | ||
|
|
||
| /** | ||
| * Called after click. | ||
| * @param {SyntheticEvent} event - React's original SyntheticEvent. | ||
| * @param {object} data - All props. | ||
| */ | ||
| onClick?: ComponentEventHandler<CheckboxProps> | ||
|
|
||
| /** A checkbox can be formatted to show an on or off choice. */ | ||
| toggle?: boolean | ||
| } | ||
|
|
||
| export interface CheckboxState { | ||
| checked: boolean | ||
| isFromKeyboard: boolean | ||
| } | ||
|
|
||
| class Checkbox extends AutoControlledComponent<WithAsProp<CheckboxProps>, CheckboxState> { | ||
| static create: Function | ||
|
|
||
| static displayName = 'Checkbox' | ||
|
|
||
| static className = 'ui-checkbox' | ||
|
|
||
| static propTypes = { | ||
| ...commonPropTypes.createCommon({ | ||
| content: false, | ||
| }), | ||
| checked: PropTypes.bool, | ||
| defaultChecked: PropTypes.bool, | ||
| disabled: PropTypes.bool, | ||
| icon: customPropTypes.itemShorthand, | ||
| label: customPropTypes.itemShorthand, | ||
| onChange: PropTypes.func, | ||
| onClick: PropTypes.func, | ||
| toggle: PropTypes.bool, | ||
| } | ||
|
|
||
| static defaultProps = { | ||
| accessibility: checkboxBehavior, | ||
| icon: {}, | ||
| } | ||
|
|
||
| static autoControlledProps = ['checked'] | ||
|
|
||
| actionHandlers = { | ||
| performClick: (e: any /* TODO: use React.KeyboardEvent */) => { | ||
| e.preventDefault() | ||
| this.handleClick(e) | ||
| }, | ||
| } | ||
|
|
||
| getInitialAutoControlledState(): CheckboxState { | ||
| return { checked: false, isFromKeyboard: false } | ||
| } | ||
|
|
||
| handleChange = (e: React.ChangeEvent) => { | ||
| // Checkbox component doesn't present any `input` component in markup, however all of our | ||
| // components should handle events transparently. | ||
| const { disabled } = this.props | ||
| const checked = !this.state.checked | ||
|
|
||
| if (!disabled) { | ||
| this.trySetState({ checked }) | ||
| _.invoke(this.props, 'onChange', e, { ...this.props, checked }) | ||
| } | ||
| } | ||
|
|
||
| handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { | ||
| const { disabled } = this.props | ||
| const checked = !this.state.checked | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, could be moved in the if statement
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will keep as it is for now :) |
||
|
|
||
| if (!disabled) { | ||
| this.trySetState({ checked }) | ||
|
|
||
| _.invoke(this.props, 'onClick', e, { ...this.props, checked }) | ||
| _.invoke(this.props, 'onChange', e, { ...this.props, checked }) | ||
| } | ||
| } | ||
|
|
||
| handleFocus = (e: React.FocusEvent) => { | ||
| this.setState({ isFromKeyboard: isFromKeyboard() }) | ||
|
|
||
| _.invoke(this.props, 'onFocus', e, this.props) | ||
| } | ||
|
|
||
| renderComponent({ ElementType, classes, unhandledProps, styles, accessibility }) { | ||
| const { label, icon, toggle } = this.props | ||
|
|
||
| return ( | ||
| <ElementType | ||
| className={classes.root} | ||
| onClick={this.handleClick} | ||
| onChange={this.handleChange} | ||
| onFocus={this.handleFocus} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this the thing missing for the focus styles? :)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep :) No IDE highlight 😭 |
||
| {...accessibility.attributes.root} | ||
| {...unhandledProps} | ||
| {...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)} | ||
| > | ||
| {Icon.create(icon, { | ||
| defaultProps: { | ||
| name: toggle ? 'stardust-circle' : 'stardust-checkmark', | ||
| styles: toggle ? styles.toggle : styles.checkbox, | ||
| }, | ||
| })} | ||
| {Text.create(label)} | ||
| </ElementType> | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| Checkbox.create = createShorthandFactory({ | ||
| Component: Checkbox, | ||
| mappedProp: 'label', | ||
| }) | ||
|
|
||
| /** | ||
| * A single checkbox within a checkbox group. | ||
| */ | ||
| export default withSafeTypeForAs<typeof Checkbox, CheckboxProps>(Checkbox) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import * as keyboardKey from 'keyboard-key' | ||
| import { Accessibility } from '../../types' | ||
|
|
||
| // TODO: use after https://github.com/stardust-ui/react/pull/1421 | ||
| // type CheckboxBehaviorProps = { | ||
| // checked: boolean | ||
| // } | ||
|
|
||
| /** | ||
| * @specification | ||
| * Adds role='checkbox'. This allows screen readers to handle the component as a checkbox button. | ||
| * Adds attribute 'aria-checked=true' based on the property 'checked'. | ||
| * Adds attribute 'tabIndex=0' to 'root' component's part. | ||
| */ | ||
| const checkboxBehavior: Accessibility = props => ({ | ||
| attributes: { | ||
| root: { | ||
| 'aria-checked': props.checked, | ||
| role: 'checkbox', | ||
| tabIndex: 0, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to be sure, we should not add any attribute if the checkbox is disabled?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.w3.org/TR/wai-aria-practices/examples/checkbox/checkbox-1/checkbox-1.html I don't see this in the spec, so decided to skip for the initial implementation |
||
| }, | ||
| }, | ||
| keyActions: { | ||
| root: { | ||
| performClick: { | ||
| keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }], | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| export default checkboxBehavior | ||
Uh oh!
There was an error while loading. Please reload this page.