-
Notifications
You must be signed in to change notification settings - Fork 232
[UI] Add DashboardLayout and WidgetPicker components #1706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
NSTKrishna
wants to merge
15
commits into
layer5io:master
Choose a base branch
from
NSTKrishna:feat/dashboard-layout-mobile-responsive
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
8ce6b10
Add DashboardLayout and WidgetPicker components
NSTKrishna c1fd1f8
Call onClose on drawer close; tighten WidgetPicker types
NSTKrishna 7d0b48d
Fix mobile drawer visibility
NSTKrishna 5a65f9d
Fix mobile dashboard drawer behavior
NSTKrishna 7acdc32
Remove DashboardLayout onClose prop
NSTKrishna eaba5c9
Merge branch 'master' into feat/dashboard-layout-mobile-responsive
Rajesh-Nagarajan-11 6aa7ca3
Merge branch 'master' into feat/dashboard-layout-mobile-responsive
NSTKrishna 6eec363
Merge remote-tracking branch 'origin/master' into feat/dashboard-layo…
NSTKrishna d24fc97
Add WidgetEmptyState and tidy exports
NSTKrishna 80ee06b
Polish dashboard widgets and layout behavior
NSTKrishna 88e7815
Type table options and normalize empty state
NSTKrishna ff7f718
Merge branch 'master' into feat/dashboard-layout-mobile-responsive
rishiraj38 b708519
Resolve merge conflict
NSTKrishna 67f627f
fix(dashboard-layout): fix imports, state model, and structure issues
NSTKrishna de805c2
fix(a11y): address CodeRabbit accessibility feedback
NSTKrishna File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import React, { useState, useEffect, useRef } from 'react'; | ||
| import { Box, Fab } from '../../base'; | ||
| import { AddIcon } from '../../icons/Add'; | ||
| import { useTheme, useMediaQuery } from '../../theme'; | ||
| import { BottomSheet } from '../BottomSheet'; | ||
|
|
||
| export interface DashboardLayoutProps { | ||
| /** The main dashboard content (typically the React-Grid-Layout) */ | ||
| children: React.ReactNode; | ||
|
|
||
| /** Whether Edit Mode is active (controls sidebar visibility). When this | ||
| * transitions from false → true the mobile sheet auto-opens. */ | ||
| isSidebarOpen: boolean; | ||
|
|
||
| /** The content to render inside the sidebar (e.g., Widget Gallery) */ | ||
| sidebarContent: React.ReactNode; | ||
|
|
||
| /** Accessible title for the mobile bottom sheet (used as aria-labelledby on the Dialog). | ||
| * Defaults to 'Widget Picker'. */ | ||
| sidebarTitle?: string; | ||
|
|
||
| /** Optional custom width for the sidebar. Defaults to responsive width. */ | ||
| sidebarWidth?: string | number | Partial<Record<'xs' | 'sm' | 'md' | 'lg' | 'xl', string | number>>; | ||
|
|
||
| /** 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; | ||
| } | ||
|
|
||
| export const DashboardLayout: React.FC<DashboardLayoutProps> = ({ | ||
| children, | ||
| isSidebarOpen, | ||
| sidebarContent, | ||
| sidebarTitle = 'Widget Picker', | ||
| sidebarWidth = { xs: '100%', md: '350px' }, | ||
| sidebarTopOffset = '0', | ||
| sidebarHeight = '100vh' | ||
| }) => { | ||
| const theme = useTheme(); | ||
| // We use the 'md' breakpoint (900px default) to switch between mobile and desktop layout | ||
| const isMobile = useMediaQuery(theme.breakpoints.down('md')); | ||
|
|
||
| // isSheetVisible is independently owned by DashboardLayout: | ||
| // - resets to true whenever Edit Mode (isSidebarOpen) transitions OFF → ON | ||
| // - can be set to false by the user dismissing the sheet (FAB appears instead) | ||
| // - set to false when Edit Mode turns OFF | ||
| // This two-dimension model prevents the sheet from re-opening on every | ||
| // isSidebarOpen change after the user has intentionally minimized it. | ||
| const [isSheetVisible, setIsSheetVisible] = useState(isSidebarOpen); | ||
| const prevIsSidebarOpen = useRef(isSidebarOpen); | ||
|
|
||
| useEffect(() => { | ||
| if (isSidebarOpen && !prevIsSidebarOpen.current) { | ||
| // Edit Mode just turned ON → pop the sheet open | ||
| setIsSheetVisible(true); | ||
| } | ||
| if (!isSidebarOpen) { | ||
| // Edit Mode turned OFF → close the sheet and hide the FAB | ||
| setIsSheetVisible(false); | ||
| } | ||
| prevIsSidebarOpen.current = isSidebarOpen; | ||
| }, [isSidebarOpen]); | ||
|
|
||
| return ( | ||
| <Box sx={{ display: 'flex', flexDirection: 'row', gap: '1rem', width: '100%' }}> | ||
| <Box sx={{ flex: 1, padding: 0, minWidth: 0 }}> | ||
| {children} | ||
| </Box> | ||
|
|
||
| {isSidebarOpen && isMobile && ( | ||
| <> | ||
| <BottomSheet | ||
| open={isSheetVisible} | ||
| onClose={() => setIsSheetVisible(false)} | ||
| title={sidebarTitle} | ||
| maxHeight="50vh" | ||
| > | ||
| {sidebarContent} | ||
| </BottomSheet> | ||
|
|
||
| {/* FAB appears when Edit Mode is active but the sheet has been minimized, | ||
| letting users rearrange the dashboard and pull the picker back up. */} | ||
| {!isSheetVisible && ( | ||
| <Fab | ||
| color="primary" | ||
| aria-label="Open Widget Picker" | ||
| onClick={() => setIsSheetVisible(true)} | ||
| sx={(fabTheme) => ({ | ||
| position: 'fixed', | ||
| bottom: 24, | ||
| right: 24, | ||
| zIndex: fabTheme.zIndex.drawer, | ||
| })} | ||
| > | ||
| <AddIcon fill={theme.palette.primary.contrastText} /> | ||
| </Fab> | ||
| )} | ||
| </> | ||
| )} | ||
|
|
||
| {isSidebarOpen && !isMobile && ( | ||
| <Box | ||
| sx={{ | ||
| width: sidebarWidth, | ||
| flexShrink: 0, | ||
| position: 'sticky', | ||
| top: sidebarTopOffset, | ||
| alignSelf: 'flex-start', | ||
| height: sidebarHeight, | ||
| maxHeight: sidebarHeight, | ||
| }} | ||
| > | ||
| {sidebarContent} | ||
| </Box> | ||
| )} | ||
| </Box> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { DashboardLayout } from './DashboardLayout'; | ||
| export type { DashboardLayoutProps } from './DashboardLayout'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import React from 'react'; | ||
| import { Box, Typography, Button, Stack } from '../../base'; | ||
| import { useTheme } from '../../theme'; | ||
|
|
||
| export interface WidgetEmptyStateProps { | ||
| /** The message to display when no data is available */ | ||
| message?: string; | ||
|
|
||
| /** Optional icon to display above the message */ | ||
| icon?: React.ReactNode; | ||
|
|
||
| /** Optional action button configuration */ | ||
| action?: { | ||
| label: string; | ||
| onClick: () => void; | ||
| }; | ||
| } | ||
|
|
||
| export const WidgetEmptyState: React.FC<WidgetEmptyStateProps> = ({ | ||
| message = 'No data available', | ||
| icon, | ||
| action, | ||
| }) => { | ||
| const theme = useTheme(); | ||
|
|
||
| return ( | ||
| // Outer container is a plain presentational box — role="status" is scoped | ||
| // only to the message Typography below so interactive descendants (icon, Button) | ||
| // are not degraded by the live-region semantics. | ||
| <Box | ||
| sx={{ | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| height: '100%', | ||
| width: '100%', | ||
| minHeight: '120px', | ||
| p: 3, | ||
| }} | ||
| > | ||
| <Stack spacing={1.5} sx={{ alignItems: 'center' }}> | ||
| {icon && ( | ||
| <Box | ||
| sx={{ | ||
| color: theme.palette.text.secondary, | ||
| opacity: 0.6, | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| '& svg': { | ||
| width: 48, | ||
| height: 48, | ||
| }, | ||
| }} | ||
| > | ||
| {icon} | ||
| </Box> | ||
| )} | ||
| {/* role="status" + aria-live scoped only to the message text, not the | ||
| interactive siblings — per ARIA spec, live regions must not contain | ||
| interactive elements or AT may hide/degrade their semantics. */} | ||
| <Typography | ||
| role="status" | ||
| aria-live="polite" | ||
| variant="body2" | ||
| sx={{ | ||
| color: theme.palette.text.secondary, | ||
| textAlign: 'center', | ||
| maxWidth: '280px', | ||
| }} | ||
| > | ||
| {message} | ||
| </Typography> | ||
| {action && ( | ||
| <Button | ||
| variant="outlined" | ||
| size="small" | ||
| onClick={action.onClick} | ||
| sx={{ mt: 0.5 }} | ||
| > | ||
| {action.label} | ||
| </Button> | ||
| )} | ||
| </Stack> | ||
| </Box> | ||
| ); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { WidgetEmptyState } from './WidgetEmptyState'; | ||
| export type { WidgetEmptyStateProps } from './WidgetEmptyState'; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.