@stamprally/ui 0.2.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/dist/index.cjs CHANGED
@@ -5,6 +5,19 @@ var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  // src/index.tsx
8
+ var FONT_STACKS = {
9
+ "system-ui": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
10
+ serif: 'Georgia, "Times New Roman", "Yu Mincho", serif',
11
+ "rounded-sans": '"Arial Rounded MT Bold", "Hiragino Maru Gothic ProN", "Yu Gothic", sans-serif',
12
+ monospace: 'ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace',
13
+ handwritten: '"Segoe Print", "Bradley Hand", "Comic Sans MS", cursive'
14
+ };
15
+ function formatStampDate(value) {
16
+ const date = new Date(value);
17
+ if (Number.isNaN(date.getTime())) return value;
18
+ const pad = (part) => String(part).padStart(2, "0");
19
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
20
+ }
8
21
  function StampSlot({
9
22
  stamp,
10
23
  record,
@@ -15,9 +28,12 @@ function StampSlot({
15
28
  disabled = false,
16
29
  slotShape = "rounded",
17
30
  locale = "ja",
31
+ statusText,
18
32
  onSelect
19
33
  }) {
20
34
  const status = record === void 0 ? isNext ? "available" : "locked" : "stamped";
35
+ const resolvedStatus = statusText ?? status;
36
+ const statusSeparator = statusText !== void 0 && locale === "ja" ? "\u3001" : ", ";
21
37
  const name = core.resolveLocalizedText(stamp.name, locale);
22
38
  const label = presentation.label ?? stamp.condition.type;
23
39
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -26,16 +42,37 @@ function StampSlot({
26
42
  type: "button",
27
43
  disabled,
28
44
  onClick: onSelect,
29
- "aria-label": `${slotNumber}. ${name} (${status})`,
45
+ "aria-label": `#${String(slotNumber).padStart(2, "0")} ${name}${statusSeparator}${resolvedStatus}`,
30
46
  "data-status": status,
31
47
  "data-shape": slotShape,
32
- className: `stamp-slot stamp-slot--${status}${isAnimating ? " stamp-slot--animating" : ""}`,
48
+ className: `stamp-slot stamp-slot--${status} stamp-slot--${presentation.ink ?? "vermilion"} stamp-slot--shape-${slotShape}${isAnimating ? " stamp-slot--animating" : ""}`,
33
49
  style: { "--stamp-primary": "var(--stamprally-primary, #9e551e)" },
34
50
  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 })
51
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__topline", children: [
52
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__number", children: [
53
+ "#",
54
+ String(slotNumber).padStart(2, "0")
55
+ ] }),
56
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `stamp-slot__state stamp-slot__state--${status}`, children: resolvedStatus })
57
+ ] }),
58
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-slot__watermark", "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
59
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-slot__name", children: name }),
60
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-slot__condition", children: [
61
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
62
+ label
63
+ ] }),
64
+ record !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
65
+ "span",
66
+ {
67
+ className: `stamp-imprint ${isAnimating ? "stamp-press" : ""}`,
68
+ role: "img",
69
+ "aria-label": `Acquired at ${formatStampDate(record.acquiredAt)}`,
70
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-imprint__inner", children: [
71
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-imprint__word", children: "STAMP" }),
72
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stamp-imprint__date", children: formatStampDate(record.acquiredAt) })
73
+ ] })
74
+ }
75
+ )
39
76
  ]
40
77
  }
41
78
  );
