@stamprally/ui 0.2.1 → 0.4.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/dist/index.cjs CHANGED
@@ -5,6 +5,59 @@ var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  // src/index.tsx
8
+ function useFocusTrap(active, onClose, closeDisabled = false) {
9
+ const containerRef = react.useRef(null);
10
+ react.useEffect(() => {
11
+ if (!active) return;
12
+ const container = containerRef.current;
13
+ if (container === null) return;
14
+ const focusableSelector = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
15
+ const focusable = () => Array.from(container.querySelectorAll(focusableSelector));
16
+ const first = focusable()[0];
17
+ first?.focus();
18
+ const handleKeyDown = (event) => {
19
+ if (event.key === "Escape") {
20
+ if (!closeDisabled) {
21
+ event.preventDefault();
22
+ onClose?.();
23
+ }
24
+ return;
25
+ }
26
+ if (event.key !== "Tab") return;
27
+ const elements = focusable();
28
+ if (elements.length === 0) {
29
+ event.preventDefault();
30
+ container.focus();
31
+ return;
32
+ }
33
+ const firstElement = elements[0];
34
+ const lastElement = elements[elements.length - 1];
35
+ if (event.shiftKey && document.activeElement === firstElement) {
36
+ event.preventDefault();
37
+ lastElement?.focus();
38
+ } else if (!event.shiftKey && document.activeElement === lastElement) {
39
+ event.preventDefault();
40
+ firstElement?.focus();
41
+ }
42
+ };
43
+ document.addEventListener("keydown", handleKeyDown);
44
+ return () => document.removeEventListener("keydown", handleKeyDown);
45
+ }, [active, closeDisabled, onClose]);
46
+ return containerRef;
47
+ }
48
+ var FONT_STACKS = {
49
+ "system-ui": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
50
+ serif: 'Georgia, "Times New Roman", "Yu Mincho", serif',
51
+ "rounded-sans": '"Arial Rounded MT Bold", "Hiragino Maru Gothic ProN", "Yu Gothic", sans-serif',
52
+ monospace: 'ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace',
53
+ handwritten: '"Segoe Print", "Bradley Hand", "Comic Sans MS", cursive'
54
+ };
55
+ function formatStampDate(value) {
56
+ const date = new Date(value);
57
+ if (Number.isNaN(date.getTime())) return value;
58
+ const pad = (part) => String(part).padStart(2, "0");
59
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
60
+ }
8
61
  function StampSlot({
9
62
  stamp,
10
63
  record,
@@ -15,9 +68,12 @@ function StampSlot({
15
68
  disabled = false,
16
69
  slotShape = "rounded",
17
70
  locale = "ja",
71
+ statusText,
18
72
  onSelect
19
73
  }) {
20
74
  const status = record === void 0 ? isNext ? "available" : "locked" : "stamped";
75
+ const resolvedStatus = statusText ?? status;
76
+ const statusSeparator = statusText !== void 0 && locale === "ja" ? "\u3001" : ", ";
21
77
  const name = core.resolveLocalizedText(stamp.name, locale);
22
78
  const label = presentation.label ?? stamp.condition.type;
23
79
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -26,16 +82,38 @@ function StampSlot({
26
82
  type: "button",
27
83
  disabled,
28
84
  onClick: onSelect,
29
- "aria-label": `${slotNumber}. ${name} (${status})`,
85
+ "aria-label": `#${String(slotNumber).padStart(2, "0")} ${name}${statusSeparator}${resolvedStatus}`,
86
+ "aria-disabled": disabled,
30
87
  "data-status": status,
31
88
  "data-shape": slotShape,
32
- className: `stamp-slot stamp-slot--${status}${isAnimating ? " stamp-slot--animating" : ""}`,
89
+ className: `stamp-slot stamp-slot--${status} stamp-slot--${presentation.ink ?? "vermilion"} stamp-slot--shape-${slotShape}${isAnimating ? " stamp-slot--animating" : ""}`,
33
90
  style: { "--stamp-primary": "var(--stamprally-primary, #9e551e)" },
34
91
  children: [
35
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
36
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: name }),
37
- /* @__PURE__ */ jsxRuntime.jsx("small", { children: label }),
38
- record !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: record.acquiredAt, children: record.acquiredAt })
92
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__topline", children: [
93
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__number", children: [
94
+ "#",
95
+ String(slotNumber).padStart(2, "0")
96
+ ] }),
97
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `stamp-slot__state stamp-slot__state--${status}`, children: resolvedStatus })
98
+ ] }),
99
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-slot__watermark", "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
100
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-slot__name", children: name }),
101
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__condition", children: [
102
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
103
+ label
104
+ ] }),
105
+ record !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
106
+ "span",
107
+ {
108
+ className: `stamp-imprint ${isAnimating ? "stamp-press" : ""}`,
109
+ role: "img",
110
+ "aria-label": `Acquired at ${formatStampDate(record.acquiredAt)}`,
111
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-imprint__inner", children: [
112
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-imprint__word", children: "STAMP" }),
113
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-imprint__date", children: formatStampDate(record.acquiredAt) })
114
+ ] })
115
+ }
116
+ )
39
117
  ]
40
118
  }
41
119
  );
