|
1 | | -import React, { ComponentType, createContext, ReactNode, useContext, useState } from "react"; |
2 | | -import { PanelItem } from "../Definitions/PanelItem"; |
| 1 | +import React, { Context, createContext, useCallback, useContext, useEffect, useState } from "react"; |
| 2 | +import { getActivePanel } from "../Utils/getActivePanel"; |
| 3 | +import { PanelSideBarContextProps } from "./PanelSideBarContextProps"; |
3 | 4 |
|
4 | | -export interface PanelLinkRendererProps<T> { |
5 | | - /** |
6 | | - * The generic panel item. |
7 | | - */ |
8 | | - item: PanelItem<T>; |
9 | | - /** |
10 | | - * The panel children item. |
11 | | - */ |
12 | | - children: ReactNode; |
13 | | -} |
14 | | - |
15 | | -export type MenuItemToggleFn<TPanelItem> = (menuItem: PanelItem<TPanelItem>) => void; |
16 | | - |
17 | | -export interface PanelSideBarContextProps<TPanelItem> { |
18 | | - activePanelId: string; |
19 | | - /** |
20 | | - * The global panel items. |
21 | | - */ |
22 | | - globalItems: PanelItem<TPanelItem>[]; |
23 | | - /** |
24 | | - * The local panel items. |
25 | | - */ |
26 | | - localItems?: PanelItem[]; |
27 | | - /** |
28 | | - * The function used to set a panel as active |
29 | | - * @param panelId The panel item identifier |
30 | | - */ |
31 | | - setActivePanel: (panelId: string) => void; |
32 | | - /** |
33 | | - * The component used to render the menu item links. |
34 | | - */ |
35 | | - LinkRenderer: ComponentType<PanelLinkRendererProps<TPanelItem>>; |
36 | | - /** |
37 | | - * The list of toggled menu item identifier |
38 | | - */ |
39 | | - toggledMenuItemIds: string[]; |
40 | | - /** |
41 | | - * The function used to toggle a menu item |
42 | | - */ |
43 | | - toggleMenuItem: MenuItemToggleFn<TPanelItem>; |
44 | | - /** |
45 | | - * The footer content. |
46 | | - */ |
47 | | - footer?: ReactNode; |
48 | | - /** |
49 | | - * The brand content shown on the top navigation bar. |
50 | | - */ |
51 | | - brand?: ReactNode; |
52 | | - /** |
53 | | - * The user dropdown toggle content. |
54 | | - */ |
55 | | - userDropDownMenuToggle?: ReactNode; |
56 | | - /** |
57 | | - * The user dropdown menu content. |
58 | | - */ |
59 | | - userDropDownMenu?: ReactNode; |
60 | | - /** |
61 | | - * The menu content on the right. |
62 | | - */ |
63 | | - topBarRightCustomItems?: ReactNode[]; |
64 | | - /** |
65 | | - * The menu content on the left. |
66 | | - */ |
67 | | - topBarLeftCustomItems?: ReactNode[]; |
68 | | - /** |
69 | | - * The context theme |
70 | | - */ |
71 | | - theme?: "light"; |
72 | | - /** |
73 | | - * Boolean indicating if you want to render first items level as icons or directly as menu entries |
74 | | - */ |
75 | | - renderFirstItemsLevelAsTiles?: boolean; |
76 | | - |
77 | | - /** |
78 | | - * Boolean indicating if you want to render first level items as links or as button |
79 | | - */ |
80 | | - renderTilesAsLinks?: boolean; |
81 | | - |
82 | | - /** |
83 | | - * The default active panel id that will be taken if no active panel is dinamically found |
84 | | - */ |
85 | | - defaultActivePanelId?: string; |
86 | | -} |
| 5 | +export type MenuItemToggleFn<TPanelItemId extends string> = (menuItemId: TPanelItemId) => void; |
87 | 6 |
|
88 | | -export const PanelSideBarContext = createContext<PanelSideBarContextProps<any> | null>(null); |
| 7 | +const PanelSideBarContext = createContext<PanelSideBarContextProps<any, any> | null>(null); |
89 | 8 |
|
90 | | -export interface PanelSideBarMenuProviderProps<TPanelItem> |
91 | | - extends Pick< |
92 | | - PanelSideBarContextProps<TPanelItem>, |
93 | | - | "globalItems" |
94 | | - | "LinkRenderer" |
95 | | - | "brand" |
96 | | - | "footer" |
97 | | - | "userDropDownMenu" |
98 | | - | "userDropDownMenuToggle" |
99 | | - | "topBarRightCustomItems" |
100 | | - | "topBarLeftCustomItems" |
101 | | - | "localItems" |
102 | | - | "theme" |
103 | | - | "renderFirstItemsLevelAsTiles" |
104 | | - | "renderTilesAsLinks" |
105 | | - | "defaultActivePanelId" |
106 | | - > { |
| 9 | +export interface PanelSideBarMenuProviderProps<TPanelItemId extends string, TPanelItem> |
| 10 | + extends Pick<PanelSideBarContextProps<TPanelItemId, TPanelItem>, "menuItems" | "defaultActivePanelId"> { |
107 | 11 | /** |
108 | 12 | * The children elements. |
109 | 13 | */ |
110 | 14 | children: React.ReactNode; |
111 | | -} |
112 | | - |
113 | | -export const PanelSideBarProvider = <TPanelItem,>(props: PanelSideBarMenuProviderProps<TPanelItem>) => { |
114 | | - const { |
115 | | - children, |
116 | | - globalItems, |
117 | | - localItems = [], |
118 | | - LinkRenderer, |
119 | | - brand = null, |
120 | | - footer = null, |
121 | | - userDropDownMenu, |
122 | | - userDropDownMenuToggle, |
123 | | - topBarRightCustomItems, |
124 | | - topBarLeftCustomItems, |
125 | | - renderFirstItemsLevelAsTiles = true, |
126 | | - renderTilesAsLinks = false, |
127 | | - theme = "light", |
128 | | - defaultActivePanelId, |
129 | | - } = props; |
130 | | - |
131 | | - const activePanel = globalItems.find((x) => |
132 | | - x.children ? x.children.find((y) => (y.children ? y.children.find((s) => s.active) : y.active)) : x.active, |
133 | | - ); |
134 | | - const firstActivePanel = activePanel ?? globalItems.find((x) => (defaultActivePanelId ? x.id === defaultActivePanelId : x.id)); |
135 | 15 |
|
136 | | - const getActivePanelId = () => localItems?.at(0)?.id ?? firstActivePanel?.id ?? ""; |
137 | | - |
138 | | - const [activePanelId, setActivePanelId] = useState(getActivePanelId()); |
| 16 | + /** |
| 17 | + * if the sidebar should be open by default. |
| 18 | + */ |
| 19 | + sidebarOpenByDefault?: boolean; |
| 20 | +} |
139 | 21 |
|
140 | | - const [toggledMenuItemIds, setToggledMenuItemIds] = useState<string[]>([ |
141 | | - (firstActivePanel?.children |
142 | | - ? firstActivePanel.children?.find((x) => x.children?.find((s) => s.active)) |
143 | | - : firstActivePanel?.children?.find((x) => x.active) |
144 | | - )?.id ?? "", |
145 | | - ]); |
| 22 | +export const PanelSideBarProvider = <TPanelItemId extends string, TPanelItem>( |
| 23 | + props: PanelSideBarMenuProviderProps<TPanelItemId, TPanelItem>, |
| 24 | +) => { |
| 25 | + const { children, defaultActivePanelId, sidebarOpenByDefault = true } = props; |
| 26 | + const [menuItems, setMenuItems] = useState(props.menuItems); |
| 27 | + const [isSidebarOpen, setIsSidebarOpen] = useState(sidebarOpenByDefault); |
| 28 | + const toggleSidebar = () => setIsSidebarOpen((prev) => !prev); |
146 | 29 |
|
147 | | - const setActivePanel = (panelId: string) => setActivePanelId(panelId); |
| 30 | + const [activePanelId, setActivePanelId] = useState(getActivePanel(menuItems, defaultActivePanelId)?.id); |
| 31 | + const [toggledMenuItemIds, setToggledMenuItemIds] = useState<TPanelItemId[]>(activePanelId ? [activePanelId] : []); |
| 32 | + const setActivePanel = (panelId: TPanelItemId) => setActivePanelId(panelId); |
148 | 33 |
|
149 | | - const toggleMenuItem: MenuItemToggleFn<TPanelItem> = (menuItem) => { |
| 34 | + const toggleMenuItem: MenuItemToggleFn<TPanelItemId> = (menuItemId) => { |
150 | 35 | setToggledMenuItemIds((prev) => { |
151 | | - const idExists = !!prev.find((id) => id == menuItem.id); |
| 36 | + const idExists = !!prev.find((id) => id == menuItemId); |
152 | 37 |
|
153 | 38 | if (idExists) { |
154 | | - return prev.filter((id) => id !== menuItem.id); |
| 39 | + return prev.filter((id) => id !== menuItemId); |
155 | 40 | } |
156 | 41 |
|
157 | | - return [...prev, menuItem.id]; |
| 42 | + return [...prev, menuItemId]; |
158 | 43 | }); |
159 | 44 | }; |
160 | 45 |
|
| 46 | + const untoggleMenuItems = () => setToggledMenuItemIds([]); |
| 47 | + |
| 48 | + const recomputeActivePanel = useCallback(() => { |
| 49 | + const activePanelId = getActivePanel(menuItems, defaultActivePanelId)?.id; |
| 50 | + setActivePanelId(activePanelId); |
| 51 | + if (activePanelId) { |
| 52 | + setToggledMenuItemIds(prev => prev.includes(activePanelId) ? prev : [...prev, activePanelId]); |
| 53 | + } |
| 54 | + }, []); |
| 55 | + |
| 56 | + useEffect(() => recomputeActivePanel(), [menuItems]); |
| 57 | + |
161 | 58 | return ( |
162 | 59 | <PanelSideBarContext.Provider |
163 | 60 | value={{ |
| 61 | + menuItems, |
| 62 | + setMenuItems, |
164 | 63 | activePanelId, |
165 | | - globalItems, |
166 | | - localItems, |
167 | | - LinkRenderer, |
168 | 64 | setActivePanel, |
| 65 | + recomputeActivePanel, |
169 | 66 | toggledMenuItemIds, |
170 | 67 | toggleMenuItem, |
171 | | - footer, |
172 | | - userDropDownMenu, |
173 | | - userDropDownMenuToggle, |
174 | | - topBarRightCustomItems, |
175 | | - topBarLeftCustomItems, |
176 | | - brand, |
177 | | - theme, |
178 | | - renderFirstItemsLevelAsTiles, |
179 | | - renderTilesAsLinks, |
| 68 | + untoggleMenuItems, |
| 69 | + isSidebarOpen, |
| 70 | + toggleSidebar, |
180 | 71 | }} |
181 | 72 | > |
182 | 73 | {children} |
183 | 74 | </PanelSideBarContext.Provider> |
184 | 75 | ); |
185 | 76 | }; |
186 | 77 |
|
187 | | -export const usePanelSideBarContext = () => { |
188 | | - const context = useContext(PanelSideBarContext); |
| 78 | +export const usePanelSideBarContext = <TPanelItemId extends string, TPanelItem>() => { |
| 79 | + const context = useContext(PanelSideBarContext as Context<PanelSideBarContextProps<TPanelItemId, TPanelItem>>); |
189 | 80 | if (context === null) { |
190 | 81 | throw new Error("usePanelSideBarContext must be used within a PanelSideBarProvider"); |
191 | 82 | } |
|
0 commit comments