@@ -58,54 +95,96 @@ function StampSheet({
58
95
  const total = progress?.total ?? config.stamps.length;
59
96
  const percentage = progress?.percentage ?? (total === 0 ? 0 : count / total * 100);
60
97
  const style = {
61
- "--stamprally-primary": theme.primaryColor,
62
- "--stamprally-background": theme.backgroundColor ?? "transparent",
63
- "--stamprally-card": theme.cardBackgroundColor,
64
- "--stamprally-text": theme.textColor,
98
+ "--stamp-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
99
+ "--stamp-bg": `var(--stamprally-background-override, ${theme.backgroundColor ?? core.DEFAULT_SHEET_THEME.backgroundColor ?? "#ffffff"})`,
100
+ "--stamp-card-bg": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
101
+ "--stamp-text": `var(--stamprally-text-override, ${theme.textColor})`,
102
+ "--stamp-grid-cols": String(theme.gridColumns),
103
+ "--stamp-grid-cols-mobile": String(Math.min(theme.gridColumns, 2)),
104
+ "--stamp-unclaimed-opacity": String(theme.unclaimedOpacity ?? 1),
105
+ "--stamp-font-family": FONT_STACKS[theme.fontFamily ?? "serif"],
106
+ ...theme.completedStampColor === void 0 ? {} : { "--stamp-completed-color": theme.completedStampColor },
107
+ "--stamprally-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
108
+ "--stamprally-background": `var(--stamprally-background-override, ${theme.backgroundColor ?? "transparent"})`,
109
+ "--stamprally-card": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
110
+ "--stamprally-text": `var(--stamprally-text-override, ${theme.textColor})`,
65
111
  "--stamprally-columns": String(theme.gridColumns)
66
112
  };
113
+ const messages = locale === "en" ? {
114
+ available: "available",
115
+ locked: "locked",
116
+ stamped: "stamped",
117
+ remaining: (value) => `${value} spots remaining`,
118
+ completed: "RALLY COMPLETED"
119
+ } : {
120
+ available: "\u53D6\u5F97\u53EF\u80FD",
121
+ locked: "\u9806\u5E8F\u5F85\u3061",
122
+ stamped: "\u53D6\u5F97\u6E08\u307F",
123
+ remaining: (value) => `\u3042\u3068${value}\u7B87\u6240`,
124
+ completed: "\u30E9\u30EA\u30FC\u9054\u6210"
125
+ };
67
126
  return /* @__PURE__ */ jsxRuntime.jsxs(
68
127
  "section",
69
128
  {
70
- className: "stamprally-sheet",
129
+ className: `stamp-sheet ${percentage >= 100 ? "stamp-sheet--completed" : ""}`,
71
130
  style,
72
131
  "aria-label": core.resolveLocalizedText(title, locale) || config.id,
73
132
  children: [
74
- /* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
133
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "stamp-sheet__header", children: [
75
134
  /* @__PURE__ */ jsxRuntime.jsx("h2", { children: core.resolveLocalizedText(title, locale) || config.id }),
76
- /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-live": "polite", children: [
135
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stamp-sheet__score", "aria-live": "polite", children: [
77
136
  count,
78
137
  " / ",
79
138
  total
80
139
  ] })
81
140
  ] }),
141
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stamp-sheet__progress-label", children: [
142
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: messages.remaining(Math.max(0, total - count)) }),
143
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
144
+ Math.round(percentage),
145
+ "%"
146
+ ] })
147
+ ] }),
82
148
  /* @__PURE__ */ jsxRuntime.jsx(
83
149
  "div",
84
150
  {
151
+ className: "stamp-sheet__progress-track",
85
152
  role: "progressbar",
86
153
  "aria-valuemin": 0,
87
154
  "aria-valuemax": 100,
88
155
  "aria-valuenow": Math.round(percentage),
89
- "aria-label": "Rally progress",
90
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "block", width: `${percentage}%` } })
156
+ "aria-label": locale === "en" ? "Rally progress" : "\u30B9\u30BF\u30F3\u30D7\u53D6\u5F97\u9032\u6357",
157
+ children: /* @__PURE__ */ jsxRuntime.jsx(
158
+ "span",
159
+ {
160
+ className: "stamp-sheet__progress-fill",
161
+ style: { display: "block", width: `${percentage}%` }
162
+ }
163
+ )
91
164
  }
92
165
  ),
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
- )) })
166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__grid", children: config.stamps.map((stamp, index) => {
167
+ const recordForStamp = state?.records.find((record) => record.stampId === stamp.id);
168
+ return /* @__PURE__ */ jsxRuntime.jsx(
169
+ StampSlot,
170
+ {
171
+ stamp,
172
+ ...recordForStamp === void 0 ? {} : { record: recordForStamp },
173
+ isNext: next.has(stamp.id),
174
+ slotNumber: index + 1,
175
+ ...presentations[stamp.id] === void 0 ? {} : { presentation: presentations[stamp.id] },
176
+ isAnimating: animatedStampId === stamp.id,
177
+ disabled,
178
+ slotShape: theme.slotShape,
179
+ locale,
180
+ statusText: recordForStamp === void 0 ? next.has(stamp.id) ? messages.available : messages.locked : messages.stamped,
181
+ onSelect: () => onStampSelect?.(stamp.id)
182
+ },
183
+ stamp.id
184
+ );
185
+ }) }),
186
+ percentage >= 100 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__complete-mark", children: "COMPLETE!!" }),
187
+ percentage >= 100 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stamp-sheet__complete-message", children: messages.completed })
109
188
  ]