@@ -58,54 +136,96 @@ function StampSheet({
58
136
  const total = progress?.total ?? config.stamps.length;
59
137
  const percentage = progress?.percentage ?? (total === 0 ? 0 : count / total * 100);
60
138
  const style = {
61
- "--stamprally-primary": theme.primaryColor,
62
- "--stamprally-background": theme.backgroundColor ?? "transparent",
63
- "--stamprally-card": theme.cardBackgroundColor,
64
- "--stamprally-text": theme.textColor,
139
+ "--stamp-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
140
+ "--stamp-bg": `var(--stamprally-background-override, ${theme.backgroundColor ?? core.DEFAULT_SHEET_THEME.backgroundColor ?? "#ffffff"})`,
141
+ "--stamp-card-bg": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
142
+ "--stamp-text": `var(--stamprally-text-override, ${theme.textColor})`,
143
+ "--stamp-grid-cols": String(theme.gridColumns),
144
+ "--stamp-grid-cols-mobile": String(Math.min(theme.gridColumns, 2)),
145
+ "--stamp-unclaimed-opacity": String(theme.unclaimedOpacity ?? 1),
146
+ "--stamp-font-family": FONT_STACKS[theme.fontFamily ?? "serif"],
147
+ ...theme.completedStampColor === void 0 ? {} : { "--stamp-completed-color": theme.completedStampColor },
148
+ "--stamprally-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
149
+ "--stamprally-background": `var(--stamprally-background-override, ${theme.backgroundColor ?? "transparent"})`,
150
+ "--stamprally-card": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
151
+ "--stamprally-text": `var(--stamprally-text-override, ${theme.textColor})`,
65
152
  "--stamprally-columns": String(theme.gridColumns)
66
153
  };
154
+ const messages = locale === "en" ? {
155
+ available: "available",
156
+ locked: "locked",
157
+ stamped: "stamped",
158
+ remaining: (value) => `${value} spots remaining`,
159
+ completed: "RALLY COMPLETED"
160
+ } : {
161
+ available: "\u53D6\u5F97\u53EF\u80FD",
162
+ locked: "\u9806\u5E8F\u5F85\u3061",
163
+ stamped: "\u53D6\u5F97\u6E08\u307F",
164
+ remaining: (value) => `\u3042\u3068${value}\u7B87\u6240`,
165
+ completed: "\u30E9\u30EA\u30FC\u9054\u6210"
166
+ };
67
167
  return /* @__PURE__ */ jsxRuntime.jsxs(
68
168
  "section",
69
169
  {
70
- className: "stamprally-sheet",
170
+ className: `stamp-sheet ${percentage >= 100 ? "stamp-sheet--completed" : ""}`,
71
171
  style,
72
172
  "aria-label": core.resolveLocalizedText(title, locale) || config.id,
73
173
  children: [
74
- /* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
174
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "stamp-sheet__header", children: [
75
175
  /* @__PURE__ */ jsxRuntime.jsx("h2", { children: core.resolveLocalizedText(title, locale) || config.id }),
76
- /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-live": "polite", children: [
176
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-sheet__score", role: "status", "aria-live": "polite", children: [
77
177
  count,
78
178
  " / ",
79
179
  total
80
180
  ] })
81
181
  ] }),
182
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stamp-sheet__progress-label", children: [
183
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: messages.remaining(Math.max(0, total - count)) }),
184
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
185
+ Math.round(percentage),
186
+ "%"
187
+ ] })
188
+ ] }),
82
189
  /* @__PURE__ */ jsxRuntime.jsx(
83
190
  "div",
84
191
  {
192
+ className: "stamp-sheet__progress-track",
85
193
  role: "progressbar",
86
194
  "aria-valuemin": 0,
87
195
  "aria-valuemax": 100,
88
196
  "aria-valuenow": Math.round(percentage),
89
- "aria-label": "Rally progress",
90
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "block", width: `${percentage}%` } })
197
+ "aria-label": locale === "en" ? "Rally progress" : "\u30B9\u30BF\u30F3\u30D7\u53D6\u5F97\u9032\u6357",
198
+ children: /* @__PURE__ */ jsxRuntime.jsx(
199
+ "span",
200
+ {
201
+ className: "stamp-sheet__progress-fill",
202
+ style: { display: "block", width: `${percentage}%` }
203
+ }
204
+ )
91
205
  }
92
206
  ),
93
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamprally-sheet__grid", children: config.stamps.map((stamp, index) => /* @__PURE__ */ jsxRuntime.jsx(
94
- StampSlot,
95
- {
96
- stamp,
97
- ...state?.records.find((record) => record.stampId === stamp.id) === void 0 ? {} : { record: state?.records.find((record) => record.stampId === stamp.id) },
98
- isNext: next.has(stamp.id),
99
- slotNumber: index + 1,
100
- ...presentations[stamp.id] === void 0 ? {} : { presentation: presentations[stamp.id] },
101
- isAnimating: animatedStampId === stamp.id,
102
- disabled,
103
- slotShape: theme.slotShape,
104
- locale,
105
- onSelect: () => onStampSelect?.(stamp.id)
106
- },
107
- stamp.id
108
- )) })
207
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__grid", children: config.stamps.map((stamp, index) => {
208
+ const recordForStamp = state?.records.find((record) => record.stampId === stamp.id);
209
+ return /* @__PURE__ */ jsxRuntime.jsx(
210
+ StampSlot,
211
+ {
212
+ stamp,
213
+ ...recordForStamp === void 0 ? {} : { record: recordForStamp },
214
+ isNext: next.has(stamp.id),
215
+ slotNumber: index + 1,
216
+ ...presentations[stamp.id] === void 0 ? {} : { presentation: presentations[stamp.id] },
217
+ isAnimating: animatedStampId === stamp.id,
218
+ disabled,
219
+ slotShape: theme.slotShape,
220
+ locale,
221
+ statusText: recordForStamp === void 0 ? next.has(stamp.id) ? messages.available : messages.locked : messages.stamped,
222
+ onSelect: () => onStampSelect?.(stamp.id)
223
+ },
224
+ stamp.id
225
+ );
226
+ }) }),
227
+ percentage >= 100 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__complete-mark", role: "status", "aria-live": "polite", children: "COMPLETE!!" }),
228
+ percentage >= 100 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__complete-message", children: messages.completed })
109
229
  ]
110
230
  }
111
231
  );
