From 8ce6b105f3271b90dc4e45f544ee1d469f5b2de9 Mon Sep 17 00:00:00 2001 From: NSTKrishna Date: Mon, 13 Jul 2026 06:45:37 +0530 Subject: [PATCH 1/5] Add DashboardLayout and WidgetPicker components Introduce a responsive DashboardLayout (sticky sidebar on desktop, bottom SwipeableDrawer on mobile) and a WidgetPicker UI with types for WidgetItem. Added files: src/custom/DashboardLayout/index.tsx, src/custom/WidgetPicker/WidgetPicker.tsx, src/custom/WidgetPicker/index.tsx. Updated exports in src/custom/index.ts, src/custom/index.tsx and root src/index.tsx to re-export the new components and their props/types so they are available from the package API. Signed-off-by: NSTKrishna --- src/custom/DashboardLayout/index.tsx | 134 +++++++++++++++++++ src/custom/WidgetPicker/WidgetPicker.tsx | 157 +++++++++++++++++++++++ src/custom/WidgetPicker/index.tsx | 1 + src/custom/index.ts | 2 + src/custom/index.tsx | 1 + src/index.tsx | 11 ++ 6 files changed, 306 insertions(+) create mode 100644 src/custom/DashboardLayout/index.tsx create mode 100644 src/custom/WidgetPicker/WidgetPicker.tsx create mode 100644 src/custom/WidgetPicker/index.tsx diff --git a/src/custom/DashboardLayout/index.tsx b/src/custom/DashboardLayout/index.tsx new file mode 100644 index 000000000..1044393b5 --- /dev/null +++ b/src/custom/DashboardLayout/index.tsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Drawer } from '../../base'; +import { useTheme, useMediaQuery } from '../../theme'; +import { SwipeableDrawer } from '@mui/material'; + +export interface DashboardLayoutProps { + /** The main dashboard content (typically the React-Grid-Layout) */ + children: React.ReactNode; + + /** Whether the right-hand sidebar should be visible */ + isSidebarOpen: boolean; + + /** The content to render inside the sidebar (e.g., Widget Gallery) */ + sidebarContent: React.ReactNode; + + /** Optional custom width for the sidebar. Defaults to responsive width. */ + sidebarWidth?: string | number | Partial>; + + /** Optional sticky top offset for the sidebar (useful if page has a top navbar) */ + sidebarTopOffset?: string | number; + + /** Optional fixed height for the sticky sidebar. Defaults to 100vh */ + sidebarHeight?: string | number; + + /** Callback fired when the component requests to be closed (e.g. clicking the backdrop on mobile) */ + onClose?: () => void; +} + +export const DashboardLayout: React.FC = ({ + children, + isSidebarOpen, + sidebarContent, + sidebarWidth = { xs: '100%', md: '350px' }, + sidebarTopOffset = '0', + sidebarHeight = '100vh', + onClose, +}) => { + const theme = useTheme(); + // We use the 'md' breakpoint (900px default) to switch between mobile and desktop layout + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + + const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false); + const drawerBleeding = 56; + + useEffect(() => { + if (isSidebarOpen) { + setIsMobileDrawerOpen(true); + } else { + setIsMobileDrawerOpen(false); + } + }, [isSidebarOpen]); + + return ( + + + {children} + + + {isSidebarOpen && isMobile && ( + <> + setIsMobileDrawerOpen(false)} + onOpen={() => setIsMobileDrawerOpen(true)} + swipeAreaWidth={drawerBleeding} + disableSwipeToOpen={false} + ModalProps={{ + keepMounted: true, + }} + sx={{ + '& .MuiPaper-root': { + height: `calc(50% - ${drawerBleeding}px)`, + overflow: 'visible', + }, + '& .MuiDrawer-paper': { + borderTopLeftRadius: '16px', + borderTopRightRadius: '16px', + }, + }} + > + setIsMobileDrawerOpen(!isMobileDrawerOpen)} + > + + + + {sidebarContent} + + + + )} + + {isSidebarOpen && !isMobile && ( + + {sidebarContent} + + )} + + ); +}; diff --git a/src/custom/WidgetPicker/WidgetPicker.tsx b/src/custom/WidgetPicker/WidgetPicker.tsx new file mode 100644 index 000000000..6c83ddb37 --- /dev/null +++ b/src/custom/WidgetPicker/WidgetPicker.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import { Box, IconButton, Stack, Typography } from '../../base'; +import { AddIcon, CloseIcon } from '../../icons'; +import { useTheme } from '../../theme'; +import { SxProps, Theme } from '@mui/material'; + +export interface WidgetItem { + key: string; + title: string; + thumbnail?: string; + [key: string]: any; // Allow passing extra widget properties +} + +export interface WidgetPickerProps { + /** The list of widgets available to add */ + widgetsToAdd: WidgetItem[]; + + /** Callback when a widget is clicked to be added */ + onAddWidget: (widget: any, key: string) => void; + + /** Optional callback to close the picker (renders a Close icon if provided) */ + onClose?: () => void; + + /** Custom background color for the header. Defaults to theme.palette.background.card */ + headerBackgroundColor?: string; + + /** Custom text color for the header. Defaults to theme.palette.text.primary */ + headerTextColor?: string; + + /** Custom styles for the outer container (e.g. for custom box shadows or borders) */ + containerSx?: SxProps; +} + +export const WidgetPicker: React.FC = ({ + widgetsToAdd, + onAddWidget, + onClose, + headerBackgroundColor, + headerTextColor, + containerSx = {}, +}) => { + const theme = useTheme(); + + return ( + + + + Widgets + + {onClose && ( + + + + )} + + + + {widgetsToAdd.length === 0 && ( + + All widgets added to the layout. + + )} + + {widgetsToAdd.map(({ key, ...widget }) => ( + + + {widget.title} + onAddWidget(widget, key)} + > + + + + {widget.thumbnail && ( + {widget.title} + )} + + ))} + + + ); +}; diff --git a/src/custom/WidgetPicker/index.tsx b/src/custom/WidgetPicker/index.tsx new file mode 100644 index 000000000..075b1fead --- /dev/null +++ b/src/custom/WidgetPicker/index.tsx @@ -0,0 +1 @@ +export * from './WidgetPicker'; diff --git a/src/custom/index.ts b/src/custom/index.ts index 3f35c351e..8de85c68e 100644 --- a/src/custom/index.ts +++ b/src/custom/index.ts @@ -1,7 +1,9 @@ // Export all custom components export * from './CustomTooltip'; +export * from './DashboardLayout'; export * from './HelperTextPopover'; export * from './Markdown'; export * from './Modal'; export * from './RJSFFormWrapper'; export * from './StyledAccordion'; +export * from './WidgetPicker'; diff --git a/src/custom/index.tsx b/src/custom/index.tsx index 608c8f233..ccf81fe0a 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -186,3 +186,4 @@ export * from './RJSFFormWrapper'; export * from './ShareModal'; export * from './UserSearchField'; export * from './Workspaces'; +export * from './WidgetPicker'; diff --git a/src/index.tsx b/src/index.tsx index 1129de512..84da48173 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -32,6 +32,11 @@ export { type DangerConfirmationCheckbox, type DangerConfirmationModalProps } from './custom/DangerConfirmationModal'; + +export { + DashboardLayout, + type DashboardLayoutProps +} from './custom/DashboardLayout'; // Same nested-barrel dts-drop quirk as FeedbackButton above: UniversalFilter // (and its FilterColumn / UniversalFilterProps types) reaches the entry only // through `export * from './custom'`, so rollup-plugin-dts drops it from the @@ -51,3 +56,9 @@ export { type Key, type PermissionShieldProps } from './custom/permissions'; + +export { + WidgetPicker, + type WidgetPickerProps, + type WidgetItem +} from './custom/WidgetPicker'; From c1fd1f8aa2776130a0adb9fc135a1a58b6722229 Mon Sep 17 00:00:00 2001 From: NSTKrishna Date: Mon, 13 Jul 2026 06:59:24 +0530 Subject: [PATCH 2/5] Call onClose on drawer close; tighten WidgetPicker types Invoke the optional onClose callback when the mobile drawer is closed (in SwipeableDrawer.onClose and the header toggle). Remove an unused Drawer import. Improve WidgetPicker typings: use unknown for the extra properties index signature and change onAddWidget to accept the widget payload without its 'key'. Also update the headerBackgroundColor JSDoc default. These changes ensure proper close propagation and stronger type safety. Signed-off-by: NSTKrishna --- src/custom/DashboardLayout/index.tsx | 15 ++++++++++++--- src/custom/WidgetPicker/WidgetPicker.tsx | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/custom/DashboardLayout/index.tsx b/src/custom/DashboardLayout/index.tsx index 1044393b5..4558625d7 100644 --- a/src/custom/DashboardLayout/index.tsx +++ b/src/custom/DashboardLayout/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Box, Drawer } from '../../base'; +import { Box } from '../../base'; import { useTheme, useMediaQuery } from '../../theme'; import { SwipeableDrawer } from '@mui/material'; @@ -61,7 +61,10 @@ export const DashboardLayout: React.FC = ({ setIsMobileDrawerOpen(false)} + onClose={() => { + setIsMobileDrawerOpen(false); + onClose?.(); + }} onOpen={() => setIsMobileDrawerOpen(true)} swipeAreaWidth={drawerBleeding} disableSwipeToOpen={false} @@ -96,7 +99,13 @@ export const DashboardLayout: React.FC = ({ borderBottom: `1px solid ${theme.palette.divider}`, cursor: 'pointer', }} - onClick={() => setIsMobileDrawerOpen(!isMobileDrawerOpen)} + onClick={() => { + const nextState = !isMobileDrawerOpen; + setIsMobileDrawerOpen(nextState); + if (!nextState) { + onClose?.(); + } + }} > void; + onAddWidget: (widget: Omit, key: string) => void; /** Optional callback to close the picker (renders a Close icon if provided) */ onClose?: () => void; - /** Custom background color for the header. Defaults to theme.palette.background.card */ + /** Custom background color for the header. Defaults to theme.palette.background.default */ headerBackgroundColor?: string; /** Custom text color for the header. Defaults to theme.palette.text.primary */ From 7d0b48d232b67fd1c5df4ee6aee6f5c6c42c4cdc Mon Sep 17 00:00:00 2001 From: NSTKrishna Date: Mon, 13 Jul 2026 08:19:19 +0530 Subject: [PATCH 3/5] Fix mobile drawer visibility Remove the isSidebarOpen condition from the mobile SwipeableDrawer to ensure the drawer is always displayed on mobile devices, regardless of sidebar state. Signed-off-by: NSTKrishna --- src/custom/DashboardLayout/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/DashboardLayout/index.tsx b/src/custom/DashboardLayout/index.tsx index 4558625d7..1fd87e52d 100644 --- a/src/custom/DashboardLayout/index.tsx +++ b/src/custom/DashboardLayout/index.tsx @@ -56,7 +56,7 @@ export const DashboardLayout: React.FC = ({ {children} - {isSidebarOpen && isMobile && ( + {isMobile && ( <> Date: Mon, 13 Jul 2026 09:38:51 +0530 Subject: [PATCH 4/5] Fix mobile dashboard drawer behavior Only expose the swipe area when the sidebar is open, and stop firing the layout `onClose` callback from mobile drawer toggles. This keeps mobile drawer state changes local while preserving the wider sidebar interaction area when appropriate. Signed-off-by: NSTKrishna --- src/custom/DashboardLayout/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/custom/DashboardLayout/index.tsx b/src/custom/DashboardLayout/index.tsx index 1fd87e52d..ed25df8d2 100644 --- a/src/custom/DashboardLayout/index.tsx +++ b/src/custom/DashboardLayout/index.tsx @@ -63,10 +63,9 @@ export const DashboardLayout: React.FC = ({ open={isMobileDrawerOpen} onClose={() => { setIsMobileDrawerOpen(false); - onClose?.(); }} onOpen={() => setIsMobileDrawerOpen(true)} - swipeAreaWidth={drawerBleeding} + swipeAreaWidth={isSidebarOpen ? drawerBleeding : 0} disableSwipeToOpen={false} ModalProps={{ keepMounted: true, @@ -102,9 +101,6 @@ export const DashboardLayout: React.FC = ({ onClick={() => { const nextState = !isMobileDrawerOpen; setIsMobileDrawerOpen(nextState); - if (!nextState) { - onClose?.(); - } }} > Date: Mon, 13 Jul 2026 09:45:31 +0530 Subject: [PATCH 5/5] Remove DashboardLayout onClose prop Drops the unused `onClose` prop from `DashboardLayout`'s public props and implementation, simplifying the component API. Signed-off-by: NSTKrishna --- src/custom/DashboardLayout/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/custom/DashboardLayout/index.tsx b/src/custom/DashboardLayout/index.tsx index ed25df8d2..d5806b18a 100644 --- a/src/custom/DashboardLayout/index.tsx +++ b/src/custom/DashboardLayout/index.tsx @@ -22,8 +22,7 @@ export interface DashboardLayoutProps { /** Optional fixed height for the sticky sidebar. Defaults to 100vh */ sidebarHeight?: string | number; - /** Callback fired when the component requests to be closed (e.g. clicking the backdrop on mobile) */ - onClose?: () => void; + } export const DashboardLayout: React.FC = ({ @@ -33,7 +32,6 @@ export const DashboardLayout: React.FC = ({ sidebarWidth = { xs: '100%', md: '350px' }, sidebarTopOffset = '0', sidebarHeight = '100vh', - onClose, }) => { const theme = useTheme(); // We use the 'md' breakpoint (900px default) to switch between mobile and desktop layout