Skip to content

Fix mobile dashboard layout: separate metric grid from full-width widgets - #569

Merged
hotlong merged 3 commits into
mainfrom
copilot/fix-mobile-crm-dashboard-issues
Feb 18, 2026
Merged

Fix mobile dashboard layout: separate metric grid from full-width widgets#569
hotlong merged 3 commits into
mainfrom
copilot/fix-mobile-crm-dashboard-issues

Conversation

Copilot AI commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Mobile dashboard rendered all widgets in a horizontal scroll carousel with fixed w-[85vw] width, causing stat cards to overflow and charts to be unreadable. Header was cluttered with theme/language controls.

Changes

DashboardRenderer.tsx

  • Mobile threshold: 640px768px
  • Split widget rendering by type:
    • Metrics: grid grid-cols-2 gap-3
    • Charts/tables: flex flex-col gap-4 (full-width)
  • Added px-4 container padding
  • Removed w-[85vw] fixed width and horizontal scroll snap

DashboardView.tsx

  • Mobile padding: p-4p-0 (DashboardRenderer handles its own padding)

AppHeader.tsx

  • Hidden theme toggle and language switcher on mobile via hidden sm:flex

Implementation

// Before: All widgets in horizontal carousel
<div className="flex overflow-x-auto snap-x snap-mandatory">
  {widgets.map(w => <div className="w-[85vw] snap-center">{w}</div>)}
</div>

// After: Type-aware mobile layout
const metricWidgets = widgets.filter(w => w.type === 'metric');
const otherWidgets = widgets.filter(w => w.type !== 'metric');

<div className="flex flex-col gap-4 px-4">
  <div className="grid grid-cols-2 gap-3">{metricWidgets}</div>
  <div className="flex flex-col gap-4">{otherWidgets}</div>
</div>

Desktop layout (≥768px) unchanged.

Original prompt

问题描述

手机端 CRM Dashboard 存在以下样式问题(如截图所示):

image1

从截图可以看出:

  1. Stat 卡片被截断:第一张 "Total Revenue" 卡片右侧溢出屏幕,且旁边的卡片只显示了一小条边缘,说明横向滚动容器和卡片宽度设置存在问题
  2. 大量空白区域:Dashboard 主体内容(图表、表格等 widgets)没有正确渲染,或者因为移动端布局问题导致内容区完全空白
  3. 顶部导航栏内容过多Crm Dashboard 标题、sidebar trigger、搜索、主题切换等图标挤在一行

需要修复的文件

1. packages/plugin-dashboard/src/DashboardRenderer.tsx

当前移动端逻辑的问题:

// 当前代码 - 移动端检测阈值是 640px
const checkMobile = () => setIsMobile(window.innerWidth < 640);

// 移动端渲染:横向滚动卡片轮播
if (isMobile) {
  return (
    <div ref={ref} className={cn("flex flex-col", className)} {...props}>
      {refreshButton}
      <div
        className="flex overflow-x-auto snap-x snap-mandatory gap-3 pb-4 [-webkit-overflow-scrolling:touch]"
        style={{ scrollPaddingLeft: '0.75rem' }}
      >
        {schema.widgets?.map(...renderWidget...)}
      </div>
    </div>
  );
}

问题分析:

  • 移动端卡片宽度为 w-[85vw],但容器没有 px padding,导致第一张卡片从最左边开始,与屏幕边缘无间距,视觉上被截断
  • snap-x 轮播中所有 widgets(包括图表、metric cards)都变成了横向滚动卡片,导致图表等大型 widget 无法在可见区域内正常展示
  • metric 类型的 self-contained widget 和 Card 型 widget 应该分别处理:metric card 适合 2 列网格,图表等大型 widget 应该全宽显示

修复方案:

// 移动端应该分两区渲染:
// 1. Metric cards -> 2列网格 (grid grid-cols-2 gap-3)
// 2. 其他 widget (chart, table等) -> 全宽垂直堆叠

// 移动端完整修复
if (isMobile) {
  const metricWidgets = schema.widgets?.filter(w => w.type === 'metric') || [];
  const otherWidgets = schema.widgets?.filter(w => w.type !== 'metric') || [];
  
  return (
    <div ref={ref} className={cn("flex flex-col gap-4 px-4", className)} {...props}>
      {refreshButton}
      {/* Metric cards: 2列网格 */}
      {metricWidgets.length > 0 && (
        <div className="grid grid-cols-2 gap-3">
          {metricWidgets.map((widget, index) => renderMobileMetricWidget(widget, index))}
        </div>
      )}
      {/* 其他 widgets: 全宽垂直堆叠 */}
      {otherWidgets.map((widget, index) => renderMobileWidget(widget, index))}
    </div>
  );
}

具体修改要点:

  1. 移动端 metric cards 改为 grid grid-cols-2 gap-3,不再使用横向滚动
  2. 移动端图表/表格等非 metric widget 改为全宽垂直堆叠 (flex flex-col gap-4)
  3. 移��端容器加 px-4 padding,确保内容不贴边
  4. 移除 w-[85vw] 的固定宽度,改用 w-full
  5. 检测阈值从 640 调整至 768,更接近标准移动设备宽度

2. apps/console/src/components/DashboardView.tsx

当前代码:

<div className="flex-1 overflow-auto p-4 sm:p-6">
  <DashboardRenderer schema={dashboard} dataSource={dataSource} />
</div>

