Fix all TypeScript errors; remove unused element/magicui components

- Add missing deps: @base-ui/react, react-hook-form, next-themes, react-is
- Bump recharts to 3.9.1 and update chart.tsx to the recharts v3-compatible
  shadcn version
- Pin react-resizable-panels to v3 (v4 renamed the PanelGroup exports)
- Add showCloseButton prop to DialogContent (used by CommandDialog)
- Replace custom cn with standard clsx + twMerge (fixes Base UI className
  types and object-syntax classes being dropped at runtime)
- Clean up tsconfig (drop dead Vite project references; Next.js updated
  moduleResolution/jsx during build)
- Remove unused components/elements and components/magicui, drop bun.lock
  in favor of yarn.lock, simplify app/page.tsx

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bE5ox6WnAPgqRgkx3pLCu
This commit is contained in:
Rami Bitar
2026-07-02 15:08:11 -04:00
parent 95ad3c9d02
commit 4b363b00ed
22 changed files with 175 additions and 1602 deletions

View File

@@ -1,38 +0,0 @@
import type { ElementConfig } from "./types"
export const ButtonConfig: ElementConfig = {
Button: {
label: "Button",
fields: {
children: {
type: "text",
label: "Label",
},
variant: {
type: "select",
label: "Variant",
options: [
{ label: "Default", value: "default" },
{ label: "Destructive", value: "destructive" },
{ label: "Outline", value: "outline" },
{ label: "Secondary", value: "secondary" },
{ label: "Ghost", value: "ghost" },
{ label: "Link", value: "link" },
],
},
size: {
type: "select",
label: "Size",
options: [
{ label: "Default", value: "default" },
{ label: "Extra Small", value: "xs" },
{ label: "Small", value: "sm" },
{ label: "Large", value: "lg" },
{ label: "Icon", value: "icon" },
],
},
},
},
}
export default ButtonConfig

View File

@@ -1,30 +0,0 @@
import * as React from "react"
import {
Button as ShadcnButton,
type buttonVariants,
} from "@/components/ui/button"
import type { VariantProps } from "class-variance-authority"
export type ButtonVariant = NonNullable<
VariantProps<typeof buttonVariants>["variant"]
>
export type ButtonSize = NonNullable<
VariantProps<typeof buttonVariants>["size"]
>
export interface ButtonProps
extends React.ComponentProps<typeof ShadcnButton> {
variant?: ButtonVariant
size?: ButtonSize
}
function Button({ variant = "default", size = "default", ...props }: ButtonProps) {
return (
<span className="inline-block">
<ShadcnButton variant={variant} size={size} {...props} />
</span>
)
}
export { Button }

View File

@@ -1,27 +0,0 @@
import type { ElementConfig } from "./types"
export const CardConfig: ElementConfig = {
Card: {
label: "Card",
fields: {
image: {
type: "text",
label: "Image URL",
},
title: {
type: "text",
label: "Title",
},
subtitle: {
type: "text",
label: "Subtitle",
},
tags: {
type: "tags",
label: "Tags",
},
},
},
}
export default CardConfig

View File

@@ -1,68 +0,0 @@
import * as React from "react"
import {
Card as ShadcnCard,
CardContent,
CardHeader,
} from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
import { Image } from "./Image"
import { Typography } from "./Typography"
export interface CardProps extends React.ComponentProps<typeof ShadcnCard> {
image?: string
title?: string
subtitle?: string
tags?: string[]
}
function Card({
image,
title,
subtitle,
tags,
className,
...props
}: CardProps) {
return (
<ShadcnCard
className={cn("overflow-hidden pt-0", className)}
{...props}
>
{image ? (
<Image
src={image}
alt={title ?? ""}
objectFit="cover"
width={600}
height={300}
className="h-48 w-full rounded-none"
/>
) : null}
<CardHeader>
{title ? <Typography variant="h5" text={title} /> : null}
{subtitle ? (
<Typography
variant="body2"
text={subtitle}
className="text-muted-foreground"
/>
) : null}
</CardHeader>
{tags && tags.length > 0 ? (
<CardContent className="flex flex-wrap gap-2">
{tags.map((tag, index) => (
<Badge key={`${tag}-${index}`} variant="secondary">
{tag}
</Badge>
))}
</CardContent>
) : null}
</ShadcnCard>
)
}
export { Card }

