mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
fix(studio): the reindeer's antlers sit on its head, every sticker on a tile of one size, no theme chips, and the weight on Studio's own slider
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { BOOK_STICKER_THEMES, type BookPageSetup, type BookStickerTheme } from '@trek/shared';
|
||||
import { BOOK_STICKER_THEMES, type BookPageSetup } from '@trek/shared';
|
||||
import { bookStickersOf, type BookStickerDef } from '@trek/shared/stickers';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useMemo, useState, type DragEvent } from 'react';
|
||||
|
||||
import { useStudioStore } from '../../store/studioStore';
|
||||
import '../../styles/studio-stickers.css';
|
||||
import { StickerArt } from './StickerView';
|
||||
import { PanelHead } from './StudioPanelHead';
|
||||
import {
|
||||
@@ -37,7 +38,7 @@ function StickerTile({ def, t, onPick }: { def: BookStickerDef; t: Translate; on
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="st-shape-btn is-glyph"
|
||||
className="st-shape-btn st-sticker-btn"
|
||||
draggable
|
||||
onDragStart={onDragStart}
|
||||
onClick={() => onPick(def)}
|
||||
@@ -70,15 +71,14 @@ function StickerGrid({
|
||||
|
||||
export function StudioStickersPanel({ page, t }: { page: BookPageSetup; t: Translate }) {
|
||||
const [query, setQuery] = useState('');
|
||||
const [theme, setTheme] = useState<BookStickerTheme | null>(null);
|
||||
const [recent, setRecent] = useState<BookStickerDef[]>(readRecentStickers);
|
||||
const addElement = useStudioStore((s) => s.addElement);
|
||||
const active = useStudioStore((s) => s.activeSpread);
|
||||
const role = useStudioStore((s) => s.doc?.spreads[s.activeSpread]?.role);
|
||||
const single = role !== undefined && role !== 'inner';
|
||||
|
||||
const matches = useMemo(() => matchStickers(query, theme, t), [query, theme, t]);
|
||||
const browsing = !query.trim() && theme === null;
|
||||
const matches = useMemo(() => matchStickers(query, null, t), [query, t]);
|
||||
const browsing = !query.trim();
|
||||
|
||||
const place = (def: BookStickerDef) => {
|
||||
addElement(active, newStickerElement(def, page, single));
|
||||
@@ -105,32 +105,6 @@ export function StudioStickersPanel({ page, t }: { page: BookPageSetup; t: Trans
|
||||
</div>
|
||||
|
||||
<div className="st-panel-scroll">
|
||||
<div className="st-section">
|
||||
<div className="st-row" role="radiogroup" aria-label={t('journey.studio.stickerThemes')}>
|
||||
<button
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={theme === null}
|
||||
className={`st-chip ${theme === null ? 'is-on' : ''}`}
|
||||
onClick={() => setTheme(null)}
|
||||
>
|
||||
{t('journey.studio.stickersAll')}
|
||||
</button>
|
||||
{BOOK_STICKER_THEMES.map((id) => (
|
||||
<button
|
||||
type="button"
|
||||
key={id}
|
||||
role="radio"
|
||||
aria-checked={theme === id}
|
||||
className={`st-chip ${theme === id ? 'is-on' : ''}`}
|
||||
onClick={() => setTheme(theme === id ? null : id)}
|
||||
>
|
||||
{stickerThemeLabel(t, id)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{browsing && recent.length > 0 && (
|
||||
<div className="st-section">
|
||||
<div className="st-section-label">{t('journey.studio.stickersRecent')}</div>
|
||||
@@ -138,7 +112,7 @@ export function StudioStickersPanel({ page, t }: { page: BookPageSetup; t: Trans
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Browsing, the library by theme; searching or filtered, one grid of what matches. */}
|
||||
{/* Browsing, the library by theme; searching, one grid of what matches. */}
|
||||
{browsing ? (
|
||||
BOOK_STICKER_THEMES.map((id) => (
|
||||
<div className="st-section" key={id}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BookWeight } from '@trek/shared';
|
||||
import '../../styles/studio-paint.css';
|
||||
import { BOOK_FONTS, nearestWeight, type BookFontId } from './bookFonts';
|
||||
import { Line } from './StudioControls';
|
||||
|
||||
@@ -41,19 +42,21 @@ export function WeightLine({
|
||||
<Line label={t('journey.studio.weight')}>
|
||||
<div className="st-field st-weight">
|
||||
{weights.length > 1 ? (
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={weights.length - 1}
|
||||
step={1}
|
||||
value={at}
|
||||
aria-label={t('journey.studio.weight')}
|
||||
aria-valuetext={word}
|
||||
onChange={(e) => {
|
||||
const next = weights[Number(e.target.value)];
|
||||
if (next !== undefined) onPick(next);
|
||||
}}
|
||||
/>
|
||||
<span className="st-paint-slider st-weight-slider">
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={weights.length - 1}
|
||||
step={1}
|
||||
value={at}
|
||||
aria-label={t('journey.studio.weight')}
|
||||
aria-valuetext={word}
|
||||
onChange={(e) => {
|
||||
const next = weights[Number(e.target.value)];
|
||||
if (next !== undefined) onPick(next);
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<span className="st-weight-only">{t('journey.studio.weightOnly')}</span>
|
||||
)}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
.trek-dash .st-weight {
|
||||
width: 100%;
|
||||
}
|
||||
.trek-dash .st-weight > .st-weight-slider {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.trek-dash .st-weight > .st-weight-value,
|
||||
.trek-dash .st-weight > .st-weight-only {
|
||||
font-size: calc(11.5px * var(--fs-scale-caption, 1));
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/* The sticker library: every sticker on a square tile of the same size, drawn
|
||||
to fit inside it whether it is tall, wide or round. */
|
||||
.trek-dash .st-sticker-btn {
|
||||
min-height: 0;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.trek-dash .st-sticker-btn svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
@@ -106,12 +106,10 @@ describe('the stickers panel', () => {
|
||||
expect(screen.getByText('journey.studio.stickersRecent')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('filters by theme and by what is typed, and says when nothing matches', () => {
|
||||
it('finds stickers by what is typed, and says when nothing matches', () => {
|
||||
render(<StudioStickersPanel page={page} t={t} />);
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'journey.studio.stickerTheme.food' }));
|
||||
expect(screen.getByRole('button', { name: 'journey.studio.sticker.croissant' })).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: 'journey.studio.sticker.eiffelTower' })).toBeNull();
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'journey.studio.stickersAll' }));
|
||||
// No theme chips: the library is browsed by its theme sections and searched by name.
|
||||
expect(screen.queryByRole('radiogroup')).toBeNull();
|
||||
fireEvent.change(screen.getByRole('textbox', { name: 'journey.studio.searchStickers' }), {
|
||||
target: { value: 'zzzz' },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user