Skip to content

Commit

Permalink
feat: added example with toggler btn
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaevAlexandr committed Nov 14, 2023
1 parent 936c8fe commit dae52f5
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 2 deletions.
25 changes: 25 additions & 0 deletions src/components/ListNext/__stories__/PopupWithToggler.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import type {Meta, StoryFn} from '@storybook/react';

import {Flex} from '../../layout';

import {PopupWithTogglerList, PopupWithTogglerListProps} from './components/PopupWithTogglerList';

export default {
title: 'Unstable/ListNext/PopupWithToggler',
component: PopupWithTogglerList,
} as Meta;

const PopupWithTogglerScroll: StoryFn<PopupWithTogglerListProps> = (props) => {
return (
<Flex>
<PopupWithTogglerList {...props} />
</Flex>
);
};
export const Examples = PopupWithTogglerScroll.bind({});
Examples.args = {
itemsCount: 10,
size: 'm',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from 'react';

import {Button} from '../../../Button';
import {Popup} from '../../../Popup';
import {Flex, sp} from '../../../layout';
import {ListGroupItemView} from '../../components/ListGroupItemView/ListGroupItemView';
import {ListItemRenderer} from '../../components/ListItemRenderer/ListItemRenderer';
import {ListItemView} from '../../components/ListItemView/ListItemView';
import {ListRecursiveRenderer} from '../../components/ListRecursiveRenderer/ListRecursiveRenderer';
import {bListRadiuses} from '../../constants';
import {useListController} from '../../hooks/useListController';
import {useListKeydown} from '../../hooks/useListKeydown';
import type {ListItemId, ListSizeTypes} from '../../types';
import {scrollToItem} from '../../utils/scrollToItem';
import {createRandomizedData} from '../utils/makeData';

export interface PopupWithTogglerListProps {
itemsCount: number;
size: ListSizeTypes;
}

const COMPONENT_WIDTH = 300;

export const PopupWithTogglerList = ({size, itemsCount}: PopupWithTogglerListProps) => {
const controlWrapRef = React.useRef(null);
const controlRef = React.useRef(null);
const [open, setOpen] = React.useState(false);
const items = React.useMemo(
() => createRandomizedData<{title: string}>(itemsCount),
[itemsCount],
);

const listController = useListController(items);

const [selectedId] = React.useMemo(
() => Object.keys(listController.selected),
[listController.selected],
);

// restoring focus when popup opens
React.useLayoutEffect(() => {
if (open) {
listController.containerRef.current?.focus();
listController.setActiveItemId(selectedId ?? '0');

if (selectedId) {
scrollToItem(selectedId, listController.containerRef.current);
}
}
// subscribe only in open event
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);

const onItemClick = React.useCallback(
(id: ListItemId) => {
if (id in listController.groupsState) {
listController.setExpanded((state) => ({
...state,
[id]: id in state ? !state[id] : false,
}));
listController.setActiveItemId(id);
} else {
// only one item active
listController.setSelected((state) => ({
[id]: !state[id],
}));
setOpen(false);
listController.setActiveItemId(undefined);
}
},
[listController],
);

useListKeydown({
onItemClick,
...listController,
});

return (
<Flex direction="column" gap="5" width={COMPONENT_WIDTH} ref={controlWrapRef}>
<Button ref={controlRef} onClick={() => setOpen((x) => !x)} width="max">
{selectedId ? listController.byId[selectedId].title : 'Select person'}
</Button>
<Popup
style={{width: COMPONENT_WIDTH}}
contentClassName={bListRadiuses({size})}
anchorRef={controlWrapRef as React.RefObject<HTMLDivElement>}
placement={['bottom-start', 'bottom-end', 'top-start', 'top-end']}
offset={[0, 10]}
open={open}
onClose={() => setOpen(false)}
disablePortal
restoreFocus
restoreFocusRef={controlRef}
>
<Flex
direction="column"
grow
ref={listController.containerRef}
tabIndex={-1}
role="listbox"
style={{height: 400, overflow: 'auto'}}
className={sp({p: 2})}
>
{items.map((item, index) => (
<ListRecursiveRenderer
item={item}
key={index}
index={index}
{...listController}
>
{({item, id}) => (
<ListItemRenderer
onItemClick={onItemClick}
id={id}
{...listController}
>
{({isGroup, ...props}) => {
const View = isGroup ? ListGroupItemView : ListItemView;

return <View size={size} {...props} {...item} />;
}}
</ListItemRenderer>
)}
</ListRecursiveRenderer>
))}
</Flex>
</Popup>
</Flex>
);
};
3 changes: 2 additions & 1 deletion src/components/ListNext/hooks/useListKeydown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const useListKeydown = ({
(index?: number, scrollTo = true) => {
if (typeof index === 'number' && order[index]) {
if (scrollTo) {
scrollToItem(order[index], containerRef?.current ?? undefined);
scrollToItem(order[index], containerRef?.current);
}

setActiveItemId?.(order[index]);
Expand Down Expand Up @@ -67,6 +67,7 @@ export const useListKeydown = ({
const handleKeyDown = (event: KeyboardEvent) => {
switch (event.key) {
case 'ArrowDown': {
console.log('FIRED!!');
handleKeyMove(event, 1, -1);
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ListNext/utils/scrollToItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {ListItemId} from '../types';

import {createListItemId} from './createListItemId';

export const scrollToItem = (itemId: ListItemId, containerRef?: HTMLDivElement) => {
export const scrollToItem = (itemId: ListItemId, containerRef?: HTMLDivElement | null) => {
if (document) {
const element = (containerRef || document).querySelector(
`[data-list-item="${createListItemId(itemId)}"]`,
Expand Down

0 comments on commit dae52f5

Please sign in to comment.