Skip to content

refactor(console/dashboard): replace Card with HeroUI Pro Widget#3

Merged
dingyi merged 6 commits into
feature/console-template-stylefrom
feature/widget-dashboard-cards-clean
Apr 26, 2026
Merged

refactor(console/dashboard): replace Card with HeroUI Pro Widget#3
dingyi merged 6 commits into
feature/console-template-stylefrom
feature/widget-dashboard-cards-clean

Conversation

@dingyi

@dingyi dingyi commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

Replace every panel on /console (the dashboard) with HeroUI Pro's
Widget primitive (@heroui-pro/react) so each surface gets the
characteristic gray outer shell + elevated white content area + subtle
inner shadow defined by widget.css.

Stacks on top of #1 (feature/console-template-style). PR base is
set to that branch so the diff shows only the 6 Widget-related
commits. Once #1 merges, this PR auto-rebases onto main.

Components migrated to Widget

  • StatsCards — 4 KPI tiles (账户数据 / 使用统计 / 资源消耗 / 性能指标).
  • ChartsPanel — 模型数据分析 chart panel with the inline Tabs.List strip.
  • ApiInfoPanel — API信息 list with speedtest/jump action rows.
  • AnnouncementsPanel — 系统公告 timeline + status legend
    (Widget.Legend / Widget.LegendItem).
  • FaqPanel — 常见问答 accordion + EmptyState.
  • UptimePanel — 服务可用性 monitor list + status legend in
    Widget.Footer.

Polish included in this PR

  • Title typography: createSectionTitle in helpers/dashboard.jsx
    now wraps text in a text-sm font-semibold text-foreground span so
    the four StatsCards titles match Widget.Title typography across
    every other panel.
  • Header height: every simple Widget.Header uses h-12 so the
    strip matches the AnnouncementsPanel header (which already had to be
    taller for the chip + legend row). Multi-row headers
    (ChartsPanel, AnnouncementsPanel) keep h-auto min-h-12 … flex-col xl:flex-row so the trailing tabs / legend can wrap.
  • Empty state: every empty state now uses EmptyState size='sm'
    with EmptyState.Media variant='icon', replacing the bespoke
    96×96 illustration container override. FaqPanel's custom div +
    HelpCircle 42px is also routed through EmptyState. ApiInfoPanel
    wraps its empty state in flex flex-1 min-h-80 items-center justify-center so it stays vertically centered inside the
    taller-than-its-content grid row.
  • Responsive grid: StatsCards uses grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 (4-col only at ≥1280px) so each KPI tile has
    ~360px at common 1024–1280 widths. ChartsPanel + ApiInfoPanel
    switch to lg:grid-cols-3 xl:grid-cols-4 with the chart panel
    spanning 2/3, giving the API column ~256px at lg. The bottom info
    row uses lg:grid-cols-2 xl:grid-cols-4 so 系统公告 takes the full
    row at lg with FAQ + Uptime side-by-side beneath.
  • ChartsPanel tabs: keeps title and Tabs.List on separate rows
    through lg, only inlining at xl. The tab list is wrapped in an
    overflow-x-auto container with whitespace-nowrap per
    Tabs.Tab, and tab labels drop to text-xs (12px) so the 6 chart
    tabs no longer collapse into vertical CJK character columns.
  • CTA button: 当前余额 → 充值 button now uses
    variant='primary' (accent fill + accent-foreground text) rather
    than variant='secondary' (default surface + accent-soft text)
    so it reads as a proper recharge CTA.
  • Header icon buttons: DashboardHeader's search + refresh
    triggers switch to variant='ghost' so they read as bare icons,
    with the surrounding pill only fading in on hover/press.
  • Uptime refresh: UptimePanel's refresh trigger gets !h-6 !w-6 !min-w-0 + [&_svg]:!size-3 to land at 24×24 with a 12px glyph,
    proportionate to the compact 48px panel header. HeroUI Button only
    ships sm/md/lg, so the className escape hatch is the
    cleanest sub-sm path.

