Commit a878b481 authored by Andy-bubu's avatar Andy-bubu

feat: complete xinyuan material center ui

parent 30d1d111
......@@ -21,10 +21,10 @@ upload field, confirmation dialog and application switcher. They expose current
HeroUI selection/value/event contracts; page controllers adapt those values to
existing material services.
`Select`-shaped compatibility wrappers and page-local ECharts theme helpers are
`Select`-shaped compatibility wrappers and page-local chart theme helpers are
not part of the target architecture. Enum/reference fields use the current
HeroUI Autocomplete compound model, while dashboards use HeroUI Pro KPI and
chart compounds with the approved chart Tokens.
HeroUI Autocomplete compound model, while dashboards use HeroUI Pro KPI and the
shared `XinyuanEChart` ECharts adapter with approved chart Tokens.
## Dependency Source
......
......@@ -12,7 +12,7 @@ Status legend: `done`, `in progress`, `superseded`, `blocked`, `pending`.
| Baseline login QA | superseded | Earlier viewport evidence predates the corrected problem-rectification login parity pass |
| Authoritative theme | done | Exact baseline `theme.css`, Xinyuan assets, Inter and app-only extension Tokens installed |
| Login | done | Baseline structure, assets and approved 12px label / 24px field rhythm synchronized |
| Shell | done | Pro AppLayout/Sidebar/Navbar; desktop offcanvas plus an accessible Pro Sheet mobile sidebar |
| Shell | done | Pro Sidebar.Provider/Sidebar/Navbar; fixed dark Sidebar, light content canvas, desktop offcanvas plus accessible mobile Sheet |
| Application switcher | done | model_analysis parity; `/info -> site.group`, counts, empty-group filtering, 其他应用/全部应用 fallbacks, two-column grid |
| Dashboard | done | Pro KPI uses the approved 24px Card and 48px toned icon Surface; analytical modules follow the same radius contract |
| Material archive filters | done | HeroUI Surface, Disclosure, InputGroup, Autocomplete and DatePicker compounds |
......@@ -27,10 +27,10 @@ Status legend: `done`, `in progress`, `superseded`, `blocked`, `pending`.
| Real-data-only states | done | Demo material/project fallbacks removed; empty/error states reflect the API |
| Private visual CSS | done | Duplicate portable/app-token layers removed; approved shared shell/login baseline retained |
| Production build | done | `npm run build` completed after core migration |
| Browser acceptance | in progress | Four switcher viewports, dark theme, desktop/mobile sidebar and user menu pass; Autocomplete library warnings remain |
| Browser acceptance | in progress | 1440x900 and 390x844 shell/material flows pass; remaining warnings are HeroUI Autocomplete internal dialog labels |
| Cross-app scope classification | done | Real app name is 审计材料中心; notification-only rollout, full-feature items intentionally excluded |
| Notification route | done | Independent `/notifications` page with message/todo, all/unread, search and fixed pagination |
| Notification Navbar entry | done | HeroUI Badge.Anchor/Badge/Button/Tooltip, 36px Bell left of theme action, no Sidebar entry |
| Notification Navbar entry | done | HeroUI Badge.Anchor/Badge/Button/Tooltip, 36px Bell action, no theme toggle or Sidebar entry |
| Notification mutations | done | Existing query/count/read/action APIs; single/all read and `{notice_id}` dynamic actions preserved |
| Notification link safety | done | Plain text content, base-aware same-app links, external new-window links and normalized login redirect |
| Notification browser acceptance | done | Four required viewports in both themes; badge boundaries, actions, links, overflow and isolated console pass |
......
......@@ -29,8 +29,8 @@ paths are not valid delivery mechanisms.
- Primary brand: `#9E1B1B` through the authoritative `--accent` token.
- Light identity: `xy-logo-red.png`.
- Dark identity: `xy-logo-white.png`.
- Root theme: `.light`/`.dark` plus matching `data-theme`.
- Theme storage key: `xinyuan_color_theme`.
- Root theme: the application shell is explicitly `.light`; the content canvas stays light while the Sidebar consumes the fixed dark split-shell tokens.
- Theme switching is intentionally absent from the production Navbar.
- Application display name: `审计材料中心` everywhere.
## Surface Hierarchy
......@@ -92,8 +92,8 @@ IDs only. Dates are submitted as ISO 8601. `source_system` and
## Notification Center
- The Bell is a 36px HeroUI icon Button immediately left of the theme action,
composed with `Tooltip`, `Badge.Anchor` and `Badge`.
- The Bell is a 36px HeroUI icon Button in the Navbar action area, composed with
`Tooltip`, `Badge.Anchor` and `Badge`; no theme toggle is rendered.
- Zero unread hides the Badge, 1-99 shows the real count, and values above 99
display `99+` while retaining the real accessible count.
- `/notifications` uses message/todo and all/unread HeroUI Tabs, HeroUI
......
......@@ -63,7 +63,7 @@ The authoritative references are `src/services/record-api.md`,
## Remaining UI Inventory
Production routes contain no Catalyst, Headless UI, ECharts, visible native
Production routes contain no Catalyst, Headless UI or visible native
select/date/file controls, legacy UI compatibility imports or page-owned
HeroUI appearance styles. The historical kit, unreachable workspace and unused
decorative empty-state page were removed after production references reached
......@@ -88,11 +88,12 @@ switcher grouping gap without changing its application source or navigation.
## Theme Audit
- `src/design-system/theme.css` is byte-identical to the approved baseline.
- `src/design-system/theme.css` retains the Xinyuan HeroUI tokens and now includes
the fixed dark Sidebar and ECharts chart tokens used by the approved baseline.
- `tokens.css`, `shell.css` and the login contract in `tailwind.css` are exact
copies of the approved `problem_rectification` shared baseline.
- Inter weights and the stable red/white Xinyuan assets are loaded globally.
- Official `@heroui-pro/react/css` loads before the exact Xinyuan theme and
- Official `@heroui-pro/react/css` loads before the Xinyuan theme and
extension Tokens. The temporary Pro CSS compatibility snapshot was removed.
## Browser Evidence
......
......@@ -29,6 +29,8 @@
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"jszip": "^3.10.1",
......@@ -12642,6 +12644,36 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/echarts": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz",
"integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "6.1.0"
}
},
"node_modules/echarts-for-react": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.6.tgz",
"integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"size-sensor": "^1.0.1"
},
"peerDependencies": {
"echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
"react": "^15.0.0 || >=16.0.0"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
......@@ -23635,6 +23667,12 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/size-sensor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.3.tgz",
"integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==",
"license": "ISC"
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
......@@ -26605,6 +26643,21 @@
"zod": "^3.18.0"
}
},
"node_modules/zrender": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz",
"integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
......
......@@ -31,6 +31,8 @@
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"jszip": "^3.10.1",
......
import { Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react";
import { Avatar, Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react";
import { ActionBar } from "@heroui-pro/react/action-bar";
import { DataGrid } from "@heroui-pro/react/data-grid";
import { EmptyState } from "@heroui-pro/react/empty-state";
import XinyuanAutocomplete from "@/components/XinyuanAutocomplete";
import { useMemo } from "react";
function resolveRowKey(row, index, getRowKey) {
......@@ -239,13 +240,57 @@ export function DataTableTextPair({ primary, secondary, className = "" }) {
);
}
export function DataTablePersonIdentity({ name, account, className = "" }) {
function getPersonFallback(name) {
const normalized = String(name ?? "").trim();
if (!normalized || normalized === "-") {
return "?";
}
const compact = normalized.replace(/\s+/g, "");
if (/^[\u3400-\u9fff]/.test(compact)) {
return Array.from(compact)[0];
}
return Array.from(compact).slice(0, 2).join("").toUpperCase();
}
export function DataTablePersonIdentity({
name,
account,
secondary,
avatar,
className = "",
}) {
const primaryText = String(name ?? "").trim() || "-";
const secondaryText = String(account ?? secondary ?? "").trim();
return (
<DataTableTextPair
primary={name}
secondary={account}
className={className}
/>
<Tooltip delay={0}>
<div
className={["flex min-w-0 items-center gap-2", className]
.filter(Boolean)
.join(" ")}
tabIndex={0}
>
<Avatar size="sm" variant="soft" className="shrink-0">
{avatar ? <Avatar.Image alt={primaryText} src={avatar} /> : null}
<Avatar.Fallback>{getPersonFallback(primaryText)}</Avatar.Fallback>
</Avatar>
<span className="grid min-w-0 gap-1">
<span className="min-w-0 truncate text-sm font-medium">
{primaryText}
</span>
{secondaryText ? (
<span className="min-w-0 truncate text-sm text-muted">
{secondaryText}
</span>
) : null}
</span>
</div>
<Tooltip.Content>
<span className="whitespace-pre-wrap">
{[primaryText, secondaryText].filter(Boolean).join("\n")}
</span>
</Tooltip.Content>
</Tooltip>
);
}
......@@ -264,8 +309,10 @@ export function DataTableChip({ children, text, className = "" }) {
export function DataTablePagination({
page,
pageSize,
pageSizeOptions = [],
total,
onPageChange,
onPageSizeChange,
isDisabled = false,
itemLabel = "条",
}) {
......@@ -274,13 +321,50 @@ export function DataTablePagination({
const pageWindowEnd = total
? Math.min(totalPages, pageWindowStart + 5)
: pageWindowStart;
const normalizedPageSizeOptions = pageSizeOptions
.map((value) => Number(value))
.filter(
(value, index, values) =>
Number.isFinite(value) &&
value > 0 &&
values.indexOf(value) === index,
);
const pageSizeOptionsForPicker = normalizedPageSizeOptions.map((value) => ({
id: String(value),
label: `${value} 条/页`,
}));
const hasPageSizePicker =
pageSizeOptionsForPicker.length > 0 &&
typeof onPageSizeChange === "function";
return (
<div className="mt-2">
<Pagination size="sm" className="w-full">
<Pagination.Summary>
<div className="mt-2 flex min-w-0 flex-wrap items-center justify-between gap-3">
<div className="flex min-w-0 shrink-0 items-center gap-3">
<span className="min-w-0 text-sm text-muted">
共 {total} {itemLabel},第 {total ? page + 1 : 0}{" "}
/ {total ? totalPages : 0} 页
</Pagination.Summary>
</span>
{hasPageSizePicker ? (
<XinyuanAutocomplete
aria-label="每页条数"
className="w-24 shrink-0"
isDisabled={isDisabled}
options={pageSizeOptionsForPicker}
placeholder="每页条数"
selectedKey={String(pageSize)}
showSearch={false}
valueText={`${pageSize} 条/页`}
variant="secondary"
onSelectionChange={(key) => {
const nextPageSize = Number(key);
if (normalizedPageSizeOptions.includes(nextPageSize)) {
onPageSizeChange(nextPageSize);
}
}}
/>
) : null}
</div>
<Pagination className="ml-auto w-auto shrink-0" size={hasPageSizePicker ? "lg" : "sm"}>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
......
......@@ -54,6 +54,7 @@ export default function XinyuanAutocomplete({
popoverHeader,
renderOption,
searchPlaceholder = "输入关键词搜索",
showSearch = true,
selectedKey,
selectedKeys,
selectionMode = "single",
......@@ -139,6 +140,7 @@ export default function XinyuanAutocomplete({
onInputChange={onInputChange}
>
{popoverHeader}
{showSearch ? (
<SearchField
aria-label={searchPlaceholder}
fullWidth
......@@ -154,6 +156,7 @@ export default function XinyuanAutocomplete({
</SearchField.ClearButton>
</SearchField.Group>
</SearchField>
) : null}
<ListBox
aria-label={ariaLabel || placeholder}
dependencies={[Array.from(selectedKeyStringSet).join("\u0000")]}
......
import { getXinyuanEChartsTheme } from '@/design-system/chartPalette';
import ReactECharts from 'echarts-for-react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
function mergeAxisTheme(axis, theme) {
if (!axis) return axis;
const applyTheme = (item) => ({
...item,
axisLabel: { color: theme.axis, fontFamily: theme.fontFamily, fontSize: 12, ...item?.axisLabel },
axisLine: { ...item?.axisLine, lineStyle: { color: theme.grid, ...item?.axisLine?.lineStyle } },
axisTick: { ...item?.axisTick, lineStyle: { color: theme.grid, ...item?.axisTick?.lineStyle } },
splitLine: { ...item?.splitLine, lineStyle: { color: theme.grid, ...item?.splitLine?.lineStyle } },
});
return Array.isArray(axis) ? axis.map(applyTheme) : applyTheme(axis);
}
export default function XinyuanEChart({ ariaLabel, className = '', height = 320, onEvents, option }) {
const chartRef = useRef(null);
const hideTooltip = useCallback(() => {
chartRef.current?.getEchartsInstance?.().dispatchAction({ type: 'hideTip' });
}, []);
useEffect(() => {
window.addEventListener('resize', hideTooltip);
return () => window.removeEventListener('resize', hideTooltip);
}, [hideTooltip]);
const themedOption = useMemo(() => {
const theme = getXinyuanEChartsTheme();
const applyPieTheme = (series) => series?.type === 'pie'
? {
...series,
itemStyle: { borderColor: theme.sectorGap, borderRadius: theme.sectorRadius, borderWidth: 2, ...series.itemStyle },
padAngle: series.padAngle ?? 1,
}
: series;
const series = Array.isArray(option?.series) ? option.series.map(applyPieTheme) : applyPieTheme(option?.series);
return {
animationDuration: 320,
animationDurationUpdate: 240,
animationEasing: 'cubicOut',
aria: { enabled: true, decal: { show: false }, ...option?.aria },
color: option?.color || theme.palette,
textStyle: { color: theme.label, fontFamily: theme.fontFamily, ...option?.textStyle },
...option,
series,
legend: option?.legend ? {
icon: 'circle', itemHeight: 8, itemWidth: 8, ...option.legend,
textStyle: { color: theme.label, fontFamily: theme.fontFamily, fontSize: 12, lineHeight: 16, ...option.legend?.textStyle },
} : undefined,
tooltip: {
appendToBody: true,
backgroundColor: theme.tooltipBackground,
borderColor: theme.tooltipBorder,
borderWidth: 1,
className: 'rc-echarts-tooltip',
confine: true,
enterable: false,
extraCssText: `border-radius: 12px; box-shadow: ${theme.tooltipShadow};`,
padding: [10, 12],
renderMode: 'html',
...option?.tooltip,
textStyle: { color: theme.foreground, fontFamily: theme.fontFamily, fontSize: 12, ...option?.tooltip?.textStyle },
},
xAxis: mergeAxisTheme(option?.xAxis, theme),
yAxis: mergeAxisTheme(option?.yAxis, theme),
};
}, [option]);
return (
<div aria-label={ariaLabel} className={`rc-chart-interaction w-full min-w-0 ${className}`.trim()} role="img" tabIndex={0}>
<ReactECharts lazyUpdate notMerge onEvents={{ globalout: hideTooltip, ...onEvents }} option={themedOption} opts={{ renderer: 'canvas' }} ref={chartRef} style={{ height, width: '100%' }} />
</div>
);
}
......@@ -93,3 +93,36 @@ export function colorizeXinyuanChartTooltipPayload(payload = [], resolveColor) {
};
});
}
function readRootToken(tokenName, fallback = '') {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return fallback;
}
return window.getComputedStyle(document.documentElement).getPropertyValue(tokenName).trim() || fallback;
}
export function createResolvedXinyuanChartColorMap(keys = [], options = {}) {
return new Map(Array.from(createXinyuanChartColorMap(keys, options)).map(([key, color]) => {
const match = String(color).match(/^var\((--[^)]+)\)$/);
return [key, match ? readRootToken(match[1], '#667085') : color];
}));
}
export function getXinyuanEChartsTheme() {
const fontFamily = typeof document === 'undefined'
? 'Inter, sans-serif'
: window.getComputedStyle(document.body).fontFamily;
return {
axis: readRootToken('--xinyuan-chart-axis', '#737373'),
grid: readRootToken('--xinyuan-chart-grid', '#e8e8e8'),
label: readRootToken('--xinyuan-chart-label', '#737373'),
foreground: readRootToken('--xinyuan-chart-tooltip-foreground', '#222222'),
fontFamily: fontFamily || 'Inter, sans-serif',
palette: Array.from({ length: 16 }, (_, index) => readRootToken(`--xinyuan-chart-${index + 1}`, '#667085')),
tooltipBackground: readRootToken('--xinyuan-chart-tooltip-background', '#ffffff'),
tooltipBorder: readRootToken('--xinyuan-chart-tooltip-border', '#dedede'),
tooltipShadow: readRootToken('--xinyuan-chart-tooltip-shadow', '0 16px 40px rgb(15 23 42 / 14%)'),
sectorGap: readRootToken('--xinyuan-chart-sector-gap', '#ffffff'),
sectorRadius: Number.parseFloat(readRootToken('--xinyuan-chart-sector-radius', '10')) || 10,
};
}
......@@ -436,6 +436,24 @@
gap: var(--rc-page-module-gap);
}
.rc-section-title {
margin: 0;
color: var(--rc-text-primary);
font-size: 1.125rem;
font-weight: var(--rc-font-bold);
line-height: 1.5rem;
letter-spacing: 0;
}
.rc-module-title {
margin: 0;
color: var(--rc-text-primary);
font-size: 1rem;
font-weight: var(--rc-font-semibold);
line-height: 1.5rem;
letter-spacing: 0;
}
.rc-page-grid {
min-width: 0;
gap: var(--rc-page-module-gap);
......@@ -466,6 +484,10 @@
outline-offset: 2px;
}
.rc-echarts-tooltip {
position: fixed !important;
}
.rc-kpi-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
......@@ -1454,3 +1476,118 @@
border-right: 0;
}
}
/* Final fixed dark Sidebar overrides. */
.template-dashboard-shell .app-sidebar-glass,
.rc-mobile-sidebar.app-sidebar-glass {
color: var(--rc-sidebar-foreground);
border-color: var(--rc-sidebar-border) !important;
background-color: var(--rc-sidebar-background) !important;
}
.template-dashboard-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label,
.template-dashboard-shell .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-icon { color: var(--rc-sidebar-muted); }
.template-dashboard-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-label,
.template-dashboard-shell .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item-content { color: var(--rc-sidebar-foreground); }
.template-dashboard-shell .sidebar__menu-item[data-current='true'] .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item[data-current='true'] .sidebar__menu-item-content {
color: var(--rc-sidebar-item-selected-foreground);
background: var(--rc-sidebar-item-selected);
}
.template-dashboard-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.template-dashboard-shell .sidebar__menu-item[data-hovered='true'] .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item[data-hovered='true'] .sidebar__menu-item-content {
color: var(--rc-sidebar-foreground);
background: var(--rc-sidebar-item-hover);
}
.template-dashboard-shell .app-sidebar-user-trigger,
.rc-mobile-sidebar .app-sidebar-user-trigger { color: var(--rc-sidebar-foreground); }
/* Keep HeroUI's generated slot styles from replacing the fixed dark-sidebar tokens. */
.template-dashboard-shell .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-icon,
.template-dashboard-shell .sidebar__menu-icon .anticon,
.rc-mobile-sidebar .sidebar__menu-icon .anticon {
color: var(--rc-sidebar-muted) !important;
}
.template-dashboard-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-label,
.template-dashboard-shell .app-sidebar-title,
.rc-mobile-sidebar .app-sidebar-title,
.template-dashboard-shell .app-sidebar-user-name,
.rc-mobile-sidebar .app-sidebar-user-name,
.template-dashboard-shell .app-sidebar-app-trigger.button,
.rc-mobile-sidebar .app-sidebar-app-trigger.button {
color: var(--rc-sidebar-foreground) !important;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
color: var(--rc-sidebar-item-selected-foreground) !important;
background: var(--rc-sidebar-item-selected) !important;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content
:is(.sidebar__menu-icon, .sidebar__menu-label, .anticon),
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content
:is(.sidebar__menu-icon, .sidebar__menu-label, .anticon) {
color: var(--rc-sidebar-item-selected-foreground) !important;
}
.template-dashboard-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.template-dashboard-shell .sidebar__menu-item[data-hovered='true'] .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item[data-hovered='true'] .sidebar__menu-item-content {
color: var(--rc-sidebar-foreground) !important;
background: var(--rc-sidebar-item-hover) !important;
}
.template-dashboard-shell .app-sidebar-user-description,
.rc-mobile-sidebar .app-sidebar-user-description {
color: var(--rc-sidebar-muted) !important;
}
.template-dashboard-shell .app-sidebar-user-trigger,
.rc-mobile-sidebar .app-sidebar-user-trigger {
color: var(--rc-sidebar-foreground) !important;
}
.template-dashboard-shell .app-sidebar-user-trigger.dropdown__trigger:hover,
.template-dashboard-shell .app-sidebar-user-trigger.dropdown__trigger[data-hovered='true'],
.template-dashboard-shell .app-sidebar-user-trigger.dropdown__trigger[aria-expanded='true'],
.rc-mobile-sidebar .app-sidebar-user-trigger.dropdown__trigger:hover,
.rc-mobile-sidebar .app-sidebar-user-trigger.dropdown__trigger[data-hovered='true'],
.rc-mobile-sidebar .app-sidebar-user-trigger.dropdown__trigger[aria-expanded='true'] {
color: var(--rc-sidebar-foreground) !important;
background: var(--rc-sidebar-item-hover) !important;
}
.template-dashboard-shell .app-sidebar-user-avatar,
.rc-mobile-sidebar .app-sidebar-user-avatar {
background: var(--rc-sidebar-item-hover) !important;
}
.template-dashboard-shell .app-sidebar-app-trigger.button:hover,
.template-dashboard-shell .app-sidebar-app-trigger.button[data-hovered='true'],
.template-dashboard-shell .app-sidebar-app-trigger.button[data-pressed='true'],
.template-dashboard-shell .app-sidebar-app-trigger.button[aria-expanded='true'],
.rc-mobile-sidebar .app-sidebar-app-trigger.button:hover,
.rc-mobile-sidebar .app-sidebar-app-trigger.button[data-hovered='true'],
.rc-mobile-sidebar .app-sidebar-app-trigger.button[data-pressed='true'],
.rc-mobile-sidebar .app-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--rc-sidebar-item-selected-foreground) !important;
background: var(--rc-sidebar-item-selected) !important;
}
......@@ -11,38 +11,37 @@
[data-theme="light"],
[data-theme="default"] {
/* Theme Colors (Light Mode) */
--accent: oklch(45.32% 0.1661 27.03);
--accent: oklch(57.14% 0.2156 29.20);
--accent-foreground: oklch(99.11% 0 0);
--background: oklch(97.02% 0.0045 27.03);
--border: oklch(90.00% 0.0045 27.03);
--background: oklch(97.02% 0.0000 29.20);
--border: oklch(90.00% 0.0000 29.20);
--danger: oklch(45.32% 0.1661 27.03);
--danger-foreground: oklch(99.11% 0 0);
/* HeroUI control aliases: primary = L1, secondary = L2. */
--default: var(--surface-secondary);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--default: oklch(94.00% 0.0000 29.20);
--default-foreground: oklch(21.03% 0.0059 29.20);
--field-background: oklch(100.00% 0.0000 29.20);
--field-border: transparent;
--field-foreground: var(--surface-foreground);
--field-placeholder: oklch(55.17% 0.0090 27.03);
--focus: oklch(45.32% 0.1661 27.03);
--foreground: oklch(21.03% 0.0045 27.03);
--muted: oklch(55.17% 0.0090 27.03);
--overlay: var(--surface);
--overlay-foreground: var(--surface-foreground);
--scrollbar: oklch(87.10% 0.0045 27.03);
--segment: oklch(100.00% 0.0045 27.03);
--segment-foreground: oklch(21.03% 0.0045 27.03);
--separator: oklch(92.00% 0.0045 27.03);
--success: oklch(73.29% 0.1952 123.59);
--success-foreground: oklch(21.03% 0.0059 123.59);
--surface: oklch(100.00% 0.0022 27.03);
--surface-foreground: oklch(21.03% 0.0045 27.03);
--surface-secondary: oklch(95.24% 0.0036 27.03);
--surface-secondary-foreground: oklch(21.03% 0.0045 27.03);
--surface-tertiary: oklch(93.73% 0.0036 27.03);
--surface-tertiary-foreground: oklch(21.03% 0.0045 27.03);
--warning: oklch(78.19% 0.1599 45.11);
--warning-foreground: oklch(21.03% 0.0059 45.11);
--field-foreground: oklch(21.03% 0.0000 29.20);
--field-placeholder: oklch(55.17% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20);
--foreground: oklch(21.03% 0.0000 29.20);
--muted: oklch(55.17% 0.0000 29.20);
--overlay: oklch(100.00% 0.0000 29.20);
--overlay-foreground: oklch(21.03% 0.0000 29.20);
--scrollbar: oklch(87.10% 0.0000 29.20);
--segment: oklch(100.00% 0.0000 29.20);
--segment-foreground: oklch(21.03% 0.0000 29.20);
--separator: oklch(92.00% 0.0000 29.20);
--success: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.85);
--surface: oklch(100.00% 0.0000 29.20);
--surface-foreground: oklch(21.03% 0.0000 29.20);
--surface-secondary: oklch(95.24% 0.0000 29.20);
--surface-secondary-foreground: oklch(21.03% 0.0000 29.20);
--surface-tertiary: oklch(93.73% 0.0000 29.20);
--surface-tertiary-foreground: oklch(21.03% 0.0000 29.20);
--warning: oklch(78.19% 0.1585 45.37);
--warning-foreground: oklch(21.03% 0.0059 45.37);
/* Border Radius */
--radius: 0.5rem;
......@@ -51,42 +50,86 @@
/* Font Family */
/* Make sure to load Inter font in your app */
--font-sans: var(--font-inter);
/*
* Xinyuan fixed split-shell contract.
* The application canvas always uses this light theme. The Sidebar is the
* only persistent dark plane and must consume these semantic tokens.
*/
--xinyuan-sidebar-background: oklch(16% 0.020 255);
--xinyuan-sidebar-border: oklch(26% 0.022 255);
--xinyuan-sidebar-foreground: oklch(97% 0.004 255);
--xinyuan-sidebar-muted: oklch(76% 0.018 255);
--xinyuan-sidebar-item-hover: oklch(22% 0.026 255);
--xinyuan-sidebar-item-selected: oklch(28% 0.016 255);
--xinyuan-sidebar-item-selected-foreground: oklch(99% 0.002 255);
--xinyuan-sidebar-focus: oklch(78% 0.105 29.20);
/*
* ECharts is the required chart renderer for Xinyuan applications.
* Ant Design remains the icon source. Resolve these CSS variables to
* concrete colors before assigning them to an ECharts option object.
*/
--xinyuan-chart-1: #d92d20;
--xinyuan-chart-2: #ef4444;
--xinyuan-chart-3: #f97316;
--xinyuan-chart-4: #f59e0b;
--xinyuan-chart-5: #22c55e;
--xinyuan-chart-6: #14b87a;
--xinyuan-chart-7: #06b6d4;
--xinyuan-chart-8: #0ea5e9;
--xinyuan-chart-9: #3b82f6;
--xinyuan-chart-10: #6366f1;
--xinyuan-chart-11: #8b5cf6;
--xinyuan-chart-12: #7c3aed;
--xinyuan-chart-13: #b42318;
--xinyuan-chart-14: #84cc16;
--xinyuan-chart-15: #a855f7;
--xinyuan-chart-16: #64748b;
--xinyuan-chart-grid: #e8e8e8;
--xinyuan-chart-axis: #737373;
--xinyuan-chart-label: #737373;
--xinyuan-chart-tooltip-background: #ffffff;
--xinyuan-chart-tooltip-border: #dedede;
--xinyuan-chart-tooltip-foreground: #222222;
--xinyuan-chart-tooltip-shadow: 0 16px 40px rgb(15 23 42 / 14%);
--xinyuan-chart-legend-font-size: 0.75rem;
--xinyuan-chart-legend-line-height: 1rem;
}
.dark,
[data-theme="dark"] {
color-scheme: dark;
/* Theme Colors (Dark Mode) */
--accent: oklch(45.32% 0.1661 27.03);
--accent: oklch(57.14% 0.2156 29.20);
--accent-foreground: oklch(99.11% 0 0);
--background: oklch(12.00% 0.0045 27.03);
--border: oklch(28.00% 0.0045 27.03);
--danger: oklch(62.00% 0.1600 27.03);
--background: oklch(12.00% 0.0000 29.20);
--border: oklch(28.00% 0.0000 29.20);
--danger: oklch(62% 0.16 27.03);
--danger-foreground: oklch(99.11% 0 0);
/* HeroUI control aliases: primary = L1, secondary = L2. */
--default: var(--surface-secondary);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--default: oklch(27.40% 0.0000 29.20);
--default-foreground: oklch(99.11% 0 0);
--field-background: oklch(21.03% 0.0000 29.20);
--field-border: transparent;
--field-foreground: var(--surface-foreground);
--field-placeholder: oklch(70.50% 0.0090 27.03);
--focus: oklch(45.32% 0.1661 27.03);
--foreground: oklch(99.11% 0.0045 27.03);
--muted: oklch(70.50% 0.0090 27.03);
--overlay: var(--surface);
--overlay-foreground: var(--surface-foreground);
--scrollbar: oklch(70.50% 0.0045 27.03);
--segment: oklch(39.64% 0.0045 27.03);
--segment-foreground: oklch(99.11% 0.0045 27.03);
--separator: oklch(25.00% 0.0045 27.03);
--success: oklch(73.29% 0.1952 123.59);
--success-foreground: oklch(21.03% 0.0059 123.59);
--surface: oklch(21.03% 0.0090 27.03);
--surface-foreground: oklch(99.11% 0.0045 27.03);
--surface-secondary: oklch(25.70% 0.0067 27.03);
--surface-secondary-foreground: oklch(99.11% 0.0045 27.03);
--surface-tertiary: oklch(27.21% 0.0067 27.03);
--surface-tertiary-foreground: oklch(99.11% 0.0045 27.03);
--warning: oklch(82.03% 0.1400 49.12);
--warning-foreground: oklch(21.03% 0.0059 49.12);
--field-foreground: oklch(99.11% 0.0000 29.20);
--field-placeholder: oklch(70.50% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20);
--foreground: oklch(99.11% 0.0000 29.20);
--muted: oklch(70.50% 0.0000 29.20);
--overlay: oklch(21.03% 0.0000 29.20);
--overlay-foreground: oklch(99.11% 0.0000 29.20);
--scrollbar: oklch(70.50% 0.0000 29.20);
--segment: oklch(39.64% 0.0000 29.20);
--segment-foreground: oklch(99.11% 0.0000 29.20);
--separator: oklch(25.00% 0.0000 29.20);
--success: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.85);
--surface: oklch(21.03% 0.0000 29.20);
--surface-foreground: oklch(99.11% 0.0000 29.20);
--surface-secondary: oklch(25.70% 0.0000 29.20);
--surface-secondary-foreground: oklch(99.11% 0.0000 29.20);
--surface-tertiary: oklch(27.21% 0.0000 29.20);
--surface-tertiary-foreground: oklch(99.11% 0.0000 29.20);
--warning: oklch(82.03% 0.1388 49.38);
--warning-foreground: oklch(21.03% 0.0059 49.38);
}
......@@ -27,10 +27,19 @@
var(--background)
);
--rc-text-muted: var(--muted);
--rc-sidebar-background: var(--xinyuan-sidebar-background);
--rc-sidebar-border: var(--xinyuan-sidebar-border);
--rc-sidebar-foreground: var(--xinyuan-sidebar-foreground);
--rc-sidebar-muted: var(--xinyuan-sidebar-muted);
--rc-sidebar-item-hover: var(--xinyuan-sidebar-item-hover);
--rc-sidebar-item-selected: var(--xinyuan-sidebar-item-selected);
--rc-sidebar-item-selected-foreground: var(--xinyuan-sidebar-item-selected-foreground);
--rc-sidebar-focus: var(--xinyuan-sidebar-focus);
--rc-font-sans: var(--font-sans);
--rc-font-medium: 500;
--rc-font-semibold: 600;
--rc-font-bold: 700;
--rc-line-normal: 1.5;
--rc-text-xs: 0.75rem;
--rc-text-sm: 0.875rem;
......@@ -146,6 +155,14 @@
--rc-chart-6: oklch(70% 0.12 80);
--rc-chart-7: oklch(60% 0.1 295);
--rc-chart-8: oklch(57% 0.035 250);
--rc-chart-9: var(--xinyuan-chart-9);
--rc-chart-10: var(--xinyuan-chart-10);
--rc-chart-11: var(--xinyuan-chart-11);
--rc-chart-12: var(--xinyuan-chart-12);
--rc-chart-13: var(--xinyuan-chart-13);
--rc-chart-14: var(--xinyuan-chart-14);
--rc-chart-15: var(--xinyuan-chart-15);
--rc-chart-16: var(--xinyuan-chart-16);
--rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: oklch(47% 0.145 27.03);
--rc-chart-warning: var(--rc-chart-6);
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment