melta-app 0.6.0 → 0.7.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.
Files changed (295) hide show
  1. package/README.md +220 -12
  2. package/docs/architecture.md +11 -4
  3. package/eslint-rules/melta.d.mts +44 -0
  4. package/lib/module/a11y/tap-target.js +41 -0
  5. package/lib/module/a11y/tap-target.js.map +1 -0
  6. package/lib/module/components/ActionSheet.js +20 -6
  7. package/lib/module/components/ActionSheet.js.map +1 -1
  8. package/lib/module/components/Alert.js +11 -7
  9. package/lib/module/components/Alert.js.map +1 -1
  10. package/lib/module/components/Avatar.js +7 -2
  11. package/lib/module/components/Avatar.js.map +1 -1
  12. package/lib/module/components/BottomSheet.js +11 -3
  13. package/lib/module/components/BottomSheet.js.map +1 -1
  14. package/lib/module/components/Card.js +4 -3
  15. package/lib/module/components/Card.js.map +1 -1
  16. package/lib/module/components/Checkbox.js +8 -5
  17. package/lib/module/components/Checkbox.js.map +1 -1
  18. package/lib/module/components/EmptyState.js +2 -1
  19. package/lib/module/components/EmptyState.js.map +1 -1
  20. package/lib/module/components/Header.js.map +1 -1
  21. package/lib/module/components/Image.js +5 -3
  22. package/lib/module/components/Image.js.map +1 -1
  23. package/lib/module/components/Modal.js +12 -4
  24. package/lib/module/components/Modal.js.map +1 -1
  25. package/lib/module/components/Progress.js +2 -1
  26. package/lib/module/components/Progress.js.map +1 -1
  27. package/lib/module/components/Radio.js +11 -3
  28. package/lib/module/components/Radio.js.map +1 -1
  29. package/lib/module/components/Screen.js +28 -3
  30. package/lib/module/components/Screen.js.map +1 -1
  31. package/lib/module/components/Skeleton.js +2 -1
  32. package/lib/module/components/Skeleton.js.map +1 -1
  33. package/lib/module/components/Surface.js +2 -1
  34. package/lib/module/components/Surface.js.map +1 -1
  35. package/lib/module/components/TextField.js +16 -4
  36. package/lib/module/components/TextField.js.map +1 -1
  37. package/lib/module/components/Toast.js +17 -9
  38. package/lib/module/components/Toast.js.map +1 -1
  39. package/lib/module/components/Toggle.js +13 -4
  40. package/lib/module/components/Toggle.js.map +1 -1
  41. package/lib/module/components/alert.styles.js +12 -26
  42. package/lib/module/components/alert.styles.js.map +1 -1
  43. package/lib/module/components/avatar.styles.js +24 -2
  44. package/lib/module/components/avatar.styles.js.map +1 -1
  45. package/lib/module/components/card.validate.js.map +1 -1
  46. package/lib/module/components/checkbox.styles.js +5 -2
  47. package/lib/module/components/checkbox.styles.js.map +1 -1
  48. package/lib/module/components/index.js.map +1 -1
  49. package/lib/module/components/modal.styles.js +9 -0
  50. package/lib/module/components/modal.styles.js.map +1 -1
  51. package/lib/module/components/radio.styles.js +11 -2
  52. package/lib/module/components/radio.styles.js.map +1 -1
  53. package/lib/module/components/safe-area-registry.js +64 -13
  54. package/lib/module/components/safe-area-registry.js.map +1 -1
  55. package/lib/module/components/status-colors.js +53 -0
  56. package/lib/module/components/status-colors.js.map +1 -0
  57. package/lib/module/components/textfield.styles.js +15 -8
  58. package/lib/module/components/textfield.styles.js.map +1 -1
  59. package/lib/module/components/toast.styles.js +36 -25
  60. package/lib/module/components/toast.styles.js.map +1 -1
  61. package/lib/module/components/toggle.styles.js +20 -0
  62. package/lib/module/components/toggle.styles.js.map +1 -1
  63. package/lib/module/icons/Icon.js +4 -1
  64. package/lib/module/icons/Icon.js.map +1 -1
  65. package/lib/module/icons/icon.styles.js +32 -1
  66. package/lib/module/icons/icon.styles.js.map +1 -1
  67. package/lib/module/icons/index.js.map +1 -1
  68. package/lib/module/index.js.map +1 -1
  69. package/lib/module/primitives/Button.js +23 -11
  70. package/lib/module/primitives/Button.js.map +1 -1
  71. package/lib/module/primitives/Metric.js +2 -1
  72. package/lib/module/primitives/Metric.js.map +1 -1
  73. package/lib/module/primitives/Row.js.map +1 -1
  74. package/lib/module/primitives/Stack.js.map +1 -1
  75. package/lib/module/primitives/Tag.js +15 -7
  76. package/lib/module/primitives/Tag.js.map +1 -1
  77. package/lib/module/primitives/Text.js +13 -5
  78. package/lib/module/primitives/Text.js.map +1 -1
  79. package/lib/module/primitives/_internal/focus-ring.js.map +1 -1
  80. package/lib/module/primitives/button.styles.js +58 -8
  81. package/lib/module/primitives/button.styles.js.map +1 -1
  82. package/lib/module/primitives/index.js.map +1 -1
  83. package/lib/module/primitives/metric.styles.js +2 -2
  84. package/lib/module/primitives/metric.styles.js.map +1 -1
  85. package/lib/module/primitives/tag.styles.js +42 -3
  86. package/lib/module/primitives/tag.styles.js.map +1 -1
  87. package/lib/module/primitives/text.styles.js +6 -7
  88. package/lib/module/primitives/text.styles.js.map +1 -1
  89. package/lib/module/safe-area/index.js +11 -5
  90. package/lib/module/safe-area/index.js.map +1 -1
  91. package/lib/module/theme/ThemeProvider.js +5 -2
  92. package/lib/module/theme/ThemeProvider.js.map +1 -1
  93. package/lib/module/theme/define-theme.js +14 -1
  94. package/lib/module/theme/define-theme.js.map +1 -1
  95. package/lib/module/theme/index.js +6 -8
  96. package/lib/module/theme/index.js.map +1 -1
  97. package/lib/module/theme/letter-spacing.js +20 -0
  98. package/lib/module/theme/letter-spacing.js.map +1 -0
  99. package/lib/module/theme/line-height.js +9 -0
  100. package/lib/module/theme/line-height.js.map +1 -1
  101. package/lib/module/theme/native-theme.js +1 -1
  102. package/lib/typescript/src/a11y/tap-target.d.ts +34 -0
  103. package/lib/typescript/src/a11y/tap-target.d.ts.map +1 -0
  104. package/lib/typescript/src/components/ActionSheet.d.ts +8 -1
  105. package/lib/typescript/src/components/ActionSheet.d.ts.map +1 -1
  106. package/lib/typescript/src/components/Alert.d.ts +9 -2
  107. package/lib/typescript/src/components/Alert.d.ts.map +1 -1
  108. package/lib/typescript/src/components/Avatar.d.ts +1 -1
  109. package/lib/typescript/src/components/Avatar.d.ts.map +1 -1
  110. package/lib/typescript/src/components/BottomSheet.d.ts +4 -1
  111. package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -1
  112. package/lib/typescript/src/components/Card.d.ts +2 -2
  113. package/lib/typescript/src/components/Checkbox.d.ts +3 -3
  114. package/lib/typescript/src/components/Checkbox.d.ts.map +1 -1
  115. package/lib/typescript/src/components/Image.d.ts +4 -3
  116. package/lib/typescript/src/components/Image.d.ts.map +1 -1
  117. package/lib/typescript/src/components/Modal.d.ts +9 -3
  118. package/lib/typescript/src/components/Modal.d.ts.map +1 -1
  119. package/lib/typescript/src/components/Progress.d.ts +1 -1
  120. package/lib/typescript/src/components/Progress.d.ts.map +1 -1
  121. package/lib/typescript/src/components/Radio.d.ts +3 -2
  122. package/lib/typescript/src/components/Radio.d.ts.map +1 -1
  123. package/lib/typescript/src/components/Screen.d.ts +28 -5
  124. package/lib/typescript/src/components/Screen.d.ts.map +1 -1
  125. package/lib/typescript/src/components/Skeleton.d.ts +1 -1
  126. package/lib/typescript/src/components/Skeleton.d.ts.map +1 -1
  127. package/lib/typescript/src/components/Surface.d.ts +1 -1
  128. package/lib/typescript/src/components/Surface.d.ts.map +1 -1
  129. package/lib/typescript/src/components/TextField.d.ts +11 -4
  130. package/lib/typescript/src/components/TextField.d.ts.map +1 -1
  131. package/lib/typescript/src/components/Toast.d.ts +10 -3
  132. package/lib/typescript/src/components/Toast.d.ts.map +1 -1
  133. package/lib/typescript/src/components/Toggle.d.ts +3 -2
  134. package/lib/typescript/src/components/Toggle.d.ts.map +1 -1
  135. package/lib/typescript/src/components/action-sheet.styles.d.ts +1 -1
  136. package/lib/typescript/src/components/action-sheet.styles.d.ts.map +1 -1
  137. package/lib/typescript/src/components/alert.styles.d.ts +14 -14
  138. package/lib/typescript/src/components/alert.styles.d.ts.map +1 -1
  139. package/lib/typescript/src/components/avatar.styles.d.ts +36 -1
  140. package/lib/typescript/src/components/avatar.styles.d.ts.map +1 -1
  141. package/lib/typescript/src/components/bottom-sheet.styles.d.ts +1 -1
  142. package/lib/typescript/src/components/bottom-sheet.styles.d.ts.map +1 -1
  143. package/lib/typescript/src/components/card.styles.d.ts +1 -1
  144. package/lib/typescript/src/components/card.styles.d.ts.map +1 -1
  145. package/lib/typescript/src/components/card.validate.d.ts +1 -1
  146. package/lib/typescript/src/components/card.validate.d.ts.map +1 -1
  147. package/lib/typescript/src/components/checkbox.styles.d.ts +6 -3
  148. package/lib/typescript/src/components/checkbox.styles.d.ts.map +1 -1
  149. package/lib/typescript/src/components/empty-state.styles.d.ts +1 -1
  150. package/lib/typescript/src/components/empty-state.styles.d.ts.map +1 -1
  151. package/lib/typescript/src/components/header.styles.d.ts +1 -1
  152. package/lib/typescript/src/components/header.styles.d.ts.map +1 -1
  153. package/lib/typescript/src/components/image.styles.d.ts +1 -1
  154. package/lib/typescript/src/components/image.styles.d.ts.map +1 -1
  155. package/lib/typescript/src/components/index.d.ts +18 -18
  156. package/lib/typescript/src/components/index.d.ts.map +1 -1
  157. package/lib/typescript/src/components/modal.styles.d.ts +13 -2
  158. package/lib/typescript/src/components/modal.styles.d.ts.map +1 -1
  159. package/lib/typescript/src/components/progress.styles.d.ts +1 -1
  160. package/lib/typescript/src/components/progress.styles.d.ts.map +1 -1
  161. package/lib/typescript/src/components/radio.styles.d.ts +10 -2
  162. package/lib/typescript/src/components/radio.styles.d.ts.map +1 -1
  163. package/lib/typescript/src/components/safe-area-registry.d.ts +47 -11
  164. package/lib/typescript/src/components/safe-area-registry.d.ts.map +1 -1
  165. package/lib/typescript/src/components/screen.styles.d.ts +1 -1
  166. package/lib/typescript/src/components/screen.styles.d.ts.map +1 -1
  167. package/lib/typescript/src/components/skeleton.styles.d.ts +1 -1
  168. package/lib/typescript/src/components/skeleton.styles.d.ts.map +1 -1
  169. package/lib/typescript/src/components/status-colors.d.ts +33 -0
  170. package/lib/typescript/src/components/status-colors.d.ts.map +1 -0
  171. package/lib/typescript/src/components/surface.styles.d.ts +1 -1
  172. package/lib/typescript/src/components/surface.styles.d.ts.map +1 -1
  173. package/lib/typescript/src/components/textfield.styles.d.ts +9 -5
  174. package/lib/typescript/src/components/textfield.styles.d.ts.map +1 -1
  175. package/lib/typescript/src/components/toast.styles.d.ts +34 -14
  176. package/lib/typescript/src/components/toast.styles.d.ts.map +1 -1
  177. package/lib/typescript/src/components/toggle.styles.d.ts +17 -1
  178. package/lib/typescript/src/components/toggle.styles.d.ts.map +1 -1
  179. package/lib/typescript/src/icons/Icon.d.ts +12 -6
  180. package/lib/typescript/src/icons/Icon.d.ts.map +1 -1
  181. package/lib/typescript/src/icons/icon.styles.d.ts +14 -2
  182. package/lib/typescript/src/icons/icon.styles.d.ts.map +1 -1
  183. package/lib/typescript/src/icons/index.d.ts +3 -3
  184. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  185. package/lib/typescript/src/index.d.ts +4 -4
  186. package/lib/typescript/src/index.d.ts.map +1 -1
  187. package/lib/typescript/src/primitives/Button.d.ts +8 -4
  188. package/lib/typescript/src/primitives/Button.d.ts.map +1 -1
  189. package/lib/typescript/src/primitives/Metric.d.ts +1 -1
  190. package/lib/typescript/src/primitives/Metric.d.ts.map +1 -1
  191. package/lib/typescript/src/primitives/Row.d.ts +2 -2
  192. package/lib/typescript/src/primitives/Row.d.ts.map +1 -1
  193. package/lib/typescript/src/primitives/Stack.d.ts +2 -2
  194. package/lib/typescript/src/primitives/Stack.d.ts.map +1 -1
  195. package/lib/typescript/src/primitives/Tag.d.ts +2 -2
  196. package/lib/typescript/src/primitives/Tag.d.ts.map +1 -1
  197. package/lib/typescript/src/primitives/Text.d.ts +20 -6
  198. package/lib/typescript/src/primitives/Text.d.ts.map +1 -1
  199. package/lib/typescript/src/primitives/button.styles.d.ts +44 -7
  200. package/lib/typescript/src/primitives/button.styles.d.ts.map +1 -1
  201. package/lib/typescript/src/primitives/index.d.ts +6 -6
  202. package/lib/typescript/src/primitives/index.d.ts.map +1 -1
  203. package/lib/typescript/src/primitives/metric.styles.d.ts +1 -1
  204. package/lib/typescript/src/primitives/metric.styles.d.ts.map +1 -1
  205. package/lib/typescript/src/primitives/row.styles.d.ts +1 -1
  206. package/lib/typescript/src/primitives/row.styles.d.ts.map +1 -1
  207. package/lib/typescript/src/primitives/stack.styles.d.ts +1 -1
  208. package/lib/typescript/src/primitives/stack.styles.d.ts.map +1 -1
  209. package/lib/typescript/src/primitives/tag.styles.d.ts +34 -1
  210. package/lib/typescript/src/primitives/tag.styles.d.ts.map +1 -1
  211. package/lib/typescript/src/primitives/text.styles.d.ts +2 -4
  212. package/lib/typescript/src/primitives/text.styles.d.ts.map +1 -1
  213. package/lib/typescript/src/safe-area/index.d.ts +19 -5
  214. package/lib/typescript/src/safe-area/index.d.ts.map +1 -1
  215. package/lib/typescript/src/theme/ThemeProvider.d.ts +2 -2
  216. package/lib/typescript/src/theme/ThemeProvider.d.ts.map +1 -1
  217. package/lib/typescript/src/theme/define-theme.d.ts +15 -2
  218. package/lib/typescript/src/theme/define-theme.d.ts.map +1 -1
  219. package/lib/typescript/src/theme/index.d.ts +14 -14
  220. package/lib/typescript/src/theme/index.d.ts.map +1 -1
  221. package/lib/typescript/src/theme/letter-spacing.d.ts +15 -0
  222. package/lib/typescript/src/theme/letter-spacing.d.ts.map +1 -0
  223. package/lib/typescript/src/theme/line-height.d.ts +10 -0
  224. package/lib/typescript/src/theme/line-height.d.ts.map +1 -1
  225. package/lib/typescript/src/theme/native-theme.d.ts +1 -1
  226. package/lib/typescript/src/theme/native-theme.d.ts.map +1 -1
  227. package/lib/typescript/src/theme/types.d.ts +1 -1
  228. package/package.json +22 -7
  229. package/src/a11y/tap-target.ts +38 -0
  230. package/src/components/ActionSheet.tsx +25 -15
  231. package/src/components/Alert.tsx +37 -10
  232. package/src/components/Avatar.tsx +12 -4
  233. package/src/components/BottomSheet.tsx +16 -13
  234. package/src/components/Card.tsx +9 -8
  235. package/src/components/Checkbox.tsx +12 -9
  236. package/src/components/EmptyState.tsx +7 -6
  237. package/src/components/Header.tsx +4 -4
  238. package/src/components/Image.tsx +9 -7
  239. package/src/components/Modal.tsx +22 -6
  240. package/src/components/Progress.tsx +5 -4
  241. package/src/components/Radio.tsx +11 -6
  242. package/src/components/Screen.tsx +66 -8
  243. package/src/components/Skeleton.tsx +6 -5
  244. package/src/components/Surface.tsx +5 -4
  245. package/src/components/TextField.tsx +25 -7
  246. package/src/components/Toast.tsx +33 -11
  247. package/src/components/Toggle.tsx +15 -6
  248. package/src/components/action-sheet.styles.ts +1 -1
  249. package/src/components/alert.styles.ts +13 -25
  250. package/src/components/avatar.styles.ts +25 -3
  251. package/src/components/bottom-sheet.styles.ts +1 -1
  252. package/src/components/card.styles.ts +1 -1
  253. package/src/components/card.validate.ts +2 -2
  254. package/src/components/checkbox.styles.ts +6 -3
  255. package/src/components/empty-state.styles.ts +1 -1
  256. package/src/components/header.styles.ts +1 -1
  257. package/src/components/image.styles.ts +1 -1
  258. package/src/components/index.ts +18 -18
  259. package/src/components/modal.styles.ts +11 -2
  260. package/src/components/progress.styles.ts +1 -1
  261. package/src/components/radio.styles.ts +11 -2
  262. package/src/components/safe-area-registry.ts +81 -14
  263. package/src/components/screen.styles.ts +1 -1
  264. package/src/components/skeleton.styles.ts +1 -1
  265. package/src/components/status-colors.ts +59 -0
  266. package/src/components/surface.styles.ts +1 -1
  267. package/src/components/textfield.styles.ts +18 -15
  268. package/src/components/toast.styles.ts +32 -25
  269. package/src/components/toggle.styles.ts +21 -1
  270. package/src/icons/Icon.tsx +13 -8
  271. package/src/icons/icon.styles.ts +38 -3
  272. package/src/icons/index.ts +3 -3
  273. package/src/index.ts +4 -4
  274. package/src/primitives/Button.tsx +28 -14
  275. package/src/primitives/Metric.tsx +5 -4
  276. package/src/primitives/Row.tsx +4 -4
  277. package/src/primitives/Stack.tsx +4 -4
  278. package/src/primitives/Tag.tsx +29 -11
  279. package/src/primitives/Text.tsx +28 -11
  280. package/src/primitives/_internal/focus-ring.tsx +1 -1
  281. package/src/primitives/button.styles.ts +63 -10
  282. package/src/primitives/index.ts +6 -6
  283. package/src/primitives/metric.styles.ts +3 -3
  284. package/src/primitives/row.styles.ts +1 -1
  285. package/src/primitives/stack.styles.ts +1 -1
  286. package/src/primitives/tag.styles.ts +43 -4
  287. package/src/primitives/text.styles.ts +7 -12
  288. package/src/safe-area/index.ts +21 -7
  289. package/src/theme/ThemeProvider.tsx +8 -5
  290. package/src/theme/define-theme.ts +15 -2
  291. package/src/theme/index.ts +14 -17
  292. package/src/theme/letter-spacing.ts +17 -0
  293. package/src/theme/line-height.ts +9 -0
  294. package/src/theme/native-theme.ts +2 -2
  295. package/src/theme/types.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melta-app",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "melta for APP — melta デザインシステムの React Native UI kit。melta-contracts(デザイン契約)を single source of truth に RN 実装でその契約を満たす。アプリ本体ではなくライブラリ。",
