feat(chat): 39 nuove chat bubble (253-291) + paginator soundboard

- 39 bubble custom con relativo pointer colorato (colore campionato dal
  fondo di ogni bubble) + regole CSS in Chats.css (resa in-stanza border-image
  + anteprima nel selettore)
- selezionabili via chat.styles dell'ui-config (lato server)
- soundboard: paginazione 9/pagina (griglia 3x3) con frecce + indicatore,
  cosi la card non cresce a dismisura
This commit is contained in:
medievalshell
2026-05-31 04:01:04 +02:00
parent fa71e8eb4a
commit 7745c5f66b
80 changed files with 506 additions and 12 deletions
+38 -12
View File
@@ -10,6 +10,18 @@ export const SoundboardView: FC<{}> = () =>
const [ isVisible, setIsVisible ] = useState(false);
const { enabled, sounds, lastPlayed, play } = useSoundboard();
const PAGE_SIZE = 9;
const [ page, setPage ] = useState(0);
const totalPages = Math.max(1, Math.ceil(sounds.length / PAGE_SIZE));
// Clamp the page if the sound list shrinks (or on first load).
useEffect(() =>
{
if(page > (totalPages - 1)) setPage(0);
}, [ totalPages, page ]);
const pageSounds = sounds.slice(page * PAGE_SIZE, (page * PAGE_SIZE) + PAGE_SIZE);
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
@@ -49,18 +61,32 @@ export const SoundboardView: FC<{}> = () =>
{ !sounds.length &&
<Text small className="text-black/50">{ LocalizeText('soundboard.empty') }</Text> }
{ !!sounds.length &&
<div className="grid grid-cols-3 gap-2">
{ sounds.map(sound => (
<button
key={ sound.id }
onClick={ () => play(sound) }
title={ sound.name }
className="flex h-20 cursor-pointer flex-col items-center justify-center gap-1 rounded-lg bg-[#3a7bb5] px-2 text-white shadow transition-transform hover:bg-[#336ea3] active:scale-95">
<span className="text-2xl leading-none">🔊</span>
<span className="line-clamp-2 text-center text-[11px] font-bold leading-tight">{ sound.name }</span>
</button>
)) }
</div> }
<>
<div className="grid grid-cols-3 gap-2">
{ pageSounds.map(sound => (
<button
key={ sound.id }
onClick={ () => play(sound) }
title={ sound.name }
className="flex h-20 cursor-pointer flex-col items-center justify-center gap-1 rounded-lg bg-[#3a7bb5] px-2 text-white shadow transition-transform hover:bg-[#336ea3] active:scale-95">
<span className="text-2xl leading-none">🔊</span>
<span className="line-clamp-2 text-center text-[11px] font-bold leading-tight">{ sound.name }</span>
</button>
)) }
</div>
{ totalPages > 1 &&
<Flex alignItems="center" justifyContent="center" gap={ 2 } className="select-none pt-1">
<button
disabled={ page === 0 }
onClick={ () => setPage(p => Math.max(0, p - 1)) }
className="cursor-pointer rounded bg-[#3a7bb5] px-3 py-1 text-sm font-bold text-white hover:bg-[#336ea3] disabled:cursor-default disabled:opacity-40"></button>
<Text small bold className="min-w-[44px] text-center text-[#2f6f95]">{ page + 1 } / { totalPages }</Text>
<button
disabled={ page >= (totalPages - 1) }
onClick={ () => setPage(p => Math.min(totalPages - 1, p + 1)) }
className="cursor-pointer rounded bg-[#3a7bb5] px-3 py-1 text-sm font-bold text-white hover:bg-[#336ea3] disabled:cursor-default disabled:opacity-40"></button>
</Flex> }
</> }
{ lastPlayed &&
<Flex alignItems="center" justifyContent="center" className="pt-1">
<Text small className="text-[#2f6f95]">