feat: update theme provider and density styles, enhance typography and font mappings, and refactor showcase components

This commit is contained in:
Firman Ramdhani
2026-02-27 17:02:32 +07:00
parent 3439c7c606
commit 69e26e96e3
16 changed files with 293 additions and 77 deletions
+1 -1
View File
@@ -4,5 +4,5 @@
"mode": "auto"
}
],
"cSpell.words": ["mantine", "Millis", "Pandang", "Ujung", "WITA"]
"cSpell.words": ["mantine", "Menlo", "Millis", "Pandang", "Segoe", "Ujung", "WITA"]
}
+1 -1
View File
@@ -9,7 +9,7 @@ const ShowcaseView = lazy(() => import('./showcase/showcase-view'));
export default function App() {
const [colorScheme, setColorScheme] = useState<ColorSchemeType>('light');
const [density, setDensity] = useState<DensityType>('standard');
const [density, setDensity] = useState<DensityType>('compact');
return (
<ThemeProvider colorScheme={colorScheme} density={density}>
+157 -32
View File
@@ -1,5 +1,24 @@
import { ColorSchemeType, DensityType } from '@repo/ui/provider';
import { Button, Card, Container, Select, Group, Stack, Text, Title, TextInput, Checkbox } from '@repo/ui/components';
import {
Button,
Card,
Container,
Select,
Group,
Stack,
Text,
Title,
TextInput,
Checkbox,
PasswordInput,
NumberInput,
Textarea,
Switch,
Radio,
Table,
Badge,
Divider,
} from '@repo/ui/components';
interface ShowcaseViewProps {
colorScheme: ColorSchemeType;
@@ -9,15 +28,24 @@ interface ShowcaseViewProps {
}
export default function ShowcaseView({ colorScheme, setColorScheme, density, setDensity }: ShowcaseViewProps) {
return (
<Container size="md" py="xl">
<Stack gap="xl">
<Title order={1}>UI Gateway Showcase</Title>
// Mock data for the table
const tableData = [
{ id: 'ORD-001', customer: 'John Doe', status: 'Shipped', total: '$120.00' },
{ id: 'ORD-002', customer: 'Jane Smith', status: 'Pending', total: '$85.50' },
{ id: 'ORD-003', customer: 'Acme Corp', status: 'Delivered', total: '$1,250.00' },
];
{/* Control Panel */}
return (
<Container size="lg" py="xl">
<Stack gap="xl">
<Title order={1}>Super App UI Showcase</Title>
{/* =========================================
CONTROL PANEL
========================================= */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={3} mb="md">
Control Panel
Theme Controls
</Title>
<Group grow align="flex-end">
<Select
@@ -30,55 +58,152 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
]}
/>
<Select
label="Density"
label="Density (Spacing & Sizing)"
value={density}
onChange={(val: string | null) => setDensity((val as DensityType) || 'standard')}
data={[
{ value: 'compact', label: 'Compact' },
{ value: 'standard', label: 'Standard' },
{ value: 'spacious', label: 'Spacious' },
{ value: 'compact', label: 'Compact (ERP Mode)' },
{ value: 'standard', label: 'Standard (UI Mode)' },
]}
/>
</Group>
</Card>
{/* Showcase Area */}
{/* =========================================
TAILWIND V4 BRIDGE TEST
========================================= */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={3} mb="md">
Tailwind v4 Synchronization
</Title>
{/* This div purely uses Tailwind classes to prove it inherits Mantine's variables */}
<div className="bg-brand-500 text-brand-50 p-md rounded-md shadow-md text-base">
<span className="font-bold">Tailwind works!</span> The padding (p-md), border-radius (rounded-md), text size
(text-base), and background color of this box are entirely controlled by the ThemeProvider's current state.
</div>
</Card>
{/* =========================================
TYPOGRAPHY & BUTTONS
========================================= */}
<Card withBorder shadow="sm" radius="md" p="md">
<Stack gap="lg">
<div className="bg-brand-100">
<Title order={3} mb="sm">
Typography
<div>
<Title order={3} mb="xs">
Typography & Badges
</Title>
<Text size="sm" c="dimmed">
This is dimmed small text.
This is dimmed small text indicating a subtitle.
</Text>
<Text mb="md">
This is standard text describing the components below. Watch how the font changes when you switch
density.
</Text>
<Text>This is standard text describing the components below.</Text>
</div>
<div>
<Title order={3} mb="sm">
Buttons
</Title>
<Group>
<Button variant="filled">Filled Button</Button>
<Button variant="outline">Outline Button</Button>
<Button variant="light">Light Button</Button>
<Button variant="subtle">Subtle Button</Button>
<Badge color="brand">Brand Badge</Badge>
<Badge color="success" variant="light">
Success Status
</Badge>
<Badge color="error" variant="outline">
Error State
</Badge>
</Group>
</div>
<Divider />
<div>
<Title order={3} mb="md">
Form Elements
Buttons
</Title>
<Group grow>
<TextInput label="First Name" placeholder="John" />
<TextInput label="Last Name" placeholder="Doe" />
<Group>
<Button variant="filled" color="brand">
Filled Button
</Button>
<Button variant="outline" color="brand">
Outline Button
</Button>
<Button variant="light" color="info">
Light Info
</Button>
<Button variant="subtle" color="error">
Cancel
</Button>
</Group>
<Checkbox label="I agree to the terms and conditions" mt="md" defaultChecked />
</div>
</Stack>
</Card>
{/* =========================================
COMPLEX FORMS (ERP STYLE)
========================================= */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={3} mb="md">
Form Elements
</Title>
<Stack gap="md">
<Group grow align="flex-start">
<TextInput label="First Name" placeholder="Enter your first name" withAsterisk />
<TextInput label="Last Name" placeholder="Enter your last name" />
</Group>
<Group grow align="flex-start">
<NumberInput label="Age" placeholder="25" min={0} max={100} />
<PasswordInput label="Password" placeholder="Your secret password" withAsterisk />
</Group>
<Textarea label="Bio" placeholder="Tell us about yourself" minRows={3} />
<Group mt="sm">
<Checkbox label="I agree to the terms and conditions" defaultChecked />
<Switch label="Enable notifications" defaultChecked />
</Group>
<Radio.Group name="favoriteFramework" label="Select your favorite framework" withAsterisk>
<Group mt="xs">
<Radio value="react" label="React" />
<Radio value="svelte" label="Svelte" />
<Radio value="vue" label="Vue" />
</Group>
</Radio.Group>
</Stack>
</Card>
{/* =========================================
DATA GRID / TABLE
========================================= */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={3} mb="md">
Data Grid
</Title>
<Table striped highlightOnHover withTableBorder withColumnBorders>
<Table.Thead>
<Table.Tr>
<Table.Th>Order ID</Table.Th>
<Table.Th>Customer</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th>Total Amount</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{tableData.map((row) => (
<Table.Tr key={row.id}>
<Table.Td>{row.id}</Table.Td>
<Table.Td>{row.customer}</Table.Td>
<Table.Td>
<Badge
size="sm"
color={row.status === 'Delivered' ? 'success' : row.status === 'Shipped' ? 'info' : 'warning'}
>
{row.status}
</Badge>
</Table.Td>
<Table.Td>{row.total}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Card>
</Stack>
</Container>
);
+14 -9
View File
@@ -1,12 +1,12 @@
import { MantineProvider, createTheme, MantineThemeOverride } from '@mantine/core';
import { MantineProvider, createTheme, mergeThemeOverrides } from '@mantine/core';
import React from 'react';
import { brandColors, errorColors, warningColors, successColors, infoColors } from '../theme/tokens/colors';
import { typography } from '../theme/tokens/typography';
import { radius } from '../theme/tokens/radius';
import { densityStyles } from '../theme/tokens/density';
import { compactDensity, standardDensity } from '../theme/tokens/density';
export type ColorSchemeType = 'light' | 'dark';
export type DensityType = 'compact' | 'standard' | 'spacious';
export type DensityType = 'compact' | 'standard';
interface ThemeProviderProps {
children: React.ReactNode;
@@ -14,6 +14,11 @@ interface ThemeProviderProps {
density?: DensityType;
}
const densityMap = {
compact: compactDensity,
standard: standardDensity,
};
export function ThemeProvider({ children, colorScheme = 'light', density = 'standard' }: ThemeProviderProps) {
const baseTheme = createTheme({
colors: {
@@ -25,19 +30,19 @@ export function ThemeProvider({ children, colorScheme = 'light', density = 'stan
},
primaryColor: 'brand',
fontFamily: typography.fontFamily,
fontFamilyMonospace: typography.fontFamilyMonospace,
headings: typography.headings,
spacing: densityStyles[density].spacing,
radius: radius,
});
const mantineColorScheme: 'light' | 'dark' = colorScheme;
const themeOverride: MantineThemeOverride = baseTheme;
const selectedDensity = densityMap[density];
const mergedTheme = mergeThemeOverrides(baseTheme, selectedDensity);
return (
<MantineProvider
theme={themeOverride}
forceColorScheme={mantineColorScheme}
defaultColorScheme={mantineColorScheme}
theme={mergedTheme}
forceColorScheme={colorScheme}
defaultColorScheme={colorScheme}
>
{children}
</MantineProvider>
+19
View File
@@ -3,6 +3,13 @@
@source "../src";
@theme {
/* =========================================
FONT FAMILY MAPPING (Mantine -> Tailwind)
========================================= */
--base-font-size: 13px;
--font-sans: var(--mantine-font-family);
--font-mono: var(--mantine-font-family-monospace);
/* =========================================
1. COLORS (Strictly mapped to Mantine 0-9)
========================================= */
@@ -244,3 +251,15 @@
}
}
}
/* =========================================
BASE RESETS
========================================= */
@layer base {
body {
font-size: var(--base-font-size);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
+96 -7
View File
@@ -1,11 +1,100 @@
export const densityStyles = {
compact: {
spacing: { xs: '0.25rem', sm: '0.5rem', md: '0.75rem', lg: '1rem', xl: '1.25rem' },
},
standard: {
import { MantineThemeOverride } from '@mantine/core';
export const standardDensity: MantineThemeOverride = {
spacing: { xs: '0.5rem', sm: '0.75rem', md: '1rem', lg: '1.5rem', xl: '2rem' },
fontSizes: { xs: '0.75rem', sm: '0.875rem', md: '1rem', lg: '1.125rem', xl: '1.25rem' },
components: {
// Inputs & Buttons
Button: { defaultProps: { size: 'md' } },
TextInput: { defaultProps: { size: 'md' } },
Select: { defaultProps: { size: 'md' } },
NumberInput: { defaultProps: { size: 'md' } },
PasswordInput: { defaultProps: { size: 'md' } },
Textarea: { defaultProps: { size: 'md' } },
MultiSelect: { defaultProps: { size: 'md' } },
DatePickerInput: { defaultProps: { size: 'md' } },
// Toggles
Checkbox: { defaultProps: { size: 'md' } },
Radio: { defaultProps: { size: 'md' } },
Switch: { defaultProps: { size: 'md' } },
// Data Display & Layout
Badge: { defaultProps: { size: 'md' } },
Pagination: { defaultProps: { size: 'md' } },
Table: { defaultProps: { verticalSpacing: 'sm', horizontalSpacing: 'md' } },
Card: { defaultProps: { padding: 'md' } },
Modal: { defaultProps: { padding: 'md' } },
},
spacious: {
spacing: { xs: '0.75rem', sm: '1rem', md: '1.5rem', lg: '2rem', xl: '3rem' },
};
export const compactDensity: MantineThemeOverride = {
spacing: { xs: '0.25rem', sm: '0.5rem', md: '0.75rem', lg: '1rem', xl: '1.5rem' },
fontSizes: { xs: '0.6875rem', sm: '0.75rem', md: '0.8125rem', lg: '0.9375rem', xl: '1.125rem' },
components: {
// 1. Text Inputs (Physical size 'sm')
TextInput: { defaultProps: { size: 'sm' } },
Select: { defaultProps: { size: 'sm' } },
NumberInput: { defaultProps: { size: 'sm' } },
PasswordInput: { defaultProps: { size: 'sm' } },
Textarea: { defaultProps: { size: 'sm' } },
MultiSelect: { defaultProps: { size: 'sm' } },
DatePickerInput: { defaultProps: { size: 'sm' } },
// 2. Form Toggles (Checkbox 16px, Switch compact)
Checkbox: {
defaultProps: { size: 'sm' },
styles: {
label: { fontSize: 'var(--mantine-font-size-md)' }, // Paksa teks label 13px
},
},
Radio: {
defaultProps: { size: 'sm' },
styles: {
label: { fontSize: 'var(--mantine-font-size-md)' }, // Paksa teks label 13px
},
},
Switch: {
defaultProps: { size: 'sm' },
styles: {
label: { fontSize: 'var(--mantine-font-size-md)' }, // Paksa teks label 13px
},
},
// 3. Data Display (Badges, Pagination for Tables)
Badge: { defaultProps: { size: 'sm' } },
Pagination: { defaultProps: { size: 'sm' } },
// 4. Tables & Layouts (Tighter padding for ERP grids)
Table: {
defaultProps: { verticalSpacing: 'sm', horizontalSpacing: 'sm' },
styles: {
th: { fontSize: 'var(--mantine-font-size-md)' }, // Header tabel 13px
td: { fontSize: 'var(--mantine-font-size-md)' }, // Isi tabel 13px
},
},
Card: { defaultProps: { padding: 'sm' } }, // Card padding mengecil
Modal: { defaultProps: { padding: 'sm' } }, // Modal padding mengecil
// 5. Force 13px ('md') text inside 'sm' physical inputs
Input: {
styles: {
input: { fontSize: 'var(--mantine-font-size-md)' },
},
},
InputWrapper: {
styles: {
label: {
fontSize: 'var(--mantine-font-size-md)',
fontWeight: '500',
},
},
},
Button: {
defaultProps: { size: 'sm' },
styles: {
root: { fontSize: 'var(--mantine-font-size-md)' },
},
},
},
};
+1
View File
@@ -1,3 +1,4 @@
export * from './colors';
export * from './typography';
export * from './radius';
export * from './density';
+3 -26
View File
@@ -1,28 +1,5 @@
export const typography = {
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
fontSizes: {
xs: 'calc(13px * 0.846)',
sm: 'calc(13px * 0.923)',
md: '13px',
lg: 'calc(13px * 1.154)',
xl: 'calc(13px * 1.385)',
},
lineHeights: {
xs: '1.4',
sm: '1.45',
md: '1.5',
lg: '1.55',
xl: '1.6',
},
headings: {
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
sizes: {
h1: { fontSize: 'calc(13px * 2.5)' },
h2: { fontSize: 'calc(13px * 2)' },
h3: { fontSize: 'calc(13px * 1.75)' },
h4: { fontSize: 'calc(13px * 1.5)' },
h5: { fontSize: 'calc(13px * 1.25)' },
h6: { fontSize: '13px' },
},
},
fontFamily: " -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
fontFamilyMonospace: ' ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
headings: { fontFamily: " -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" },
};