melta-app 0.5.3 → 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.
- package/README.md +493 -186
- package/docs/architecture.md +99 -0
- package/eslint-rules/melta.d.mts +44 -0
- package/lib/module/a11y/tap-target.js +41 -0
- package/lib/module/a11y/tap-target.js.map +1 -0
- package/lib/module/components/ActionSheet.js +20 -6
- package/lib/module/components/ActionSheet.js.map +1 -1
- package/lib/module/components/Alert.js +11 -7
- package/lib/module/components/Alert.js.map +1 -1
- package/lib/module/components/Avatar.js +7 -2
- package/lib/module/components/Avatar.js.map +1 -1
- package/lib/module/components/BottomSheet.js +11 -3
- package/lib/module/components/BottomSheet.js.map +1 -1
- package/lib/module/components/Card.js +4 -3
- package/lib/module/components/Card.js.map +1 -1
- package/lib/module/components/Checkbox.js +8 -5
- package/lib/module/components/Checkbox.js.map +1 -1
- package/lib/module/components/EmptyState.js +2 -1
- package/lib/module/components/EmptyState.js.map +1 -1
- package/lib/module/components/Header.js.map +1 -1
- package/lib/module/components/Image.js +5 -3
- package/lib/module/components/Image.js.map +1 -1
- package/lib/module/components/Modal.js +12 -4
- package/lib/module/components/Modal.js.map +1 -1
- package/lib/module/components/Progress.js +2 -1
- package/lib/module/components/Progress.js.map +1 -1
- package/lib/module/components/Radio.js +11 -3
- package/lib/module/components/Radio.js.map +1 -1
- package/lib/module/components/Screen.js +28 -3
- package/lib/module/components/Screen.js.map +1 -1
- package/lib/module/components/Skeleton.js +2 -1
- package/lib/module/components/Skeleton.js.map +1 -1
- package/lib/module/components/Surface.js +2 -1
- package/lib/module/components/Surface.js.map +1 -1
- package/lib/module/components/TextField.js +16 -4
- package/lib/module/components/TextField.js.map +1 -1
- package/lib/module/components/Toast.js +17 -9
- package/lib/module/components/Toast.js.map +1 -1
- package/lib/module/components/Toggle.js +13 -4
- package/lib/module/components/Toggle.js.map +1 -1
- package/lib/module/components/alert.styles.js +12 -26
- package/lib/module/components/alert.styles.js.map +1 -1
- package/lib/module/components/avatar.styles.js +24 -2
- package/lib/module/components/avatar.styles.js.map +1 -1
- package/lib/module/components/card.validate.js.map +1 -1
- package/lib/module/components/checkbox.styles.js +5 -2
- package/lib/module/components/checkbox.styles.js.map +1 -1
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/modal.styles.js +9 -0
- package/lib/module/components/modal.styles.js.map +1 -1
- package/lib/module/components/radio.styles.js +11 -2
- package/lib/module/components/radio.styles.js.map +1 -1
- package/lib/module/components/safe-area-registry.js +64 -13
- package/lib/module/components/safe-area-registry.js.map +1 -1
- package/lib/module/components/status-colors.js +53 -0
- package/lib/module/components/status-colors.js.map +1 -0
- package/lib/module/components/textfield.styles.js +15 -8
- package/lib/module/components/textfield.styles.js.map +1 -1
- package/lib/module/components/toast.styles.js +36 -25
- package/lib/module/components/toast.styles.js.map +1 -1
- package/lib/module/components/toggle.styles.js +20 -0
- package/lib/module/components/toggle.styles.js.map +1 -1
- package/lib/module/icons/Icon.js +4 -1
- package/lib/module/icons/Icon.js.map +1 -1
- package/lib/module/icons/icon.styles.js +32 -1
- package/lib/module/icons/icon.styles.js.map +1 -1
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/Button.js +23 -11
- package/lib/module/primitives/Button.js.map +1 -1
- package/lib/module/primitives/Metric.js +2 -1
- package/lib/module/primitives/Metric.js.map +1 -1
- package/lib/module/primitives/Row.js.map +1 -1
- package/lib/module/primitives/Stack.js.map +1 -1
- package/lib/module/primitives/Tag.js +15 -7
- package/lib/module/primitives/Tag.js.map +1 -1
- package/lib/module/primitives/Text.js +28 -7
- package/lib/module/primitives/Text.js.map +1 -1
- package/lib/module/primitives/_internal/focus-ring.js.map +1 -1
- package/lib/module/primitives/button.styles.js +58 -8
- package/lib/module/primitives/button.styles.js.map +1 -1
- package/lib/module/primitives/index.js.map +1 -1
- package/lib/module/primitives/metric.styles.js +4 -1
- package/lib/module/primitives/metric.styles.js.map +1 -1
- package/lib/module/primitives/tag.styles.js +42 -3
- package/lib/module/primitives/tag.styles.js.map +1 -1
- package/lib/module/primitives/text.styles.js +10 -5
- package/lib/module/primitives/text.styles.js.map +1 -1
- package/lib/module/safe-area/index.js +11 -5
- package/lib/module/safe-area/index.js.map +1 -1
- package/lib/module/theme/ThemeProvider.js +5 -2
- package/lib/module/theme/ThemeProvider.js.map +1 -1
- package/lib/module/theme/define-theme.js +27 -1
- package/lib/module/theme/define-theme.js.map +1 -1
- package/lib/module/theme/index.js +7 -8
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/letter-spacing.js +20 -0
- package/lib/module/theme/letter-spacing.js.map +1 -0
- package/lib/module/theme/line-height.js +52 -0
- package/lib/module/theme/line-height.js.map +1 -0
- package/lib/module/theme/native-theme.js +8 -7
- package/lib/module/theme/native-theme.js.map +1 -1
- package/lib/typescript/src/a11y/tap-target.d.ts +34 -0
- package/lib/typescript/src/a11y/tap-target.d.ts.map +1 -0
- package/lib/typescript/src/components/ActionSheet.d.ts +8 -1
- package/lib/typescript/src/components/ActionSheet.d.ts.map +1 -1
- package/lib/typescript/src/components/Alert.d.ts +9 -2
- package/lib/typescript/src/components/Alert.d.ts.map +1 -1
- package/lib/typescript/src/components/Avatar.d.ts +1 -1
- package/lib/typescript/src/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/BottomSheet.d.ts +4 -1
- package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/src/components/Card.d.ts +2 -2
- package/lib/typescript/src/components/Checkbox.d.ts +3 -3
- package/lib/typescript/src/components/Checkbox.d.ts.map +1 -1
- package/lib/typescript/src/components/Image.d.ts +4 -3
- package/lib/typescript/src/components/Image.d.ts.map +1 -1
- package/lib/typescript/src/components/Modal.d.ts +9 -3
- package/lib/typescript/src/components/Modal.d.ts.map +1 -1
- package/lib/typescript/src/components/Progress.d.ts +1 -1
- package/lib/typescript/src/components/Progress.d.ts.map +1 -1
- package/lib/typescript/src/components/Radio.d.ts +3 -2
- package/lib/typescript/src/components/Radio.d.ts.map +1 -1
- package/lib/typescript/src/components/Screen.d.ts +28 -5
- package/lib/typescript/src/components/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/Skeleton.d.ts +1 -1
- package/lib/typescript/src/components/Skeleton.d.ts.map +1 -1
- package/lib/typescript/src/components/Surface.d.ts +1 -1
- package/lib/typescript/src/components/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/TextField.d.ts +11 -4
- package/lib/typescript/src/components/TextField.d.ts.map +1 -1
- package/lib/typescript/src/components/Toast.d.ts +10 -3
- package/lib/typescript/src/components/Toast.d.ts.map +1 -1
- package/lib/typescript/src/components/Toggle.d.ts +3 -2
- package/lib/typescript/src/components/Toggle.d.ts.map +1 -1
- package/lib/typescript/src/components/action-sheet.styles.d.ts +1 -1
- package/lib/typescript/src/components/action-sheet.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/alert.styles.d.ts +14 -14
- package/lib/typescript/src/components/alert.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar.styles.d.ts +36 -1
- package/lib/typescript/src/components/avatar.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet.styles.d.ts +1 -1
- package/lib/typescript/src/components/bottom-sheet.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/card.styles.d.ts +1 -1
- package/lib/typescript/src/components/card.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/card.validate.d.ts +1 -1
- package/lib/typescript/src/components/card.validate.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox.styles.d.ts +6 -3
- package/lib/typescript/src/components/checkbox.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state.styles.d.ts +1 -1
- package/lib/typescript/src/components/empty-state.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/header.styles.d.ts +1 -1
- package/lib/typescript/src/components/header.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/image.styles.d.ts +1 -1
- package/lib/typescript/src/components/image.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/index.d.ts +18 -18
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/modal.styles.d.ts +13 -2
- package/lib/typescript/src/components/modal.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/progress.styles.d.ts +1 -1
- package/lib/typescript/src/components/progress.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/radio.styles.d.ts +10 -2
- package/lib/typescript/src/components/radio.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/safe-area-registry.d.ts +47 -11
- package/lib/typescript/src/components/safe-area-registry.d.ts.map +1 -1
- package/lib/typescript/src/components/screen.styles.d.ts +1 -1
- package/lib/typescript/src/components/screen.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/skeleton.styles.d.ts +1 -1
- package/lib/typescript/src/components/skeleton.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/status-colors.d.ts +33 -0
- package/lib/typescript/src/components/status-colors.d.ts.map +1 -0
- package/lib/typescript/src/components/surface.styles.d.ts +1 -1
- package/lib/typescript/src/components/surface.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/textfield.styles.d.ts +9 -5
- package/lib/typescript/src/components/textfield.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/toast.styles.d.ts +34 -14
- package/lib/typescript/src/components/toast.styles.d.ts.map +1 -1
- package/lib/typescript/src/components/toggle.styles.d.ts +17 -1
- package/lib/typescript/src/components/toggle.styles.d.ts.map +1 -1
- package/lib/typescript/src/icons/Icon.d.ts +12 -6
- package/lib/typescript/src/icons/Icon.d.ts.map +1 -1
- package/lib/typescript/src/icons/icon.styles.d.ts +14 -2
- package/lib/typescript/src/icons/icon.styles.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +3 -3
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Button.d.ts +8 -4
- package/lib/typescript/src/primitives/Button.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Metric.d.ts +1 -1
- package/lib/typescript/src/primitives/Metric.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Row.d.ts +2 -2
- package/lib/typescript/src/primitives/Row.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Stack.d.ts +2 -2
- package/lib/typescript/src/primitives/Stack.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Tag.d.ts +2 -2
- package/lib/typescript/src/primitives/Tag.d.ts.map +1 -1
- package/lib/typescript/src/primitives/Text.d.ts +20 -6
- package/lib/typescript/src/primitives/Text.d.ts.map +1 -1
- package/lib/typescript/src/primitives/button.styles.d.ts +44 -7
- package/lib/typescript/src/primitives/button.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/index.d.ts +6 -6
- package/lib/typescript/src/primitives/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/metric.styles.d.ts +1 -1
- package/lib/typescript/src/primitives/metric.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/row.styles.d.ts +1 -1
- package/lib/typescript/src/primitives/row.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/stack.styles.d.ts +1 -1
- package/lib/typescript/src/primitives/stack.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/tag.styles.d.ts +34 -1
- package/lib/typescript/src/primitives/tag.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.styles.d.ts +2 -4
- package/lib/typescript/src/primitives/text.styles.d.ts.map +1 -1
- package/lib/typescript/src/safe-area/index.d.ts +19 -5
- package/lib/typescript/src/safe-area/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/ThemeProvider.d.ts +2 -2
- package/lib/typescript/src/theme/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/theme/define-theme.d.ts +15 -2
- package/lib/typescript/src/theme/define-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/index.d.ts +14 -13
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/letter-spacing.d.ts +15 -0
- package/lib/typescript/src/theme/letter-spacing.d.ts.map +1 -0
- package/lib/typescript/src/theme/line-height.d.ts +44 -0
- package/lib/typescript/src/theme/line-height.d.ts.map +1 -0
- package/lib/typescript/src/theme/native-theme.d.ts +1 -1
- package/lib/typescript/src/theme/native-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +10 -1
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/llms.txt +2 -1
- package/package.json +22 -7
- package/src/a11y/tap-target.ts +38 -0
- package/src/components/ActionSheet.tsx +25 -15
- package/src/components/Alert.tsx +37 -10
- package/src/components/Avatar.tsx +12 -4
- package/src/components/BottomSheet.tsx +16 -13
- package/src/components/Card.tsx +9 -8
- package/src/components/Checkbox.tsx +12 -9
- package/src/components/EmptyState.tsx +7 -6
- package/src/components/Header.tsx +4 -4
- package/src/components/Image.tsx +9 -7
- package/src/components/Modal.tsx +22 -6
- package/src/components/Progress.tsx +5 -4
- package/src/components/Radio.tsx +11 -6
- package/src/components/Screen.tsx +66 -8
- package/src/components/Skeleton.tsx +6 -5
- package/src/components/Surface.tsx +5 -4
- package/src/components/TextField.tsx +25 -7
- package/src/components/Toast.tsx +33 -11
- package/src/components/Toggle.tsx +15 -6
- package/src/components/action-sheet.styles.ts +1 -1
- package/src/components/alert.styles.ts +13 -25
- package/src/components/avatar.styles.ts +25 -3
- package/src/components/bottom-sheet.styles.ts +1 -1
- package/src/components/card.styles.ts +1 -1
- package/src/components/card.validate.ts +2 -2
- package/src/components/checkbox.styles.ts +6 -3
- package/src/components/empty-state.styles.ts +1 -1
- package/src/components/header.styles.ts +1 -1
- package/src/components/image.styles.ts +1 -1
- package/src/components/index.ts +18 -18
- package/src/components/modal.styles.ts +11 -2
- package/src/components/progress.styles.ts +1 -1
- package/src/components/radio.styles.ts +11 -2
- package/src/components/safe-area-registry.ts +81 -14
- package/src/components/screen.styles.ts +1 -1
- package/src/components/skeleton.styles.ts +1 -1
- package/src/components/status-colors.ts +59 -0
- package/src/components/surface.styles.ts +1 -1
- package/src/components/textfield.styles.ts +18 -15
- package/src/components/toast.styles.ts +32 -25
- package/src/components/toggle.styles.ts +21 -1
- package/src/icons/Icon.tsx +13 -8
- package/src/icons/icon.styles.ts +38 -3
- package/src/icons/index.ts +3 -3
- package/src/index.ts +4 -4
- package/src/primitives/Button.tsx +28 -14
- package/src/primitives/Metric.tsx +5 -4
- package/src/primitives/Row.tsx +4 -4
- package/src/primitives/Stack.tsx +4 -4
- package/src/primitives/Tag.tsx +29 -11
- package/src/primitives/Text.tsx +42 -9
- package/src/primitives/_internal/focus-ring.tsx +1 -1
- package/src/primitives/button.styles.ts +63 -10
- package/src/primitives/index.ts +6 -6
- package/src/primitives/metric.styles.ts +9 -2
- package/src/primitives/row.styles.ts +1 -1
- package/src/primitives/stack.styles.ts +1 -1
- package/src/primitives/tag.styles.ts +43 -4
- package/src/primitives/text.styles.ts +10 -6
- package/src/safe-area/index.ts +21 -7
- package/src/theme/ThemeProvider.tsx +8 -5
- package/src/theme/define-theme.ts +33 -2
- package/src/theme/index.ts +18 -16
- package/src/theme/letter-spacing.ts +17 -0
- package/src/theme/line-height.ts +53 -0
- package/src/theme/native-theme.ts +9 -8
- package/src/theme/types.ts +10 -1
package/src/components/Alert.tsx
CHANGED
|
@@ -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 {
|
|
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({
|
|
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
|
-
// 最小タップターゲット確保:
|
|
87
|
+
// 最小タップターゲット確保: 正典パターン 24 + hitSlop 10 = 実効 44pt(Tag removable と同型)。
|
|
67
88
|
<Pressable
|
|
68
89
|
onPress={onClose}
|
|
69
90
|
accessibilityRole="button"
|
|
70
|
-
accessibilityLabel=
|
|
71
|
-
hitSlop={
|
|
72
|
-
style={{
|
|
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
|
-
//
|
|
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
|
-
|
|
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)のため、
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
77
|
+
</SafeArea>
|
|
75
78
|
</View>
|
|
76
79
|
</Pressable>
|
|
77
80
|
</RNModal>
|
package/src/components/Card.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
8
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 として読み上げ)。 */
|
package/src/components/Image.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image — 画像表示 component(設計書 §1)。contract: image(app 先行定義)。
|
|
3
3
|
*
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
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
|
-
//
|
|
89
|
+
// conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
|
|
90
|
+
// 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
|
|
89
91
|
Image.__contract = CONTRACTS.image;
|
package/src/components/Modal.tsx
CHANGED
|
@@ -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={
|
|
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
|
-
//
|
|
123
|
+
// conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
|
|
124
|
+
// 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
|
|
124
125
|
Progress.__contract = CONTRACTS.progress;
|
package/src/components/Radio.tsx
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* - variant はレイアウト: vertical(標準)/ horizontal(wrap する横並び)/ card-style
|
|
8
8
|
* (各 option を枠付きカードにする。web の grid 2col は RN では縦積みに読み替え)。
|
|
9
9
|
* - RN にネイティブ radio が無いため各 option は Pressable + 描画(circle + 内側 dot)。
|
|
10
|
+
* option 行は minHeight 44pt(RADIO_SPEC.optionMinHeight)で実効タップ標的を確保する。
|
|
10
11
|
* - states(contract → prop の写像): selected/unselected → value との一致、
|
|
11
12
|
* error → error?: string(circle の borderColor 差分 + errorText 表示。selected と併用時は
|
|
12
13
|
* error を優先 = 契約 stateSpecs)、disabled → グループ全体 or option 個別(opacity 0.5 + 非活性)。
|
|
13
|
-
* focus
|
|
14
|
+
* focus は非対応(recipe にも focus 差分なし)。
|
|
14
15
|
* - a11y: グループに accessibilityRole="radiogroup"、各 option に "radio" +
|
|
15
16
|
* accessibilityState={{ checked, disabled }}。
|
|
16
17
|
* - 色・寸法の決定は pure resolver(radio.styles.ts)に分離。
|
|
@@ -19,15 +20,15 @@
|
|
|
19
20
|
|
|
20
21
|
import { useMemo } from "react";
|
|
21
22
|
import { Pressable, View } from "react-native";
|
|
22
|
-
import { Text } from "../primitives/Text";
|
|
23
|
-
import { useTheme } from "../theme";
|
|
24
|
-
import { CONTRACTS } from "../contracts/contract-types";
|
|
23
|
+
import { Text } from "../primitives/Text.js";
|
|
24
|
+
import { useTheme } from "../theme/index.js";
|
|
25
|
+
import { CONTRACTS } from "../contracts/contract-types.js";
|
|
25
26
|
import {
|
|
26
27
|
RADIO_SPEC,
|
|
27
28
|
resolveRadioCircleStyle,
|
|
28
29
|
resolveRadioGroupStyle,
|
|
29
30
|
type RadioVariant,
|
|
30
|
-
} from "./radio.styles";
|
|
31
|
+
} from "./radio.styles.js";
|
|
31
32
|
|
|
32
33
|
export interface RadioOption {
|
|
33
34
|
label: string;
|
|
@@ -99,6 +100,9 @@ export function Radio({
|
|
|
99
100
|
accessibilityState={{ checked: selected, disabled: optionDisabled }}
|
|
100
101
|
accessibilityLabel={option.label}
|
|
101
102
|
style={[
|
|
103
|
+
// 実効タップ標的 44pt(A11Y_MIN_TAP_TARGET_44)。option 行は背景を持たないので
|
|
104
|
+
// minHeight で下げ止める(視覚不変・隣接 option と当たり判定が重ならない)。
|
|
105
|
+
{ minHeight: RADIO_SPEC.optionMinHeight, justifyContent: "center" },
|
|
102
106
|
variant === "card-style"
|
|
103
107
|
? [group.cardStyle, selected ? group.cardSelectedStyle : null]
|
|
104
108
|
: null,
|
|
@@ -150,5 +154,6 @@ export function Radio({
|
|
|
150
154
|
);
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
//
|
|
157
|
+
// conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
|
|
158
|
+
// 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
|
|
154
159
|
Radio.__contract = CONTRACTS.radio;
|
|
@@ -13,22 +13,54 @@
|
|
|
13
13
|
* react-native-safe-area-context 利用者は subpath "melta-app/safe-area" の
|
|
14
14
|
* enableSafeAreaContext() で context hook + View adapter に差し替えられる
|
|
15
15
|
* (rally-nav dogfood 由来、RN 0.85 Fabric/Android の初回 inset flash 対応)。
|
|
16
|
+
* 適用する edge は `edges` prop(省略時は enableSafeAreaContext の既定)。
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
import { useMemo, type ReactNode } from "react";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
import { useEffect, useMemo, type ReactNode, type Ref } from "react";
|
|
20
|
+
import {
|
|
21
|
+
ScrollView,
|
|
22
|
+
View,
|
|
23
|
+
type ScrollViewProps,
|
|
24
|
+
type StyleProp,
|
|
25
|
+
type ViewStyle,
|
|
26
|
+
} from "react-native";
|
|
27
|
+
import { resolveSafeAreaView, type SafeAreaEdge } from "./safe-area-registry.js";
|
|
28
|
+
import { isDev } from "../theme/define-theme.js";
|
|
29
|
+
import { useTheme } from "../theme/index.js";
|
|
30
|
+
import { CONTRACTS, type VariantOf } from "../contracts/contract-types.js";
|
|
31
|
+
import { resolveScreenStyle, type ScreenPadding, type ScreenStyle } from "./screen.styles.js";
|
|
32
|
+
|
|
33
|
+
/** dev 警告の重複抑止(inline object の scrollViewProps で毎 render 鳴らさない)。 */
|
|
34
|
+
const warned = new Set<string>();
|
|
24
35
|
|
|
25
36
|
interface ScreenProps {
|
|
26
37
|
/** 画面の骨格(contract variant)。default "scroll"。 */
|
|
27
38
|
variant?: VariantOf<"screen">;
|
|
28
39
|
/** content の padding(spacing token キー限定 | "none")。default "4"。 */
|
|
29
40
|
padding?: ScreenPadding;
|
|
41
|
+
/**
|
|
42
|
+
* safe-area を適用する辺。省略時は enableSafeAreaContext({ edges }) の既定。
|
|
43
|
+
* タブバーが bottom を自前処理する画面は `["top"]` のように絞る。
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ adapter 未登録(RN core SafeAreaView)では**無視される**(core は辺を選べない)。
|
|
46
|
+
*/
|
|
47
|
+
edges?: readonly SafeAreaEdge[];
|
|
30
48
|
/** scroll 外(SafeArea 直下)に固定されるヘッダー slot(Header を差す想定)。 */
|
|
31
49
|
header?: ReactNode;
|
|
50
|
+
/**
|
|
51
|
+
* 内部 ScrollView への passthrough(variant="scroll" のみ)。children は Screen が持つ。
|
|
52
|
+
*
|
|
53
|
+
* - `contentContainerStyle` は DS の padding と**配列合成**される(渡した側が後勝ち。
|
|
54
|
+
* 意図的な非常口 — DS の padding を残したい場合は padding 系を書かないこと)。
|
|
55
|
+
* - `contentInsetAdjustmentBehavior` は**型で遮断**(safe-area は Screen 側が持つので
|
|
56
|
+
* iOS の自動 inset と必ず二重になる。正当な用途が無いため Omit)。
|
|
57
|
+
*/
|
|
58
|
+
scrollViewProps?: Omit<ScrollViewProps, "children" | "contentInsetAdjustmentBehavior">;
|
|
59
|
+
/**
|
|
60
|
+
* 内部 ScrollView の ref(scrollTo / scrollToEnd 用、variant="scroll" のみ)。
|
|
61
|
+
* Screen 自体は `__contract` 静的プロパティを持つため forwardRef 化せず別 prop にしている。
|
|
62
|
+
*/
|
|
63
|
+
scrollViewRef?: Ref<ScrollView>;
|
|
32
64
|
style?: StyleProp<ViewStyle>;
|
|
33
65
|
testID?: string;
|
|
34
66
|
children: ReactNode;
|
|
@@ -37,7 +69,10 @@ interface ScreenProps {
|
|
|
37
69
|
export function Screen({
|
|
38
70
|
variant = "scroll",
|
|
39
71
|
padding = "4",
|
|
72
|
+
edges,
|
|
40
73
|
header,
|
|
74
|
+
scrollViewProps,
|
|
75
|
+
scrollViewRef,
|
|
41
76
|
style,
|
|
42
77
|
testID,
|
|
43
78
|
children,
|
|
@@ -49,13 +84,36 @@ export function Screen({
|
|
|
49
84
|
[theme, mode, variant, padding],
|
|
50
85
|
);
|
|
51
86
|
|
|
52
|
-
|
|
87
|
+
// fixed には内部 ScrollView が無い=渡された prop は黙って捨てられる。dev で気づけるようにする。
|
|
88
|
+
const ignoresScrollProps =
|
|
89
|
+
variant === "fixed" && (scrollViewProps != null || scrollViewRef != null);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isDev || !ignoresScrollProps) return;
|
|
92
|
+
const message =
|
|
93
|
+
'melta: Screen variant="fixed" では scrollViewProps / scrollViewRef は適用されない' +
|
|
94
|
+
"(内部 ScrollView が無いため)。variant=\"scroll\" にするか prop を外すこと。";
|
|
95
|
+
if (warned.has(message)) return;
|
|
96
|
+
warned.add(message);
|
|
97
|
+
console.warn(message);
|
|
98
|
+
}, [ignoresScrollProps]);
|
|
99
|
+
|
|
100
|
+
const SafeArea = resolveSafeAreaView(edges);
|
|
53
101
|
|
|
54
102
|
return (
|
|
55
103
|
<SafeArea style={[safeAreaStyle, style]} testID={testID}>
|
|
56
104
|
{header}
|
|
57
105
|
{variant === "scroll" ? (
|
|
58
|
-
<ScrollView
|
|
106
|
+
<ScrollView
|
|
107
|
+
ref={scrollViewRef}
|
|
108
|
+
// iOS の既定 0 は onScroll が 1 ドラッグ 1 発しか来ない(スクロール連動ヘッダが動かない)。
|
|
109
|
+
// spread より前に置いて消費者が上書きできるようにする。
|
|
110
|
+
scrollEventThrottle={16}
|
|
111
|
+
{...scrollViewProps}
|
|
112
|
+
// contentContainerStyle は素の spread だと DS の padding ごと消えるため配列合成(後勝ち)。
|
|
113
|
+
contentContainerStyle={[contentStyle, scrollViewProps?.contentContainerStyle]}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</ScrollView>
|
|
59
117
|
) : (
|
|
60
118
|
<View style={contentStyle}>{children}</View>
|
|
61
119
|
)}
|
|
@@ -22,9 +22,9 @@ import {
|
|
|
22
22
|
type StyleProp,
|
|
23
23
|
type ViewStyle,
|
|
24
24
|
} from "react-native";
|
|
25
|
-
import { Surface } from "./Surface";
|
|
26
|
-
import { useTheme } from "../theme";
|
|
27
|
-
import { CONTRACTS } from "../contracts/contract-types";
|
|
25
|
+
import { Surface } from "./Surface.js";
|
|
26
|
+
import { useTheme } from "../theme/index.js";
|
|
27
|
+
import { CONTRACTS } from "../contracts/contract-types.js";
|
|
28
28
|
import {
|
|
29
29
|
CARD_SURFACE_SPEC,
|
|
30
30
|
PULSE_OPACITY,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
resolveSkeletonStates,
|
|
33
33
|
resolveSkeletonVariants,
|
|
34
34
|
type SkeletonVariant,
|
|
35
|
-
} from "./skeleton.styles";
|
|
35
|
+
} from "./skeleton.styles.js";
|
|
36
36
|
|
|
37
37
|
interface SkeletonProps {
|
|
38
38
|
variant?: SkeletonVariant;
|
|
@@ -150,5 +150,6 @@ export function Skeleton({
|
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
//
|
|
153
|
+
// conformance test 用の contract メタ(§2 A-3)。scripts/lib/conformance.test.ts が
|
|
154
|
+
// 「この宣言が正しい contract を指しているか」を静的スキャンで照合する。
|
|
154
155
|
Skeleton.__contract = CONTRACTS.skeleton;
|