Skip to content

[UI] Add DashboardLayout and WidgetPicker components - #1706

Open
NSTKrishna wants to merge 15 commits into
layer5io:masterfrom
NSTKrishna:feat/dashboard-layout-mobile-responsive
Open

[UI] Add DashboardLayout and WidgetPicker components#1706
NSTKrishna wants to merge 15 commits into
layer5io:masterfrom
NSTKrishna:feat/dashboard-layout-mobile-responsive

Conversation

@NSTKrishna

@NSTKrishna NSTKrishna commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Description

This PR introduces three new reusable components to @sistent/sistent to support responsive, widget-based dashboard layouts across Meshery and Meshery Cloud, and structurally integrates a standardized empty state universally across both ecosystems.

1. DashboardLayout
A layout wrapper that manages the main content area and a collapsible sidebar for widget configuration.

  • Responsive Design: On desktop (md+), the sidebar is rendered as a sticky side panel. On mobile (below md), it becomes a BottomSheet (Dialog-based) that slides up from the bottom.
  • Minimize-while-Editing UX: The mobile sheet can be dismissed independently of Edit Mode. When minimized, a Floating Action Button (FAB) appears so the user can rearrange the dashboard freely and pull the picker back up without exiting Edit Mode. The FAB disappears when Edit Mode is turned off.
  • Correct state model: isSheetVisible is owned by DashboardLayout and is independent from isSidebarOpen (Edit Mode). It resets true only when Edit Mode transitions off→on, preventing desyncs. All MUI imports go through ../../base (no direct @mui/material imports).
  • Customizable: Accepts props for sidebarWidth, sidebarTopOffset, and sidebarHeight for easy integration with different navigation structures.

2. WidgetPicker
A component for displaying and adding widgets to a layout.

  • Clean Separation: Takes a list of widgetsToAdd and an onAddWidget callback, keeping it completely decoupled from domain logic.
  • Configurable Header: Supports custom background and text colors for its header, and an optional onClose callback.
  • Custom Scrollbar: Includes a customized, slim scrollbar for a polished look.
  • Empty State: Gracefully handles the case where all widgets have been added.

3. WidgetEmptyState
A standardized centered empty-state component, now structurally embedded into Sistent's core data-display components.

  • ARIA live region: Uses role="status" + aria-live="polite" for screen reader compatibility.
  • Composable: Accepts an optional icon, message, and action button.
  • ResponsiveDataTable: Intercepts string-based empty messages and safely wraps them in WidgetEmptyState, while strictly adhering to MUIDataTableOptions typing (noMatch: string | React.ReactNode).
  • PlainCard: Natively renders the empty state when the resources array is empty.

Changes Made

  • Added src/custom/DashboardLayout/DashboardLayout.tsx and src/custom/DashboardLayout/index.tsx.
  • Added src/custom/WidgetPicker/WidgetPicker.tsx and src/custom/WidgetPicker/index.tsx.
  • Added src/custom/WidgetEmptyState/WidgetEmptyState.tsx with index.tsx as a clean re-export barrel.
  • Exported all new components via named exports in src/custom/index.ts and src/index.tsx (explicit root re-exports per the rollup-plugin-dts barrel-drop requirement).
  • Integrated WidgetEmptyState into ResponsiveDataTable.tsx and PlainCard.tsx.
  • Fixed: Fab imported from ../../base; SxProps<Theme> uses import type; removed duplicate exports from src/custom/index.tsx.
  • Replaced the useEffect state mirror with a useRef-guarded two-dimension state model for the mobile sheet.

Related Issues

  • Extracted to resolve code duplication between meshery and meshery-cloud dashboards and properly support mobile views.
  • Ensures uniform empty state UI across all tables and lists in both dashboards without requiring consumer-side widget rewrites.
  • Future: once #1653 is stable, the internal BottomSheet usage in DashboardLayout will be reviewed for alignment.

