refactor: migrate frontend from shadcn/Tailwind to Mantine v9
Reescribe todos los componentes UI para usar Mantine v9 en lugar de shadcn/Tailwind. Elimina cn(), CVA, components.json, theme_provider custom y globals.css con Tailwind. Añade 25+ componentes nuevos (AppShell, AuthForm, DatePickerInput, Dropzone, etc.) y MantineProvider como wrapper estándar del sistema de temas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { cn } from "../core/cn"
|
||||
import { Group, Stack, Title, Text, ActionIcon, Tabs, Box } from "@mantine/core"
|
||||
import { IconChevronLeft } from "@tabler/icons-react"
|
||||
|
||||
interface TabItem {
|
||||
label: string
|
||||
@@ -10,7 +11,7 @@ interface TabItem {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface PageHeaderProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
actions?: React.ReactNode
|
||||
@@ -24,70 +25,67 @@ interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
function PageHeader({
|
||||
title, subtitle, actions, onBack, tabs, activeTab, onTabChange,
|
||||
badge, sticky = false, className, ...props
|
||||
badge, sticky = false,
|
||||
}: PageHeaderProps) {
|
||||
return (
|
||||
<header
|
||||
<Box
|
||||
data-slot="page-header"
|
||||
className={cn("space-y-4 border-b bg-background pb-4", sticky && "sticky top-0 z-20", className)}
|
||||
{...props}
|
||||
pb="md"
|
||||
style={{
|
||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
...(sticky ? { position: 'sticky', top: 0, zIndex: 20 } : {}),
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
{onBack && (
|
||||
<button onClick={onBack} className="mt-1 inline-flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-muted">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m15 18-6-6 6-6"/></svg>
|
||||
</button>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
||||
{badge}
|
||||
</div>
|
||||
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
|
||||
</div>
|
||||
</div>
|
||||
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
||||
</div>
|
||||
{tabs && tabs.length > 0 && (
|
||||
<nav className="flex gap-4 border-b -mb-4 pb-0">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.value}
|
||||
type="button"
|
||||
disabled={tab.disabled}
|
||||
onClick={() => onTabChange?.(tab.value)}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 border-b-2 px-1 pb-3 text-sm font-medium transition-colors",
|
||||
activeTab === tab.value ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground",
|
||||
tab.disabled && "pointer-events-none opacity-50"
|
||||
)}
|
||||
>
|
||||
{tab.icon && <span className="size-4">{tab.icon}</span>}
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" align="flex-start" gap="md">
|
||||
<Group align="flex-start" gap="sm">
|
||||
{onBack && (
|
||||
<ActionIcon variant="subtle" size="sm" onClick={onBack} mt={4}>
|
||||
<IconChevronLeft size={16} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<Stack gap={4}>
|
||||
<Group gap="sm" align="center">
|
||||
<Title order={2}>{title}</Title>
|
||||
{badge}
|
||||
</Group>
|
||||
{subtitle && <Text size="sm" c="dimmed">{subtitle}</Text>}
|
||||
</Stack>
|
||||
</Group>
|
||||
{actions && <Group gap="xs" style={{ flexShrink: 0 }}>{actions}</Group>}
|
||||
</Group>
|
||||
{tabs && tabs.length > 0 && (
|
||||
<Tabs value={activeTab} onChange={(v) => v && onTabChange?.(v)} style={{ marginBottom: 'calc(-1 * var(--mantine-spacing-md))' }}>
|
||||
<Tabs.List>
|
||||
{tabs.map((tab) => (
|
||||
<Tabs.Tab key={tab.value} value={tab.value} disabled={tab.disabled} leftSection={tab.icon}>
|
||||
{tab.label}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
interface SimplePageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface SimplePageHeaderProps {
|
||||
title: string
|
||||
description?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
function SimplePageHeader({ title, description, children, className, ...props }: SimplePageHeaderProps) {
|
||||
function SimplePageHeader({ title, description, children }: SimplePageHeaderProps) {
|
||||
return (
|
||||
<div className={cn("flex items-center justify-between border-b pb-4", className)} {...props}>
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
||||
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
{children && <div className="flex items-center gap-2">{children}</div>}
|
||||
</div>
|
||||
<Group justify="space-between" pb="md" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
||||
<Stack gap={4}>
|
||||
<Title order={2}>{title}</Title>
|
||||
{description && <Text size="sm" c="dimmed">{description}</Text>}
|
||||
</Stack>
|
||||
{children && <Group gap="xs">{children}</Group>}
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user