View File

@@ -1,23 +0,0 @@
import type { ElementConfig } from "./types"
export const IconConfig: ElementConfig = {
Icon: {
label: "Icon",
fields: {
name: {
type: "icon",
label: "Icon",
},
size: {
type: "text",
label: "Size",
},
className: {
type: "text",
label: "Class Name",
},
},
},
}
export default IconConfig

View File

@@ -1,25 +0,0 @@
import * as React from "react"
import { DynamicIcon, type IconName } from "lucide-react/dynamic"
import { cn } from "@/lib/utils"
export interface IconProps
extends Omit<React.ComponentProps<typeof DynamicIcon>, "name"> {
name: IconName
size?: number
className?: string
}
function Icon({ name, size = 24, className, ...props }: IconProps) {
return (
<DynamicIcon
name={name}
size={size}
className={cn(className)}
{...props}
/>
)
}
export { Icon }
export type { IconName }

View File

@@ -1,42 +0,0 @@
import type { ElementConfig } from "./types"
export const ImageConfig: ElementConfig = {
Image: {
label: "Image",
fields: {
src: {
type: "image",
label: "Source URL",
},
alt: {
type: "text",
label: "Alt Text",
},
objectFit: {
type: "select",
label: "Object Fit",
options: [
{ label: "Cover", value: "cover" },
{ label: "Contain", value: "contain" },
{ label: "Fill", value: "fill" },
{ label: "None", value: "none" },
{ label: "Scale Down", value: "scale-down" },
],
},
circle: {
type: "boolean",
label: "Circle",
},
width: {
type: "number",
label: "Width",
},
height: {
type: "number",
label: "Height",
},
},
},
}
export default ImageConfig

View File

@@ -1,57 +0,0 @@
import * as React from "react"
import NextImage from "next/image"
import { cn } from "@/lib/utils"
export type ObjectFit =
| "contain"
| "cover"
| "fill"
| "none"
| "scale-down"
const objectFitStyles: Record<ObjectFit, string> = {
contain: "object-contain",
cover: "object-cover",
fill: "object-fill",
none: "object-none",
"scale-down": "object-scale-down",
}
export interface ImageProps {
src: string
alt?: string
objectFit?: ObjectFit
circle?: boolean
height?: number
width?: number
className?: string
}
function Image({
src,
alt = "",
objectFit = "cover",
circle = false,
height = 300,
width = 300,
className,
}: ImageProps) {
return (
<span className="inline-block">
<NextImage
src={src}
alt={alt}
height={height}
width={width}
className={cn(
objectFitStyles[objectFit],
circle ? "rounded-full" : "rounded-md",
className
)}
/>
</span>
)
}
export { Image }

View File

@@ -1,59 +0,0 @@
import type { ElementConfig } from "./types"
export const TypographyConfig: ElementConfig = {
Typography: {
label: "Typography",
fields: {
text: {
type: "textarea",
label: "Text",
},
variant: {
type: "select",
label: "Variant",
options: [
{ label: "Heading 1", value: "h1" },
{ label: "Heading 2", value: "h2" },
{ label: "Heading 3", value: "h3" },
{ label: "Heading 4", value: "h4" },
{ label: "Heading 5", value: "h5" },
{ label: "Heading 6", value: "h6" },
{ label: "Body 1", value: "body1" },
{ label: "Body 2", value: "body2" },
{ label: "Caption", value: "caption" },
],
},
textAlign: {
type: "select",
label: "Text Align",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
{ label: "Justify", value: "justify" },
],
},
fontWeight: {
type: "select",
label: "Font Weight",
options: [
{ label: "Thin", value: "thin" },
{ label: "Extra Light", value: "extralight" },
{ label: "Light", value: "light" },
{ label: "Normal", value: "normal" },
{ label: "Medium", value: "medium" },
{ label: "Semibold", value: "semibold" },
{ label: "Bold", value: "bold" },
{ label: "Extra Bold", value: "extrabold" },
{ label: "Black", value: "black" },
],
},
className: {
type: "text",
label: "Class Name",
},
},
},
}
export default TypographyConfig

