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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack — 縦積み layout primitive。RN コア View の薄いラッパ。
|
|
3
|
+
* contract: stack(app 先行定義、melta-contracts)。dogfood 不足-1(生 flexbox 手書き)の解消。
|
|
4
|
+
*
|
|
5
|
+
* - gap は spacing token キー限定(生数値不可、§5 lint の意味を保つ)。
|
|
6
|
+
* - align は交差軸の配置(default stretch = RN デフォルト)。
|
|
7
|
+
* - 決定ロジックは pure resolver(stack.styles.ts)に分離 — recipe との機械照合対象。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useMemo, type ReactNode } from "react";
|
|
11
|
+
import { View, type StyleProp, type ViewStyle } from "react-native";
|
|
12
|
+
import { useTheme } from "../theme";
|
|
13
|
+
import type { SpacingKey } from "../theme";
|
|
14
|
+
import { CONTRACTS } from "../contracts/contract-types";
|
|
15
|
+
import { resolveStackStyle, type StackAlign, type StackStyle } from "./stack.styles";
|
|
16
|
+
|
|
17
|
+
interface StackProps {
|
|
18
|
+
/** 子要素間の gap(spacing token キー限定)。省略時 gap なし。 */
|
|
19
|
+
gap?: SpacingKey;
|
|
20
|
+
/** 交差軸の配置。default "stretch"。 */
|
|
21
|
+
align?: StackAlign;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
testID?: string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Stack({ gap, align = "stretch", style, testID, children }: StackProps) {
|
|
28
|
+
const { theme } = useTheme();
|
|
29
|
+
|
|
30
|
+
const layout = useMemo<StackStyle>(
|
|
31
|
+
() => resolveStackStyle(theme, { gap, align }),
|
|
32
|
+
[theme, gap, align],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={[layout, style]} testID={testID}>
|
|
37
|
+
{children}
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Stack.__contract = CONTRACTS.stack;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag — タグ / フィルターチップ primitive(設計書 §1, §4 C-3)。contract: tag。discriminated union。
|
|
3
|
+
*
|
|
4
|
+
* - variant で必須 prop が変わるため DU(§4: basic は表示のみ、removable は onRemove 必須、
|
|
5
|
+
* filter-chip は selected/onToggle 必須)。
|
|
6
|
+
* - contract states active/inactive → prop selected:boolean(§2)。selected→accessibilityState.selected。
|
|
7
|
+
* - 配色: basic/removable は bg-page-alt、filter-chip は bg-surface+border、selected 時は
|
|
8
|
+
* active-bg=primary.50 / active-border=primary.200 / active-text=primary.700(tag.contract tokenRefs)。
|
|
9
|
+
* 色・寸法の決定は pure resolver(tag.styles.ts)に分離、recipe との機械照合は
|
|
10
|
+
* scripts/lib/tag-conformance.test.ts が行う。
|
|
11
|
+
* - label は Text primitive。filter-chip active の文字色だけ primary.700(SemanticColors 外なので style 上書き)。
|
|
12
|
+
* - focus outline は Button(Step8) で Pressable focus handling を共通化してから Tag にも適用する
|
|
13
|
+
* (§1 は Phase1 scope だが acceptance 外。今は accessibilityState のみ。Button 実装時に回収)。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Pressable, View, type StyleProp, type ViewStyle } from "react-native";
|
|
17
|
+
import { Text } from "./Text";
|
|
18
|
+
import { useTheme } from "../theme";
|
|
19
|
+
import { useFocusRing, FocusRing } from "./_internal/focus-ring";
|
|
20
|
+
import { CONTRACTS } from "../contracts/contract-types";
|
|
21
|
+
import { resolveTagBase, resolveTagVariant } from "./tag.styles";
|
|
22
|
+
|
|
23
|
+
interface TagBase {
|
|
24
|
+
label: string;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
testID?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type TagProps =
|
|
30
|
+
| (TagBase & { variant?: "basic" })
|
|
31
|
+
| (TagBase & { variant: "removable"; onRemove: () => void; removeAccessibilityLabel: string })
|
|
32
|
+
| (TagBase & { variant: "filter-chip"; selected: boolean; onToggle: () => void });
|
|
33
|
+
|
|
34
|
+
/** 共通形状(px-3 py-1 rounded-full。寸法は resolver、layout はここで足す)。 */
|
|
35
|
+
function useTagBase(): ViewStyle {
|
|
36
|
+
const { theme } = useTheme();
|
|
37
|
+
return {
|
|
38
|
+
flexDirection: "row",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
...resolveTagBase(theme),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function FilterChipTag(props: TagBase & { selected: boolean; onToggle: () => void }) {
|
|
45
|
+
const { theme, mode } = useTheme();
|
|
46
|
+
const base = useTagBase();
|
|
47
|
+
const { focused, focusHandlers } = useFocusRing();
|
|
48
|
+
const { label, selected, onToggle, style, testID } = props;
|
|
49
|
+
const v = resolveTagVariant(theme, mode, "filter-chip", selected);
|
|
50
|
+
return (
|
|
51
|
+
<Pressable
|
|
52
|
+
{...focusHandlers}
|
|
53
|
+
onPress={onToggle}
|
|
54
|
+
accessibilityRole="button"
|
|
55
|
+
accessibilityState={{ selected }}
|
|
56
|
+
testID={testID}
|
|
57
|
+
style={[
|
|
58
|
+
base,
|
|
59
|
+
{
|
|
60
|
+
backgroundColor: v.bg,
|
|
61
|
+
borderWidth: v.borderWidth,
|
|
62
|
+
borderColor: v.border,
|
|
63
|
+
},
|
|
64
|
+
style,
|
|
65
|
+
]}
|
|
66
|
+
>
|
|
67
|
+
{/* 非選択時の文字色は Text の color prop(text-default)と同値なので上書き不要。 */}
|
|
68
|
+
<Text variant={v.font} color="text-default" style={selected ? { color: v.textColor } : undefined}>
|
|
69
|
+
{label}
|
|
70
|
+
</Text>
|
|
71
|
+
<FocusRing visible={focused} radius={theme.radius.full} />
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function RemovableTag(
|
|
77
|
+
props: TagBase & { onRemove: () => void; removeAccessibilityLabel: string },
|
|
78
|
+
) {
|
|
79
|
+
const { theme, mode } = useTheme();
|
|
80
|
+
const base = useTagBase();
|
|
81
|
+
const { focused, focusHandlers } = useFocusRing();
|
|
82
|
+
const { label, onRemove, removeAccessibilityLabel, style, testID } = props;
|
|
83
|
+
const v = resolveTagVariant(theme, mode, "removable");
|
|
84
|
+
return (
|
|
85
|
+
<View testID={testID} style={[base, { backgroundColor: v.bg }, style]}>
|
|
86
|
+
<Text variant={v.font} color="text-default">
|
|
87
|
+
{label}
|
|
88
|
+
</Text>
|
|
89
|
+
{/* 最小タップターゲット確保: 親 Tag の高さを超えられないので remove 自身に min 24 + hitSlop(Codex M-2)。 */}
|
|
90
|
+
<Pressable
|
|
91
|
+
{...focusHandlers}
|
|
92
|
+
onPress={onRemove}
|
|
93
|
+
accessibilityRole="button"
|
|
94
|
+
accessibilityLabel={removeAccessibilityLabel}
|
|
95
|
+
hitSlop={10}
|
|
96
|
+
style={{ minWidth: 24, minHeight: 24, alignItems: "center", justifyContent: "center" }}
|
|
97
|
+
>
|
|
98
|
+
<Text variant="xs" color="text-muted">
|
|
99
|
+
×
|
|
100
|
+
</Text>
|
|
101
|
+
<FocusRing visible={focused} radius={theme.radius.full} />
|
|
102
|
+
</Pressable>
|
|
103
|
+
</View>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function Tag(props: TagProps) {
|
|
108
|
+
const { theme, mode } = useTheme();
|
|
109
|
+
const base = useTagBase();
|
|
110
|
+
|
|
111
|
+
if (props.variant === "filter-chip") return <FilterChipTag {...props} />;
|
|
112
|
+
if (props.variant === "removable") return <RemovableTag {...props} />;
|
|
113
|
+
|
|
114
|
+
// basic(表示のみ)
|
|
115
|
+
const v = resolveTagVariant(theme, mode, "basic");
|
|
116
|
+
return (
|
|
117
|
+
<View testID={props.testID} style={[base, { backgroundColor: v.bg }, props.style]}>
|
|
118
|
+
<Text variant={v.font} color="text-default">
|
|
119
|
+
{props.label}
|
|
120
|
+
</Text>
|
|
121
|
+
</View>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Phase 2 conformance test 用の contract メタ(§2 A-3)。
|
|
126
|
+
Tag.__contract = CONTRACTS.tag;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text — 基本テキスト primitive(設計書 §1)。RN コア Text の薄いラッパ。
|
|
3
|
+
* contract: text(app 先行定義、melta-contracts)。
|
|
4
|
+
*
|
|
5
|
+
* - variant は contract 由来の VariantOf<"text">(= fontSize の段階)。theme の FontSizeKey と
|
|
6
|
+
* 構造一致している前提で theme.typography.fontSize[variant] を引く。乖離したら型エラーで検知
|
|
7
|
+
* される(= A-3 conformance の型レベル最低ライン、§2)。
|
|
8
|
+
* - 色は token キーのみ(生 hex 不可、§5 lint の意味を保つ)。
|
|
9
|
+
* - letterSpacing は role(heading/body) の ratio を fontSize から pt 換算(text.styles.ts)。
|
|
10
|
+
* - 形状(fontSize/lineHeight/letterSpacing/fontWeight)は variant/role/weight 依存なので useMemo で
|
|
11
|
+
* 分離(B-3 の「形状/色 分離 + 参照安定性」を Phase1 は useMemo で満たす。StyleSheet 事前生成の
|
|
12
|
+
* 最適化は Card で実証予定)。色のみ render 時に colors から取る。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useMemo, type ReactNode } from "react";
|
|
16
|
+
import { Text as RNText, type StyleProp, type TextStyle } from "react-native";
|
|
17
|
+
import { useTheme } from "../theme";
|
|
18
|
+
import type { FontWeightKey, SemanticColors } from "../theme";
|
|
19
|
+
import { CONTRACTS, type VariantOf } from "../contracts/contract-types";
|
|
20
|
+
import { resolveTextShape, type TextRole } from "./text.styles";
|
|
21
|
+
|
|
22
|
+
type TextVariant = VariantOf<"text">;
|
|
23
|
+
|
|
24
|
+
interface TextProps {
|
|
25
|
+
/** fontSize の段階(contract variant)。default "base"。 */
|
|
26
|
+
variant?: TextVariant;
|
|
27
|
+
/** フォントウェイト(token キー)。未指定は RN デフォルト。 */
|
|
28
|
+
weight?: FontWeightKey;
|
|
29
|
+
/** 文字色(semantic token キーのみ)。default "text-default"。 */
|
|
30
|
+
color?: keyof SemanticColors;
|
|
31
|
+
/** letterSpacing の切替軸。default "body"。 */
|
|
32
|
+
role?: TextRole;
|
|
33
|
+
numberOfLines?: number;
|
|
34
|
+
style?: StyleProp<TextStyle>;
|
|
35
|
+
testID?: string;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Text({
|
|
40
|
+
variant = "base",
|
|
41
|
+
weight,
|
|
42
|
+
color = "text-default",
|
|
43
|
+
role = "body",
|
|
44
|
+
numberOfLines,
|
|
45
|
+
style,
|
|
46
|
+
testID,
|
|
47
|
+
children,
|
|
48
|
+
}: TextProps) {
|
|
49
|
+
const { theme, colors } = useTheme();
|
|
50
|
+
|
|
51
|
+
// 形状(mode 非依存)は variant/role/weight にだけ依存させてメモ化(B-3)。
|
|
52
|
+
// 決定ロジックは pure resolver(text.styles.ts)に分離済み — recipe との機械照合対象。
|
|
53
|
+
const shape = useMemo<TextStyle>(
|
|
54
|
+
() => resolveTextShape(theme, variant, role, weight),
|
|
55
|
+
[theme, variant, role, weight],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<RNText
|
|
60
|
+
style={[shape, { color: colors[color] }, style]}
|
|
61
|
+
numberOfLines={numberOfLines}
|
|
62
|
+
accessibilityRole={role === "heading" ? "header" : undefined}
|
|
63
|
+
testID={testID}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</RNText>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Phase 2 conformance test 用の contract メタ(§2 A-3)。
|
|
71
|
+
Text.__contract = CONTRACTS.text;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* focus-ring — Pressable の focus 可視化を共通化する internal helper(設計書 §1/§2)。
|
|
3
|
+
*
|
|
4
|
+
* RN にブラウザの focus-visible / ring はないので、focus 時に「outline 相当の View overlay」を
|
|
5
|
+
* 対象の外側に絶対配置で描く(§2 mapping: focus → accessible + Pressable onFocus で outline)。
|
|
6
|
+
* 外付けキーボード / TV / a11y focus で効く。Button / Tag / Card で共有する(個別実装の重複を防ぐ)。
|
|
7
|
+
*
|
|
8
|
+
* 使い方:
|
|
9
|
+
* const { focused, focusHandlers } = useFocusRing();
|
|
10
|
+
* <Pressable {...focusHandlers} style={{ ... }}>
|
|
11
|
+
* {children}
|
|
12
|
+
* <FocusRing visible={focused} radius={theme.radius.md} />
|
|
13
|
+
* </Pressable>
|
|
14
|
+
* 注: 親 Pressable は position:relative(RN 既定)かつ overflow を hidden にしないこと
|
|
15
|
+
* (ring は inset 負値で外側に出るため)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { View } from "react-native";
|
|
20
|
+
import { useTheme } from "../../theme";
|
|
21
|
+
|
|
22
|
+
export function useFocusRing() {
|
|
23
|
+
const [focused, setFocused] = useState(false);
|
|
24
|
+
return {
|
|
25
|
+
focused,
|
|
26
|
+
focusHandlers: {
|
|
27
|
+
onFocus: () => setFocused(true),
|
|
28
|
+
onBlur: () => setFocused(false),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FocusRingProps {
|
|
34
|
+
visible: boolean;
|
|
35
|
+
/** 対象の borderRadius。ring はこれに inset 分を足した角丸で描く。 */
|
|
36
|
+
radius: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const INSET = 3;
|
|
40
|
+
|
|
41
|
+
export function FocusRing({ visible, radius }: FocusRingProps) {
|
|
42
|
+
const { theme } = useTheme();
|
|
43
|
+
if (!visible) return null;
|
|
44
|
+
return (
|
|
45
|
+
<View
|
|
46
|
+
pointerEvents="none"
|
|
47
|
+
style={{
|
|
48
|
+
position: "absolute",
|
|
49
|
+
top: -INSET,
|
|
50
|
+
left: -INSET,
|
|
51
|
+
right: -INSET,
|
|
52
|
+
bottom: -INSET,
|
|
53
|
+
borderWidth: 2,
|
|
54
|
+
borderColor: theme.color.primary["500"],
|
|
55
|
+
borderRadius: radius + INSET,
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* button.styles — Button の pure style resolver(P4 conformance 対応で Button.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/button.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/recipe-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, ThemeMode, FontSizeKey, SpacingKey } from "../theme";
|
|
10
|
+
|
|
11
|
+
export type ButtonVariant =
|
|
12
|
+
| "contained"
|
|
13
|
+
| "outlined"
|
|
14
|
+
| "brand-outline"
|
|
15
|
+
| "neutral"
|
|
16
|
+
| "lighted"
|
|
17
|
+
| "danger"
|
|
18
|
+
| "subtle";
|
|
19
|
+
export type ButtonSize = "small" | "medium" | "large";
|
|
20
|
+
|
|
21
|
+
/** size → height / 横 padding / label fontSize / icon box(contract sizes/iconButton と整合)。 */
|
|
22
|
+
export const SIZE_SPEC: Record<
|
|
23
|
+
ButtonSize,
|
|
24
|
+
{ height: number; px: SpacingKey; font: FontSizeKey; iconBox: number }
|
|
25
|
+
> = {
|
|
26
|
+
small: { height: 32, px: "3", font: "sm", iconBox: 32 },
|
|
27
|
+
medium: { height: 40, px: "4", font: "base", iconBox: 40 },
|
|
28
|
+
large: { height: 48, px: "6", font: "base", iconBox: 48 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* variant → 色解決(button.contract tokenRefs の 1:1 写像)。
|
|
33
|
+
* 文字色は variant により semantic 外(primary.500 等)もあるため hex 文字列で返し、
|
|
34
|
+
* Text の style で color 上書きする(Text の color prop は SemanticColors キー限定のため)。
|
|
35
|
+
*/
|
|
36
|
+
export function resolveVariant(
|
|
37
|
+
theme: NativeTheme,
|
|
38
|
+
mode: ThemeMode,
|
|
39
|
+
variant: ButtonVariant,
|
|
40
|
+
): { bg?: string; pressedBg?: string; textColor: string; border?: string } {
|
|
41
|
+
const c = theme.color;
|
|
42
|
+
const sem = c.semantic[mode];
|
|
43
|
+
switch (variant) {
|
|
44
|
+
case "contained":
|
|
45
|
+
return { bg: c.primary["500"], pressedBg: c.primary["700"], textColor: sem["text-on-accent"] };
|
|
46
|
+
case "outlined":
|
|
47
|
+
return {
|
|
48
|
+
bg: sem["bg-surface"],
|
|
49
|
+
pressedBg: sem["bg-surface-alt"],
|
|
50
|
+
textColor: sem["text-default"],
|
|
51
|
+
border: sem["border-default"],
|
|
52
|
+
};
|
|
53
|
+
case "brand-outline":
|
|
54
|
+
return {
|
|
55
|
+
bg: sem["bg-surface"],
|
|
56
|
+
pressedBg: c.primary["50"],
|
|
57
|
+
textColor: c.primary["500"],
|
|
58
|
+
border: c.primary["500"],
|
|
59
|
+
};
|
|
60
|
+
case "neutral":
|
|
61
|
+
return { bg: sem["bg-surface-alt"], pressedBg: sem["border-default"], textColor: sem["text-default"] };
|
|
62
|
+
case "lighted":
|
|
63
|
+
return { bg: c.primary["50"], pressedBg: c.primary["100"], textColor: c.primary["500"] };
|
|
64
|
+
case "danger":
|
|
65
|
+
return { bg: c.status.danger.base, pressedBg: c.status.danger.textLight, textColor: sem["text-on-accent"] };
|
|
66
|
+
case "subtle":
|
|
67
|
+
return { bg: "transparent", pressedBg: sem["bg-surface-alt"], textColor: sem["text-default"] };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* metric.styles — Metric の pure style resolver(styleRefs conformance 対応で Metric.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュール(型 import のみ可)にすることで node テスト
|
|
5
|
+
* (tsx --test)から直接実行できる。recipes/app/metric.recipe.json(melta-contracts の
|
|
6
|
+
* styleRefs)との機械照合は scripts/lib/metric-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FontVariant } from "react-native";
|
|
10
|
+
import type { NativeTheme, ThemeMode, FontSizeKey, FontWeightValue } from "../theme";
|
|
11
|
+
|
|
12
|
+
export type MetricSize = "sm" | "md" | "lg";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* size → value / unit の fontSize token(metric.recipe sizes と整合)。
|
|
16
|
+
* unit は value の1段下の fontSize(ベースライン揃え用)。
|
|
17
|
+
*/
|
|
18
|
+
export const METRIC_FONT: Record<MetricSize, { value: FontSizeKey; unit: FontSizeKey }> = {
|
|
19
|
+
sm: { value: "lg", unit: "base" },
|
|
20
|
+
md: { value: "2xl", unit: "lg" },
|
|
21
|
+
lg: { value: "3xl", unit: "xl" },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface MetricStyles {
|
|
25
|
+
/** value(数値本体): tabular-nums で桁揃え + bold + text-heading。 */
|
|
26
|
+
valueStyle: {
|
|
27
|
+
fontSize: number;
|
|
28
|
+
lineHeight: number;
|
|
29
|
+
fontWeight: FontWeightValue;
|
|
30
|
+
color: string;
|
|
31
|
+
fontVariant: FontVariant[];
|
|
32
|
+
};
|
|
33
|
+
/** unit(単位): value の1段下 fontSize / medium / text-muted / marginLeft spacing.1。 */
|
|
34
|
+
unitStyle: {
|
|
35
|
+
fontSize: number;
|
|
36
|
+
fontWeight: FontWeightValue;
|
|
37
|
+
color: string;
|
|
38
|
+
marginLeft: number;
|
|
39
|
+
};
|
|
40
|
+
/** label(ラベル): sm / text-muted。 */
|
|
41
|
+
labelStyle: {
|
|
42
|
+
fontSize: number;
|
|
43
|
+
color: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** size → value / unit / label の style 解決(metric.recipe styleRefs の 1:1 写像)。 */
|
|
48
|
+
export function resolveMetricStyles(
|
|
49
|
+
theme: NativeTheme,
|
|
50
|
+
mode: ThemeMode,
|
|
51
|
+
size: MetricSize,
|
|
52
|
+
): MetricStyles {
|
|
53
|
+
const sem = theme.color.semantic[mode];
|
|
54
|
+
const fontSize = theme.typography.fontSize;
|
|
55
|
+
const font = METRIC_FONT[size];
|
|
56
|
+
return {
|
|
57
|
+
valueStyle: {
|
|
58
|
+
fontSize: fontSize[font.value].fontSize,
|
|
59
|
+
lineHeight: fontSize[font.value].lineHeight,
|
|
60
|
+
fontWeight: theme.typography.fontWeight.bold,
|
|
61
|
+
color: sem["text-heading"],
|
|
62
|
+
fontVariant: ["tabular-nums"],
|
|
63
|
+
},
|
|
64
|
+
unitStyle: {
|
|
65
|
+
fontSize: fontSize[font.unit].fontSize,
|
|
66
|
+
fontWeight: theme.typography.fontWeight.medium,
|
|
67
|
+
color: sem["text-muted"],
|
|
68
|
+
marginLeft: theme.spacing["1"],
|
|
69
|
+
},
|
|
70
|
+
labelStyle: {
|
|
71
|
+
fontSize: fontSize.sm.fontSize,
|
|
72
|
+
color: sem["text-muted"],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* row.styles — Row の pure style resolver(styleRefs conformance 対応で Row.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/row.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/row-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, SpacingKey } from "../theme";
|
|
10
|
+
|
|
11
|
+
/** 交差軸の配置(RN alignItems への写像)。default "center"(recipe default に焼いてある)。 */
|
|
12
|
+
export type RowAlign = "start" | "center" | "end" | "baseline" | "stretch";
|
|
13
|
+
/** 主軸の配置(RN justifyContent への写像)。default "start"(= RN デフォルト、style 出力なし)。 */
|
|
14
|
+
export type RowJustify = "start" | "center" | "end" | "between";
|
|
15
|
+
|
|
16
|
+
const ALIGN_MAP = {
|
|
17
|
+
start: "flex-start",
|
|
18
|
+
center: "center",
|
|
19
|
+
end: "flex-end",
|
|
20
|
+
baseline: "baseline",
|
|
21
|
+
stretch: "stretch",
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
const JUSTIFY_MAP = {
|
|
25
|
+
start: "flex-start",
|
|
26
|
+
center: "center",
|
|
27
|
+
end: "flex-end",
|
|
28
|
+
between: "space-between",
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export interface RowStyleProps {
|
|
32
|
+
/** 子要素間の gap(spacing token キー限定)。省略時は gap を出力しない。 */
|
|
33
|
+
gap?: SpacingKey;
|
|
34
|
+
/** 交差軸の配置。default "center"。 */
|
|
35
|
+
align?: RowAlign;
|
|
36
|
+
/** 主軸の配置。default "start"(style 出力なし)。 */
|
|
37
|
+
justify?: RowJustify;
|
|
38
|
+
/** 折返し。default false(style 出力なし)。 */
|
|
39
|
+
wrap?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RowStyle {
|
|
43
|
+
flexDirection: "row";
|
|
44
|
+
alignItems: (typeof ALIGN_MAP)[RowAlign];
|
|
45
|
+
justifyContent?: (typeof JUSTIFY_MAP)[RowJustify];
|
|
46
|
+
flexWrap?: "wrap";
|
|
47
|
+
gap?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* props → 横並び style の解決(row.recipe styleRefs の 1:1 写像)。
|
|
52
|
+
* デフォルト(align=center / justify=start / wrap=false / gap なし)は
|
|
53
|
+
* recipe の default variant(flexDirection + alignItems: "center")と一致させている。
|
|
54
|
+
*/
|
|
55
|
+
export function resolveRowStyle(
|
|
56
|
+
theme: NativeTheme,
|
|
57
|
+
{ gap, align = "center", justify = "start", wrap = false }: RowStyleProps = {},
|
|
58
|
+
): RowStyle {
|
|
59
|
+
return {
|
|
60
|
+
flexDirection: "row",
|
|
61
|
+
alignItems: ALIGN_MAP[align],
|
|
62
|
+
...(justify !== "start" ? { justifyContent: JUSTIFY_MAP[justify] } : null),
|
|
63
|
+
...(wrap ? { flexWrap: "wrap" as const } : null),
|
|
64
|
+
...(gap != null ? { gap: theme.spacing[gap] } : null),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stack.styles — Stack の pure style resolver(styleRefs conformance 対応で Stack.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/stack.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/stack-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, SpacingKey } from "../theme";
|
|
10
|
+
|
|
11
|
+
/** 交差軸の配置(RN alignItems への写像。default stretch = RN デフォルトなので出力しない)。 */
|
|
12
|
+
export type StackAlign = "start" | "center" | "end" | "stretch";
|
|
13
|
+
|
|
14
|
+
const ALIGN_MAP = {
|
|
15
|
+
start: "flex-start",
|
|
16
|
+
center: "center",
|
|
17
|
+
end: "flex-end",
|
|
18
|
+
stretch: "stretch",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export interface StackStyleProps {
|
|
22
|
+
/** 子要素間の gap(spacing token キー限定)。省略時は gap を出力しない。 */
|
|
23
|
+
gap?: SpacingKey;
|
|
24
|
+
/** 交差軸の配置。default "stretch"(= RN デフォルト、style 出力なし)。 */
|
|
25
|
+
align?: StackAlign;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface StackStyle {
|
|
29
|
+
flexDirection: "column";
|
|
30
|
+
gap?: number;
|
|
31
|
+
alignItems?: (typeof ALIGN_MAP)[StackAlign];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* props → 縦積み style の解決(stack.recipe styleRefs の 1:1 写像)。
|
|
36
|
+
* デフォルト(gap なし / align=stretch)は recipe の default variant(flexDirection のみ)と一致させている。
|
|
37
|
+
*/
|
|
38
|
+
export function resolveStackStyle(
|
|
39
|
+
theme: NativeTheme,
|
|
40
|
+
{ gap, align = "stretch" }: StackStyleProps = {},
|
|
41
|
+
): StackStyle {
|
|
42
|
+
return {
|
|
43
|
+
flexDirection: "column",
|
|
44
|
+
...(gap != null ? { gap: theme.spacing[gap] } : null),
|
|
45
|
+
...(align !== "stretch" ? { alignItems: ALIGN_MAP[align] } : null),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tag.styles — Tag の pure style resolver(styleRefs conformance 対応で Tag.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/tag.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/tag-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, ThemeMode, FontSizeKey } from "../theme";
|
|
10
|
+
|
|
11
|
+
export type TagVariant = "basic" | "removable" | "filter-chip";
|
|
12
|
+
|
|
13
|
+
/** 共通形状(px-3 py-1 gap-1 rounded-full。tag.recipe の全 variant style と共通の寸法キー)。 */
|
|
14
|
+
export function resolveTagBase(theme: NativeTheme): {
|
|
15
|
+
gap: number;
|
|
16
|
+
paddingHorizontal: number;
|
|
17
|
+
paddingVertical: number;
|
|
18
|
+
borderRadius: number;
|
|
19
|
+
} {
|
|
20
|
+
return {
|
|
21
|
+
gap: theme.spacing["1"],
|
|
22
|
+
paddingHorizontal: theme.spacing["3"],
|
|
23
|
+
paddingVertical: theme.spacing["1"],
|
|
24
|
+
borderRadius: theme.radius.full,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* variant(+ filter-chip の selected 状態)→ 配色 + label fontSize(tag.recipe styleRefs の 1:1 写像)。
|
|
30
|
+
*
|
|
31
|
+
* - selected は contract states active/inactive → prop selected:boolean の写像(filter-chip のみ有効)。
|
|
32
|
+
* active は bg=primary.50 / border=primary.200 / text=primary.700 の差分、inactive は variant style そのまま。
|
|
33
|
+
* - 文字色は active 時に semantic 外(primary.700)になるため hex 文字列で返し、
|
|
34
|
+
* Text の style で color 上書きする(Text の color prop は SemanticColors キー限定のため)。
|
|
35
|
+
*/
|
|
36
|
+
export function resolveTagVariant(
|
|
37
|
+
theme: NativeTheme,
|
|
38
|
+
mode: ThemeMode,
|
|
39
|
+
variant: TagVariant,
|
|
40
|
+
selected = false,
|
|
41
|
+
): { bg: string; border?: string; borderWidth?: number; textColor: string; font: FontSizeKey } {
|
|
42
|
+
const c = theme.color;
|
|
43
|
+
const sem = c.semantic[mode];
|
|
44
|
+
switch (variant) {
|
|
45
|
+
case "basic":
|
|
46
|
+
case "removable":
|
|
47
|
+
return { bg: sem["bg-page-alt"], textColor: sem["text-default"], font: "xs" };
|
|
48
|
+
case "filter-chip":
|
|
49
|
+
return selected
|
|
50
|
+
? {
|
|
51
|
+
bg: c.primary["50"],
|
|
52
|
+
borderWidth: 1,
|
|
53
|
+
border: c.primary["200"],
|
|
54
|
+
textColor: c.primary["700"],
|
|
55
|
+
font: "sm",
|
|
56
|
+
}
|
|
57
|
+
: {
|
|
58
|
+
bg: sem["bg-surface"],
|
|
59
|
+
borderWidth: 1,
|
|
60
|
+
border: sem["border-default"],
|
|
61
|
+
textColor: sem["text-default"],
|
|
62
|
+
font: "sm",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* text.styles — Text の pure style resolver(styleRefs conformance 対応で Text.tsx から分離)。
|
|
3
|
+
*
|
|
4
|
+
* react-native を import しない純粋モジュールにすることで node テスト(tsx --test)から
|
|
5
|
+
* 直接実行できる。recipes/app/text.recipe.json(melta-contracts の styleRefs)との
|
|
6
|
+
* 機械照合は scripts/lib/text-conformance.test.ts が行う。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NativeTheme, FontSizeKey, FontWeightKey, FontWeightValue } from "../theme";
|
|
10
|
+
|
|
11
|
+
/** letterSpacing の切替軸(text.recipe description の role prop に対応)。 */
|
|
12
|
+
export type TextRole = "heading" | "body";
|
|
13
|
+
|
|
14
|
+
/** Text の形状 style(mode 非依存部分。文字色は render 時に semantic colors から取る)。 */
|
|
15
|
+
export interface TextShape {
|
|
16
|
+
fontSize: number;
|
|
17
|
+
lineHeight: number;
|
|
18
|
+
letterSpacing: number;
|
|
19
|
+
fontWeight?: FontWeightValue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* variant / role / weight → 形状解決(text.recipe styleRefs の 1:1 写像)。
|
|
24
|
+
* - fontSize token は複合値(px + lineHeight 比率)。theme 生成時に lineHeight は px 化済み。
|
|
25
|
+
* - letterSpacing は em 比率 token のため fontSize × ratio で pt 化する。
|
|
26
|
+
* theme の resolveLetterSpacing と同一式だが、theme index 経由の runtime import は
|
|
27
|
+
* ThemeProvider → react-native を引くため、pure に保つ目的でここでは式を直書きする。
|
|
28
|
+
*/
|
|
29
|
+
export function resolveTextShape(
|
|
30
|
+
theme: NativeTheme,
|
|
31
|
+
variant: FontSizeKey,
|
|
32
|
+
role: TextRole,
|
|
33
|
+
weight?: FontWeightKey,
|
|
34
|
+
): TextShape {
|
|
35
|
+
const fs = theme.typography.fontSize[variant];
|
|
36
|
+
return {
|
|
37
|
+
fontSize: fs.fontSize,
|
|
38
|
+
lineHeight: fs.lineHeight,
|
|
39
|
+
letterSpacing: fs.fontSize * theme.typography.letterSpacingRatio[role],
|
|
40
|
+
...(weight ? { fontWeight: theme.typography.fontWeight[weight] } : null),
|
|
41
|
+
};
|
|
42
|
+
}
|