melta-app 0.1.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/LICENSE +21 -0
- package/README.md +197 -0
- package/THIRD_PARTY_LICENSES.md +198 -0
- package/lib/module/components/ActionSheet.js +87 -0
- package/lib/module/components/ActionSheet.js.map +1 -0
- package/lib/module/components/Alert.js +93 -0
- package/lib/module/components/Alert.js.map +1 -0
- package/lib/module/components/Avatar.js +93 -0
- package/lib/module/components/Avatar.js.map +1 -0
- package/lib/module/components/BottomSheet.js +69 -0
- package/lib/module/components/BottomSheet.js.map +1 -0
- package/lib/module/components/Card.js +93 -0
- package/lib/module/components/Card.js.map +1 -0
- package/lib/module/components/Checkbox.js +115 -0
- package/lib/module/components/Checkbox.js.map +1 -0
- package/lib/module/components/EmptyState.js +70 -0
- package/lib/module/components/EmptyState.js.map +1 -0
- package/lib/module/components/Header.js +53 -0
- package/lib/module/components/Header.js.map +1 -0
- package/lib/module/components/Image.js +67 -0
- package/lib/module/components/Image.js.map +1 -0
- package/lib/module/components/Modal.js +95 -0
- package/lib/module/components/Modal.js.map +1 -0
- package/lib/module/components/Progress.js +113 -0
- package/lib/module/components/Progress.js.map +1 -0
- package/lib/module/components/Radio.js +134 -0
- package/lib/module/components/Radio.js.map +1 -0
- package/lib/module/components/Screen.js +57 -0
- package/lib/module/components/Screen.js.map +1 -0
- package/lib/module/components/Skeleton.js +169 -0
- package/lib/module/components/Skeleton.js.map +1 -0
- package/lib/module/components/Surface.js +49 -0
- package/lib/module/components/Surface.js.map +1 -0
- package/lib/module/components/TextField.js +84 -0
- package/lib/module/components/TextField.js.map +1 -0
- package/lib/module/components/Toast.js +105 -0
- package/lib/module/components/Toast.js.map +1 -0
- package/lib/module/components/Toggle.js +77 -0
- package/lib/module/components/Toggle.js.map +1 -0
- package/lib/module/components/action-sheet.styles.js +70 -0
- package/lib/module/components/action-sheet.styles.js.map +1 -0
- package/lib/module/components/alert.styles.js +80 -0
- package/lib/module/components/alert.styles.js.map +1 -0
- package/lib/module/components/avatar.styles.js +94 -0
- package/lib/module/components/avatar.styles.js.map +1 -0
- package/lib/module/components/bottom-sheet.styles.js +55 -0
- package/lib/module/components/bottom-sheet.styles.js.map +1 -0
- package/lib/module/components/card.styles.js +50 -0
- package/lib/module/components/card.styles.js.map +1 -0
- package/lib/module/components/checkbox.styles.js +55 -0
- package/lib/module/components/checkbox.styles.js.map +1 -0
- package/lib/module/components/empty-state.styles.js +60 -0
- package/lib/module/components/empty-state.styles.js.map +1 -0
- package/lib/module/components/header.styles.js +33 -0
- package/lib/module/components/header.styles.js.map +1 -0
- package/lib/module/components/image.styles.js +37 -0
- package/lib/module/components/image.styles.js.map +1 -0
- package/lib/module/components/index.js +26 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/components/modal.styles.js +68 -0
- package/lib/module/components/modal.styles.js.map +1 -0
- package/lib/module/components/progress.styles.js +55 -0
- package/lib/module/components/progress.styles.js.map +1 -0
- package/lib/module/components/radio.styles.js +96 -0
- package/lib/module/components/radio.styles.js.map +1 -0
- package/lib/module/components/screen.styles.js +38 -0
- package/lib/module/components/screen.styles.js.map +1 -0
- package/lib/module/components/skeleton.styles.js +114 -0
- package/lib/module/components/skeleton.styles.js.map +1 -0
- package/lib/module/components/surface.styles.js +37 -0
- package/lib/module/components/surface.styles.js.map +1 -0
- package/lib/module/components/textfield.styles.js +121 -0
- package/lib/module/components/textfield.styles.js.map +1 -0
- package/lib/module/components/toast.styles.js +81 -0
- package/lib/module/components/toast.styles.js.map +1 -0
- package/lib/module/components/toggle.styles.js +69 -0
- package/lib/module/components/toggle.styles.js.map +1 -0
- package/lib/module/contracts/contract-types.js +192 -0
- package/lib/module/contracts/contract-types.js.map +1 -0
- package/lib/module/icons/Icon.js +57 -0
- package/lib/module/icons/Icon.js.map +1 -0
- package/lib/module/icons/glyphs.js +257 -0
- package/lib/module/icons/glyphs.js.map +1 -0
- package/lib/module/icons/icon.styles.js +28 -0
- package/lib/module/icons/icon.styles.js.map +1 -0
- package/lib/module/icons/index.js +14 -0
- package/lib/module/icons/index.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/primitives/Button.js +105 -0
- package/lib/module/primitives/Button.js.map +1 -0
- package/lib/module/primitives/Metric.js +68 -0
- package/lib/module/primitives/Metric.js.map +1 -0
- package/lib/module/primitives/Row.js +43 -0
- package/lib/module/primitives/Row.js.map +1 -0
- package/lib/module/primitives/Stack.js +39 -0
- package/lib/module/primitives/Stack.js.map +1 -0
- package/lib/module/primitives/Tag.js +160 -0
- package/lib/module/primitives/Tag.js.map +1 -0
- package/lib/module/primitives/Text.js +54 -0
- package/lib/module/primitives/Text.js.map +1 -0
- package/lib/module/primitives/_internal/focus-ring.js +57 -0
- package/lib/module/primitives/_internal/focus-ring.js.map +1 -0
- package/lib/module/primitives/button.styles.js +88 -0
- package/lib/module/primitives/button.styles.js.map +1 -0
- package/lib/module/primitives/index.js +11 -0
- package/lib/module/primitives/index.js.map +1 -0
- package/lib/module/primitives/metric.styles.js +54 -0
- package/lib/module/primitives/metric.styles.js.map +1 -0
- package/lib/module/primitives/row.styles.js +53 -0
- package/lib/module/primitives/row.styles.js.map +1 -0
- package/lib/module/primitives/stack.styles.js +37 -0
- package/lib/module/primitives/stack.styles.js.map +1 -0
- package/lib/module/primitives/tag.styles.js +56 -0
- package/lib/module/primitives/tag.styles.js.map +1 -0
- package/lib/module/primitives/text.styles.js +33 -0
- package/lib/module/primitives/text.styles.js.map +1 -0
- package/lib/module/theme/ThemeProvider.js +41 -0
- package/lib/module/theme/ThemeProvider.js.map +1 -0
- package/lib/module/theme/index.js +19 -0
- package/lib/module/theme/index.js.map +1 -0
- package/lib/module/theme/native-theme.js +201 -0
- package/lib/module/theme/native-theme.js.map +1 -0
- package/lib/module/theme/types.js +4 -0
- package/lib/module/theme/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/ActionSheet.d.ts +45 -0
- package/lib/typescript/src/components/ActionSheet.d.ts.map +1 -0
- package/lib/typescript/src/components/Alert.d.ts +43 -0
- package/lib/typescript/src/components/Alert.d.ts.map +1 -0
- package/lib/typescript/src/components/Avatar.d.ts +45 -0
- package/lib/typescript/src/components/Avatar.d.ts.map +1 -0
- package/lib/typescript/src/components/BottomSheet.d.ts +36 -0
- package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -0
- package/lib/typescript/src/components/Card.d.ts +46 -0
- package/lib/typescript/src/components/Card.d.ts.map +1 -0
- package/lib/typescript/src/components/Checkbox.d.ts +40 -0
- package/lib/typescript/src/components/Checkbox.d.ts.map +1 -0
- package/lib/typescript/src/components/EmptyState.d.ts +36 -0
- package/lib/typescript/src/components/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/Header.d.ts +34 -0
- package/lib/typescript/src/components/Header.d.ts.map +1 -0
- package/lib/typescript/src/components/Image.d.ts +41 -0
- package/lib/typescript/src/components/Image.d.ts.map +1 -0
- package/lib/typescript/src/components/Modal.d.ts +46 -0
- package/lib/typescript/src/components/Modal.d.ts.map +1 -0
- package/lib/typescript/src/components/Progress.d.ts +39 -0
- package/lib/typescript/src/components/Progress.d.ts.map +1 -0
- package/lib/typescript/src/components/Radio.d.ts +52 -0
- package/lib/typescript/src/components/Radio.d.ts.map +1 -0
- package/lib/typescript/src/components/Screen.d.ts +42 -0
- package/lib/typescript/src/components/Screen.d.ts.map +1 -0
- package/lib/typescript/src/components/Skeleton.d.ts +37 -0
- package/lib/typescript/src/components/Skeleton.d.ts.map +1 -0
- package/lib/typescript/src/components/Surface.d.ts +31 -0
- package/lib/typescript/src/components/Surface.d.ts.map +1 -0
- package/lib/typescript/src/components/TextField.d.ts +45 -0
- package/lib/typescript/src/components/TextField.d.ts.map +1 -0
- package/lib/typescript/src/components/Toast.d.ts +45 -0
- package/lib/typescript/src/components/Toast.d.ts.map +1 -0
- package/lib/typescript/src/components/Toggle.d.ts +39 -0
- package/lib/typescript/src/components/Toggle.d.ts.map +1 -0
- package/lib/typescript/src/components/action-sheet.styles.d.ts +65 -0
- package/lib/typescript/src/components/action-sheet.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/alert.styles.d.ts +61 -0
- package/lib/typescript/src/components/alert.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar.styles.d.ts +60 -0
- package/lib/typescript/src/components/avatar.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet.styles.d.ts +50 -0
- package/lib/typescript/src/components/bottom-sheet.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/card.styles.d.ts +27 -0
- package/lib/typescript/src/components/card.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/checkbox.styles.d.ts +55 -0
- package/lib/typescript/src/components/checkbox.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state.styles.d.ts +58 -0
- package/lib/typescript/src/components/empty-state.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/header.styles.d.ts +30 -0
- package/lib/typescript/src/components/header.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/image.styles.d.ts +28 -0
- package/lib/typescript/src/components/image.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +23 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -0
- package/lib/typescript/src/components/modal.styles.d.ts +66 -0
- package/lib/typescript/src/components/modal.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/progress.styles.d.ts +44 -0
- package/lib/typescript/src/components/progress.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/radio.styles.d.ts +91 -0
- package/lib/typescript/src/components/radio.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/screen.styles.d.ts +34 -0
- package/lib/typescript/src/components/screen.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/skeleton.styles.d.ts +105 -0
- package/lib/typescript/src/components/skeleton.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/surface.styles.d.ts +31 -0
- package/lib/typescript/src/components/surface.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/textfield.styles.d.ts +75 -0
- package/lib/typescript/src/components/textfield.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/toast.styles.d.ts +62 -0
- package/lib/typescript/src/components/toast.styles.d.ts.map +1 -0
- package/lib/typescript/src/components/toggle.styles.d.ts +53 -0
- package/lib/typescript/src/components/toggle.styles.d.ts.map +1 -0
- package/lib/typescript/src/contracts/contract-types.d.ts +193 -0
- package/lib/typescript/src/contracts/contract-types.d.ts.map +1 -0
- package/lib/typescript/src/icons/Icon.d.ts +40 -0
- package/lib/typescript/src/icons/Icon.d.ts.map +1 -0
- package/lib/typescript/src/icons/glyphs.d.ts +259 -0
- package/lib/typescript/src/icons/glyphs.d.ts.map +1 -0
- package/lib/typescript/src/icons/icon.styles.d.ts +20 -0
- package/lib/typescript/src/icons/icon.styles.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +12 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +12 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Button.d.ts +48 -0
- package/lib/typescript/src/primitives/Button.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Metric.d.ts +38 -0
- package/lib/typescript/src/primitives/Metric.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Row.d.ts +37 -0
- package/lib/typescript/src/primitives/Row.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Stack.d.ts +33 -0
- package/lib/typescript/src/primitives/Stack.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Tag.d.ts +43 -0
- package/lib/typescript/src/primitives/Tag.d.ts.map +1 -0
- package/lib/typescript/src/primitives/Text.d.ts +45 -0
- package/lib/typescript/src/primitives/Text.d.ts.map +1 -0
- package/lib/typescript/src/primitives/_internal/focus-ring.d.ts +31 -0
- package/lib/typescript/src/primitives/_internal/focus-ring.d.ts.map +1 -0
- package/lib/typescript/src/primitives/button.styles.d.ts +29 -0
- package/lib/typescript/src/primitives/button.styles.d.ts.map +1 -0
- package/lib/typescript/src/primitives/index.d.ts +8 -0
- package/lib/typescript/src/primitives/index.d.ts.map +1 -0
- package/lib/typescript/src/primitives/metric.styles.d.ts +43 -0
- package/lib/typescript/src/primitives/metric.styles.d.ts.map +1 -0
- package/lib/typescript/src/primitives/row.styles.d.ts +50 -0
- package/lib/typescript/src/primitives/row.styles.d.ts.map +1 -0
- package/lib/typescript/src/primitives/stack.styles.d.ts +34 -0
- package/lib/typescript/src/primitives/stack.styles.d.ts.map +1 -0
- package/lib/typescript/src/primitives/tag.styles.d.ts +32 -0
- package/lib/typescript/src/primitives/tag.styles.d.ts.map +1 -0
- package/lib/typescript/src/primitives/text.styles.d.ts +26 -0
- package/lib/typescript/src/primitives/text.styles.d.ts.map +1 -0
- package/lib/typescript/src/theme/ThemeProvider.d.ts +32 -0
- package/lib/typescript/src/theme/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +15 -0
- package/lib/typescript/src/theme/index.d.ts.map +1 -0
- package/lib/typescript/src/theme/native-theme.d.ts +3 -0
- package/lib/typescript/src/theme/native-theme.d.ts.map +1 -0
- package/lib/typescript/src/theme/types.d.ts +121 -0
- package/lib/typescript/src/theme/types.d.ts.map +1 -0
- package/package.json +120 -0
- package/src/components/ActionSheet.tsx +112 -0
- package/src/components/Alert.tsx +84 -0
- package/src/components/Avatar.tsx +110 -0
- package/src/components/BottomSheet.tsx +81 -0
- package/src/components/Card.tsx +102 -0
- package/src/components/Checkbox.tsx +107 -0
- package/src/components/EmptyState.tsx +64 -0
- package/src/components/Header.tsx +51 -0
- package/src/components/Image.tsx +89 -0
- package/src/components/Modal.tsx +113 -0
- package/src/components/Progress.tsx +124 -0
- package/src/components/Radio.tsx +151 -0
- package/src/components/Screen.tsx +62 -0
- package/src/components/Skeleton.tsx +154 -0
- package/src/components/Surface.tsx +37 -0
- package/src/components/TextField.tsx +102 -0
- package/src/components/Toast.tsx +103 -0
- package/src/components/Toggle.tsx +94 -0
- package/src/components/action-sheet.styles.ts +102 -0
- package/src/components/alert.styles.ts +115 -0
- package/src/components/avatar.styles.ts +110 -0
- package/src/components/bottom-sheet.styles.ts +78 -0
- package/src/components/card.styles.ts +56 -0
- package/src/components/checkbox.styles.ts +83 -0
- package/src/components/empty-state.styles.ts +98 -0
- package/src/components/header.styles.ts +44 -0
- package/src/components/image.styles.ts +36 -0
- package/src/components/index.ts +23 -0
- package/src/components/modal.styles.ts +106 -0
- package/src/components/progress.styles.ts +74 -0
- package/src/components/radio.styles.ts +132 -0
- package/src/components/screen.styles.ts +44 -0
- package/src/components/skeleton.styles.ts +155 -0
- package/src/components/surface.styles.ts +50 -0
- package/src/components/textfield.styles.ts +135 -0
- package/src/components/toast.styles.ts +115 -0
- package/src/components/toggle.styles.ts +73 -0
- package/src/contracts/contract-types.ts +199 -0
- package/src/icons/Icon.tsx +65 -0
- package/src/icons/glyphs.ts +54 -0
- package/src/icons/icon.styles.ts +37 -0
- package/src/icons/index.ts +12 -0
- package/src/index.ts +24 -0
- package/src/primitives/Button.tsx +114 -0
- package/src/primitives/Metric.tsx +67 -0
- package/src/primitives/Row.tsx +54 -0
- package/src/primitives/Stack.tsx +42 -0
- package/src/primitives/Tag.tsx +126 -0
- package/src/primitives/Text.tsx +71 -0
- package/src/primitives/_internal/focus-ring.tsx +59 -0
- package/src/primitives/button.styles.ts +69 -0
- package/src/primitives/index.ts +8 -0
- package/src/primitives/metric.styles.ts +75 -0
- package/src/primitives/row.styles.ts +66 -0
- package/src/primitives/stack.styles.ts +47 -0
- package/src/primitives/tag.styles.ts +65 -0
- package/src/primitives/text.styles.ts +42 -0
- package/src/theme/ThemeProvider.tsx +60 -0
- package/src/theme/index.ts +18 -0
- package/src/theme/native-theme.ts +215 -0
- package/src/theme/types.ts +137 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle — ON/OFF スイッチ(contract: toggle)。custom track+thumb 実装。
|
|
3
|
+
*
|
|
4
|
+
* - RN Switch は token 制御(track/thumb 色 + サイズ段階)が弱いため使わない
|
|
5
|
+
* (recipe description の規約)。Pressable の track + 内側 thumb で組む。
|
|
6
|
+
* - variant(off/on = variantModeledStates)は value:boolean から暗黙決定する。
|
|
7
|
+
* - thumb 位置: track に padding=thumbOffset を持たせ、value で justifyContent
|
|
8
|
+
* flex-start / flex-end を切り替える(アニメは Phase 外)。
|
|
9
|
+
* - disabled: recipe states.disabled の opacity + accessibilityState.disabled + onPress 無効。
|
|
10
|
+
* - a11y: accessibilityRole="switch" + accessibilityState.checked。label があれば右に表示し、
|
|
11
|
+
* Pressable 全体がタップ領域 + accessibilityLabel になる。
|
|
12
|
+
* - 色・寸法の決定は pure resolver(toggle.styles.ts)に分離。
|
|
13
|
+
* recipes/app/toggle.recipe.json との機械照合は scripts/lib/toggle-conformance.test.ts が行う。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useMemo } from "react";
|
|
17
|
+
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 {
|
|
22
|
+
resolveToggleStyle,
|
|
23
|
+
TOGGLE_DISABLED_OPACITY,
|
|
24
|
+
type ToggleSize,
|
|
25
|
+
type ToggleVariant,
|
|
26
|
+
} from "./toggle.styles";
|
|
27
|
+
|
|
28
|
+
interface ToggleProps {
|
|
29
|
+
/** 現在値(true=on / false=off。variant はここから暗黙決定)。 */
|
|
30
|
+
value: boolean;
|
|
31
|
+
onValueChange?: (value: boolean) => void;
|
|
32
|
+
/** あれば track の右にラベル表示(Pressable 全体がタップ領域)。 */
|
|
33
|
+
label?: string;
|
|
34
|
+
size?: ToggleSize;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
testID?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function Toggle({
|
|
41
|
+
value,
|
|
42
|
+
onValueChange,
|
|
43
|
+
label,
|
|
44
|
+
size = "medium",
|
|
45
|
+
disabled = false,
|
|
46
|
+
style,
|
|
47
|
+
testID,
|
|
48
|
+
}: ToggleProps) {
|
|
49
|
+
const { theme, mode } = useTheme();
|
|
50
|
+
|
|
51
|
+
const variant: ToggleVariant = value ? "on" : "off";
|
|
52
|
+
const resolved = useMemo(
|
|
53
|
+
() => resolveToggleStyle(theme, mode, { variant, size }),
|
|
54
|
+
[theme, mode, variant, size],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Pressable
|
|
59
|
+
onPress={() => onValueChange?.(!value)}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
accessibilityRole="switch"
|
|
62
|
+
accessibilityState={{ checked: value, disabled }}
|
|
63
|
+
accessibilityLabel={label}
|
|
64
|
+
testID={testID}
|
|
65
|
+
style={[
|
|
66
|
+
{
|
|
67
|
+
flexDirection: "row",
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
alignSelf: "flex-start",
|
|
70
|
+
gap: theme.spacing["2"],
|
|
71
|
+
opacity: disabled ? TOGGLE_DISABLED_OPACITY : 1,
|
|
72
|
+
},
|
|
73
|
+
style,
|
|
74
|
+
]}
|
|
75
|
+
>
|
|
76
|
+
<View
|
|
77
|
+
style={[
|
|
78
|
+
resolved.track,
|
|
79
|
+
{
|
|
80
|
+
flexDirection: "row",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
justifyContent: value ? "flex-end" : "flex-start",
|
|
83
|
+
},
|
|
84
|
+
]}
|
|
85
|
+
>
|
|
86
|
+
<View style={resolved.thumb} />
|
|
87
|
+
</View>
|
|
88
|
+
{label != null && <Text>{label}</Text>}
|
|
89
|
+
</Pressable>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Phase 2 conformance test 用の contract メタ(§2 A-3)。
|
|
94
|
+
Toggle.__contract = CONTRACTS.toggle;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* action-sheet.styles — ActionSheet の pure style resolver(styleRefs conformance 対応で
|
|
3
|
+
* ActionSheet.tsx から分離)。
|
|
4
|
+
*
|
|
5
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
6
|
+
* 直接実行できる。recipes/app/action-sheet.recipe.json(melta-contracts の styleRefs)との
|
|
7
|
+
* 機械照合は scripts/lib/action-sheet-conformance.test.ts が行う。
|
|
8
|
+
*
|
|
9
|
+
* - actions は縦積み(区切り線 border-default)、cancel は下に分離した独立ブロック。
|
|
10
|
+
* - destructive アクションは actionTextStyle の色を destructiveColor(status.danger.base)に
|
|
11
|
+
* 差し替える(destructiveTextStyle)。
|
|
12
|
+
* - overlay の黒 50% は token 外の literal(modal と同じ)。
|
|
13
|
+
* - sheet の下余白(SafeArea)は実装(ActionSheet.tsx)が SafeAreaView で加算する。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { FontWeightValue, NativeTheme, ThemeMode } from "../theme";
|
|
17
|
+
|
|
18
|
+
/** overlay の黒 50%(token 外 literal。modal と同値)。 */
|
|
19
|
+
// eslint-disable-next-line melta/no-raw-color -- overlay の黒 50% は契約上 token 外の literal(recipe description / web の bg-black/50 と同値)
|
|
20
|
+
export const ACTION_SHEET_OVERLAY_COLOR = "rgba(0,0,0,0.5)";
|
|
21
|
+
|
|
22
|
+
export interface ActionSheetStyle {
|
|
23
|
+
overlayStyle: {
|
|
24
|
+
flex: 1;
|
|
25
|
+
backgroundColor: string;
|
|
26
|
+
justifyContent: "flex-end";
|
|
27
|
+
padding: number;
|
|
28
|
+
};
|
|
29
|
+
sheetStyle: { backgroundColor: string; borderRadius: number; overflow: "hidden" };
|
|
30
|
+
titleStyle: {
|
|
31
|
+
fontSize: number;
|
|
32
|
+
color: string;
|
|
33
|
+
textAlign: "center";
|
|
34
|
+
paddingVertical: number;
|
|
35
|
+
};
|
|
36
|
+
actionStyle: {
|
|
37
|
+
paddingVertical: number;
|
|
38
|
+
alignItems: "center";
|
|
39
|
+
borderTopWidth: 1;
|
|
40
|
+
borderTopColor: string;
|
|
41
|
+
};
|
|
42
|
+
actionTextStyle: { fontSize: number; color: string };
|
|
43
|
+
destructiveTextStyle: { fontSize: number; color: string };
|
|
44
|
+
cancelStyle: {
|
|
45
|
+
backgroundColor: string;
|
|
46
|
+
borderRadius: number;
|
|
47
|
+
marginTop: number;
|
|
48
|
+
paddingVertical: number;
|
|
49
|
+
alignItems: "center";
|
|
50
|
+
};
|
|
51
|
+
cancelTextStyle: { fontSize: number; fontWeight: FontWeightValue; color: string };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** mode → ActionSheet style の解決(action-sheet.recipe styleRefs の 1:1 写像)。 */
|
|
55
|
+
export function resolveActionSheetStyle(theme: NativeTheme, mode: ThemeMode): ActionSheetStyle {
|
|
56
|
+
const semantic = theme.color.semantic[mode];
|
|
57
|
+
return {
|
|
58
|
+
overlayStyle: {
|
|
59
|
+
flex: 1,
|
|
60
|
+
backgroundColor: ACTION_SHEET_OVERLAY_COLOR,
|
|
61
|
+
justifyContent: "flex-end",
|
|
62
|
+
padding: theme.spacing["2"],
|
|
63
|
+
},
|
|
64
|
+
sheetStyle: {
|
|
65
|
+
backgroundColor: semantic["bg-surface"],
|
|
66
|
+
borderRadius: theme.radius.lg,
|
|
67
|
+
overflow: "hidden",
|
|
68
|
+
},
|
|
69
|
+
titleStyle: {
|
|
70
|
+
fontSize: theme.typography.fontSize.sm.fontSize,
|
|
71
|
+
color: semantic["text-muted"],
|
|
72
|
+
textAlign: "center",
|
|
73
|
+
paddingVertical: theme.spacing["3"],
|
|
74
|
+
},
|
|
75
|
+
actionStyle: {
|
|
76
|
+
paddingVertical: theme.spacing["4"],
|
|
77
|
+
alignItems: "center",
|
|
78
|
+
borderTopWidth: 1,
|
|
79
|
+
borderTopColor: semantic["border-default"],
|
|
80
|
+
},
|
|
81
|
+
actionTextStyle: {
|
|
82
|
+
fontSize: theme.typography.fontSize.lg.fontSize,
|
|
83
|
+
color: theme.color.primary["500"],
|
|
84
|
+
},
|
|
85
|
+
destructiveTextStyle: {
|
|
86
|
+
fontSize: theme.typography.fontSize.lg.fontSize,
|
|
87
|
+
color: theme.color.status.danger.base,
|
|
88
|
+
},
|
|
89
|
+
cancelStyle: {
|
|
90
|
+
backgroundColor: semantic["bg-surface"],
|
|
91
|
+
borderRadius: theme.radius.lg,
|
|
92
|
+
marginTop: theme.spacing["2"],
|
|
93
|
+
paddingVertical: theme.spacing["4"],
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
},
|
|
96
|
+
cancelTextStyle: {
|
|
97
|
+
fontSize: theme.typography.fontSize.lg.fontSize,
|
|
98
|
+
fontWeight: theme.typography.fontWeight.semibold,
|
|
99
|
+
color: theme.color.primary["500"],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* alert.styles — Alert の pure style resolver(styleRefs conformance 対応で Alert.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/alert.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/alert-conformance.test.ts が行う。
|
|
7
|
+
*
|
|
8
|
+
* - RN では border を持たず bg subtle + text 色で表現する(status token に border 用の中間色が
|
|
9
|
+
* 無いため。COLOR_ONLY_FORBIDDEN は title/message のテキストと icon slot で担保、recipe description)。
|
|
10
|
+
* - dark mode は status.*.subtleDark / textDark 側を解決する。info variant のみ primary 固定
|
|
11
|
+
* (dark token が無い既知の割り切り、web と同じ)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
FontSizeKey,
|
|
16
|
+
FontWeightKey,
|
|
17
|
+
FontWeightValue,
|
|
18
|
+
NativeTheme,
|
|
19
|
+
RadiusKey,
|
|
20
|
+
SpacingKey,
|
|
21
|
+
ThemeMode,
|
|
22
|
+
} from "../theme";
|
|
23
|
+
|
|
24
|
+
export type AlertVariant = "info" | "success" | "warning" | "error";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 構成キー(token キー)。component は Text にこのキーをそのまま渡し、resolver は同じキーから
|
|
28
|
+
* style 値を解決する(component と resolver が別々のキーを持って drift する事故を防ぐ SSOT)。
|
|
29
|
+
*/
|
|
30
|
+
export const ALERT_SPEC = {
|
|
31
|
+
padding: "4",
|
|
32
|
+
gap: "3",
|
|
33
|
+
radius: "md",
|
|
34
|
+
titleFont: "base",
|
|
35
|
+
titleWeight: "medium",
|
|
36
|
+
messageFont: "sm",
|
|
37
|
+
} as const satisfies {
|
|
38
|
+
padding: SpacingKey;
|
|
39
|
+
gap: SpacingKey;
|
|
40
|
+
radius: RadiusKey;
|
|
41
|
+
titleFont: FontSizeKey;
|
|
42
|
+
titleWeight: FontWeightKey;
|
|
43
|
+
messageFont: FontSizeKey;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** slot 構成(alert.recipe 各 variant の containerStyle / titleStyle / messageStyle と 1:1)。 */
|
|
47
|
+
export interface AlertStyles {
|
|
48
|
+
containerStyle: {
|
|
49
|
+
backgroundColor: string;
|
|
50
|
+
borderRadius: number;
|
|
51
|
+
padding: number;
|
|
52
|
+
flexDirection: "row";
|
|
53
|
+
alignItems: "flex-start";
|
|
54
|
+
gap: number;
|
|
55
|
+
};
|
|
56
|
+
/** compose する Text primitive(title)へ渡す値の解決結果 */
|
|
57
|
+
titleStyle: {
|
|
58
|
+
color: string;
|
|
59
|
+
fontSize: number;
|
|
60
|
+
fontWeight: FontWeightValue;
|
|
61
|
+
};
|
|
62
|
+
/** compose する Text primitive(message)へ渡す値の解決結果 */
|
|
63
|
+
messageStyle: {
|
|
64
|
+
color: string;
|
|
65
|
+
fontSize: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* variant → bg / text 色の解決。
|
|
71
|
+
* - info: primary.50 / primary.800 固定(mode 非依存)。
|
|
72
|
+
* - success / warning / error: status token(error は status キー上は danger)。
|
|
73
|
+
* light は subtleLight / textLight、dark は subtleDark / textDark(recipe description の写像)。
|
|
74
|
+
*/
|
|
75
|
+
export function resolveAlertColors(
|
|
76
|
+
theme: NativeTheme,
|
|
77
|
+
mode: ThemeMode,
|
|
78
|
+
variant: AlertVariant,
|
|
79
|
+
): { bg: string; text: string } {
|
|
80
|
+
if (variant === "info") {
|
|
81
|
+
return { bg: theme.color.primary["50"], text: theme.color.primary["800"] };
|
|
82
|
+
}
|
|
83
|
+
const status = theme.color.status[variant === "error" ? "danger" : variant];
|
|
84
|
+
return mode === "dark"
|
|
85
|
+
? { bg: status.subtleDark, text: status.textDark }
|
|
86
|
+
: { bg: status.subtleLight, text: status.textLight };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** variant → 全 slot の style 解決(alert.recipe styleRefs の 1:1 写像)。 */
|
|
90
|
+
export function resolveAlertStyles(
|
|
91
|
+
theme: NativeTheme,
|
|
92
|
+
mode: ThemeMode,
|
|
93
|
+
variant: AlertVariant,
|
|
94
|
+
): AlertStyles {
|
|
95
|
+
const { bg, text } = resolveAlertColors(theme, mode, variant);
|
|
96
|
+
return {
|
|
97
|
+
containerStyle: {
|
|
98
|
+
backgroundColor: bg,
|
|
99
|
+
borderRadius: theme.radius[ALERT_SPEC.radius],
|
|
100
|
+
padding: theme.spacing[ALERT_SPEC.padding],
|
|
101
|
+
flexDirection: "row",
|
|
102
|
+
alignItems: "flex-start",
|
|
103
|
+
gap: theme.spacing[ALERT_SPEC.gap],
|
|
104
|
+
},
|
|
105
|
+
titleStyle: {
|
|
106
|
+
color: text,
|
|
107
|
+
fontSize: theme.typography.fontSize[ALERT_SPEC.titleFont].fontSize,
|
|
108
|
+
fontWeight: theme.typography.fontWeight[ALERT_SPEC.titleWeight],
|
|
109
|
+
},
|
|
110
|
+
messageStyle: {
|
|
111
|
+
color: text,
|
|
112
|
+
fontSize: theme.typography.fontSize[ALERT_SPEC.messageFont].fontSize,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* avatar.styles — Avatar の pure style resolver(styleRefs conformance 対応で Avatar.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/avatar.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/avatar-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FontWeightValue, NativeTheme, ThemeMode } from "../theme";
|
|
10
|
+
|
|
11
|
+
export type AvatarSize = "small" | "medium" | "large";
|
|
12
|
+
export type AvatarStatus = "online" | "away" | "offline";
|
|
13
|
+
|
|
14
|
+
/** サイズ軸(contract sizes の height と 1:1。recipe sizes の width/height/dotSize/fontSize に対応)。 */
|
|
15
|
+
const SIZE_TABLE = {
|
|
16
|
+
small: { box: 32, dot: 8, fontSize: "xs" },
|
|
17
|
+
medium: { box: 40, dot: 10, fontSize: "sm" },
|
|
18
|
+
large: { box: 48, dot: 12, fontSize: "base" },
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/** group の重なり(-spacing.2 相当。負値 token が無いため recipe と同じ literal)。 */
|
|
22
|
+
export const AVATAR_GROUP_OVERLAP = -8;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* group variant(avatar.recipe の group.style / group.overlapStyle の 1:1 写像)。
|
|
26
|
+
* container は横並び、2枚目以降の子に overlap(負 margin)を適用する。
|
|
27
|
+
*/
|
|
28
|
+
export function resolveAvatarGroupStyle(): {
|
|
29
|
+
container: { flexDirection: "row" };
|
|
30
|
+
overlap: { marginLeft: number };
|
|
31
|
+
} {
|
|
32
|
+
return {
|
|
33
|
+
container: { flexDirection: "row" },
|
|
34
|
+
overlap: { marginLeft: AVATAR_GROUP_OVERLAP },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AvatarStyleResult {
|
|
39
|
+
/** 円形コンテナ(image は overflow clip / initials は bg + 中央寄せ)。 */
|
|
40
|
+
container: {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
borderRadius: number;
|
|
44
|
+
overflow?: "hidden";
|
|
45
|
+
backgroundColor?: string;
|
|
46
|
+
alignItems?: "center";
|
|
47
|
+
justifyContent?: "center";
|
|
48
|
+
};
|
|
49
|
+
/** initials 文字(image variant では未使用)。 */
|
|
50
|
+
text: { color: string; fontWeight: FontWeightValue; fontSize: number };
|
|
51
|
+
/** statusDot(status 指定時のみ使用。右下配置は Avatar.tsx 側の絶対配置で行う)。 */
|
|
52
|
+
dot: { width: number; height: number; borderRadius: number; borderWidth: 2; borderColor: string };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* variant / size → Avatar style の解決(avatar.recipe styleRefs の 1:1 写像)。
|
|
57
|
+
*/
|
|
58
|
+
export function resolveAvatarStyle(
|
|
59
|
+
theme: NativeTheme,
|
|
60
|
+
mode: ThemeMode,
|
|
61
|
+
variant: "image" | "initials",
|
|
62
|
+
size: AvatarSize,
|
|
63
|
+
): AvatarStyleResult {
|
|
64
|
+
const s = SIZE_TABLE[size];
|
|
65
|
+
return {
|
|
66
|
+
container: {
|
|
67
|
+
width: s.box,
|
|
68
|
+
height: s.box,
|
|
69
|
+
borderRadius: theme.radius.full,
|
|
70
|
+
...(variant === "image"
|
|
71
|
+
? { overflow: "hidden" as const }
|
|
72
|
+
: {
|
|
73
|
+
backgroundColor: theme.color.primary["50"],
|
|
74
|
+
alignItems: "center" as const,
|
|
75
|
+
justifyContent: "center" as const,
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
text: {
|
|
79
|
+
color: theme.color.primary["500"],
|
|
80
|
+
fontWeight: theme.typography.fontWeight.medium,
|
|
81
|
+
fontSize: theme.typography.fontSize[s.fontSize].fontSize,
|
|
82
|
+
},
|
|
83
|
+
dot: {
|
|
84
|
+
width: s.dot,
|
|
85
|
+
height: s.dot,
|
|
86
|
+
borderRadius: theme.radius.full,
|
|
87
|
+
borderWidth: 2,
|
|
88
|
+
borderColor: theme.color.semantic[mode]["bg-surface"],
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* status → statusDot の背景色(avatar.recipe states の 1:1 写像)。
|
|
95
|
+
* online=success / away=warning / offline=ニュートラル(border-strong)。
|
|
96
|
+
*/
|
|
97
|
+
export function resolveAvatarStatusColor(
|
|
98
|
+
theme: NativeTheme,
|
|
99
|
+
mode: ThemeMode,
|
|
100
|
+
status: AvatarStatus,
|
|
101
|
+
): string {
|
|
102
|
+
switch (status) {
|
|
103
|
+
case "online":
|
|
104
|
+
return theme.color.status.success.base;
|
|
105
|
+
case "away":
|
|
106
|
+
return theme.color.status.warning.base;
|
|
107
|
+
case "offline":
|
|
108
|
+
return theme.color.semantic[mode]["border-strong"];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bottom-sheet.styles — BottomSheet の pure style resolver(styleRefs conformance 対応で
|
|
3
|
+
* BottomSheet.tsx から分離)。
|
|
4
|
+
*
|
|
5
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
6
|
+
* 直接実行できる。recipes/app/bottom-sheet.recipe.json(melta-contracts の styleRefs)との
|
|
7
|
+
* 機械照合は scripts/lib/bottom-sheet-conformance.test.ts が行う。
|
|
8
|
+
*
|
|
9
|
+
* - sheet は上角のみ radius(下端密着のため)。grabber は装飾バー(a11y ツリーから除外は
|
|
10
|
+
* 実装側の importantForAccessibility / accessibilityElementsHidden が担う)。
|
|
11
|
+
* - content は自由 slot で padding のみ供給。
|
|
12
|
+
* - overlay の黒 50% は token 外の literal(modal と同じ)。
|
|
13
|
+
* - sheet の下余白(SafeArea)は実装(BottomSheet.tsx)が SafeAreaView で加算する。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { FontWeightValue, NativeTheme, ThemeMode } from "../theme";
|
|
17
|
+
|
|
18
|
+
/** overlay の黒 50%(token 外 literal。modal と同値)。 */
|
|
19
|
+
// eslint-disable-next-line melta/no-raw-color -- overlay の黒 50% は契約上 token 外の literal(recipe description / web の bg-black/50 と同値)
|
|
20
|
+
export const BOTTOM_SHEET_OVERLAY_COLOR = "rgba(0,0,0,0.5)";
|
|
21
|
+
|
|
22
|
+
export interface BottomSheetStyle {
|
|
23
|
+
overlayStyle: { flex: 1; backgroundColor: string; justifyContent: "flex-end" };
|
|
24
|
+
sheetStyle: {
|
|
25
|
+
backgroundColor: string;
|
|
26
|
+
borderTopLeftRadius: number;
|
|
27
|
+
borderTopRightRadius: number;
|
|
28
|
+
};
|
|
29
|
+
grabberStyle: {
|
|
30
|
+
width: 36;
|
|
31
|
+
height: 4;
|
|
32
|
+
borderRadius: number;
|
|
33
|
+
backgroundColor: string;
|
|
34
|
+
alignSelf: "center";
|
|
35
|
+
marginTop: number;
|
|
36
|
+
};
|
|
37
|
+
titleStyle: {
|
|
38
|
+
fontSize: number;
|
|
39
|
+
fontWeight: FontWeightValue;
|
|
40
|
+
color: string;
|
|
41
|
+
paddingHorizontal: number;
|
|
42
|
+
paddingTop: number;
|
|
43
|
+
};
|
|
44
|
+
contentStyle: { padding: number };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** mode → BottomSheet style の解決(bottom-sheet.recipe styleRefs の 1:1 写像)。 */
|
|
48
|
+
export function resolveBottomSheetStyle(theme: NativeTheme, mode: ThemeMode): BottomSheetStyle {
|
|
49
|
+
const semantic = theme.color.semantic[mode];
|
|
50
|
+
return {
|
|
51
|
+
overlayStyle: {
|
|
52
|
+
flex: 1,
|
|
53
|
+
backgroundColor: BOTTOM_SHEET_OVERLAY_COLOR,
|
|
54
|
+
justifyContent: "flex-end",
|
|
55
|
+
},
|
|
56
|
+
sheetStyle: {
|
|
57
|
+
backgroundColor: semantic["bg-surface"],
|
|
58
|
+
borderTopLeftRadius: theme.radius.lg,
|
|
59
|
+
borderTopRightRadius: theme.radius.lg,
|
|
60
|
+
},
|
|
61
|
+
grabberStyle: {
|
|
62
|
+
width: 36,
|
|
63
|
+
height: 4,
|
|
64
|
+
borderRadius: theme.radius.full,
|
|
65
|
+
backgroundColor: semantic["border-strong"],
|
|
66
|
+
alignSelf: "center",
|
|
67
|
+
marginTop: theme.spacing["2"],
|
|
68
|
+
},
|
|
69
|
+
titleStyle: {
|
|
70
|
+
fontSize: theme.typography.fontSize.lg.fontSize,
|
|
71
|
+
fontWeight: theme.typography.fontWeight.bold,
|
|
72
|
+
color: semantic["text-heading"],
|
|
73
|
+
paddingHorizontal: theme.spacing["4"],
|
|
74
|
+
paddingTop: theme.spacing["4"],
|
|
75
|
+
},
|
|
76
|
+
contentStyle: { padding: theme.spacing["4"] },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* card.styles — Card の pure style resolver(styleRefs conformance 対応で Card.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュール(型 import のみ)にすることで node テスト
|
|
5
|
+
* (tsx --test)から直接実行できる。recipes/app/card.recipe.json(melta-contracts の styleRefs)
|
|
6
|
+
* との機械照合は scripts/lib/card-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ViewStyle } from "react-native";
|
|
10
|
+
import type { NativeTheme, ThemeMode, ElevationKey } from "../theme";
|
|
11
|
+
|
|
12
|
+
export type CardVariant = "basic" | "media" | "action" | "link";
|
|
13
|
+
|
|
14
|
+
/** variant → インタラクティブ(Pressable)か。action/link のみ true(recipe pressedStyle の有無と対応)。 */
|
|
15
|
+
export const CARD_INTERACTIVE: Record<CardVariant, boolean> = {
|
|
16
|
+
basic: false,
|
|
17
|
+
media: false,
|
|
18
|
+
action: true,
|
|
19
|
+
link: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Card の外枠 style を一元生成(card.recipe variants.style の 1:1 写像、
|
|
24
|
+
* 非インタラクティブ View / インタラクティブ Pressable の両分岐で共有)。
|
|
25
|
+
*
|
|
26
|
+
* - pressed は action/link のみ elevation sm→md(recipe pressedStyle、contract hover→pressed mapping)。
|
|
27
|
+
* - 注: overflow:hidden は付けない。iOS は shadow(elevation) と overflow:hidden が同居すると影が
|
|
28
|
+
* 消えるため(contract は media でも elevation.sm 要求)。media のクリップは内側の clip View で行う。
|
|
29
|
+
* - media は内側 clip View が padding を持つ(resolveCardBodyStyle / recipe bodyStyle)ので外枠は padding なし。
|
|
30
|
+
*/
|
|
31
|
+
export function resolveCardShape(
|
|
32
|
+
theme: NativeTheme,
|
|
33
|
+
mode: ThemeMode,
|
|
34
|
+
variant: CardVariant,
|
|
35
|
+
pressed = false,
|
|
36
|
+
): ViewStyle {
|
|
37
|
+
const sem = theme.color.semantic[mode];
|
|
38
|
+
const elevation: ElevationKey = pressed && CARD_INTERACTIVE[variant] ? "md" : "sm";
|
|
39
|
+
return {
|
|
40
|
+
backgroundColor: sem["bg-surface"],
|
|
41
|
+
borderRadius: theme.radius.lg,
|
|
42
|
+
borderWidth: 1,
|
|
43
|
+
borderColor: sem["border-default"],
|
|
44
|
+
// media は内側 clip View が padding を持つので外枠は padding なし。
|
|
45
|
+
...(variant === "media" ? null : { padding: theme.spacing["6"] }),
|
|
46
|
+
...theme.elevation[elevation],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** media の body padding(内側 clip View 側が持つ。recipe bodyStyle の写像)。media 以外は undefined。 */
|
|
51
|
+
export function resolveCardBodyStyle(
|
|
52
|
+
theme: NativeTheme,
|
|
53
|
+
variant: CardVariant,
|
|
54
|
+
): { padding: number } | undefined {
|
|
55
|
+
return variant === "media" ? { padding: theme.spacing["6"] } : undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkbox.styles — Checkbox の pure style resolver(styleRefs conformance 対応で Checkbox.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/checkbox.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/checkbox-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, ThemeMode } from "../theme";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* recipe 由来の literal 値テーブル(token 化されていない数値。conformance が recipe と照合する)。
|
|
13
|
+
* - box は 20px(web の 16px より大きいのはタッチ前提、recipe description)
|
|
14
|
+
* - hitSlop は (44 - box 20) / 2 = 12。行全体 Pressable に足して実タッチ領域 44pt を確保
|
|
15
|
+
*/
|
|
16
|
+
export const CHECKBOX_SPEC = {
|
|
17
|
+
boxSize: 20,
|
|
18
|
+
boxBorderWidth: 2,
|
|
19
|
+
disabledOpacity: 0.5,
|
|
20
|
+
hitSlop: 12,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/** 見た目(色)に効く状態入力。disabled は opacity のみで色を変えないため含めない。 */
|
|
24
|
+
export interface CheckboxStateInput {
|
|
25
|
+
checked: boolean;
|
|
26
|
+
/** true で variant "indeterminate"。box の塗りは checked と同じ、mark が横棒になる。 */
|
|
27
|
+
indeterminate?: boolean;
|
|
28
|
+
/** true で states.error の borderColor 差分。checked より優先(選択済みでもエラーなら赤枠)。 */
|
|
29
|
+
error?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CheckboxResolved {
|
|
33
|
+
/** box(20px の四角)。recipe の boxStyle スロットと 1:1。 */
|
|
34
|
+
boxStyle: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
borderWidth: number;
|
|
38
|
+
borderColor: string;
|
|
39
|
+
borderRadius: number;
|
|
40
|
+
backgroundColor: string;
|
|
41
|
+
};
|
|
42
|
+
/** label(recipe の labelStyle スロットと 1:1。実装は Text primitive の base/text-default で同値)。 */
|
|
43
|
+
labelStyle: { fontSize: number; color: string };
|
|
44
|
+
/** box と label の間隔。 */
|
|
45
|
+
gap: number;
|
|
46
|
+
/** checkmark(L 字 border)/ indeterminate 横棒の色(states.checked.markColor)。 */
|
|
47
|
+
markColor: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 状態 → 最終 style 値の解決(checkbox.recipe styleRefs の 1:1 写像)。
|
|
52
|
+
* - 塗り(bg/border = primary.500)は checked / indeterminate 共通(recipe の variants.indeterminate
|
|
53
|
+
* boxStyle と states.checked boxStyle が同値であることは conformance が recipe 側で担保)。
|
|
54
|
+
* - error は borderColor のみ上書き(states.error)。
|
|
55
|
+
*/
|
|
56
|
+
export function resolveCheckboxStyle(
|
|
57
|
+
theme: NativeTheme,
|
|
58
|
+
mode: ThemeMode,
|
|
59
|
+
{ checked, indeterminate = false, error = false }: CheckboxStateInput,
|
|
60
|
+
): CheckboxResolved {
|
|
61
|
+
const sem = theme.color.semantic[mode];
|
|
62
|
+
const filled = checked || indeterminate;
|
|
63
|
+
return {
|
|
64
|
+
boxStyle: {
|
|
65
|
+
width: CHECKBOX_SPEC.boxSize,
|
|
66
|
+
height: CHECKBOX_SPEC.boxSize,
|
|
67
|
+
borderWidth: CHECKBOX_SPEC.boxBorderWidth,
|
|
68
|
+
borderColor: error
|
|
69
|
+
? theme.color.status.danger.base
|
|
70
|
+
: filled
|
|
71
|
+
? theme.color.primary["500"]
|
|
72
|
+
: sem["border-strong"],
|
|
73
|
+
borderRadius: theme.radius.sm,
|
|
74
|
+
backgroundColor: filled ? theme.color.primary["500"] : sem["input-bg"],
|
|
75
|
+
},
|
|
76
|
+
labelStyle: {
|
|
77
|
+
fontSize: theme.typography.fontSize.base.fontSize,
|
|
78
|
+
color: sem["text-default"],
|
|
79
|
+
},
|
|
80
|
+
gap: theme.spacing["2"],
|
|
81
|
+
markColor: sem["text-on-accent"],
|
|
82
|
+
};
|
|
83
|
+
}
|