5
5
  "license": "MIT",
6
6
  "homepage": "https://app.melta.tsubotax.com",
@@ -37,8 +37,16 @@
37
37
  "import": "./lib/module/safe-area/index.js",
38
38
  "default": "./lib/module/safe-area/index.js"
39
39
  },
40
- "./eslint-plugin": "./eslint-rules/melta.mjs",
41
- "./eslint-rules/melta.mjs": "./eslint-rules/melta.mjs",
40
+ "./eslint-plugin": {
41
+ "types": "./eslint-rules/melta.d.mts",
42
+ "import": "./eslint-rules/melta.mjs",
43
+ "default": "./eslint-rules/melta.mjs"
44
+ },
45
+ "./eslint-rules/melta.mjs": {
46
+ "types": "./eslint-rules/melta.d.mts",
47
+ "import": "./eslint-rules/melta.mjs",
48
+ "default": "./eslint-rules/melta.mjs"
49
+ },
42
50
  "./package.json": "./package.json"
43
51
  },
44
52
  "files": [
@@ -55,8 +63,8 @@
55
63
  "example"
56
64
  ],
57
65
  "peerDependencies": {
58
- "react": "*",
59
- "react-native": "*",
66
+ "react": ">=18.2.0",
67
+ "react-native": ">=0.71.0",
60
68
  "react-native-safe-area-context": ">=4",
61
69
  "react-native-svg": ">=15"
62
70
  },