Testing Performed

  • npm run build — clean, all three symbols (DashboardLayout, WidgetPicker, WidgetEmptyState) present in dist/index.d.ts.
  • npm run lint — no errors.
  • 511/511 jest tests pass, including optionalPeerDependencies and publishedTypeSurfaceDependencies guards.
  • Validated via direct node_modules local link in both meshery and meshery-cloud development environments.
  • Confirmed responsive behavior switches correctly at the md breakpoint.
  • Verified minimize → FAB → reopen flow works correctly on mobile.
  • Verified that empty tables and lists safely trigger WidgetEmptyState without errors.

Demo

Screen.Recording.2026-07-13.at.6.43.33.AM.mov
Screen.Recording.2026-07-13.at.10.29.58.AM.mov

Notes for Reviewers

This PR fixes #

Signed commits

  • Yes, I signed my commits.

Summary by CodeRabbit

  • New Features

    • Added a responsive dashboard layout with configurable sidebars and mobile bottom-sheet behavior.
    • Added a widget picker with searchable-style cards, thumbnails, add actions, and empty-list messaging.
    • Added a reusable empty-state component with customizable messages, icons, and actions.
    • Exposed the new components through the public package exports.
  • Bug Fixes

    • Improved empty-state displays for resource lists and data tables.
    • Preserved custom data-table messages while providing a default “No data available” message.

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 <krishnagehlot936@gmail.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two new custom components: DashboardLayout, which provides a responsive layout with a sticky sidebar on desktop and a swipeable bottom drawer on mobile, and WidgetPicker, which displays a list of available widgets to add. The feedback highlights a critical state desync issue in DashboardLayout where the onClose callback is not triggered when the mobile drawer is closed. Additionally, improvements are suggested for WidgetPicker to enhance type safety by replacing an any type with a more specific type, and to correct a discrepancy in the JSDoc documentation regarding the default header background color.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/custom/DashboardLayout/index.tsx Outdated
Comment thread src/custom/DashboardLayout/index.tsx Outdated
Comment thread src/custom/WidgetPicker/WidgetPicker.tsx Outdated
Comment thread src/custom/WidgetPicker/WidgetPicker.tsx Outdated
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 <krishnagehlot936@gmail.com>
@NSTKrishna
NSTKrishna requested a review from Copilot July 13, 2026 01:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@NSTKrishna
NSTKrishna requested a review from rishiraj38 July 13, 2026 01:48
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 <krishnagehlot936@gmail.com>
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 <krishnagehlot936@gmail.com>
Drops the unused `onClose` prop from `DashboardLayout`'s public props and implementation, simplifying the component API.

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>

@Katotodan Katotodan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NSTKrishna, thanks for the work.
Can you consider adding some accessibility features to clickable elements of this PR?

Comment thread src/custom/DashboardLayout/index.tsx Outdated
@NSTKrishna
NSTKrishna requested review from banana-three-join and removed request for KhushamBansal July 23, 2026 16:33

@banana-three-join banana-three-join left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the requested changes, it looks good to me!

Comment thread src/custom/DashboardLayout/index.tsx Outdated
Comment thread src/custom/WidgetPicker/index.tsx Outdated
Comment thread src/custom/DashboardLayout/index.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

DashboardLayout, WidgetPicker, and WidgetEmptyState are added. PlainCard and ResponsiveDataTable use the shared empty-state component. Custom and package barrel exports expose the new components and their public types.

Changes

Custom dashboard components

Layer / File(s) Summary
Responsive dashboard layout
src/custom/DashboardLayout/...
DashboardLayout renders mobile sidebar content through BottomSheet and desktop content in a sticky sidebar.
Widget picker behavior
src/custom/WidgetPicker/...
WidgetPicker defines widget types, renders widget cards and empty content, and handles add and close callbacks.
Widget empty-state rendering
src/custom/WidgetEmptyState/...
WidgetEmptyState renders configurable messages, icons, and action buttons.
Empty-state integrations
src/custom/DashboardWidgets/PlainCard.tsx, src/custom/ResponsiveDataTable.tsx
PlainCard and ResponsiveDataTable render WidgetEmptyState for empty content. ResponsiveDataTable now types options as MUIDataTableOptions.
Public component exports
src/custom/..., src/index.tsx
Barrel modules export the new components and their related types.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Parent
  participant DashboardLayout
  participant BottomSheet
  participant Fab
  Parent->>DashboardLayout: pass sidebar state and content
  DashboardLayout->>DashboardLayout: evaluate mobile breakpoint
  DashboardLayout->>BottomSheet: render mobile sidebar
  BottomSheet->>DashboardLayout: report dismissal
  DashboardLayout->>Fab: show reopen control
  Fab->>BottomSheet: reopen sidebar
