[dyad] Added status indicators to tabs - wrote 4 file(s)
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getLengthIndicatorColor } from "@/lib/analysis";
|
||||
|
||||
interface LengthIndicatorProps {
|
||||
length: number;
|
||||
@@ -24,20 +25,24 @@ const DESCRIPTION_THRESHOLDS = {
|
||||
};
|
||||
|
||||
export function LengthIndicator({ length, type }: LengthIndicatorProps) {
|
||||
const color = getLengthIndicatorColor(length, type);
|
||||
|
||||
const colorClassMap = {
|
||||
green: "bg-green-500",
|
||||
yellow: "bg-yellow-500",
|
||||
red: "bg-red-500",
|
||||
gray: "bg-gray-400",
|
||||
};
|
||||
|
||||
const thresholds =
|
||||
type === "title" ? TITLE_THRESHOLDS : DESCRIPTION_THRESHOLDS;
|
||||
|
||||
let colorClass = "bg-red-500";
|
||||
let tooltipText = "Length is not optimal";
|
||||
|
||||
if (length === 0) {
|
||||
tooltipText = "Not found";
|
||||
colorClass = "bg-gray-400";
|
||||
} else if (length >= thresholds.good.min && length <= thresholds.good.max) {
|
||||
colorClass = "bg-green-500";
|
||||
tooltipText = "Optimal length";
|
||||
} else if (length >= thresholds.ok.min && length <= thresholds.ok.max) {
|
||||
colorClass = "bg-yellow-500";
|
||||
tooltipText = "Length could be improved";
|
||||
} else if (length < thresholds.ok.min) {
|
||||
tooltipText = "Too short";
|
||||
@@ -49,7 +54,7 @@ export function LengthIndicator({ length, type }: LengthIndicatorProps) {
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={cn("h-3 w-3 rounded-full", colorClass)} />
|
||||
<div className={cn("h-3 w-3 rounded-full", colorClassMap[color])} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
ImageOff,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { extractMetaData, type HeadlineNode, type ImageAltData } from "@/app/actions";
|
||||
import {
|
||||
extractMetaData,
|
||||
type HeadlineNode,
|
||||
type ImageAltData,
|
||||
} from "@/app/actions";
|
||||
import { LengthIndicator } from "./length-indicator";
|
||||
import { CopyButton } from "./copy-button";
|
||||
import { SerpPreview } from "./serp-preview";
|
||||
@@ -23,6 +27,8 @@ import { FaqDisplay } from "./faq-display";
|
||||
import { HeadlineTree } from "./headline-tree";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ImageAltDisplay } from "./image-alt-display";
|
||||
import { TabIndicator } from "./tab-indicator";
|
||||
import { getLengthIndicatorColor, type IndicatorColor } from "@/lib/analysis";
|
||||
|
||||
interface MetaData {
|
||||
title: string;
|
||||
@@ -57,6 +63,62 @@ export function MetaForm() {
|
||||
}
|
||||
}, [metaData]);
|
||||
|
||||
const tabColors = useMemo(() => {
|
||||
if (!metaData) return null;
|
||||
|
||||
// Analysis Tab
|
||||
const titleStatus = getLengthIndicatorColor(editableTitle.length, "title");
|
||||
const descStatus = getLengthIndicatorColor(
|
||||
editableDescription.length,
|
||||
"description"
|
||||
);
|
||||
const analysisStatuses: IndicatorColor[] = [titleStatus, descStatus];
|
||||
let analysisColor: IndicatorColor;
|
||||
if (
|
||||
analysisStatuses.includes("red") ||
|
||||
analysisStatuses.includes("gray")
|
||||
) {
|
||||
analysisColor = "red";
|
||||
} else if (analysisStatuses.includes("yellow")) {
|
||||
analysisColor = "yellow";
|
||||
} else {
|
||||
analysisColor = "green";
|
||||
}
|
||||
|
||||
// Headlines Tab
|
||||
let headlinesColor: IndicatorColor = "gray";
|
||||
if (metaData.headlines && metaData.headlines.length > 0) {
|
||||
const h1s = metaData.headlines.filter((h) => h.tag === "h1");
|
||||
if (h1s.length === 0) {
|
||||
headlinesColor = "red";
|
||||
} else if (h1s.length > 1) {
|
||||
headlinesColor = "yellow";
|
||||
} else {
|
||||
headlinesColor = "green";
|
||||
}
|
||||
}
|
||||
|
||||
// Images Tab
|
||||
let imagesColor: IndicatorColor = "gray";
|
||||
if (metaData.images && metaData.images.length > 0) {
|
||||
const missingAltCount = metaData.images.filter((img) => !img.alt).length;
|
||||
imagesColor = missingAltCount > 0 ? "red" : "green";
|
||||
}
|
||||
|
||||
// FAQ Tab
|
||||
let faqColor: IndicatorColor = "gray";
|
||||
if (metaData.faq && metaData.faq.length > 0) {
|
||||
faqColor = "green";
|
||||
}
|
||||
|
||||
return {
|
||||
analysis: analysisColor,
|
||||
headlines: headlinesColor,
|
||||
images: imagesColor,
|
||||
faq: faqColor,
|
||||
};
|
||||
}, [metaData, editableTitle, editableDescription]);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setLoading(true);
|
||||
@@ -149,15 +211,27 @@ export function MetaForm() {
|
||||
{!loading && metaData && (
|
||||
<Tabs defaultValue="analysis" className="w-full">
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="analysis">Meta Analysis</TabsTrigger>
|
||||
<TabsTrigger value="analysis">
|
||||
{tabColors && <TabIndicator color={tabColors.analysis} />}
|
||||
Meta Analysis
|
||||
</TabsTrigger>
|
||||
{metaData.headlines && metaData.headlines.length > 0 && (
|
||||
<TabsTrigger value="headlines">Headlines</TabsTrigger>
|
||||
<TabsTrigger value="headlines">
|
||||
{tabColors && <TabIndicator color={tabColors.headlines} />}
|
||||
Headlines
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{metaData.images && metaData.images.length > 0 && (
|
||||
<TabsTrigger value="images">Images</TabsTrigger>
|
||||
<TabsTrigger value="images">
|
||||
{tabColors && <TabIndicator color={tabColors.images} />}
|
||||
Images
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{metaData.faq && metaData.faq.length > 0 && (
|
||||
<TabsTrigger value="faq">FAQ</TabsTrigger>
|
||||
<TabsTrigger value="faq">
|
||||
{tabColors && <TabIndicator color={tabColors.faq} />}
|
||||
FAQ
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
|
||||
26
src/components/tab-indicator.tsx
Normal file
26
src/components/tab-indicator.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { IndicatorColor } from "@/lib/analysis";
|
||||
|
||||
interface TabIndicatorProps {
|
||||
color: IndicatorColor;
|
||||
}
|
||||
|
||||
export function TabIndicator({ color }: TabIndicatorProps) {
|
||||
const colorClassMap = {
|
||||
green: "bg-green-500",
|
||||
yellow: "bg-yellow-500",
|
||||
red: "bg-red-500",
|
||||
gray: "bg-gray-400",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-2 w-2 rounded-full mr-2 flex-shrink-0",
|
||||
colorClassMap[color]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
30
src/lib/analysis.ts
Normal file
30
src/lib/analysis.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export type IndicatorColor = "green" | "yellow" | "red" | "gray";
|
||||
|
||||
const TITLE_THRESHOLDS = {
|
||||
good: { min: 30, max: 60 },
|
||||
ok: { min: 15, max: 70 },
|
||||
};
|
||||
|
||||
const DESCRIPTION_THRESHOLDS = {
|
||||
good: { min: 120, max: 158 },
|
||||
ok: { min: 50, max: 170 },
|
||||
};
|
||||
|
||||
export function getLengthIndicatorColor(
|
||||
length: number,
|
||||
type: "title" | "description"
|
||||
): IndicatorColor {
|
||||
const thresholds =
|
||||
type === "title" ? TITLE_THRESHOLDS : DESCRIPTION_THRESHOLDS;
|
||||
|
||||
if (length === 0) {
|
||||
return "gray";
|
||||
}
|
||||
if (length >= thresholds.good.min && length <= thresholds.good.max) {
|
||||
return "green";
|
||||
}
|
||||
if (length >= thresholds.ok.min && length <= thresholds.ok.max) {
|
||||
return "yellow";
|
||||
}
|
||||
return "red";
|
||||
}
|
||||
Reference in New Issue
Block a user