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,40 +1,27 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '../core/cn'
|
||||
import { Sparkline as MantineSparkline } from '@mantine/charts'
|
||||
|
||||
type SparklineVariant = 'line' | 'area' | 'bar'
|
||||
|
||||
interface SparklineProps extends React.SVGAttributes<SVGSVGElement> {
|
||||
interface SparklineProps {
|
||||
data: number[]
|
||||
variant?: SparklineVariant
|
||||
color?: string
|
||||
/** Per-bar colors for 'bar' variant. Cycles if shorter than data. */
|
||||
colors?: string[]
|
||||
width?: number
|
||||
height?: number
|
||||
strokeWidth?: number
|
||||
showLastPoint?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
function getPath(data: number[], width: number, height: number, padding: number = 2) {
|
||||
if (data.length === 0) return { linePath: '', areaPath: '' }
|
||||
const min = Math.min(...data)
|
||||
const max = Math.max(...data)
|
||||
const range = max - min || 1
|
||||
const ew = width - padding * 2
|
||||
const eh = height - padding * 2
|
||||
const points = data.map((value, index) => ({
|
||||
x: padding + (index / (data.length - 1)) * ew,
|
||||
y: padding + eh - ((value - min) / range) * eh,
|
||||
}))
|
||||
const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ')
|
||||
const areaPath = `${linePath} L ${points[points.length - 1].x} ${height - padding} L ${padding} ${height - padding} Z`
|
||||
return { linePath, areaPath }
|
||||
}
|
||||
|
||||
const Sparkline = React.forwardRef<SVGSVGElement, SparklineProps>(
|
||||
({ data, variant = 'line', color = 'currentColor', colors, width = 80, height = 24, strokeWidth = 1.5, showLastPoint = true, className, ...props }, ref) => {
|
||||
if (data.length === 0) return <svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props} />
|
||||
const Sparkline = React.forwardRef<HTMLDivElement, SparklineProps>(
|
||||
({ data, variant = 'line', color, colors, width = 80, height = 24, strokeWidth = 1.5, className, ...props }, ref) => {
|
||||
if (data.length === 0) {
|
||||
return <div ref={ref} style={{ width, height }} className={className} />
|
||||
}
|
||||
|
||||
// Bar variant: use custom SVG (Mantine Sparkline only supports area)
|
||||
if (variant === 'bar') {
|
||||
const min = Math.min(...data, 0)
|
||||
const max = Math.max(...data)
|
||||
@@ -42,31 +29,36 @@ const Sparkline = React.forwardRef<SVGSVGElement, SparklineProps>(
|
||||
const p = 2
|
||||
const eh = height - p * 2
|
||||
const bw = (width - p * 2) / data.length - 1
|
||||
const barColor = color ?? 'currentColor'
|
||||
return (
|
||||
<svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props}>
|
||||
{data.map((value, index) => {
|
||||
const bh = ((value - min) / range) * eh
|
||||
const x = p + index * ((width - p * 2) / data.length) + 0.5
|
||||
const y = p + eh - bh
|
||||
const barColor = colors ? colors[index % colors.length] : color
|
||||
return <rect key={index} x={x} y={y} width={Math.max(bw, 1)} height={Math.max(bh, 1)} fill={barColor} rx={1} opacity={0.85} />
|
||||
})}
|
||||
</svg>
|
||||
<div ref={ref} className={className} {...props}>
|
||||
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
|
||||
{data.map((value, index) => {
|
||||
const bh = ((value - min) / range) * eh
|
||||
const x = p + index * ((width - p * 2) / data.length) + 0.5
|
||||
const y = p + eh - bh
|
||||
const fill = colors ? colors[index % colors.length] : barColor
|
||||
return <rect key={index} x={x} y={y} width={Math.max(bw, 1)} height={Math.max(bh, 1)} fill={fill} rx={1} opacity={0.85} />
|
||||
})}
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const { linePath, areaPath } = getPath(data, width, height)
|
||||
const lastPoint = {
|
||||
x: width - 2,
|
||||
y: 2 + (height - 4) - ((data[data.length - 1] - Math.min(...data)) / (Math.max(...data) - Math.min(...data) || 1)) * (height - 4)
|
||||
}
|
||||
|
||||
// Line/area: use Mantine Sparkline
|
||||
return (
|
||||
<svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props}>
|
||||
{variant === 'area' && <path d={areaPath} fill={color} opacity={0.2} />}
|
||||
<path d={linePath} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
|
||||
{showLastPoint && <circle cx={lastPoint.x} cy={lastPoint.y} r={2.5} fill={color} />}
|
||||
</svg>
|
||||
<div ref={ref} className={className} {...props}>
|
||||
<MantineSparkline
|
||||
data={data}
|
||||
color={color ?? 'blue'}
|
||||
w={width}
|
||||
h={height}
|
||||
strokeWidth={strokeWidth}
|
||||
curveType="natural"
|
||||
withGradient={variant === 'area'}
|
||||
fillOpacity={variant === 'area' ? 0.3 : 0}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user