View File

@@ -1,119 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export type TypographyVariant =
| "h1"
| "h2"
| "h3"
| "h4"
| "h5"
| "h6"
| "body1"
| "body2"
| "caption"
export type TypographyAlign = "left" | "center" | "right" | "justify"
export type TypographyWeight =
| "thin"
| "extralight"
| "light"
| "normal"
| "medium"
| "semibold"
| "bold"
| "extrabold"
| "black"
const variantStyles: Record<TypographyVariant, string> = {
h1: "font-heading scroll-m-20 text-4xl tracking-tight text-balance lg:text-5xl",
h2: "font-heading scroll-m-20 text-3xl tracking-tight",
h3: "font-heading scroll-m-20 text-2xl tracking-tight",
h4: "font-heading scroll-m-20 text-xl tracking-tight",
h5: "font-heading scroll-m-20 text-lg tracking-tight",
h6: "font-heading scroll-m-20 text-base tracking-tight",
body1: "leading-7 text-base",
body2: "leading-6 text-sm",
caption: "text-xs text-muted-foreground",
}
const variantElement: Record<TypographyVariant, React.ElementType> = {
h1: "h1",
h2: "h2",
h3: "h3",
h4: "h4",
h5: "h5",
h6: "h6",
body1: "p",
body2: "p",
caption: "span",
}
const weightStyles: Record<TypographyWeight, string> = {
thin: "font-thin",
extralight: "font-extralight",
light: "font-light",
normal: "font-normal",
medium: "font-medium",
semibold: "font-semibold",
bold: "font-bold",
extrabold: "font-extrabold",
black: "font-black",
}
const alignStyles: Record<TypographyAlign, string> = {
left: "text-left",
center: "text-center",
right: "text-right",
justify: "text-justify",
}
const defaultWeight: Record<TypographyVariant, TypographyWeight> = {
h1: "bold",
h2: "bold",
h3: "bold",
h4: "bold",
h5: "bold",
h6: "bold",
body1: "normal",
body2: "normal",
caption: "normal",
}
export interface TypographyProps
extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
text?: React.ReactNode
variant?: TypographyVariant
textAlign?: TypographyAlign
fontWeight?: TypographyWeight
className?: string
}
function Typography({
text,
variant = "body1",
textAlign,
fontWeight,
className,
...props
}: TypographyProps) {
const Comp = variantElement[variant]
return (
<Comp
data-variant={variant}
className={cn(
variantStyles[variant],
weightStyles[fontWeight ?? defaultWeight[variant]],
textAlign && alignStyles[textAlign],
className
)}
{...props}
>
{text}
</Comp>
)
}
export { Typography }

View File

@@ -1,32 +0,0 @@
export type FieldType =
| "text"
| "textarea"
| "color"
| "icon"
| "array"
| "object"
| "boolean"
| "select"
| "image"
| "number"
| "tags"
export interface FieldOption {
label: string
value: string
}
export interface FieldConfig {
type: FieldType
label?: string
options?: FieldOption[]
arrayFields?: Record<string, FieldConfig>
objectFields?: Record<string, FieldConfig>
}
export interface ComponentConfig {
label: string
fields: Record<string, FieldConfig>
}
export type ElementConfig = Record<string, ComponentConfig>

View File

@@ -1,40 +0,0 @@
import React, { memo } from 'react';
import { cn } from '@/lib/utils';
interface AuroraTextProps {
children: React.ReactNode;
className?: string;
colors?: string[];
speed?: number;
}
export const AuroraText = memo(
({
children,
className,
colors = ['#FF0080', '#7928CA', '#0070F3', '#38bdf8'],
speed = 1,
}: AuroraTextProps) => {
const animationDuration = `${10 / speed}s`;
const gradientStyle = {
backgroundImage: `linear-gradient(90deg, ${colors.join(', ')}, ${colors[0]})`,
backgroundSize: '200% 100%',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
animation: `aurora-flow ${animationDuration} ease-in-out infinite`,
} as React.CSSProperties;
return (
<span className={cn('relative inline-block', className)}>
<span className="sr-only">{children}</span>
<span className="relative" style={gradientStyle} aria-hidden="true">
{children}
</span>
</span>
);
}
);
AuroraText.displayName = 'AuroraText';

