ハコヅミを作る #13:ギアのおすすめ度を、★をタップして付ける
前回(#12)のギアの画像に続いて、今回はおすすめ度です。
使ってみてよかったギア、いまひとつだったギアを、あとから見返せるようにしたいと思っていました。ギアごとに、おすすめ度を5段階で付け、その理由も書き残せるようにします。 Notion の構想では「ギアのおすすめ度」として挙げていたもので、将来、おすすめ情報を他の人と共有する(ステップ2)ときの土台にもなります。
見た目
ギアの一覧では、おすすめ度が付いているギアの製品名の後ろに、★が出ます。

ギアの一覧(上のほうを切り出したもの)。おすすめ度はオレンジの★で出る
画面は、この記事の時点のコードを手元で動かして撮ったものです。データは#7でNotionから取り込んだ、実際のギアを入れています(おすすめ度は、記事用に付けたものです)。
編集画面では、★をタップして選びます。3つ目の★をタップすると ★★★☆☆ になります。

編集画面のおすすめ度とおすすめ理由
Issue で決めたこと
最初の下書きは「おすすめ度を 1〜5 から選ぶ(ラジオボタン)」だけでした。下書きを見て、2点を足しました。
- おすすめ理由(任意)も入れられるようにする:Notion の構想にも「おすすめ度やおすすめ理由」とあった。数字だけだと、なぜそう付けたかを忘れる
- 入力は、★をタップする見た目にする:ラジオボタンが5つ並ぶより、おすすめ度らしい
テーブルは、gears に列を2つ足すだけです。
| 列 | 内容 |
|---|---|
rating |
おすすめ度。1〜5 の整数、null なら未設定 |
rating_note |
おすすめ理由。1000 文字まで |
見た目は★、中身はラジオボタン
★をタップする見た目は、JavaScript で★を並べて、クリックで値を変える形でも作れます。 ただそれだと、次のことを自分で作り込む必要があります。
- JavaScript が動かなくても、フォームで値が送られること
- キーボード(矢印キー)で選べること
- 読み上げで「おすすめ度 3」のように読まれること
そこで、中身はふつうのラジオボタンにして、見た目だけを★にする形にしました。ラジオボタンなら、この3つはブラウザが最初からやってくれます。
{#each levels as level (level)}
<label class="cursor-pointer">
<!-- ラジオボタンは見えないようにする(sr-only)。読み上げやキーボードでは使える -->
<input
type="radio"
name="rating"
value={level}
checked={selected === level}
aria-label={`おすすめ度 ${level}`}
class="peer sr-only"
onchange={() => (selected = level)}
/>
<!-- 選んだ★までを塗る。キーボードで選んでいるときは枠を出す -->
<span
aria-hidden="true"
class={[
'text-3xl peer-focus-visible:outline-2',
level <= selected ? 'text-accent' : 'text-line'
]}>{level <= selected ? '★' : '☆'}</span
>
</label>
{/each}
ポイントは Tailwind の peer です。見えないラジオボタンにキーボードの焦点があるとき、隣の★に枠を出せます。見た目を★にしても、「今どこを選んでいるか」がキーボードでも分かります。
「未設定」に戻すには
ラジオボタンには弱点が1つあります。一度どれかを選ぶと、どれも選んでいない状態に戻せないことです。 そこで、選んでいるときだけ「未設定に戻す」ボタンを出しました。押すと、どのラジオボタンにもチェックが付いていない状態になり、フォームでは値が送られず、サーバーで null になります。
差し色を初めて使った
#11で、配色「森」の差し色として焚き火のオレンジ(accent)を用意しましたが、使いどころが決まらず、どこにも使っていませんでした。
★の色にちょうどよかったので、今回が初めての出番です。緑ばかりの画面の中で、おすすめ度だけが目に入るようになりました。
DB の制約は付けなかった
おすすめ度は 1〜5 の整数だけにしたい値です。DB の制約(CHECK)で守る方法もありますが、今回は付けていません。
ハコヅミではマイグレーションを Drizzle で作っていて、既存のテーブルに CHECK 制約を足すと、テーブルを作り直すマイグレーションが作られます。#5で外部キーを足したときに、データを移しながら作り直す大変さを経験しました。
列を足すだけなら、マイグレーションは ALTER TABLE ... ADD の2行で済みます。範囲はアプリの入力チェックで守り、そのチェックをユニットテストで確かめる形にしました。
if (values.rating !== '' && !/^[1-5]$/.test(values.rating)) {
errors.rating = 'おすすめ度を選び直してください';
}
画面を通さずに rating=6 や rating=3.5 を送っても保存されないことは、E2E でも確かめています。
受入確認で、AC を自動テストに回した
受入確認は、ほぼそのまま OK でした。1つだけ、ACを「(自動テストで確認)」に書き直しています。
おすすめ理由が 1000 文字を超えると保存されず、エラーが表示される
受入確認のために 1000 文字を超える文章を用意して貼るのは、手間のわりに得るものが少ないためです。E2E で 1001 文字だとエラーになること、ユニットテストで 1000 文字ちょうどは通ることを確かめているので、受入はそのテストの結果で行いました。
#4で決めた「画面から確かめられないACは、自動テストで確認とする」の使い方が、少し広がりました。画面から確かめられないものだけでなく、画面から確かめるのが割に合わないものも、自動テストに任せてよいと考えています。前回(#12)の「1 MB を超える画像」も同じです。
おわりに
- おすすめ度は、見た目は★、中身はラジオボタン。キーボードや読み上げはブラウザに任せる
- 「未設定に戻す」は、ラジオボタンの弱点を補うボタン
- 範囲は DB の制約ではなく入力チェックで守る(テーブルの作り直しを避ける)
- 受入確認で試すのが割に合わない AC は、自動テストに回す
画像とおすすめ度がそろって、ギアの一覧が少し「自分のギアの記録」らしくなってきました。 この2つとプランの改善は、あわせて本番にもリリースしています。