Skip to content
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
2 changes: 2 additions & 0 deletions docs/guides/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ type: embed

### Checkbox

`readOnly` checkboxes are now focusable, in line with WCAG accessibility requirements. Previously, `readOnly` checkboxes were treated the same as `disabled` and were excluded from the tab order. Clicking a `readOnly` checkbox still has no effect — neither `onClick` nor `onChange` will fire.

#### Checkbox (simple variant)

```js
Expand Down
24 changes: 20 additions & 4 deletions packages/ui-checkbox/src/Checkbox/v2/CheckboxFacade/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class CheckboxFacade extends Component<CheckboxFacadeProps> {
static allowedProps = allowedProps
static defaultProps = {
checked: false,
disabled: false,
readOnly: false,
focused: false,
hovered: false,
size: 'medium',
Expand All @@ -70,10 +72,24 @@ class CheckboxFacade extends Component<CheckboxFacadeProps> {
}

renderIcon() {
if (this.props.indeterminate) {
return renderIconWithProps(MinusInstUIIcon, 'sm', 'inverseColor')
} else if (this.props.checked) {
return renderIconWithProps(CheckInstUIIcon, 'sm', 'inverseColor')
const { disabled, readOnly, indeterminate, checked } = this.props

const getIconColor = () => {
if (disabled) {
return 'disabledBaseColor'
}
if (readOnly) {
return 'baseColor'
}
return 'inverseColor'
}

const iconColor = getIconColor()

if (indeterminate) {
return renderIconWithProps(MinusInstUIIcon, 'sm', iconColor)
} else if (checked) {
return renderIconWithProps(CheckInstUIIcon, 'sm', iconColor)
} else {
return null
}
Expand Down
7 changes: 6 additions & 1 deletion packages/ui-checkbox/src/Checkbox/v2/CheckboxFacade/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
type CheckboxFacadeOwnProps = {
children: React.ReactNode
checked?: boolean
disabled?: boolean
readOnly?: boolean
focused?: boolean
hovered?: boolean
size?: 'small' | 'medium' | 'large'
Expand All @@ -52,10 +54,13 @@ type CheckboxFacadeStyle = ComponentStyle<'checkboxFacade' | 'facade' | 'label'>
const allowedProps: AllowedPropKeys = [
'children',
'checked',
'disabled',
'readOnly',
'focused',
'hovered',
'size',
'indeterminate'
'indeterminate',
'invalid'
]

export type { CheckboxFacadeProps, CheckboxFacadeStyle }
Expand Down
137 changes: 106 additions & 31 deletions packages/ui-checkbox/src/Checkbox/v2/CheckboxFacade/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ const generateStyle = (
props: CheckboxFacadeProps,
sharedTokens: SharedTokens
): CheckboxFacadeStyle => {
const { size, checked, focused, hovered, indeterminate, invalid } = props
const {
size,
checked,
disabled,
readOnly,
focused,
hovered,
indeterminate,
invalid
} = props

const isChecked = checked || indeterminate

Expand Down Expand Up @@ -73,60 +82,126 @@ const generateStyle = (
}
}
}
const sizeVariant =
sizeVariants[size as keyof typeof sizeVariants] ?? sizeVariants.medium

return {
checkboxFacade: {
label: 'checkboxFacade',
display: 'flex',
alignItems: 'flex-start'
},
facade: {
label: 'checkboxFacade__facade',
color: '#FFFFFF',
background: componentTheme.backgroundColor,
const getLabelColor = () => {
if (disabled) {
return componentTheme.labelDisabledColor
}

if (readOnly) {
return componentTheme.labelReadonlyColor
}

// DEFAULT state
return hovered
? componentTheme.labelHoverColor
: componentTheme.labelBaseColor
}

const getFacadeStyles = () => {
const baseStyles = {
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxSizing: 'border-box',
flexShrink: 0,
transition: 'all 0.2s',
border: `${componentTheme.borderWidth} solid ${
invalid ? componentTheme.errorBorderColor : componentTheme.borderColor
}`,
borderRadius: componentTheme.borderRadius,
marginInlineEnd: componentTheme.gap,
marginInlineStart: '0',
...sizeVariants[size!].facade,
...sizeVariant.facade
}

if (disabled) {
return {
...baseStyles,
background: componentTheme.backgroundDisabledColor,
border: `${componentTheme.borderWidth} solid ${componentTheme.borderDisabledColor}`
}
}

if (readOnly) {
return {
...baseStyles,
background: componentTheme.backgroundReadonlyColor,
border: `${componentTheme.borderWidth} solid ${componentTheme.borderReadonlyColor}`,
pointerEvents: 'none'
}
}

if (invalid) {
return {
...baseStyles,
...(isChecked && {
background: componentTheme.backgroundCheckedColor,
border: `${componentTheme.borderWidth} solid ${
hovered
? componentTheme.errorBorderHoverColor
: componentTheme.errorBorderColor
}`
}),
...(!isChecked && {
background: hovered
? componentTheme.backgroundHoverColor
: componentTheme.backgroundColor,
border: `${componentTheme.borderWidth} solid ${
hovered
? componentTheme.errorBorderHoverColor
: componentTheme.errorBorderColor
}`
})
}
}

if (isChecked) {
return {
...baseStyles,
background: componentTheme.backgroundCheckedColor,
border: `${componentTheme.borderWidth} solid ${componentTheme.borderCheckedColor}`
}
}

// DEFAULT (unchecked) state
return {
...baseStyles,
background: hovered
? componentTheme.backgroundHoverColor
: componentTheme.backgroundColor,
border: `${componentTheme.borderWidth} solid ${
hovered ? componentTheme.borderHoverColor : componentTheme.borderColor
}`
}
}

return {
checkboxFacade: {
label: 'checkboxFacade',
display: 'flex',
alignItems: 'flex-start',
cursor: disabled ? 'not-allowed' : readOnly ? 'default' : 'pointer'
},
facade: {
label: 'checkboxFacade__facade',
...getFacadeStyles(),
...(sharedTokens?.focusOutline
? calcFocusOutlineStyles(sharedTokens.focusOutline, {
withFocusOutline: focused
})
: {}),
...(isChecked && {
background: componentTheme.backgroundCheckedColor,
borderColor: componentTheme.borderCheckedColor
}),
...(!isChecked &&
hovered && {
background: componentTheme.backgroundHoverColor,
borderColor: componentTheme.borderHoverColor
})
: {})
},
label: {
label: 'checkboxFacade__label',
flex: '1 1 auto',
alignSelf: 'center',
minWidth: '0.0625rem',
color: componentTheme.labelBaseColor,
color: getLabelColor(),
fontFamily: componentTheme.fontFamily,
fontWeight: componentTheme.fontWeight,
lineHeight: componentTheme.lineHeight,
...sizeVariants[size!].label,

...(isChecked && {
color: componentTheme.labelBaseColor
})
...sizeVariant.label
}
}
}
Expand Down
23 changes: 0 additions & 23 deletions packages/ui-checkbox/src/Checkbox/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,29 +162,6 @@ type: example
/>
```

### Error messages

Checkboxes can display error messages using the `messages` prop. This works for both the default checkbox and the toggle variant.

```js
---
type: example
---
<FormFieldGroup>
<Checkbox
label="Checkbox"
isRequired={true}
messages={[{type: 'newError', text: 'Short error message'}]}
/>
<Checkbox
variant="toggle"
label="Checkbox_toggle"
isRequired={true}
messages={[{type: 'newError', text: 'Short error message'}]}
/>
</FormFieldGroup>
```

### Guidelines

```js
Expand Down
22 changes: 19 additions & 3 deletions packages/ui-checkbox/src/Checkbox/v2/ToggleFacade/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ToggleFacade extends Component<ToggleFacadeProps> {
static defaultProps = {
checked: false,
focused: false,
hovered: false,
size: 'medium',
disabled: false,
readOnly: false,
Expand All @@ -71,12 +72,27 @@ class ToggleFacade extends Component<ToggleFacadeProps> {
}

renderIcon() {
const { checked } = this.props
const { disabled, readOnly, checked } = this.props

const getIconColor = () => {
if (disabled) {
return 'disabledBaseColor'
}
if (readOnly) {
return 'mutedColor'
}
if (checked) {
return 'successColor'
}
return 'baseColor'
}

const iconColor = getIconColor()

if (checked) {
return renderIconWithProps(CheckInstUIIcon, 'sm', 'successColor')
return renderIconWithProps(CheckInstUIIcon, 'xs', iconColor)
} else {
return renderIconWithProps(XInstUIIcon, 'sm', 'baseColor')
return renderIconWithProps(XInstUIIcon, 'xs', iconColor)
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/ui-checkbox/src/Checkbox/v2/ToggleFacade/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ToggleFacadeOwnProps = {
disabled?: boolean
readOnly?: boolean
focused?: boolean
hovered?: boolean
size?: 'small' | 'medium' | 'large'
labelPlacement?: 'top' | 'start' | 'end'
/**
Expand All @@ -56,8 +57,10 @@ const allowedProps: AllowedPropKeys = [
'disabled',
'readOnly',
'focused',
'hovered',
'size',
'labelPlacement'
'labelPlacement',
'invalid'
]

export type { ToggleFacadeProps, ToggleFacadeStyle }
Expand Down
Loading
Loading