feat: Integrate Mantine UI library, establish a theme provider, and refactor core UI components.

This commit is contained in:
Firman Ramdhani
2026-02-27 15:00:17 +07:00
parent 8181bd61ec
commit 1d38a188b7
23 changed files with 646 additions and 344 deletions
+33 -15
View File
@@ -1,5 +1,6 @@
import { lazy, Suspense } from 'react';
import { lazy, Suspense, useState } from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { ThemeProvider, ColorSchemeType, DensityType } from '@repo/ui/provider';
import { NotFound } from '@repo/ui/components/system-pages/not-found';
import { Forbidden } from '@repo/ui/components/system-pages/forbidden';
import { Maintenance } from '@repo/ui/components/system-pages/maintenance';
@@ -7,22 +8,39 @@ import { ComingSoon } from '@repo/ui/components/system-pages/coming-soon';
const AuthModule = lazy(() => import('./auth'));
const AppModule = lazy(() => import('./modules'));
const ShowcaseView = lazy(() => import('./showcase/showcase-view'));
export default function App() {
const [colorScheme, setColorScheme] = useState<ColorSchemeType>('light');
const [density, setDensity] = useState<DensityType>('standard');
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/auth/*" element={<AuthModule />} />
<Route path="/app/*" element={<AppModule />} />
<Route path="/404" element={<NotFound />} />
<Route path="/403" element={<Forbidden />} />
<Route path="/maintenance" element={<Maintenance />} />
<Route path="/coming-soon" element={<ComingSoon />} />
<Route path="/" element={<Navigate to="/app" />} />
<Route path="*" element={<Navigate to="/404" />} />
</Routes>
</Suspense>
</BrowserRouter>
<ThemeProvider colorScheme={colorScheme} density={density}>
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/auth/*" element={<AuthModule />} />
<Route path="/app/*" element={<AppModule />} />
<Route
path="/showcase"
element={
<ShowcaseView
colorScheme={colorScheme}
setColorScheme={setColorScheme}
density={density}
setDensity={setDensity}
/>
}
/>
<Route path="/404" element={<NotFound />} />
<Route path="/403" element={<Forbidden />} />
<Route path="/maintenance" element={<Maintenance />} />
<Route path="/coming-soon" element={<ComingSoon />} />
<Route path="/" element={<Navigate to="/showcase" />} />
<Route path="*" element={<Navigate to="/404" />} />
</Routes>
</Suspense>
</BrowserRouter>
</ThemeProvider>
);
}
@@ -0,0 +1,84 @@
import { ColorSchemeType, DensityType } from '@repo/ui/provider';
import { Button } from '@repo/ui/components/button';
import { Card } from '@repo/ui/components/card';
import { Container } from '@repo/ui/components/container';
import { Select } from '@repo/ui/components/select';
import { Group, Stack } from '@repo/ui/components/group';
import { Text, Title } from '@repo/ui/components/text';
import { TextInput } from '@repo/ui/components/input';
import { Checkbox } from '@repo/ui/components/checkbox';
interface ShowcaseViewProps {
colorScheme: ColorSchemeType;
setColorScheme: (val: ColorSchemeType) => void;
density: DensityType;
setDensity: (val: DensityType) => void;
}
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>
{/* Control Panel */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={3} mb="md">Control Panel</Title>
<Group grow align="flex-end">
<Select
label="Color Scheme"
value={colorScheme}
onChange={(val: string | null) => setColorScheme((val as ColorSchemeType) || 'light')}
data={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'dracula', label: 'Dracula' },
{ value: 'blue', label: 'Blue' },
]}
/>
<Select
label="Density"
value={density}
onChange={(val: string | null) => setDensity((val as DensityType) || 'standard')}
data={[
{ value: 'compact', label: 'Compact' },
{ value: 'standard', label: 'Standard' },
{ value: 'spacious', label: 'Spacious' },
]}
/>
</Group>
</Card>
{/* Showcase Area */}
<Card withBorder shadow="sm" radius="md" p="md">
<Stack gap="lg">
<div>
<Title order={3} mb="sm">Typography</Title>
<Text size="sm" c="dimmed">This is dimmed small text.</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>
</Group>
</div>
<div>
<Title order={3} mb="md">Form Elements</Title>
<Group grow>
<TextInput label="First Name" placeholder="John" />
<TextInput label="Last Name" placeholder="Doe" />
</Group>
<Checkbox label="I agree to the terms and conditions" mt="md" defaultChecked />
</div>
</Stack>
</Card>
</Stack>
</Container>
);
}
+6 -2
View File
@@ -3,9 +3,11 @@
"version": "0.0.0",
"exports": {
"./theme.css": "./src/theme.css",
"./components/*": "./src/components/*.tsx",
"./components/system-pages/*": "./src/components/system-pages/*.tsx",
"./components/*": "./src/components/*.ts",
"./hooks/*": "./src/hooks/*.ts",
"./utils/*": "./src/utils/*.ts"
"./utils/*": "./src/utils/*.ts",
"./provider": "./src/provider/index.ts"
},
"license": "MIT",
"scripts": {
@@ -18,6 +20,8 @@
"react-dom": "^19.2.3"
},
"dependencies": {
"@mantine/core": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@repo/utils": "workspace:*",
"dayjs": "^1.11.19",
"tailwind-merge": "^3.4.0",
+1
View File
@@ -0,0 +1 @@
export { Button, type ButtonProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { Card, type CardProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { Checkbox, type CheckboxProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { Container, type ContainerProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { Group, type GroupProps, Stack, type StackProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { TextInput, type TextInputProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export { Select, type SelectProps } from '@mantine/core';
@@ -2,35 +2,32 @@ interface ComingSoonProps {
showActions?: boolean;
}
export function ComingSoon(props: ComingSoonProps) {
const { showActions = false } = props;
import { Title, Text, Button, Container, Stack, Center } from '@mantine/core';
export function ComingSoon({ showActions = false }: ComingSoonProps) {
return (
<div className="flex min-h-screen items-center justify-center bg-white px-4">
<div className="w-full max-w-md text-center">
{/* Code */}
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">Coming Soon</p>
<Container size="sm" h="100vh" pos="relative">
<Center h="100%">
<Stack align="center" ta="center" gap="md">
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
Coming Soon
</Text>
{/* Title */}
<h1 className="mt-3 text-6xl font-extrabold text-brand-900">Feature in Progress</h1>
<Title order={1} fw={900} c="brand">
Feature in Progress
</Title>
{/* Description */}
<p className="mt-4 text-base text-gray-500">
This feature is currently under development. Stay tuned! We'll have it ready for you very soon.
</p>
<Text size="md" c="dimmed">
This feature is currently under development. Stay tuned! We'll have it ready for you very soon.
</Text>
{/* Actions */}
{showActions && (
<div className="mt-8 flex justify-center gap-3">
<a
href="/"
className="rounded-md bg-brand-500 px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-400"
>
{showActions && (
<Button component="a" href="/" color="brand" size="md" mt="xl">
Back to Home
</a>
</div>
)}
</div>
</div>
</Button>
)}
</Stack>
</Center>
</Container>
);
}
@@ -3,47 +3,43 @@ interface ForbiddenProps {
showActionsHome?: boolean;
}
export function Forbidden(props: ForbiddenProps) {
const { showActionsBack = true, showActionsHome = true } = props;
import { Title, Text, Button, Container, Stack, Group, Center } from '@mantine/core';
export function Forbidden({ showActionsBack = true, showActionsHome = true }: ForbiddenProps) {
return (
<div className="flex min-h-screen items-center justify-center bg-white px-4">
<div className="w-full max-w-md text-center">
{/* Code */}
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">403 Error</p>
<Container size="sm" h="100vh" pos="relative">
<Center h="100%">
<Stack align="center" ta="center" gap="md">
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
403 Error
</Text>
{/* Title */}
<h1 className="mt-3 text-6xl font-extrabold text-brand-900">Access Denied</h1>
<Title order={1} fw={900} c="brand">
Access Denied
</Title>
{/* Description */}
<p className="mt-4 text-base text-gray-500">
Sorry, you dont have permission to access this page. Please contact your administrator if you believe this is
a mistake.
</p>
<Text size="md" c="dimmed">
Sorry, you dont have permission to access this page. Please contact your administrator if you believe this
is a mistake.
</Text>
{/* Actions */}
{(showActionsBack || showActionsHome) && (
<div className="mt-8 flex justify-center gap-3">
{showActionsBack && (
<button
onClick={() => window.history.back()}
className="rounded-md border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-100 cursor-pointer"
>
Go Back
</button>
)}
{(showActionsBack || showActionsHome) && (
<Group mt="xl" justify="center">
{showActionsBack && (
<Button variant="default" size="md" onClick={() => window.history.back()}>
Go Back
</Button>
)}
{showActionsHome && (
<a
href="/"
className="rounded-md bg-brand-500 px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-400"
>
Back to Home
</a>
)}
</div>
)}
</div>
</div>
{showActionsHome && (
<Button component="a" href="/" color="brand" size="md">
Back to Home
</Button>
)}
</Group>
)}
</Stack>
</Center>
</Container>
);
}
@@ -2,36 +2,33 @@ interface MaintenanceProps {
showActions?: boolean;
}
export function Maintenance(props: MaintenanceProps) {
const { showActions = false } = props;
import { Title, Text, Button, Container, Stack, Center } from '@mantine/core';
export function Maintenance({ showActions = false }: MaintenanceProps) {
return (
<div className="flex min-h-screen items-center justify-center bg-white px-4">
<div className="w-full max-w-md text-center">
{/* Code */}
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">Maintenance</p>
<Container size="sm" h="100vh" pos="relative">
<Center h="100%">
<Stack align="center" ta="center" gap="md">
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
Maintenance
</Text>
{/* Title */}
<h1 className="mt-3 text-6xl font-extrabold text-brand-900">We'll Be Back Soon</h1>
<Title order={1} fw={900} c="brand">
We'll Be Back Soon
</Title>
{/* Description */}
<p className="mt-4 text-base text-gray-500">
Our system is currently undergoing scheduled maintenance. We are working hard to bring it back online as soon
as possible.
</p>
<Text size="md" c="dimmed">
Our system is currently undergoing scheduled maintenance. We are working hard to bring it back online as
soon as possible.
</Text>
{/* Optional Actions */}
{showActions && (
<div className="mt-8 flex justify-center gap-3">
<a
href="/"
className="rounded-md bg-brand-500 px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-400"
>
{showActions && (
<Button component="a" href="/" color="brand" size="md" mt="xl">
Back to Home
</a>
</div>
)}
</div>
</div>
</Button>
)}
</Stack>
</Center>
</Container>
);
}
@@ -3,42 +3,42 @@ interface NotFoundProps {
showActionsHome?: boolean;
}
export function NotFound(props: NotFoundProps) {
const { showActionsBack = true, showActionsHome = true } = props;
import { Title, Text, Button, Container, Stack, Group, Center } from '@mantine/core';
export function NotFound({ showActionsBack = true, showActionsHome = true }: NotFoundProps) {
return (
<div className="flex min-h-screen items-center justify-center bg-white px-4">
<div className="w-full max-w-md text-center">
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">404 Error</p>
<Container size="sm" h="100vh" pos="relative">
<Center h="100%">
<Stack align="center" ta="center" gap="md">
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
404 Error
</Text>
<h1 className="mt-3 text-6xl font-extrabold text-brand-900">Page Not Found</h1>
<Title order={1} fw={900} c="brand">
Page Not Found
</Title>
<p className="mt-4 text-base text-gray-500">
Sorry, the page youre looking for doesnt exist or has been moved.
</p>
<Text size="md" c="dimmed">
Sorry, the page youre looking for doesnt exist or has been moved.
</Text>
{(showActionsBack || showActionsHome) && (
<div className="mt-8 flex justify-center gap-3">
{showActionsBack && (
<button
onClick={() => window.history.back()}
className="rounded-md border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-100 cursor-pointer"
>
Go Back
</button>
)}
{(showActionsBack || showActionsHome) && (
<Group mt="xl" justify="center">
{showActionsBack && (
<Button variant="default" size="md" onClick={() => window.history.back()}>
Go Back
</Button>
)}
{showActionsHome && (
<a
href="/"
className="rounded-md bg-brand-500 px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-400"
>
Back to Home
</a>
)}
</div>
)}
</div>
</div>
{showActionsHome && (
<Button component="a" href="/" color="brand" size="md">
Back to Home
</Button>
)}
</Group>
)}
</Stack>
</Center>
</Container>
);
}
+1
View File
@@ -0,0 +1 @@
export { Text, type TextProps, Title, type TitleProps } from '@mantine/core';
+1
View File
@@ -0,0 +1 @@
export * from './theme-provider';
@@ -0,0 +1,70 @@
import { MantineProvider, createTheme, MantineThemeOverride } from '@mantine/core';
import React from 'react';
import { brandColors, errorColors, warningColors, successColors, infoColors, draculaColors, blueColors } from '../theme/tokens/colors';
import { typography } from '../theme/tokens/typography';
import { radius } from '../theme/tokens/radius';
export type ColorSchemeType = 'light' | 'dark' | 'dracula' | 'blue';
export type DensityType = 'compact' | 'standard' | 'spacious';
interface ThemeProviderProps {
children: React.ReactNode;
colorScheme?: ColorSchemeType;
density?: DensityType;
}
export function ThemeProvider({ children, colorScheme = 'light', density = 'standard' }: ThemeProviderProps) {
const densityStyles = {
compact: {
spacing: { xs: '0.25rem', sm: '0.5rem', md: '0.75rem', lg: '1rem', xl: '1.25rem' },
},
standard: {
spacing: { xs: '0.5rem', sm: '0.75rem', md: '1rem', lg: '1.5rem', xl: '2rem' },
},
spacious: {
spacing: { xs: '0.75rem', sm: '1rem', md: '1.5rem', lg: '2rem', xl: '3rem' },
}
};
const baseTheme = createTheme({
colors: {
brand: brandColors,
error: errorColors,
warning: warningColors,
success: successColors,
info: infoColors,
dracula: draculaColors,
bluetheme: blueColors,
},
primaryColor: 'brand',
fontFamily: typography.fontFamily,
headings: typography.headings,
spacing: densityStyles[density].spacing,
radius: radius,
});
let mantineColorScheme: 'light' | 'dark' = 'light';
let themeOverride: MantineThemeOverride = baseTheme;
if (colorScheme === 'dark') {
mantineColorScheme = 'dark';
} else if (colorScheme === 'dracula') {
mantineColorScheme = 'dark';
themeOverride = createTheme({
...baseTheme,
primaryColor: 'dracula',
});
} else if (colorScheme === 'blue') {
mantineColorScheme = 'light';
themeOverride = createTheme({
...baseTheme,
primaryColor: 'bluetheme',
});
}
return (
<MantineProvider theme={themeOverride} forceColorScheme={mantineColorScheme} defaultColorScheme={mantineColorScheme}>
{children}
</MantineProvider>
);
}
+6 -208
View File
@@ -1,219 +1,17 @@
@import 'tailwindcss';
@source "../src";
/* =========================================
THEME VARIABLES (CENTRALIZED)
MANTINE BASE STYLES
========================================= */
@theme {
/* -------------------------------
COLORS: PALETTE
------------------------------- */
/* Brand (Purple based on your code) */
--color-brand-50: #f5f3fd;
--color-brand-100: #e6dbfb;
--color-brand-200: #d0b6f8;
--color-brand-300: #b78ff5;
--color-brand-400: #9f64f1;
--color-brand-500: #7d39ed; /* Primary */
--color-brand-600: #652ed9;
--color-brand-700: #4a23a5;
--color-brand-800: #321a7a;
--color-brand-900: #1b0f4a;
/* Neutrals */
--color-gray-50: #f5f6f7;
--color-gray-100: #eceff1;
--color-gray-200: #e0e3e7;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
/* -------------------------------
COLORS: SEMANTICS (COMPLETED)
------------------------------- */
/* Error (Red) */
--color-error-50: #fef2f2;
--color-error-100: #fee2e2;
--color-error-200: #fecaca;
--color-error-300: #fca5a5;
--color-error-400: #f87171;
--color-error-500: #ef4444;
--color-error-600: #dc2626;
--color-error-700: #b91c1c;
--color-error-800: #991b1b;
--color-error-900: #7f1d1d;
/* Warning (Amber/Orange) */
--color-warning-50: #fffbeb;
--color-warning-100: #fef3c7;
--color-warning-200: #fde68a;
--color-warning-300: #fcd34d;
--color-warning-400: #fbbf24;
--color-warning-500: #f59e0b;
--color-warning-600: #d97706;
--color-warning-700: #b45309;
--color-warning-800: #92400e;
--color-warning-900: #78350f;
/* Success (Green) */
--color-success-50: #f0fdf4;
--color-success-100: #dcfce7;
--color-success-200: #bbf7d0;
--color-success-300: #86efac;
--color-success-400: #4ade80;
--color-success-500: #22c55e;
--color-success-600: #16a34a;
--color-success-700: #15803d;
--color-success-800: #166534;
--color-success-900: #14532d;
/* Info (Blue/Sky) */
--color-info-50: #f0f9ff;
--color-info-100: #e0f2fe;
--color-info-200: #bae6fd;
--color-info-300: #7dd3fc;
--color-info-400: #38bdf8;
--color-info-500: #0ea5e9;
--color-info-600: #0284c7;
--color-info-700: #0369a1;
--color-info-800: #075985;
--color-info-900: #0c4a6e;
/* -------------------------------
BASE FONT & TYPOGRAPHY
------------------------------- */
--base-font-size: 13px;
/* Scale */
--text-xs: calc(var(--base-font-size) * 0.846); /* ~11px */
--text-sm: calc(var(--base-font-size) * 0.923); /* ~12px */
--text-base: var(--base-font-size); /* 13px */
--text-lg: calc(var(--base-font-size) * 1.154); /* ~15px */
--text-xl: calc(var(--base-font-size) * 1.385); /* ~18px */
--text-2xl: calc(var(--base-font-size) * 1.692); /* ~22px */
/* Weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* -------------------------------
SHAPE & DEPTH
------------------------------- */
--radius-sm: 0.125rem; /* 2px */
--radius-md: 0.25rem; /* 4px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
/* -------------------------------
FORM ITEM SPECIFICS
------------------------------- */
/* Form Item Spacing */
--form-item-margin-bottom: 1.25rem; /* 20px */
/* Label Configuration */
--form-label-font-size: var(--text-sm); /* Uses scale text-sm (~12px) */
--form-label-color: var(--color-gray-700);
--form-label-font-weight: var(--font-weight-medium);
--form-label-margin-bottom: 0.375rem; /* ~6px */
/* Error/Warning Message Configuration */
--form-msg-font-size: var(--text-xs); /* Uses scale text-xs (~11px) */
--form-msg-margin-top: 0.375rem; /* ~6px */
--input-height: 2.6rem; /* ~34px */
--input-font-size: var(--text-base);
--input-padding-x: 0.92rem; /* ~12px */
--input-padding-y: 0.62rem; /* ~8px */
--input-radius: 0.375rem; /* 6px */
/* Input Number Specific */
--input-handler-width: 1.5rem; /* 24px (Slightly wider for better clickable) */
/* Textarea Specific */
--textarea-min-height: 6.15rem; /* 80px */
--textarea-padding-x: 0.92rem; /* 12px */
--textarea-padding-y: 0.62rem; /* 8px */
/* Input Colors */
--input-bg: #ffffff;
--input-border: var(--color-gray-300);
--input-border-hover: var(--color-brand-400);
--input-text: var(--color-gray-800);
--input-placeholder: var(--color-gray-400);
--focus-ring-color: var(--color-brand-100);
--focus-border-color: var(--color-brand-500);
--disabled-bg: #f3f4f6;
--disabled-text: var(--color-gray-400);
--disabled-border: #e5e7eb;
/* CHECKBOX & RADIO COMPONENT VARIABLES*/
/* 16px is a perfect fit for a 13px font size.
It's easier to read than 14px, but not overpowering. */
--checkbox-size: 16px; /* ~16px (relative to 13px base) */
--checkbox-radius: var(--radius-md);
/* * Position the check mark icon precisely in the center of the 16px box */
--checkbox-check-width: 5px;
--checkbox-check-height: 9px;
--checkbox-check-top: 1.5px; /* Fine-tuning vertical position */
--checkbox-check-left: 4.5px; /* Fine-tuning horizontal position */
/* SWITCH COMPONENT VARIABLES */
/* Using pixels (px) for Switch is often safer
to avoid sub-pixel rendering artifacts (circles becoming oval)
on low screen resolutions, while still maintaining proportion to the rest scale. */
/* A height of 20px fits with a line-height of 13px text,
A width of 36px provides a compact and modern ratio. */
--switch-width: 36px;
--switch-height: 20px;
/* Handle (lingkaran) = Height - (Padding * 2) */
/* 20px - (2px * 2) = 16px */
--switch-handle-size: 16px;
--switch-padding: 2px; /* Distance between handle and edge */
/* Calculate active position (Checked) */
/* 36px - 16px - 2px = 18px */
--switch-checked-pos: calc(var(--switch-width) - var(--switch-handle-size) - var(--switch-padding));
/* Additional variable for label inside switch (ON/OFF text) if any */
--switch-inner-margin-left: 6px;
--switch-inner-margin-right: 20px;
/* Margin Calculation for Inner Text */
/* Handle edge spacing: Handle size (16px) + Padding (2px) + Gap (4px) = 22px */
--switch-inner-indent-handle: 22px;
/* Empty edge spacing: Padding (2px) + Gap (4px) = 6px */
--switch-inner-indent-edge: 6px;
}
@import '@mantine/core/styles.css';
/* =========================================
BASE RESETS
BASE RESETS (Minimal, let Mantine handle most)
========================================= */
@layer base {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #ffffff;
color: var(--color-gray-800);
font-size: var(--base-font-size);
line-height: var(--text-base--line-height);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
input,
textarea,
select,
button {
font-family: inherit;
font-size: inherit;
color: inherit;
}
}
+50
View File
@@ -0,0 +1,50 @@
import { MantineColorsTuple } from '@mantine/core';
export const brandColors: MantineColorsTuple = [
'#f5f3fd', // 50
'#e6dbfb', // 100
'#d0b6f8', // 200
'#b78ff5', // 300
'#9f64f1', // 400
'#7d39ed', // 500 (Primary)
'#652ed9', // 600
'#4a23a5', // 700
'#321a7a', // 800
'#1b0f4a', // 900
];
export const errorColors: MantineColorsTuple = [
'#fef2f2', '#fee2e2', '#fecaca', '#fca5a5', '#f87171',
'#ef4444', '#dc2626', '#b91c1c', '#991b1b', '#7f1d1d',
];
export const warningColors: MantineColorsTuple = [
'#fffbeb', '#fef3c7', '#fde68a', '#fcd34d', '#fbbf24',
'#f59e0b', '#d97706', '#b45309', '#92400e', '#78350f',
];
export const successColors: MantineColorsTuple = [
'#f0fdf4', '#dcfce7', '#bbf7d0', '#86efac', '#4ade80',
'#22c55e', '#16a34a', '#15803d', '#166534', '#14532d',
];
export const infoColors: MantineColorsTuple = [
'#f0f9ff', '#e0f2fe', '#bae6fd', '#7dd3fc', '#38bdf8',
'#0ea5e9', '#0284c7', '#0369a1', '#075985', '#0c4a6e',
];
export const draculaColors: MantineColorsTuple = [
'#f8f8f2', '#f1fa8c', '#8be9fd', '#50fa7b', '#ffb86c',
'#ff79c6', '#bd93f9', '#6272a4', '#44475a', '#282a36',
]; // Reusing standard dracula palette as a tuple for simplicity,
// usually this would be mapped to specific grays/primary.
export const blueColors: MantineColorsTuple = [
'#e3f2fd', '#bbdefb', '#90caf9', '#64b5f6', '#42a5f5',
'#2196f3', '#1e88e5', '#1976d2', '#1565c0', '#0d47a1',
];
export const grayColors: MantineColorsTuple = [
'#f5f6f7', '#eceff1', '#e0e3e7', '#d1d5db', '#9ca3af',
'#6b7280', '#4b5563', '#374151', '#1f2937', '#111827',
];
+3
View File
@@ -0,0 +1,3 @@
export * from './colors';
export * from './typography';
export * from './radius';
+7
View File
@@ -0,0 +1,7 @@
export const radius = {
xs: '0.125rem', // 2px
sm: '0.25rem', // 4px
md: '0.375rem', // 6px (Input radius)
lg: '0.5rem', // 8px
xl: '0.75rem', // 12px
};
@@ -0,0 +1,28 @@
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' },
},
},
};
+244 -3
View File
@@ -161,6 +161,12 @@ importers:
packages/ui:
dependencies:
'@mantine/core':
specifier: ^8.3.15
version: 8.3.15(@mantine/hooks@8.3.15)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3)
'@mantine/hooks':
specifier: ^8.3.15
version: 8.3.15(react@19.2.3)
'@repo/utils':
specifier: workspace:*
version: link:../utils
@@ -392,7 +398,6 @@ packages:
/@babel/runtime@7.28.4:
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/template@7.27.2:
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
@@ -899,6 +904,47 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
/@floating-ui/core@1.7.4:
resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==}
dependencies:
'@floating-ui/utils': 0.2.10
dev: false
/@floating-ui/dom@1.7.5:
resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==}
dependencies:
'@floating-ui/core': 1.7.4
'@floating-ui/utils': 0.2.10
dev: false
/@floating-ui/react-dom@2.1.7(react-dom@19.2.3)(react@19.2.3):
resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/dom': 1.7.5
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
dev: false
/@floating-ui/react@0.27.18(react-dom@19.2.3)(react@19.2.3):
resolution: {integrity: sha512-xJWJxvmy3a05j643gQt+pRbht5XnTlGpsEsAPnMi5F5YTOEEJymA90uZKBD8OvIv5XvZ1qi4GcccSlqT3Bq44Q==}
peerDependencies:
react: '>=17.0.0'
react-dom: '>=17.0.0'
dependencies:
'@floating-ui/react-dom': 2.1.7(react-dom@19.2.3)(react@19.2.3)
'@floating-ui/utils': 0.2.10
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
tabbable: 6.4.0
dev: false
/@floating-ui/utils@0.2.10:
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
dev: false
/@humanwhocodes/config-array@0.13.0:
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -989,6 +1035,34 @@ packages:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
/@mantine/core@8.3.15(@mantine/hooks@8.3.15)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3):
resolution: {integrity: sha512-wBn/GogB4x7a2Uj7Ztt3amRaApjED+9XqfE4wyCLh88R7KV55k9vnTdCx+irI/GLOOu9tXNUGm3a4t5sTajwkQ==}
peerDependencies:
'@mantine/hooks': 8.3.15
react: ^18.x || ^19.x
react-dom: ^18.x || ^19.x
dependencies:
'@floating-ui/react': 0.27.18(react-dom@19.2.3)(react@19.2.3)
'@mantine/hooks': 8.3.15(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-number-format: 5.4.4(react-dom@19.2.3)(react@19.2.3)
react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.3)
react-textarea-autosize: 8.5.9(@types/react@19.2.7)(react@19.2.3)
type-fest: 4.41.0
transitivePeerDependencies:
- '@types/react'
dev: false
/@mantine/hooks@8.3.15(react@19.2.3):
resolution: {integrity: sha512-AUSnpUlzttHzJht3CJ1YWi16iy6NWRwtyWO5RLGHHsmiW05DyG0qOPKF8+R5dLHuOCnl3XOu4roI2Y1ku9U04Q==}
peerDependencies:
react: ^18.x || ^19.x
dependencies:
react: 19.2.3
dev: false
/@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
peerDependencies:
@@ -2137,7 +2211,6 @@ packages:
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
dependencies:
csstype: 3.2.3
dev: true
/@types/resolve@1.20.6:
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
@@ -3182,6 +3255,11 @@ packages:
is-wsl: 2.2.0
dev: true
/clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
dev: false
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -3253,7 +3331,6 @@ packages:
/csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
dev: true
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -3388,6 +3465,10 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false
/devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
dependencies:
@@ -4335,6 +4416,11 @@ packages:
hasown: 2.0.2
math-intrinsics: 1.1.0
/get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
dev: false
/get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
@@ -6049,11 +6135,56 @@ packages:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: false
/react-number-format@5.4.4(react-dom@19.2.3)(react@19.2.3):
resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==}
peerDependencies:
react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
dev: false
/react-refresh@0.18.0:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
dev: true
/react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.3)
tslib: 2.8.1
dev: false
/react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.3)
react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.3)
tslib: 2.8.1
use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.3)
use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.3)
dev: false
/react-router-dom@7.11.0(react-dom@19.2.3)(react@19.2.3):
resolution: {integrity: sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==}
engines: {node: '>=20.0.0'}
@@ -6082,6 +6213,36 @@ packages:
set-cookie-parser: 2.7.2
dev: false
/react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
get-nonce: 1.0.1
react: 19.2.3
tslib: 2.8.1
dev: false
/react-textarea-autosize@8.5.9(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
'@babel/runtime': 7.28.4
react: 19.2.3
use-composed-ref: 1.4.0(@types/react@19.2.7)(react@19.2.3)
use-latest: 1.3.0(@types/react@19.2.7)(react@19.2.3)
transitivePeerDependencies:
- '@types/react'
dev: false
/react@19.2.3:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'}
@@ -6780,6 +6941,10 @@ packages:
'@pkgr/core': 0.2.9
dev: false
/tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
dev: false
/tailwind-merge@3.4.0:
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
dev: false
@@ -6976,6 +7141,11 @@ packages:
engines: {node: '>=14.16'}
dev: false
/type-fest@4.41.0:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
dev: false
/typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -7181,6 +7351,77 @@ packages:
dependencies:
punycode: 2.3.1
/use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
tslib: 2.8.1
dev: false
/use-composed-ref@1.4.0(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
dev: false
/use-isomorphic-layout-effect@1.2.1(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
dev: false
/use-latest@1.3.0(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
react: 19.2.3
use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.7)(react@19.2.3)
dev: false
/use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.3):
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.2.7
detect-node-es: 1.1.0
react: 19.2.3
tslib: 2.8.1
dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false