Option List
In it's simplest form, the Option List is a flat, selectable item list. With multi-selection, a Checkbox is incorporated.
<State initial={[]}>{([value, setValue]) => (<OptionListoptions={[{value: 1, text: "Fort Lauderdale"},{value: 2, text: "Glendale"},{value: 3, text: "Honolulu"},{value: 4, text: "Indianapolis"},{value: 5, text: "Jacksonville"}]}value={value}onChange={(data, checked) =>checked? setValue(prev => [...prev, data]): setValue(prev => prev.filter(item => item !== data))}multiple/>)}</State>
Option Lists can have custom content inside individual options, regardless of what structure the Option List takes.
<State initial={[]}>{([value, setValue]) => (<OptionListoptions={['Jane Doe', 'Dana Green', 'George Johnson'].map((name, index) => ({value: index,text: name,content: (<Stack alignItems="center" spacing={1}><Avatar name={name} autoColor size="s" /><BodyText size="small">{name}</BodyText></Stack>)}))}value={value}onChange={data => setValue([data])}/>)}</State>
const Example01 = () => {const dummyFolderData = [{content: (<Stack alignItems="center" spacing={1}><Avatar name="Jane Doe" autoColor size="s" /><BodyText size="small">Jane Doe</BodyText></Stack>),value: 11,options: [{content: (<Stack alignItems="center"><BodyText size="small">Irrigation 01/02/20</BodyText><Button size="xsmall" fill="subtle" primary iconName="chevron_right" onClick={() => alert("New page about this specific item")} className="m-l-half" /></Stack>),value: 21,}, {content: (<Stack alignItems="center"><BodyText size="small">Irrigation 03/04/20</BodyText><Button size="xsmall" fill="subtle" primary iconName="chevron_right" onClick={() => alert("New page about this specific item")} className="m-l-half" /></Stack>),value: 22,}],}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([11]);const onChange = (data, checked, children) => {const getValue = [data];const getChildrenValues = (options) => {options.map((option) => {if (option.options) getChildrenValues(option.options);if (option.disabled) return null;return getValue.push(option.value);});};if (children.length > 0) getChildrenValues(children);if (checked)setValue(prev => [...prev, ...getValue])elsesetValue(prev => prev.filter(item => !getValue.includes(item)));};const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev =>prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return (<OptionListoptions={dummyFolderData}onChange={onChange}onExpand={onExpand}value={value}collapseValue={collapseValue}multiple/>);}render (Example01)
The Option List can be paired with an Eyebrow to categorize a simple item list.
<State initial={[]}>{([value, setValue]) => (<OptionListflatGroupvalue={value}onChange={(data) => setValue([data])}options={[{text: "Florida",value: 1,options: [{value: 11, text: "Miami"},{value: 12, text: "Orlando"},{value: 13, text: "Tampa"}]}, {text: "Minnesota",value: 2,options: [{value: 2, text: "Minneapolis"},{value: 3, text: "Rochester"},{value: 4, text: "Saint Paul"}]}]}/>)}</State>
<State initial={[]}>{([value, setValue]) => (<OptionListflatGroupmultiplevalue={value}onChange={(data, checked) => checked? setValue(prev => [...prev, data]): setValue(prev => prev.filter(item => item !== data))}options={[{text: "Montana",value: 1,options: [{value: 11, text: "Billings"},{value: 12, text: "Missoula"},{value: 13, text: "Helena"}]}, {text: "Colorado",value: 2,options: [{value: 2, text: "Denver"},{value: 3, text: "Fort Collins"},{value: 4, text: "Boulder"}]}]}/>)}</State>
In a flat group, individual groups can be given a select all and none control.
const Example01 = () => {const flatData = [{text: "HVAC Install",value: 1,options: [{text: "Jane Doe", value: 11},{text: "Jackie Robinson", value: 12},{text: "Joseph Garcia", value: 13}]}, {text: "Plumbing Replacement",value: 2,options: [{text: "Dana Green", value: 2},{text: "Isabella Martinez", value: 3},{text: "George Johnson", value: 4}]}]const [value, setValue] = React.useState([]);const onChange = (data, checked) => {checked? setValue(prev => [...prev, data]):setValue(prev => prev.filter(item => item !== data));};const onGroupSelectAll = (data) => {const values = [...data.map(item => item.value)];setValue(prev => [...prev, ...values]);};const onGroupSelectNone = (data) => {const values = [...data.map(item => item.value)];setValue(prev => prev.filter(item => !values.includes(item)));};return <OptionList options={flatData} value={value} onChange={onChange} flatGroup={{ onGroupSelectAll, onGroupSelectNone }} multiple />}render (Example01)
A secondary action can be performed on individual options. Example actions include editing, deleting, or navigating to more information. Secondary actions are only visible on hover.
const Example01 = () => {const flatData = [{text: "Raleigh",value: 1,secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)}, {text: "Sacramento",value: 2,secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)}, {text: "Tampa",value: 3,secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)}];const [value, setValue] = React.useState([]);const onChange = (data) => setValue([data]);return <OptionList options={flatData} value={value} onChange={onChange} />;}render (Example01)
The tree view is a major variation of the Option List. It is used to represent hierarchical data, as understood by users. For example, an organizational chart, divisions within business units, or categorization of inventory items. Tree views work best when users perceive items as categorical.
const Example01 = () => {const dummyData = [{text: 'Plumbing',value: 1,collapsed: false,options: [{text: 'Install Materials',value: 11,options: [{text: 'Disposer Waste', value: 111},{text: 'Frostproof Hydrant', value: 112}]}, {text: 'Service Materials',value: 12,options: [{text: 'Fluidmaster Fill Valve', value: 121},{text: 'Tailpiece Slip Joint', value: 122}]}]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data) => setValue([data]);const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />}render (Example01)
const Example01 = () => {const dummyData = [{text: 'Plumbing',value: 1,collapsed: false,options: [{text: 'Install Materials',value: 11,options: [{text: 'Disposer Waste', value: 111},{text: 'Frostproof Hydrant', value: 112}]}, {text: 'Service Materials',value: 12,options: [{text: 'Fluidmaster Fill Valve', value: 121},{text: 'Tailpiece Slip Joint', value: 122}]}]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data, checked, children) => {const getValue = [data];const getChildrenValues = (options) => {options.map((option) => {if (option.options) getChildrenValues(option.options);if (option.disabled) return null;return getValue.push(option.value);});};if (children.length > 0) getChildrenValues(children);if (checked)setValue(prev => [...prev, ...getValue])elsesetValue(prev => prev.filter(item => !getValue.includes(item)));};const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return (<OptionListmultipleoptions={dummyData}value={value}collapseValue={collapseValue}onChange={onChange}onExpand={onExpand}/>);}render (Example01)
The Option List is not opinionated on what is selected when a parent of options is selected. Different variants can be used depending on the design context.
When a parent is just the sum of all its children, selecting the parent can select all children. This is the most common
const Example01 = () => {const dummyData = [{text: 'Option',value: 1,collapsed: false,options: [{text: 'Sub Option',value: 11,options: [{text: 'Sub Sub Option', value: 111},{text: 'Sub Sub Option', value: 112}]}, {text: 'Sub Option',value: 12,options: [{text: 'Sub Sub Option', value: 121},{text: 'Sub Sub Option', value: 122}]}]}, {text: 'Option',value: 2,options: [{ text: 'Sub Option', value: 21 }]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data, checked, children) => {const getValue = [data];const getChildrenValues = (options) => {options.map((option) => {if (option.options) getChildrenValues(option.options);if (option.disabled) return null;return getValue.push(option.value);});};if (children.length > 0) getChildrenValues(children);if (checked)setValue(prev => [...prev, ...getValue])elsesetValue(prev => prev.filter(item => !getValue.includes(item)));};const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} multiple />;}render (Example01)
const Example01 = () => {const dummyData = [{text: 'Option',value: 1,collapsed: false,options: [{text: 'Sub Option',value: 11,options: [{text: 'Sub Sub Option', value: 111},{text: 'Sub Sub Option', value: 112}]}, {text: 'Sub Option',value: 12,options: [{text: 'Sub Sub Option', value: 121},{text: 'Sub Sub Option', value: 122}]}]}, {text: 'Option',value: 2,options: [{ text: 'Sub Option', value: 21 }]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data, checked, children) => {const getValue = [data];const getChildrenValues = (options) => {options.map((option) => {if (option.options) getChildrenValues(option.options);if (option.disabled) return null;return getValue.push(option.value);});};if (children.length > 0) getChildrenValues(children);setValue(prev => [...getValue]);};const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />}render (Example01)
There are times when a parent and child are independent selections from each other. This is useful when parent items can be configured distinctly from its children.
const Example01 = () => {const dummyData = [{text: 'Option',value: 1,collapsed: false,options: [{text: 'Sub Option',value: 11,options: [{text: 'Sub Sub Option', value: 111},{text: 'Sub Sub Option', value: 112}]}, {text: 'Sub Option',value: 12,options: [{text: 'Sub Sub Option', value: 121},{text: 'Sub Sub Option', value: 122}]}]}, {text: 'Option',value: 2,options: [{ text: 'Sub Option', value: 21 }]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data, checked) => {checked? setValue(prev => [...prev, data]): setValue(prev => prev.filter(item => item !== data));};const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} multiple />}render (Example01)
const Example01 = () => {const dummyData = [{text: 'Option',value: 1,collapsed: false,options: [{text: 'Sub Option',value: 11,options: [{text: 'Sub Sub Option', value: 111},{text: 'Sub Sub Option', value: 112}]}, {text: 'Sub Option',value: 12,options: [{text: 'Sub Sub Option', value: 121},{text: 'Sub Sub Option', value: 122}]}]}, {text: 'Option',value: 2,options: [{ text: 'Sub Option', value: 21 }]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);const onChange = (data) => setValue([data]);const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />}render (Example01)
Sometimes only childless options should be selected. For example, if parents represent folder structures, but the user needs to select a file.
const Example01 = () => {const FolderIcon = () => {return (<IconclassName="m-r-1 c-neutral-100"name="folder"size="16px"style={{verticalAlign: 'initial'}}/>);};const dummyFolderData = [{content: <BodyText size="small"><FolderIcon />Pictures</BodyText>,value: 'pictures',readOnly: true,options: [{content: <BodyText size="small" className="m-l-1">dog.png</BodyText>,value: 11,}, {content: <BodyText size="small" className="m-l-1">cat.jpg</BodyText>,value: 12,}]}, {content: <BodyText size="small"><FolderIcon />Documents</BodyText>,value: 'documents',readOnly: true,options: [{content: <BodyText size="small" className="m-l-1">worksheet.xls</BodyText>,value: 21,}, {content: <BodyText size="small" className="m-l-1">README.md</BodyText>,value: 22,}]}];const [value, setValue] = React.useState([]);const [collapseValue, setCollapseValue] = React.useState(['pictures']);const onChange = (data) => setValue([data]);const onExpand = (data) => {collapseValue.includes(data)? setCollapseValue(prev => prev.filter(item => item !== data)): setCollapseValue(prev => [...prev, data]);};return (<OptionListoptions={dummyFolderData}onChange={onChange}onExpand={onExpand}value={value}collapseValue={collapseValue}/>)}render (Example01)
A tree can also take advantage of the Checkbox's indeterminate state to show partial selections of parents.
const Example01 = () => {const memoizedData = React.useMemo(() => [{text: 'Install Materials',value: 11,collapseControl: false,options: [{ text: 'Disposer Waste', value: 111 },{ text: 'Frostproof Hydrant', value: 112 },{ text: 'HVAC FGXB221', value: 113 },],},],[]);const [value, setValue] = React.useState([112]);const [indeterminate, setIndeterminate] = React.useState([11]);const memoizedParentValue = React.useMemo(() => {const parentValue = memoizedData[0].value;return parentValue;}, [memoizedData]);const memoizedChildValues = React.useMemo(() => {const allChildValues = memoizedData[0].options.map((option) => option.value);return allChildValues;}, [memoizedData]);/** Check for indeterminate values after the value hook has updated* Note: This logic is basic, and would need additional work for multiple parent trees or multi-level trees.*/React.useEffect(() => {const checkAllChildrenSelected = memoizedChildValues.every((i) =>value.includes(i));if (value.some((i) => memoizedChildValues.includes(i)) && // Are any of the child elements part of value?!checkAllChildrenSelected && // Is every child value selected? Negative!value.includes(memoizedParentValue) // Is the parent value within value?) {setIndeterminate([memoizedParentValue]);} else setIndeterminate([]);// If all children are selected, also select the parentif (checkAllChildrenSelected && !value.includes(memoizedParentValue)) {setValue((prev) => [...prev, memoizedParentValue]);}}, [memoizedChildValues, memoizedParentValue, value]);const onChange = (data, checked, children) => {const getValue = [data];const getChildrenValues = (options) => {options.map((option) => {if (option.options) getChildrenValues(option.options);if (option.disabled) return null;return getValue.push(option.value);});};if (children.length > 0) getChildrenValues(children);if (checked) setValue((prev) => [...prev, ...getValue]);elsesetValue((prev) =>prev.filter((item) =>!getValue.includes(item) && item !== memoizedParentValue));};return (<div style={{ maxWidth: '400px' }}><OptionListoptions={memoizedData}multipleonChange={onChange}value={value}indeterminateValue={indeterminate}/></div>);}render (Example01)