Add Text Length Checker tool with comprehensive text analysis features

- Add new TextLengthTool.js with real-time text statistics
- Features: character/word/line/sentence/paragraph counting, reading time estimation
- Add Text Length Checker to navigation (ToolSidebar, Layout, App routing)
- Add Text Length Checker card to homepage
- Fix button styling with flex alignment for better UX
- Route: /text-length with Type icon from lucide-react
This commit is contained in:
dwindown
2025-09-21 07:09:33 +07:00
parent 6f5bdf5f0d
commit 82d14622ac
7 changed files with 571 additions and 117 deletions

265
src/pages/TextLengthTool.js Normal file
View File

@@ -0,0 +1,265 @@
import React, { useState, useEffect } from 'react';
import { Type, Copy, RotateCcw } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const TextLengthTool = () => {
const [text, setText] = useState('');
const [stats, setStats] = useState({
characters: 0,
charactersNoSpaces: 0,
words: 0,
sentences: 0,
paragraphs: 0,
lines: 0,
bytes: 0
});
const [showDetails, setShowDetails] = useState(false);
// Calculate text statistics
useEffect(() => {
const calculateStats = () => {
if (!text) {
setStats({
characters: 0,
charactersNoSpaces: 0,
words: 0,
sentences: 0,
paragraphs: 0,
lines: 0,
bytes: 0
});
return;
}
// Characters
const characters = text.length;
const charactersNoSpaces = text.replace(/\s/g, '').length;
// Words (split by whitespace and filter empty strings)
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
// Sentences (split by sentence endings)
const sentences = text.trim() ? text.split(/[.!?]+/).filter(s => s.trim().length > 0).length : 0;
// Paragraphs (split by double line breaks or more)
const paragraphs = text.trim() ? text.split(/\n\s*\n/).filter(p => p.trim().length > 0).length : 0;
// Lines (split by line breaks)
const lines = text ? text.split('\n').length : 0;
// Bytes (UTF-8 encoding)
const bytes = new TextEncoder().encode(text).length;
setStats({
characters,
charactersNoSpaces,
words,
sentences,
paragraphs,
lines,
bytes
});
};
calculateStats();
}, [text]);
const clearText = () => {
setText('');
};
const loadSample = () => {
setText(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum! Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium?`);
};
const formatNumber = (num) => {
return num.toLocaleString();
};
const getReadingTime = () => {
// Average reading speed: 200-250 words per minute
const wordsPerMinute = 225;
const minutes = Math.ceil(stats.words / wordsPerMinute);
return minutes === 1 ? '1 minute' : `${minutes} minutes`;
};
const getTypingTime = () => {
// Average typing speed: 40 words per minute
const wordsPerMinute = 40;
const minutes = Math.ceil(stats.words / wordsPerMinute);
return minutes === 1 ? '1 minute' : `${minutes} minutes`;
};
return (
<ToolLayout
title="Text Length Checker"
description="Analyze text length, word count, and other text statistics"
icon={Type}
>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={loadSample} className="tool-button-secondary">
Load Sample Text
</button>
<button onClick={clearText} className="tool-button-secondary flex items-center whitespace-nowrap">
<RotateCcw className="h-4 w-4 mr-2" />
Clear Text
</button>
<button
onClick={() => setShowDetails(!showDetails)}
className="tool-button-secondary"
>
{showDetails ? 'Hide' : 'Show'} Details
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Text Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Text to Analyze
</label>
<div className="relative">
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter or paste your text here to analyze its length and statistics..."
className="tool-input h-96 resize-none"
style={{ minHeight: '400px' }}
/>
{text && <CopyButton text={text} />}
</div>
</div>
{/* Statistics */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Text Statistics
</h3>
{/* Main Stats Grid */}
<div className="grid grid-cols-2 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-2xl font-bold text-primary-600 dark:text-primary-400">
{formatNumber(stats.characters)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Characters</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-2xl font-bold text-primary-600 dark:text-primary-400">
{formatNumber(stats.charactersNoSpaces)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Characters (no spaces)</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-2xl font-bold text-green-600 dark:text-green-400">
{formatNumber(stats.words)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Words</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{formatNumber(stats.lines)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Lines</div>
</div>
</div>
{/* Additional Stats (when details are shown) */}
{showDetails && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-xl font-bold text-purple-600 dark:text-purple-400">
{formatNumber(stats.sentences)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Sentences</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-xl font-bold text-orange-600 dark:text-orange-400">
{formatNumber(stats.paragraphs)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Paragraphs</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="text-xl font-bold text-red-600 dark:text-red-400">
{formatNumber(stats.bytes)} bytes
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Size (UTF-8 encoding)</div>
</div>
{/* Reading & Typing Time */}
{stats.words > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div className="text-lg font-semibold text-blue-800 dark:text-blue-200">
📖 {getReadingTime()}
</div>
<div className="text-sm text-blue-600 dark:text-blue-400">Estimated reading time</div>
</div>
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4">
<div className="text-lg font-semibold text-green-800 dark:text-green-200">
{getTypingTime()}
</div>
<div className="text-sm text-green-600 dark:text-green-400">Estimated typing time</div>
</div>
</div>
)}
</div>
)}
{/* Copy Statistics */}
{(stats.characters > 0 || stats.words > 0) && (
<div className="mt-4">
<button
onClick={() => {
const statsText = `Text Statistics:
Characters: ${formatNumber(stats.characters)}
Characters (no spaces): ${formatNumber(stats.charactersNoSpaces)}
Words: ${formatNumber(stats.words)}
Lines: ${formatNumber(stats.lines)}
Sentences: ${formatNumber(stats.sentences)}
Paragraphs: ${formatNumber(stats.paragraphs)}
Bytes: ${formatNumber(stats.bytes)}
${stats.words > 0 ? `Reading time: ${getReadingTime()}
Typing time: ${getTypingTime()}` : ''}`;
navigator.clipboard.writeText(statsText);
}}
className="flex items-center space-x-2 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<Copy className="h-4 w-4" />
<span>Copy Statistics</span>
</button>
</div>
)}
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Perfect for checking character limits for social media posts, essays, or articles</li>
<li> Real-time counting updates as you type or paste text</li>
<li> Includes reading and typing time estimates based on average speeds</li>
<li> Byte count shows the actual storage size of your text in UTF-8 encoding</li>
<li> Use "Show Details" to see additional statistics like sentences and paragraphs</li>
</ul>
</div>
</ToolLayout>
);
};
export default TextLengthTool;