View File

@@ -1,63 +0,0 @@
import React from 'react';
import { useRef, useEffect, useState } from 'react';
import { cn } from '@/lib/utils';
interface BlurFadeProps {
children: React.ReactNode;
className?: string;
duration?: number;
delay?: number;
inView?: boolean;
}
export function BlurFade({
children,
className,
duration = 0.4,
delay = 0,
inView = false,
}: BlurFadeProps) {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(!inView);
useEffect(() => {
if (!inView) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(entry.target);
}
},
{ threshold: 0.1, rootMargin: '-50px' }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
if (ref.current) {
observer.unobserve(ref.current);
}
};
}, [inView]);
return (
<div
ref={ref}
className={cn(
isVisible ? 'opacity-100 blur-none' : 'opacity-0 blur-sm',
className
)}
style={{
animation: isVisible
? `blur-fade ${duration}s ease-out ${delay}s forwards`
: 'none',
}}
>
{children}
</div>
);
}

View File

@@ -2,21 +2,26 @@
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import type { TooltipValueType } from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
const INITIAL_DIMENSION = { width: 320, height: 200 } as const
type TooltipNameType = number | string
export type ChartConfig = Record<
string,
{
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
>
type ChartContextProps = {
config: ChartConfig
@@ -39,15 +44,20 @@ function ChartContainer({
className,
children,
config,
initialDimension = INITIAL_DIMENSION,
...props
}: React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
initialDimension?: {
width: number
height: number
}
}) {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
@@ -55,13 +65,15 @@ function ChartContainer({
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
<RechartsPrimitive.ResponsiveContainer
initialDimension={initialDimension}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
@@ -71,7 +83,7 @@ function ChartContainer({
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
([, config]) => config.theme ?? config.color
)
if (!colorConfig.length) {
@@ -88,7 +100,7 @@ ${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
@@ -125,7 +137,13 @@ function ChartTooltipContent({
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}) {
} & Omit<
RechartsPrimitive.DefaultTooltipContentProps<
TooltipValueType,
TooltipNameType
>,
"accessibilityLayer"
>) {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
@@ -134,11 +152,11 @@ function ChartTooltipContent({
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
? (config[label]?.label ?? label)
: itemConfig?.label
if (labelFormatter) {
@@ -173,7 +191,7 @@ function ChartTooltipContent({
return (
<div
className={cn(
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
@@ -182,15 +200,15 @@ function ChartTooltipContent({
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
const indicatorColor = color ?? item.payload?.fill ?? item.color
return (
<div
key={item.dataKey}
key={index}
className={cn(
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
@@ -231,12 +249,14 @@ function ChartTooltipContent({
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
{itemConfig?.label ?? item.name}
</span>
</div>
{item.value && (
<span className="text-foreground font-mono font-medium tabular-nums">
{item.value.toLocaleString()}
{item.value != null && (
<span className="font-mono font-medium text-foreground tabular-nums">
{typeof item.value === "number"
? item.value.toLocaleString()
: String(item.value)}
</span>
)}
</div>
@@ -258,11 +278,10 @@ function ChartLegendContent({
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}) {
}: React.ComponentProps<"div"> & {
hideIcon?: boolean
nameKey?: string
} & RechartsPrimitive.DefaultLegendContentProps) {
const { config } = useChart()
if (!payload?.length) {
@@ -279,15 +298,15 @@ function ChartLegendContent({
>
{payload
.filter((item) => item.type !== "none")
.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
.map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
key={index}
className={cn(
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
@@ -342,9 +361,7 @@ function getPayloadConfigFromPayload(
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
return configLabelKey in config ? config[configLabelKey] : config[key]
}
export {

View File

@@ -50,8 +50,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}
>(({ className, children, showCloseButton = true, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
{/* Wrapper div handles centering, so animation transforms don't interfere */}
@@ -68,10 +70,12 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<RiCloseLine className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
{showCloseButton && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<RiCloseLine className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</div>
</DialogPortal>