110
189
  }
111
190
  );
@@ -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":["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 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 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\" 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 && <div className=\"stamp-sheet__complete-mark\">COMPLETE!!</div>}\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 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"]}
package/dist/index.css ADDED
@@ -0,0 +1,116 @@
1
+ .stamp-sheet,
2
+ :where(.stamprally-sheet, .stamprally-rewards) {
3
+ color: var(--stamprally-text, #352f25);
4
+ background: var(--stamprally-background, transparent);
5
+ }
6
+ .stamp-sheet {
7
+ display: grid;
8
+ gap: 1rem;
9
+ padding: 1.5rem;
10
+ border: 1px solid color-mix(in srgb, var(--stamp-primary, #9e551e) 35%, #fff);
11
+ border-radius: 1.5rem;
12
+ color: var(--stamp-text, #352f25);
13
+ font-family: var(--stamp-font-family, Georgia, "Times New Roman", serif);
14
+ background: var(--stamp-bg, #fbf4df);
15
+ }
16
+ .stamp-sheet__header,
17
+ .stamp-sheet__progress-label,
18
+ .stamp-slot__topline {
19
+ display: flex;
20
+ align-items: baseline;
21
+ justify-content: space-between;
22
+ gap: 1rem;
23
+ }
24
+ .stamp-sheet__header h2 {
25
+ margin: 0;
26
+ }
27
+ .stamp-sheet__progress-label {
28
+ font-size: 0.85rem;
29
+ }
30
+ .stamp-sheet__progress-track {
31
+ overflow: hidden;
32
+ height: 0.5rem;
33
+ border-radius: 999px;
34
+ background: color-mix(in srgb, var(--stamp-primary, #9e551e) 15%, #fff);
35
+ }
36
+ .stamp-sheet__progress-fill {
37
+ height: 100%;
38
+ border-radius: inherit;
39
+ background: var(--stamp-primary, #9e551e);
40
+ }
41
+ .stamp-sheet__grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(var(--stamp-grid-cols, 3), minmax(0, 1fr));
44
+ gap: 1rem;
45
+ }
46
+ .stamp-slot {
47
+ position: relative;
48
+ color: var(--stamprally-text, #352f25);
49
+ display: grid;
50
+ gap: 0.35rem;
51
+ min-height: 10rem;
52
+ padding: 1rem;
53
+ background: var(--stamprally-card, #fff);
54
+ border: 2px solid var(--stamprally-primary, #9e551e);
55
+ cursor: pointer;
56
+ }
57
+ .stamp-slot__number,
58
+ .stamp-slot__state,
59
+ .stamp-slot__condition {
60
+ font-size: 0.72rem;
61
+ font-weight: 700;
62
+ }
63
+ .stamp-slot__watermark {
64
+ font-size: 2rem;
65
+ opacity: 0.35;
66
+ }
67
+ .stamp-slot__name {
68
+ font-weight: 800;
69
+ }
70
+ .stamp-slot__condition {
71
+ opacity: 0.75;
72
+ }
73
+ .stamp-imprint {
74
+ display: grid;
75
+ place-items: center;
76
+ padding: 0.35rem;
77
+ border: 2px solid var(--stamp-completed-color, #b33c2e);
78
+ color: var(--stamp-completed-color, #b33c2e);
79
+ transform: rotate(-4deg);
80
+ }
81
+ .stamp-imprint__inner {
82
+ display: grid;
83
+ gap: 0.1rem;
84
+ text-align: center;
85
+ }
86
+ .stamp-slot--locked {
87
+ opacity: var(--stamp-unclaimed-opacity, 0.55);
88
+ cursor: not-allowed;
89
+ }
90
+ .stamp-slot--animating {
91
+ animation: stamprally-stamp-press 0.5s ease-out;
92
+ }
93
+ .stamprally-modal {
94
+ position: fixed;
95
+ inset: 0;
96
+ display: grid;
97
+ place-items: center;
98
+ padding: 1rem;
99
+ background: #0008;
100
+ }
101
+ .stamprally-modal__panel {
102
+ max-width: 32rem;
103
+ width: 100%;
104
+ padding: 1.5rem;
105
+ background: var(--stamprally-card, #fff);
106
+ }
107
+ @keyframes stamprally-stamp-press {
108
+ 50% {
109
+ transform: scale(0.96) rotate(-2deg);
110
+ }
111
+ }
112
+ @media (max-width: 640px) {
113
+ .stamp-sheet__grid {
114
+ grid-template-columns: repeat(var(--stamp-grid-cols-mobile, 2), minmax(0, 1fr));
115
+ }
116
+ }
package/dist/index.d.cts CHANGED
@@ -5,6 +5,7 @@ interface StampPresentation {
5
5
  readonly label?: string;
6
6
  readonly icon?: string;
7
7
  readonly ink?: "vermilion" | "indigo";
8
+ readonly channel?: "instant" | "qr" | "nfc" | "geo";
8
9
  }
9
10
  interface StampSlotProps {
10
11
  readonly stamp: SpotItem;
@@ -16,9 +17,10 @@ interface StampSlotProps {
16
17
  readonly disabled?: boolean;
17
18
  readonly slotShape?: SlotShape;
18
19
  readonly locale?: SupportedLocale;
20
+ readonly statusText?: string;
19
21
  readonly onSelect?: () => void;
20
22
  }
21
- declare function StampSlot({ stamp, record, isNext, slotNumber, presentation, isAnimating, disabled, slotShape, locale, onSelect, }: StampSlotProps): react.JSX.Element;
23
+ declare function StampSlot({ stamp, record, isNext, slotNumber, presentation, isAnimating, disabled, slotShape, locale, statusText, onSelect, }: StampSlotProps): react.JSX.Element;
22
24
  interface StampSheetProps {
23
25
  readonly title?: LocalizedText;
24
26
  readonly config: RallyConfig;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ interface StampPresentation {
5
5
  readonly label?: string;
6
6
  readonly icon?: string;
7
7
  readonly ink?: "vermilion" | "indigo";
8
+ readonly channel?: "instant" | "qr" | "nfc" | "geo";
8
9
  }
9
10
  interface StampSlotProps {
10
11
  readonly stamp: SpotItem;
@@ -16,9 +17,10 @@ interface StampSlotProps {
16
17
  readonly disabled?: boolean;
17
18
  readonly slotShape?: SlotShape;
18
19
  readonly locale?: SupportedLocale;
20
+ readonly statusText?: string;
19
21
  readonly onSelect?: () => void;
20
22
  }
21
- declare function StampSlot({ stamp, record, isNext, slotNumber, presentation, isAnimating, disabled, slotShape, locale, onSelect, }: StampSlotProps): react.JSX.Element;
23
+ declare function StampSlot({ stamp, record, isNext, slotNumber, presentation, isAnimating, disabled, slotShape, locale, statusText, onSelect, }: StampSlotProps): react.JSX.Element;
22
24
  interface StampSheetProps {
23
25
  readonly title?: LocalizedText;
24
26
  readonly config: RallyConfig;
package/dist/index.js CHANGED
@@ -3,6 +3,19 @@ import { useState, useEffect } from 'react';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
5
5
  // src/index.tsx
6
+ var FONT_STACKS = {
7
+ "system-ui": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
8
+ serif: 'Georgia, "Times New Roman", "Yu Mincho", serif',
9
+ "rounded-sans": '"Arial Rounded MT Bold", "Hiragino Maru Gothic ProN", "Yu Gothic", sans-serif',
10
+ monospace: 'ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace',
11
+ handwritten: '"Segoe Print", "Bradley Hand", "Comic Sans MS", cursive'
12
+ };
13
+ function formatStampDate(value) {
14
+ const date = new Date(value);
15
+ if (Number.isNaN(date.getTime())) return value;
16
+ const pad = (part) => String(part).padStart(2, "0");
17
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
18
+ }
6
19
  function StampSlot({
7
20
  stamp,
8
21
  record,
@@ -13,9 +26,12 @@ function StampSlot({
13
26
  disabled = false,
14
27
  slotShape = "rounded",
15
28
  locale = "ja",
29
+ statusText,
16
30
  onSelect
17
31
  }) {
18
32
  const status = record === void 0 ? isNext ? "available" : "locked" : "stamped";
33
+ const resolvedStatus = statusText ?? status;
34
+ const statusSeparator = statusText !== void 0 && locale === "ja" ? "\u3001" : ", ";
19
35
  const name = resolveLocalizedText(stamp.name, locale);
20
36
  const label = presentation.label ?? stamp.condition.type;
21
37
  return /* @__PURE__ */ jsxs(
@@ -24,16 +40,37 @@ function StampSlot({
24
40
  type: "button",
25
41
  disabled,
26
42
  onClick: onSelect,
27
- "aria-label": `${slotNumber}. ${name} (${status})`,
43
+ "aria-label": `#${String(slotNumber).padStart(2, "0")} ${name}${statusSeparator}${resolvedStatus}`,
28
44
  "data-status": status,
29
45
  "data-shape": slotShape,
30
- className: `stamp-slot stamp-slot--${status}${isAnimating ? " stamp-slot--animating" : ""}`,
46
+ className: `stamp-slot stamp-slot--${status} stamp-slot--${presentation.ink ?? "vermilion"} stamp-slot--shape-${slotShape}${isAnimating ? " stamp-slot--animating" : ""}`,
31
47
  style: { "--stamp-primary": "var(--stamprally-primary, #9e551e)" },
32
48
  children: [
33
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
34
- /* @__PURE__ */ jsx("strong", { children: name }),
35
- /* @__PURE__ */ jsx("small", { children: label }),
36
- record !== void 0 && /* @__PURE__ */ jsx("time", { dateTime: record.acquiredAt, children: record.acquiredAt })
49
+ /* @__PURE__ */ jsxs("span", { className: "stamp-slot__topline", children: [
50
+ /* @__PURE__ */ jsxs("span", { className: "stamp-slot__number", children: [
51
+ "#",
52
+ String(slotNumber).padStart(2, "0")
53
+ ] }),
54
+ /* @__PURE__ */ jsx("span", { className: `stamp-slot__state stamp-slot__state--${status}`, children: resolvedStatus })
55
+ ] }),
56
+ /* @__PURE__ */ jsx("span", { className: "stamp-slot__watermark", "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
57
+ /* @__PURE__ */ jsx("span", { className: "stamp-slot__name", children: name }),
58
+ /* @__PURE__ */ jsxs("span", { className: "stamp-slot__condition", children: [
59
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: presentation.icon ?? "\u2726" }),
60
+ label
61
+ ] }),
62
+ record !== void 0 && /* @__PURE__ */ jsx(
63
+ "span",
64
+ {
65
+ className: `stamp-imprint ${isAnimating ? "stamp-press" : ""}`,
66
+ role: "img",
67
+ "aria-label": `Acquired at ${formatStampDate(record.acquiredAt)}`,
68
+ children: /* @__PURE__ */ jsxs("span", { className: "stamp-imprint__inner", children: [
69
+ /* @__PURE__ */ jsx("span", { className: "stamp-imprint__word", children: "STAMP" }),
70
+ /* @__PURE__ */ jsx("span", { className: "stamp-imprint__date", children: formatStampDate(record.acquiredAt) })
71
+ ] })
72
+ }
73
+ )
37
74
  ]
38
75
  }
39
76
  );
@@ -56,54 +93,96 @@ function StampSheet({
56
93
  const total = progress?.total ?? config.stamps.length;
57
94
  const percentage = progress?.percentage ?? (total === 0 ? 0 : count / total * 100);
58
95
  const style = {
59
- "--stamprally-primary": theme.primaryColor,
60
- "--stamprally-background": theme.backgroundColor ?? "transparent",
61
- "--stamprally-card": theme.cardBackgroundColor,
62
- "--stamprally-text": theme.textColor,
96
+ "--stamp-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
97
+ "--stamp-bg": `var(--stamprally-background-override, ${theme.backgroundColor ?? DEFAULT_SHEET_THEME.backgroundColor ?? "#ffffff"})`,
98
+ "--stamp-card-bg": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
99
+ "--stamp-text": `var(--stamprally-text-override, ${theme.textColor})`,
100
+ "--stamp-grid-cols": String(theme.gridColumns),
101
+ "--stamp-grid-cols-mobile": String(Math.min(theme.gridColumns, 2)),
102
+ "--stamp-unclaimed-opacity": String(theme.unclaimedOpacity ?? 1),
103
+ "--stamp-font-family": FONT_STACKS[theme.fontFamily ?? "serif"],
104
+ ...theme.completedStampColor === void 0 ? {} : { "--stamp-completed-color": theme.completedStampColor },
105
+ "--stamprally-primary": `var(--stamprally-primary-override, ${theme.primaryColor})`,
106
+ "--stamprally-background": `var(--stamprally-background-override, ${theme.backgroundColor ?? "transparent"})`,
107
+ "--stamprally-card": `var(--stamprally-card-override, ${theme.cardBackgroundColor})`,
108
+ "--stamprally-text": `var(--stamprally-text-override, ${theme.textColor})`,
63
109
  "--stamprally-columns": String(theme.gridColumns)
64
110
  };
111
+ const messages = locale === "en" ? {
112
+ available: "available",
113
+ locked: "locked",
114
+ stamped: "stamped",
115
+ remaining: (value) => `${value} spots remaining`,
116
+ completed: "RALLY COMPLETED"
117
+ } : {
118
+ available: "\u53D6\u5F97\u53EF\u80FD",
119
+ locked: "\u9806\u5E8F\u5F85\u3061",
120
+ stamped: "\u53D6\u5F97\u6E08\u307F",
121
+ remaining: (value) => `\u3042\u3068${value}\u7B87\u6240`,
122
+ completed: "\u30E9\u30EA\u30FC\u9054\u6210"
123
+ };
65
124
  return /* @__PURE__ */ jsxs(
66
125
  "section",
67
126
  {
68
- className: "stamprally-sheet",
127
+ className: `stamp-sheet ${percentage >= 100 ? "stamp-sheet--completed" : ""}`,
69
128
  style,
70
129
  "aria-label": resolveLocalizedText(title, locale) || config.id,
71
130
  children: [
72
- /* @__PURE__ */ jsxs("header", { children: [
131
+ /* @__PURE__ */ jsxs("header", { className: "stamp-sheet__header", children: [
73
132
  /* @__PURE__ */ jsx("h2", { children: resolveLocalizedText(title, locale) || config.id }),
74
- /* @__PURE__ */ jsxs("span", { "aria-live": "polite", children: [
133
+ /* @__PURE__ */ jsxs("span", { className: "stamp-sheet__score", "aria-live": "polite", children: [
75
134
  count,
76
135
  " / ",
77
136
  total
78
137
  ] })
79
138
  ] }),
139
+ /* @__PURE__ */ jsxs("div", { className: "stamp-sheet__progress-label", children: [
140
+ /* @__PURE__ */ jsx("span", { children: messages.remaining(Math.max(0, total - count)) }),
141
+ /* @__PURE__ */ jsxs("strong", { children: [
142
+ Math.round(percentage),
143
+ "%"
144
+ ] })
145
+ ] }),
80
146
  /* @__PURE__ */ jsx(
81
147
  "div",
82
148
  {
149
+ className: "stamp-sheet__progress-track",
83
150
  role: "progressbar",
84
151
  "aria-valuemin": 0,
85
152
  "aria-valuemax": 100,
86
153
  "aria-valuenow": Math.round(percentage),
87
- "aria-label": "Rally progress",
88
- children: /* @__PURE__ */ jsx("span", { style: { display: "block", width: `${percentage}%` } })
154
+ "aria-label": locale === "en" ? "Rally progress" : "\u30B9\u30BF\u30F3\u30D7\u53D6\u5F97\u9032\u6357",
155
+ children: /* @__PURE__ */ jsx(
156
+ "span",
157
+ {
158
+ className: "stamp-sheet__progress-fill",
159
+ style: { display: "block", width: `${percentage}%` }
160
+ }
161
+ )
89
162
  }
90
163
  ),
91
- /* @__PURE__ */ jsx("div", { className: "stamprally-sheet__grid", children: config.stamps.map((stamp, index) => /* @__PURE__ */ jsx(
92
- StampSlot,
93
- {
94
- stamp,
95
- ...state?.records.find((record) => record.stampId === stamp.id) === void 0 ? {} : { record: state?.records.find((record) => record.stampId === stamp.id) },
96
- isNext: next.has(stamp.id),
97
- slotNumber: index + 1,
98
- ...presentations[stamp.id] === void 0 ? {} : { presentation: presentations[stamp.id] },
99
- isAnimating: animatedStampId === stamp.id,
100
- disabled,
101
- slotShape: theme.slotShape,
102
- locale,
103
- onSelect: () => onStampSelect?.(stamp.id)
104
- },
105
- stamp.id
106
- )) })
164
+ /* @__PURE__ */ jsx("div", { className: "stamp-sheet__grid", children: config.stamps.map((stamp, index) => {
165
+ const recordForStamp = state?.records.find((record) => record.stampId === stamp.id);
166
+ return /* @__PURE__ */ jsx(
167
+ StampSlot,
168
+ {
169
+ stamp,
170
+ ...recordForStamp === void 0 ? {} : { record: recordForStamp },
171
+ isNext: next.has(stamp.id),
172
+ slotNumber: index + 1,
173
+ ...presentations[stamp.id] === void 0 ? {} : { presentation: presentations[stamp.id] },
174
+ isAnimating: animatedStampId === stamp.id,
175
+ disabled,
176
+ slotShape: theme.slotShape,
177
+ locale,
178
+ statusText: recordForStamp === void 0 ? next.has(stamp.id) ? messages.available : messages.locked : messages.stamped,
179
+ onSelect: () => onStampSelect?.(stamp.id)
180
+ },
181
+ stamp.id
182
+ );
183
+ }) }),
184
+ percentage >= 100 && /* @__PURE__ */ jsx("div", { className: "stamp-sheet__complete-mark", children: "COMPLETE!!" }),
185
+ percentage >= 100 && /* @__PURE__ */ jsx("div", { className: "stamp-sheet__complete-message", children: messages.completed })
107
186
  ]
108
187
  }
109
188
  );
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","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":[],"mappings":"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","file":"index.js","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 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 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\" 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 && <div className=\"stamp-sheet__complete-mark\">COMPLETE!!</div>}\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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamprally/ui",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Accessible participant UI components for stamp rallies.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,8 +15,7 @@
15
15
  "type": "module",
16
16
  "sideEffects": false,
17
17
  "files": [
18
- "dist",
19
- "src/styles.css"
18
+ "dist"
20
19
  ],
21
20
  "main": "./dist/index.cjs",
22
21
  "module": "./dist/index.js",
@@ -27,11 +26,11 @@
27
26
  "import": "./dist/index.js",
28
27
  "require": "./dist/index.cjs"
29
28
  },
30
- "./styles.css": "./src/styles.css"
29
+ "./styles.css": "./dist/index.css"
31
30
  },
32
31
  "dependencies": {
33
- "@stamprally/core": "0.2.1",
34
- "@stamprally/react": "0.2.1"
32
+ "@stamprally/react": "0.3.0",
33
+ "@stamprally/core": "0.3.0"
35
34
  },
36
35
  "peerDependencies": {
37
36
  "react": "^18.0.0 || ^19.0.0",
@@ -48,7 +47,7 @@
48
47
  "vitest": "^4.1.11"
49
48
  },
50
49
  "scripts": {
51
- "build": "tsup",
50
+ "build": "tsup && node ../../scripts/copy-package-css.mjs packages/ui",
52
51
  "dev": "tsup --watch",
53
52
  "test": "vitest run",
54
53
  "typecheck": "tsc -p tsconfig.json"
package/src/styles.css DELETED
@@ -1,45 +0,0 @@
1
- :where(.stamprally-sheet, .stamprally-rewards) {
2
- color: var(--stamprally-text, #352f25);
3
- background: var(--stamprally-background, transparent);
4
- }
5
- .stamprally-sheet__grid {
6
- display: grid;
7
- grid-template-columns: repeat(var(--stamprally-columns, 3), minmax(0, 1fr));
8
- gap: 1rem;
9
- }
10
- .stamp-slot {
11
- display: grid;
12
- gap: 0.35rem;
13
- min-height: 8rem;
14
- padding: 1rem;
15
- color: var(--stamprally-text, #352f25);
16
- background: var(--stamprally-card, #fff);
17
- border: 2px solid var(--stamprally-primary, #9e551e);
18
- cursor: pointer;
19
- }
20
- .stamp-slot--locked {
21
- opacity: 0.55;
22
- cursor: not-allowed;
23
- }
24
- .stamp-slot--animating {
25
- animation: stamprally-stamp-press 0.5s ease-out;
26
- }
27
- .stamprally-modal {
28
- position: fixed;
29
- inset: 0;
30
- display: grid;
31
- place-items: center;
32
- padding: 1rem;
33
- background: #0008;
34
- }
35
- .stamprally-modal__panel {
36
- max-width: 32rem;
37
- width: 100%;
38
- padding: 1.5rem;
39
- background: var(--stamprally-card, #fff);
40
- }
41
- @keyframes stamprally-stamp-press {
42
- 50% {
43
- transform: scale(0.96) rotate(-2deg);
44
- }
45
- }