Add spell management to book settings
- Moved spell-related components to the `book/settings/spells` directory for better organization. - Added "Spells" as a new tool in book settings and composer sidebar with localization support. - Integrated spell-related UI elements (`SpellComponent`, `SpellList`, `SpellTagManager`) into settings and sidebars. - Updated logic to handle enabling/disabling of the spells tool per book.
This commit is contained in:
141
components/book/settings/spells/SpellList.tsx
Normal file
141
components/book/settings/spells/SpellList.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
'use client';
|
||||
import React, {useState} from 'react';
|
||||
import {SpellListItem, SpellTagProps} from "@/lib/models/Spell";
|
||||
import InputField from "@/components/form/InputField";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import SpellTagChip from "@/components/book/settings/spells/SpellTagChip";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faChevronRight, faCog, faHatWizard, faPlus} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
interface SpellListProps {
|
||||
spells: SpellListItem[];
|
||||
tags: SpellTagProps[];
|
||||
handleSpellClick: (spell: SpellListItem) => void;
|
||||
handleAddSpell: () => void;
|
||||
handleManageTags: () => void;
|
||||
}
|
||||
|
||||
export default function SpellList(
|
||||
{
|
||||
spells,
|
||||
tags,
|
||||
handleSpellClick,
|
||||
handleAddSpell,
|
||||
handleManageTags,
|
||||
}: SpellListProps) {
|
||||
const t = useTranslations();
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [filterTag, setFilterTag] = useState<string>('all');
|
||||
|
||||
function getFilteredSpells(): SpellListItem[] {
|
||||
return spells.filter((spell: SpellListItem) => {
|
||||
const matchesSearch = spell.name.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesTag = filterTag === 'all' || spell.tags.some((tag: SpellTagProps) => tag.id === filterTag);
|
||||
return matchesSearch && matchesTag;
|
||||
});
|
||||
}
|
||||
|
||||
const filteredSpells = getFilteredSpells();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="px-4 space-y-3">
|
||||
<InputField
|
||||
input={
|
||||
<TextInput
|
||||
value={searchQuery}
|
||||
setValue={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t("spellList.search")}
|
||||
/>
|
||||
}
|
||||
actionIcon={faPlus}
|
||||
actionLabel={t("spellList.add")}
|
||||
addButtonCallBack={async () => handleAddSpell()}
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<div className="flex-1 min-w-[150px]">
|
||||
<select
|
||||
value={filterTag}
|
||||
onChange={(e) => setFilterTag(e.target.value)}
|
||||
className="w-full text-text-primary bg-secondary/50 hover:bg-secondary px-4 py-2.5 rounded-xl border border-secondary/50 focus:border-primary focus:ring-4 focus:ring-primary/20 focus:bg-secondary hover:border-secondary outline-none transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
<option value="all"
|
||||
className="bg-tertiary text-text-primary">{t("spellList.allTags")}</option>
|
||||
{tags.map((tag: SpellTagProps) => (
|
||||
<option key={tag.id} value={tag.id} className="bg-tertiary text-text-primary">
|
||||
{tag.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleManageTags}
|
||||
className="flex items-center gap-2 px-4 py-2.5 bg-secondary/50 rounded-xl border border-secondary/50 hover:bg-secondary hover:border-secondary hover:shadow-md hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCog} className="text-primary w-4 h-4"/>
|
||||
<span className="text-text-primary text-sm font-medium">{t("spellList.manageTags")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-450px)] px-2">
|
||||
{filteredSpells.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="w-20 h-20 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<FontAwesomeIcon icon={faHatWizard} className="text-primary w-10 h-10"/>
|
||||
</div>
|
||||
<h3 className="text-text-primary font-semibold text-lg mb-2">{t("spellList.noSpells")}</h3>
|
||||
<p className="text-muted text-sm max-w-xs">{t("spellList.noSpellsDescription")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 p-2">
|
||||
{filteredSpells.map((spell: SpellListItem) => (
|
||||
<div
|
||||
key={spell.id}
|
||||
onClick={() => handleSpellClick(spell)}
|
||||
className="group flex items-center p-4 bg-secondary/30 rounded-xl border-l-4 border-primary border border-secondary/50 cursor-pointer hover:bg-secondary hover:shadow-md hover:scale-102 transition-all duration-200 hover:border-primary/50"
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-full border-2 border-primary overflow-hidden bg-secondary shadow-md group-hover:scale-110 transition-transform flex items-center justify-center">
|
||||
<FontAwesomeIcon icon={faHatWizard} className="text-primary w-6 h-6"/>
|
||||
</div>
|
||||
|
||||
<div className="ml-4 flex-1 min-w-0">
|
||||
<div
|
||||
className="text-text-primary font-bold text-base group-hover:text-primary transition-colors">
|
||||
{spell.name}
|
||||
</div>
|
||||
<div className="text-text-secondary text-sm mt-0.5 truncate">
|
||||
{spell.description}
|
||||
</div>
|
||||
{spell.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{spell.tags.slice(0, 3).map((tag: SpellTagProps) => (
|
||||
<SpellTagChip key={tag.id} tag={tag} size="sm"/>
|
||||
))}
|
||||
{spell.tags.length > 3 && (
|
||||
<span
|
||||
className="text-muted text-xs px-2 py-0.5 bg-secondary/50 rounded-full">
|
||||
+{spell.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-8 flex justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className="text-muted group-hover:text-primary group-hover:translate-x-1 transition-all w-4 h-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user