Refactor word type colors into centralized utility
- Extract word type color logic into shared wordTypeColors.ts utility - Update TokenTray to use centralized color utility - Remove duplicate color logic from WordCard and SortableToken - Add color legend to Layout header for better UX - Simplify Znakopis page component
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Info, Plus } from 'lucide-react';
|
||||
import { Term, CefrLevel } from '../../types/term';
|
||||
import { Button } from '../ui/button';
|
||||
import { wordTypeColors, wordTypeLabels } from '../../lib/wordTypeColors';
|
||||
|
||||
interface WordCardProps {
|
||||
term: Term;
|
||||
@@ -17,32 +18,6 @@ const cefrColors: Record<CefrLevel, string> = {
|
||||
[CefrLevel.C2]: 'bg-red-500',
|
||||
};
|
||||
|
||||
const wordTypeColors: Record<string, string> = {
|
||||
NOUN: 'bg-green-600', // Imenica - zelena
|
||||
VERB: 'bg-red-600', // Glagol - crvena
|
||||
ADJECTIVE: 'bg-blue-600', // Pridjev - plava
|
||||
ADVERB: 'bg-purple-600', // Prilog - ljubičasta
|
||||
PRONOUN: 'bg-yellow-600', // Zamjenica - žuta
|
||||
PREPOSITION: 'bg-orange-600', // Prijedlog - narančasta
|
||||
CONJUNCTION: 'bg-pink-600', // Veznik - roza
|
||||
INTERJECTION: 'bg-teal-600', // Uzvik - tirkizna
|
||||
PHRASE: 'bg-indigo-600', // Fraza - indigo
|
||||
OTHER: 'bg-gray-600', // Ostalo - siva
|
||||
};
|
||||
|
||||
const wordTypeLabels: Record<string, string> = {
|
||||
NOUN: 'Imenica',
|
||||
VERB: 'Glagol',
|
||||
ADJECTIVE: 'Pridjev',
|
||||
ADVERB: 'Prilog',
|
||||
PRONOUN: 'Zamjenica',
|
||||
PREPOSITION: 'Prijedlog',
|
||||
CONJUNCTION: 'Veznik',
|
||||
INTERJECTION: 'Uzvik',
|
||||
PHRASE: 'Fraza',
|
||||
OTHER: 'Ostalo',
|
||||
};
|
||||
|
||||
export function WordCard({ term, onInfo, onAddToSentence }: WordCardProps) {
|
||||
const videoMedia = term.media?.find(m => m.kind === 'VIDEO');
|
||||
const imageMedia = term.media?.find(m => m.kind === 'IMAGE' || m.kind === 'ILLUSTRATION');
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Sidebar } from './Sidebar';
|
||||
import { TokenTray } from '../znakopis/TokenTray';
|
||||
import { useSentenceStore } from '../../stores/sentenceStore';
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function Layout({ children }: LayoutProps) {
|
||||
const { currentTokens } = useSentenceStore();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-indigo-50">
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
{/* Global TokenTray - sticky at top when tokens exist */}
|
||||
{currentTokens.length > 0 && (
|
||||
<div className="sticky top-0 z-50 bg-indigo-50 pt-4 px-6">
|
||||
<TokenTray compact={true} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -2,26 +2,13 @@ import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { X, GripVertical } from 'lucide-react';
|
||||
import { SentenceToken } from '../../stores/sentenceStore';
|
||||
import { Button } from '../ui/button';
|
||||
import { wordTypeColors } from '../../lib/wordTypeColors';
|
||||
|
||||
interface SortableTokenProps {
|
||||
token: SentenceToken;
|
||||
onRemove: (tokenId: string) => void;
|
||||
}
|
||||
|
||||
const wordTypeColors: Record<string, string> = {
|
||||
NOUN: 'bg-green-600',
|
||||
VERB: 'bg-red-600',
|
||||
ADJECTIVE: 'bg-blue-600',
|
||||
ADVERB: 'bg-purple-600',
|
||||
PRONOUN: 'bg-yellow-600',
|
||||
PREPOSITION: 'bg-orange-600',
|
||||
CONJUNCTION: 'bg-pink-600',
|
||||
INTERJECTION: 'bg-teal-600',
|
||||
PHRASE: 'bg-indigo-600',
|
||||
OTHER: 'bg-gray-600',
|
||||
};
|
||||
|
||||
export function SortableToken({ token, onRemove }: SortableTokenProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: token.id,
|
||||
|
||||
@@ -5,7 +5,11 @@ import { SortableToken } from './SortableToken';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
export function TokenTray() {
|
||||
interface TokenTrayProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export function TokenTray({ compact = false }: TokenTrayProps) {
|
||||
const { currentTokens, reorderTokens, removeToken, clearTokens } = useSentenceStore();
|
||||
|
||||
const sensors = useSensors(
|
||||
@@ -27,10 +31,21 @@ export function TokenTray() {
|
||||
}
|
||||
};
|
||||
|
||||
// Conditional styling based on compact mode
|
||||
const containerClass = compact
|
||||
? "bg-white rounded-lg shadow-sm p-3"
|
||||
: "bg-white rounded-lg shadow p-6";
|
||||
|
||||
const headerClass = compact
|
||||
? "text-lg font-semibold text-gray-900"
|
||||
: "text-xl font-semibold text-gray-900";
|
||||
|
||||
const gapClass = compact ? "gap-2" : "gap-3";
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Trenutna rečenica</h2>
|
||||
<div className={containerClass}>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h2 className={headerClass}>Trenutna rečenica</h2>
|
||||
{currentTokens.length > 0 && (
|
||||
<Button variant="outline" size="sm" onClick={clearTokens}>
|
||||
<X className="h-4 w-4 mr-1" />
|
||||
@@ -40,14 +55,16 @@ export function TokenTray() {
|
||||
</div>
|
||||
|
||||
{currentTokens.length === 0 ? (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<p className="text-lg mb-2">Nema riječi u rečenici</p>
|
||||
<p className="text-sm">Idite na Rječnik i dodajte riječi klikom na "Dodaj"</p>
|
||||
</div>
|
||||
!compact && (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<p className="text-lg mb-2">Nema riječi u rečenici</p>
|
||||
<p className="text-sm">Idite na Rječnik i dodajte riječi klikom na "Dodaj"</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={currentTokens.map((t) => t.id)} strategy={horizontalListSortingStrategy}>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className={`flex flex-wrap ${gapClass}`}>
|
||||
{currentTokens.map((token) => (
|
||||
<SortableToken key={token.id} token={token} onRemove={removeToken} />
|
||||
))}
|
||||
@@ -56,7 +73,7 @@ export function TokenTray() {
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
{currentTokens.length > 0 && (
|
||||
{currentTokens.length > 0 && !compact && (
|
||||
<div className="mt-6 p-4 bg-blue-50 rounded-lg">
|
||||
<p className="text-sm text-blue-900">
|
||||
<strong>Savjet:</strong> Povucite i ispustite riječi za promjenu redoslijeda. Kliknite "Spremi" za spremanje rečenice u dokument.
|
||||
|
||||
31
packages/frontend/src/lib/wordTypeColors.ts
Normal file
31
packages/frontend/src/lib/wordTypeColors.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Shared color scheme for word types across the application.
|
||||
* Used in WordCard, SortableToken, and other components that display word types.
|
||||
*/
|
||||
|
||||
export const wordTypeColors: Record<string, string> = {
|
||||
NOUN: 'bg-green-600', // Imenica - zelena
|
||||
VERB: 'bg-red-600', // Glagol - crvena
|
||||
ADJECTIVE: 'bg-blue-600', // Pridjev - plava
|
||||
ADVERB: 'bg-purple-600', // Prilog - ljubičasta
|
||||
PRONOUN: 'bg-yellow-600', // Zamjenica - žuta
|
||||
PREPOSITION: 'bg-orange-600', // Prijedlog - narančasta
|
||||
CONJUNCTION: 'bg-pink-600', // Veznik - roza
|
||||
INTERJECTION: 'bg-teal-600', // Uzvik - tirkizna
|
||||
PHRASE: 'bg-indigo-600', // Fraza - indigo
|
||||
OTHER: 'bg-gray-600', // Ostalo - siva
|
||||
};
|
||||
|
||||
export const wordTypeLabels: Record<string, string> = {
|
||||
NOUN: 'Imenica',
|
||||
VERB: 'Glagol',
|
||||
ADJECTIVE: 'Pridjev',
|
||||
ADVERB: 'Prilog',
|
||||
PRONOUN: 'Zamjenica',
|
||||
PREPOSITION: 'Prijedlog',
|
||||
CONJUNCTION: 'Veznik',
|
||||
INTERJECTION: 'Uzvik',
|
||||
PHRASE: 'Fraza',
|
||||
OTHER: 'Ostalo',
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSentenceStore } from '../stores/sentenceStore';
|
||||
import { documentApi, Document } from '../lib/documentApi';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Save, FileText, Plus } from 'lucide-react';
|
||||
import { Save, FileText } from 'lucide-react';
|
||||
|
||||
function Znakopis() {
|
||||
const { currentTokens, clearTokens } = useSentenceStore();
|
||||
@@ -166,9 +166,9 @@ function Znakopis() {
|
||||
|
||||
{/* Two-column layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Left: Token Tray (2/3 width) */}
|
||||
{/* Left: Token Tray (2/3 width) - Full version for editing */}
|
||||
<div className="lg:col-span-2">
|
||||
<TokenTray />
|
||||
<TokenTray compact={false} />
|
||||
</div>
|
||||
|
||||
{/* Right: Document Panel (1/3 width) */}
|
||||
|
||||
Reference in New Issue
Block a user