widget.css is already pulled in via @import '@heroui-pro/react/css';
in web/src/index.css, so no extra CSS plumbing was required.

Test plan

  • bun run build — passes.
  • bunx prettier --check + bunx eslint — pass on all touched
    files.
  • Manual screenshot QA at /console: stats cards, chart panel,
    API info panel, announcements panel, FAQ panel, uptime panel
    all render with the new Widget surface, consistent header
    heights, consistent empty state icons, and a clean 2-col KPI
    grid + horizontally scrollable chart tabs at the typical
    1024px viewport.

This PR was generated automatically by Cursor

Made with Cursor

dingyi added 6 commits April 27, 2026 00:24
Migrate every panel on /console (StatsCards, ChartsPanel, ApiInfoPanel,
AnnouncementsPanel, FaqPanel, UptimePanel) from `@heroui/react` Card to
the new `Widget` primitive in `@heroui-pro/react` so each surface gets
the gray outer shell + elevated white content area + subtle inner
shadow defined by widget.css.

Notable adjustments while wiring this up:

- StatsCards: Widget.Content padding tightened from p-4 to p-3 and the
  KPI value font dropped from text-lg to text-base, with min-w-0 on the
  label cluster and shrink-0 on the trailing button/sparkline so the
  rendered "充值" button no longer clips against "$200.00" inside the
  narrower Widget content area.
- ChartsPanel + AnnouncementsPanel: override Widget.Header from the
  default fixed h-8 to h-auto min-h-12 flex-col … lg:flex-row so the
  inline Tabs strip / legend has room to wrap on narrow viewports.
- AnnouncementsPanel + UptimePanel: adopt Widget.Legend / Widget.LegendItem
  for status legends; the announcement panel maps the existing
  grey/blue/green/orange/red swatch keys to concrete CSS colors via a
  small LEGEND_COLOR_MAP because Widget.LegendItem requires a CSS color
  string.
- All title clusters use a whitespace-nowrap flex wrapper around the
  lucide icon and Widget.Title so CJK title characters do not stack
  vertically when the grid column is narrow.

widget.css is already pulled in via `@import '@heroui-pro/react/css';`
in web/src/index.css, so no extra CSS plumbing is required.

Verified: `bun run build` passes; `bunx prettier --check` and
`bunx eslint` pass for all touched files; manual screenshot QA
confirms the dashboard panels render with the new Widget surface.

Made-with: Cursor
Address visual polish feedback on the Widget rebuild:

- Title size: createSectionTitle in helpers/dashboard.jsx now wraps the
  text in a `text-sm font-semibold text-foreground` span (matching the
  widget__title CSS) so the four StatsCards titles (账户数据 / 使用统计 /
  资源消耗 / 性能指标) match the Widget.Title used by every other panel.
  Also adds whitespace-nowrap + shrink-0 on the icon to prevent CJK
  characters from stacking.

- Header height: every simple Widget.Header now uses `h-12` so the
  header strip matches the AnnouncementsPanel header height (which the
  user pointed out as the correct one). The complex headers
  (ChartsPanel, AnnouncementsPanel) keep their `h-auto min-h-12 …
  flex-col` rule for content that can wrap to a second row.

- Empty state: every empty state now uses HeroUI Pro's `EmptyState`
  with `size='sm'` and `EmptyState.Media variant='icon'`, dropping the
  custom 96×96 illustration container override. FaqPanel previously
  used a bespoke div + HelpCircle 42px; it now uses EmptyState too, so
  the four panels (ApiInfoPanel / AnnouncementsPanel / FaqPanel /
  UptimePanel) share the same circular muted icon background and
  consistent typography.

