diff --git a/package.json b/package.json index 7a7599adb0..ec033fcac2 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", diff --git a/packages/ui-select/src/Select/v2/README.md b/packages/ui-select/src/Select/v2/README.md index 78992236fe..0d30c0c800 100644 --- a/packages/ui-select/src/Select/v2/README.md +++ b/packages/ui-select/src/Select/v2/README.md @@ -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) => ( + + {getOptionById(id).label} + + } + margin="xxx-small xx-small xxx-small 0" + onClick={(e) => dismissTag(e, id)} + /> + )) + + return ( + + + + ) + } + + 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( + + + + + + + + + + ) +``` + #### 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. @@ -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() @@ -534,9 +697,7 @@ type: example {getOptionById(id).label} } - 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)} /> )) @@ -544,8 +705,12 @@ type: example return (
+ +
) } @@ -589,8 +755,8 @@ type: example