Skip to content

Commit 04ea6fc

Browse files
committed
feat: Enhance UI and functionality across components
- Added Google Fonts for improved typography. - Updated NavBar and Footer styles for better visual hierarchy. - Enhanced DataStructureCard with expandable details and improved layout. - Improved ArrayVisualizer with dynamic capacity management and error handling. - Updated HashVisualizer to allow dynamic bucket sizing and improved SVG rendering. - Added ArrayComparison component to illustrate differences between static and dynamic arrays. - Enhanced Home page with a more engaging layout and search functionality. - Updated Learn page to include ArrayComparison for the 'array' topic. - Improved Tailwind CSS configuration with custom colors and shadows. - Added utility classes for buttons and inputs for consistent styling.
1 parent 29e2753 commit 04ea6fc

11 files changed

Lines changed: 405 additions & 81 deletions

File tree

index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>DataStructViz</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
710
</head>
8-
<body class="bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100">
11+
<body class="app-bg text-gray-900 dark:text-gray-100">
912
<div id="root"></div>
1013
<script type="module" src="/src/main.tsx"></script>
1114
</body>
1215
</html>
13-

src/App.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ function NavBar() {
1212
const link = ({ isActive }: { isActive: boolean }) =>
1313
`${base} ${isActive ? active : 'hover:bg-gray-100 dark:hover:bg-gray-900'}`;
1414
return (
15-
<header className="sticky top-0 z-10 border-b border-gray-200 dark:border-gray-800 bg-white/90 dark:bg-gray-950/90 backdrop-blur">
16-
<div className="max-w-6xl mx-auto px-4 flex items-center justify-between h-14">
15+
<header className="sticky top-0 z-10 border-b border-gray-200/80 dark:border-gray-800/80 bg-white/70 dark:bg-gray-950/70 backdrop-blur supports-[backdrop-filter]:bg-white/50">
16+
<div className="container max-w-6xl flex items-center justify-between h-14">
1717
<NavLink to="/" className="font-semibold tracking-tight">
18-
DataStructViz
18+
<span className="brand-gradient">DataStructViz</span>
1919
</NavLink>
2020
<nav className="flex items-center gap-2">
2121
<NavLink to="/" className={link}>Home</NavLink>
@@ -41,7 +41,7 @@ export default function App() {
4141
return (
4242
<div className="min-h-screen flex flex-col">
4343
<NavBar />
44-
<main className="flex-1 max-w-6xl mx-auto w-full px-4 py-6">
44+
<main className="flex-1 container max-w-6xl py-6">
4545
<Routes>
4646
<Route path="/" element={<Home />} />
4747
<Route path="/learn/:topic" element={<Learn />} />
@@ -54,4 +54,3 @@ export default function App() {
5454
</div>
5555
);
5656
}
57-

src/components/ArrayComparison.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export default function ArrayComparison() {
2+
const rows: { label: string; static: string; dynamic: string }[] = [
3+
{ label: 'Capacity', static: 'Fixed at allocation time', dynamic: 'Grows automatically (resizes when full)' },
4+
{ label: 'Access by index', static: 'O(1)', dynamic: 'O(1)' },
5+
{ label: 'Append (average)', static: 'O(1) if space remains, else impossible', dynamic: 'O(1) amortized' },
6+
{ label: 'Append (worst case)', static: 'N/A (no growth)', dynamic: 'O(n) during resize + copy' },
7+
{ label: 'Insert/delete in middle', static: 'O(n) (shift elements)', dynamic: 'O(n) (shift elements)' },
8+
{ label: 'Memory overhead', static: 'Exact size only', dynamic: 'Extra capacity (unused slots)' },
9+
{ label: 'Typical implementation', static: 'Stack/heap-allocated fixed buffer', dynamic: 'Resizable buffer (e.g., doubling strategy)' },
10+
{ label: 'Great for', static: 'Known size, tight memory, fixed buffers', dynamic: 'Unknown size, frequent appends' },
11+
];
12+
13+
return (
14+
<section className="rounded border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 overflow-hidden">
15+
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-800">
16+
<h2 className="font-semibold">Static vs Dynamic Arrays</h2>
17+
<p className="text-sm text-gray-600 dark:text-gray-400">Key differences, performance, and trade‑offs.</p>
18+
</div>
19+
<div className="divide-y divide-gray-200 dark:divide-gray-800">
20+
<div className="grid grid-cols-3 text-sm px-4 py-2 font-medium">
21+
<div className="text-gray-500">Factor</div>
22+
<div>Static Array</div>
23+
<div>Dynamic Array</div>
24+
</div>
25+
{rows.map((r) => (
26+
<div key={r.label} className="grid grid-cols-3 text-sm px-4 py-2">
27+
<div className="text-gray-500">{r.label}</div>
28+
<div>{r.static}</div>
29+
<div>{r.dynamic}</div>
30+
</div>
31+
))}
32+
</div>
33+
<div className="px-4 py-3 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-900">
34+
Amortized O(1) means most appends are O(1), with occasional O(n) when the array resizes and copies elements.
35+
</div>
36+
</section>
37+
);
38+
}
39+

src/components/DataStructureCard.tsx

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Link } from 'react-router-dom';
2+
import { useState } from 'react';
23
import type { Topic } from '@data/topics';
34

45
type Props = {
@@ -7,24 +8,75 @@ type Props = {
78
};
89

910
export default function DataStructureCard({ topic, progress = 0 }: Props) {
11+
const [open, setOpen] = useState(false);
12+
const complexity = topic.complexity || {};
13+
const keys = ['Access', 'Search', 'Insert', 'Delete', 'Push', 'Pop', 'Enqueue', 'Dequeue', 'Extract', 'Get', 'Put', 'Space', 'Traverse', 'Prefix', 'Peek'];
14+
const compPairs = keys.filter(k => complexity[k]).map(k => [k, complexity[k]!] as const);
15+
const useCases = (topic.useCases || []).slice(0, 3);
16+
const considerations = (topic.considerations || []).slice(0, 3);
17+
1018
return (
11-
<Link
12-
to={`/learn/${topic.slug}`}
13-
className="block rounded-lg border border-gray-200 dark:border-gray-800 p-4 hover:shadow-sm transition"
14-
>
15-
<div className="flex items-center justify-between gap-2">
16-
<h3 className="font-semibold text-lg">{topic.title}</h3>
17-
<span className="text-xs text-gray-500 uppercase tracking-wide">{topic.category}</span>
18-
</div>
19-
<p className="text-sm mt-1 text-gray-600 dark:text-gray-400 line-clamp-2">{topic.description}</p>
20-
<div className="mt-3 h-2 bg-gray-100 dark:bg-gray-900 rounded">
21-
<div
22-
className="h-2 bg-blue-500 rounded"
23-
style={{ width: `${Math.round(progress * 100)}%` }}
24-
/>
19+
<div className="rounded-lg border border-gray-200 dark:border-gray-800 hover:shadow-sm transition">
20+
<Link to={`/learn/${topic.slug}`} className="block p-4">
21+
<div className="flex items-center justify-between gap-2">
22+
<h3 className="font-semibold text-lg">{topic.title}</h3>
23+
<span className="text-xs text-gray-500 uppercase tracking-wide">{topic.category}</span>
24+
</div>
25+
<p className="text-sm mt-1 text-gray-600 dark:text-gray-400 line-clamp-2">{topic.description}</p>
26+
<div className="mt-3 h-2 bg-gray-100 dark:bg-gray-900 rounded">
27+
<div className="h-2 bg-blue-500 rounded" style={{ width: `${Math.round(progress * 100)}%` }} />
28+
</div>
29+
<div className="mt-2 text-xs text-gray-500">{Math.round(progress * 100)}% complete</div>
30+
</Link>
31+
<div className="px-4 pb-3 flex justify-between items-center gap-2">
32+
<button
33+
className="text-sm text-blue-600 hover:underline"
34+
onClick={(e) => { e.preventDefault(); setOpen(o => !o); }}
35+
>
36+
{open ? 'Hide details' : 'Quick details'}
37+
</button>
38+
<Link to={`/learn/${topic.slug}`} className="text-sm text-gray-600 dark:text-gray-400 hover:underline">Go to Learn →</Link>
2539
</div>
26-
<div className="mt-2 text-xs text-gray-500">{Math.round(progress * 100)}% complete</div>
27-
</Link>
40+
{open && (
41+
<div className="px-4 pb-4 space-y-2 text-sm">
42+
{topic.how && (
43+
<div>
44+
<div className="text-xs font-semibold text-gray-500">How it works</div>
45+
<div className="text-gray-700 dark:text-gray-300">{topic.how}</div>
46+
</div>
47+
)}
48+
{compPairs.length > 0 && (
49+
<div>
50+
<div className="text-xs font-semibold text-gray-500">Time Complexity</div>
51+
<ul className="grid grid-cols-2 gap-x-4">
52+
{compPairs.map(([k, v]) => (
53+
<li key={k} className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">{k}</span><span className="font-mono">{v}</span></li>
54+
))}
55+
</ul>
56+
</div>
57+
)}
58+
{(useCases.length > 0 || considerations.length > 0) && (
59+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
60+
{useCases.length > 0 && (
61+
<div>
62+
<div className="text-xs font-semibold text-gray-500">Use Cases</div>
63+
<ul className="list-disc ml-4">
64+
{useCases.map(u => <li key={u}>{u}</li>)}
65+
</ul>
66+
</div>
67+
)}
68+
{considerations.length > 0 && (
69+
<div>
70+
<div className="text-xs font-semibold text-gray-500">Considerations</div>
71+
<ul className="list-disc ml-4">
72+
{considerations.map(u => <li key={u}>{u}</li>)}
73+
</ul>
74+
</div>
75+
)}
76+
</div>
77+
)}
78+
</div>
79+
)}
80+
</div>
2881
);
2982
}
30-

src/components/Visualizer/ArrayVisualizer.tsx

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,92 @@
1-
import { useState } from 'react';
1+
import { useMemo, useState } from 'react';
22

33
export default function ArrayVisualizer() {
44
const [arr, setArr] = useState<string[]>([]);
55
const [val, setVal] = useState('');
66
const [idx, setIdx] = useState('0');
7+
const [cap, setCap] = useState<number>(4);
8+
const [capInput, setCapInput] = useState('4');
9+
const [error, setError] = useState<string | null>(null);
10+
11+
const n = arr.length;
12+
const iNum = useMemo(() => Number(idx), [idx]);
13+
const idxIsInt = Number.isInteger(iNum) && iNum >= 0;
14+
const idxValidForSet = idxIsInt && iNum < cap; // allow setting within capacity
15+
const idxValidForInsert = idxIsInt && iNum <= n && n < cap; // insert shifts, must have space
16+
17+
const clearErrorSoon = () => setTimeout(() => setError(null), 1500);
18+
19+
const applySize = () => {
20+
const next = Number(capInput);
21+
if (!Number.isInteger(next) || next < 0 || next > 50) {
22+
setError('Size must be an integer 0..50');
23+
clearErrorSoon();
24+
return;
25+
}
26+
setCap(next);
27+
if (next < arr.length) setArr(a => a.slice(0, next));
28+
};
729

830
const push = () => {
9-
if (!val) return; setArr(a => [...a, val]); setVal('');
31+
if (!val) return;
32+
if (n >= cap) { setError('Array is full'); clearErrorSoon(); return; }
33+
setArr(a => [...a, val]); setVal('');
1034
};
1135
const insertAt = () => {
12-
const i = Number(idx); if (!Number.isInteger(i) || i < 0 || i > arr.length) return;
13-
const a = arr.slice(); a.splice(i, 0, val || ''); setArr(a); setVal('');
36+
if (!idxIsInt) { setError('Index must be an integer'); clearErrorSoon(); return; }
37+
if (n >= cap) { setError('Array is full'); clearErrorSoon(); return; }
38+
if (iNum < 0 || iNum > n) { setError(`Insert index must be 0..${n}`); clearErrorSoon(); return; }
39+
const a = arr.slice(); a.splice(iNum, 0, val || ''); setArr(a); setVal('');
1440
};
1541
const removeAt = () => {
16-
const i = Number(idx); if (!Number.isInteger(i) || i < 0 || i >= arr.length) return;
17-
const a = arr.slice(); a.splice(i, 1); setArr(a);
42+
if (!idxIsInt || iNum < 0 || iNum >= n) { setError(`Remove index must be 0..${Math.max(0, n - 1)}`); clearErrorSoon(); return; }
43+
const a = arr.slice(); a.splice(iNum, 1); setArr(a);
1844
};
1945
const setAt = () => {
20-
const i = Number(idx); if (!Number.isInteger(i) || i < 0 || i >= arr.length) return;
21-
const a = arr.slice(); a[i] = val; setArr(a); setVal('');
46+
if (!idxIsInt || iNum < 0 || iNum >= cap) { setError(`Set index must be 0..${Math.max(0, cap - 1)}`); clearErrorSoon(); return; }
47+
// Setting beyond current length expands up to cap by filling empty slots
48+
const a = arr.slice();
49+
while (a.length < iNum) a.push('');
50+
if (iNum === a.length && a.length >= cap) { setError('Array is full'); clearErrorSoon(); return; }
51+
a[iNum] = val;
52+
if (iNum === arr.length) {
53+
// grew by one
54+
if (a.length > cap) { setError('Array is full'); clearErrorSoon(); return; }
55+
}
56+
setArr(a);
57+
setVal('');
2258
};
2359

2460
return (
2561
<div className="space-y-3">
2662
<div className="flex flex-wrap gap-2 items-center">
2763
<input className="border rounded px-2 py-1 w-32 bg-white dark:bg-gray-900" placeholder="value" value={val} onChange={e => setVal(e.target.value)} />
28-
<input className="border rounded px-2 py-1 w-20 bg-white dark:bg-gray-900" placeholder="index" value={idx} onChange={e => setIdx(e.target.value)} />
64+
<input className="border rounded px-2 py-1 w-24 bg-white dark:bg-gray-900" placeholder={`index`} value={idx} onChange={e => setIdx(e.target.value)} />
2965
<button className="px-3 py-1 rounded bg-blue-600 text-white" onClick={push}>Push</button>
3066
<button className="px-3 py-1 rounded bg-emerald-600 text-white" onClick={insertAt}>Insert@i</button>
31-
<button className="px-3 py-1 rounded bg-amber-600 text-white" onClick={setAt}>Set@i</button>
32-
<button className="px-3 py-1 rounded bg-red-600 text-white" onClick={removeAt} disabled={!arr.length}>Remove@i</button>
67+
<button className="px-3 py-1 rounded bg-amber-600 text-white" onClick={setAt} disabled={!idxValidForSet}>Set@i</button>
68+
<button className="px-3 py-1 rounded bg-red-600 text-white" onClick={removeAt} disabled={n === 0}>Remove@i</button>
3369
<button className="px-3 py-1 rounded bg-gray-600 text-white" onClick={() => setArr([])} disabled={!arr.length}>Clear</button>
70+
<span className="ml-4 text-xs text-gray-500">Size</span>
71+
<input className="border rounded px-2 py-1 w-20 bg-white dark:bg-gray-900" value={capInput} onChange={e => setCapInput(e.target.value)} />
72+
<button className="px-3 py-1 rounded border border-gray-300 dark:border-gray-700" onClick={applySize}>Apply</button>
73+
{error && <span className="text-xs text-red-600">{error}</span>}
3474
</div>
3575
<div className="overflow-auto">
3676
<div className="flex items-stretch">
37-
{arr.map((v, i) => (
38-
<div key={i} className="m-1">
77+
{Array.from({ length: cap }).map((_, i) => (
78+
<div key={i} className="m-1 text-center">
3979
<div className="text-center text-xs text-gray-500">{i}</div>
40-
<div className="w-20 h-12 border rounded flex items-center justify-center bg-white dark:bg-gray-900">
41-
{v}
80+
<div className={`w-20 h-12 border rounded flex items-center justify-center bg-white dark:bg-gray-900 ${i === iNum && idxValidForSet ? 'ring-2 ring-blue-500' : ''}`}>
81+
{arr[i] ?? ''}
4282
</div>
4383
</div>
4484
))}
45-
{arr.length === 0 && <div className="text-sm text-gray-500">(empty array)</div>}
85+
{cap === 0 && <div className="text-sm text-gray-500">(size = 0)</div>}
4686
</div>
4787
</div>
88+
<div className="text-xs text-gray-500">length = {n} • capacity = {cap}</div>
89+
<div className="text-xs text-gray-500">Insert index range: 0..{n} • Set index range: 0..{Math.max(0, cap - 1)}</div>
4890
</div>
4991
);
5092
}
51-

0 commit comments

Comments
 (0)