@@ -123,6 +243,7 @@ function StampModal({
123
243
  onNotify
124
244
  }) {
125
245
  const [token, setToken] = react.useState("");
246
+ const modalRef = useFocusTrap(open && stamp !== null, onClose, isPending);
126
247
  react.useEffect(() => {
127
248
  if (!open) setToken("");
128
249
  }, [open]);
@@ -143,8 +264,18 @@ function StampModal({
143
264
  "aria-modal": "true",
144
265
  "aria-labelledby": "stamprally-modal-title",
145
266
  className: "stamprally-modal",
146
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stamprally-modal__panel", children: [
147
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, disabled: isPending, children: "\xD7" }),
267
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stamprally-modal__panel", ref: modalRef, tabIndex: -1, children: [
268
+ /* @__PURE__ */ jsxRuntime.jsx(
269
+ "button",
270
+ {
271
+ type: "button",
272
+ "aria-label": "Close stamp details",
273
+ "aria-disabled": isPending,
274
+ onClick: onClose,
275
+ disabled: isPending,
276
+ children: "\xD7"
277
+ }
278
+ ),
148
279
  /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "stamprally-modal-title", children: name }),
149
280
  record !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Already claimed." }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: core.resolveLocalizedText(stamp.description, locale) }),
150
281
  stamp.condition.type === "token" ? /* @__PURE__ */ jsxRuntime.jsxs(
@@ -159,13 +290,24 @@ function StampModal({
159
290
  "Passcode ",
160
291
  /* @__PURE__ */ jsxRuntime.jsx("input", { value: token, onChange: (event) => setToken(event.target.value) })
161
292
  ] }),
162
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: !isAvailable || isPending, children: "Claim stamp" })
293
+ /* @__PURE__ */ jsxRuntime.jsx(
294
+ "button",
295
+ {
296
+ type: "submit",
297
+ "aria-label": "Claim stamp",
298
+ "aria-disabled": !isAvailable || isPending,
299
+ disabled: !isAvailable || isPending,
300
+ children: "Claim stamp"
301
+ }
302
+ )
163
303
  ]
164
304
  }
165
305
  ) : /* @__PURE__ */ jsxRuntime.jsx(
166
306
  "button",
167
307
  {
168
308
  type: "button",
309
+ "aria-label": "Claim stamp",
310
+ "aria-disabled": !isAvailable || isPending,
169
311
  onClick: () => void submit({ type: "instant" }),
170
312
  disabled: !isAvailable || isPending,
171
313
  children: "Claim stamp"
@@ -180,66 +322,105 @@ function RewardPanel({
180
322
  states,
181
323
  locale = "ja",
182
324
  isPending = false,
325
+ open = true,
326
+ onClose,
183
327
  onRedeem,
184
328
  onNotify
185
329
  }) {
186
330
  const [passcodes, setPasscodes] = react.useState({});
187
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-labelledby": "stamprally-rewards-title", className: "stamprally-rewards", children: [
188
- /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "stamprally-rewards-title", children: "Rewards" }),
189
- rewards.map((reward) => {
190
- const state = states.find((item) => item.rewardId === reward.id);
191
- const available = state?.status === "AVAILABLE";
192
- const redeem = async (options) => {
193
- const result = await onRedeem(reward.id, options);
194
- onNotify?.(result.ok, result.ok ? "Reward claimed." : result.error.code);
195
- };
196
- return /* @__PURE__ */ jsxRuntime.jsxs(
197
- "article",
198
- {
199
- className: `stamprally-reward stamprally-reward--${state?.status ?? "LOCKED"}`,
200
- children: [
201
- /* @__PURE__ */ jsxRuntime.jsx("h3", { children: core.resolveLocalizedText(reward.title, locale) }),
202
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: core.resolveLocalizedText(reward.description, locale) }),
203
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state?.status ?? "LOCKED" }),
204
- reward.redemptionMethod === "staff_passcode" && /* @__PURE__ */ jsxRuntime.jsxs(
205
- "form",
206
- {
207
- onSubmit: (event) => {
208
- event.preventDefault();
209
- void redeem({ passcode: passcodes[reward.id] ?? "" });
210
- },
211
- children: [
212
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
213
- "Staff passcode",
214
- " ",
215
- /* @__PURE__ */ jsxRuntime.jsx(
216
- "input",
217
- {
218
- type: "password",
219
- value: passcodes[reward.id] ?? "",
220
- onChange: (event) => setPasscodes((current) => ({ ...current, [reward.id]: event.target.value }))
221
- }
222
- )
223
- ] }),
224
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: !available || isPending, children: "Redeem" })
225
- ]
226
- }
227
- ),
228
- reward.redemptionMethod !== "staff_passcode" && /* @__PURE__ */ jsxRuntime.jsx(
229
- "button",
230
- {
231
- type: "button",
232
- disabled: !available || isPending,
233
- onClick: () => void redeem(),
234
- children: "Redeem"
235
- }
236
- )
237
- ]
238
- },
239
- reward.id
240
- );
241
- })
242
- ] });
331
+ const panelRef = useFocusTrap(open && onClose !== void 0, onClose, isPending);
332
+ if (!open) return null;
333
+ return /* @__PURE__ */ jsxRuntime.jsxs(
334
+ "section",
335
+ {
336
+ ref: panelRef,
337
+ role: "dialog",
338
+ "aria-labelledby": "stamprally-rewards-title",
339
+ "aria-modal": "true",
340
+ className: "stamprally-rewards",
341
+ tabIndex: -1,
342
+ children: [
343
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "stamprally-rewards__header", children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "stamprally-rewards-title", children: "Rewards" }),
345
+ onClose !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
346
+ "button",
347
+ {
348
+ type: "button",
349
+ "aria-label": "Close rewards",
350
+ "aria-disabled": isPending,
351
+ disabled: isPending,
352
+ onClick: onClose,
353
+ children: "\xD7"
354
+ }
355
+ )
356
+ ] }),
357
+ rewards.map((reward) => {
358
+ const state = states.find((item) => item.rewardId === reward.id);
359
+ const available = state?.status === "AVAILABLE";
360
+ const redeem = async (options) => {
361
+ const result = await onRedeem(reward.id, options);
362
+ onNotify?.(result.ok, result.ok ? "Reward claimed." : result.error.code);
363
+ };
364
+ return /* @__PURE__ */ jsxRuntime.jsxs(
365
+ "article",
366
+ {
367
+ className: `stamprally-reward stamprally-reward--${state?.status ?? "LOCKED"}`,
368
+ children: [
369
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: core.resolveLocalizedText(reward.title, locale) }),
370
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: core.resolveLocalizedText(reward.description, locale) }),
371
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", children: state?.status ?? "LOCKED" }),
372
+ reward.redemptionMethod === "staff_passcode" && /* @__PURE__ */ jsxRuntime.jsxs(
373
+ "form",
374
+ {
375
+ onSubmit: (event) => {
376
+ event.preventDefault();
377
+ void redeem({ passcode: passcodes[reward.id] ?? "" });
378
+ },
379
+ children: [
380
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
381
+ "Staff passcode",
382
+ " ",
383
+ /* @__PURE__ */ jsxRuntime.jsx(
384
+ "input",
385
+ {
386
+ type: "password",
387
+ value: passcodes[reward.id] ?? "",
388
+ onChange: (event) => setPasscodes((current) => ({ ...current, [reward.id]: event.target.value }))
389
+ }
390
+ )
391
+ ] }),
392
+ /* @__PURE__ */ jsxRuntime.jsx(
393
+ "button",
394
+ {
395
+ type: "submit",
396
+ "aria-label": `Redeem ${core.resolveLocalizedText(reward.title, locale)}`,
397
+ "aria-disabled": !available || isPending,
398
+ disabled: !available || isPending,
399
+ children: "Redeem"
400
+ }
401
+ )
402
+ ]
403
+ }
404
+ ),
405
+ reward.redemptionMethod !== "staff_passcode" && /* @__PURE__ */ jsxRuntime.jsx(
406
+ "button",
407
+ {
408
+ type: "button",
409
+ "aria-label": `Redeem ${core.resolveLocalizedText(reward.title, locale)}`,
410
+ "aria-disabled": !available || isPending,
411
+ disabled: !available || isPending,
412
+ onClick: () => void redeem(),
413
+ children: "Redeem"
414
+ }
415
+ )
416
+ ]
417
+ },
418
+ reward.id
419
+ );
420
+ })
421
+ ]
422
+ }
423
+ );
243
424
  }
