melta-app 0.2.3 → 0.4.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 +2 -2
- package/README.md +39 -0
- package/docs/patterns.md +263 -0
- package/lib/module/components/Radio.js +11 -7
- package/lib/module/components/Radio.js.map +1 -1
- package/lib/module/components/TextField.js +31 -5
- package/lib/module/components/TextField.js.map +1 -1
- package/lib/module/safe-area/index.js +29 -24
- package/lib/module/safe-area/index.js.map +1 -1
- package/lib/module/theme/ThemeProvider.js +45 -6
- package/lib/module/theme/ThemeProvider.js.map +1 -1
- package/lib/module/theme/define-theme.js +360 -0
- package/lib/module/theme/define-theme.js.map +1 -0
- package/lib/module/theme/index.js +2 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/typescript/src/components/Radio.d.ts.map +1 -1
- package/lib/typescript/src/components/TextField.d.ts +21 -4
- package/lib/typescript/src/components/TextField.d.ts.map +1 -1
- package/lib/typescript/src/safe-area/index.d.ts +7 -3
- package/lib/typescript/src/safe-area/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/ThemeProvider.d.ts +26 -5
- package/lib/typescript/src/theme/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/theme/define-theme.d.ts +113 -0
- package/lib/typescript/src/theme/define-theme.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +3 -0
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +5 -0
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/llms.txt +62 -0
- package/package.json +5 -2
- package/src/components/Radio.tsx +9 -6
- package/src/components/TextField.tsx +48 -6
- package/src/theme/ThemeProvider.tsx +74 -12
- package/src/theme/define-theme.ts +426 -0
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +6 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* define-theme — 消費者ブランドの theme を注入するための入口(設計書 Step 2-①)。
|
|
3
|
+
*
|
|
4
|
+
* melta 既定の theme は melta-contracts から codegen した `nativeTheme` だが、消費者が自分の
|
|
5
|
+
* ブランドトークンで塗り替えられる正規の口をここで提供する。`defineTheme()` を通した成果物だけを
|
|
6
|
+
* `<ThemeProvider theme={...}>` が受け取る(Chakra の `$$chakra` と同じ brand 方式。
|
|
7
|
+
* 生オブジェクトの直渡しを型で塞ぎ、validation を必ず1回通すため)。
|
|
8
|
+
*
|
|
9
|
+
* 設計の要点:
|
|
10
|
+
* - **capability は宣言でなく導出する**。「この theme は dark しか持たない」は
|
|
11
|
+
* `color.semantic` のキー集合そのものが語る(Primer の colorSchemes / DTCG Resolver の contexts と同じ流儀)。
|
|
12
|
+
* 別欄で宣言すると「宣言と実体の不一致」という新種の嘘が生まれ、それを警備する検査が要るだけになる。
|
|
13
|
+
* - **嘘の値で埋めない**。単一 colorScheme の theme は存在しない mode を **書かない**。
|
|
14
|
+
* ゼロ埋め・他 mode のコピー・センチネル値はいずれも「その mode が存在する」という嘘をトークンに残す。
|
|
15
|
+
* - **Proxy は使わない**。Hermes は Proxy が使われていなくても全プロパティアクセスに分岐が入る
|
|
16
|
+
* (hermes#33)。存在しない mode の検出は non-enumerable な getter で行う(下記)。
|
|
17
|
+
*
|
|
18
|
+
* 注: accent(primary パレット / text-accent)・elevation・status の light 値は、単一 dark の
|
|
19
|
+
* theme でも現状 **必須のまま**。①の射程は colorScheme 軸だけで、それらの capability 化は
|
|
20
|
+
* 後続(accent 依存の除去)で扱う。
|
|
21
|
+
*/
|
|
22
|
+
import type { NativeTheme, SemanticColors, ThemeColor, ThemeMode } from "./types";
|
|
23
|
+
/**
|
|
24
|
+
* 開発ビルドか。**裸の `__DEV__` を直接書かないこと。**
|
|
25
|
+
*
|
|
26
|
+
* `__DEV__` は Metro のグローバルで、Metro なら prod ビルド時に `false` へ畳み込まれて
|
|
27
|
+
* dev ブロックごと消える。一方 melta は package.json の exports の `import` / `default` 条件で
|
|
28
|
+
* 同じ ESM を非 Metro の web バンドラにも配っており、react-native-web は `__DEV__` を
|
|
29
|
+
* **定義しない**(vendored 側で `process.env.NODE_ENV` にローカル shim している)。
|
|
30
|
+
* そこで `typeof` で存在を確かめ、無い環境では `NODE_ENV === "development"` のときだけ有効にする
|
|
31
|
+
* (判定不能なら dev 機能を**切る**側に倒す = 本番に dev コストを漏らさない)。
|
|
32
|
+
* `process` を識別子として直接参照しないのは、それを書くと tsconfig.build.json(bob build)だけが
|
|
33
|
+
* TS2591 で落ちるため(typecheck は通ってしまうので気づけない)。
|
|
34
|
+
*/
|
|
35
|
+
export declare const isDev: boolean;
|
|
36
|
+
/** theme が持つ配色の能力。`color.semantic` のキー集合から導出される(宣言ではない)。 */
|
|
37
|
+
export type ColorSchemeCapability = "light-dark" | "single-light" | "single-dark";
|
|
38
|
+
/**
|
|
39
|
+
* 解決済みの capability 集合。context 経由で全 component が読める。
|
|
40
|
+
* 後続で accent / elevation の軸が増えるのもこの器(それらはキー集合から導出できないので宣言になる)。
|
|
41
|
+
*/
|
|
42
|
+
export interface ResolvedCapabilities {
|
|
43
|
+
colorScheme: ColorSchemeCapability;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `defineTheme()` の入力。`NativeTheme` との差は `color.semantic` が mode ごとに任意な点だけ。
|
|
47
|
+
* 持っていない mode は **書かない**(capability はキー集合から導出される)。
|
|
48
|
+
*/
|
|
49
|
+
export interface ThemeOptions extends Omit<NativeTheme, "color"> {
|
|
50
|
+
/** 診断・エラーメッセージ用の識別子(任意)。preset compiler の成果物では preset 名を入れる。 */
|
|
51
|
+
readonly id?: string;
|
|
52
|
+
readonly version?: string;
|
|
53
|
+
color: Omit<ThemeColor, "semantic"> & {
|
|
54
|
+
semantic: Partial<Record<ThemeMode, SemanticColors>>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `defineTheme()` の成果物。`ThemeProvider` の `theme` prop が受け取れる唯一の形。
|
|
59
|
+
* `$$melta` brand により生オブジェクトの直渡しを型で塞ぐ(= validation を必ず1回通す)。
|
|
60
|
+
*/
|
|
61
|
+
export interface ResolvedNativeTheme extends ThemeOptions {
|
|
62
|
+
readonly $$melta: true;
|
|
63
|
+
readonly capabilities: ResolvedCapabilities;
|
|
64
|
+
}
|
|
65
|
+
/** 解決した mode が theme の能力と食い違ったこと。ThemeProvider は dev で報告し、値は clamp する。 */
|
|
66
|
+
export interface ThemeModeViolation {
|
|
67
|
+
kind: "forced-mode-unsupported";
|
|
68
|
+
/** 呼び出し側が forcedMode で明示した mode。 */
|
|
69
|
+
requested: ThemeMode;
|
|
70
|
+
/** 実際に使われる mode。 */
|
|
71
|
+
resolved: ThemeMode;
|
|
72
|
+
colorScheme: ColorSchemeCapability;
|
|
73
|
+
}
|
|
74
|
+
/** theme が実際に値を持っている mode(enumerable なキーのみ = dev getter を踏まない)。 */
|
|
75
|
+
export declare function declaredModes(semantic: Partial<Record<ThemeMode, SemanticColors>>): ThemeMode[];
|
|
76
|
+
/** colorScheme capability を `color.semantic` のキー集合から導出する。 */
|
|
77
|
+
export declare function deriveColorScheme(semantic: Partial<Record<ThemeMode, SemanticColors>>): ColorSchemeCapability;
|
|
78
|
+
/** capability が実際に描ける mode。 */
|
|
79
|
+
export declare function supportedModes(colorScheme: ColorSchemeCapability): ThemeMode[];
|
|
80
|
+
/**
|
|
81
|
+
* 表示 mode を解決する(純関数。ThemeProvider はこの結果に従うだけで判定はしない)。
|
|
82
|
+
*
|
|
83
|
+
* - **OS 由来の不一致は無反応で clamp する**。「light テーマを作らない」は消費者の意図的な設計判断で、
|
|
84
|
+
* OS が light なのは環境の事実にすぎない。ここで警告を出すと light 設定のユーザ全員に出てしまう
|
|
85
|
+
* (CSS の `color-scheme` / Expo の `userInterfaceStyle` も黙って clamp する)。
|
|
86
|
+
* - **`forcedMode` との不一致は報告する**。宣言同士の矛盾=呼び出し側の間違いなので violation を返す。
|
|
87
|
+
* ただし throw はしない(clamp して描き続ける)。厳格に落としたいテストや compiler が
|
|
88
|
+
* violation を error に昇格させる側の責務にする。
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveMode(colorScheme: ColorSchemeCapability, forcedMode: ThemeMode | undefined, systemMode: ThemeMode): {
|
|
91
|
+
mode: ThemeMode;
|
|
92
|
+
violation?: ThemeModeViolation;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* theme の形を検査して問題の一覧を返す(空配列 = 問題なし)。
|
|
96
|
+
*
|
|
97
|
+
* 「宣言した mode の色が欠けている」「as キャストでキーが抜けた」といった、型を通り抜けた欠落を拾う。
|
|
98
|
+
* `defineTheme()` が dev で1回だけ呼ぶ。**render 中に呼ばないこと**(theme-ui は Provider の
|
|
99
|
+
* render body で毎レンダー全色を再帰走査して性能を落とした前例がある)。
|
|
100
|
+
*/
|
|
101
|
+
export declare function validateTheme(options: ThemeOptions): string[];
|
|
102
|
+
/**
|
|
103
|
+
* ブランド theme を組み立てる。`ThemeProvider` の `theme` prop に渡せるのはこの戻り値だけ。
|
|
104
|
+
*
|
|
105
|
+
* **module スコープで1回だけ呼ぶこと**(render の中で呼ぶと毎レンダー新しい参照になり、
|
|
106
|
+
* context の consumer が全部再レンダーする)。styled-components / Emotion が docs で
|
|
107
|
+
* 同じことを求めているのと同じ理由。
|
|
108
|
+
*
|
|
109
|
+
* dev では validateTheme を1回通し、問題があれば throw する(構造が壊れた theme は
|
|
110
|
+
* 後段で必ず事故になるので早く落とす)。production では検査ごと省く。
|
|
111
|
+
*/
|
|
112
|
+
export declare function defineTheme(options: ThemeOptions): ResolvedNativeTheme;
|
|
113
|
+
//# sourceMappingURL=define-theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-theme.d.ts","sourceRoot":"","sources":["../../../../src/theme/define-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAIV,WAAW,EAGX,cAAc,EAGd,UAAU,EACV,SAAS,EACV,MAAM,SAAS,CAAC;AAMjB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,EAAE,OAID,CAAC;AAMpB,4DAA4D;AAC5D,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,CAAC;AAElF;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,qBAAqB,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9D,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG;QACpC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;KACtD,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;CAC7C;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,yBAAyB,CAAC;IAChC,oCAAoC;IACpC,SAAS,EAAE,SAAS,CAAC;IACrB,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,EAAE,qBAAqB,CAAC;CACpC;AA0GD,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,GAAG,SAAS,EAAE,CAE/F;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,GACnD,qBAAqB,CAQvB;AAED,+BAA+B;AAC/B,wBAAgB,cAAc,CAAC,WAAW,EAAE,qBAAqB,GAAG,SAAS,EAAE,CAI9E;AAMD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,qBAAqB,EAClC,UAAU,EAAE,SAAS,GAAG,SAAS,EACjC,UAAU,EAAE,SAAS,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAAE,CAYrD;AAWD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAwD7D;AAoDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,mBAAmB,CAyBtE"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* melta-app theme エントリ。
|
|
3
3
|
* 型は types.ts、値は native-theme.ts(melta-contracts から自動生成)。
|
|
4
|
+
* 消費者ブランドの theme を注入する口は define-theme.ts(`defineTheme`)。
|
|
4
5
|
*/
|
|
5
6
|
export * from "./types";
|
|
6
7
|
export { nativeTheme } from "./native-theme";
|
|
7
8
|
export { ThemeProvider, useTheme } from "./ThemeProvider";
|
|
8
9
|
export type { ThemeContextValue, ThemeMode } from "./ThemeProvider";
|
|
10
|
+
export { defineTheme, validateTheme, resolveMode, supportedModes, deriveColorScheme, declaredModes, } from "./define-theme";
|
|
11
|
+
export type { ColorSchemeCapability, ResolvedCapabilities, ResolvedNativeTheme, ThemeModeViolation, ThemeOptions, } from "./define-theme";
|
|
9
12
|
/**
|
|
10
13
|
* letterSpacingRatio(em 相当の比率)を、適用先の fontSize から RN の letterSpacing(pt) に解決する。
|
|
11
14
|
* 例: resolveLetterSpacing(18, theme.typography.letterSpacingRatio.body)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EACL,WAAW,EACX,aAAa,EACb,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5E"}
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* web 形式 → RN 形式の正規化規則は requirements-melta-app.md §4 を参照。
|
|
7
7
|
*/
|
|
8
8
|
import type { ViewStyle } from "react-native";
|
|
9
|
+
/**
|
|
10
|
+
* 表示モード。ThemeProvider が OS の colorScheme / forcedMode / theme の能力から解決する。
|
|
11
|
+
* (define-theme.ts と ThemeProvider.tsx の相互 import を避けるため型の実体はここに置く)
|
|
12
|
+
*/
|
|
13
|
+
export type ThemeMode = "light" | "dark";
|
|
9
14
|
/** primary パレット(50〜950)。値は hex 文字列。 */
|
|
10
15
|
export type PrimaryScale = "50" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900" | "950";
|
|
11
16
|
/** semantic カラー(light / dark で同じキー集合を持つ)。値は色文字列。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# melta-app
|
|
2
|
+
|
|
3
|
+
> melta デザインシステムの React Native(Expo)UI kit。web 版 melta-ui と同じデザイン契約(melta-contracts、全 40 契約)を SSOT に、RN 実装 25 個で契約を満たす(planned 9 / not-planned 6 / adapted 形 4)。アプリ本体ではなく `npm install melta-app` で使うライブラリ。必須 peerDependencies は react / react-native のみ(optional peer: react-native-svg = Icon 用 / react-native-safe-area-context = SafeArea 差替用)。
|
|
4
|
+
|
|
5
|
+
UI を組む前に docs/patterns.md(組み方の規範)を読むこと。exact value(トークン・契約・禁止ルール)の SSOT は melta-contracts。melta-app にトークンの手書きは存在しない(theme は契約からの生成物)。デザイン言語の一次資料は web 版 https://melta.tsubotax.com/llms.txt。
|
|
6
|
+
|
|
7
|
+
## Docs
|
|
8
|
+
|
|
9
|
+
- [README.md](https://github.com/tsubotax/melta-app#readme): 全体像(Install / 設計の核 / トークン正規化 / コンポーネント表)
|
|
10
|
+
- [patterns.md](https://app.melta.tsubotax.com/docs/patterns.md): フォーム等「組み方」の規範 + コピペ可能スニペット(実コードと drift 検査済み)
|
|
11
|
+
- [showcase](https://app.melta.tsubotax.com/): Live Catalog(実 RN コンポーネントの web export)+ 実装状態の全量表
|
|
12
|
+
- [tokens.json](https://melta.tsubotax.com/design/contracts/tokens.json): デザイントークン(契約 SSOT の配信面)
|
|
13
|
+
- [rules.json](https://melta.tsubotax.com/design/contracts/rules.json): 禁止ルール
|
|
14
|
+
|
|
15
|
+
## Components
|
|
16
|
+
|
|
17
|
+
- [action-sheet](https://melta.tsubotax.com/design/contracts/components/action-sheet.contract.json): ✅ `ActionSheet` — 画面下から出るアクション/選択リスト。web の select / dropdown を APP に adapted 変換する際の受け皿(構造的 P0)。destructive アクションは色で区別しラベルでも伝える。
|
|
18
|
+
- [alert](https://melta.tsubotax.com/design/contracts/components/alert.contract.json): ✅ `Alert` — インラインアラート通知。Info / Success / Warning / Error の4タイプ。
|
|
19
|
+
- [avatar](https://melta.tsubotax.com/design/contracts/components/avatar.contract.json): ✅ `Avatar` — ユーザーアバター。Image / Initials / Status / Group。
|
|
20
|
+
- [bottom-sheet](https://melta.tsubotax.com/design/contracts/components/bottom-sheet.contract.json): ✅ `BottomSheet` — 画面下から出る汎用シート container(content は自由 slot)。フィルタ・詳細・複雑な選択 UI の受け皿。ActionSheet がリスト特化なのに対しこちらは容器。
|
|
21
|
+
- [button](https://melta.tsubotax.com/design/contracts/components/button.contract.json): ✅ `Button` — CTA / secondary / icon button。3階層ヒエラルキー。
|
|
22
|
+
- [card](https://melta.tsubotax.com/design/contracts/components/card.contract.json): ✅ `Card` — コンテンツコンテナ。Basic / Metrics / Media / Action / Link の5バリエーション。
|
|
23
|
+
- [checkbox](https://melta.tsubotax.com/design/contracts/components/checkbox.contract.json): ✅ `Checkbox` — 複数選択フォームコントロール。CSS 18px サイズ。
|
|
24
|
+
- [empty-state](https://melta.tsubotax.com/design/contracts/components/empty-state.contract.json): ✅ `EmptyState` — データ無し時のプレースホルダー(アイコン + タイトル + 説明 + 任意のアクション)。Text + Button の compose。
|
|
25
|
+
- [header](https://melta.tsubotax.com/design/contracts/components/header.contract.json): ✅ `Header` — 画面ヘッダー primitive(title + leading? / trailing? slot、下 border)。Screen の header slot に差して使う想定。title は text-heading の見出しとして読
|
|
26
|
+
- [icon](https://melta.tsubotax.com/design/contracts/components/icon.contract.json): ✅ `Icon` — 統一アイコン primitive。name prop でグリフを選択し(グリフ集合は実装側の公開面 = assets/icons の Charcoal セット由来)、色は semantic token キー限定の prop(default
|
|
27
|
+
- [image](https://melta.tsubotax.com/design/contracts/components/image.contract.json): ✅ `Image` — 画像表示 primitive。aspectRatio / radius / contentFit / onError fallback を持つ。Phase1 は RN コア Image、expo-image は Phase2。
|
|
28
|
+
- [metric](https://melta.tsubotax.com/design/contracts/components/metric.contract.json): ✅ `Metric` — 数値 + 単位 + ラベルの指標表示 primitive(距離 / 時間 / 獲得標高 等の走行サマリー表示)。tabular-nums で桁揃え。sizes の height は数値テキストの想定行高(px)で、実 fontSize は実
|
|
29
|
+
- [modal](https://melta.tsubotax.com/design/contracts/components/modal.contract.json): ✅ `Modal` — フォーカストラップとバックドロップを備えたモーダルダイアログ。
|
|
30
|
+
- [progress](https://melta.tsubotax.com/design/contracts/components/progress.contract.json): ✅ `Progress` — リニアプログレスバー。aria-valuenow/min/max必須。
|
|
31
|
+
- [radio](https://melta.tsubotax.com/design/contracts/components/radio.contract.json): ✅ `Radio` — 単一選択フォームコントロール。fieldsetラッパー必須。
|
|
32
|
+
- [row](https://melta.tsubotax.com/design/contracts/components/row.contract.json): ✅ `Row` — 子要素を横に並べる layout primitive。gap は spacing token キー限定の prop。折返し(wrap)と主軸配置(justify)を持ち、タグ列・メトリクス列・ヘッダー行の生 flexbox 手書きを置き換え
|
|
33
|
+
- [screen](https://melta.tsubotax.com/design/contracts/components/screen.contract.json): ✅ `Screen` — 画面骨格 primitive(SafeArea + bg-page + content padding)。header slot を scroll 外に固定し、毎画面手書きされる SafeAreaView + ScrollView + ヘッ
|
|
34
|
+
- [skeleton](https://melta.tsubotax.com/design/contracts/components/skeleton.contract.json): ✅ `Skeleton` — ローディングプレースホルダー。aria-busy="true"必須。
|
|
35
|
+
- [stack](https://melta.tsubotax.com/design/contracts/components/stack.contract.json): ✅ `Stack` — 子要素を縦に積む layout primitive。gap は spacing token キー限定の prop(省略時 gap なし)。画面の縦リズムを生 flexbox 手書きから置き換える(dogfood 不足-1)。
|
|
36
|
+
- [surface](https://melta.tsubotax.com/design/contracts/components/surface.contract.json): ✅ `Surface` — Card / Skeleton / EmptyState の共通土台となる箱 primitive(bg / radius / elevation / padding)。melta-app では internal(未 export)。
|
|
37
|
+
- [tag](https://melta.tsubotax.com/design/contracts/components/tag.contract.json): ✅ `Tag` — 削除可能なタグ / フィルターチップ。
|
|
38
|
+
- [text](https://melta.tsubotax.com/design/contracts/components/text.contract.json): ✅ `Text` — 本文・見出し・キャプションの基本テキスト primitive。fontSize variant と semantic color、heading/body で letterSpacing を切替。
|
|
39
|
+
- [textfield](https://melta.tsubotax.com/design/contracts/components/textfield.contract.json): ✅ `TextField` — テキスト入力フィールド。Default / Error / Disabled / Success の4状態。
|
|
40
|
+
- [toast](https://melta.tsubotax.com/design/contracts/components/toast.contract.json): ✅ `Toast` — 一時的な通知メッセージ。4タイプ(success/error/warning/info)。
|
|
41
|
+
- [toggle](https://melta.tsubotax.com/design/contracts/components/toggle.contract.json): ✅ `Toggle` — ON/OFFスイッチコンポーネント。role="switch"必須。
|
|
42
|
+
- [accordion](https://melta.tsubotax.com/design/contracts/components/accordion.contract.json): ⬜ planned
|
|
43
|
+
- [badge](https://melta.tsubotax.com/design/contracts/components/badge.contract.json): ⬜ planned
|
|
44
|
+
- [datepicker](https://melta.tsubotax.com/design/contracts/components/datepicker.contract.json): ⬜ planned(adapted: カレンダー自作はしない。OS 標準の日付ピッカー(@react-native-community/datetimepicker 等)への委譲でトークンのみ供給)
|
|
45
|
+
- [divider](https://melta.tsubotax.com/design/contracts/components/divider.contract.json): ⬜ planned
|
|
46
|
+
- [dropdown](https://melta.tsubotax.com/design/contracts/components/dropdown.contract.json): ⬜ planned(adapted: hover 起動は存在しない。タップ起動の Menu(アンカー付き)or ActionSheet に変換)
|
|
47
|
+
- [list](https://melta.tsubotax.com/design/contracts/components/list.contract.json): ⬜ planned
|
|
48
|
+
- [select](https://melta.tsubotax.com/design/contracts/components/select.contract.json): ⬜ planned(adapted: web のドロップリストは持ち込まない。ActionSheet / BottomSheet / OS Picker で開く選択 UI に変換(gluestack/Tamagui も native では Sheet 化))
|
|
49
|
+
- [stepper](https://melta.tsubotax.com/design/contracts/components/stepper.contract.json): ⬜ planned
|
|
50
|
+
- [tabs](https://melta.tsubotax.com/design/contracts/components/tabs.contract.json): ⬜ planned(adapted: トップタブ / SegmentedControl 型として実装(web の下線タブ意味論を M3 segmented / iOS UISegmentedControl に写像))
|
|
51
|
+
- [breadcrumb](https://melta.tsubotax.com/design/contracts/components/breadcrumb.contract.json): 🚫 not-planned — RN 主要ライブラリ提供 0/5。Apple HIG が multisegment breadcrumb を明示的に否定。back ボタン + nav bar タイトルで代替
|
|
52
|
+
- [copy-button](https://melta.tsubotax.com/design/contracts/components/copy-button.contract.json): 🚫 not-planned — DS プリミティブでなくアプリ層のコンポジット(button + clipboard + toast)
|
|
53
|
+
- [pagination](https://melta.tsubotax.com/design/contracts/components/pagination.contract.json): 🚫 not-planned — モバイルは無限スクロール(FlatList onEndReached)で代替。カルーセル文脈は PageControl の領分
|
|
54
|
+
- [sidebar](https://melta.tsubotax.com/design/contracts/components/sidebar.contract.json): 🚫 not-planned — Navigation Drawer の領分=react-navigation 側の責務。DS はトークン供給のみ
|
|
55
|
+
- [table](https://melta.tsubotax.com/design/contracts/components/table.contract.json): 🚫 not-planned — モバイルは単列リスト / Card への再構成が定石(M3 に data table なし)。list + metric で分解表現する
|
|
56
|
+
- [tooltip](https://melta.tsubotax.com/design/contracts/components/tooltip.contract.json): 🚫 not-planned — hover 前提のため。iPhone HIG に tooltip 概念なし。必要になれば長押し Hint として別契約を切る
|
|
57
|
+
|
|
58
|
+
## Optional
|
|
59
|
+
|
|
60
|
+
- [melta-ui llms.txt](https://melta.tsubotax.com/llms.txt): web 版デザインシステムの AI 入口(デザイン言語の一次資料)
|
|
61
|
+
- [npm](https://www.npmjs.com/package/melta-app): パッケージ(Icon は melta-app/icons、SafeArea 差替は melta-app/safe-area サブパス)
|
|
62
|
+
- [THIRD_PARTY_LICENSES.md](https://github.com/tsubotax/melta-app/blob/main/THIRD_PARTY_LICENSES.md): Icon グリフの帰属表示
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "melta-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "melta for APP — melta デザインシステムの React Native UI kit。melta-contracts(デザイン契約)を single source of truth に RN 実装でその契約を満たす。アプリ本体ではなくライブラリ。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://app.melta.tsubotax.com",
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"lib",
|
|
44
44
|
"src",
|
|
45
45
|
"!src/**/*.test.*",
|
|
46
|
+
"llms.txt",
|
|
47
|
+
"docs",
|
|
46
48
|
"THIRD_PARTY_LICENSES.md"
|
|
47
49
|
],
|
|
48
50
|
"sideEffects": false,
|
|
@@ -100,7 +102,8 @@
|
|
|
100
102
|
"typecheck": "tsc --noEmit && tsc --noEmit -p example/tsconfig.json && tsc --noEmit -p scripts/tsconfig.json",
|
|
101
103
|
"lint": "eslint . --max-warnings 0",
|
|
102
104
|
"check:drift": "tsx scripts/check-drift.ts",
|
|
103
|
-
"check:installability": "bash scripts/check-installability.sh"
|
|
105
|
+
"check:installability": "bash scripts/check-installability.sh",
|
|
106
|
+
"build:llms": "tsx scripts/build-llms-txt.ts"
|
|
104
107
|
},
|
|
105
108
|
"jest": {
|
|
106
109
|
"preset": "@react-native/jest-preset",
|
package/src/components/Radio.tsx
CHANGED
|
@@ -136,12 +136,15 @@ export function Radio({
|
|
|
136
136
|
</View>
|
|
137
137
|
{hasError ? (
|
|
138
138
|
// danger.text-light は SemanticColors 外なので style で上書き(Button の textColor と同じ扱い)。
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
// View の liveRegion で出現を通知(melta Text は a11y props を透過しないため)。
|
|
140
|
+
<View accessibilityLiveRegion="polite">
|
|
141
|
+
<Text
|
|
142
|
+
variant="xs"
|
|
143
|
+
style={{ color: group.errorTextStyle.color, marginTop: group.errorTextStyle.marginTop }}
|
|
144
|
+
>
|
|
145
|
+
{error}
|
|
146
|
+
</Text>
|
|
147
|
+
</View>
|
|
145
148
|
) : null}
|
|
146
149
|
</View>
|
|
147
150
|
);
|
|
@@ -5,11 +5,18 @@
|
|
|
5
5
|
* - variant は検証状態(default/error/success)。disabled=true で variant を "disabled" に上書きし、
|
|
6
6
|
* editable=false + accessibilityState.disabled を併せて適用する(recipe description の規約)。
|
|
7
7
|
* - error 時は errorText を必ずテキストで表示する(FORM_NO_COLOR_ONLY_ERROR = 色だけで伝えない)。
|
|
8
|
-
* RN には aria-describedby 相当が無いため、
|
|
8
|
+
* RN には aria-describedby 相当が無いため、(a) errorText を input の accessibilityLabel に合成
|
|
9
|
+
* (再フォーカス時に読み上げ)、(b) errorText 側に accessibilityLiveRegion="polite"(Android は
|
|
10
|
+
* 出現時に通知。iOS の即時通知が要る画面は submit 時に announceForAccessibility を併用する —
|
|
11
|
+
* docs/patterns.md 参照)。
|
|
9
12
|
* - focus は内部 state(onFocus/onBlur)で recipe states.focus の inputStyle 差分
|
|
10
|
-
* (borderColor=primary.500)を重ねる。disabled 時は focus
|
|
13
|
+
* (borderColor=primary.500)を重ねる。disabled 時は focus スタイルを付けない。
|
|
14
|
+
* 外部の onFocus/onBlur は RN の event ごと常に透過する(内部の視覚 state と分離)。
|
|
11
15
|
* - 色・寸法の決定は pure resolver(textfield.styles.ts)に分離。
|
|
12
16
|
* recipes/app/textfield.recipe.json との機械照合は scripts/lib/textfield-conformance.test.ts が行う。
|
|
17
|
+
* - RN TextInput へ透過するのは入力メソッド系 props(keyboardType / autoCapitalize / autoCorrect /
|
|
18
|
+
* maxLength / returnKeyType / onSubmitEditing / onBlur / onFocus)のみ。style 系 props は
|
|
19
|
+
* 透過しない(token 純度維持)。
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
import { useMemo, useState } from "react";
|
|
@@ -18,6 +25,9 @@ import {
|
|
|
18
25
|
TextInput,
|
|
19
26
|
View,
|
|
20
27
|
Text as RNText,
|
|
28
|
+
type KeyboardTypeOptions,
|
|
29
|
+
type ReturnKeyTypeOptions,
|
|
30
|
+
type TextInputProps,
|
|
21
31
|
type StyleProp,
|
|
22
32
|
type TextStyle,
|
|
23
33
|
type ViewStyle,
|
|
@@ -70,6 +80,16 @@ interface TextFieldProps {
|
|
|
70
80
|
helperText?: string;
|
|
71
81
|
/** variant="error" 時に表示するエラーメッセージ(色だけでなくテキストで伝える)。 */
|
|
72
82
|
errorText?: string;
|
|
83
|
+
/** キーボード種別(RN TextInput へ透過)。 */
|
|
84
|
+
keyboardType?: KeyboardTypeOptions;
|
|
85
|
+
autoCapitalize?: "none" | "sentences" | "words" | "characters";
|
|
86
|
+
autoCorrect?: boolean;
|
|
87
|
+
maxLength?: number;
|
|
88
|
+
returnKeyType?: ReturnKeyTypeOptions;
|
|
89
|
+
onSubmitEditing?: TextInputProps["onSubmitEditing"];
|
|
90
|
+
/** blur 時コールバック(blur 検証用)。RN の event ごと透過し、内部の focus スタイル制御と分離。 */
|
|
91
|
+
onBlur?: TextInputProps["onBlur"];
|
|
92
|
+
onFocus?: TextInputProps["onFocus"];
|
|
73
93
|
style?: StyleProp<ViewStyle>;
|
|
74
94
|
testID?: string;
|
|
75
95
|
}
|
|
@@ -84,6 +104,14 @@ export function TextField({
|
|
|
84
104
|
size = "medium",
|
|
85
105
|
helperText,
|
|
86
106
|
errorText,
|
|
107
|
+
keyboardType,
|
|
108
|
+
autoCapitalize,
|
|
109
|
+
autoCorrect,
|
|
110
|
+
maxLength,
|
|
111
|
+
returnKeyType,
|
|
112
|
+
onSubmitEditing,
|
|
113
|
+
onBlur,
|
|
114
|
+
onFocus,
|
|
87
115
|
style,
|
|
88
116
|
testID,
|
|
89
117
|
}: TextFieldProps) {
|
|
@@ -109,18 +137,32 @@ export function TextField({
|
|
|
109
137
|
placeholder={placeholder}
|
|
110
138
|
placeholderTextColor={colors["text-muted"]}
|
|
111
139
|
editable={!disabled}
|
|
112
|
-
|
|
140
|
+
keyboardType={keyboardType}
|
|
141
|
+
autoCapitalize={autoCapitalize}
|
|
142
|
+
autoCorrect={autoCorrect}
|
|
143
|
+
maxLength={maxLength}
|
|
144
|
+
returnKeyType={returnKeyType}
|
|
145
|
+
onSubmitEditing={onSubmitEditing}
|
|
146
|
+
accessibilityLabel={showError ? `${label}。エラー: ${errorText}` : label}
|
|
113
147
|
accessibilityState={{ disabled }}
|
|
114
|
-
onFocus={() => {
|
|
148
|
+
onFocus={(e) => {
|
|
115
149
|
if (!disabled) setFocused(true);
|
|
150
|
+
onFocus?.(e);
|
|
151
|
+
}}
|
|
152
|
+
onBlur={(e) => {
|
|
153
|
+
setFocused(false);
|
|
154
|
+
onBlur?.(e);
|
|
116
155
|
}}
|
|
117
|
-
onBlur={() => setFocused(false)}
|
|
118
156
|
style={[resolved.input, INPUT_VERTICAL_FIX, focused && !disabled ? focusStyle : null]}
|
|
119
157
|
/>
|
|
120
158
|
{helperText != null && !showError && (
|
|
121
159
|
<RNText style={resolved.helperText}>{helperText}</RNText>
|
|
122
160
|
)}
|
|
123
|
-
{showError &&
|
|
161
|
+
{showError && (
|
|
162
|
+
<RNText accessibilityLiveRegion="polite" style={resolved.errorText}>
|
|
163
|
+
{errorText}
|
|
164
|
+
</RNText>
|
|
165
|
+
)}
|
|
124
166
|
</View>
|
|
125
167
|
);
|
|
126
168
|
}
|
|
@@ -6,47 +6,109 @@
|
|
|
6
6
|
* useColorScheme() を Provider 内で1回だけ読む(RN コア、追加依存なし)。
|
|
7
7
|
*
|
|
8
8
|
* context value は useMemo 必須(§3): 毎レンダで新オブジェクトを配ると全 consumer が再レンダする。
|
|
9
|
+
*
|
|
10
|
+
* theme 注入(Step 2-①): `theme` を渡すと消費者ブランドの theme で塗り替わる。未指定なら
|
|
11
|
+
* 従来どおり codegen 済みの `nativeTheme`(=既存消費者に破壊的変更なし)。渡せるのは
|
|
12
|
+
* `defineTheme()` の戻り値だけ(define-theme.ts 参照)。
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
15
|
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
12
16
|
import { useColorScheme } from "react-native";
|
|
13
17
|
import { nativeTheme } from "./native-theme";
|
|
14
|
-
import type
|
|
18
|
+
import { defineTheme, isDev, resolveMode, type ResolvedCapabilities, type ResolvedNativeTheme, type ThemeModeViolation } from "./define-theme";
|
|
19
|
+
import type { NativeTheme, SemanticColors, ThemeMode } from "./types";
|
|
15
20
|
|
|
16
|
-
export type ThemeMode
|
|
21
|
+
export type { ThemeMode };
|
|
17
22
|
|
|
18
23
|
export interface ThemeContextValue {
|
|
19
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* 正規化済み theme 全体(色以外の形状トークンもここから取る)。
|
|
26
|
+
*
|
|
27
|
+
* 注: 単一 colorScheme の theme を注入した場合、`color.semantic` は**宣言された mode しか持たない**
|
|
28
|
+
* (型は両方あるように見える)。持たない側を直接読むと原因を名指しするエラーで落ちる。
|
|
29
|
+
* 現在 mode の色は `colors` を使うこと。
|
|
30
|
+
*/
|
|
20
31
|
theme: NativeTheme;
|
|
21
32
|
/** 現在の表示モード。 */
|
|
22
33
|
mode: ThemeMode;
|
|
23
34
|
/** 現在 mode の semantic 色(= theme.color.semantic[mode])。最頻アクセスなので展開済みで配る。 */
|
|
24
35
|
colors: SemanticColors;
|
|
36
|
+
/** theme が持つ能力(`color.semantic` のキー集合などから導出済み)。 */
|
|
37
|
+
capabilities: ResolvedCapabilities;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
28
41
|
|
|
42
|
+
/**
|
|
43
|
+
* 既定 theme。melta-contracts 由来の codegen 結果を注入経路と同じ形に通す(自分でも dogfood する)。
|
|
44
|
+
* 副作用として dev では `nativeTheme` の入れ子オブジェクト(トークン群)も freeze される
|
|
45
|
+
* — 生成物のトークンを実行時に書き換える経路は元々無いので、意図した副作用として許容する。
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_THEME: ResolvedNativeTheme = defineTheme({ ...nativeTheme, id: "melta" });
|
|
48
|
+
|
|
49
|
+
/** 同じ矛盾を毎レンダー報告しないためのラッチ(StrictMode の二重 render も1回に畳む)。 */
|
|
50
|
+
const reportedViolations = new Set<string>();
|
|
51
|
+
|
|
52
|
+
function reportViolation(themeId: string | undefined, violation: ThemeModeViolation): void {
|
|
53
|
+
const key = `${themeId ?? "(id 未設定)"}:${violation.requested}`;
|
|
54
|
+
if (reportedViolations.has(key)) return;
|
|
55
|
+
reportedViolations.add(key);
|
|
56
|
+
console.error(
|
|
57
|
+
`melta: forcedMode="${violation.requested}" が指定されたが、この theme は ` +
|
|
58
|
+
`colorScheme=${violation.colorScheme} なので "${violation.resolved}" で描画する。` +
|
|
59
|
+
`useTheme().capabilities.colorScheme を見て、対応していない mode は UI 側で出さないこと。`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
29
63
|
interface ThemeProviderProps {
|
|
30
64
|
children: ReactNode;
|
|
31
65
|
/**
|
|
32
66
|
* 明示モード。指定時は OS の colorScheme を無視して固定する。
|
|
33
67
|
* カタログの light/dark トグル(§6)や、特定画面の固定表示に使う。
|
|
68
|
+
*
|
|
69
|
+
* theme が対応していない mode を渡した場合は theme 側の mode に clamp する(描画は止めない)。
|
|
70
|
+
* dev ではその矛盾を console.error で1回だけ報告する。
|
|
34
71
|
*/
|
|
35
72
|
forcedMode?: ThemeMode;
|
|
73
|
+
/**
|
|
74
|
+
* 消費者ブランドの theme。未指定なら melta 既定(`nativeTheme`)。
|
|
75
|
+
* `defineTheme()` の戻り値を **module スコープで保持したもの**を渡すこと
|
|
76
|
+
* (render 中に組み立てると毎レンダー参照が変わり、全 consumer が再レンダする)。
|
|
77
|
+
*/
|
|
78
|
+
theme?: ResolvedNativeTheme;
|
|
36
79
|
}
|
|
37
80
|
|
|
38
|
-
export function ThemeProvider({ children, forcedMode }: ThemeProviderProps) {
|
|
81
|
+
export function ThemeProvider({ children, forcedMode, theme }: ThemeProviderProps) {
|
|
39
82
|
const system = useColorScheme();
|
|
40
|
-
const
|
|
83
|
+
const activeTheme = theme ?? DEFAULT_THEME;
|
|
41
84
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
colors: nativeTheme.color.semantic[mode],
|
|
47
|
-
}),
|
|
48
|
-
[mode],
|
|
85
|
+
const { mode, violation } = resolveMode(
|
|
86
|
+
activeTheme.capabilities.colorScheme,
|
|
87
|
+
forcedMode,
|
|
88
|
+
system === "dark" ? "dark" : "light",
|
|
49
89
|
);
|
|
90
|
+
if (isDev && violation) reportViolation(activeTheme.id, violation);
|
|
91
|
+
|
|
92
|
+
const value = useMemo<ThemeContextValue>(() => {
|
|
93
|
+
const colors = activeTheme.color.semantic[mode];
|
|
94
|
+
if (!colors) {
|
|
95
|
+
// resolveMode が capability に従って選んだ mode なので、ここに来るのは theme が壊れている場合だけ。
|
|
96
|
+
// 色が undefined のまま下流に流れると全 component が別々の場所で死ぬので、ここで落とす。
|
|
97
|
+
throw new Error(
|
|
98
|
+
`melta: theme "${activeTheme.id ?? "(id 未設定)"}" に mode="${mode}" の semantic 色が無い。`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
// 配布時の型は NativeTheme のまま(既存の全 style resolver の署名を維持するため)。
|
|
103
|
+
// 単一 colorScheme の theme では持たない mode のキーが実体に無く、型が実体より広い。
|
|
104
|
+
// 到達しうる読みは resolveMode が保証した現在 mode だけで、他 mode を直接読んだ場合は
|
|
105
|
+
// define-theme.ts の guard getter が原因を名指しして落とす。
|
|
106
|
+
theme: activeTheme as unknown as NativeTheme,
|
|
107
|
+
mode,
|
|
108
|
+
colors,
|
|
109
|
+
capabilities: activeTheme.capabilities,
|
|
110
|
+
};
|
|
111
|
+
}, [activeTheme, mode]);
|
|
50
112
|
|
|
51
113
|
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
52
114
|
}
|