HeroUI v3 console refactor: tables, modals, drawers, density#16
Conversation
Build the foundation every console list page sits on so per-page glue stays thin and visual parity is enforced through shared primitives instead of copy-paste. New shared components - HeroTable: HeroUI Table wrapper with sticky-right operations column, selection adapter (Antd-style ↔ React Aria Set), nested dataIndex support, stable string keys to avoid React Aria reconciliation errors, and an inline empty state via renderEmptyState. - SideSheet: HeroUI Drawer wrapper that uses React Portal to escape parent backdrop-filter containing blocks (the bug behind the "modal leaks into card" reports). Drives a CSS custom property for reliable dynamic widths under Tailwind v4's JIT. Refactored shared primitives - CardPro: drop the hard-coded `border-t` between table and pagination (the card already has its own surface chrome) and tighten footer padding. - ConfirmDialog / ColumnSelectorDialog: switch to canonical HeroUI Modal anatomy (ModalHeading wraps title text — bare children fall to body font-size and read smaller than description), drop manual border dividers, swap Button color='danger'/'primary' for variant=. ColumnSelectorDialog also adopts the full Checkbox anatomy so the actual box renders. - TableFilterForm: FilterInput uses HeroUI Input, FilterSelect drops hand-rolled Tailwind overrides and lets HeroUI's --field-background paint the primary variant for pixel-equal rows with adjacent inputs. - TablePagination: switch to ct-compact-select utility class. - JSONEditor: whitespace-nowrap on tabs to prevent CJK wrap. Global CSS (index.css) - Body font-size lowered to 12px to match per-component density and surface this as the default for bare prose / labels. - .button--sm and .toggle-button--sm font-size dropped to 12px so toolbar pills don't read larger than surrounding 12px text. - .switch__thumb locked to white in both states (HeroUI default flips to --accent-foreground when checked, which on our cyan accent resolves to near-black and looked like a different control). - .ct-compact-table / .ct-compact-alert / .ct-compact-select utility classes published to replace Tailwind v4 arbitrary selectors that silently mangled BEM `__` separators into descendant spaces. - .ct-compact-alert.alert__content gets justify-content:center so single-child Alerts vertically center the description with the icon. Made-with: Cursor
Sweep every console list page (tokens, channels, users, redemptions,
subscriptions, models, usage-logs, model-pricing) onto the shared
HeroTable + SideSheet primitives so they share the same row rhythm,
sticky-right operations column, hover state, empty state, loading
spinner and edit-modal anatomy.
Per-page tables
- TokensTable / ChannelsTable / UsersTable / RedemptionsTable /
SubscriptionsTable / ModelsTable: thin wrappers around HeroTable.
Adapt each hook's legacy {selectedRowKeys, onChange} selection
shape to HeroTable's {selectionMode, selectedRows,
onSelectionChange} contract via a useMemo adapter so downstream
consumers (batch delete, copy, prefill management) stay untouched.
- All operations cells switch from `flex flex-wrap` to
`inline-flex whitespace-nowrap` + compact buttons
(`!h-7 !px-2.5 !text-[11px]`) so rows stay ~44px tall and the
sticky-right column stays a single line under horizontal scroll.
Tabs and toolbars
- ChannelsTabs / ModelsTabs: rewrite vendor pills as HeroUI
ToggleButtonGroup + ToggleButton split-pill (toggle paired with
ClickMenu for vendor edit/delete). `!justify-start` overrides
HeroUI's default centered group so wrapped tabs left-align.
- ChannelsActions: switch local ClickDropdown to shared ClickMenu;
fix `color='danger'` → `variant='danger-soft'` so the disabled
"delete selected channels" button reads as destructive.
- ChannelsFilters: `variant='secondary'` → `variant='primary'` on
Add channel so it matches every other "Add X" CTA in the console.
- UsageLogsFilters: expand grid to 8 cols at 2xl, place LogType
as HeroUI Select.
- ModelsActions: remove inherited order/width hacks now that the
parent flex layout is gone.
Modals
- EditTokenModal / EditChannelModal / EditTagModal: migrate the
hand-rolled <aside> drawer to the shared SideSheet (portals fix
the backdrop-filter containment bug).
- AddUserModal / EditUserModal / UserSubscriptionsModal /
EditRedemptionModal / AddEditSubscriptionModal /
EditModelModal / EditVendorModal / EditPrefillGroupModal /
PrefillGroupManagement / ModelDetailSideSheet: same SideSheet
treatment, plus drop IconTile decorations, drop X / Save icons
from footer buttons, replace native inputs/selects with HeroUI
Input/Select/TextArea/InputGroup, swap manual badges for shared
StatusChip, and use ModalHeading inside ModalHeader.
Page-level
- /console/models: split actionsArea (ModelsActions) and searchArea
(ModelsFilters) into CardPro's dedicated slots — they used to
fight for one flex row at medium widths and the buttons stacked
vertically.
- /console/subscription: warning Alert lives next to "Create plan"
in actionsArea (status='warning' + AlertTriangle icon, vertically
centered via the new ct-compact-alert helper).
- /console/models page-level Alert and JSONEditor warnings adopt the
same HeroUI Alert + ct-compact-alert pattern.
Column defs
- All column header titles inherit `whitespace-nowrap` from HeroTable
so short CJK / slash-bearing titles like "已用/剩余" no longer
wrap mid-character in narrow columns.
- Compact button styling shared across every operations cell.
Made-with: Cursor
- Setting/index.jsx: rewrite the top-level settings tab strip from hand-rolled <button> pills (no pointer cursor, black-pill selected state, missing radiogroup semantics) to a HeroUI ToggleButtonGroup + ToggleButton row. `!justify-start` keeps wrapped tabs left- aligned. Pane definitions split icon + label into separate fields so each ToggleButton renders both consistently at 12px. - SettingGlobalModel / SettingsPaymentGatewayStripe / SettingsPaymentGatewayWaffoPancake: replace custom amber warning banners with HeroUI Alert (status='warning') + AlertTriangle + ct-compact-alert density tweaks, matching the /console/models notice and /console/subscription Stripe banner. - GroupRatioSettings: migrate the "分组设置使用说明" sidebar from a hand-rolled <aside> drawer to the shared SideSheet so it inherits the portal-based escape from backdrop-filter containing blocks. - SecureVerificationModal: wrap the modal title in ModalHeading so it picks up the proper text-base + font-medium heading style instead of falling through to body font-size. Made-with: Cursor
|
Direct merge instead. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82968a4898
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| key={String(o.value)} | ||
| id={String(o.value)} |
There was a problem hiding this comment.
Keep empty Select key consistent with option ids
FormSelect maps empty values to SELECT_EMPTY_KEY via toSelectKey, but the rendered ListBox.Item ids are still raw String(o.value). When upgrade_group (or any optional select field) is '', selectedKey becomes __plan_select_empty__, which doesn't match any item id, so the control can't represent a real selected option (e.g. “不升级” is shown only as placeholder and reopens with no selected item/checkmark). Map item ids through the same key-normalization path to keep controlled selection state consistent.
Useful? React with 👍 / 👎.
Summary
End-to-end migration of every console list page (tokens, channels, users, redemptions, subscriptions, models, usage-logs, model-pricing) and the related modals/drawers/settings pages onto HeroUI v3 + canonical anatomy. The goal: enforce visual parity through shared primitives instead of copy-paste, and standardize the row rhythm, density, button hierarchy, and overlay behavior across the entire admin.
What changed
1. Shared infrastructure (
commit 1)HeroTablewrapper around HeroUI Table — sticky-right operations column, selection adapter (Antd-style ↔ React AriaSet), nesteddataIndexsupport, stable string keys to dodge React Aria reconciliation errors, inline empty state viarenderEmptyState.SideSheetwrapper around HeroUI Drawer — uses React Portal to escape parentbackdrop-filtercontaining blocks (the bug behind every "modal leaks into card" report), plus a CSS custom property for reliable dynamic widths under Tailwind v4 JIT.ConfirmDialog/ColumnSelectorDialog/CardPro/TableFilterForm/TablePagination/JSONEditor: switch to canonical HeroUI Modal anatomy (ModalHeadingwraps title text), fullCheckboxanatomy,--field-backgroundtoken,whitespace-nowrapon tabs, etc..button--sm/.toggle-button--sm→ 12px.switch__thumblocked to white in both states (HeroUI default flips to near-black on cyan accent).ct-compact-table/.ct-compact-alert/.ct-compact-selectutility classes — replaces Tailwind v4 arbitrary BEM selectors that silently mangled__separators into descendant spaces2. List pages (
commit 2)HeroTablenow, with consistent ~44px rows and 11px compact operation buttons (!h-7 !px-2.5 !text-[11px]) underinline-flex whitespace-nowrap./console/channel+/console/modelsrewritten asToggleButtonGroup+ToggleButtonsplit-pill, each toggle paired with aClickMenufor vendor edit/delete actions.!justify-startoverrides HeroUI's default centered group so wrapped tabs left-align.variant='primary'(cyan + black text). Destructive batch actions onvariant='danger-soft'.SideSheet: dropIconTiledecorations, drop<X>/<Save>icons from footer buttons, replace native inputs/selects with HeroUIInput/Select/TextArea/InputGroup, swap manual chip badges for sharedStatusChip./console/modelsactions and filters split into CardPro's dedicatedactionsAreaandsearchAreaslots — they used to fight for one flex row at medium widths./console/subscriptionand/console/modelspage-level notices use HeroUIAlert(status='warning'+AlertTriangle+ct-compact-alert).whitespace-nowrapfromHeroTableso titles like "已用/剩余" no longer wrap mid-character.3. Settings (
commit 3)/console/settingtop-level tab strip rewritten asToggleButtonGroup(replaces hand-rolled<button>pills with no pointer cursor and a black-pill selected state).SettingGlobalModel, two payment gateways) replace custom warning banners with HeroUIAlert.GroupRatioSettings"分组设置使用说明" sidebar moves to sharedSideSheet.SecureVerificationModaltitle wrapped inModalHeadingfor proper heading sizing.Test plan
/console/token: rows ~44px, sticky right ops column, edit modal opens/closes via SideSheet/console/channel: vendor tab split-pill, "Add channel" cyan/black primary, "Delete selected channels" red soft/console/user: AddUser / EditUser modals render, table rows compact/console/redemption: add/edit modal placement (left for new, right for edit), date picker works/console/subscription: warning Alert next to "Create plan" vertically centered, table empty state inline/console/models: actions row, search row, vendor tabs, model edit/delete confirm dialog title in ModalHeading/console/log: filters + Select all line up pixel-equal in light AND dark mode/console/setting: tab pills cyan accent-soft when selected, pointer cursor, wrapped tabs left-alignThis PR was generated automatically by Cursor
Made with Cursor