- Responsive grid: StatsCards collapses to 4-col only at xl (≥1280px)
  via `grid-cols-1 sm:grid-cols-2 xl:grid-cols-4` so each KPI card has
  ~360px at common 1024–1280 widths instead of ~150px (which was
  forcing labels like "统计次数" to wrap CJK character per line).
  ChartsPanel + ApiInfoPanel switch to `lg:grid-cols-3 xl:grid-cols-4`
  with the chart panel spanning 2/3, giving the API info column a
  more usable ~256px width at lg. The bottom info panels grid switches
  to `lg:grid-cols-2 xl:grid-cols-4` so 系统公告 takes the full row at
  lg with FAQ + Uptime side-by-side beneath it.

- ChartsPanel tabs strip: keeps the title and the Tabs.List on
  separate rows through lg and only inlines them at xl. The tab list
  is wrapped in an overflow-x-auto container with whitespace-nowrap
  on each Tabs.Tab so the 4–6 chart tabs no longer collapse into
  vertical CJK character columns when the chart panel narrows.

- Cleanup: drop the now-unused ILLUSTRATION_SIZE prop from
  AnnouncementsPanel / ApiInfoPanel / FaqPanel / UptimePanel and from
  the dashboard index that wired it through.

Verified: bun run build passes, prettier --check + eslint pass on all
touched files, and manual screenshot QA confirms consistent header
heights, title sizes, empty state icons, and a clean 2-col KPI grid +
horizontally scrollable tabs at the typical 1024px viewport.

Made-with: Cursor
…tons

- StatsCards: switch the 当前余额 → 充值 button from `variant='secondary'`
  (default surface fill + accent-soft text, which read as a muted green
  pill) to `variant='primary'` so it now renders with the accent fill +
  accent-foreground text expected of a recharge CTA.

- DashboardHeader: switch the 搜索条件 + 刷新 icon-only buttons from
  `variant='tertiary'` (which paints the surrounding pill at rest) to
  `variant='ghost'` so the trigger reads as a bare icon and the
  background only fades in on hover/press. Drop the gap from `gap-2`
  to `gap-1` since the buttons no longer carry their own pill.

Made-with: Cursor
The refresh trigger inside 服务可用性's compact 48px Widget.Header was
still rendering at full Button `sm` icon-only dimensions (32×32 with a
16px svg, since `.button--sm` forces svg to size-4 regardless of the
lucide `size` prop). Override to `!h-6 !w-6 !min-w-0` plus
`[&_svg]:!size-3` so the affordance reads as a 24×24 hover pill with a
12px refresh glyph — proportionate to the small panel header without
dominating the title. HeroUI Button only ships `sm`/`md`/`lg`, so a
className escape hatch is the cleanest sub-`sm` path.

Made-with: Cursor
The Tabs.List inside 模型数据分析's Widget.Header carries 4–6 entries
(消耗分布 / 调用趋势 / 调用次数分布 / 调用次数排行 / 用户消耗排行 /
用户消耗趋势). At the default 14px tab font, only the first three fit
across the chart panel at lg, forcing the rest behind the horizontal
scroll. Drop each Tabs.Tab label to text-xs (12px) so 4–5 tabs are
visible at rest while the secondary chrome stays subordinate to the
14px Widget.Title.

Made-with: Cursor
ApiInfoPanel sits in a CSS grid row alongside the much-taller
ChartsPanel, so the row stretches the API info Widget to ~24rem.
Previously the empty state branch rendered straight inside
ScrollableContainer, which collapses to its content height — leaving
the empty state pinned to the top with all the empty space below it.

Split the two render paths:

- Data path keeps the ScrollableContainer for scroll-with-fade and now
  carries `flex-1` so the scroll region claims the stretched height.
- Empty path renders into a `flex flex-1 min-h-80 items-center
  justify-center` wrapper, which propagates the row height down via the
  `flex` Widget.Content + `flex-1` child chain and centers the
  EmptyState both axes regardless of grid stretch height.

Made-with: Cursor
@dingyi
dingyi merged commit a15e35f into feature/console-template-style Apr 26, 2026
@dingyi
dingyi deleted the feature/widget-dashboard-cards-clean branch April 26, 2026 16:42
dingyi added a commit that referenced this pull request Apr 26, 2026
* feat(console): port template-dashboard styling to /console pages

