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:
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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>
|
||||
@@ -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';
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user