244
425
 
245
426
  exports.RewardPanel = RewardPanel;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":["resolveLocalizedText","jsxs","jsx","DEFAULT_SHEET_THEME","useState","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n type ConsumeResult,\n DEFAULT_SHEET_THEME,\n type LocalizedText,\n type RallyConfig,\n type RewardItem,\n type RewardState,\n resolveLocalizedText,\n type SheetTheme,\n type SlotShape,\n type SpotItem,\n type StampRallyProgress,\n type StampRallyState,\n type SupportedLocale,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type { CSSProperties, FormEvent } from \"react\";\nimport { useEffect, useState } from \"react\";\n\nexport interface StampPresentation {\n readonly label?: string;\n readonly icon?: string;\n readonly ink?: \"vermilion\" | \"indigo\";\n}\n\nexport interface StampSlotProps {\n readonly stamp: SpotItem;\n readonly record?: StampRallyState[\"records\"][number] | undefined;\n readonly isNext?: boolean;\n readonly slotNumber?: number;\n readonly presentation?: StampPresentation | undefined;\n readonly isAnimating?: boolean;\n readonly disabled?: boolean;\n readonly slotShape?: SlotShape;\n readonly locale?: SupportedLocale;\n readonly onSelect?: () => void;\n}\n\nexport function StampSlot({\n stamp,\n record,\n isNext = false,\n slotNumber = 1,\n presentation = {},\n isAnimating = false,\n disabled = false,\n slotShape = \"rounded\",\n locale = \"ja\",\n onSelect,\n}: StampSlotProps) {\n const status = record === undefined ? (isNext ? \"available\" : \"locked\") : \"stamped\";\n const name = resolveLocalizedText(stamp.name, locale);\n const label = presentation.label ?? stamp.condition.type;\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onSelect}\n aria-label={`${slotNumber}. ${name} (${status})`}\n data-status={status}\n data-shape={slotShape}\n className={`stamp-slot stamp-slot--${status}${isAnimating ? \" stamp-slot--animating\" : \"\"}`}\n style={{ \"--stamp-primary\": \"var(--stamprally-primary, #9e551e)\" } as CSSProperties}\n >\n <span aria-hidden=\"true\">{presentation.icon ?? \"✦\"}</span>\n <strong>{name}</strong>\n <small>{label}</small>\n {record !== undefined && <time dateTime={record.acquiredAt}>{record.acquiredAt}</time>}\n </button>\n );\n}\n\nexport interface StampSheetProps {\n readonly title?: LocalizedText;\n readonly config: RallyConfig;\n readonly state?: StampRallyState | null;\n readonly progress?: StampRallyProgress;\n readonly presentations?: Readonly<Record<string, StampPresentation>>;\n readonly animatedStampId?: string | null;\n readonly disabled?: boolean;\n readonly locale?: SupportedLocale;\n readonly theme?: SheetTheme;\n readonly onStampSelect?: (stampId: string) => void;\n}\n\nexport function StampSheet({\n title,\n config,\n state = null,\n progress,\n presentations = {},\n animatedStampId = null,\n disabled = false,\n locale = \"ja\",\n theme = config.theme ?? DEFAULT_SHEET_THEME,\n onStampSelect,\n}: StampSheetProps) {\n const acquired = new Set(state?.records.map((record) => record.stampId) ?? []);\n const next = new Set(progress?.nextAvailableStamps.map((stamp) => stamp.id) ?? []);\n const count = progress?.acquired ?? acquired.size;\n const total = progress?.total ?? config.stamps.length;\n const percentage = progress?.percentage ?? (total === 0 ? 0 : (count / total) * 100);\n const style = {\n \"--stamprally-primary\": theme.primaryColor,\n \"--stamprally-background\": theme.backgroundColor ?? \"transparent\",\n \"--stamprally-card\": theme.cardBackgroundColor,\n \"--stamprally-text\": theme.textColor,\n \"--stamprally-columns\": String(theme.gridColumns),\n } as CSSProperties;\n return (\n <section\n className=\"stamprally-sheet\"\n style={style}\n aria-label={resolveLocalizedText(title, locale) || config.id}\n >\n <header>\n <h2>{resolveLocalizedText(title, locale) || config.id}</h2>\n <span aria-live=\"polite\">\n {count} / {total}\n </span>\n </header>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percentage)}\n aria-label=\"Rally progress\"\n >\n <span style={{ display: \"block\", width: `${percentage}%` }} />\n </div>\n <div className=\"stamprally-sheet__grid\">\n {config.stamps.map((stamp, index) => (\n <StampSlot\n key={stamp.id}\n stamp={stamp}\n {...(state?.records.find((record) => record.stampId === stamp.id) === undefined\n ? {}\n : { record: state?.records.find((record) => record.stampId === stamp.id) })}\n isNext={next.has(stamp.id)}\n slotNumber={index + 1}\n {...(presentations[stamp.id] === undefined\n ? {}\n : { presentation: presentations[stamp.id] })}\n isAnimating={animatedStampId === stamp.id}\n disabled={disabled}\n slotShape={theme.slotShape}\n locale={locale}\n onSelect={() => onStampSelect?.(stamp.id)}\n />\n ))}\n </div>\n </section>\n );\n}\n\nexport interface AcquisitionFeedback {\n readonly ok: boolean;\n readonly message: string;\n}\nexport interface StampModalProps {\n readonly open: boolean;\n readonly stamp: SpotItem | null;\n readonly record?: StampRallyState[\"records\"][number] | undefined;\n readonly presentation?: StampPresentation | null;\n readonly requiredStampName?: string | null;\n readonly isAvailable?: boolean;\n readonly isPending?: boolean;\n readonly isCompleted?: boolean;\n readonly locale?: SupportedLocale;\n readonly onClose: () => void;\n readonly onAcquire?: (\n stampId: string,\n context: VerificationContext,\n ) => Promise<AcquisitionFeedback>;\n readonly onCheckIn?: (\n stampId: string,\n context: VerificationContext,\n ) => Promise<AcquisitionFeedback>;\n readonly onNotify?: (feedback: AcquisitionFeedback) => void;\n}\n\nexport function StampModal({\n open,\n stamp,\n record,\n isAvailable = true,\n isPending = false,\n locale = \"ja\",\n onClose,\n onAcquire,\n onCheckIn,\n onNotify,\n}: StampModalProps) {\n const [token, setToken] = useState(\"\");\n useEffect(() => {\n if (!open) setToken(\"\");\n }, [open]);\n if (!open || stamp === null) return null;\n const submit = async (context: VerificationContext): Promise<void> => {\n if (!isAvailable || record !== undefined) return;\n const handler = onAcquire ?? onCheckIn;\n if (handler === undefined) return;\n const feedback = await handler(stamp.id, context);\n onNotify?.(feedback);\n if (feedback.ok) onClose();\n };\n const name = resolveLocalizedText(stamp.name, locale);\n return (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"stamprally-modal-title\"\n className=\"stamprally-modal\"\n >\n <div className=\"stamprally-modal__panel\">\n <button type=\"button\" aria-label=\"Close\" onClick={onClose} disabled={isPending}>\n ×\n </button>\n <h2 id=\"stamprally-modal-title\">{name}</h2>\n {record !== undefined ? (\n <p>Already claimed.</p>\n ) : (\n <p>{resolveLocalizedText(stamp.description, locale)}</p>\n )}\n {stamp.condition.type === \"token\" ? (\n <form\n onSubmit={(event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n void submit({ type: \"token\", token });\n }}\n >\n <label>\n Passcode <input value={token} onChange={(event) => setToken(event.target.value)} />\n </label>\n <button type=\"submit\" disabled={!isAvailable || isPending}>\n Claim stamp\n </button>\n </form>\n ) : (\n <button\n type=\"button\"\n onClick={() => void submit({ type: \"instant\" })}\n disabled={!isAvailable || isPending}\n >\n Claim stamp\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport interface RewardPanelProps {\n readonly rewards: ReadonlyArray<RewardItem>;\n readonly states: ReadonlyArray<RewardState>;\n readonly locale?: SupportedLocale;\n readonly isPending?: boolean;\n readonly onRedeem: (\n rewardId: string,\n options?: { readonly passcode?: string; readonly staffId?: string },\n ) => Promise<ConsumeResult>;\n readonly onNotify?: (ok: boolean, message: string) => void;\n}\n\nexport function RewardPanel({\n rewards,\n states,\n locale = \"ja\",\n isPending = false,\n onRedeem,\n onNotify,\n}: RewardPanelProps) {\n const [passcodes, setPasscodes] = useState<Readonly<Record<string, string>>>({});\n return (\n <section aria-labelledby=\"stamprally-rewards-title\" className=\"stamprally-rewards\">\n <h2 id=\"stamprally-rewards-title\">Rewards</h2>\n {rewards.map((reward) => {\n const state = states.find((item) => item.rewardId === reward.id);\n const available = state?.status === \"AVAILABLE\";\n const redeem = async (options?: { readonly passcode?: string }): Promise<void> => {\n const result = await onRedeem(reward.id, options);\n onNotify?.(result.ok, result.ok ? \"Reward claimed.\" : result.error.code);\n };\n return (\n <article\n key={reward.id}\n className={`stamprally-reward stamprally-reward--${state?.status ?? \"LOCKED\"}`}\n >\n <h3>{resolveLocalizedText(reward.title, locale)}</h3>\n <p>{resolveLocalizedText(reward.description, locale)}</p>\n <span>{state?.status ?? \"LOCKED\"}</span>\n {reward.redemptionMethod === \"staff_passcode\" && (\n <form\n onSubmit={(event) => {\n event.preventDefault();\n void redeem({ passcode: passcodes[reward.id] ?? \"\" });\n }}\n >\n <label>\n Staff passcode{\" \"}\n <input\n type=\"password\"\n value={passcodes[reward.id] ?? \"\"}\n onChange={(event) =>\n setPasscodes((current) => ({ ...current, [reward.id]: event.target.value }))\n }\n />\n </label>\n <button type=\"submit\" disabled={!available || isPending}>\n Redeem\n </button>\n </form>\n )}\n {reward.redemptionMethod !== \"staff_passcode\" && (\n <button\n type=\"button\"\n disabled={!available || isPending}\n onClick={() => void redeem()}\n >\n Redeem\n </button>\n )}\n </article>\n );\n })}\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["useRef","useEffect","resolveLocalizedText","jsxs","jsx","DEFAULT_SHEET_THEME","useState"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n type ConsumeResult,\n DEFAULT_SHEET_THEME,\n type LocalizedText,\n type RallyConfig,\n type RewardItem,\n type RewardState,\n resolveLocalizedText,\n type SheetTheme,\n type SlotShape,\n type SpotItem,\n type StampRallyProgress,\n type StampRallyState,\n type SupportedLocale,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type { CSSProperties, FormEvent, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nfunction useFocusTrap(\n active: boolean,\n onClose: (() => void) | undefined,\n closeDisabled = false,\n): RefObject<HTMLDivElement | null> {\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (container === null) return;\n\n const focusableSelector =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const focusable = (): ReadonlyArray<HTMLElement> =>\n Array.from(container.querySelectorAll<HTMLElement>(focusableSelector));\n const first = focusable()[0];\n first?.focus();\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") {\n if (!closeDisabled) {\n event.preventDefault();\n onClose?.();\n }\n return;\n }\n if (event.key !== \"Tab\") return;\n\n const elements = focusable();\n if (elements.length === 0) {\n event.preventDefault();\n container.focus();\n return;\n }\n const firstElement = elements[0];\n const lastElement = elements[elements.length - 1];\n if (event.shiftKey && document.activeElement === firstElement) {\n event.preventDefault();\n lastElement?.focus();\n } else if (!event.shiftKey && document.activeElement === lastElement) {\n event.preventDefault();\n firstElement?.focus();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [active, closeDisabled, onClose]);\n\n return containerRef;\n}\n\nexport interface StampPresentation {\n readonly label?: string;\n readonly icon?: string;\n readonly ink?: \"vermilion\" | \"indigo\";\n readonly channel?: \"instant\" | \"qr\" | \"nfc\" | \"geo\";\n}\n\nexport interface StampSlotProps {\n readonly stamp: SpotItem;\n readonly record?: StampRallyState[\"records\"][number] | undefined;\n readonly isNext?: boolean;\n readonly slotNumber?: number;\n readonly presentation?: StampPresentation | undefined;\n readonly isAnimating?: boolean;\n readonly disabled?: boolean;\n readonly slotShape?: SlotShape;\n readonly locale?: SupportedLocale;\n readonly statusText?: string;\n readonly onSelect?: () => void;\n}\n\nconst FONT_STACKS: Readonly<Record<NonNullable<SheetTheme[\"fontFamily\"]>, string>> = {\n \"system-ui\": 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif',\n serif: 'Georgia, \"Times New Roman\", \"Yu Mincho\", serif',\n \"rounded-sans\": '\"Arial Rounded MT Bold\", \"Hiragino Maru Gothic ProN\", \"Yu Gothic\", sans-serif',\n monospace: 'ui-monospace, SFMono-Regular, Consolas, \"Liberation Mono\", monospace',\n handwritten: '\"Segoe Print\", \"Bradley Hand\", \"Comic Sans MS\", cursive',\n};\n\nfunction formatStampDate(value: string): string {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n const pad = (part: number) => String(part).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\nexport function StampSlot({\n stamp,\n record,\n isNext = false,\n slotNumber = 1,\n presentation = {},\n isAnimating = false,\n disabled = false,\n slotShape = \"rounded\",\n locale = \"ja\",\n statusText,\n onSelect,\n}: StampSlotProps) {\n const status = record === undefined ? (isNext ? \"available\" : \"locked\") : \"stamped\";\n const resolvedStatus = statusText ?? status;\n const statusSeparator = statusText !== undefined && locale === \"ja\" ? \"、\" : \", \";\n const name = resolveLocalizedText(stamp.name, locale);\n const label = presentation.label ?? stamp.condition.type;\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onSelect}\n aria-label={`#${String(slotNumber).padStart(2, \"0\")} ${name}${statusSeparator}${resolvedStatus}`}\n aria-disabled={disabled}\n data-status={status}\n data-shape={slotShape}\n className={`stamp-slot stamp-slot--${status} stamp-slot--${presentation.ink ?? \"vermilion\"} stamp-slot--shape-${slotShape}${isAnimating ? \" stamp-slot--animating\" : \"\"}`}\n style={{ \"--stamp-primary\": \"var(--stamprally-primary, #9e551e)\" } as CSSProperties}\n >\n <span className=\"stamp-slot__topline\">\n <span className=\"stamp-slot__number\">#{String(slotNumber).padStart(2, \"0\")}</span>\n <span className={`stamp-slot__state stamp-slot__state--${status}`}>{resolvedStatus}</span>\n </span>\n <span className=\"stamp-slot__watermark\" aria-hidden=\"true\">\n {presentation.icon ?? \"✦\"}\n </span>\n <span className=\"stamp-slot__name\">{name}</span>\n <span className=\"stamp-slot__condition\">\n <span aria-hidden=\"true\">{presentation.icon ?? \"✦\"}</span>\n {label}\n </span>\n {record !== undefined && (\n <span\n className={`stamp-imprint ${isAnimating ? \"stamp-press\" : \"\"}`}\n role=\"img\"\n aria-label={`Acquired at ${formatStampDate(record.acquiredAt)}`}\n >\n <span className=\"stamp-imprint__inner\">\n <span className=\"stamp-imprint__word\">STAMP</span>\n <span className=\"stamp-imprint__date\">{formatStampDate(record.acquiredAt)}</span>\n </span>\n </span>\n )}\n </button>\n );\n}\n\nexport interface StampSheetProps {\n readonly title?: LocalizedText;\n readonly config: RallyConfig;\n readonly state?: StampRallyState | null;\n readonly progress?: StampRallyProgress;\n readonly presentations?: Readonly<Record<string, StampPresentation>>;\n readonly animatedStampId?: string | null;\n readonly disabled?: boolean;\n readonly locale?: SupportedLocale;\n readonly theme?: SheetTheme;\n readonly onStampSelect?: (stampId: string) => void;\n}\n\nexport function StampSheet({\n title,\n config,\n state = null,\n progress,\n presentations = {},\n animatedStampId = null,\n disabled = false,\n locale = \"ja\",\n theme = config.theme ?? DEFAULT_SHEET_THEME,\n onStampSelect,\n}: StampSheetProps) {\n const acquired = new Set(state?.records.map((record) => record.stampId) ?? []);\n const next = new Set(progress?.nextAvailableStamps.map((stamp) => stamp.id) ?? []);\n const count = progress?.acquired ?? acquired.size;\n const total = progress?.total ?? config.stamps.length;\n const percentage = progress?.percentage ?? (total === 0 ? 0 : (count / total) * 100);\n const style = {\n \"--stamp-primary\": `var(--stamprally-primary-override, ${theme.primaryColor})`,\n \"--stamp-bg\": `var(--stamprally-background-override, ${theme.backgroundColor ?? DEFAULT_SHEET_THEME.backgroundColor ?? \"#ffffff\"})`,\n \"--stamp-card-bg\": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,\n \"--stamp-text\": `var(--stamprally-text-override, ${theme.textColor})`,\n \"--stamp-grid-cols\": String(theme.gridColumns),\n \"--stamp-grid-cols-mobile\": String(Math.min(theme.gridColumns, 2)),\n \"--stamp-unclaimed-opacity\": String(theme.unclaimedOpacity ?? 1),\n \"--stamp-font-family\": FONT_STACKS[theme.fontFamily ?? \"serif\"],\n ...(theme.completedStampColor === undefined\n ? {}\n : { \"--stamp-completed-color\": theme.completedStampColor }),\n \"--stamprally-primary\": `var(--stamprally-primary-override, ${theme.primaryColor})`,\n \"--stamprally-background\": `var(--stamprally-background-override, ${theme.backgroundColor ?? \"transparent\"})`,\n \"--stamprally-card\": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,\n \"--stamprally-text\": `var(--stamprally-text-override, ${theme.textColor})`,\n \"--stamprally-columns\": String(theme.gridColumns),\n } as CSSProperties;\n const messages =\n locale === \"en\"\n ? {\n available: \"available\",\n locked: \"locked\",\n stamped: \"stamped\",\n remaining: (value: number) => `${value} spots remaining`,\n completed: \"RALLY COMPLETED\",\n }\n : {\n available: \"取得可能\",\n locked: \"順序待ち\",\n stamped: \"取得済み\",\n remaining: (value: number) => `あと${value}箇所`,\n completed: \"ラリー達成\",\n };\n return (\n <section\n className={`stamp-sheet ${percentage >= 100 ? \"stamp-sheet--completed\" : \"\"}`}\n style={style}\n aria-label={resolveLocalizedText(title, locale) || config.id}\n >\n <header className=\"stamp-sheet__header\">\n <h2>{resolveLocalizedText(title, locale) || config.id}</h2>\n <span className=\"stamp-sheet__score\" role=\"status\" aria-live=\"polite\">\n {count} / {total}\n </span>\n </header>\n <div className=\"stamp-sheet__progress-label\">\n <span>{messages.remaining(Math.max(0, total - count))}</span>\n <strong>{Math.round(percentage)}%</strong>\n </div>\n <div\n className=\"stamp-sheet__progress-track\"\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percentage)}\n aria-label={locale === \"en\" ? \"Rally progress\" : \"スタンプ取得進捗\"}\n >\n <span\n className=\"stamp-sheet__progress-fill\"\n style={{ display: \"block\", width: `${percentage}%` }}\n />\n </div>\n <div className=\"stamp-sheet__grid\">\n {config.stamps.map((stamp, index) => {\n const recordForStamp = state?.records.find((record) => record.stampId === stamp.id);\n return (\n <StampSlot\n key={stamp.id}\n stamp={stamp}\n {...(recordForStamp === undefined ? {} : { record: recordForStamp })}\n isNext={next.has(stamp.id)}\n slotNumber={index + 1}\n {...(presentations[stamp.id] === undefined\n ? {}\n : { presentation: presentations[stamp.id] })}\n isAnimating={animatedStampId === stamp.id}\n disabled={disabled}\n slotShape={theme.slotShape}\n locale={locale}\n statusText={\n recordForStamp === undefined\n ? next.has(stamp.id)\n ? messages.available\n : messages.locked\n : messages.stamped\n }\n onSelect={() => onStampSelect?.(stamp.id)}\n />\n );\n })}\n </div>\n {percentage >= 100 && (\n <div className=\"stamp-sheet__complete-mark\" role=\"status\" aria-live=\"polite\">\n COMPLETE!!\n </div>\n )}\n {percentage >= 100 && (\n <div className=\"stamp-sheet__complete-message\">{messages.completed}</div>\n )}\n </section>\n );\n}\n\nexport interface AcquisitionFeedback {\n readonly ok: boolean;\n readonly message: string;\n}\nexport interface StampModalProps {\n readonly open: boolean;\n readonly stamp: SpotItem | null;\n readonly record?: StampRallyState[\"records\"][number] | undefined;\n readonly presentation?: StampPresentation | null;\n readonly requiredStampName?: string | null;\n readonly isAvailable?: boolean;\n readonly isPending?: boolean;\n readonly isCompleted?: boolean;\n readonly locale?: SupportedLocale;\n readonly onClose: () => void;\n readonly onAcquire?: (\n stampId: string,\n context: VerificationContext,\n ) => Promise<AcquisitionFeedback>;\n readonly onCheckIn?: (\n stampId: string,\n context: VerificationContext,\n ) => Promise<AcquisitionFeedback>;\n readonly onNotify?: (feedback: AcquisitionFeedback) => void;\n}\n\nexport function StampModal({\n open,\n stamp,\n record,\n isAvailable = true,\n isPending = false,\n locale = \"ja\",\n onClose,\n onAcquire,\n onCheckIn,\n onNotify,\n}: StampModalProps) {\n const [token, setToken] = useState(\"\");\n const modalRef = useFocusTrap(open && stamp !== null, onClose, isPending);\n useEffect(() => {\n if (!open) setToken(\"\");\n }, [open]);\n if (!open || stamp === null) return null;\n const submit = async (context: VerificationContext): Promise<void> => {\n if (!isAvailable || record !== undefined) return;\n const handler = onAcquire ?? onCheckIn;\n if (handler === undefined) return;\n const feedback = await handler(stamp.id, context);\n onNotify?.(feedback);\n if (feedback.ok) onClose();\n };\n const name = resolveLocalizedText(stamp.name, locale);\n return (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"stamprally-modal-title\"\n className=\"stamprally-modal\"\n >\n <div className=\"stamprally-modal__panel\" ref={modalRef} tabIndex={-1}>\n <button\n type=\"button\"\n aria-label=\"Close stamp details\"\n aria-disabled={isPending}\n onClick={onClose}\n disabled={isPending}\n >\n ×\n </button>\n <h2 id=\"stamprally-modal-title\">{name}</h2>\n {record !== undefined ? (\n <p>Already claimed.</p>\n ) : (\n <p>{resolveLocalizedText(stamp.description, locale)}</p>\n )}\n {stamp.condition.type === \"token\" ? (\n <form\n onSubmit={(event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n void submit({ type: \"token\", token });\n }}\n >\n <label>\n Passcode <input value={token} onChange={(event) => setToken(event.target.value)} />\n </label>\n <button\n type=\"submit\"\n aria-label=\"Claim stamp\"\n aria-disabled={!isAvailable || isPending}\n disabled={!isAvailable || isPending}\n >\n Claim stamp\n </button>\n </form>\n ) : (\n <button\n type=\"button\"\n aria-label=\"Claim stamp\"\n aria-disabled={!isAvailable || isPending}\n onClick={() => void submit({ type: \"instant\" })}\n disabled={!isAvailable || isPending}\n >\n Claim stamp\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport interface RewardPanelProps {\n readonly rewards: ReadonlyArray<RewardItem>;\n readonly states: ReadonlyArray<RewardState>;\n readonly locale?: SupportedLocale;\n readonly isPending?: boolean;\n readonly open?: boolean;\n readonly onClose?: () => void;\n readonly onRedeem: (\n rewardId: string,\n options?: { readonly passcode?: string; readonly staffId?: string },\n ) => Promise<ConsumeResult>;\n readonly onNotify?: (ok: boolean, message: string) => void;\n}\n\nexport function RewardPanel({\n rewards,\n states,\n locale = \"ja\",\n isPending = false,\n open = true,\n onClose,\n onRedeem,\n onNotify,\n}: RewardPanelProps) {\n const [passcodes, setPasscodes] = useState<Readonly<Record<string, string>>>({});\n const panelRef = useFocusTrap(open && onClose !== undefined, onClose, isPending);\n if (!open) return null;\n return (\n <section\n ref={panelRef}\n role=\"dialog\"\n aria-labelledby=\"stamprally-rewards-title\"\n aria-modal=\"true\"\n className=\"stamprally-rewards\"\n tabIndex={-1}\n >\n <header className=\"stamprally-rewards__header\">\n <h2 id=\"stamprally-rewards-title\">Rewards</h2>\n {onClose !== undefined && (\n <button\n type=\"button\"\n aria-label=\"Close rewards\"\n aria-disabled={isPending}\n disabled={isPending}\n onClick={onClose}\n >\n ×\n </button>\n )}\n </header>\n {rewards.map((reward) => {\n const state = states.find((item) => item.rewardId === reward.id);\n const available = state?.status === \"AVAILABLE\";\n const redeem = async (options?: { readonly passcode?: string }): Promise<void> => {\n const result = await onRedeem(reward.id, options);\n onNotify?.(result.ok, result.ok ? \"Reward claimed.\" : result.error.code);\n };\n return (\n <article\n key={reward.id}\n className={`stamprally-reward stamprally-reward--${state?.status ?? \"LOCKED\"}`}\n >\n <h3>{resolveLocalizedText(reward.title, locale)}</h3>\n <p>{resolveLocalizedText(reward.description, locale)}</p>\n <span role=\"status\" aria-live=\"polite\">\n {state?.status ?? \"LOCKED\"}\n </span>\n {reward.redemptionMethod === \"staff_passcode\" && (\n <form\n onSubmit={(event) => {\n event.preventDefault();\n void redeem({ passcode: passcodes[reward.id] ?? \"\" });\n }}\n >\n <label>\n Staff passcode{\" \"}\n <input\n type=\"password\"\n value={passcodes[reward.id] ?? \"\"}\n onChange={(event) =>\n setPasscodes((current) => ({ ...current, [reward.id]: event.target.value }))\n }\n />\n </label>\n <button\n type=\"submit\"\n aria-label={`Redeem ${resolveLocalizedText(reward.title, locale)}`}\n aria-disabled={!available || isPending}\n disabled={!available || isPending}\n >\n Redeem\n </button>\n </form>\n )}\n {reward.redemptionMethod !== \"staff_passcode\" && (\n <button\n type=\"button\"\n aria-label={`Redeem ${resolveLocalizedText(reward.title, locale)}`}\n aria-disabled={!available || isPending}\n disabled={!available || isPending}\n onClick={() => void redeem()}\n >\n Redeem\n </button>\n )}\n </article>\n );\n })}\n </section>\n );\n}\n"]}