Brings the visual language of the standalone @heroui-pro/template-dashboard
project into all /console routes by aligning background, typography, and
color tokens with the template:

- Strip body radial-gradient background; body now matches template's flat
  var(--background) so cards/tables sit on a uniform surface.
- DashboardHeader greeting drops to text-xl + text-foreground (matches
  template navbar title weight/size; was text-2xl text-gray-800).
- StatsCards / ApiInfoPanel / AnnouncementsPanel / FaqPanel / UptimePanel
  swap literal text-gray-* / text-slate-* / bg-gray-50 utilities for
  semantic HeroUI tokens (text-foreground, text-muted, text-primary,
  border-border, bg-surface-secondary).
- ApiInfoPanel API route label drops !font-bold for template's
  font-semibold; KPI numbers in StatsCards gain tabular-nums.

Also rolls in pre-existing in-flight HeroUI/Semi cleanup that was sitting
in the working tree, plus .gitignore entries for .playwright-mcp and
loose screenshot artifacts.

Made-with: Cursor

* refactor(sidebar): port template-dashboard Sidebar styling to console SiderBar

Brings the visual layer of the console sidebar in line with
@heroui-pro/template-dashboard's Sidebar component (without changing the
underlying PageLayout structure).

- Drop sidebar-shell glassmorphism: remove backdrop-blur, heavy box-shadow,
  and gradient background. Sidebar now uses flat bg-background +
  border-r border-border like the template.
- Add a Sidebar.Header-style block at the top: avatar + display name + role
  (super admin / admin / user), mirroring template's Kate Moore / Admin block.
- Tone down section labels: uppercase tracking-wide bold → quiet
  `text-xs font-medium text-muted` labels.
- Menu items: rounded-2xl → rounded-md, font-semibold → font-medium,
  slate-600/300 → text-muted, primary-tinted active → bg-surface-secondary
  + text-foreground.
- Nested chat items drop to font-normal, matching template's relative
  weight hierarchy.
- Collapse button: drop bordered + backdrop-blur look, switch to ghost
  variant with semantic hover tokens.

Build passes (`bun run build`). Functional behavior (routing, collapse
state, mobile drawer, chat sub-menu) is untouched.

Made-with: Cursor

* refactor(console): port template-dashboard chrome to header + sidebar

Continues aligning console chrome with @heroui-pro/template-dashboard
after the sidebar visual refresh.

Header bar (always visible):
- Drop bg-white/75 + backdrop-blur-lg glassmorphism on header element;
  use flat bg-background + border-b border-border like template's Navbar.
- Navigation: text-slate-700/200 -> text-foreground; reduce link weight
  from font-semibold to font-medium to match template.
- HeaderLogo: text-slate-950/white -> text-foreground.
- UserArea: replace bg-slate-900/[0.04] hover:bg-slate-900/[0.07] etc.
  with bg-surface-secondary / hover:bg-surface-tertiary; chevron and
  username swap slate literal colors for text-muted/foreground; menu
  popover drops backdrop-blur + bg-white/95 in favor of bg-background +
  border-border + shadow-lg.

Dashboard panels:
- DashboardHeader: drop hard bg-emerald-500 / bg-blue-500 buttons in
  favor of HeroUI's `size='sm' variant='tertiary'` to match template's
  navbar action buttons.
- useDashboardStats: drop pastel `bg-blue-50/green-50/yellow-50/indigo-50`
  card tints (which had no dark-mode adaptation) so KPI cards render on
  the default neutral surface like template's `<KPI>` cards.

Sidebar (in-flight HeroUI Pro adoption + my user header):
- SiderBar.jsx now uses the actual `<Sidebar>` / `<Sidebar.Mobile>` /
  `<Sidebar.Header>` / `<Sidebar.Group>` / `<Sidebar.MenuItem>` components
  from @heroui-pro/react, mirroring template's `dashboard-sidebar.tsx`
  exactly. Avatar + display name + role block sits inside Sidebar.Header.