Loading

Suggested reviewers: rishiraj38

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two primary components added by the pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Introduces a new `WidgetEmptyState` component with optional icon and action button support, and exports it through both `custom` and root barrels. Refactors `DashboardLayout` into its own file with explicit type/value re-exports and updates mobile sidebar rendering so the drawer is only mounted when the sidebar is open. Also switches `WidgetPicker` to explicit named/type exports for a clearer public API surface.

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/custom/DashboardLayout/index.tsx`:
- Around line 104-110: Update the puller Box styling in DashboardLayout to
replace the hard-coded light/dark background colors with the semantic theme
token theme.palette.divider, preserving the existing dimensions and border
radius.
- Around line 82-102: The minimized drawer trigger in DashboardLayout must be
keyboard-operable and accessible. Replace the interactive Box behavior with a
focusable labelled button (or Sistent button) rendered outside the drawer when
minimized, reuse the existing toggle logic, and expose isMobileDrawerOpen
through the button’s expanded state.

In `@src/custom/index.tsx`:
- Line 193: Update the custom barrel in src/custom/index.tsx to re-export
DashboardLayout alongside WidgetPicker, using the existing DashboardLayout
export from the custom domain index; preserve the current exports and ensure the
symbol is available through the feature-local, domain, and root barrel chain.

In `@src/custom/WidgetPicker/WidgetPicker.tsx`:
- Around line 40-52: Update the `WidgetPicker` component’s `Box` `sx` prop to
compose the base styles and `containerSx` as an array, preserving callback and
array-based `SxProps` values instead of spreading `containerSx` into the base
object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f6f90140-acf6-4123-bd39-b29179641844

📥 Commits

Reviewing files that changed from the base of the PR and between cc1436e and 6eec363.

📒 Files selected for processing (6)
  • src/custom/DashboardLayout/index.tsx
  • src/custom/WidgetPicker/WidgetPicker.tsx
  • src/custom/WidgetPicker/index.tsx
  • src/custom/index.ts
  • src/custom/index.tsx
  • src/index.tsx

Comment thread src/custom/DashboardLayout/index.tsx Outdated
Comment thread src/custom/DashboardLayout/index.tsx Outdated
Comment thread src/custom/index.tsx Outdated
Comment thread src/custom/WidgetPicker/WidgetPicker.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/custom/DashboardLayout/DashboardLayout.tsx`:
- Around line 25-36: Remove the unused onClose prop from DashboardLayoutProps,
delete its callback documentation, and stop destructuring it in DashboardLayout.
Keep the component’s internal close behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 361b392d-3dc7-4524-bcbb-7fac97080c43

📥 Commits

Reviewing files that changed from the base of the PR and between 6eec363 and d24fc97.

📒 Files selected for processing (6)
  • src/custom/DashboardLayout/DashboardLayout.tsx
  • src/custom/DashboardLayout/index.tsx
  • src/custom/WidgetEmptyState/index.tsx
  • src/custom/WidgetPicker/index.tsx
  • src/custom/index.ts
  • src/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/custom/index.ts
  • src/index.tsx

