instructure-ui
instructure-ui copied to clipboard
fix(ui-selectable,ui-select): fix typing of Select and Selectable eve…
…nt types and TypeScript errors in the examples
INSTUI-4866
ISSUE:
- events in Select are wrongly typed
- examples in Select therefore show TypeScript errors
TEST PLAN:
- open the branch locally
- copy these code examples from README that use event.key or event.keyCode into a new .tsx file in your local repository, they should not show TypeScript errors.
import React, { useState, useRef } from 'react'
import { Select } from '@instructure/ui-select'
/**
* This file tests all the README examples that use event.key or event.keyCode
* to ensure they work correctly with TypeScript after our type fixes.
*/
// Example 1: SingleSelectExample (line 47 in README)
export const SingleSelectExample = ({ options }: { options: Array<{ id: string; label: string }> }) => {
const [inputValue, setInputValue] = useState(options[0].label)
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [highlightedOptionId, setHighlightedOptionId] = useState<string | null>(null)
const [selectedOptionId, setSelectedOptionId] = useState(options[0].id)
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || selectedOptionId || options.length === 0) return
// This is the exact code from the README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down')
case 'ArrowUp':
return console.log('Arrow up')
}
}
}
const handleHighlightOption = (
event: React.KeyboardEvent | React.MouseEvent,
{ id }: { id?: string; direction?: 1 | -1 }
) => {
event.persist()
setHighlightedOptionId(id || null)
}
return (
<Select
renderLabel="Single Select"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
onRequestHighlightOption={handleHighlightOption}
>
{options.map((option) => (
<Select.Option
key={option.id}
id={option.id}
isHighlighted={option.id === highlightedOptionId}
isSelected={option.id === selectedOptionId}
>
{option.label}
</Select.Option>
))}
</Select>
)
}
// Example 2: AutocompleteExample (line 237 in README)
export const AutocompleteExample = ({ options }: { options: Array<{ id: string; label: string }> }) => {
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [selectedOptionId, setSelectedOptionId] = useState<string | null>(null)
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || selectedOptionId || options.length === 0) return
// Exact code from README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down')
case 'ArrowUp':
return console.log('Arrow up')
}
}
}
return (
<Select
renderLabel="Autocomplete"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
>
{options.map((option) => (
<Select.Option key={option.id} id={option.id}>
{option.label}
</Select.Option>
))}
</Select>
)
}
// Example 3: MultipleSelectExample (lines 443 and 504 in README)
export const MultipleSelectExample = ({ options }: { options: Array<{ id: string; label: string }> }) => {
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [selectedOptionId, setSelectedOptionId] = useState<string[]>(['opt1', 'opt6'])
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || options.length === 0) return
// Exact code from README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down')
case 'ArrowUp':
return console.log('Arrow up')
}
}
}
const handleKeyDown = (event: React.KeyboardEvent) => {
// Exact code from README with type guard (line 504)
if ('keyCode' in event && event.keyCode === 8) {
// when backspace key is pressed
if (inputValue === '' && selectedOptionId.length > 0) {
// remove last selected option, if input has no entered text
setSelectedOptionId(selectedOptionId.slice(0, -1))
}
}
}
return (
<Select
renderLabel="Multiple Select"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
onKeyDown={handleKeyDown}
>
{options.map((option) => (
<Select.Option key={option.id} id={option.id}>
{option.label}
</Select.Option>
))}
</Select>
)
}
// Example 4: GroupSelectExample (line 669 in README)
export const GroupSelectExample = ({
options
}: {
options: Record<string, Array<{ id: string; label: string }>>
}) => {
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || Object.keys(options).length === 0) return
// Exact code from README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down - first group option')
case 'ArrowUp':
return console.log('Arrow up - last group option')
}
}
}
return (
<Select
renderLabel="Group Select"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
>
{Object.keys(options).map((key, index) => (
<Select.Group key={index} renderLabel={key}>
{options[key].map((option) => (
<Select.Option key={option.id} id={option.id}>
{option.label}
</Select.Option>
))}
</Select.Group>
))}
</Select>
)
}
// Example 5: GroupSelectAutocompleteExample (line 853 in README)
export const GroupSelectAutocompleteExample = ({
options
}: {
options: Record<string, Array<{ id: string; label: string }>>
}) => {
const [inputValue, setInputValue] = useState('')
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [selectedOptionId, setSelectedOptionId] = useState<string | null>(null)
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || selectedOptionId || Object.keys(options).length === 0) return
// Exact code from README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down')
case 'ArrowUp':
return console.log('Arrow up')
}
}
}
return (
<Select
renderLabel="Group Select with autocomplete"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
>
{Object.keys(options).map((key, index) => (
<Select.Group key={index} renderLabel={key}>
{options[key].map((option) => (
<Select.Option key={option.id} id={option.id}>
{option.label}
</Select.Option>
))}
</Select.Group>
))}
</Select>
)
}
// Example 6: Option Icons Example (line 1218 in README)
export const OptionIconsExample = ({ options }: { options: Array<{ id: string; label: string }> }) => {
const [inputValue, setInputValue] = useState(options[0].label)
const [isShowingOptions, setIsShowingOptions] = useState(false)
const [selectedOptionId, setSelectedOptionId] = useState(options[0].id)
const handleShowOptions = (event: React.KeyboardEvent | React.MouseEvent) => {
setIsShowingOptions(true)
if (inputValue || selectedOptionId || options.length === 0) return
// Exact code from README with type guard
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
return console.log('Arrow down')
case 'ArrowUp':
return console.log('Arrow up')
}
}
}
return (
<Select
renderLabel="Option Icons"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onRequestShowOptions={handleShowOptions}
>
{options.map((option) => (
<Select.Option
key={option.id}
id={option.id}
isSelected={option.id === selectedOptionId}
>
{option.label}
</Select.Option>
))}
</Select>
)
}
// Test with inferred types (most common usage pattern)
export const InferredTypesExample = () => {
const [isShowingOptions, setIsShowingOptions] = useState(false)
return (
<Select
renderLabel="Inferred Types"
isShowingOptions={isShowingOptions}
// Inline handler with inferred types - this is how most users write it
onRequestShowOptions={(event) => {
setIsShowingOptions(true)
// Type guard needed for union type
if ('key' in event) {
switch (event.key) {
case 'ArrowDown':
console.log('Down')
break
case 'ArrowUp':
console.log('Up')
break
}
}
}}
// Test FocusEvent compatibility
onRequestHideOptions={(event) => {
setIsShowingOptions(false)
// Can access common properties
console.log(event.type)
// Can check for keyboard-specific properties
if ('key' in event) {
console.log('Keyboard event:', event.key)
}
// Can check for mouse-specific properties
if ('button' in event) {
console.log('Mouse event:', event.button)
}
// Can check for focus-specific properties
if ('relatedTarget' in event) {
console.log('Focus event:', event.relatedTarget)
}
}}
>
<Select.Option id="1">Option 1</Select.Option>
<Select.Option id="2">Option 2</Select.Option>
</Select>
)
}
PR Preview Action v1.6.3 :---: Preview removed because the pull request was closed. 2025-12-23 13:00 UTC