- PageLayout.jsx now wraps the app in `<Sidebar.Provider>` and bridges
  the existing localStorage-backed collapse state to its open/onOpenChange
  API.
- MobileMenuButton + useHeaderBar updated to drive the new
  Sidebar.Provider open state.

`bun run build` passes (27.42s). Visual smoke test on /login + / shows
flat header with subtle border-bottom, semantic surface tokens
throughout, no glassmorphism.

Made-with: Cursor

* refactor(sidebar): move trigger to navbar, avatar to footer, drop misaligned skeleton

Three product-spec deviations from the literal template-dashboard layout:

1. Sidebar.Trigger lives in the top navbar (top-left, after the mobile
   menu button), NOT inside the sidebar itself. Mirrors the same place
   template-dashboard puts <Sidebar.Trigger /> in `dashboard-navbar.tsx`,
   which is conceptually "sidebar collapse is at the top" rather than
   bottom of the sidebar. Only renders on /console routes + non-mobile.

2. User avatar / display-name / role block moves from Sidebar.Header
   to Sidebar.Footer (bottom of the sidebar) per product preference
   (template puts it at top with Kate Moore / Admin; we want it at the
   bottom).

3. Drop the SkeletonWrapper that was wrapping <SidebarBody/>. The old
   skeleton renders bare divs sized for the previous self-implemented
   sidebar; nesting them inside heroui-pro's <Sidebar> component (which
   expects Sidebar.Content / .Footer children with its own internal
   layout) caused all menu items to misalign — items rendered flush-left
   without sidebar padding, no visible container background, and broke
   the visual structure entirely. Now the real Sidebar structure renders
   immediately; visibleSections gracefully empties while useSidebar() is
   still resolving, so the menu briefly shows nothing rather than
   misaligned placeholder bars.

Build passes. Visual smoke test on /login confirms the trigger appears at
top-left of the navbar and no misaligned bars remain.

Made-with: Cursor

* fix(sidebar): import heroui-pro stylesheet so <Sidebar> actually has CSS

The misalignment everyone saw on /console was rooted in a single missing
import. The codebase used <Sidebar> / <Sidebar.MenuItem> / <Sidebar.Group>
from @heroui-pro/react but only loaded @heroui/styles in index.css.
Without @heroui-pro/react/css the Pro components have zero styling — menu
icons stack on top of labels instead of sitting beside them, group labels
disappear, sidebar has no container width or border-right, and footer
slot renders into nothing.

Mirror the template-dashboard's globals.css layout exactly:
  @import '@heroui/styles';
  @import '@heroui-pro/react/css';

Plus two follow-on cleanups now that the stylesheet is loaded:
- Wrap the user avatar / role block as a real <Sidebar.MenuItem> inside a
  <Sidebar.Menu> within <Sidebar.Footer>. Sidebar.Footer ignores arbitrary
  children when not given a Sidebar.Menu structure.
- Skip menu items / chat sub-items with no resolvable href to silence the
  remaining `PressResponder was rendered without a pressable child`
  warnings from react-aria.

Verified locally with the Go backend running:
  - sidebar trigger □ at top-left of navbar (template parity)
  - menu items render as icon + label rows with primary-tinted active state
  - group labels (Chat / Console / Personal center / Admin) visible
  - user avatar + display name + role at the very bottom of the sidebar
  - collapse / expand via the navbar trigger works

Made-with: Cursor

* fix(console): scroll, trigger placement, and active icon color

Three follow-on UX fixes after the heroui-pro Sidebar wiring:

1. Right-side content was no longer scrollable.
   Sidebar.Provider was set to `min-h-dvh` so tall console pages grew the
   document body and the whole page (sidebar included) scrolled. Switched
   to `h-dvh overflow-hidden` so the <main> element becomes the scroll
   container and the sidebar stays sticky like template-dashboard.

