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,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '../core/cn'
|
||||
import { Stack, Group, Title, Text, Paper, TextInput, Textarea, Switch, NativeSelect, Button, Box, Anchor } from '@mantine/core'
|
||||
|
||||
interface SettingField {
|
||||
key: string
|
||||
@@ -31,81 +31,92 @@ export function settingsPage({
|
||||
subtitle,
|
||||
sections,
|
||||
onSave,
|
||||
className,
|
||||
}: SettingsPageProps): React.ReactElement {
|
||||
return (
|
||||
<div className={cn('space-y-6', className)}>
|
||||
<Stack gap="lg">
|
||||
{/* Header */}
|
||||
<div className="border-b pb-4">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
||||
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
|
||||
</div>
|
||||
<Box pb="md" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
||||
<Title order={2}>{title}</Title>
|
||||
{subtitle && <Text size="sm" c="dimmed">{subtitle}</Text>}
|
||||
</Box>
|
||||
|
||||
{/* Tabs navigation */}
|
||||
<div className="flex gap-6">
|
||||
<nav className="hidden w-48 shrink-0 md:block">
|
||||
<div className="space-y-1">
|
||||
{/* Content with sidebar nav */}
|
||||
<Group align="flex-start" gap="xl">
|
||||
<Box w={192} visibleFrom="md" style={{ flexShrink: 0 }}>
|
||||
<Stack gap={4}>
|
||||
{sections.map((section) => (
|
||||
<a
|
||||
<Anchor
|
||||
key={section.id}
|
||||
href={`#${section.id}`}
|
||||
className="block rounded-md px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
underline="never"
|
||||
size="sm"
|
||||
fw={500}
|
||||
c="dimmed"
|
||||
py={6}
|
||||
px="sm"
|
||||
style={{ display: 'block', borderRadius: 'var(--mantine-radius-sm)' }}
|
||||
>
|
||||
{section.title}
|
||||
</a>
|
||||
</Anchor>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Sections */}
|
||||
<div className="flex-1 space-y-8">
|
||||
<Stack gap="xl" style={{ flex: 1 }}>
|
||||
{sections.map((section) => (
|
||||
<div key={section.id} id={section.id} className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">{section.title}</h2>
|
||||
{section.description && <p className="text-sm text-muted-foreground">{section.description}</p>}
|
||||
</div>
|
||||
<div className="space-y-4 rounded-lg border p-4">
|
||||
{section.fields.map((field) => (
|
||||
<div key={field.key} className="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<label className="text-sm font-medium">{field.label}</label>
|
||||
{field.description && <p className="text-xs text-muted-foreground">{field.description}</p>}
|
||||
</div>
|
||||
<div className="w-full sm:w-64">
|
||||
{field.type === 'toggle' ? (
|
||||
<button className={cn(
|
||||
'relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
|
||||
field.value ? 'bg-primary' : 'bg-input'
|
||||
)}>
|
||||
<span className={cn('pointer-events-none block size-4 rounded-full bg-background shadow-lg ring-0 transition-transform', field.value ? 'translate-x-4' : 'translate-x-0')} />
|
||||
</button>
|
||||
) : field.type === 'select' ? (
|
||||
<select className="h-8 w-full rounded-lg border border-input bg-transparent px-2.5 text-sm">
|
||||
{field.options?.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
||||
</select>
|
||||
) : field.type === 'textarea' ? (
|
||||
<textarea className="w-full rounded-lg border border-input bg-transparent px-2.5 py-1.5 text-sm" rows={3} placeholder={field.placeholder} defaultValue={String(field.value ?? '')} />
|
||||
) : (
|
||||
<input type={field.type} className="h-8 w-full rounded-lg border border-input bg-transparent px-2.5 text-sm" placeholder={field.placeholder} defaultValue={String(field.value ?? '')} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Stack key={section.id} id={section.id} gap="md">
|
||||
<Box>
|
||||
<Text size="lg" fw={500}>{section.title}</Text>
|
||||
{section.description && <Text size="sm" c="dimmed">{section.description}</Text>}
|
||||
</Box>
|
||||
<Paper withBorder p="md" radius="md">
|
||||
<Stack gap="md">
|
||||
{section.fields.map((field) => (
|
||||
<Group key={field.key} justify="space-between" align="center" wrap="wrap" gap="md">
|
||||
<Stack gap={2} style={{ flex: '1 1 auto' }}>
|
||||
<Text size="sm" fw={500}>{field.label}</Text>
|
||||
{field.description && <Text size="xs" c="dimmed">{field.description}</Text>}
|
||||
</Stack>
|
||||
<Box w={{ base: '100%', sm: 256 }}>
|
||||
{field.type === 'toggle' ? (
|
||||
<Switch defaultChecked={!!field.value} />
|
||||
) : field.type === 'select' ? (
|
||||
<NativeSelect
|
||||
data={field.options?.map(o => ({ label: o.label, value: o.value })) ?? []}
|
||||
size="xs"
|
||||
/>
|
||||
) : field.type === 'textarea' ? (
|
||||
<Textarea
|
||||
rows={3}
|
||||
placeholder={field.placeholder}
|
||||
defaultValue={String(field.value ?? '')}
|
||||
size="xs"
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
defaultValue={String(field.value ?? '')}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{onSave && (
|
||||
<div className="flex justify-end border-t pt-4">
|
||||
<button className="inline-flex h-8 items-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary/80">
|
||||
Save changes
|
||||
</button>
|
||||
</div>
|
||||
<Group justify="flex-end" pt="md" style={{ borderTop: '1px solid var(--mantine-color-default-border)' }}>
|
||||
<Button size="xs">Save changes</Button>
|
||||
</Group>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user