33import { useMemo , useState } from "react" ;
44
55import type { Series } from "@/lib/types" ;
6- import { type SeriesStyleDefinition } from "@/lib/seriesStyle" ;
6+ import type { SpotMark } from "@/lib/spotStyle" ;
7+ import { UNSET_SERIES , type SeriesStyleDefinition } from "@/lib/seriesStyle" ;
78import { resolveSeriesChip } from "@/lib/spotStyle" ;
89import SpotMarkGlyph from "@/components/SpotMarkGlyph" ;
910import ChoiceRow , { toggleChoice } from "@/components/ChoiceRow" ;
1011
11- /** シリーズの選択肢がこれを超える種別(放送回番号など)はボタン列を並べきれないため検索できる一覧にする */
12- export const SERIES_FILTER_BUTTONS_MAX = 12 ;
12+ /**
13+ * ボタン列で並べられる上限。**アイコンで見分けられる種別だけがここまで並べられる。**
14+ * アイコンがあっても数十個になれば探せないので、上限自体は残す
15+ * (それを超える種別は検索できる一覧に回す)。
16+ */
17+ export const SERIES_FILTER_TILE_MAX = 20 ;
18+
19+ /** 詰めて並べられる中身の長さ(ラベルが1〜2文字ならアイコンと同じ扱い) */
20+ const TILEABLE_LABEL_MAX = 2 ;
21+
22+ /**
23+ * ボタン列で並べられるか。**数ではなく中身で決める。**
24+ *
25+ * アイコン(や1〜2文字のラベル)なら、絵で見分けが付くので詰めて並べられる。
26+ * 一方、**番組タイトルや作品名のような長い名前は3〜4個でも横に並べると読めない**
27+ * ——1つあたりの幅が要るうえ、折り返して2行になった名前は隣とくっついて見える。
28+ * そういう種別は数が少なくても検索できる一覧のほうが読みやすい。
29+ *
30+ * かつては「12個まではボタン列」と数だけで決めていたが、観光地(11個)が
31+ * 境目に張り付いていて、シリーズを1つ足すだけで見た目が切り替わっていた。
32+ */
33+ function canTileSeries (
34+ series : Series [ ] ,
35+ seriesStyles : SeriesStyleDefinition [ ]
36+ ) : boolean {
37+ if ( series . length > SERIES_FILTER_TILE_MAX ) return false ;
38+ return series . every ( ( r ) => {
39+ // 「未設定」はアイコンを持たないが、アプリが出す短い固定の名前なので詰められる
40+ // (シリーズ未設定のスポットが1件でもあれば必ず選択肢に入るため、
41+ // 例外にしないとアイコンの揃った種別まで一覧に落ちる)
42+ if ( r === UNSET_SERIES ) return true ;
43+ const { mark } = resolveSeriesChip ( r , seriesStyles ) ;
44+ if ( mark . kind === "icon" || mark . kind === "image" ) return true ;
45+ return mark . kind === "text" && mark . text . length <= TILEABLE_LABEL_MAX ;
46+ } ) ;
47+ }
1348
1449/**
1550 * シリーズによる複数選択の絞り込みUI。地図・一覧の絞り込み(FilterBar)と
@@ -30,10 +65,10 @@ export default function SeriesFilter({
3065} ) {
3166 if ( series . length === 0 ) return null ;
3267
33- // ボタンを並べきれない種別 (放送回番号・作品名など)は、検索できる一覧にする。
68+ // アイコンで見分けられない種別 (放送回番号・作品名など)は、検索できる一覧にする。
3469 // かつては単一選択のプルダウンだったが、アニメ聖地のようにシリーズが数百ある
3570 // 種別では目当ての値を探せず、複数選択もできなかった
36- if ( series . length > SERIES_FILTER_BUTTONS_MAX ) {
71+ if ( ! canTileSeries ( series , seriesStyles ) ) {
3772 return (
3873 < SearchableSeriesFilter
3974 series = { series }
@@ -51,24 +86,27 @@ export default function SeriesFilter({
5186 < ChoiceRow
5287 options = { series . map ( ( r ) => {
5388 const { mark } = resolveSeriesChip ( r , seriesStyles ) ;
89+ // **中身が無いシリーズ(「未設定」)は名前の1文字目を絵の代わりに置く。**
90+ // 名前だけを出すと、絵のある選択肢と背の高さが揃わず、行の中で1つだけ
91+ // 浮いて見える。「未」の下に「未設定」と並べれば、他と同じ形になる。
92+ const glyph : SpotMark =
93+ mark . kind === "none" ? { kind : "text" , text : r . slice ( 0 , 1 ) } : mark ;
5494 return {
5595 value : r ,
5696 title : r ,
5797 // **絵の下にシリーズ名を添える。** アイコンだけでは何のシリーズか読めない
5898 // (丼の絵が3種類並ぶような種別では特に見分けが付かない)。
59- // 中身が無いシリーズ(アイコンも文字も未設定)は名前だけを出す ——
60- // チップは押す対象なので、空の四角では選べない。
6199 // **詰めて並べる`compact`は使わない** —— 名前を出すぶんの幅が要る
62100 // (`compact`はアイコン+左右の余白ぶんしかなく、名前が1文字ずつに潰れる)
63- content :
64- mark . kind === "none" ? (
65- r
66- ) : (
67- < span className = "flex flex-col items-center gap-0.5 leading-tight" >
68- < SpotMarkGlyph mark = { mark } alt = { r } className = "h-4 w-4" />
69- < span className = "text-[10px]" > { r } </ span >
101+ content : (
102+ < span className = "flex flex-col items-center gap-0.5 leading-tight" >
103+ { /* 文字の中身も絵と同じ高さに収める(揃えないと行ごとに背がずれる) */ }
104+ < span className = "flex h-4 items-center justify-center text-sm leading-none" >
105+ < SpotMarkGlyph mark = { glyph } alt = { r } className = "h-4 w-4" />
70106 </ span >
71- ) ,
107+ < span className = "text-[10px]" > { r } </ span >
108+ </ span >
109+ ) ,
72110 } ;
73111 } ) }
74112 selected = { selected }
0 commit comments