2. Sidebar.Trigger now switches sides based on expand/collapse state.
   - When the sidebar is collapsed: trigger renders in the navbar's
     top-left (NavbarTrigger). Lets the user re-expand from anywhere.
   - When the sidebar is expanded: trigger renders inside the page
     content's top-left (ConsolePageTrigger), above the page heading.
     Avoids visually crowding the trigger flush against the expanded
     sidebar's right edge.
   Both wrappers read `isOpen` from `useSidebar()` (heroui-pro exposes
   the desktop expand/collapse flag as `isOpen`, NOT `open`).

3. Active menu item icon no longer tints to primary.
   `getLucideIcon` was hard-setting `color: var(--app-primary)` for
   selected items, which clashed with the menu item's own
   foreground-on-active styling. Now selected icons keep `currentColor`
   (so they inherit the deepened foreground tone of the active row) and
   bump strokeWidth from 2 to 2.5 so they read as "deepened" without
   changing tint. Also drops the scale-105 transform.

Plus index.css cleanup: remove the legacy `--sidebar-width` /
`--sidebar-width-collapsed` / `--sidebar-current-width` vars and the
`body.sidebar-collapsed` selector. Those drove the previous custom
sidebar's width and conflict with heroui-pro's internal width vars,
causing the icon-mode column to render at the wrong width.

Made-with: Cursor

* fix(sidebar): show user footer, fix trigger position, drop strokeWidth bump

Three follow-up fixes after the live console smoke test exposed missing /
out-of-spec behaviors:

1. User avatar block at the bottom was invisible.
   Root cause: heroui-pro's `.sidebar` is hard-coded `height: 100svh`,
   intended for an <AppLayout> that hoists the navbar above the
   Sidebar.Provider. Our PageLayout puts the navbar *inside* the provider,
   so the sidebar starts ~64px below the viewport top and its 100svh
   height pushes Sidebar.Footer (the avatar block) ~64px below the
   visible viewport. Override `[data-slot='sidebar'] { height: 100% }` so
   the sidebar fits its parent's available height. Footer now sticks at
   the bottom of the visible sidebar.

2. Sidebar.Trigger position must NOT change between expand/collapse.
   Reverted the conditional NavbarTrigger / ConsolePageTrigger split.
   ConsolePageTrigger now renders unconditionally above every console
   page (top-left of the content area, above the page heading), and the
   navbar no longer hosts a trigger at all.

3. Active menu item icons no longer bump strokeWidth.
   heroui-pro's sidebar.css already deepens active-item icons from
   `--muted` to `--foreground` automatically via
   `.sidebar__menu-item[data-current=true] .sidebar__menu-icon { color }`,
   so my custom `strokeWidth: 2.5` for selected items was producing icons
   visibly heavier than template-dashboard. Reverted to a uniform stroke
   of 2 for both states.

Verified locally on /console with the Go backend + dingyi user logged in:
trigger stays put through expand/collapse, avatar+role block reads
"Root User / Super Admin" at the bottom of the sidebar in both states,
active item icon matches template-dashboard's neutral (non-tinted) look.

Made-with: Cursor

* fix(sidebar): trigger relocates to sidebar top when collapsed

When the sidebar is collapsed, render <Sidebar.Trigger /> inside a
<Sidebar.Header className='pt-6 pb-3'> at the top of the sidebar so the
user can re-expand from within the icon column itself. When the sidebar
is expanded, keep the trigger above the page heading in the content area
(unchanged).

`pt-6` gives roughly 1.5rem of breathing room above the trigger so it
doesn't sit flush against the navbar's bottom border.

Renamed the heroui-pro `useSidebar` import to `useSidebarUI` inside
SiderBar.jsx to avoid colliding with our own
`hooks/common/useSidebar` (which manages module visibility).

Made-with: Cursor

