chore(build): refactor codebase for production

This commit is contained in:
shafin-r
2026-03-12 02:39:34 +06:00
parent 121cc2bf71
commit bd35f6a852
123 changed files with 3501 additions and 3254 deletions

View File

@ -1,7 +1,14 @@
import React, { useState } from 'react';
import { MousePointerClick } from 'lucide-react';
import { useState } from "react";
import { MousePointerClick } from "lucide-react";
export type SegmentType = 'ic' | 'dc' | 'modifier' | 'conjunction' | 'punct' | 'subject' | 'verb';
export type SegmentType =
| "ic"
| "dc"
| "modifier"
| "conjunction"
| "punct"
| "subject"
| "verb";
export interface Segment {
text: string;
@ -19,52 +26,95 @@ interface ClauseBreakdownWidgetProps {
accentColor?: string;
}
const TYPE_STYLES: Record<SegmentType, { bg: string; text: string; border: string; ring: string }> = {
ic: { bg: 'bg-blue-100', text: 'text-blue-800', border: 'border-blue-300', ring: '#93c5fd' },
dc: { bg: 'bg-green-100', text: 'text-green-800', border: 'border-green-300', ring: '#86efac' },
modifier: { bg: 'bg-orange-100', text: 'text-orange-800', border: 'border-orange-300', ring: '#fdba74' },
conjunction: { bg: 'bg-purple-100', text: 'text-purple-800', border: 'border-purple-300', ring: '#c4b5fd' },
subject: { bg: 'bg-sky-100', text: 'text-sky-800', border: 'border-sky-300', ring: '#7dd3fc' },
verb: { bg: 'bg-rose-100', text: 'text-rose-800', border: 'border-rose-300', ring: '#fda4af' },
punct: { bg: 'bg-gray-100', text: 'text-gray-600', border: 'border-gray-300', ring: '#d1d5db' },
const TYPE_STYLES: Record<
SegmentType,
{ bg: string; text: string; border: string; ring: string }
> = {
ic: {
bg: "bg-blue-100",
text: "text-blue-800",
border: "border-blue-300",
ring: "#93c5fd",
},
dc: {
bg: "bg-green-100",
text: "text-green-800",
border: "border-green-300",
ring: "#86efac",
},
modifier: {
bg: "bg-orange-100",
text: "text-orange-800",
border: "border-orange-300",
ring: "#fdba74",
},
conjunction: {
bg: "bg-purple-100",
text: "text-purple-800",
border: "border-purple-300",
ring: "#c4b5fd",
},
subject: {
bg: "bg-sky-100",
text: "text-sky-800",
border: "border-sky-300",
ring: "#7dd3fc",
},
verb: {
bg: "bg-rose-100",
text: "text-rose-800",
border: "border-rose-300",
ring: "#fda4af",
},
punct: {
bg: "bg-gray-100",
text: "text-gray-600",
border: "border-gray-300",
ring: "#d1d5db",
},
};
const TYPE_LABELS: Record<SegmentType, string> = {
ic: 'Independent Clause',
dc: 'Dependent Clause',
modifier: 'Modifier',
conjunction: 'Conjunction',
subject: 'Subject',
verb: 'Verb / Predicate',
punct: 'Punctuation',
ic: "Independent Clause",
dc: "Dependent Clause",
modifier: "Modifier",
conjunction: "Conjunction",
subject: "Subject",
verb: "Verb / Predicate",
punct: "Punctuation",
};
// Pre-resolved tab accent classes (avoids Tailwind purge issues with dynamic strings)
const TAB_ACTIVE: Record<string, string> = {
purple: 'border-b-2 border-purple-600 text-purple-700 bg-white',
teal: 'border-b-2 border-teal-600 text-teal-700 bg-white',
fuchsia: 'border-b-2 border-fuchsia-600 text-fuchsia-700 bg-white',
amber: 'border-b-2 border-amber-600 text-amber-700 bg-white',
purple: "border-b-2 border-purple-600 text-purple-700 bg-white",
teal: "border-b-2 border-teal-600 text-teal-700 bg-white",
fuchsia: "border-b-2 border-fuchsia-600 text-fuchsia-700 bg-white",
amber: "border-b-2 border-amber-600 text-amber-700 bg-white",
};
export default function ClauseBreakdownWidget({ examples, accentColor = 'purple' }: ClauseBreakdownWidgetProps) {
export default function ClauseBreakdownWidget({
examples,
accentColor = "purple",
}: ClauseBreakdownWidgetProps) {
const [activeTab, setActiveTab] = useState(0);
const [selected, setSelected] = useState<number | null>(null);
const example = examples[activeTab];
const switchTab = (i: number) => { setActiveTab(i); setSelected(null); };
const switchTab = (i: number) => {
setActiveTab(i);
setSelected(null);
};
const selectedSeg = selected !== null ? example.segments[selected] : null;
const tabActive = TAB_ACTIVE[accentColor] ?? TAB_ACTIVE.purple;
// Unique labeled segment types for the legend
const legendTypes = Array.from(
new Set(example.segments.filter(s => s.label).map(s => s.type))
new Set(example.segments.filter((s) => s.label).map((s) => s.type)),
);
return (
<div className="rounded-2xl border border-gray-200 bg-white overflow-hidden shadow-sm">
{/* Tab strip */}
{examples.length > 1 && (
<div className="flex border-b border-gray-200 bg-gray-50 overflow-x-auto">
@ -73,7 +123,9 @@ export default function ClauseBreakdownWidget({ examples, accentColor = 'purple'
key={i}
onClick={() => switchTab(i)}
className={`px-4 py-2.5 text-sm font-medium whitespace-nowrap transition-colors ${
i === activeTab ? tabActive : 'text-gray-500 hover:text-gray-700'
i === activeTab
? tabActive
: "text-gray-500 hover:text-gray-700"
}`}
>
{ex.title}
@ -83,14 +135,18 @@ export default function ClauseBreakdownWidget({ examples, accentColor = 'purple'
)}
{examples.length === 1 && (
<div className="px-5 pt-4 pb-1">
<p className="text-xs font-semibold uppercase tracking-wider text-gray-400">{example.title}</p>
<p className="text-xs font-semibold uppercase tracking-wider text-gray-400">
{example.title}
</p>
</div>
)}
{/* Instruction */}
<div className="px-5 pt-3 pb-1 flex items-center gap-1.5">
<MousePointerClick className="w-3.5 h-3.5 text-gray-400 shrink-0" />
<p className="text-xs text-gray-400 italic">Click any colored part to see its grammatical role</p>
<p className="text-xs text-gray-400 italic">
Click any colored part to see its grammatical role
</p>
</div>
{/* Sentence display */}
@ -99,7 +155,11 @@ export default function ClauseBreakdownWidget({ examples, accentColor = 'purple'
{example.segments.map((seg, i) => {
if (!seg.label) {
// Punctuation / unlabeled — plain unstyled text, not clickable
return <span key={i} className="text-gray-700">{seg.text}</span>;
return (
<span key={i} className="text-gray-700">
{seg.text}
</span>
);
}
const style = TYPE_STYLES[seg.type];
const isSelected = selected === i;
@ -112,7 +172,14 @@ export default function ClauseBreakdownWidget({ examples, accentColor = 'purple'
? `border-2 ${style.border} font-semibold`
: `border ${style.border} hover:opacity-80`
}`}
style={isSelected ? { outline: `2.5px solid ${style.ring}`, outlineOffset: '1px' } : {}}
style={
isSelected
? {
outline: `2.5px solid ${style.ring}`,
outlineOffset: "1px",
}
: {}
}
>
{seg.text}
</span>
@ -130,29 +197,38 @@ export default function ClauseBreakdownWidget({ examples, accentColor = 'purple'
style={{ backgroundColor: TYPE_STYLES[selectedSeg.type].ring }}
/>
<div className="flex-1 min-w-0">
<p className={`text-xs font-bold uppercase tracking-wider mb-0.5 ${TYPE_STYLES[selectedSeg.type].text}`}>
<p
className={`text-xs font-bold uppercase tracking-wider mb-0.5 ${TYPE_STYLES[selectedSeg.type].text}`}
>
{selectedSeg.label ?? TYPE_LABELS[selectedSeg.type]}
</p>
<p className={`text-sm font-semibold leading-snug ${TYPE_STYLES[selectedSeg.type].text}`}>
<p
className={`text-sm font-semibold leading-snug ${TYPE_STYLES[selectedSeg.type].text}`}
>
"{selectedSeg.text.trim()}"
</p>
</div>
</div>
) : (
<p className="mt-2 text-xs text-gray-400 italic px-1">No element selected click a colored span above.</p>
<p className="mt-2 text-xs text-gray-400 italic px-1">
No element selected click a colored span above.
</p>
)}
</div>
{/* Legend */}
<div className="px-5 py-3 border-t border-gray-100 bg-gray-50 flex flex-wrap gap-2">
{legendTypes.map(type => {
{legendTypes.map((type) => {
const style = TYPE_STYLES[type];
return (
<span
key={type}
className={`inline-flex items-center gap-1.5 text-xs font-medium px-2.5 py-1 rounded-full border ${style.bg} ${style.text} ${style.border}`}
>
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: TYPE_STYLES[type].ring }} />
<span
className="w-2 h-2 rounded-full"
style={{ backgroundColor: TYPE_STYLES[type].ring }}
/>
{TYPE_LABELS[type]}
</span>
);