@@ -69,6 +77,7 @@
69
77
  }
70
78
  },
71
79
  "devDependencies": {
80
+ "@arethetypeswrong/cli": "0.18.5",
72
81
  "@jest/globals": "^29.7.0",
73
82
  "@react-native/babel-preset": "^0.85.3",
74
83
  "@react-native/jest-preset": "^0.85.3",
@@ -77,7 +86,7 @@
77
86
  "@types/react": "~19.2.2",
78
87
  "eslint": "^10.4.1",
79
88
  "jest": "^29.7.0",
80
- "melta-contracts": "^0.6.0",
89
+ "melta-contracts": "^0.7.0",
81
90
  "react": "19.2.3",
82
91
  "react-native": "0.85.3",
83
92
  "react-native-builder-bob": "^0.40.13",
@@ -117,7 +126,10 @@
117
126
  ],
118
127
  "testMatch": [
119
128
  "<rootDir>/src/**/__tests__/**/*.test.tsx"
120
- ]
129
+ ],
130
+ "moduleNameMapper": {
131
+ "^(\\.{1,2}/.*)\\.js$": "$1"
132
+ }
121
133
  },
122
134
  "react-native-builder-bob": {
123
135
  "source": "src",
@@ -136,5 +148,8 @@
136
148
  }
