perf(style): codex-skills/cowa-admin-list-ui

This commit is contained in:
zhangheng
2026-04-28 14:36:36 +08:00
parent 2e33b6fd7e
commit 9d9bf4ff7f
20 changed files with 2672 additions and 812 deletions

View File

@@ -0,0 +1,81 @@
.header {
border-bottom: 1px solid #e5e6eb;
background: #ffffff;
}
.headerInner {
height: 100%;
padding: 0 16px;
}
.brand {
display: flex;
width: 196px;
align-items: center;
gap: 8px;
color: #1d2129;
font-size: 14px;
font-weight: 500;
line-height: 22px;
}
.brandMark {
display: inline-flex;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
border-radius: 4px;
background: #1874ff;
color: #ffffff;
font-size: 11px;
font-weight: 600;
}
.headerNav {
display: flex;
flex: 1;
align-items: center;
gap: 8px;
height: 100%;
}
.headerUser {
display: flex;
align-items: center;
gap: 6px;
}
.navbar {
border-right: 1px solid #e5e6eb;
background: #ffffff;
transition: width 0.2s ease;
}
.navbarScroll {
height: calc(100vh - 108px);
padding: 8px 8px 0;
}
.navbarFooter {
border-top: 1px solid #e5e6eb;
padding: 6px 0 0;
}
.main {
background: #f7f8fa;
}
.userName {
color: #1d2129;
font-size: 14px;
font-weight: 500;
line-height: 18px;
}
.userBaseCity {
color: #86909c;
font-size: 12px;
line-height: 14px;
}

View File

