-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate Checkbox to functional component (#12614)
* Migrate Checkbox to functional component * Refactor * Remove extraneous state interface. * Change files * Add new hooks used in migrating Fabric components to functional components * Change files * Refactor further to move onChange logic to hook * Cherry pick revision from MLoughry:functional/checkbox * Fix lint error * Address comments from dzearing * Fix launch.json for debugging tests * Use common onChange call signature * Fix hook * Update Checkbox to use updated hook definition * Fix useControllableValue types in case with no onChange handler * Export ChangeCallback type * Persist the controlled vs uncontrolled state * Update packages/react-hooks/src/useMergedRefs.ts Co-Authored-By: Elizabeth Craig <[email protected]> * Update README * Allow for undefined events in the onChange callback * CRLF -> LF * Fix error in isControlled value * Delete test for deprecated behavior * Update packages/react-hooks/README.md Co-Authored-By: Elizabeth Craig <[email protected]> * Update packages/react-hooks/README.md Co-Authored-By: Elizabeth Craig <[email protected]> * Migrate changes to react-next * Change files * Delete extraneous changefiles * Add React.memo * Update api.md * Copy existing lint config from OUFR * Should add react-next to bundle validation. * Pass handlers directly * useFocusRects * Don't memoize callback passed to DOM element * Remove FocusRects * Remove React.memo * Add display name * Address David's comment * Update API file Co-authored-by: Elizabeth Craig <[email protected]> Co-authored-by: David Zearing <[email protected]>
- Loading branch information
1 parent
1c286ff
commit 24f53d1
Showing
24 changed files
with
1,805 additions
and
8 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
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
8 changes: 8 additions & 0 deletions
8
change/@fluentui-react-next-2020-04-22-17-09-48-functional-checkbox.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,8 @@ | ||
{ | ||
"type": "prerelease", | ||
"comment": "Migrate Checkbox to functional component", | ||
"packageName": "@fluentui/react-next", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch", | ||
"date": "2020-04-22T17:09:48.443Z" | ||
} |
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 |
---|---|---|
@@ -1 +1 @@ | ||
export * from 'office-ui-fabric-react/lib/Checkbox'; | ||
export * from './components/Checkbox/index'; |
149 changes: 149 additions & 0 deletions
149
packages/react-next/src/components/Checkbox/Checkbox.base.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,149 @@ | ||
import * as React from 'react'; | ||
import { classNamesFunction, mergeAriaAttributeValues, warnMutuallyExclusive } from '../../Utilities'; | ||
import { Icon } from '../../Icon'; | ||
import { ICheckboxProps, ICheckboxStyleProps, ICheckboxStyles } from './Checkbox.types'; | ||
import { KeytipData } from '../../KeytipData'; | ||
import { useId, useControllableValue, useMergedRefs } from '@uifabric/react-hooks'; | ||
import { useFocusRects } from 'office-ui-fabric-react'; | ||
|
||
const getClassNames = classNamesFunction<ICheckboxStyleProps, ICheckboxStyles>(); | ||
|
||
export const CheckboxBase = React.forwardRef((props: ICheckboxProps, forwardedRef: React.Ref<HTMLDivElement>) => { | ||
const { | ||
className, | ||
disabled, | ||
inputProps, | ||
name, | ||
boxSide = 'start', | ||
theme, | ||
ariaLabel, | ||
ariaLabelledBy, | ||
ariaDescribedBy, | ||
styles, | ||
checkmarkIconProps, | ||
ariaPositionInSet, | ||
ariaSetSize, | ||
keytipProps, | ||
title, | ||
label, | ||
onChange, | ||
} = props; | ||
|
||
const rootRef = React.useRef<HTMLDivElement | null>(null); | ||
const mergedRootRefs = useMergedRefs(rootRef, forwardedRef); | ||
const checkBox = React.useRef<HTMLInputElement>(null); | ||
const [isChecked, setIsChecked] = useControllableValue(props.checked, props.defaultChecked, onChange); | ||
const [isIndeterminate, setIsIndeterminate] = useControllableValue(props.indeterminate, props.defaultIndeterminate); | ||
|
||
useFocusRects(rootRef); | ||
useDebugWarning(props); | ||
useComponentRef(props, isChecked, isIndeterminate, checkBox); | ||
|
||
const id = useId('checkbox-', props.id); | ||
const classNames: { [key in keyof ICheckboxStyles]: string } = getClassNames(styles!, { | ||
theme: theme!, | ||
className, | ||
disabled, | ||
indeterminate: isIndeterminate, | ||
checked: isChecked, | ||
reversed: boxSide !== 'start', | ||
isUsingCustomLabelRender: !!props.onRenderLabel, | ||
}); | ||
|
||
const onRenderLabel = (): JSX.Element | null => { | ||
return label ? ( | ||
<span aria-hidden="true" className={classNames.text} title={title}> | ||
{label} | ||
</span> | ||
) : null; | ||
}; | ||
|
||
const _onChange = (ev: React.ChangeEvent<HTMLElement>): void => { | ||
if (!isIndeterminate) { | ||
setIsChecked(!isChecked, ev); | ||
} else { | ||
// If indeterminate, clicking the checkbox *only* removes the indeterminate state (or if | ||
// controlled, lets the consumer know to change it by calling onChange). It doesn't | ||
// change the checked state. | ||
setIsChecked(!!isChecked, ev); | ||
setIsIndeterminate(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<KeytipData keytipProps={keytipProps} disabled={disabled}> | ||
{(keytipAttributes: any): JSX.Element => ( | ||
<div className={classNames.root} title={title} ref={mergedRootRefs}> | ||
<input | ||
type="checkbox" | ||
{...inputProps} | ||
data-ktp-execute-target={keytipAttributes['data-ktp-execute-target']} | ||
checked={!!isChecked} | ||
disabled={disabled} | ||
className={classNames.input} | ||
ref={checkBox} | ||
name={name} | ||
id={id} | ||
title={title} | ||
onChange={_onChange} | ||
onFocus={inputProps?.onFocus} | ||
onBlur={inputProps?.onBlur} | ||
aria-disabled={disabled} | ||
aria-label={ariaLabel || label} | ||
aria-labelledby={ariaLabelledBy} | ||
aria-describedby={mergeAriaAttributeValues(ariaDescribedBy, keytipAttributes['aria-describedby'])} | ||
aria-posinset={ariaPositionInSet} | ||
aria-setsize={ariaSetSize} | ||
aria-checked={isIndeterminate ? 'mixed' : isChecked ? 'true' : 'false'} | ||
/> | ||
<label className={classNames.label} htmlFor={id}> | ||
<div className={classNames.checkbox} data-ktp-target={keytipAttributes['data-ktp-target']}> | ||
<Icon iconName="CheckMark" {...checkmarkIconProps} className={classNames.checkmark} /> | ||
</div> | ||
{(props.onRenderLabel || onRenderLabel)(props, onRenderLabel)} | ||
</label> | ||
</div> | ||
)} | ||
</KeytipData> | ||
); | ||
}); | ||
|
||
CheckboxBase.displayName = 'CheckboxBase'; | ||
|
||
function useDebugWarning(props: ICheckboxProps) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
// This is a build-time conditional that will be constant at runtime | ||
// tslint:disable-next-line:react-hooks-nesting | ||
React.useEffect(() => { | ||
warnMutuallyExclusive('Checkbox', props, { | ||
checked: 'defaultChecked', | ||
indeterminate: 'defaultIndeterminate', | ||
}); | ||
}, []); | ||
} | ||
} | ||
|
||
function useComponentRef( | ||
props: ICheckboxProps, | ||
isChecked: boolean | undefined, | ||
isIndeterminate: boolean | undefined, | ||
checkBox: React.RefObject<HTMLInputElement>, | ||
) { | ||
React.useImperativeHandle( | ||
props.componentRef, | ||
() => ({ | ||
get checked() { | ||
return !!isChecked; | ||
}, | ||
get indeterminate() { | ||
return !!isIndeterminate; | ||
}, | ||
focus() { | ||
if (checkBox.current) { | ||
checkBox.current.focus(); | ||
} | ||
}, | ||
}), | ||
[isChecked, isIndeterminate], | ||
); | ||
} |
Oops, something went wrong.