Files
labelmain-demo/components/setting/PageSurface.tsx
2026-05-14 15:12:32 +08:00

422 lines
8.8 KiB
TypeScript

"use client"
import {
Group,
Paper,
Stack,
Text,
type GroupProps,
type PaperProps,
type StackProps,
} from "@mantine/core"
import {
DataTable,
type DataTableDefaultColumnProps,
type DataTableProps,
} from "mantine-datatable"
import type { CSSProperties, PropsWithChildren, ReactNode } from "react"
import classes from "./PageSurface.module.css"
function mergeClassName(...classNames: Array<string | undefined>) {
return classNames.filter(Boolean).join(" ")
}
export const settingModalClassNames = {
content: classes.modalContent,
header: classes.modalHeader,
title: classes.modalTitle,
body: classes.modalBody,
}
export const settingModalFormClassName = classes.modalForm
export const settingModalActionsClassName = classes.modalActions
export const settingModalMetaTextClassName = classes.modalMetaText
export const settingSurfaceClassNames = classes
type SettingTableViewportProps = PropsWithChildren<{
maxHeight: number | string
className?: string
style?: CSSProperties
}>
export function SettingPage({
className,
...props
}: PropsWithChildren<StackProps>) {
return (
<Stack
w="100%"
h="calc(100vh - 60px)"
p="md"
gap="md"
className={mergeClassName(classes.page, className)}
{...props}
/>
)
}
export function SettingPanel({
className,
withBorder = true,
radius = "sm",
p = "md",
shadow,
...props
}: PropsWithChildren<PaperProps>) {
return (
<Paper
withBorder={withBorder}
radius={radius}
p={p}
shadow={shadow}
className={mergeClassName(classes.panel, className)}
{...props}
/>
)
}
export function SettingTableViewport({
maxHeight,
className,
style,
children,
}: SettingTableViewportProps) {
return (
<div
style={{ maxHeight, ...style }}
className={mergeClassName(classes.tableViewport, className)}>
{children}
</div>
)
}
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 className={classes.listHeaderTitle}>{title}</Text>
{count !== undefined ? (
<Text className={classes.listHeaderCount}>{count}</Text>
) : 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}
/>
)
}
export function SettingFilterStack({
className,
gap = 12,
...props
}: PropsWithChildren<StackProps>) {
return (
<Stack
gap={gap}
className={mergeClassName(classes.filterStack, className)}
{...props}
/>
)
}
type SettingInlineFilterFieldProps = {
label: ReactNode
children: ReactNode
className?: string
labelClassName?: string
controlClassName?: string
labelWidth?: number | string
}
export function SettingInlineFilterField({
label,
children,
className,
labelClassName,
controlClassName,
labelWidth = 80,
}: SettingInlineFilterFieldProps) {
const style = {
"--setting-inline-filter-label-width":
typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth,
} as CSSProperties
return (
<div
style={style}
className={mergeClassName(classes.filterField, className)}>
<Text className={mergeClassName(classes.filterLabel, labelClassName)}>
{label}
</Text>
<div className={mergeClassName(classes.filterControl, controlClassName)}>
{children}
</div>
</div>
)
}
const settingDataTableStyles = {
header: {
backgroundColor: "#F7F8FA",
borderBottom: "1px solid #E5E6EB",
color: "#4E5969",
fontSize: "14px",
fontWeight: 500,
},
root: {
maxWidth: "100%",
minWidth: 0,
fontSize: "14px",
color: "#1D2129",
borderBottom: "1px solid #E5E6EB",
borderRadius: 4,
overflow: "hidden",
boxSizing: "border-box",
backgroundColor: "#FFFFFF",
},
table: {
backgroundColor: "#FFFFFF",
},
footer: {
backgroundColor: "#FFFFFF",
},
pagination: {
padding: "12px 16px",
borderTop: "1px solid #E5E6EB",
},
} as const
const settingDataTableDefaultColumnProps: DataTableDefaultColumnProps<
Record<string, unknown>
> = {
titleStyle: {
whiteSpace: "nowrap",
fontWeight: 500,
},
}
export function SettingDataTable<T = Record<string, unknown>>(
props: DataTableProps<T>
) {
const {
styles,
defaultColumnProps,
withTableBorder,
withRowBorders,
striped,
highlightOnHover,
loaderBackgroundBlur,
loadingText,
noRecordsText,
minHeight,
maxHeight,
height,
page,
onPageChange,
totalRecords,
recordsPerPage,
recordsPerPageLabel,
paginationText,
scrollAreaProps,
...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 ?? false,
highlightOnHover: highlightOnHover ?? false,
defaultColumnProps:
defaultColumnProps ??
(settingDataTableDefaultColumnProps as DataTableDefaultColumnProps<T>),
loaderBackgroundBlur: loaderBackgroundBlur ?? 0,
noRecordsText: noRecordsText ?? "暂无数据",
minHeight: minHeight ?? 360,
maxHeight,
height: height ?? (maxHeight !== undefined ? "auto" : undefined),
scrollAreaProps: {
type: "auto",
...scrollAreaProps,
},
styles: styles
? { ...settingDataTableStyles, ...styles }
: settingDataTableStyles,
...paginationProps,
...rest,
} as DataTableProps<T>
return <DataTable {...tableProps} />
}
export const settingTabsStyles = {
list: {
padding: 4,
backgroundColor: "#F7F8FA",
border: "1px solid #E5E6EB",
borderRadius: 4,
gap: 8,
},
tab: {
minHeight: 32,
borderRadius: 4,
padding: "0 12px",
fontSize: 14,
fontWeight: 500,
lineHeight: "20px",
},
tabLabel: {
color: "inherit",
fontSize: 14,
lineHeight: "20px",
},
panel: {
flex: 1,
minHeight: 0,
minWidth: 0,
paddingTop: 16,
},
} as const
export const settingTabsClassNames = {
list: classes.settingTabsList,
tab: classes.settingTabsTab,
tabLabel: classes.settingTabsTabLabel,
panel: classes.settingTabsPanel,
} as const