137
149
  ]
138
150
  ]
151
+ },
152
+ "engines": {
153
+ "node": ">=22"
139
154
  }
140
155
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * tap-target — 実効タップ標的 44pt(横断方針)の数値 SSOT。
3
+ *
4
+ * 正本は melta-contracts の `A11Y_MIN_TAP_TARGET_44`(rules.json):
5
+ * 「すべての操作要素は実効タップ標的 44pt を下回らない。視覚寸法は契約どおり据え置き、
6
+ * 当たり判定だけを広げる。app は視覚 24pt + hitSlop 10 の正典パターンか minHeight で下限を保証する
7
+ * (height 固定は fontScale でクリップするので使わない)」。
8
+ *
9
+ * ここは「44」という数と正典パターンの寸法を**一箇所**に置くための pure module。
10
+ * 各コンポーネントの hitSlop 値そのものは **literal で各 `*.styles.ts` に置く**(ここから
11
+ * 自動導出しない)。導出にすると視覚寸法が変わったとき hitSlop も一緒に動いてしまい、
12
+ * conformance テスト(scripts/lib/tap-target-conformance.test.ts)が構造的に fail-open になるため。
13
+ *
14
+ * react-native を import しない純粋モジュール(`*.styles.ts` と同じ規約)。
15
+ */
16
+
17
+ /** 実効タップ標的の下限(pt)。WCAG 2.2 SC 2.5.5(AAA)水準を melta の横断方針として採る。 */
18
+ export const MIN_TAP_TARGET = 44;
19
+
20
+ /**
21
+ * 正典パターン: 視覚 24pt の箱 + hitSlop 10 → 実効 44pt。
22
+ * 背景を持たない小さな操作要素(Tag removable の × / Toast の × / Alert の × / Modal の ×)はこれ。
23
+ */
24
+ export const CANONICAL_TAP_TARGET = {
25
+ minWidth: 24,
26
+ minHeight: 24,
27
+ hitSlop: 10,
28
+ } as const;
29
+
30
+ /**
31
+ * 視覚寸法 `visual` を実効 44pt に届かせるのに必要な**片側** hitSlop(切り上げ = 安全側)。
32
+ *
33
+ * ⚠️ 実装の hitSlop 値をこの関数で計算しないこと(上のコメント参照)。
34
+ * 使うのは (a) 値を決めるときの検算、(b) conformance テストの期待値算出。
35
+ */
36
+ export function requiredHitSlop(visual: number): number {
37
+ return Math.max(0, Math.ceil((MIN_TAP_TARGET - visual) / 2));
38
+ }
@@ -9,22 +9,27 @@
9
9
  * 色だけでなくラベル文言でも破壊的操作と分かる文言にするのは呼び出し側の責務(contract a11y)。
10
10
  * - 先頭 action の borderTop は title が無い場合は消す(title がある時だけ区切りとして意味を持つ)。
11
11
  * - action の onPress 後は onClose も呼ぶ(選択 = シートを閉じる)。
12
- * - sheet 群は SafeAreaView で包み下余白(ホームインジケータ)を確保(recipe description 参照)。
12
+ * - 下余白(ホームインジケータ)は SafeArea で確保する(recipe description 参照)。位置は
13
+ * **最下部の面である cancel ブロックの内側**(BottomSheet と同じ「内側」方針に統一)。
14
+ * 0.5.x までは sheet 群の外側に置いていたため、iOS では inset 帯に overlay の黒が見えていた。
15
+ * 内側に移したことで inset 帯まで bg-surface で塗られる代わり、cancel ブロックは
16
+ * inset のぶん背が高くなる(ラベルは上寄りに見える)=意図した見た目の変更。
17
+ * edge は bottom + 左右のみ(top は overlay 側で、シートは上端に接しない)。
18
+ * SafeArea 実装は registry 経由(safe-area-registry.ts)— "melta-app/safe-area" を
19
+ * 有効化したアプリでは Android でも inset が入る(RN core の SafeAreaView は Android で no-op)。
13
20
  * - sheet 側は onStartShouldSetResponder で touch を claim し、overlay tap でだけ閉じる。
14
21
  * - style の決定は pure resolver(action-sheet.styles.ts)に分離 — recipe との機械照合対象。
15
22
  */
16
23
 
17
24
  import { useMemo } from "react";
