Skip to content
Draft
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.27.2",
"babel-loader": "^10.1.1",
"@babel/runtime": "^7.29.2",
"moment-timezone": "^0.6.2",
"@commitlint/cli": "^20.5.3",
"@commitlint/config-conventional": "^20.5.3",
"@emotion/cache": "^11.14.0",
Expand All @@ -74,6 +72,7 @@
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "^4.5.1",
"@vitest/eslint-plugin": "^1.6.14",
"babel-loader": "^10.1.1",
"babel-plugin-add-import-extension": "^1.6.0",
"chai": "^4.4.1",
"chalk": "^4.1.2",
Expand All @@ -97,6 +96,8 @@
"jsdom": "^26.1.0",
"lerna": "9.0.7",
"lint-staged": "^16.4.0",
"moment-timezone": "^0.6.2",
"prettier": "^2.8.8",
"react": "18.3.1",
"typescript": "6.0.3",
"typescript-eslint": "^8.59.1",
Expand Down
178 changes: 172 additions & 6 deletions packages/ui-select/src/Select/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,162 @@ describes: Select
> - Before implementing Select, see if a [SimpleSelect](SimpleSelect) will suffice.
> - The `id` prop on options must be globally unique, it will be translated to an `id` prop in the DOM.

#### Multiple select

```js
---
type: example
---
const MultiSelectSizeExample = ({ size, options }) => {
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [highlightedOptionId, setHighlightedOptionId] = useState(null)
const [selectedOptionId, setSelectedOptionId] = useState(
// the small version starts with an extra (7th) selected state
size === 'small'
? ['opt1', 'opt2', 'opt3', 'opt4', 'opt5', 'opt6', 'opt7']
: ['opt1', 'opt2', 'opt3', 'opt4', 'opt5', 'opt6']
)
const inputRef = useRef()

const tagFillOverride = (theme) => {
const inputHeightRem =
theme.key === 'light' || theme.key === 'dark'
? { small: 2, medium: 2.5, large: 3 }
: { small: 1.75, medium: 2.375, large: 3 }
const tagHeightKey = {
small: 'heightSmall',
medium: 'heightMedium',
large: 'heightLarge'
}[size]
return {
components: {
Tag: { [tagHeightKey]: `${inputHeightRem[size] - 0.5}rem` }
}
}
}

const getOptionById = (id) => options.find((o) => o.id === id)

const availableOptions = () =>
options.filter((o) => !selectedOptionId.includes(o.id))

const focusInput = () => {
if (inputRef.current) {
inputRef.current.blur()
inputRef.current.focus()
}
}

const handleSelectOption = (event, { id }) => {
if (!getOptionById(id)) return
focusInput()
setSelectedOptionId([...selectedOptionId, id])
setHighlightedOptionId(null)
setInputValue('')
setIsShowingOptions(false)
}

const handleKeyDown = (event) => {
// remove last selected option on backspace, if input has no entered text
if (event.keyCode === 8 && inputValue === '' && selectedOptionId.length > 0) {
setHighlightedOptionId(null)
setSelectedOptionId(selectedOptionId.slice(0, -1))
}
}

const dismissTag = (e, tag) => {
// prevent closing of list
e.stopPropagation()
e.preventDefault()
setSelectedOptionId(selectedOptionId.filter((id) => id !== tag))
setHighlightedOptionId(null)
inputRef.current.focus()
}

const renderTags = () =>
selectedOptionId.map((id) => (
<Tag
dismissible
key={id}
size={size}
text={
<AccessibleContent alt={`Remove ${getOptionById(id).label}`}>
{getOptionById(id).label}
</AccessibleContent>
}
margin="xxx-small xx-small xxx-small 0"
onClick={(e) => dismissTag(e, id)}
/>
))

return (
<InstUISettingsProvider themeOverride={tagFillOverride}>
<Select
renderLabel={`Multiple Select (${size})`}
size={size}
width="30rem"
htmlSize={2}
assistiveText="Type or use arrow keys to navigate options. Multiple selections allowed."
inputValue={inputValue}
isShowingOptions={isShowingOptions}
inputRef={(el) => {
inputRef.current = el
}}
onInputChange={(e) => setInputValue(e.target.value)}
onRequestShowOptions={() => setIsShowingOptions(true)}
onRequestHideOptions={() => setIsShowingOptions(false)}
onRequestHighlightOption={(e, { id }) => setHighlightedOptionId(id)}
onRequestSelectOption={handleSelectOption}
onKeyDown={handleKeyDown}
renderBeforeInput={selectedOptionId.length > 0 ? renderTags() : null}
>
{availableOptions().length > 0 ? (
availableOptions().map((option) => (
<Select.Option
id={option.id}
key={option.id}
isHighlighted={option.id === highlightedOptionId}
>
{option.label}
</Select.Option>
))
) : (
<Select.Option id="empty-option" key="empty-option">
---
</Select.Option>
)}
</Select>
</InstUISettingsProvider>
)
}

const states = [
{ id: 'opt1', label: 'Alabama' },
{ id: 'opt2', label: 'Alaska' },
{ id: 'opt3', label: 'Arizona' },
{ id: 'opt4', label: 'Arkansas' },
{ id: 'opt5', label: 'California' },
{ id: 'opt6', label: 'Colorado' },
{ id: 'opt7', label: 'Connecticut' },
{ id: 'opt8', label: 'Delaware' },
{ id: 'opt9', label: 'Florida' },
{ id: 'opt10', label: 'Georgia' }
]

render(
<View as="div">
<MultiSelectSizeExample size="small" options={states} />
<View as="div" margin="medium 0 0 0">
<MultiSelectSizeExample size="medium" options={states} />
</View>
<View as="div" margin="medium 0 0 0">
<MultiSelectSizeExample size="large" options={states} />
</View>
</View>
)
```

