Skip to content

Commit 999cded

Browse files
adids1221M-i-k-e-l
andcommitted
Picker selection control bar and toggle all items functionality (#3557)
* Picker selection control bar and toggle all items functionality * Refactor PickerScreen layout * Rename PickerSelectionControlBar to PickerSelectionStatusToolbar and update related references * Enhance selection status label functionality in PickerSelectionStatusToolbar * Refactor PickerSelectionStatusToolbar and types for improved selection handling * Remove 'none' option from select all types in Picker and update related types * Fix types issue in PickerItemsList * Refactor toggleAllItemsSelection and availableItems usage * Remove customLabel prop from PickerSelectionStatusProps * Add renderTopCustomElement and renderBottomCustomElement props to PickerSelectionStatusToolbar * Rename getLabel prop to getSelectionStatusLabe and update related usages * Refactor selectionStatus handling in PickerItemsList * Remove unnecessary null return in renderLabel * Remove renderTopCustomElement prop * Rename _onSegmentChange to onSegmentChange * Fix TypeScript ignore directive for selectAllType in Picker * Update buttonProps onPress parameter to use selectionValue and set default selectAllType to 'button' * Refactor Picker selection logic to use areAllItemsSelected and simplify selection status handling * Rename getSelectionStatusLabel to getLabel and update type definitions for clarity * Remove renderBottomCustomElement prop and add divider in PickerSelectionStatusToolbar * Refactor Picker component to remove PickerSelectAllType enum * Refactor PickerSelectionStatusToolbar to simplify props and selection handling * Rename PickerSelectionStatusToolbar to PickerSelectionStatusBar and update related references * Update buttonProps onPress to use customValue instead of selectionValue in PickerSelectionStatusBar * Add selectAllType prop to PickerSelectionStatusBar for flexible selection handling * Refactor PickerSelectionStatusBar to remove selectAllType prop and simplify selection handling * Fix type assertion for value in PickerSelectionStatusBar to ensure correct handling of PickerMultiValue * Refactor renderSelectionStatus, fix when showLabel is false * Add PickerSelectionStatusProps import to PickerScreen and index files * Remove unused setMultiFinalValue from usePickerSelection hook * Remove value from PickerSelectionStatusLabelData * selectedCount moved to usePickerSelection * PickerSelectionStatusBar simplify selectedCount --------- Co-authored-by: Miki Leib <[email protected]>
1 parent a3464ec commit 999cded

File tree

7 files changed

+219
-31
lines changed

7 files changed

+219
-31
lines changed

demo/src/screens/componentScreens/PickerScreen.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import {
1515
PanningProvider,
1616
PickerProps,
1717
RenderCustomModalProps,
18-
PickerMethods
18+
PickerMethods,
19+
SegmentedControl,
20+
PickerSelectionStatusProps
1921
} from 'react-native-ui-lib'; //eslint-disable-line
2022
import contactsData from '../../data/conversations';
2123
import {longOptions} from './PickerScreenLongOptions';
@@ -24,6 +26,16 @@ const tagIcon = require('../../assets/icons/tags.png');
2426
const dropdown = require('../../assets/icons/chevronDown.png');
2527
const dropdownIcon = <Icon source={dropdown} tintColor={Colors.$iconDefault}/>;
2628

29+
const selectAllSegment = [{label: 'button'}, {label: 'checkbox'}];
30+
31+
const buttonProps = {
32+
onPress: (selectionValue: any) => console.log('onPress', selectionValue)
33+
};
34+
35+
const checkboxProps = {
36+
onValueChange: (value: boolean) => console.log('onValueChange', value)
37+
};
38+
2739
const renderContact = (contactValue: any, props: any) => {
2840
const contact = contacts[contactValue as number];
2941
return (
@@ -107,7 +119,12 @@ export default class PickerScreen extends Component {
107119
filter: undefined,
108120
statOption: [],
109121
scheme: undefined,
110-
contact: 0
122+
contact: 0,
123+
selectAllType: 'button' as PickerSelectionStatusProps['selectAllType']
124+
};
125+
126+
onSegmentChange = (index: number) => {
127+
this.setState({selectAllType: selectAllSegment[index].label});
111128
};
112129

113130
renderDialog: PickerProps['renderOverlay'] = (modalProps: RenderCustomModalProps) => {
@@ -148,7 +165,7 @@ export default class PickerScreen extends Component {
148165
<Text text40 $textDefault>
149166
Picker
150167
</Text>
151-
168+
152169
<Picker
153170
placeholder="Favorite Language"
154171
floatingPlaceholder
@@ -215,9 +232,12 @@ export default class PickerScreen extends Component {
215232
items={dialogOptions}
216233
/>
217234

218-
<Text text70 $textDefault>
219-
Custom Top Element:
220-
</Text>
235+
<View row spread centerV>
236+
<Text text70 $textDefault>
237+
Selection Status:
238+
</Text>
239+
<SegmentedControl segments={selectAllSegment} onChangeIndex={this.onSegmentChange}/>
240+
</View>
221241
<Picker
222242
placeholder="Status"
223243
floatingPlaceholder
@@ -226,21 +246,14 @@ export default class PickerScreen extends Component {
226246
topBarProps={{title: 'Status'}}
227247
mode={Picker.modes.MULTI}
228248
items={statusOptions}
229-
renderCustomTopElement={value => {
230-
const allOptionsSelected = Array.isArray(value) && value.length === statusOptions.length;
231-
return (
232-
<View margin-s3>
233-
<Button
234-
label={allOptionsSelected ? 'Unselect All' : 'Select All'}
235-
onPress={() => this.onTopElementPress(allOptionsSelected)}
236-
size="small"
237-
/>
238-
</View>
239-
);
249+
selectionStatus={{
250+
selectAllType: this.state.selectAllType,
251+
buttonProps,
252+
checkboxProps
240253
}}
241254
/>
242255

243-
<Text marginB-10 text70 $textDefault>
256+
<Text marginV-10 text70 $textDefault>
244257
Custom Picker:
245258
</Text>
246259
<Picker
@@ -299,7 +312,7 @@ export default class PickerScreen extends Component {
299312
style={{alignSelf: 'flex-start'}}
300313
onPress={() => this.picker.current?.openExpandable?.()}
301314
/>
302-
315+
303316
<Text text60 marginT-s5>
304317
Different Field Types
305318
</Text>

src/components/picker/PickerItemsList.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {PickerItemProps, PickerItemsListProps, PickerSingleValue, PickerModes} f
1313
import PickerContext from './PickerContext';
1414
import PickerItem from './PickerItem';
1515
import {Constants} from '../../commons/new';
16+
import PickerSelectionStatusBar from './PickerSelectionStatusBar';
1617

1718
const keyExtractor = (_item: string, index: number) => index.toString();
1819

@@ -35,7 +36,8 @@ const PickerItemsList = (props: PickerItemsListProps) => {
3536
testID,
3637
showLoader,
3738
customLoaderElement,
38-
renderCustomTopElement
39+
renderCustomTopElement,
40+
selectionStatus: selectionStatusProps
3941
} = props;
4042
const context = useContext(PickerContext);
4143

@@ -169,13 +171,17 @@ const PickerItemsList = (props: PickerItemsListProps) => {
169171
);
170172
};
171173

174+
const selectionStatus = useMemo(() => mode === PickerModes.MULTI && selectionStatusProps && <PickerSelectionStatusBar {...selectionStatusProps}/>,
175+
[selectionStatusProps, mode]);
176+
172177
const renderContent = () => {
173178
return useWheelPicker ? (
174179
renderWheel()
175180
) : (
176181
<>
177182
{renderSearchInput()}
178183
{renderCustomTopElement?.(context.value)}
184+
{selectionStatus}
179185
{renderList()}
180186
</>
181187
);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React, {useCallback, useContext} from 'react';
2+
import {StyleSheet} from 'react-native';
3+
import Button from '../button';
4+
import Checkbox from '../checkbox';
5+
import View from '../view';
6+
import Text from '../text';
7+
import Dividers from '../../style/dividers';
8+
import PickerContext from './PickerContext';
9+
import {PickerSelectionStatusProps} from './types';
10+
11+
export default function PickerSelectionStatusBar(props: PickerSelectionStatusProps) {
12+
const {containerStyle, getLabel, showLabel = true} = props;
13+
const context = useContext(PickerContext);
14+
const {toggleAllItemsSelection, selectedCount, areAllItemsSelected} = context;
15+
16+
const checkboxIndeterminate = selectedCount > 0 && !areAllItemsSelected;
17+
const label =
18+
getLabel?.({selectedCount, areAllItemsSelected}) ??
19+
`${selectedCount} Selected ${areAllItemsSelected ? '(All)' : ''}`;
20+
21+
const handlePress = useCallback(() => {
22+
const newSelectionState = !areAllItemsSelected;
23+
toggleAllItemsSelection?.(newSelectionState);
24+
if (props.selectAllType === 'button') {
25+
props?.buttonProps?.onPress?.({customValue: newSelectionState});
26+
} else if (props.selectAllType === 'checkbox') {
27+
props?.checkboxProps?.onValueChange?.(newSelectionState);
28+
}
29+
// eslint-disable-next-line react-hooks/exhaustive-deps
30+
}, [areAllItemsSelected, toggleAllItemsSelection]);
31+
32+
const renderSelectionStatus = () => {
33+
return (
34+
<View flexG={!showLabel} style={!showLabel && styles.componentContainer}>
35+
{props.selectAllType === 'button' ? (
36+
<Button
37+
label={areAllItemsSelected ? 'Deselect All' : 'Select All'}
38+
link
39+
{...props?.buttonProps}
40+
onPress={handlePress}
41+
/>
42+
) : (
43+
props.selectAllType === 'checkbox' && (
44+
<Checkbox
45+
{...props.checkboxProps}
46+
value={selectedCount > 0}
47+
indeterminate={checkboxIndeterminate}
48+
onValueChange={handlePress}
49+
/>
50+
)
51+
)}
52+
</View>
53+
);
54+
};
55+
56+
const renderLabel = () => {
57+
if (showLabel) {
58+
return <Text>{label}</Text>;
59+
}
60+
};
61+
62+
return (
63+
<View>
64+
<View row spread paddingH-page marginV-s4 style={containerStyle}>
65+
{renderLabel()}
66+
{renderSelectionStatus()}
67+
</View>
68+
<View style={Dividers.d20}/>
69+
</View>
70+
);
71+
}
72+
73+
const styles = StyleSheet.create({
74+
componentContainer: {
75+
alignItems: 'flex-end'
76+
}
77+
});

src/components/picker/helpers/usePickerSelection.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import {RefObject, useCallback, useState, useEffect} from 'react';
1+
import {RefObject, useCallback, useState, useEffect, useMemo} from 'react';
22
import _ from 'lodash';
33
import {PickerProps, PickerValue, PickerSingleValue, PickerMultiValue, PickerModes} from '../types';
44

55
interface UsePickerSelectionProps
6-
extends Pick<PickerProps, 'migrate' | 'value' | 'onChange' | 'getItemValue' | 'topBarProps' | 'mode'> {
6+
extends Pick<PickerProps, 'migrate' | 'value' | 'onChange' | 'getItemValue' | 'topBarProps' | 'mode' | 'items'> {
77
pickerExpandableRef: RefObject<any>;
88
setSearchValue: (searchValue: string) => void;
99
}
1010

1111
const usePickerSelection = (props: UsePickerSelectionProps) => {
12-
const {migrate, value, onChange, topBarProps, pickerExpandableRef, getItemValue, setSearchValue, mode} = props;
12+
const {migrate, value, onChange, topBarProps, pickerExpandableRef, getItemValue, setSearchValue, mode, items} = props;
1313
const [multiDraftValue, setMultiDraftValue] = useState(value as PickerMultiValue);
1414
const [multiFinalValue, setMultiFinalValue] = useState(value as PickerMultiValue);
1515

@@ -48,11 +48,31 @@ const usePickerSelection = (props: UsePickerSelectionProps) => {
4848
topBarProps?.onCancel?.();
4949
}, [multiFinalValue, topBarProps]);
5050

51+
const availableItems: PickerMultiValue = useMemo(() => {
52+
return items?.filter(item => !item.disabled).map(item => item.value) || [];
53+
}, [items]);
54+
55+
const areAllItemsSelected = useMemo(() => {
56+
return multiDraftValue?.length === availableItems.length;
57+
}, [multiDraftValue, availableItems]);
58+
59+
const selectedCount = useMemo(() => {
60+
return multiDraftValue?.length;
61+
}, [multiDraftValue]);
62+
63+
const toggleAllItemsSelection = useCallback((selectAll: boolean) => {
64+
setMultiDraftValue(selectAll ? availableItems : []);
65+
},
66+
[availableItems]);
67+
5168
return {
5269
multiDraftValue,
5370
onDoneSelecting,
5471
toggleItemSelection,
55-
cancelSelect
72+
cancelSelect,
73+
areAllItemsSelected,
74+
selectedCount,
75+
toggleAllItemsSelection
5676
};
5777
};
5878

src/components/picker/index.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
PickerSearchStyle,
2929
RenderCustomModalProps,
3030
PickerItemsListProps,
31-
PickerMethods
31+
PickerMethods,
32+
PickerSelectionStatusProps
3233
} from './types';
3334
import {DialogProps} from '../../incubator/dialog';
3435

@@ -79,6 +80,7 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
7980
showLoader,
8081
customLoaderElement,
8182
renderCustomTopElement,
83+
selectionStatus,
8284
...others
8385
} = themeProps;
8486
const {preset, placeholder, style, trailingAccessory, label: propsLabel} = others;
@@ -102,15 +104,24 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
102104
setSearchValue,
103105
onSearchChange: _onSearchChange
104106
} = usePickerSearch({showSearch, onSearchChange, getItemLabel, children, items});
105-
const {multiDraftValue, onDoneSelecting, toggleItemSelection, cancelSelect} = usePickerSelection({
107+
const {
108+
multiDraftValue,
109+
onDoneSelecting,
110+
toggleItemSelection,
111+
cancelSelect,
112+
areAllItemsSelected,
113+
selectedCount,
114+
toggleAllItemsSelection
115+
} = usePickerSelection({
106116
migrate,
107117
value,
108118
onChange,
109119
pickerExpandableRef: pickerExpandable,
110120
getItemValue,
111121
topBarProps,
112122
setSearchValue,
113-
mode
123+
mode,
124+
items
114125
});
115126

116127
const {label, accessibilityInfo} = usePickerLabel({
@@ -152,7 +163,10 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
152163
getItemLabel,
153164
onSelectedLayout: onSelectedItemLayout,
154165
renderItem,
155-
selectionLimit
166+
selectionLimit,
167+
areAllItemsSelected,
168+
selectedCount,
169+
toggleAllItemsSelection
156170
};
157171
}, [
158172
migrate,
@@ -165,7 +179,10 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
165179
selectionLimit,
166180
onSelectedItemLayout,
167181
toggleItemSelection,
168-
onDoneSelecting
182+
onDoneSelecting,
183+
areAllItemsSelected,
184+
selectedCount,
185+
toggleAllItemsSelection
169186
]);
170187

171188
const renderPickerItem = useCallback((item: PickerItemProps, index: number): React.ReactElement => {
@@ -247,6 +264,7 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
247264
showLoader={showLoader}
248265
customLoaderElement={customLoaderElement}
249266
renderCustomTopElement={renderCustomTopElement}
267+
selectionStatus={selectionStatus}
250268
>
251269
{filteredItems}
252270
</PickerItemsList>
@@ -316,7 +334,8 @@ export {
316334
PickerSearchStyle,
317335
RenderCustomModalProps,
318336
PickerItemsListProps,
319-
PickerMethods
337+
PickerMethods,
338+
PickerSelectionStatusProps
320339
};
321340
export {Picker}; // For tests
322341
export default Picker as typeof Picker & PickerStatics;

0 commit comments

Comments
 (0)