Files
desa-darmasaba/src/components/admin/UnifiedTypography.tsx
2026-02-25 21:18:26 +08:00

295 lines
6.3 KiB
TypeScript

'use client';
import { useDarkMode } from '@/state/darkModeStore';
import { themeTokens, getResponsiveFz } from '@/utils/themeTokens';
import { Text, Title, Box, BoxProps } from '@mantine/core';
import React from 'react';
type TextTruncate = 'end' | 'start' | boolean;
/**
* Unified Typography Components
*
* Komponen text dengan styling konsisten di seluruh aplikasi
* Mendukung dark mode sesuai spesifikasi darkMode.md
*
* Usage:
* import { UnifiedText, UnifiedTitle } from '@/components/admin/UnifiedTypography';
*
* <UnifiedTitle order={1}>Judul Halaman</UnifiedTitle>
* <UnifiedText size="body">Konten teks</UnifiedText>
*/
// ============================================================================
// Unified Title Component
// ============================================================================
interface UnifiedTitleProps {
order?: 1 | 2 | 3 | 4 | 5 | 6;
children: React.ReactNode;
align?: 'left' | 'center' | 'right';
color?: 'primary' | 'secondary' | 'brand' | string;
mb?: string;
mt?: string;
ml?: string;
mr?: string;
mx?: string;
my?: string;
style?: React.CSSProperties;
}
export function UnifiedTitle({
order = 1,
children,
align = 'left',
color = 'primary',
mb,
mt,
ml,
mr,
mx,
my,
style,
}: UnifiedTitleProps) {
const { isDark } = useDarkMode();
const tokens = themeTokens(isDark);
const responsiveFz = getResponsiveFz(isDark);
const getTypography = () => {
switch (order) {
case 1:
return tokens.typography.h1;
case 2:
return tokens.typography.h2;
case 3:
return tokens.typography.h3;
case 4:
return tokens.typography.h4;
default:
return tokens.typography.body;
}
};
const typo = getTypography();
const getColor = () => {
if (color === 'primary') return tokens.colors.text.primary;
if (color === 'secondary') return tokens.colors.text.secondary;
if (color === 'brand') return tokens.colors.text.brand;
return color;
};
return (
<Title
order={order}
ta={align}
fz={{ base: responsiveFz.base, md: typo.fz }}
fw={typo.fw}
lh={typo.lh}
c={getColor()}
mb={mb}
mt={mt}
ml={ml}
mr={mr}
mx={mx}
my={my}
style={style}
>
{children}
</Title>
);
}
// ============================================================================
// Unified Text Component
// ============================================================================
interface UnifiedTextProps {
size?: 'small' | 'body' | 'label';
weight?: 'normal' | 'medium' | 'bold';
children: React.ReactNode;
align?: 'left' | 'center' | 'right';
color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string;
lineClamp?: number;
truncate?: TextTruncate;
span?: boolean;
mt?: string;
mb?: string;
ml?: string;
mr?: string;
mx?: string;
my?: string;
style?: React.CSSProperties;
}
export function UnifiedText({
size = 'body',
weight = 'normal',
children,
align = 'left',
color = 'primary',
lineClamp,
truncate,
span = false,
mt,
mb,
ml,
mr,
mx,
my,
style,
}: UnifiedTextProps) {
const { isDark } = useDarkMode();
const tokens = themeTokens(isDark);
const getTypography = () => {
switch (size) {
case 'small':
return tokens.typography.small;
case 'label':
return tokens.typography.label;
default:
return tokens.typography.body;
}
};
const getWeight = () => {
switch (weight) {
case 'normal':
return 400;
case 'medium':
return 500;
case 'bold':
return 700;
default:
return 400;
}
};
const getColor = () => {
switch (color) {
case 'primary':
return tokens.colors.text.primary;
case 'secondary':
return tokens.colors.text.secondary;
case 'tertiary':
return tokens.colors.text.tertiary;
case 'muted':
return tokens.colors.text.muted;
case 'brand':
return tokens.colors.text.brand;
case 'link':
return tokens.colors.text.link;
default:
return color;
}
};
const typo = getTypography();
const fw = getWeight();
const textColor = getColor();
if (span) {
return (
<Text
ta={align}
fz={typo.fz}
fw={fw}
lh={typo.lh}
c={textColor}
lineClamp={lineClamp}
truncate={truncate}
mt={mt}
mb={mb}
ml={ml}
mr={mr}
mx={mx}
my={my}
style={style}
>
{children}
</Text>
);
}
return (
<Text
ta={align}
fz={typo.fz}
fw={fw}
lh={typo.lh}
c={textColor}
lineClamp={lineClamp}
truncate={truncate}
mt={mt}
mb={mb}
ml={ml}
mr={mr}
mx={mx}
my={my}
style={style}
>
{children}
</Text>
);
}
// ============================================================================
// Unified Page Header Component
//
// Header standar untuk setiap halaman admin
// Sesuai spesifikasi: Section Header dengan font weight lebih besar
// ============================================================================
interface UnifiedPageHeaderProps extends BoxProps {
title: string;
subtitle?: string;
action?: React.ReactNode;
showBorder?: boolean;
}
export function UnifiedPageHeader({
title,
subtitle,
action,
showBorder = true,
style,
...props
}: UnifiedPageHeaderProps) {
const { isDark } = useDarkMode();
const tokens = themeTokens(isDark);
return (
<Box
mb="lg"
style={{
borderBottom: showBorder ? `1px solid ${tokens.colors.border.soft}` : 'none',
paddingBottom: showBorder ? tokens.spacing.md : 0,
...style,
}}
{...props}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: tokens.spacing.md,
}}
>
<div>
<UnifiedTitle order={3} color="primary">{title}</UnifiedTitle>
{subtitle && (
<UnifiedText size="small" color="secondary" mt="xs">
{subtitle}
</UnifiedText>
)}
</div>
{action && <div>{action}</div>}
</div>
</Box>
);
}
export default UnifiedText;