packing-bingo 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -185
- package/dist/{chunk-ZDBGKPFF.js → chunk-Z34GMJW2.js} +208 -108
- package/dist/core.cjs +208 -108
- package/dist/core.d.cts +51 -11
- package/dist/core.d.ts +51 -11
- package/dist/core.js +3 -1
- package/dist/index.cjs +212 -111
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -5
- package/dist/packing-bingo.global.js +37 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,104 +1,219 @@
|
|
|
1
|
-
# Packing Bingo
|
|
1
|
+
# 持ちものビンゴ(Packing Bingo)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**修学旅行の持ち物チェックリストを、4×4(3×3や5×5などにも変えられます)のビンゴにするReactコンポーネントです。持ち物をかばんに入れるたびにマスをタップしてスタンプを押し、タテ・ヨコ・ナナメがそろうと、コンボ・ファンファーレ・花火で盛り上がります。進み具合は各自のブラウザに自動で保存されます。**
|
|
4
4
|
|
|
5
|
+
[](https://jin-hiroo.github.io/packing-bingo/)
|
|
5
6
|
[](https://www.npmjs.com/package/packing-bingo)
|
|
6
7
|
[](LICENSE)
|
|
7
|
-
[](
|
|
8
|
+
[](#概要)
|
|
8
9
|
[](#api)
|
|
9
|
-
[](
|
|
10
|
+
[](#単体のページ)
|
|
10
11
|
|
|
11
|
-
[
|
|
12
|
+
[デモサイト](https://jin-hiroo.github.io/packing-bingo/) · [クイックスタート](#クイックスタート) · [デモコード](#デモ) · [使い方の例](#使い方の例) · [API](#api) · [組み込むときの注意](#組み込むときの注意) · [Reactなしで使う](#reactなしで使う) · [English](#english) · [変更履歴](CHANGELOG.md)
|
|
12
13
|
|
|
13
14
|
<p align="center">
|
|
14
|
-
<a href="docs/images/bingo-desktop.png"><img src="docs/images/bingo-desktop.png" alt="
|
|
15
|
+
<a href="docs/images/bingo-desktop.png"><img src="docs/images/bingo-desktop.png" alt="持ち物が並んだ4×4のビンゴカード。一番上の列に続いて、左上からのナナメの列がそろった瞬間で、大きなBINGO!の文字、「2 ライン目!」、+100の得点、紙吹雪が出ている。上の欄には8/16、2ライン、290点、2 COMBOと表示されている。" width="560"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
_2コンボで2列目がそろったところ。列に線が引かれ、BINGO!と+100が出て、紙吹雪が舞います。_
|
|
18
19
|
|
|
19
20
|
<p>
|
|
20
|
-
<a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="
|
|
21
|
-
<a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="
|
|
22
|
-
<a href="docs/images/phone-perfect.png"><img src="docs/images/phone-perfect.png" alt="
|
|
21
|
+
<a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="スマホの画面。一番上の列がそろっていて、ナナメの列を完成させる最後のマス(日焼け止め)が点線で囲まれ、リーチの札が付いて点滅している。下の1行には「リーチ! あと「日焼け止め」でビンゴ」と出ている。" height="320"></a>
|
|
22
|
+
<a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="スマホの画面。3回続けてタップしたところで、+20と+30、2 COMBO!と3 COMBO!の表示、減っていくコンボのゲージ、新しく出た2つのリーチ!が見える。" height="320"></a>
|
|
23
|
+
<a href="docs/images/phone-perfect.png"><img src="docs/images/phone-perfect.png" alt="スマホの画面。16個すべてにスタンプを押したあと。準備完了のスタンプと、得点3,410、最大コンボ×10、10ラインのパーフェクト画面に、紙吹雪と持ち物の絵文字が降っている。" height="320"></a>
|
|
23
24
|
</p>
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
_スマホでの表示。左から、1列そろって次の列にリーチがかかったところ、3回続けたコンボ、全部そろったときのパーフェクト画面です。_
|
|
26
27
|
|
|
27
|
-
##
|
|
28
|
+
## できること
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
修学旅行の前の晩の荷造りは面倒です。持ちものビンゴは16個の持ち物をビンゴカードに並べるので、持ち物を1つ入れるたびにスタンプが押せて、何個かごとにお祝いがあります。
|
|
30
31
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
32
|
+
- **タップするたびに反応があります。** スタンプ、波紋、火花、少しずつ高くなる音。2.5秒以内に続けてタップすると、コンボになります。
|
|
33
|
+
- **次の目標がいつも見えます。** あと1つで列がそろうマスは、リーチの札を付けて点滅し、下の1行にその持ち物の名前が出ます。
|
|
34
|
+
- **列がそろうたびにお祝いします。** タテ・ヨコ・ナナメの列(4×4なら10列)のどれかがそろうと、BINGO!の文字と紙吹雪が出ます。すべてそろうと、花火とスコアカードのフィナーレになります。
|
|
35
|
+
- **マスの数を選べます。** 3×3や5×5など、2×2から10×10までの正方形にできます。奇数のときは、真ん中をはじめからスタンプ済みのFREEマスにできます。[マスの数を変える](#マスの数を変える)を見てください。
|
|
36
|
+
- **得点は、そろえることへのごほうびです。** 付けたり外したりしても増えません。[得点のルール](#得点のルール)を見てください。
|
|
37
|
+
- **進み具合は自動で保存されます。** 各自のブラウザ(localStorage)に保存されるので、ページを閉じても続きから始められます。サーバーには何も送りません。
|
|
38
|
+
- **だれでも使えるように配慮しています。** マスは本物のチェックボックスなので、キーボードで操作できます。`prefers-reduced-motion`(動きを減らす設定)に従います。音は合成した音(音声ファイルなし)で、消すこともできます。
|
|
36
39
|
|
|
37
|
-
##
|
|
40
|
+
## 概要
|
|
38
41
|
|
|
39
|
-
|
|
|
42
|
+
| 項目 | 内容 |
|
|
40
43
|
| --- | --- |
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
44
|
+
| インストール | `npm install packing-bingo`。ESMとCommonJSの両方、TypeScriptの型付き |
|
|
45
|
+
| 必要なもの | React 18以上と `react-dom`。ほかに依存するパッケージはありません。CSSの読み込みもProviderも不要です |
|
|
46
|
+
| サイズ | 約14 kB(minify・gzip後、CSS込み、Reactは含まず) |
|
|
47
|
+
| サーバー側での表示 | `"use client"` 付きで配布しています。サーバー側では空の `<div>` を出し、ブラウザでカードを作ります |
|
|
48
|
+
| 見た目 | `<head>` に `<style>` を1つ追加します。クラス名はすべて `pkb-` で始まります。CSS変数で配色を変えられ、置いた場所の幅に合わせて自動で大きさが変わります |
|
|
49
|
+
| 通信 | Google Fontsからフォントを2つ読み込みます(`fonts={false}` で止められます) |
|
|
50
|
+
| 保存 | localStorageだけ。進み具合と、音のオン・オフの設定 |
|
|
51
|
+
| ブラウザ | Chrome・Edge 105以上、Safari 16以上、Firefox 121以上。今のところChromiumでしか試していません([詳しく](#対応ブラウザと確認済みの範囲)) |
|
|
49
52
|
|
|
50
|
-
##
|
|
53
|
+
## クイックスタート
|
|
54
|
+
|
|
55
|
+
コピーして貼り付けるだけで、サイトの好きな場所に置けます。進み具合は各自のブラウザに自動で保存されるので、保存の設定も要りません。
|
|
56
|
+
|
|
57
|
+
### 1. インストールする
|
|
51
58
|
|
|
52
59
|
```sh
|
|
53
|
-
npm install packing-bingo
|
|
60
|
+
npm install packing-bingo
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
pnpmなら `pnpm add packing-bingo`、yarnなら `yarn add packing-bingo` です。
|
|
64
|
+
|
|
65
|
+
### 2. 表示したい場所に置く
|
|
66
|
+
|
|
67
|
+
表示したいページやコンポーネントで読み込み、置きたい場所に `<PackingBingo />` と書きます。一般的な修学旅行の持ち物16個が、4×4のカードに並びます。
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { PackingBingo } from "packing-bingo";
|
|
71
|
+
|
|
72
|
+
// JSXの中の、表示したい場所に
|
|
73
|
+
<PackingBingo />
|
|
54
74
|
```
|
|
55
75
|
|
|
76
|
+
Next.js(App Router)で専用のページを作るなら、`app/packing/page.tsx` を作って次を貼り付けます。`/packing` を開くと表示されます。
|
|
77
|
+
|
|
56
78
|
```tsx
|
|
57
79
|
import { PackingBingo } from "packing-bingo";
|
|
58
80
|
|
|
59
81
|
export default function PackingPage() {
|
|
60
|
-
return
|
|
82
|
+
return (
|
|
83
|
+
<main style={{ padding: 16 }}>
|
|
84
|
+
<PackingBingo />
|
|
85
|
+
</main>
|
|
86
|
+
);
|
|
61
87
|
}
|
|
62
88
|
```
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
カードは置いた場所の幅いっぱい(最大720 px)に広がり、狭い場所ではスマホ向けの配置に自動で切り替わります。
|
|
65
91
|
|
|
66
|
-
|
|
92
|
+
Next.jsのServer Componentのページからでも、そのまま使えます。ただし、`onBingo` などの関数(コールバック)はServer Componentから渡せず、「Event handlers cannot be passed to Client Component props」というエラーでビルドが止まります。コールバックを使うときは、1行目に `"use client";` を書いたファイル(client component)の中に `<PackingBingo>` を置いてください。
|
|
93
|
+
|
|
94
|
+
### 3. 持ち物を変える
|
|
95
|
+
|
|
96
|
+
`items` に、持ち物を左上から横に1行ずつ並べて渡します。16個なら4×4、9個なら3×3、25個なら5×5になります([マスの数を変える](#マスの数を変える))。次のリストを書き換えて使ってください。
|
|
67
97
|
|
|
68
98
|
```tsx
|
|
69
|
-
|
|
70
|
-
"use client";
|
|
99
|
+
import { PackingBingo, type BingoItem } from "packing-bingo";
|
|
71
100
|
|
|
72
|
-
|
|
101
|
+
// 持ち物。16個なら、左上から横に4つずつ並びます。
|
|
102
|
+
// \u200B は「狭い画面ではここで改行してよい」という目印です(画面には出ません)。
|
|
103
|
+
const items: BingoItem[] = [
|
|
104
|
+
{ emoji: "👛", label: "財布" },
|
|
105
|
+
{ emoji: "📱", label: "スマホ" },
|
|
106
|
+
{ emoji: "🔋", label: "モバイル\u200B充電器" },
|
|
107
|
+
{ emoji: "📋", label: "健康調査票" },
|
|
108
|
+
{ emoji: "🪪", label: "マイナカード\u200Bまたは保険証" },
|
|
109
|
+
{ emoji: "📖", label: "しおり" },
|
|
110
|
+
{ emoji: "😷", label: "マスク" },
|
|
111
|
+
{ emoji: "🤧", label: "ハンカチ" },
|
|
112
|
+
{ emoji: "💊", label: "常備薬" },
|
|
113
|
+
{ emoji: "🛍️", label: "ビニール袋" },
|
|
114
|
+
{ emoji: "🧤", label: "軍手(学校支給)" },
|
|
115
|
+
{ emoji: "🔦", label: "ヘッドライト" },
|
|
116
|
+
{ emoji: "👕", label: "ジャージ" },
|
|
117
|
+
{ emoji: "☔", label: "雨具" },
|
|
118
|
+
{ emoji: "🛁", label: "タオル" },
|
|
119
|
+
{ emoji: "🧴", label: "日焼け止め" },
|
|
120
|
+
];
|
|
73
121
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
122
|
+
// JSXの中の、表示したい場所に
|
|
123
|
+
<PackingBingo items={items} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Reactを使っていないページに置く場合
|
|
127
|
+
|
|
128
|
+
HTMLの好きな場所に、次をそのまま貼り付けます。インストールは要りません。
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<div id="packing-bingo"></div>
|
|
132
|
+
<script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.3/dist/packing-bingo.global.js"></script>
|
|
133
|
+
<script>
|
|
134
|
+
PackingBingo.createPackingBingo(document.getElementById("packing-bingo"), {
|
|
135
|
+
items: [
|
|
136
|
+
{ emoji: "👛", label: "財布" }, { emoji: "📱", label: "スマホ" },
|
|
137
|
+
{ emoji: "🔋", label: "モバイル\u200B充電器" }, { emoji: "📋", label: "健康調査票" },
|
|
138
|
+
{ emoji: "🪪", label: "マイナカード\u200Bまたは保険証" }, { emoji: "📖", label: "しおり" },
|
|
139
|
+
{ emoji: "😷", label: "マスク" }, { emoji: "🤧", label: "ハンカチ" },
|
|
140
|
+
{ emoji: "💊", label: "常備薬" }, { emoji: "🛍️", label: "ビニール袋" },
|
|
141
|
+
{ emoji: "🧤", label: "軍手(学校支給)" }, { emoji: "🔦", label: "ヘッドライト" },
|
|
142
|
+
{ emoji: "👕", label: "ジャージ" }, { emoji: "☔", label: "雨具" },
|
|
143
|
+
{ emoji: "🛁", label: "タオル" }, { emoji: "🧴", label: "日焼け止め" },
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
77
147
|
```
|
|
78
148
|
|
|
79
|
-
##
|
|
149
|
+
## デモ
|
|
80
150
|
|
|
81
|
-
|
|
151
|
+
- **デモサイト:** https://jin-hiroo.github.io/packing-bingo/
|
|
152
|
+
その場でタップして試せます。演出だけを自動で再生するカードと、組み込み用のコード(このREADMEのクイックスタートと同じもの)も載っています。
|
|
153
|
+
- **デモコード:** [`examples/`](examples/) に、そのまま動かせるプロジェクトがあります。フォルダごとコピーして、自分のサイトの土台にもできます。
|
|
154
|
+
|
|
155
|
+
| フォルダ | 内容 | 動かし方 |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| [`examples/nextjs`](examples/nextjs) | Next.js(App Router)。クイックスタートそのままのページと、ビンゴの瞬間を受け取るページ | `npm install` → `npm run dev` |
|
|
158
|
+
| [`examples/vite-react`](examples/vite-react) | Vite + React。ふつうのReactアプリのページに置く例 | `npm install` → `npm run dev` |
|
|
159
|
+
| [`examples/html`](examples/html) | HTMLだけ。Reactもインストールも使わない例 | `index.html` をブラウザで開く |
|
|
160
|
+
|
|
161
|
+
## 使い方の例
|
|
162
|
+
|
|
163
|
+
### 持ち物と文言を変える
|
|
82
164
|
|
|
83
165
|
```tsx
|
|
84
166
|
import { PackingBingo, en, type BingoItem } from "packing-bingo";
|
|
85
167
|
|
|
86
168
|
const items: BingoItem[] = [
|
|
87
|
-
{ emoji: "
|
|
88
|
-
{ emoji: "
|
|
89
|
-
// …
|
|
169
|
+
{ emoji: "👛", label: "Wallet" },
|
|
170
|
+
{ emoji: "📱", label: "Phone" },
|
|
171
|
+
// … 4×4なら16個、左上から横に1行ずつ
|
|
90
172
|
];
|
|
91
173
|
|
|
92
|
-
<PackingBingo items={items} messages={en} title="
|
|
174
|
+
<PackingBingo items={items} messages={en} title="School Trip" tag="Class 2-B" />;
|
|
93
175
|
```
|
|
94
176
|
|
|
95
|
-
- `items`
|
|
96
|
-
- `messages`
|
|
97
|
-
-
|
|
177
|
+
- `items` の数で、カードの大きさが決まります。16個なら4×4、9個なら3×3、25個なら5×5です。正方形にならない数だと、`[packing-bingo] "items" must have a square number of entries` で始まるエラーになります。くわしくは[マスの数を変える](#マスの数を変える)を見てください。
|
|
178
|
+
- `messages` は日本語の既定値の上に重なるので、`messages={{ reset: "やり直す" }}` のように一部の文言だけを変えられます。`en` は英語の文言一式です。キーの一覧は[文言のキー](#文言のキー)にあります。
|
|
179
|
+
- 狭いカードでは、ラベルは `・` の後ろで折り返します。`常備薬・酔い止め` は「常備薬・」と「酔い止め」の2行になります。`・` を入れたくない長いラベルは、`\u200B`(画面には出ない改行位置の目印)を入れた場所で折り返します。`"モバイル\u200B充電器"` は「モバイル」と「充電器」の2行になります。スマホの幅では、1行に入るのは6文字ほどです。
|
|
180
|
+
|
|
181
|
+
### マスの数を変える
|
|
182
|
+
|
|
183
|
+
`size` で1辺のマスの数を、`free` で真ん中のFREEマスを決めます。
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
// 3×3。items を渡さなければ、既定の持ち物の最初の9個を使います
|
|
187
|
+
<PackingBingo size={3} />
|
|
188
|
+
|
|
189
|
+
// 5×5で、真ん中がFREEマス。items は 5×5 − 1 = 24個
|
|
190
|
+
<PackingBingo size={5} free items={items} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
<p>
|
|
194
|
+
<a href="docs/images/phone-5x5-free.png"><img src="docs/images/phone-5x5-free.png" alt="スマホの画面。5×5のカードで、真ん中が赤い丸のFREEマス。左上からのナナメの列がFREEマスを通ってそろい、線が引かれている。真ん中のヨコの列は、残りの「雨具」にリーチの札が付いている。上の欄は9/24、1ライン。" height="320"></a>
|
|
195
|
+
</p>
|
|
196
|
+
|
|
197
|
+
_5×5で真ん中がFREEのカード。FREEマスを通るナナメの列は、残りの4個でそろいます。_
|
|
198
|
+
|
|
199
|
+
- `size` は2〜10です。省略すると、`items` の数から決まります(9個なら3×3、16個なら4×4、25個なら5×5。`free` のときは8個で3×3、24個で5×5)。
|
|
200
|
+
- `items` は `size` × `size` 個、`free` のときはそれより1個少なくします。左上から横に1行ずつ並び、FREEマスは飛ばします。数が合わないと、必要な数を書いたエラーになります。
|
|
201
|
+
- `items` を渡さないときに使う既定の持ち物は16個です。3×3までは最初の何個かを使いますが、5×5以上では `items` が必要です。
|
|
202
|
+
- `free` は `size` が奇数のときだけ使えます。FREEマスは最初からスタンプ済みで、タップできず、持ち物の数にも入りません。FREEマスを通る列(真ん中のタテとヨコ、2本のナナメ)は、残りの `size` − 1 個でそろいます。マスの文字は `messages` の `free` で変えられます(既定は `FREE`)。
|
|
203
|
+
- `checked`・`defaultChecked`・`onChange` の配列は、持ち物の数(FREEマスを除く)と同じ長さです。
|
|
204
|
+
- 文字やすき間は、マスの大きさに合わせて変わります。スマホの幅で読みやすいのは5×5くらいまでです。それより大きいと、ラベルが細かく折り返してマスが縦に伸びます。
|
|
205
|
+
- 持ち物リストから決まる保存のキーは、実際に並ぶ持ち物から決まります。`size={3}` だけのカードと、ふつうの4×4のカードは、別々に保存されます。
|
|
206
|
+
|
|
207
|
+
### 保存のしかたを変える
|
|
208
|
+
|
|
209
|
+
何も指定しなければ、進み具合は持ち物リストから自動で決まるキーで、各自のブラウザに保存されます。持ち物リストが違うカードは別々に保存されるので、1つのサイトに複数のチェックリストを置いても混ざりません。
|
|
98
210
|
|
|
99
|
-
|
|
211
|
+
- **キーを自分で決める:** `storageKey="okinawa-2026"` のように指定します。持ち物リストから決まるキーは、持ち物を1文字でも直すと変わり、カードは新しく始まります。生徒が使い始めたあとで持ち物を直す可能性があるなら、最初からキーを決めておくと、直したあとも続きから使えます。同じ持ち物のカードを2枚、別々に保存したいときも、それぞれにキーを付けます。
|
|
212
|
+
- **保存しない:** `storageKey={null}` にします。
|
|
100
213
|
|
|
101
|
-
|
|
214
|
+
### 自分のサーバーに保存する
|
|
215
|
+
|
|
216
|
+
ブラウザへの保存は、その端末の中だけで使えます。別の端末でも続きから使いたい場合だけ、チェックを自分の state で持って `checked` で渡します(controlledといいます)。このときブラウザには保存しません。
|
|
102
217
|
|
|
103
218
|
```tsx
|
|
104
219
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -109,7 +224,7 @@ export function PackingCard({ studentId }: { studentId: string }) {
|
|
|
109
224
|
const saving = useRef(Promise.resolve());
|
|
110
225
|
|
|
111
226
|
useEffect(() => {
|
|
112
|
-
let current = true; //
|
|
227
|
+
let current = true; // 生徒が切り替わったあとに届いた古い応答は使わない
|
|
113
228
|
setChecked(null);
|
|
114
229
|
fetch(`/api/packing/${studentId}`)
|
|
115
230
|
.then((res) => res.json())
|
|
@@ -121,11 +236,11 @@ export function PackingCard({ studentId }: { studentId: string }) {
|
|
|
121
236
|
};
|
|
122
237
|
}, [studentId]);
|
|
123
238
|
|
|
124
|
-
if (!checked) return null; //
|
|
239
|
+
if (!checked) return null; // または同じ大きさの仮表示
|
|
125
240
|
|
|
126
241
|
const save = (next: boolean[]) => {
|
|
127
242
|
setChecked(next);
|
|
128
|
-
//
|
|
243
|
+
// 保存は1つずつ順に送る。遅れた古い保存が、新しい保存を上書きしないように。
|
|
129
244
|
saving.current = saving.current
|
|
130
245
|
.then(() => fetch(`/api/packing/${studentId}`, { method: "PUT", body: JSON.stringify({ checked: next }) }))
|
|
131
246
|
.then(() => undefined, () => undefined);
|
|
@@ -135,31 +250,31 @@ export function PackingCard({ studentId }: { studentId: string }) {
|
|
|
135
250
|
}
|
|
136
251
|
```
|
|
137
252
|
|
|
138
|
-
|
|
253
|
+
生徒のタップは通信より速いので、この例では保存を順番に送り、`studentId` が変わったあとに届いた読み込みは捨てています。保存のたびにすべての持ち物の状態を送るので、最後の保存がいつも最新の状態になります。
|
|
139
254
|
|
|
140
|
-
|
|
255
|
+
controlledのカードの動き:
|
|
141
256
|
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
- `storageKey`
|
|
257
|
+
- タップしただけでは、カードは変わりません。ユーザーが求めた状態(持ち物の数だけの配列)で `onChange` が呼ばれ、`checked` が変わるとカードがそれに合わせます。`checked` を変えなければ、タップは取り消されます。
|
|
258
|
+
- `checked` がちょうど1個だけ変わったときは、タップと同じように演出します(スタンプ、音、ビンゴやフィナーレ)。サーバーからデータが届いたときのように一度に何個も変わったときは、演出なしで静かに反映し、同時にそろった列のボーナスも付けません。
|
|
259
|
+
- リセットボタンを押すと、すべて `false` の配列で `onChange` が呼ばれます。
|
|
260
|
+
- `storageKey` は使われません。
|
|
146
261
|
|
|
147
|
-
###
|
|
262
|
+
### ビンゴの瞬間を受け取る
|
|
148
263
|
|
|
149
264
|
```tsx
|
|
150
265
|
<PackingBingo
|
|
151
|
-
|
|
266
|
+
items={items}
|
|
152
267
|
onBingo={({ newLines, totalLines, score }) => analytics.track("bingo", { totalLines, score })}
|
|
153
268
|
onPerfect={({ score, maxCombo }) => analytics.track("perfect", { score, maxCombo })}
|
|
154
269
|
/>
|
|
155
270
|
```
|
|
156
271
|
|
|
157
|
-
- `onBingo`
|
|
158
|
-
- `onChange`
|
|
159
|
-
- `onBingo`
|
|
160
|
-
- `onChange
|
|
272
|
+
- `onBingo` は、タップで1列以上そろったときに呼ばれます。カード全体がそろったタップでは、代わりに `onPerfect` が呼ばれます。
|
|
273
|
+
- `onChange` は、ユーザーの操作(タップ、リセットボタン、`ref.reset()`)のときだけ呼ばれます。`checked` や `ref.setChecked()` で変えたときは呼ばれません。
|
|
274
|
+
- `onBingo` と `onPerfect` は、タップと同じように演出した変化のときに呼ばれます。`checked` が1個だけ変わったときも含みます。静かに反映した変化や、最初の表示では呼ばれません。
|
|
275
|
+
- 順番は、まず `onChange`、次に `onBingo` か `onPerfect` です。どれもカードの表示が変わったあとに呼ばれるので、中でカードをリセットしたり、画面から外したりしても大丈夫です。
|
|
161
276
|
|
|
162
|
-
###
|
|
277
|
+
### 外から操作する
|
|
163
278
|
|
|
164
279
|
```tsx
|
|
165
280
|
import { useRef } from "react";
|
|
@@ -169,233 +284,226 @@ function PackingPage() {
|
|
|
169
284
|
const card = useRef<PackingBingoHandle>(null);
|
|
170
285
|
return (
|
|
171
286
|
<>
|
|
172
|
-
<button onClick={() => card.current?.reset()}
|
|
173
|
-
<PackingBingo ref={card}
|
|
287
|
+
<button onClick={() => card.current?.reset()}>最初からやり直す</button>
|
|
288
|
+
<PackingBingo ref={card} />
|
|
174
289
|
</>
|
|
175
290
|
);
|
|
176
291
|
}
|
|
177
292
|
```
|
|
178
293
|
|
|
179
|
-
|
|
294
|
+
`reset()`・`setChecked()`・`getState()` が使えます。くわしくは [ref](#ref) を見てください。controlledのカードでは、`setChecked()` ではなく `checked` を変えてください。
|
|
180
295
|
|
|
181
|
-
###
|
|
296
|
+
### 配色とレイアウト
|
|
182
297
|
|
|
183
|
-
|
|
298
|
+
次のCSS変数を、カードかその親要素に指定します。
|
|
184
299
|
|
|
185
|
-
|
|
|
300
|
+
| 変数 | 使われる場所 | 既定値 |
|
|
186
301
|
| --- | --- | --- |
|
|
187
|
-
| `--pkb-sun` |
|
|
188
|
-
| `--pkb-ink` |
|
|
189
|
-
| `--pkb-paper` |
|
|
190
|
-
| `--pkb-red` |
|
|
191
|
-
| `--pkb-mint` |
|
|
192
|
-
| `--pkb-font-fallback` |
|
|
302
|
+
| `--pkb-sun` | カードの背景 | `#FFD54F` |
|
|
303
|
+
| `--pkb-ink` | 線、枠、文字 | `#2A1F14` |
|
|
304
|
+
| `--pkb-paper` | マス | `#FFFDF6` |
|
|
305
|
+
| `--pkb-red` | スタンプとアクセント | `#E8473B` |
|
|
306
|
+
| `--pkb-mint` | そろった列 | `#BDEBD2` |
|
|
307
|
+
| `--pkb-font-fallback` | 見出しなどのフォントが使えないときのフォント | `sans-serif` |
|
|
193
308
|
|
|
194
309
|
```tsx
|
|
195
310
|
<PackingBingo style={{ "--pkb-sun": "#9AD0F5" } as React.CSSProperties} />
|
|
196
311
|
```
|
|
197
312
|
|
|
198
|
-
|
|
313
|
+
カードは置いた場所の幅いっぱい(最大720 px)に広がり、中央に寄ります。置いた場所の幅が560 pxより狭いと、スマホ向けの配置に切り替わります。画面の幅ではなく置いた場所の幅で決まるので、広い画面のサイドバーに置いてもスマホ向けの配置になります。高さは幅から自動で決まります。サーバー側では空の `<div>` を出すので、カードはhydrationのあとに現れます。レイアウトのずれが気になるときは、外側の要素に `min-height` を付けてください。
|
|
199
314
|
|
|
200
315
|
## API
|
|
201
316
|
|
|
202
|
-
###
|
|
317
|
+
### props
|
|
203
318
|
|
|
204
|
-
|
|
|
319
|
+
| prop | 型 | 既定値 | 内容 |
|
|
205
320
|
| --- | --- | --- | --- |
|
|
206
|
-
| `items` | `readonly BingoItem[]`
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
321
|
+
| `items` | `readonly BingoItem[]`(`size` × `size` 個。`free` のときは1個少なく) | `DEFAULT_ITEMS`(一般的な修学旅行の持ち物16個) | マスの中身。左上から横に1行ずつ、FREEマスは飛ばす:`{ emoji, label }` |
|
|
322
|
+
| `size` | `number`(2〜10) | `items` の数から決まる(既定の持ち物なら4) | 1辺のマスの数。[マスの数を変える](#マスの数を変える)を見てください |
|
|
323
|
+
| `free` | `boolean` | `false` | 真ん中をFREEマスにする。`size` が奇数のときだけ |
|
|
324
|
+
| `messages` | `Partial<Messages>` | `ja` | 画面の文言。日本語の既定値の上に重なります。英語の `en` も入っています |
|
|
325
|
+
| `title`・`tag`・`subtitle` | `string` | `messages` の値 | 見出し、その上の小さなラベル、その下の1行。`subtitle` は既定では空で、表示しません |
|
|
326
|
+
| `storageKey` | `string \| null` | 持ち物から自動で決まる | 進み具合を保存するlocalStorageのキー。`null` で保存しません。controlledのときは使われません |
|
|
327
|
+
| `defaultChecked` | `readonly boolean[]`(持ち物と同じ数) | すべて `false` | uncontrolledのカードの最初の状態。保存された進み具合があれば、そちらが優先です |
|
|
328
|
+
| `checked` | `readonly boolean[]`(持ち物と同じ数) | なし | 指定するとcontrolledになります。[自分のサーバーに保存する](#自分のサーバーに保存する)を見てください |
|
|
329
|
+
| `onChange` | `(checked: boolean[]) => void` | なし | ユーザーがタップかリセットをした。controlledのときは、求められた状態 |
|
|
330
|
+
| `onBingo` | `(event: BingoEvent) => void` | なし | タップで1列以上そろった |
|
|
331
|
+
| `onPerfect` | `(event: PerfectEvent) => void` | なし | タップでカード全体がそろった |
|
|
332
|
+
| `sound` | `boolean` | `true` | 音の最初の設定。ユーザーが🔊ボタンで選ぶまで使われます |
|
|
333
|
+
| `fonts` | `boolean` | `true` | Dela Gothic OneとM PLUS Rounded 1cをGoogle Fontsから読み込む |
|
|
334
|
+
| `demo` | `boolean` | `false` | デモ用のボタンを出します。保存された進み具合も `defaultChecked` もなければ、一番上の列と、左上からのナナメの途中までが埋まった状態(4×4なら6マス)から始めます。`storageKey` を指定しない限り保存しません |
|
|
335
|
+
| `className`・`style` | | なし | 外側の `<div>` に付きます |
|
|
336
|
+
| `ref` | `Ref<PackingBingoHandle>` | なし | [ref](#ref) を見てください |
|
|
337
|
+
|
|
338
|
+
propsは中身で比べるので、配列やオブジェクトをその場で書いても構いません。`items`・`size`・`free`・`title`・`tag`・`subtitle`・`storageKey`・`sound`・`fonts`・`demo`、または `messages` の文字列が変わると、カードを作り直します。保存先のキーが変わらなければ、チェックはそのまま残ります。キーが変わったとき(`items` を変えて自動のキーが変わったときも含む)は、新しいキーに保存された進み具合を読み込みます。得点は[得点のルール](#得点のルール)のとおり計算し直します。`messages` の関数はpropsやstateを使っても構いません。`messages` が変わると、作り直さずに下の1行だけを描き直します。
|
|
339
|
+
|
|
340
|
+
### イベント
|
|
224
341
|
|
|
225
342
|
```ts
|
|
226
343
|
interface BingoEvent {
|
|
227
|
-
newLines: number[]; //
|
|
228
|
-
totalLines: number; //
|
|
229
|
-
score: number; //
|
|
344
|
+
newLines: number[]; // このタップでそろった列(bingoLines(size, free) の番号。4×4ならLINESと同じ)
|
|
345
|
+
totalLines: number; // これまでにそろった列の数
|
|
346
|
+
score: number; // このタップのあとの得点
|
|
230
347
|
}
|
|
231
348
|
|
|
232
349
|
interface PerfectEvent {
|
|
233
|
-
score: number; //
|
|
350
|
+
score: number; // 最終得点。初めて全部そろえたときは1000点のボーナスを含む
|
|
234
351
|
maxCombo: number;
|
|
235
352
|
}
|
|
236
353
|
```
|
|
237
354
|
|
|
238
|
-
###
|
|
355
|
+
### ref
|
|
239
356
|
|
|
240
357
|
```ts
|
|
241
358
|
interface PackingBingoHandle {
|
|
242
|
-
/**
|
|
359
|
+
/** リセットボタンと同じ。カードと得点を消して onChange を呼ぶ。controlled のカードでは onChange を呼ぶだけ。 */
|
|
243
360
|
reset(): void;
|
|
244
|
-
/**
|
|
361
|
+
/** 1個だけの変化はタップと同じように演出し、それより大きな変化は静かに反映する。onChange は呼ばない。 */
|
|
245
362
|
setChecked(checked: readonly boolean[]): void;
|
|
246
|
-
/** null
|
|
363
|
+
/** 表示前とサーバー側では null。 */
|
|
247
364
|
getState(): PackingBingoState | null;
|
|
248
365
|
}
|
|
249
366
|
|
|
250
367
|
interface PackingBingoState {
|
|
251
|
-
checked: boolean[]; //
|
|
252
|
-
lines: number[]; //
|
|
368
|
+
checked: boolean[]; // 持ち物ごと、左上から横に1行ずつ(FREEマスは含まない)
|
|
369
|
+
lines: number[]; // そろった列(bingoLines(size, free) の番号)
|
|
253
370
|
score: number;
|
|
254
371
|
maxCombo: number;
|
|
255
|
-
complete: boolean; //
|
|
372
|
+
complete: boolean; // すべての持ち物にチェックが付いている
|
|
373
|
+
size: number; // 1辺のマスの数
|
|
374
|
+
free: boolean; // 真ん中がFREEマスか
|
|
256
375
|
}
|
|
257
376
|
```
|
|
258
377
|
|
|
259
|
-
`LINES`
|
|
378
|
+
`LINES` もexportしています。4×4のカードの列で、0〜3がヨコの列、4〜7がタテの列、8が左上からのナナメ、9が右上からのナナメです。ほかの大きさの列は `bingoLines(size, free)` で取れます。並び方は同じで、ヨコの列、タテの列、左上からのナナメ、右上からのナナメの順です。中身は `checked` と同じ持ち物の番号なので、`free` のときは真ん中を通る列だけ `size` − 1 個になります。
|
|
260
379
|
|
|
261
|
-
###
|
|
380
|
+
### 得点のルール
|
|
262
381
|
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
-
|
|
382
|
+
- 初めてチェックした持ち物は、10点 × そのときのコンボ数。コンボは、2.5秒以内に続けたタップの数です。
|
|
383
|
+
- 1回のタップでn列そろうと、100点 × n²。同じ列で得点するのは1回だけです。
|
|
384
|
+
- 初めてカード全体をそろえると、1000点を足します。
|
|
385
|
+
- チェックを外しても得点は減らず、付け直しても増えません。
|
|
386
|
+
- 静かに反映したあとや作り直したあと(保存された進み具合を読み込んだときなど)は、チェックした持ち物1個につき10点、そろった列1つにつき100点で計算し直し、最大コンボは0からになります。
|
|
268
387
|
|
|
269
|
-
###
|
|
388
|
+
### 文言のキー
|
|
270
389
|
|
|
271
|
-
`messages`
|
|
390
|
+
`messages` には、次のキーを好きなだけ指定できます。日本語と英語の一式は、[`src/messages.ts`](src/messages.ts) の `ja` と `en` です。
|
|
272
391
|
|
|
273
|
-
-
|
|
274
|
-
-
|
|
392
|
+
- **文字列:** `title`、`tag`、`subtitle`、`packed`、`bingo`、`score`、`reset`、`soundOn`、`soundOff`、`stamp`、`free`、`reachBadge`、`reachPop`、`bannerSingle`、`bannerDouble`、`bannerTriple`、`statusDone`、`perfectTag`、`perfectStamp`、`perfectTitle`、`perfectBody`、`statScore`、`statCombo`、`statLines`、`close`、`demoLabel`、`demoBingo`、`demoPerfect`、`demoRestore`。`perfectStamp` と `perfectBody` では、`\n` で改行します。
|
|
393
|
+
- **関数(どれも文字列を返します):** `combo(n)`、`comboPop(n)`、`bannerNth(totalLines)`、`bannerMulti(newLines)`、`statusStart(left)`、`statusLines(lines, left)`、`statusReach(labels)`、`perfectBonus(points)`。
|
|
394
|
+
- `perfectBody` は、文字列のほかに、持ち物の数を受け取る関数 `perfectBody(total)` にもできます。`ja` と `en` では関数で、「16こ ぜんぶそろった!」の数がカードに合わせて変わります。
|
|
395
|
+
- `subtitle`(見出しの下の1行)と `statusStart(left)`(1列もそろう前の下の1行)は、`ja` と `en` のどちらでも空なので、表示しません。出したいときは `subtitle="タップでスタンプ。タテ・ヨコ・ナナメでビンゴ!"` や `` messages={{ statusStart: (left) => `のこり ${left} こ` }} `` のように指定します。
|
|
275
396
|
|
|
276
|
-
###
|
|
397
|
+
### export
|
|
277
398
|
|
|
278
|
-
- `packing-bingo
|
|
279
|
-
- `packing-bingo/core
|
|
399
|
+
- `packing-bingo`:`PackingBingo`、`createPackingBingo`、`LINES`、`bingoLines`、`DEFAULT_ITEMS`、`ja`、`en`。型は `PackingBingoProps`、`PackingBingoHandle`、`PackingBingoState`、`BingoItem`、`BingoEvent`、`PerfectEvent`、`Messages`、`PackingBingoOptions`、`PackingBingoInstance`。
|
|
400
|
+
- `packing-bingo/core`:Reactのコンポーネントとその型(`PackingBingoProps`、`PackingBingoHandle`)を除いた残り。Reactを読み込みません。
|
|
280
401
|
|
|
281
|
-
##
|
|
402
|
+
## 組み込むときの注意
|
|
282
403
|
|
|
283
|
-
###
|
|
404
|
+
### ページに追加されるもの
|
|
284
405
|
|
|
285
|
-
|
|
406
|
+
最初に表示したとき、`<head>` に `<style id="pkb-styles">` を追加します。これはすべてのカードで共有し、カードを外したあとも残ります。`fonts={false}` でなければ、Google Fonts用の `<link id="pkb-fonts">` も追加します。CSSはすべて `.pkb` の中だけに効き、クラス名とアニメーション名はすべて `pkb-` で始まるので、ページのほかの部分には影響しません。
|
|
286
407
|
|
|
287
|
-
|
|
408
|
+
カードが描くもの(紙吹雪、BINGO!の文字、フィナーレの画面)は、すべてカード自身の枠の中に収まります。`position: fixed` を使ったり `<body>` に要素を足したりはしません。カードは独立した重なり順(stacking context)を持つので、上部に固定したヘッダーの上に描かれることもありません。
|
|
288
409
|
|
|
289
|
-
### Content Security Policy
|
|
410
|
+
### CSP(Content Security Policy)
|
|
290
411
|
|
|
291
|
-
|
|
412
|
+
追加する `<style>` と一部の `style` 属性のために、`style-src 'unsafe-inline'` が必要です。フォントを読み込む場合は、`style-src` に `https://fonts.googleapis.com`、`font-src` に `https://fonts.gstatic.com` も許可してください。
|
|
292
413
|
|
|
293
|
-
###
|
|
414
|
+
### フォント
|
|
294
415
|
|
|
295
|
-
|
|
416
|
+
Dela Gothic OneとM PLUS Rounded 1cを使います。`fonts={false}` にするとGoogleには何も取りに行きません。サイトで同じ名前のフォントを読み込んでいれば(自分のサーバーに置いている場合など)それを使い、なければ `--pkb-font-fallback` のフォントを使います。
|
|
296
417
|
|
|
297
|
-
###
|
|
418
|
+
### 音と振動
|
|
298
419
|
|
|
299
|
-
|
|
420
|
+
音はWeb Audio APIで合成しています。音を鳴らす仕組みは、ユーザーがページを操作したあとにだけ作るので、ブラウザの自動再生の警告は出ません。🔊ボタンの選択は、localStorageの `packing-bingo-muted` に保存されます。そのあとに作られるカードは、ページを読み込み直したあとも含めて、この設定で始まります。すでに表示中のほかのカードは、それぞれの設定のままです。タップやビンゴのときは、Vibration APIがある端末で振動します。実際にはAndroidのブラウザです。
|
|
300
421
|
|
|
301
|
-
###
|
|
422
|
+
### 保存されるデータ
|
|
302
423
|
|
|
303
|
-
|
|
424
|
+
チェックは、持ち物の数(4×4なら16個。FREEマスは含みません)だけの真偽値のJSON配列として、localStorageに保存されます。キーは `storageKey`、指定しなければ `packing-bingo:` で始まる、持ち物リストから決まるキーです。得点は保存しません。保存された進み具合は `defaultChecked` より優先されます。プライベートブラウズなどで保存できない環境では、保存せずにそのまま動きます。
|
|
304
425
|
|
|
305
|
-
###
|
|
426
|
+
### アクセシビリティ
|
|
306
427
|
|
|
307
|
-
|
|
428
|
+
マスは `<label>` の中の本物のチェックボックスなので、TabキーとSpaceキーで操作でき、持ち物の名前がそのまま読み上げられます。絵文字とスタンプは、読み上げの対象から外しています。下の1行は、変化を控えめに読み上げる領域(polite live region)です。`prefers-reduced-motion` のときは、揺れ、弾み、紙吹雪を止めます。スクリーンリーダーでの確認は、まだしていません。
|
|
308
429
|
|
|
309
|
-
###
|
|
430
|
+
### 対応ブラウザと確認済みの範囲
|
|
310
431
|
|
|
311
|
-
|
|
432
|
+
CSSのコンテナクエリ(Chrome・Edge 105以上、Safari 16以上、Firefox 110以上)と、キーボード操作時の枠に `:has()`(Firefox 121以上)を使っています。
|
|
312
433
|
|
|
313
|
-
|
|
434
|
+
確認済み:
|
|
314
435
|
|
|
315
|
-
- React 19
|
|
316
|
-
-
|
|
317
|
-
-
|
|
318
|
-
-
|
|
319
|
-
-
|
|
436
|
+
- React 19のStrictModeで、1ページに3枚、controlledとuncontrolledの両方
|
|
437
|
+
- `renderToString` でのサーバー側の表示(ESMとCommonJS)
|
|
438
|
+
- Next.js 16のApp Routerで本番ビルドし、Server Componentのページとclient componentの両方から使う
|
|
439
|
+
- npmとpnpmでのnpmからのインストール
|
|
440
|
+
- Chromiumで、パソコンとスマホの大きさでの自動タップ。演出の途中でのリセットやコールバックも含む
|
|
441
|
+
- 自動保存。持ち物リストごとに別々に保存されること、`storageKey={null}` で保存しないこと、持ち物を変えると新しく始まり、戻すと前の進み具合が戻ること
|
|
320
442
|
|
|
321
|
-
|
|
443
|
+
これらの自動テストは、まだこのリポジトリには入っていません。Safari、Firefox、実際のスマホでは試しておらず、実機での音も確かめていません。絵文字は端末の絵文字フォントで表示されるので、端末ごとに見た目が違います。
|
|
322
444
|
|
|
323
|
-
##
|
|
445
|
+
## Reactなしで使う
|
|
324
446
|
|
|
325
|
-
|
|
447
|
+
エンジン部分はフレームワークに依存しません。
|
|
326
448
|
|
|
327
449
|
```ts
|
|
328
450
|
import { createPackingBingo } from "packing-bingo/core";
|
|
329
451
|
|
|
330
452
|
const card = createPackingBingo(document.getElementById("bingo")!, {
|
|
331
|
-
|
|
453
|
+
tag: "2年B組",
|
|
332
454
|
onBingo: (e) => console.log(e),
|
|
333
455
|
});
|
|
334
|
-
// card.reset()
|
|
456
|
+
// card.reset()、card.getState()、card.setChecked(next, { animate: true })、card.destroy()
|
|
335
457
|
```
|
|
336
458
|
|
|
337
|
-
|
|
459
|
+
オプションは、Reactのpropsから `checked`・`className`・`style`・`ref` を除いたものです(型は `PackingBingoOptions`)。controlledにするときは `controlled: true` を渡し、`setChecked()` で状態を変えます。`setChecked()` は、`{ animate: true }` を付けないと、いつも静かに反映します。
|
|
338
460
|
|
|
339
|
-
|
|
461
|
+
バンドラーを使わないページでは、CDNからブラウザ用のファイルを読み込みます。グローバル変数 `PackingBingo` ができます。使い方は[クイックスタート](#reactを使っていないページに置く場合)にあります。
|
|
340
462
|
|
|
341
|
-
|
|
342
|
-
<div id="bingo"></div>
|
|
343
|
-
<script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.1/dist/packing-bingo.global.js"></script>
|
|
344
|
-
<script>
|
|
345
|
-
PackingBingo.createPackingBingo(document.getElementById("bingo"), { storageKey: "trip" });
|
|
346
|
-
</script>
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## Standalone page
|
|
463
|
+
## 単体のページ
|
|
350
464
|
|
|
351
|
-
`index.html`
|
|
465
|
+
`index.html` は、アプリ全体が1つに入ったファイルです。そのまま開くことも、静的なホスティングに置くこともできます。進み具合はブラウザに保存されます。
|
|
352
466
|
|
|
353
467
|
```sh
|
|
354
468
|
git clone https://github.com/Jin-hiroo/packing-bingo.git
|
|
355
469
|
cd packing-bingo
|
|
356
|
-
open index.html # macOS
|
|
470
|
+
open index.html # macOSの場合。ほかのOSでは、ファイルをブラウザで開いてください
|
|
357
471
|
```
|
|
358
472
|
|
|
359
|
-
|
|
473
|
+
URLの末尾に `?demo` を付けると、途中まで埋まった状態から始まり、デモ用のボタンが出ます。「▶ ビンゴを見る」でビンゴを、「▶ コンボ → パーフェクト」でコンボからパーフェクトまでを再生します。デモサイトの [standalone.html](https://jin-hiroo.github.io/packing-bingo/standalone.html) でも同じページを公開しています。
|
|
360
474
|
|
|
361
|
-
|
|
475
|
+
ページの中身を変えるときは、最後の `<script>` にある `PackingBingo.createPackingBingo(…)` のオプションを書き換えます。どのビルドでも使われる既定値を変えるときは、`src/items.ts` か `src/messages.ts` を編集して `npm run build` を実行します。
|
|
362
476
|
|
|
363
|
-
##
|
|
477
|
+
## 開発
|
|
364
478
|
|
|
365
479
|
```sh
|
|
366
|
-
npm install #
|
|
367
|
-
npm run build # dist
|
|
480
|
+
npm install # dist/ と index.html もビルドされます
|
|
481
|
+
npm run build # dist/(ESM、CJS、型、ブラウザ用)と単体の index.html
|
|
368
482
|
npm run typecheck
|
|
369
483
|
```
|
|
370
484
|
|
|
371
|
-
`index.html`
|
|
485
|
+
`index.html` は `scripts/build-standalone.mjs` が `src/` から作るので、直接編集しないでください。
|
|
372
486
|
|
|
373
|
-
|
|
487
|
+
デモサイトは `site/index.html` です。`node scripts/build-site.mjs` を実行すると、`_site/` にデモサイトができます。そのとき、組み込み用のコードはこのREADMEのクイックスタートから差し込まれるので、READMEとデモサイトのコードはいつも同じになります。`main` に変更が入ると、GitHub Actions(`.github/workflows/pages.yml`)がビルドしてGitHub Pagesに公開し直します。
|
|
374
488
|
|
|
375
|
-
|
|
489
|
+
## English
|
|
490
|
+
|
|
491
|
+
Packing Bingo is a React component that turns a school-trip packing checklist into a bingo card, with combos, fanfares and fireworks. Cards are 4×4 by default; `size` makes them anything from 2×2 to 10×10, and `free` puts a FREE square in the center of an odd-sized card. Progress is saved in each student's browser automatically. It has no runtime dependencies and is safe to render on the server (it ships with `"use client"`).
|
|
376
492
|
|
|
377
493
|
```sh
|
|
378
494
|
npm install packing-bingo
|
|
379
495
|
```
|
|
380
496
|
|
|
381
497
|
```tsx
|
|
382
|
-
import { PackingBingo } from "packing-bingo";
|
|
498
|
+
import { PackingBingo, en } from "packing-bingo";
|
|
383
499
|
|
|
384
|
-
<PackingBingo
|
|
500
|
+
<PackingBingo messages={en} />;
|
|
385
501
|
```
|
|
386
502
|
|
|
387
|
-
- React
|
|
388
|
-
- `storageKey` を付けると、チェックがその人のブラウザ(localStorage)に保存されます。
|
|
389
|
-
- 持ち物は `items`(ちょうど16個)で、文言は `messages` で変えられます。英語の文言は `en` です。
|
|
390
|
-
- 自分のサーバーに保存したいときは、`checked` と `onChange` を使って親側で状態を持ちます([Save progress on your server](#save-progress-on-your-server))。
|
|
391
|
-
- Next.jsのApp Routerでは、Server Componentのページにそのまま置けます(`items` や `messages={en}` も渡せます)。ただし、`onChange` などのコールバックは渡せないので、使うときは `"use client"` を付けた自分のコンポーネントで包んでください。
|
|
392
|
-
- 配色はCSS変数(`--pkb-sun` など)で変えられます。Google Fontsを読み込みたくないときは `fonts={false}` にします。
|
|
393
|
-
- CSP(Content Security Policy)を設定している場合は、`style-src 'unsafe-inline'` が必要です。
|
|
394
|
-
|
|
395
|
-
詳しい仕様は [API](#api) と [Integration notes](#integration-notes) にあります。
|
|
503
|
+
Try it on the [demo site](https://jin-hiroo.github.io/packing-bingo/); runnable example projects (Next.js, Vite + React, plain HTML) are in [`examples/`](examples/). The default UI text is Japanese; `en` is a complete English set. Pass your own `items` (`{ emoji, label }`, row by row): 16 for 4×4, size² in general, or size² − 1 with `free` (the FREE square is skipped, also in `checked`). Without `size`, the number of items sets it. `bingoLines(size, free)` gives the winning lines as item indices. The sections above cover the props, events and integration notes; the code snippets and type names can be read as they are.
|
|
396
504
|
|
|
397
|
-
##
|
|
505
|
+
## ライセンス
|
|
398
506
|
|
|
399
507
|
[MIT](LICENSE) © 2026 Jin-hiroo.
|
|
400
508
|
|
|
401
|
-
|
|
509
|
+
Dela Gothic OneとM PLUS Rounded 1cのフォントはGoogle Fontsから読み込んでおり、SIL Open Font Licenseで提供されています。このリポジトリには含まれていません。
|