Finished, responsive

This commit is contained in:
Dominik Natter
2025-03-28 09:56:55 +01:00
parent 39ae51c89d
commit 91b5a92d9f
6 changed files with 106 additions and 15 deletions

View File

@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-progress": "^1.1.2",

24
src/app/natter-black.svg Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 479.47 75">
<defs>
<style>
.cls-1 {
fill: #231f20;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer_1">
<g>
<polygon class="cls-1" points="0 75 37.5 75 37.5 37.5 0 0 0 75"/>
<polygon class="cls-1" points="37.5 0 37.5 37.5 75 75 75 0 37.5 0"/>
</g>
<g>
<path class="cls-1" d="M87.46,73.48V1.38h9.99l44.08,59.02-1.65.93c-.48-2.81-.88-6.08-1.18-9.79s-.53-7.79-.67-12.26c-.14-4.46-.21-9.2-.21-14.21V1.38h11.12v72.1h-10.2l-43.36-56.86,1.13-1.44c.48,5.22.86,9.49,1.13,12.82.27,3.33.48,5.99.62,7.98.14,1.99.22,3.55.26,4.69.03,1.13.05,2.08.05,2.83v29.97h-11.12Z"/>
<path class="cls-1" d="M161.62,73.48L189.53,1.38h11.33l27.71,72.1h-11.85l-16.48-43.47c-.21-.48-.6-1.65-1.18-3.5-.58-1.85-1.25-3.91-2.01-6.18-.76-2.27-1.44-4.34-2.06-6.23-.62-1.89-1.03-3.11-1.24-3.66l2.37-.1c-.41,1.17-.91,2.64-1.49,4.43-.58,1.79-1.2,3.67-1.85,5.67-.65,1.99-1.29,3.86-1.91,5.61-.62,1.75-1.13,3.21-1.54,4.38l-16.38,43.05h-11.33ZM173.98,55.56l4.12-10.71h32.75l4.74,10.71h-41.61Z"/>
<path class="cls-1" d="M254.93,73.48V12.09h-20.19V1.38h52.12v10.71h-20.81v61.39h-11.12Z"/>
<path class="cls-1" d="M314.26,73.48V12.09h-20.19V1.38h52.12v10.71h-20.81v61.39h-11.12Z"/>
<path class="cls-1" d="M359.89,73.48V1.38h45.42v10.71h-34.3v50.68h34.3v10.71h-45.42ZM364.94,41.86v-10.71h34.71v10.71h-34.71Z"/>
<path class="cls-1" d="M423.13,73.48V1.38h30.69c4.12,0,7.91,1.01,11.38,3.04,3.47,2.03,6.23,4.77,8.29,8.24,2.06,3.47,3.09,7.36,3.09,11.69,0,3.91-1.03,7.55-3.09,10.92-2.06,3.37-4.81,6.06-8.24,8.08-3.43,2.03-7.25,3.04-11.43,3.04h-19.57v27.09h-11.12ZM434.26,35.68h20.6c1.99,0,3.78-.5,5.36-1.49,1.58-.99,2.82-2.37,3.71-4.12.89-1.75,1.34-3.69,1.34-5.82,0-2.4-.57-4.51-1.7-6.33-1.13-1.82-2.71-3.25-4.74-4.27-2.03-1.03-4.31-1.54-6.85-1.54h-17.72v23.59ZM465.98,73.48l-18.33-32.55,11.43-2.58,20.39,35.23-13.49-.1Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -5,6 +5,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Checkbox } from "@/components/ui/checkbox";
import { Stats } from "@/components/Stats";
import LetterDensity from "@/components/LetterDensity";
import { Input } from "@/components/ui/input";
const config = {
includeSpaces: true,
@@ -16,6 +17,21 @@ export default function Home() {
const [text, setText] = useState("");
const [analysis, setAnalysis] = useState<any>(null);
const [includeSpaces, setIncludeSpaces] = useState(true);
const [useCustomLimit, setUseCustomLimit] = useState(false);
const [charLimit, setCharLimit] = useState(config.charLimit);
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newText = e.target.value;
const limitToUse = useCustomLimit ? charLimit : config.charLimit;
const currentLength = includeSpaces
? newText.length
: newText.replace(/\s/g, "").length;
if (currentLength <= limitToUse) {
setText(newText);
}
};
useEffect(() => {
const result: any = {};
@@ -24,7 +40,10 @@ export default function Home() {
result.charCount = includeSpaces ? inputText.length : inputText.replace(/\s/g, "").length;
result.wordCount = (inputText.match(/[a-zA-ZäöüÄÖÜß]+/g) || []).length;
result.sentenceCount = (inputText.match(/[\w\s][.?!](?=\s|$)/g) || []).length;
result.exceedsLimit = result.charCount > config.charLimit;
const limitToUse = useCustomLimit ? charLimit : config.charLimit;
result.exceedsLimit = result.charCount > limitToUse;
result.readingTimeMinutes = Math.ceil(result.wordCount / 200);
const letters = inputText.toLowerCase().match(/[a-z]/g) || [];
@@ -40,28 +59,51 @@ export default function Home() {
}
setAnalysis(result);
}, [text, includeSpaces]);
}, [text, includeSpaces, useCustomLimit, charLimit]);
return (
<div className="max-w-6xl m-auto min-h-screen flex flex-col">
<h1 className="text-6xl font-semibold text-center">
<div className="max-w-6xl m-auto min-h-screen flex flex-col p-4 md:px-8 lg:p-0">
<h1 className="text-6xl font-semibold text-center pt-16 pb-10">
Analyze your Text<br />in real-time.
</h1>
<Textarea
className="h-72"
placeholder="Start typing here...(or paste your text)"
onChange={(e) => setText(e.target.value)}
onChange={handleTextChange}
value={text}
/>
<div className="flex flex-row space-x-2 pt-2 justify-between">
<div>
<div className="flex flex-col md:flex-row space-x-2 pt-2 justify-between pb-8">
<div className="w-auto gap-4 flex flex-col md:flex-row justify-items-center pb-8">
<div className="flex items-center gap-2">
<Checkbox id="spaces" checked={includeSpaces} onCheckedChange={setIncludeSpaces} />
<Checkbox
id="spaces"
checked={includeSpaces}
onCheckedChange={(checked) => setIncludeSpaces(!!checked)}
/>
<label htmlFor="spaces" className="text-sm font-medium leading-none">
Exclude Spaces
</label>
</div>
<div className="flex items-center gap-2 ">
<Checkbox
id="characterLimit"
checked={useCustomLimit}
onCheckedChange={(checked) => setUseCustomLimit(!!checked)}
/>
<label htmlFor="characterLimit" className="text-sm font-medium leading-none">
Set Character Limit
</label>
<Input
type="number"
value={charLimit}
onChange={(e) => setCharLimit(Number(e.target.value))}
disabled={!useCustomLimit}
className="w-24"
/>
</div>
</div>
<div>
<p>Approx. reading time: {analysis?.readingTimeMinutes ?? 0} min</p>
@@ -69,14 +111,14 @@ export default function Home() {
</div>
{analysis && (
<>
<div className="h-auto w-full">
<Stats
charCount={analysis.charCount}
wordCount={analysis.wordCount}
sentenceCount={analysis.sentenceCount}
/>
<LetterDensity density={analysis.letterDensity} />
</>
</div>
)}
</div>
);

View File

@@ -1,12 +1,15 @@
import {Button} from "@/components/ui/button";
import {DarkModeToggle} from "@/components/DarkModeToggle";
import Image from 'next/image'
import NatterLogo from '@/app/natter-black.svg'
export default function Header(){
return(
<header className="w-full h-16 flex justify-between p-4">
<div>
<h2 className="text-3xl">Logo</h2>
<header className="w-full h-32 flex justify-between p-4 items-center">
<div className="bg-white w-auto w-auto p-2 rounded-lg">
<Image className="h-8 w-auto" src={NatterLogo} alt={"Natter Logo"}></Image>
</div>
<div>
<DarkModeToggle></DarkModeToggle>

View File

@@ -1,5 +1,6 @@
"use client";
import { Progress } from "@/components/ui/progress";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
type LetterDensityProps = {
density: Record<string, number>;
@@ -7,12 +8,14 @@ type LetterDensityProps = {
export default function LetterDensity({ density }: LetterDensityProps) {
const sorted = Object.entries(density).sort((a, b) => b[1] - a[1]);
const firstFive = sorted.slice(0, 5);
const rest = sorted.slice(5);
return (
<div className="h-auto w-full pt-8">
<h2 className="text-2xl">Letter Density</h2>
<div className="flex flex-col py-4 gap-2">
{sorted.map(([letter, value]) => (
{firstFive.map(([letter, value]) => (
<div className="flex flex-row items-center" key={letter}>
<p className="pr-4 text-lg uppercase">{letter}</p>
<Progress className="h-4 flex-1" value={value * 100} />
@@ -20,6 +23,24 @@ export default function LetterDensity({ density }: LetterDensityProps) {
</div>
))}
</div>
{rest.length > 0 && (
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger className="text-xl">See more</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col py-4 gap-2">
{rest.map(([letter, value]) => (
<div className="flex flex-row items-center" key={letter}>
<p className="pr-4 text-lg uppercase">{letter}</p>
<Progress className="h-4 flex-1" value={value * 100} />
<p className="pl-4">{(value * 100).toFixed(2)}%</p>
</div>
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
</div>
);
}

View File

@@ -8,7 +8,7 @@ interface StatsProps {
export function Stats({ charCount, wordCount, sentenceCount }: StatsProps) {
return (
<div className="flex flex-row space-x-4 gap-4 h-32">
<div className="flex flex-col md:flex-row space-x-4 gap-4 w-full">
<Card className="w-full">
<CardHeader className="relative">
<CardTitle className="text-6xl font-semibold tabular-nums">{charCount}</CardTitle>