Comment thread src/custom/DashboardLayout/DashboardLayout.tsx Outdated
This updates several dashboard-related components for better UX and consistency: DashboardLayout drops an unused `onClose` prop, adds keyboard-accessible sidebar toggle behavior, and uses theme divider color for the drawer handle. PlainCard and ResponsiveDataTable now show `WidgetEmptyState` when content is empty (including table `noMatch` text), and WidgetPicker now merges `containerSx` safely by supporting array/object forms. It also exports `DashboardLayout` from the custom barrel.

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/custom/ResponsiveDataTable.tsx`:
- Around line 167-178: Update the options transformation around bodyTextLabels
and updatedOptions to always assign WidgetEmptyState for noMatch, using the
existing string value when provided and a fallback message otherwise. Build a
fresh body object with the resolved noMatch value instead of mutating
bodyTextLabels or any caller-owned options object.
- Around line 163-164: Update the textLabels declaration in ResponsiveDataTable
to use MUIDataTableOptions or a narrow local type whose body.noMatch accepts
string | React.ReactNode. Remove both any casts and the eslint suppression while
preserving the existing fallback to an empty labels object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a9896c3-7d47-4e3c-a46f-b9137e960ed3

📥 Commits

Reviewing files that changed from the base of the PR and between d24fc97 and 80ee06b.

📒 Files selected for processing (5)
  • src/custom/DashboardLayout/DashboardLayout.tsx
  • src/custom/DashboardWidgets/PlainCard.tsx
  • src/custom/ResponsiveDataTable.tsx
  • src/custom/WidgetPicker/WidgetPicker.tsx
  • src/custom/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/custom/index.tsx
  • src/custom/WidgetPicker/WidgetPicker.tsx

Comment thread src/custom/ResponsiveDataTable.tsx Outdated
Comment thread src/custom/ResponsiveDataTable.tsx Outdated
NSTKrishna and others added 2 commits July 28, 2026 03:06
Use `MUIDataTableOptions` for `ResponsiveDataTableProps.options` instead of `object`, removing `any` casts around `textLabels`. The no-match handling now consistently renders `WidgetEmptyState`, using the provided `body.noMatch` string when available and falling back to `"No data available"`.

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>
@NSTKrishna

Copy link
Copy Markdown
Contributor Author

Once this is merged #1653 , we should replace the existing SwipeableDrawer in DashboardLayout with the new BottomSheet

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>
- Import Fab from '../../base' instead of '@mui/material' (optional-peer guard)
- Import SxProps/Theme as 'import type' in WidgetPicker (erased at runtime)
- Replace useEffect state mirror with useRef-guarded two-dimension model:
  isSheetVisible is owned by DashboardLayout and resets to true only when
  isSidebarOpen transitions OFF→ON, allowing users to minimize the sheet
  while Edit Mode stays active (FAB appears as the re-open affordance)
- Move WidgetEmptyState body from index.tsx to WidgetEmptyState.tsx;
  index.tsx becomes a clean re-export barrel (consistent with other components)
- Remove duplicate WidgetPicker/DashboardLayout exports from index.tsx
  (already covered by index.ts)

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/custom/DashboardLayout/DashboardLayout.tsx`:
- Around line 69-75: Add a sidebarTitle prop to the surrounding DashboardLayout
component and pass it as the title prop to BottomSheet, ensuring the mobile
dialog receives an accessible name while preserving the existing visibility and
close behavior.

In `@src/custom/WidgetEmptyState/WidgetEmptyState.tsx`:
- Around line 27-29: Update WidgetEmptyState so the message alone is wrapped
with role="status" and aria-live="polite"; keep the optional icon and action
Button outside that live-region element while preserving their existing
rendering and behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 47a5cca2-c87f-4280-8e42-178a12b2bdaf

📥 Commits

Reviewing files that changed from the base of the PR and between 88e7815 and 67f627f.

📒 Files selected for processing (5)
  • src/custom/DashboardLayout/DashboardLayout.tsx
  • src/custom/WidgetEmptyState/WidgetEmptyState.tsx
  • src/custom/WidgetEmptyState/index.tsx
  • src/custom/WidgetPicker/WidgetPicker.tsx
  • src/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/custom/WidgetPicker/WidgetPicker.tsx
  • src/index.tsx

Comment thread src/custom/DashboardLayout/DashboardLayout.tsx
Comment thread src/custom/WidgetEmptyState/WidgetEmptyState.tsx Outdated
- DashboardLayout: add sidebarTitle prop (default 'Widget Picker') and
  pass it to BottomSheet so the Dialog gets an aria-labelledby label;
  without a title the dialog is unlabelled for screen readers
- WidgetEmptyState: narrow role=status + aria-live=polite to the message
  Typography only; the outer Box and Button siblings are removed from the
  live region to prevent AT from hiding or degrading interactive semantics

Signed-off-by: NSTKrishna <krishnagehlot936@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants