Files
labelimage/components/setting/PageSurface.tsx
2026-04-03 15:12:00 +08:00

313 lines
6.3 KiB
TypeScript

"use client"
import {
Badge,
Group,
Paper,
Stack,
Text,
type GroupProps,
type PaperProps,
type StackProps,
} from "@mantine/core"
import type { PropsWithChildren, ReactNode } from "react"
import {
DataTable,
type DataTableDefaultColumnProps,
type DataTableProps,
} from "mantine-datatable"
import classes from "./PageSurface.module.css"
function mergeClassName(...classNames: Array<string | undefined>) {
return classNames.filter(Boolean).join(" ")
}
export function SettingPage({
className,
...props
}: PropsWithChildren<StackProps>) {
return (
<Stack
w="100%"
h="calc(100vh - 56px)"
p="md"
gap="md"
className={mergeClassName(classes.page, className)}
{...props}
/>
)
}
export function SettingPanel({
className,
withBorder = true,
radius = "lg",
p = "md",
shadow = "xs",
...props
}: PropsWithChildren<PaperProps>) {
return (
<Paper
withBorder={withBorder}
radius={radius}
p={p}
shadow={shadow}
className={mergeClassName(classes.panel, className)}
{...props}
/>
)
}
export function SettingFilterPanel({
className,
...props
}: PropsWithChildren<PaperProps>) {
return (
<SettingPanel
className={mergeClassName(classes.filterPanel, className)}
{...props}
/>
)
}
export function SettingContentPanel({
className,
...props
}: PropsWithChildren<PaperProps>) {
return (
<SettingPanel
className={mergeClassName(classes.contentPanel, className)}
{...props}
/>
)
}
type SettingSectionHeaderProps = GroupProps & {
eyebrow?: string
title: ReactNode
description?: ReactNode
actions?: ReactNode
}
export function SettingSectionHeader({
eyebrow,
title,
description,
actions,
className,
...props
}: SettingSectionHeaderProps) {
return (
<Group
justify="space-between"
align="flex-start"
gap="sm"
wrap="wrap"
className={mergeClassName(classes.sectionHeader, className)}
{...props}>
<Stack gap={2}>
{eyebrow ? (
<Text className={classes.sectionEyebrow}>{eyebrow}</Text>
) : null}
<Text className={classes.sectionTitle}>{title}</Text>
{description ? (
<Text className={classes.sectionDescription}>{description}</Text>
) : null}
</Stack>
{actions}
</Group>
)
}
type SettingListHeaderProps = {
title: ReactNode
count?: ReactNode
actions?: ReactNode
className?: string
}
export function SettingListHeader({
title,
count,
actions,
className,
}: SettingListHeaderProps) {
return (
<Group
justify="space-between"
align="center"
wrap="wrap"
gap="sm"
className={mergeClassName(classes.listHeader, className)}>
<Group gap="xs">
<Text fw={700}>{title}</Text>
{count !== undefined ? (
<Badge variant="light" color="gray">
{count}
</Badge>
) : null}
</Group>
{actions}
</Group>
)
}
export function SettingHeaderActions({
className,
gap = "sm",
...props
}: PropsWithChildren<GroupProps>) {
return (
<Group
gap={gap}
wrap="wrap"
className={mergeClassName(classes.headerActions, className)}
{...props}
/>
)
}
export function SettingFilterActions({
className,
gap = "sm",
justify = "flex-end",
mt = "md",
...props
}: PropsWithChildren<GroupProps>) {
return (
<Group
justify={justify}
gap={gap}
wrap="wrap"
mt={mt}
className={mergeClassName(classes.filterActions, className)}
{...props}
/>
)
}
const settingDataTableStyles = {
header: {
backgroundColor: "#F8F9FB",
borderBottom: "1px solid #EEF2F6",
color: "#56606A",
fontSize: "14px",
},
root: {
height: "100%",
fontSize: "14px",
color: "#31373D",
borderBottom: "1px solid #E8EDF3",
borderRadius: 10,
overflow: "hidden",
backgroundColor: "#FFFFFF",
},
pagination: {
padding: "14px 16px 18px",
},
} as const
const settingDataTableDefaultColumnProps: DataTableDefaultColumnProps<
Record<string, unknown>
> = {
titleStyle: {
whiteSpace: "nowrap",
fontWeight: 600,
},
}
export function SettingDataTable<T = Record<string, unknown>>(
props: DataTableProps<T>
) {
const {
styles,
defaultColumnProps,
withTableBorder,
withRowBorders,
striped,
highlightOnHover,
loaderBackgroundBlur,
loadingText,
noRecordsText,
minHeight,
page,
onPageChange,
totalRecords,
recordsPerPage,
recordsPerPageLabel,
paginationText,
...rest
} = props
const paginationProps =
page !== undefined &&
onPageChange !== undefined &&
recordsPerPage !== undefined
? {
page,
onPageChange,
totalRecords,
recordsPerPage,
loadingText: loadingText ?? "正在加载数据...",
recordsPerPageLabel: recordsPerPageLabel ?? "每页条数",
paginationText:
paginationText ??
(({
from,
to,
totalRecords,
}: {
from: number
to: number
totalRecords: number
}) => `${from}-${to} / ${totalRecords}`),
}
: {}
const tableProps = {
withTableBorder: withTableBorder ?? true,
withRowBorders: withRowBorders ?? true,
striped: striped ?? true,
highlightOnHover: highlightOnHover ?? true,
defaultColumnProps:
defaultColumnProps ??
(settingDataTableDefaultColumnProps as DataTableDefaultColumnProps<T>),
loaderBackgroundBlur: loaderBackgroundBlur ?? 1,
noRecordsText: noRecordsText ?? "暂无数据",
minHeight: minHeight ?? 360,
styles: styles
? { ...settingDataTableStyles, ...styles }
: settingDataTableStyles,
...paginationProps,
...rest,
} as DataTableProps<T>
return <DataTable {...tableProps} />
}
export const settingTabsStyles = {
list: {
padding: 4,
backgroundColor: "#F8F9FB",
border: "1px solid #EEF2F6",
borderRadius: 10,
gap: 8,
},
tab: {
minHeight: 36,
borderRadius: 8,
color: "#56606A",
fontWeight: 600,
"&[data-active]": {
backgroundColor: "#FFFFFF",
color: "#1F2329",
boxShadow: "0 6px 18px rgba(15, 23, 42, 0.08)",
},
},
panel: {
flex: 1,
minHeight: 0,
paddingTop: 16,
},
} as const