问题: 在移动端 p-4 的 padding 会加在 DashboardRenderer 外面,但 DashboardRenderer 内部的移动端容器也有自己的 px padding,造成双重 padding。

修复方案:

// 移动端去掉外层 padding,让 DashboardRenderer 自己控制
<div className="flex-1 overflow-auto p-0 sm:p-6">
  <DashboardRenderer schema={dashboard} dataSource={dataSource} />
</div>

或者在 DashboardRenderer 移动端模式下不加 px-4,而由外层容器统一控制。

3. apps/console/src/components/AppHeader.tsx

从截图可见,移动端顶部 header 显示了大量图标(sidebar toggle、页面 title、搜索、主题、语言切换等),在小屏幕上显得拥挤。

检查 AppHeader.tsx 中搜索、主题切换、语言切换等按钮在小屏幕时是否需要隐藏��折叠到菜单中:

  • 搜索按钮:保留(重要功能)
  • 主题切换按钮:在移动端可以隐藏(hidden sm:flex
  • 语言切换按钮:在移动端可以隐藏(hidden sm:flex

验收标准

修复后,移动端 CRM Dashboard 应该:

  1. ✅ Metric stat cards 以 2 列网格形式展示,每张卡片完整可见,无截断
  2. ✅ 图表、表格等 widgets 以全宽形式垂直堆叠展示
  3. ✅ 内容区域有合适的左右 padding(16px),不贴边也不双重 padding
  4. ✅ 所有内容正常渲染,无大面积空白
  5. ✅ 顶部 header 在移动端不过度拥挤

技术约束

  • 遵循 @objectstack/spec 协议规范
  • 使用 Tailwind CSS 响应式类
  • 不引入新的依赖
  • 保持桌面端(sm 及以上)布局不变

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel

vercel Bot commented Feb 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 18, 2026 1:00am
objectui-demo Error Error Feb 18, 2026 1:00am
objectui-storybook Ready Ready Preview, Comment Feb 18, 2026 1:00am

Request Review

Copilot AI and others added 2 commits February 18, 2026 00:40
…roper padding

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix mobile CRM dashboard styling issues Fix mobile dashboard layout: separate metric grid from full-width widgets Feb 18, 2026
Copilot AI requested a review from hotlong February 18, 2026 00:47
@hotlong
hotlong marked this pull request as ready for review February 18, 2026 00:48
Copilot AI review requested due to automatic review settings February 18, 2026 00:48
@hotlong
hotlong merged commit f16b4bf into main Feb 18, 2026
7 of 8 checks passed

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.

Pull request overview

This PR fixes critical mobile dashboard layout issues by replacing the broken horizontal scroll carousel with a responsive 2-column grid for metric cards and full-width stacking for charts/tables. The changes improve mobile UX by eliminating card overflow and making all content properly visible on small screens.

Changes:

  • Updated DashboardRenderer mobile threshold from 640px to 768px and split widget rendering by type
  • Removed double padding in DashboardView on mobile by changing p-4 to p-0
  • Hid theme toggle and language switcher on mobile in AppHeader to reduce clutter

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/plugin-dashboard/src/DashboardRenderer.tsx Changed mobile threshold to 768px, split metric widgets (2-col grid) from other widgets (full-width stack), removed horizontal scroll, added px-4 container padding
packages/plugin-dashboard/src/tests/DashboardRenderer.mobile.test.tsx Added comprehensive test coverage for mobile layout with 5 test cases covering threshold detection and widget separation
apps/console/src/components/DashboardView.tsx Changed mobile padding from p-4 to p-0 to avoid double padding with DashboardRenderer's internal px-4
apps/console/src/components/AppHeader.tsx Added hidden sm:flex to theme toggle and language switcher to reduce mobile header clutter

Comment on lines +183 to +190
{metricWidgets.map((widget: DashboardWidgetSchema, index: number) => renderWidget(widget, index))}
</div>
)}

{/* Other widgets (charts, tables): full-width vertical stack */}
{otherWidgets.length > 0 && (
<div className="flex flex-col gap-4">
{otherWidgets.map((widget: DashboardWidgetSchema, index: number) => renderWidget(widget, index, true))}

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

Potential React key collision in mobile layout. When widgets are split into metricWidgets and otherWidgets arrays and then mapped separately, the index parameter passed to renderWidget is the index within each filtered array (0, 1, 2...), not the original index from schema.widgets. If widgets lack explicit id or title fields, this creates duplicate keys like widget-0, widget-1 appearing in both metric and non-metric sections. To fix this, either pass the original widget index from schema.widgets, or prefix the fallback key with the widget type (e.g., metric-widget-0 vs other-widget-0).

Copilot uses AI. Check for mistakes.

<div className="flex-1 overflow-hidden flex flex-col sm:flex-row relative">
<div className="flex-1 overflow-auto p-4 sm:p-6">
<div className="flex-1 overflow-auto p-0 sm:p-6">

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

Padding inconsistency between 640px and 768px. DashboardView applies sm:p-6 (24px padding) at 640px+, while DashboardRenderer applies px-4 (16px padding) in mobile mode which extends up to 767px. This creates double horizontal padding (40px total) in the 640-767px range. The DashboardView should use md:p-6 instead of sm:p-6 to align with DashboardRenderer's 768px mobile threshold, or both should consistently use the same breakpoint.

Suggested change
<div className="flex-1 overflow-auto p-0 sm:p-6">
<div className="flex-1 overflow-auto p-0 md:p-6">

Copilot uses AI. Check for mistakes.
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.

3 participants