Skip to content

Commit

Permalink
feat: add SidebarMenu onMenuOpen props (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonai authored Oct 18, 2023
1 parent 83ffae5 commit 0d55bcc
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-mayflies-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@smile/react-front-kit': minor
---

Add `onMenuOpen` props for `SidebarMenu` + minor fixes
25 changes: 14 additions & 11 deletions packages/react-front-kit/src/Components/SidebarMenu/SidebarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { CollapseButtonControlled } from '../CollapseButton/CollapseButtonContro

export interface IMenuItem {
children?: IMenuItem[];
component?: ReactElement;
id: number | string;
label: number | string;
leftIcon?: ReactNode;
Expand All @@ -22,6 +21,7 @@ export interface ISidebarMenuProps extends PaperProps {
/** Keeps only one menu per level open at once */
hasOnlyOneOpenMenu?: boolean;
menu: IMenuItem[];
onMenuOpen?: (id: number | string, isOpened: boolean) => void;
/** Controlled state of which menus are currently open, using `id` field of `IMenuItem` */
openedMenuIds?: (number | string)[];
}
Expand All @@ -34,20 +34,22 @@ function getRecursiveMenu(
menu?: IMenuItem[],
level = 0,
): ReactElement[] | null {
if (!menu) {
if (!menu || menu.length === 0) {
return null;
}
return menu.map(({ children, ...props }) => (
return menu.map(({ children, id, label, leftIcon }) => (
<CollapseButtonControlled
{...props}
key={props.id}
key={id}
id={id}
isOpenOnSelect
label={label}
leftIcon={leftIcon}
level={level}
line={level === 0}
onCollapseChange={(isOpened) => onMenuOpen(props.id, isOpened)}
onCollapseChange={(isOpened) => onMenuOpen(id, isOpened)}
onSelect={setSelectedId}
opened={openedMenuIds.includes(props.id)}
selected={selectedId === props.id}
opened={openedMenuIds.includes(id)}
selected={selectedId === id}
>
{getRecursiveMenu(
setSelectedId,
Expand All @@ -66,19 +68,20 @@ export function SidebarMenu(props: ISidebarMenuProps): ReactElement {
const {
hasOnlyOneOpenMenu = false,
menu,
onMenuOpen,
openedMenuIds = [],
component,
} = props;
const [openedIds, setOpenedIds] = useState(openedMenuIds);
const [selectedId, setSelectedId] = useState<number | string>();

function handleOpenChange(menuId: number | string, isOpened: boolean): void {
onMenuOpen?.(menuId, isOpened);
if (hasOnlyOneOpenMenu && isOpened) {
/** Flatten and add calculated path property to the entire nested array of menus,
* keep only the path from the menu being clicked **/
const openedMenuPath = flattenNestedObjects<IMenuItem>(
addPathAndDepth<IMenuItem>(menu),
).find((menu) => menu.id === menuId)?.path;
const flatMenu = flattenNestedObjects(addPathAndDepth(menu));
const openedMenuPath = flatMenu.find((menu) => menu.id === menuId)?.path;
setOpenedIds(openedMenuPath ?? []);
} else {
/** Add or remove id being clicked **/
Expand Down
39 changes: 38 additions & 1 deletion packages/react-front-kit/src/helpers/nestedObject.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { addPathAndDepth, flattenNestedObjects } from './nestedObject';
import {
addPathAndDepth,
flattenNestedObjects,
setChildrenToTree,
} from './nestedObject';

describe('nestedObject', () => {
describe('flattenNestedObjects', () => {
Expand Down Expand Up @@ -65,4 +69,37 @@ describe('nestedObject', () => {
]);
});
});

describe('setChildrenToTree', () => {
it('should add path and deep property to nested object', () => {
expect(
setChildrenToTree(
[{ id: '311' }, { id: '312' }, { id: '313' }],
['3', '31'],
[
{ id: '1' },
{ children: [{ id: '21' }], id: '2' },
{
children: [
{ children: [{ id: '311' }, { id: '312' }], id: '31' },
],
id: '3',
},
],
),
).toEqual([
{ id: '1' },
{ children: [{ id: '21' }], id: '2' },
{
children: [
{
children: [{ id: '311' }, { id: '312' }, { id: '313' }],
id: '31',
},
],
id: '3',
},
]);
});
});
});
62 changes: 44 additions & 18 deletions packages/react-front-kit/src/helpers/nestedObject.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
export interface INestedObject<O extends object> {
children?: INestedObject<O>[];
depth?: number;
interface IObjectWithChildren {
children?: IObjectWithChildren[];
id: number | string;
path?: (number | string)[];
}

export function flattenNestedObjects<O extends object>(
arr: INestedObject<O>[],
): INestedObject<O>[] {
let children: INestedObject<O>[] = [];
arr.forEach((o) => {
if (o.children && o.children.length) {
children = [...children, ...o.children];
type INestedObject<O extends IObjectWithChildren> = Omit<O, 'children'> & {
children?: INestedObject<O>[];
depth: number;
path: (number | string)[];
};

export function flattenNestedObjects<O extends IObjectWithChildren>(
arr: O[],
): O[] {
let children: O[] = [];
arr.forEach((obj) => {
if (obj.children && obj.children.length) {
children = [...children, ...obj.children] as O[];
}
});
return arr.concat(children.length ? flattenNestedObjects(children) : []);
}

export function addPathAndDepth<O extends object>(
arr: INestedObject<O>[] = [],
export function addPathAndDepth<O extends IObjectWithChildren>(
arr: O[] = [],
parentPath: (number | string)[] = [],
): INestedObject<O>[] {
return arr.map((o) => {
const path = parentPath.concat(o.id);
return arr.map((obj) => {
const path = parentPath.concat(obj.id);
return {
...o,
children: addPathAndDepth(o.children, path),
...obj,
children: addPathAndDepth(obj.children, path),
depth: parentPath.length,
path,
};
} as INestedObject<O>;
});
}

export function setChildrenToTree<O extends IObjectWithChildren>(
children: O[],
path: (number | string)[],
menu?: O[],
): O[] {
if (path.length === 0) {
return children;
}
const [id, ...nextPath] = path;
return (
menu?.map((item) => {
if (item.id === id) {
return {
...item,
children: setChildrenToTree(children, nextPath, item.children),
};
}
return item;
}) ?? []
);
}
2 changes: 2 additions & 0 deletions packages/react-front-kit/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ export * from './Layouts/FoldableColumnLayout/FoldableColumnLayout';
// page exports
export * from './Pages/TestPage/TestPage';
export * from './Pages/DocumentDetails/DocumentDetails';
// other exports
export * from './helpers';

0 comments on commit 0d55bcc

Please sign in to comment.