@@ -20,7 +20,12 @@ import {
import Avvvatars from "avvvatars-react"
import { useDisclosure, useMediaQuery } from "@mantine/hooks"
import { IconLogout, IconMoon, IconSun } from "@tabler/icons-react"
import {
IconChevronDown,
IconLogout,
IconMoon,
IconSun,
} from "@tabler/icons-react"
import cx from "clsx"
import { Suspense, useEffect, useMemo, useState } from "react"
import actionToggleClasses from "./ActionToggle.module.css"
@@ -30,9 +35,11 @@ import { useUserStore } from "@/app/store/user"
import { usePathname, useRouter } from "next/navigation"
import { usePermissionStore } from "../label/store/auth"
import { deleteCookie } from "../login/api"
import classes from "./AppLayout.module.css"
import { MenuItem, withoutLayoutRoutes } from "./common"
import { ClientIcon } from "./components/ClientIcon"
import { LinksGroup } from "./components/NavbarLinks"
import { getShellNavLinkStyles } from "./navStyles"
import { useAppLayoutStore } from "./store"
const COMPOUND_SURNAMES = new Set([
@@ -182,7 +189,7 @@ export default function AppLayout({
window.location.href = toPathname
// router.replace(toPathname)
}}>
<ClientIcon icon={item.icon} style={{ width: "1rem", height: "1rem" }} />
<ClientIcon icon={item.icon} style={{ width: 14, height: 14 }} />
{item.title}
</a>
))
@@ -243,6 +250,7 @@ export default function AppLayout({
}
const matches = useMediaQuery("(min-width: 48em)")
const userName = usePermissionStore((s) => s.user_name)
const detailInfo = usePermissionStore((s) => s.detailInfo)
const avatarDisplayValue = useMemo(
() => getAvatarDisplayValue(userName),
[userName]
@@ -253,28 +261,32 @@ export default function AppLayout({
<Suspense fallback={<></>}>{children}</Suspense>
) : (
<AppShell
header={{ height: 55 }}
header={{ height: 60 }}
navbar={{
width: isOpen ? 200 : 40,
width: isOpen ? 196 : 48,
breakpoint: "sm",
collapsed: { mobile: !mobileOpened },
}}>
<AppShell.Header>
<Flex h="100%" px="md" align="center">
<AppShell.Header className={classes.header}>
<Flex className={classes.headerInner} align="center">
<Burger
opened={mobileOpened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Flex flex={1} gap={0} h="100%" align="center">
<Box w={184} visibleFrom="sm">
<Flex className={classes.headerNav}>
<Box className={classes.brand} visibleFrom="sm">
<span className={classes.brandMark}>C</span>
</Box>
<Space w={"1rem"} hiddenFrom="sm" />
{headItems}
</Flex>
<Flex justify="center" align="center" w={"max-content"}>
<Flex
className={classes.headerUser}
justify="center"
w={"max-content"}>
<UnstyledButton
display="none"
onClick={() =>
@@ -301,12 +313,28 @@ export default function AppLayout({
<Divider orientation="vertical" mx={"0.5rem"} display="none" />
<Menu shadow="md" width={220}>
<Menu.Target>
<UnstyledButton>
<Flex
align="center"
gap={12}
style={{
cursor: "pointer",
}}>
<Avvvatars
value={userName || "-"}
displayValue={avatarDisplayValue}
size={32}
/>
</UnstyledButton>
<Stack gap={1}>
<Text className={classes.userName}>{userName}</Text>
<Text className={classes.userBaseCity}>
{detailInfo?.base_city || "-"}
</Text>
</Stack>
<IconChevronDown
className={classes.userBaseCity}
size={12}
/>
</Flex>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label></Menu.Label>
@@ -327,9 +355,9 @@ export default function AppLayout({
</Flex>
</Flex>
</AppShell.Header>
<AppShell.Navbar style={{ transition: "width 0.2s ease" }}>
<AppShell.Navbar className={classes.navbar}>
<Stack justify="start" gap={4} flex={1}>
<ScrollArea h="calc(100vh - 95px)">
<ScrollArea className={classes.navbarScroll}>
{activeHeaderMenu?.items?.map((item) => {
if (item.items?.length) {
if (matches) {
@@ -359,11 +387,12 @@ export default function AppLayout({
item.icon && (
<ClientIcon
icon={item.icon}
style={{ width: "1rem", height: "1rem" }}
style={{ width: 14, height: 14 }}
/>
)
}
label={item.title}
styles={getShellNavLinkStyles(false)}
/>
</Menu.Target>
<Menu.Dropdown>
@@ -390,6 +419,10 @@ export default function AppLayout({
)
}
} else {
const isActive = pathname.startsWith(
`/${activeHeaderMenu.url}/${item.url}`
)
return (
<Tooltip
key={item.url}
@@ -410,16 +443,15 @@ export default function AppLayout({
router.push(`/${activeHeaderMenu.url}/${item.url}`)
}}
noWrap={true}
active={pathname.startsWith(
`/${activeHeaderMenu.url}/${item.url}`
)}
active={isActive}
leftSection={
<ClientIcon
icon={item.icon}
style={{ width: "1rem", height: "1rem" }}
style={{ width: 14, height: 14 }}
/>
}
label={item.title}
styles={getShellNavLinkStyles(isActive)}
/>
</Tooltip>
)
@@ -427,7 +459,7 @@ export default function AppLayout({
})}
</ScrollArea>
</Stack>
<Stack justify="center" gap={0}>
<Stack justify="center" gap={0} className={classes.navbarFooter}>
<Flex
justify={isOpen ? "flex-end" : "center"}
align="center"
@@ -442,8 +474,7 @@ export default function AppLayout({
</Flex>
</Stack>
</AppShell.Navbar>
<AppShell.Main
bg={computedColorScheme === "light" ? "gray.0" : "gray.8"}>
<AppShell.Main className={classes.main}>
<Suspense fallback={<></>}>{children}</Suspense>
</AppShell.Main>
</AppShell>

View File

@@ -1,27 +1,30 @@
.link {
display: flex;
height: 100%;
height: 40px;
justify-content: center;
align-items: center;
padding: 8px 16px;
gap: 0.5rem;
padding: 0 14px;
gap: 6px;
text-decoration: none;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
font-size: var(--mantine-font-size-lg);
border-radius: 4px;
color: #1d2129;
font-size: 16px;
font-weight: 500;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition:
background-color 0.2s ease,
color 0.2s ease;
@mixin hover {
background-color: light-dark(
var(--mantine-color-gray-0),
var(--mantine-color-dark-6)
);
background-color: #eef4ff;
color: #1874ff;
}
[data-mantine-color-scheme] &[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
&[data-active] {
background-color: #1874ff;
color: #ffffff;
}
}

View File

@@ -5,6 +5,7 @@ import { NavLink, Space } from "@mantine/core"
import { usePathname, useRouter } from "next/navigation"
import { useState } from "react"
import { MenuItem } from "../common"
import { getShellNavLinkStyles } from "../navStyles"
import { ClientIcon } from "./ClientIcon"
interface LinksGroupProps {
@@ -50,6 +51,10 @@ export function LinksGroup({
))
: null
const isActive = hasLinks
? false
: pathname.startsWith(`/${routerUrl}/${item.url}`)
return (
<>
<NavLink
@@ -63,9 +68,7 @@ export function LinksGroup({
opened={opened}
label={item.title}
noWrap={true}
active={
hasLinks ? false : pathname.startsWith(`/${routerUrl}/${item.url}`)
}
active={isActive}
leftSection={
<>
<Space w={offset} />
@@ -77,7 +80,8 @@ export function LinksGroup({
)}
</>
}
childrenOffset={0}>
childrenOffset={0}
styles={getShellNavLinkStyles(isActive)}>
{children}
</NavLink>
</>

View File

@@ -0,0 +1,29 @@
export function getShellNavLinkStyles(active: boolean) {
return {
root: {
minHeight: 40,
borderRadius: 4,
padding: "8px 12px",
color: active ? "#1874FF" : "#4E5969",
backgroundColor: active ? "#E7EFFF" : "transparent",
transition: "background-color 0.2s ease, color 0.2s ease",
},
body: {
gap: 8,
},
label: {
fontSize: 14,
fontWeight: 500,
lineHeight: "22px",
},
section: {
color: active ? "#1874FF" : "inherit",
},
chevron: {
color: active ? "#1874FF" : "#86909C",
},
children: {
paddingLeft: 0,
},
} as const
}