Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add 'lightning' icon to Teams theme @notandrew ([#1385](https://github.com/stardust-ui/react/pull/1385))
- Add automatic positioning inside viewport for `Menu` with submenus @Bugaa92 ([#1384](https://github.com/stardust-ui/react/pull/1384))
- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
- Add `Checkbox` component and `toggle` prop for it @layershifter ([#1405](https://github.com/stardust-ui/react/pull/1405))

### Documentation
- Accessibility: improve introduction section @jurokapsiar ([#1368](https://github.com/stardust-ui/react/pull/1368))
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/ExampleSnippet/renderElementToJSX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const renderElementToJSX = (element: React.ReactNode, triggerErrorOnRenderFn: bo
(typeof element.type === 'function' ? 'NoDisplayName' : element.type),
showDefaultProps: false,
showFunctions: true,
filterProps: ['accessibility'],
filterProps: ['accessibility', 'onClick', 'onChange'],
functionValue: () => (renderHasFunction = true),
})

Expand Down
26 changes: 26 additions & 0 deletions docs/src/examples/components/Checkbox/Playground.tsx
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
26 changes: 26 additions & 0 deletions docs/src/examples/components/Checkbox/Types/index.tsx
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
10 changes: 10 additions & 0 deletions docs/src/examples/components/Checkbox/index.tsx
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
168 changes: 168 additions & 0 deletions packages/react/src/components/Checkbox/Checkbox.tsx
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add conformat test for the new component.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added, have to add an additional handler for onChange. Decided to keep it flat to be more obvious

/**
* 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
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, could be moved in the if statement

Copy link
Member Author

Choose a reason for hiding this comment

The 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}
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this the thing missing for the focus styles? :)

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
3 changes: 3 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export { default as ChatItem } from './components/Chat/ChatItem'
export * from './components/Chat/ChatMessage'
export { default as ChatMessage } from './components/Chat/ChatMessage'

export * from './components/Checkbox/Checkbox'
export { default as Checkbox } from './components/Checkbox/Checkbox'

export * from './components/Divider/Divider'
export { default as Divider } from './components/Divider/Divider'

Expand Down
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,
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
1 change: 1 addition & 0 deletions packages/react/src/lib/accessibility/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ export { default as embedBehavior } from './Behaviors/Embed/embedBehavior'
export { default as accordionBehavior } from './Behaviors/Accordion/accordionBehavior'
export { default as accordionTitleBehavior } from './Behaviors/Accordion/accordionTitleBehavior'
export { default as accordionContentBehavior } from './Behaviors/Accordion/accordionContentBehavior'
export { default as checkboxBehavior } from './Behaviors/Checkbox/checkboxBehavior'
2 changes: 2 additions & 0 deletions packages/react/src/themes/base/componentStyles.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { default as Checkbox } from './components/Checkbox/checkboxStyles'

export { default as Dialog } from './components/Dialog/dialogStyles'

export { default as Flex } from './components/Flex/flexStyles'
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/themes/base/componentVariables.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { default as Checkbox } from './components/Checkbox/checkboxVariables'

export { default as Dialog } from './components/Dialog/dialogVariables'

export { default as Flex } from './components/Flex/flexVariables'
Expand Down
Loading