18
- import {
19
- Modal as RNModal,
20
- Pressable,
21
- SafeAreaView,
22
- Text as RNText,
23
- View,
24
- } from "react-native";
25
- import { useTheme } from "../theme";
26
- import { CONTRACTS } from "../contracts/contract-types";
27
- import { resolveActionSheetStyle, type ActionSheetStyle } from "./action-sheet.styles";
25
+ import { Modal as RNModal, Pressable, Text as RNText, View } from "react-native";
26
+ import { useTheme } from "../theme/index.js";
27
+ import { CONTRACTS } from "../contracts/contract-types.js";
28
+ import { resolveActionSheetStyle, type ActionSheetStyle } from "./action-sheet.styles.js";
29
+ import { resolveSafeAreaView, type SafeAreaEdge } from "./safe-area-registry.js";
30
+
31
+ /** シートは画面下端に密着するので top inset は不要。 */
32
+ const SHEET_EDGES: readonly SafeAreaEdge[] = ["bottom", "left", "right"];
28
33
 
29
34
  export interface ActionSheetAction {
30
35
  label: string;
@@ -61,6 +66,8 @@ export function ActionSheet({
61
66
  [theme, mode],
62
67
  );
63
68
 
69
+ const SafeArea = resolveSafeAreaView(SHEET_EDGES);
70
+
64
71
  return (
65
72
  <RNModal
66
73
  visible={visible}
@@ -70,8 +77,8 @@ export function ActionSheet({
70
77
  statusBarTranslucent
71
78
  >
72
79
  <Pressable style={styles.overlayStyle} onPress={onClose} accessible={false}>
73
- {/* sheet 群(sheet + cancel): SafeArea で下余白を確保し、touch を claim して overlay へ流さない */}
74
- <SafeAreaView
80
+ {/* sheet 群(sheet + cancel): touch を claim して overlay へ流さない */}
81
+ <View
75
82
  accessibilityViewIsModal
76
83
  onStartShouldSetResponder={() => true}
77
84
  testID={testID}
@@ -101,9 +108,12 @@ export function ActionSheet({
101
108
  ))}
102
109
  </View>
103
110
  <Pressable accessibilityRole="button" onPress={onClose} style={styles.cancelStyle}>
104
- <RNText style={styles.cancelTextStyle}>{cancelLabel}</RNText>
111
+ {/* SafeArea は最下部の面(cancel)の内側 = inset 帯を overlay の黒でなく bg-surface で塗る */}
112
+ <SafeArea>
113
+ <RNText style={styles.cancelTextStyle}>{cancelLabel}</RNText>
114
+ </SafeArea>
105
115
  </Pressable>
106
- </SafeAreaView>
116
+ </View>
107
117
  </Pressable>
108
118
  </RNModal>
109
119
  );
@@ -5,6 +5,8 @@
5
5
  * - RN では border を持たず bg subtle + text 色で表現(status token に border 用の中間色が無い、
6
6
  * recipe description)。COLOR_ONLY_FORBIDDEN は title/message のテキストと icon slot で担保する設計。
7
7
  * - icon は ReactNode slot(EmptyState 前例。Icon を差すなら melta-app/icons を利用者が opt-in)。
8
+ * - × の accessibilityLabel は既定 "閉じる"(closeAccessibilityLabel で差し替え可能)。
9
+ * 当たり判定は正典パターン(24 + hitSlop 10 = 44pt)。
8
10
  * - a11y(§2 web→RN mapping): web の role=alert(error/warning) / status(info/success) を、
9
11
  * error/warning → accessibilityRole="alert"、info/success → accessibilityLiveRegion="polite"
10
12
  * (Android のみ有効。RN に status role が無いための写像)に落とす。
@@ -15,10 +17,15 @@
15
17
 
16
18
  import { useMemo, type ReactNode } from "react";
17
19
  import { Pressable, View, type StyleProp, type ViewStyle } from "react-native";
18
- import { Text } from "../primitives/Text";
19
- import { useTheme } from "../theme";
20
- import { CONTRACTS } from "../contracts/contract-types";
21
- import { ALERT_SPEC, resolveAlertStyles, type AlertVariant } from "./alert.styles";
20
+ import { Text } from "../primitives/Text.js";
21
+ import { useTheme } from "../theme/index.js";
22
+ import { CONTRACTS } from "../contracts/contract-types.js";
23
+ import {
24
+ ALERT_CLOSE_TAP_TARGET,
25
+ ALERT_SPEC,
26
+ resolveAlertStyles,
27
+ type AlertVariant,
28
+ } from "./alert.styles.js";
22
29
 
23
30
  interface AlertProps {
24
31
  /** 通知タイプ(contract variant)。 */
@@ -31,11 +38,25 @@ interface AlertProps {
31
38
  icon?: ReactNode;
32
39
  /** あれば右端に閉じるボタン(Pressable の ×)を出す。 */
33
40
  onClose?: () => void;
41
+ /**
42
+ * × ボタンの accessibilityLabel(i18n フック)。既定は日本語 "閉じる"
43
+ * (既存アプリの読み上げを変えないため据え置き)。
44
+ */
45
+ closeAccessibilityLabel?: string;
34
46
  style?: StyleProp<ViewStyle>;
35
47
  testID?: string;
36
48
  }
37
49
 
38
- export function Alert({ variant, title, message, icon, onClose, style, testID }: AlertProps) {
50
+ export function Alert({
51
+ variant,
52
+ title,
53
+ message,
54
+ icon,
55
+ onClose,
56
+ closeAccessibilityLabel = "閉じる",
57
+ style,
58
+ testID,
59
+ }: AlertProps) {
39
60
  const { theme, mode } = useTheme();
40
61
  const styles = useMemo(() => resolveAlertStyles(theme, mode, variant), [theme, mode, variant]);
41
62
  const urgent = variant === "error" || variant === "warning";
@@ -63,13 +84,18 @@ export function Alert({ variant, title, message, icon, onClose, style, testID }:
63
84
  </Text>
64
85
  </View>
65
86
  {onClose != null && (
66
- // 最小タップターゲット確保: min 24 + hitSlop(Tag removable と同じ手当て)。
87
+ // 最小タップターゲット確保: 正典パターン 24 + hitSlop 10 = 実効 44pt(Tag removable と同型)。
67
88
  <Pressable
68
89
  onPress={onClose}
69
90
  accessibilityRole="button"
70
- accessibilityLabel="閉じる"
71
- hitSlop={10}
72
- style={{ minWidth: 24, minHeight: 24, alignItems: "center", justifyContent: "center" }}
91
+ accessibilityLabel={closeAccessibilityLabel}
92
+ hitSlop={ALERT_CLOSE_TAP_TARGET.hitSlop}
93
+ style={{
94
+ minWidth: ALERT_CLOSE_TAP_TARGET.minWidth,
95
+ minHeight: ALERT_CLOSE_TAP_TARGET.minHeight,
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ }}
73
99
  >
74
100
  <Text variant="xs" style={{ color: styles.messageStyle.color }}>
75
101
  ×
@@ -80,5 +106,6 @@ export function Alert({ variant, title, message, icon, onClose, style, testID }:
80
106
  );
81
107
  }
82
108
 
83
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
109
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
110
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
84
111
  Alert.__contract = CONTRACTS.alert;
@@ -18,16 +18,17 @@ import {
18
18
  type StyleProp,
19
19
  type ViewStyle,
20
20
  } from "react-native";
21
- import { useTheme } from "../theme";
22
- import { CONTRACTS } from "../contracts/contract-types";
21
+ import { useTheme } from "../theme/index.js";
22
+ import { CONTRACTS } from "../contracts/contract-types.js";
23
23
  import {
24
+ AVATAR_INITIALS_MAX_FONT_SCALE,
24
25
  resolveAvatarGroupStyle,
25
26
  resolveAvatarStatusColor,
26
27
  resolveAvatarStyle,
27
28
  type AvatarSize,
28
29
  type AvatarStatus,
29
30
  type AvatarStyleResult,
30
- } from "./avatar.styles";
31
+ } from "./avatar.styles.js";
31
32
 
32
33
  interface AvatarProps {
33
34
  /** 表示名(initials の生成元 + accessibilityLabel。contract a11y required)。 */
@@ -73,7 +74,14 @@ export function Avatar({ name, source, size = "medium", status, style, testID }:
73
74
  {source ? (
74
75
  <RNImage source={source} style={{ width: "100%", height: "100%" }} />
75
76
  ) : (
76
- <RNText style={resolved.text}>{toInitials(name)}</RNText>
77
+ // 器(円)は fontScale で伸びないので、文字側に size 別の上限を掛けて溢れを防ぐ
78
+ // (根拠と導出は avatar.styles.ts の AVATAR_INITIALS_MAX_FONT_SCALE)。
79
+ <RNText
80
+ style={resolved.text}
81
+ maxFontSizeMultiplier={AVATAR_INITIALS_MAX_FONT_SCALE[size]}
82
+ >
83
+ {toInitials(name)}
84
+ </RNText>
77
85
  )}
78
86
  </View>
79
87
  {status ? (
@@ -6,23 +6,24 @@
6
6
  * - grabber は装飾バー。a11y ツリーから除外する(importantForAccessibility="no" +
7
7
  * accessibilityElementsHidden。recipe description 参照)。
8
8
  * - スワイプで閉じるジェスチャは実装しない(overlay tap + Android back で閉じる、Phase 外)。
9
- * - sheet は下端密着(bg-surface)のため、SafeAreaView を sheet の内側に置いて
9
+ * - sheet は下端密着(bg-surface)のため、SafeArea を sheet の内側に置いて
10
10
  * 下余白(ホームインジケータ)まで sheet 背景で覆いつつ content を安全域に収める。
11
+ * edge は bottom + 左右のみ(top は overlay 側で、シートは上端に接しない)。
12
+ * SafeArea 実装は registry 経由(safe-area-registry.ts)— "melta-app/safe-area" を
13
+ * 有効化したアプリでは Android でも inset が入る(RN core の SafeAreaView は Android で no-op)。
11
14
  * - sheet 側は onStartShouldSetResponder で touch を claim し、overlay tap でだけ閉じる。
12
15
  * - style の決定は pure resolver(bottom-sheet.styles.ts)に分離 — recipe との機械照合対象。
13
16
  */
14
17
 
15
18
  import { useMemo, type ReactNode } from "react";
16
- import {
17
- Modal as RNModal,
18
- Pressable,
19
- SafeAreaView,
20
- Text as RNText,
21
- View,
22
- } from "react-native";
23
- import { useTheme } from "../theme";
24
- import { CONTRACTS } from "../contracts/contract-types";
25
- import { resolveBottomSheetStyle, type BottomSheetStyle } from "./bottom-sheet.styles";
19
+ import { Modal as RNModal, Pressable, Text as RNText, View } from "react-native";
20
+ import { useTheme } from "../theme/index.js";
21
+ import { CONTRACTS } from "../contracts/contract-types.js";
22
+ import { resolveBottomSheetStyle, type BottomSheetStyle } from "./bottom-sheet.styles.js";
23
+ import { resolveSafeAreaView, type SafeAreaEdge } from "./safe-area-registry.js";
24
+
25
+ /** シートは画面下端に密着するので top inset は不要。 */
26
+ const SHEET_EDGES: readonly SafeAreaEdge[] = ["bottom", "left", "right"];
26
27
 
27
28
  interface BottomSheetProps {
28
29
  visible: boolean;
@@ -43,6 +44,8 @@ export function BottomSheet({ visible, onClose, title, testID, children }: Botto
43
44
  [theme, mode],
44
45
  );
45
46
 
47
+ const SafeArea = resolveSafeAreaView(SHEET_EDGES);
48
+
46
49
  return (
47
50
  <RNModal
48
51
  visible={visible}
@@ -58,7 +61,7 @@ export function BottomSheet({ visible, onClose, title, testID, children }: Botto
58
61
  onStartShouldSetResponder={() => true}
59
62
  testID={testID}
60
63
  >
61
- <SafeAreaView>
64
+ <SafeArea>
62
65
  {/* grabber: 装飾のみ(スワイプ操作は持たないため a11y から隠す) */}
63
66
  <View
64
67
  style={styles.grabberStyle}
@@ -71,7 +74,7 @@ export function BottomSheet({ visible, onClose, title, testID, children }: Botto
71
74
  </RNText>
72
75
  )}
73
76
  <View style={styles.contentStyle}>{children}</View>
74
- </SafeAreaView>
77
+ </SafeArea>
75
78
  </View>
76
79
  </Pressable>
77
80
  </RNModal>
@@ -7,7 +7,7 @@
7
7
  * scripts/lib/card-conformance.test.ts が行う。
8
8
  * - variant: basic/media は非インタラクティブ、action/link は Pressable。
9
9
  * action/link は pressed 時 elevation.sm→md(§2 hover→pressed mapping)。
10
- * focus-within は RN 非サポートとして Phase1 明示 drop(§2)。
10
+ * focus-within は RN 非サポートとして明示 drop(§2)。
11
11
  * - 型で action/link は onPress 必須を強制(§4 の DU 精神。無言で非対話に落ちる事故を防ぐ)。
12
12
  * - **カード面自体は操作要素にしない**(contract 2.1.0 の a11y.required)。面の Pressable は
13
13
  * `accessibilityRole` を名乗らず `accessible={false}`。理由は2つ:
@@ -21,7 +21,7 @@
21
21
  * react-native-web では実際に `<article>` 要素になる)。accessibilityRole 系とは別系統。
22
22
  * - **focus ring は Card では描かない**。フォーカスを受けるのは `primaryAction` 側で、
23
23
  * melta の Button は自前の ring を持つ。カードにも出すと1回のフォーカスで ring が二重に描かれる。
24
- * contract の focus-within は RN 非サポートとして Phase1 で明示 drop 済み(app recipe に記録あり)で、
24
+ * contract の focus-within は RN 非サポートとして明示 drop 済み(app recipe に記録あり)で、
25
25
  * web だけ動く実装を残すとプラットフォーム非対称になるため、実装ごと持たない。
26
26
  * - `accessibilityLabel` は 0.5.0 で削除した。面が `accessible={false}` なので native では
27
27
  * 原理的に読まれず、web だけ `aria-label` として効く非対称な prop だった。
@@ -33,11 +33,11 @@
33
33
 
34
34
  import { useEffect, type ReactElement, type ReactNode } from "react";
35
35
  import { Pressable, View, type StyleProp, type ViewStyle } from "react-native";
36
- import { useTheme } from "../theme";
37
- import { CONTRACTS } from "../contracts/contract-types";
38
- import { isDev } from "../theme/define-theme";
39
- import { CARD_INTERACTIVE, resolveCardShape, resolveCardBodyStyle } from "./card.styles";
40
- import { validateCardProps } from "./card.validate";
36
+ import { useTheme } from "../theme/index.js";
37
+ import { CONTRACTS } from "../contracts/contract-types.js";
38
+ import { isDev } from "../theme/define-theme.js";
39
+ import { CARD_INTERACTIVE, resolveCardShape, resolveCardBodyStyle } from "./card.styles.js";
40
+ import { validateCardProps } from "./card.validate.js";
41
41
 
42
42
  /**
43
43
  * 同じ問題を毎レンダー報告しないためのラッチ。
@@ -144,5 +144,6 @@ export function Card({
144
144
  );
145
145
  }
146
146
 
147
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
147
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
148
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
148
149
  Card.__contract = CONTRACTS.card;
@@ -4,24 +4,24 @@
4
4
  * - RN にネイティブ checkbox が無いため Pressable(行全体タップ)+ 描画で実装する。
5
5
  * checkmark は rotate した View の L 字 border、indeterminate は横棒
6
6
  * (react-native-svg 非依存 = 本体エントリの依存ゼロ維持。recipe description 参照)。
7
- * - box は 20px(web の 16px より大きいのはタッチ前提)。実タッチ領域は hitSlop 12 で
8
- * 44pt(20 + 12×2)を確保する。
7
+ * - box は 20px(web の 16px より大きいのはタッチ前提)。実効タップ標的は行の
8
+ * minHeight 44 で確保する(規約 10-(b): 背景を持たない行は hitSlop でなく寸法)。
9
9
  * - label 必須(FORM_NO_LABEL_OMIT を props 型で強制)。label は Text primitive
10
10
  * (base/text-default = recipe labelStyle と同値)。
11
11
  * - states(contract → prop の写像): checked/unchecked → checked:boolean、
12
12
  * indeterminate → indeterminate:boolean(variant "indeterminate"。a11y は "mixed")、
13
13
  * error → error:boolean(borderColor 差分、checked より優先)、disabled → prop
14
- * (opacity 0.5 + 非活性)。focus は Phase1 非対応(recipe にも focus 差分なし)。
14
+ * (opacity 0.5 + 非活性)。focus は非対応(recipe にも focus 差分なし)。
15
15
  * - 色・寸法の決定は pure resolver(checkbox.styles.ts)に分離。
16
16
  * recipes/app/checkbox.recipe.json との機械照合は scripts/lib/checkbox-conformance.test.ts が行う。
17
17
  */
18
18
 
19
19
  import { useMemo } from "react";
20
20
  import { Pressable, View } from "react-native";
21
- import { Text } from "../primitives/Text";
22
- import { useTheme } from "../theme";
23
- import { CONTRACTS } from "../contracts/contract-types";
24
- import { CHECKBOX_SPEC, resolveCheckboxStyle } from "./checkbox.styles";
21
+ import { Text } from "../primitives/Text.js";
22
+ import { useTheme } from "../theme/index.js";
23
+ import { CONTRACTS } from "../contracts/contract-types.js";
24
+ import { CHECKBOX_SPEC, resolveCheckboxStyle } from "./checkbox.styles.js";
25
25
 
26
26
  /** checkmark / 横棒の幾何値(recipe に token 化されていない描画トリック側の定数)。 */
27
27
  const CHECKMARK = { width: 10, height: 6, borderWidth: 2, offsetY: -1 } as const;
@@ -59,7 +59,6 @@ export function Checkbox({
59
59
  <Pressable
60
60
  onPress={() => onChange(!checked)}
61
61
  disabled={disabled}
62
- hitSlop={CHECKBOX_SPEC.hitSlop}
63
62
  accessibilityRole="checkbox"
64
63
  accessibilityState={{ checked: indeterminate ? "mixed" : checked, disabled }}
65
64
  accessibilityLabel={label}
@@ -68,6 +67,9 @@ export function Checkbox({
68
67
  flexDirection: "row",
69
68
  alignItems: "center",
70
69
  gap: resolved.gap,
70
+ // 実効タップ標的 44pt。背景を持たない行なので hitSlop でなく minHeight(規約 10-(b)、
71
+ // 縦積みの隣接行と当たり判定が重ならない)
72
+ minHeight: CHECKBOX_SPEC.rowMinHeight,
71
73
  opacity: disabled ? CHECKBOX_SPEC.disabledOpacity : 1,
72
74
  }}
73
75
  >
@@ -103,5 +105,6 @@ export function Checkbox({
103
105
  );
104
106
  }
105
107
 
106
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
108
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
109
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
107
110
  Checkbox.__contract = CONTRACTS.checkbox;
@@ -12,11 +12,11 @@
12
12
 
13
13
  import { useMemo, type ReactNode } from "react";
14
14
  import { View, type StyleProp, type ViewStyle } from "react-native";
15
- import { Text } from "../primitives/Text";
16
- import { Button } from "../primitives/Button";
17
- import { useTheme } from "../theme";
18
- import { CONTRACTS } from "../contracts/contract-types";
19
- import { EMPTY_STATE_SPEC, resolveEmptyStateStyles } from "./empty-state.styles";
15
+ import { Text } from "../primitives/Text.js";
16
+ import { Button } from "../primitives/Button.js";
17
+ import { useTheme } from "../theme/index.js";
18
+ import { CONTRACTS } from "../contracts/contract-types.js";
19
+ import { EMPTY_STATE_SPEC, resolveEmptyStateStyles } from "./empty-state.styles.js";
20
20
 
21
21
  interface EmptyStateProps {
22
22
  icon?: ReactNode;
@@ -60,5 +60,6 @@ export function EmptyState({ icon, title, description, action, style, testID }:
60
60
  );
61
61
  }
62
62
 
63
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
63
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
64
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
64
65
  EmptyState.__contract = CONTRACTS.emptyState;
@@ -11,10 +11,10 @@
11
11
 
12
12
  import { useMemo, type ReactNode } from "react";
13
13
  import { View, type StyleProp, type ViewStyle } from "react-native";
14
- import { useTheme } from "../theme";
15
- import { CONTRACTS } from "../contracts/contract-types";
16
- import { Text } from "../primitives/Text";
17
- import { resolveHeaderStyle, type HeaderStyle } from "./header.styles";
14
+ import { useTheme } from "../theme/index.js";
15
+ import { CONTRACTS } from "../contracts/contract-types.js";
16
+ import { Text } from "../primitives/Text.js";
17
+ import { resolveHeaderStyle, type HeaderStyle } from "./header.styles.js";
18
18
 
19
19
  interface HeaderProps {
20
20
  /** 画面タイトル(heading として読み上げ)。 */
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Image — 画像表示 component(設計書 §1)。contract: image(app 先行定義)。
3
3
  *
4
- * - Phase 1 は RN コア Image。expo-image は Phase 2(blurhash / 高度 cache 要件化時)。
5
- * 差し替えコスト最小化のためここで包む(呼び出し側は melta-app の Image だけ見る)。
4
+ * - 実装は RN コア Image(依存ゼロ維持)。expo-image への差し替えは blurhash / 高度 cache が
5
+ * 要件化してから(未着手)。差し替えコスト最小化のためここで包む(呼び出し側は melta-app の
6
+ * Image だけ見る)。
6
7
  * - contentFit("cover"|"contain") → RN の resizeMode に変換。
7
8
  * - onError 時に fallback(ReactNode)を表示。fallback 未指定なら何も出さない。
8
9
  * - radius は token キーのみ(生数値不可、§5)。shape の解決は pure resolver
@@ -19,10 +20,10 @@ import {
19
20
  type StyleProp,
20
21
  type ViewStyle,
21
22
  } from "react-native";
22
- import { useTheme } from "../theme";
23
- import type { RadiusKey } from "../theme";
24
- import { CONTRACTS } from "../contracts/contract-types";
25
- import { resolveImageShape } from "./image.styles";
23
+ import { useTheme } from "../theme/index.js";
24
+ import type { RadiusKey } from "../theme/index.js";
25
+ import { CONTRACTS } from "../contracts/contract-types.js";
26
+ import { resolveImageShape } from "./image.styles.js";
26
27
 
27
28
  type ContentFit = "cover" | "contain";
28
29
 
@@ -85,5 +86,6 @@ export function Image({
85
86
  );
86
87
  }
87
88
 
88
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
89
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
90
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
89
91
  Image.__contract = CONTRACTS.image;
@@ -8,7 +8,8 @@
8
8
  * Escape → Android back(onRequestClose)、open/closing state → RN Modal の visible + animationType。
9
9
  * - overlay tap で閉じる(MODAL_OVERLAY_REQUIRED)。panel 側は onStartShouldSetResponder で
10
10
  * touch を claim し、overlay への伝播を止める(panel 内 tap では閉じない)。
11
- * - title 行の右端に閉じるボタン「×」(MODAL_CLOSE_REQUIRED、accessibilityLabel="閉じる")。
11
+ * - title 行の右端に閉じるボタン「×」(MODAL_CLOSE_REQUIRED、accessibilityLabel は既定 "閉じる"、
12
+ * closeAccessibilityLabel で差し替え可能)。当たり判定は正典パターン(24 + hitSlop 10 = 44pt)。
12
13
  * title は melta の Text ではなく titleStyle 直(recipe が typography を持つ)。
13
14
  * - style の決定は pure resolver(modal.styles.ts)に分離 — recipe との機械照合対象。
14
15
  * title 行の row layout と × の見た目は recipe 外の構造 glue(token 経由で解決)。
@@ -21,14 +22,15 @@ import {
21
22
  Text as RNText,
22
23
  View,
23
24
  } from "react-native";
24
- import { useTheme } from "../theme";
25
- import { CONTRACTS } from "../contracts/contract-types";
25
+ import { useTheme } from "../theme/index.js";
26
+ import { CONTRACTS } from "../contracts/contract-types.js";
26
27
  import {
28
+ MODAL_CLOSE_TAP_TARGET,
27
29
  resolveModalStyle,
28
30
  type ModalSize,
29
31
  type ModalStyle,
30
32
  type ModalVariant,
31
- } from "./modal.styles";
33
+ } from "./modal.styles.js";
32
34
 
33
35
  interface ModalProps {
34
36
  /** 表示制御(契約の open state に対応。閉じるアニメは RN Modal に委譲)。 */
@@ -43,6 +45,11 @@ interface ModalProps {
43
45
  size?: ModalSize;
44
46
  /** アクション群(任意)。footerStyle の row に載る。 */
45
47
  footer?: ReactNode;
48
+ /**
49
+ * × ボタンの accessibilityLabel(i18n フック)。既定は日本語 "閉じる"
50
+ * (既存アプリの読み上げを変えないため据え置き)。
51
+ */
52
+ closeAccessibilityLabel?: string;
46
53
  testID?: string;
47
54
  /** body。 */
48
55
  children: ReactNode;
@@ -55,6 +62,7 @@ export function Modal({
55
62
  variant = "confirmation",
56
63
  size = "medium",
57
64
  footer,
65
+ closeAccessibilityLabel = "閉じる",
58
66
  testID,
59
67
  children,
60
68
  }: ModalProps) {
@@ -86,11 +94,19 @@ export function Modal({
86
94
  <RNText style={[styles.titleStyle, { flex: 1 }]} accessibilityRole="header">
87
95
  {title}
88
96
  </RNText>
97
+ {/* 最小タップターゲット確保: 正典パターン(視覚 24 + hitSlop 10 = 実効 44)。
98
+ × のグリフは幅 13pt 前後しかないので、箱の下限を持たせないと 44 に届かない。 */}
89
99
  <Pressable
90
100
  onPress={onClose}
91
101
  accessibilityRole="button"
92
- accessibilityLabel="閉じる"
93
- hitSlop={theme.spacing["2"]}
102
+ accessibilityLabel={closeAccessibilityLabel}
103
+ hitSlop={MODAL_CLOSE_TAP_TARGET.hitSlop}
104
+ style={{
105
+ minWidth: MODAL_CLOSE_TAP_TARGET.minWidth,
106
+ minHeight: MODAL_CLOSE_TAP_TARGET.minHeight,
107
+ alignItems: "center",
108
+ justifyContent: "center",
109
+ }}
94
110
  >
95
111
  <RNText
96
112
  style={{
@@ -23,14 +23,14 @@ import {
23
23
  type StyleProp,
24
24
  type ViewStyle,
25
25
  } from "react-native";
26
- import { useTheme } from "../theme";
27
- import { CONTRACTS } from "../contracts/contract-types";
26
+ import { useTheme } from "../theme/index.js";
27
+ import { CONTRACTS } from "../contracts/contract-types.js";
28
28
  import {
29
29
  PROGRESS_INDETERMINATE_FILL_RATIO,
30
30
  clampProgressValue,
31
31
  resolveProgressStyles,
32
32
  type ProgressVariant,
33
- } from "./progress.styles";
33
+ } from "./progress.styles.js";
34
34
 
35
35
  interface ProgressProps {
36
36
  /** 進捗タイプ(contract variant)。default "primary"。 */
@@ -120,5 +120,6 @@ export function Progress({ variant = "primary", value, label, style, testID }: P
120
120
  );
121
121
  }
122
122
 
123
- // Phase 2 conformance test 用の contract メタ(§2 A-3)。
123
+ // conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
124
+ // 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
124
125
  Progress.__contract = CONTRACTS.progress;