feat: initial commit with seeded database and premium ui changes
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { MinusIcon, TrendingUpIcon, ArrowUpIcon, ChevronUpIcon, TrendingDownIcon, ArrowDownIcon, ChevronDownIcon } from "@/components/ui/phosphor-icons";
|
||||
|
||||
type DeltaIconVariant = "default" | "trend" | "arrow";
|
||||
type DeltaVariant = "default" | "badge";
|
||||
|
||||
type DeltaContextValue = {
|
||||
value: number;
|
||||
};
|
||||
|
||||
const DeltaContext = React.createContext<DeltaContextValue | null>(null);
|
||||
|
||||
function useDeltaValue() {
|
||||
const context = React.useContext(DeltaContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"DeltaIcon and DeltaValue must be used inside a `Delta` component."
|
||||
);
|
||||
}
|
||||
|
||||
return context.value;
|
||||
}
|
||||
|
||||
function Delta({
|
||||
className,
|
||||
value,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
value: number;
|
||||
variant?: DeltaVariant;
|
||||
}) {
|
||||
return (
|
||||
<DeltaContext.Provider value={{ value }}>
|
||||
{variant === "badge" ? (
|
||||
<Badge
|
||||
className={cn(
|
||||
"gap-1 border-none tabular-nums [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
value > 0
|
||||
? "bg-emerald-500/10 text-emerald-500"
|
||||
: "bg-red-500/10 text-red-500",
|
||||
className
|
||||
)}
|
||||
data-slot="delta"
|
||||
variant="secondary"
|
||||
{...(props as React.ComponentProps<typeof Badge>)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 text-muted-foreground tabular-nums",
|
||||
"[&_svg]:size-3 [&_svg]:shrink-0",
|
||||
value > 0 ? "text-emerald-600 dark:text-emerald-400" : "",
|
||||
value < 0 ? "text-rose-600 dark:text-rose-400" : "",
|
||||
className
|
||||
)}
|
||||
data-slot="delta"
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</DeltaContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function FilledShell({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: number;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex size-3 shrink-0 items-center justify-center rounded-full",
|
||||
"[&_svg]:size-2! [&_svg]:shrink-0 [&_svg]:stroke-3! [&_svg]:text-background",
|
||||
value > 0 && "bg-emerald-500",
|
||||
value < 0 && "bg-red-500",
|
||||
(!value || value === 0) && "bg-muted-foreground"
|
||||
)}
|
||||
data-slot="delta-icon"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function DeltaIcon({
|
||||
variant = "default",
|
||||
filled = false,
|
||||
className,
|
||||
...props
|
||||
}: Omit<React.ComponentProps<"svg">, "fill"> & {
|
||||
variant?: DeltaIconVariant;
|
||||
filled?: boolean;
|
||||
}) {
|
||||
const resolvedValue = useDeltaValue();
|
||||
|
||||
const mergedClassName = cn(className);
|
||||
|
||||
const shell = (node: React.ReactElement) =>
|
||||
filled ? <FilledShell value={resolvedValue}>{node}</FilledShell> : node;
|
||||
|
||||
const slotProps = filled ? {} : { "data-slot": "delta-icon" as const };
|
||||
|
||||
if (!resolvedValue || resolvedValue === 0) {
|
||||
return shell(
|
||||
<MinusIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
if (resolvedValue > 0) {
|
||||
if (variant === "trend") {
|
||||
return shell(
|
||||
<TrendingUpIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "arrow") {
|
||||
return shell(
|
||||
<ArrowUpIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
return shell(
|
||||
<ChevronUpIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "trend") {
|
||||
return shell(
|
||||
<TrendingDownIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "arrow") {
|
||||
return shell(
|
||||
<ArrowDownIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
return shell(
|
||||
<ChevronDownIcon {...slotProps} className={mergedClassName} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DeltaValue({
|
||||
className,
|
||||
precision = 1,
|
||||
suffix = "%",
|
||||
absolute = true,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> & {
|
||||
precision?: number;
|
||||
suffix?: string;
|
||||
absolute?: boolean;
|
||||
}) {
|
||||
const resolvedValue = useDeltaValue();
|
||||
|
||||
const formattedValue = (
|
||||
absolute ? Math.abs(resolvedValue) : resolvedValue
|
||||
).toFixed(precision);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn("tabular-nums", className)}
|
||||
data-slot="delta-value"
|
||||
{...props}
|
||||
>
|
||||
{formattedValue}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
export { Delta, DeltaIcon, DeltaValue };
|
||||
Reference in New Issue
Block a user