* refactor(console/dashboard): replace Card with HeroUI Pro Widget (#3)

* refactor(console/dashboard): replace Card with HeroUI Pro Widget

Migrate every panel on /console (StatsCards, ChartsPanel, ApiInfoPanel,
AnnouncementsPanel, FaqPanel, UptimePanel) from `@heroui/react` Card to
the new `Widget` primitive in `@heroui-pro/react` so each surface gets
the gray outer shell + elevated white content area + subtle inner
shadow defined by widget.css.

Notable adjustments while wiring this up:

- StatsCards: Widget.Content padding tightened from p-4 to p-3 and the
  KPI value font dropped from text-lg to text-base, with min-w-0 on the
  label cluster and shrink-0 on the trailing button/sparkline so the
  rendered "充值" button no longer clips against "$200.00" inside the
  narrower Widget content area.
- ChartsPanel + AnnouncementsPanel: override Widget.Header from the
  default fixed h-8 to h-auto min-h-12 flex-col … lg:flex-row so the
  inline Tabs strip / legend has room to wrap on narrow viewports.
- AnnouncementsPanel + UptimePanel: adopt Widget.Legend / Widget.LegendItem
  for status legends; the announcement panel maps the existing
  grey/blue/green/orange/red swatch keys to concrete CSS colors via a
  small LEGEND_COLOR_MAP because Widget.LegendItem requires a CSS color
  string.
- All title clusters use a whitespace-nowrap flex wrapper around the
  lucide icon and Widget.Title so CJK title characters do not stack
  vertically when the grid column is narrow.

widget.css is already pulled in via `@import '@heroui-pro/react/css';`
in web/src/index.css, so no extra CSS plumbing is required.

Verified: `bun run build` passes; `bunx prettier --check` and
`bunx eslint` pass for all touched files; manual screenshot QA
confirms the dashboard panels render with the new Widget surface.

Made-with: Cursor

* refactor(console/dashboard): polish Widget header, empty state, and grid

Address visual polish feedback on the Widget rebuild:

- Title size: createSectionTitle in helpers/dashboard.jsx now wraps the
  text in a `text-sm font-semibold text-foreground` span (matching the
  widget__title CSS) so the four StatsCards titles (账户数据 / 使用统计 /
  资源消耗 / 性能指标) match the Widget.Title used by every other panel.
  Also adds whitespace-nowrap + shrink-0 on the icon to prevent CJK
  characters from stacking.

- Header height: every simple Widget.Header now uses `h-12` so the
  header strip matches the AnnouncementsPanel header height (which the
  user pointed out as the correct one). The complex headers
  (ChartsPanel, AnnouncementsPanel) keep their `h-auto min-h-12 …
  flex-col` rule for content that can wrap to a second row.

- Empty state: every empty state now uses HeroUI Pro's `EmptyState`
  with `size='sm'` and `EmptyState.Media variant='icon'`, dropping the
  custom 96×96 illustration container override. FaqPanel previously
  used a bespoke div + HelpCircle 42px; it now uses EmptyState too, so
  the four panels (ApiInfoPanel / AnnouncementsPanel / FaqPanel /
  UptimePanel) share the same circular muted icon background and
  consistent typography.

- Responsive grid: StatsCards collapses to 4-col only at xl (≥1280px)
  via `grid-cols-1 sm:grid-cols-2 xl:grid-cols-4` so each KPI card has
  ~360px at common 1024–1280 widths instead of ~150px (which was
  forcing labels like "统计次数" to wrap CJK character per line).
  ChartsPanel + ApiInfoPanel switch to `lg:grid-cols-3 xl:grid-cols-4`
  with the chart panel spanning 2/3, giving the API info column a
  more usable ~256px width at lg. The bottom info panels grid switches
  to `lg:grid-cols-2 xl:grid-cols-4` so 系统公告 takes the full row at
  lg with FAQ + Uptime side-by-side beneath it.

- ChartsPanel tabs strip: keeps the title and the Tabs.List on
  separate rows through lg and only inlines them at xl. The tab list
  is wrapped in an overflow-x-auto container with whitespace-nowrap
  on each Tabs.Tab so the 4–6 chart tabs no longer collapse into
  vertical CJK character columns when the chart panel narrows.

- Cleanup: drop the now-unused ILLUSTRATION_SIZE prop from
  AnnouncementsPanel / ApiInfoPanel / FaqPanel / UptimePanel and from
  the dashboard index that wired it through.

Verified: bun run build passes, prettier --check + eslint pass on all
touched files, and manual screenshot QA confirms consistent header
heights, title sizes, empty state icons, and a clean 2-col KPI grid +
horizontally scrollable tabs at the typical 1024px viewport.

Made-with: Cursor

* style(console/dashboard): use primary 充值 CTA and bare-icon header buttons

- StatsCards: switch the 当前余额 → 充值 button from `variant='secondary'`
  (default surface fill + accent-soft text, which read as a muted green
  pill) to `variant='primary'` so it now renders with the accent fill +
  accent-foreground text expected of a recharge CTA.

- DashboardHeader: switch the 搜索条件 + 刷新 icon-only buttons from
  `variant='tertiary'` (which paints the surrounding pill at rest) to
  `variant='ghost'` so the trigger reads as a bare icon and the
  background only fades in on hover/press. Drop the gap from `gap-2`
  to `gap-1` since the buttons no longer carry their own pill.

Made-with: Cursor

* style(uptime-panel): shrink the refresh action to read as inline glyph

The refresh trigger inside 服务可用性's compact 48px Widget.Header was
still rendering at full Button `sm` icon-only dimensions (32×32 with a
16px svg, since `.button--sm` forces svg to size-4 regardless of the
lucide `size` prop). Override to `!h-6 !w-6 !min-w-0` plus
`[&_svg]:!size-3` so the affordance reads as a 24×24 hover pill with a
12px refresh glyph — proportionate to the small panel header without
dominating the title. HeroUI Button only ships `sm`/`md`/`lg`, so a
className escape hatch is the cleanest sub-`sm` path.

Made-with: Cursor

* style(charts-panel): drop chart tab label font from text-sm to text-xs

The Tabs.List inside 模型数据分析's Widget.Header carries 4–6 entries
(消耗分布 / 调用趋势 / 调用次数分布 / 调用次数排行 / 用户消耗排行 /
用户消耗趋势). At the default 14px tab font, only the first three fit
across the chart panel at lg, forcing the rest behind the horizontal
scroll. Drop each Tabs.Tab label to text-xs (12px) so 4–5 tabs are
visible at rest while the secondary chrome stays subordinate to the
14px Widget.Title.

Made-with: Cursor

* fix(api-info-panel): center empty state vertically inside the panel

ApiInfoPanel sits in a CSS grid row alongside the much-taller
ChartsPanel, so the row stretches the API info Widget to ~24rem.
Previously the empty state branch rendered straight inside
ScrollableContainer, which collapses to its content height — leaving
the empty state pinned to the top with all the empty space below it.

Split the two render paths:

- Data path keeps the ScrollableContainer for scroll-with-fade and now
  carries `flex-1` so the scroll region claims the stretched height.
- Empty path renders into a `flex flex-1 min-h-80 items-center
  justify-center` wrapper, which propagates the row height down via the
  `flex` Widget.Content + `flex-1` child chain and centers the
  EmptyState both axes regardless of grid stretch height.

Made-with: Cursor

---------

Co-authored-by: dingyi <dingyi@users.noreply.github.com>
dingyi added a commit that referenced this pull request Apr 27, 2026
Auto-merge — no conflicts. Brings the PR #5 (HeroUI rewrite) and PR #1/#3
console template work into the widget-dashboard-cards branch so the dashboard
widget polish work (6 commits: replace Card with HeroUI Pro Widget, polish
Widget header/empty state/grid, primary 充值 CTA + bare-icon header buttons,
shrink uptime panel refresh action, drop charts panel tab font, vertically
center api-info-panel empty state) can be merged on top of the latest UI
foundation.

Validation:
- bun run build: passes (20.62s)
- go build .: passes
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.

1 participant