diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index b63738e0e4..73084f2ebd 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -47,6 +47,7 @@ export const TreeSelect = React.forwardRef(function TreeSelect( items, value: propsValue, defaultValue, + placeholder, disabled = false, withExpandedState = true, defaultExpandedState = 'expanded', @@ -154,6 +155,7 @@ export const TreeSelect = React.forwardRef(function TreeSelect( const controlProps: TreeSelectRenderControlProps = { list, open, + placeholder, toggleOpen, clearValue: () => list.state.setSelected({}), ref: handleControlRef, diff --git a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx index 7437235538..5c5c25035f 100644 --- a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx +++ b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx @@ -10,6 +10,8 @@ import type {TreeSelectProps} from '../types'; import {InfinityScrollExample} from './components/InfinityScrollExample'; import type {InfinityScrollExampleProps} from './components/InfinityScrollExample'; +import {WithDisabledElementsExample} from './components/WithDisabledElementsExample'; +import type {WithDisabledElementsExampleProps} from './components/WithDisabledElementsExample'; import {WithDndListExample} from './components/WithDndListExample'; import type {WithDndListExampleProps} from './components/WithDndListExample'; import {WithFiltrationAndControlsExample} from './components/WithFiltrationAndControlsExample'; @@ -51,6 +53,7 @@ const DefaultTemplate: StoryFn< x} onItemClick={({id, list}) => { @@ -114,3 +117,12 @@ WithDndList.parameters = { // https://github.com/atlassian/react-beautiful-dnd/issues/2350 disableStrictMode: true, }; + +const WithDisabledElementsTemplate: StoryFn = (props) => { + return ; +}; +export const WithDisabledElements = WithDisabledElementsTemplate.bind({}); + +WithDisabledElements.args = { + size: 'l', +}; diff --git a/src/components/TreeSelect/__stories__/components/WithDisabledElementsExample.tsx b/src/components/TreeSelect/__stories__/components/WithDisabledElementsExample.tsx new file mode 100644 index 0000000000..38c991e6ae --- /dev/null +++ b/src/components/TreeSelect/__stories__/components/WithDisabledElementsExample.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import type {ListItemType} from '../../../useList'; +import {TreeSelect} from '../../TreeSelect'; +import type {TreeSelectProps} from '../../types'; + +interface Entity { + text: string; + id: string; +} + +export interface WithDisabledElementsExampleProps + extends Omit, 'items' | 'mapItemDataToProps'> {} + +const items: ListItemType[] = [ + { + data: {id: '1', text: 'default disabled'}, + disabled: true, + }, + { + data: {id: '2', text: 'two'}, + disabled: true, + }, + { + data: {id: '3', text: 'default selected'}, + }, + { + data: {id: '4', text: 'four'}, + disabled: true, + }, + { + data: {id: '5', text: 'five'}, + }, +]; + +export const WithDisabledElementsExample = ({...props}: WithDisabledElementsExampleProps) => { + const containerRef = React.useRef(null); + + return ( + id} + containerRef={containerRef} + mapItemDataToProps={({text}) => ({title: text})} + /> + ); +}; diff --git a/src/components/TreeSelect/types.ts b/src/components/TreeSelect/types.ts index 720fb9a46f..8d95ba67e8 100644 --- a/src/components/TreeSelect/types.ts +++ b/src/components/TreeSelect/types.ts @@ -15,7 +15,8 @@ import type {UseListParsedStateProps} from '../useList/hooks/useListParsedState' export type TreeSelectRenderControlProps = { list: UseListResult; open: boolean; - disabled: boolean; + disabled?: boolean; + placeholder?: string; toggleOpen(): void; clearValue(): void; ref: React.Ref; @@ -46,6 +47,7 @@ export interface TreeSelectProps title?: string; value?: ListItemId[]; disabled?: boolean; + placeholder?: string; defaultValue?: ListItemId[] | undefined; popupClassName?: string; popupWidth?: SelectPopupProps['width']; diff --git a/src/components/useList/hooks/useListKeydown.tsx b/src/components/useList/hooks/useListKeydown.tsx index 913b0fbc27..ecde1dc195 100644 --- a/src/components/useList/hooks/useListKeydown.tsx +++ b/src/components/useList/hooks/useListKeydown.tsx @@ -34,15 +34,18 @@ export const useListKeydown = ({containerRef, onItemClick, enabled, list}: UseLi (event: KeyboardEvent, step: number, defaultItemIndex = 0) => { event.preventDefault(); - const maybeIndex = list.structure.visibleFlattenIds.findIndex( - (i) => i === list.state.activeItemId, - ); + const maybeIndex = + typeof list.state.activeItemId === 'string' + ? list.structure.visibleFlattenIds.findIndex( + (i) => i === list.state.activeItemId, + ) + : -1; const nextIndex = findNextIndex({ list: list.structure.visibleFlattenIds, index: (maybeIndex > -1 ? maybeIndex : defaultItemIndex) + step, step: Math.sign(step), - disabledItems: list.state.disabledById, + disabledItemsById: list.state.disabledById, }); activateItem(nextIndex); diff --git a/src/components/useList/utils/findNextIndex.ts b/src/components/useList/utils/findNextIndex.ts index c00a429b50..3ce18abd7a 100644 --- a/src/components/useList/utils/findNextIndex.ts +++ b/src/components/useList/utils/findNextIndex.ts @@ -1,16 +1,20 @@ +import type {ListItemId} from '../types'; + interface FindNextItemsProps { list: string[]; index: number; step: number; - disabledItems?: Record; + disabledItemsById?: Record; } -export const findNextIndex = ({list, index, step, disabledItems = {}}: FindNextItemsProps) => { +export const findNextIndex = ({list, index, step, disabledItemsById = {}}: FindNextItemsProps) => { const dataLength = list.length; let currentIndex = (index + dataLength) % dataLength; for (let i = 0; i < dataLength; i += 1) { - if (list[currentIndex] && !disabledItems[currentIndex]) { + const id = list[currentIndex]; + + if (id && !disabledItemsById[id]) { return currentIndex; } currentIndex = (currentIndex + dataLength + step) % dataLength;