#### Managing state for a Select

`Select` is a controlled-only component. The consuming app or component must manage any state needed. A variety of request callbacks are provided as prompts for state updates. `onRequestShowOptions`, for example, is fired when `Select` thinks the `isShowingOptions` prop should be updated to `true`. Of course, the consumer can always choose how to react to these callbacks.
Expand Down Expand Up @@ -374,7 +530,14 @@ type: example
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [highlightedOptionId, setHighlightedOptionId] = useState(null)
const [selectedOptionId, setSelectedOptionId] = useState(['opt1', 'opt6'])
const [selectedOptionId, setSelectedOptionId] = useState([
'opt1',
'opt2',
'opt3',
'opt4',
'opt5',
'opt6'
])
const [filteredOptions, setFilteredOptions] = useState(options)
const [announcement, setAnnouncement] = useState(null)
const inputRef = useRef()
Expand Down Expand Up @@ -534,18 +697,20 @@ type: example
{getOptionById(id).label}
</AccessibleContent>
}
margin={
index > 0 ? 'xxx-small xx-small xxx-small 0' : '0 xx-small 0 0'
}
margin="xxx-small xx-small xxx-small 0"
onClick={(e) => dismissTag(e, id)}
/>
))
}

return (
<div>
<InstUISettingsProvider
themeOverride={{ components: { TextInput: { heightMd: '2rem' } } }}
>
<Select
renderLabel="Multiple Select"
width="30rem"
assistiveText="Type or use arrow keys to navigate options. Multiple selections allowed."
inputValue={inputValue}
isShowingOptions={isShowingOptions}
Expand Down Expand Up @@ -581,6 +746,7 @@ type: example
</Select.Option>
)}
</Select>
</InstUISettingsProvider>
</div>
)
}
Expand All @@ -589,8 +755,8 @@ type: example
<View>
<MultipleSelectExample
options={[
{ id: 'opt1', label: 'Alaska' },
{ id: 'opt2', label: 'American Samoa' },
{ id: 'opt1', label: 'Alabama' },
{ id: 'opt2', label: 'Alaska' },
{ id: 'opt3', label: 'Arizona' },
{ id: 'opt4', label: 'Arkansas' },
{ id: 'opt5', label: 'California' },
Expand Down
Loading
Loading