perf(style): codex-skills/cowa-admin-list-ui
This commit is contained in:
81
components/layout/AppLayout.module.css
Normal file
81
components/layout/AppLayout.module.css
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
29
components/layout/navStyles.ts
Normal file
29
components/layout/navStyles.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user