@multiplatform.one/cli 6.1.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +72 -0
  2. package/bin/createMultiplatformApp.mjs +77 -0
  3. package/lib/bin/multiplatformOne.mjs +23 -9
  4. package/lib/commands/e2e.mjs +3 -3
  5. package/lib/commands/init.mjs +16 -12
  6. package/lib/commands/initApp.mjs +145 -0
  7. package/lib/generateVscode.mjs +27 -10
  8. package/package.json +23 -12
  9. package/src/bin/multiplatformOne.ts +54 -16
  10. package/src/commands/init.ts +7 -18
  11. package/src/commands/initApp.spec.ts +210 -0
  12. package/src/commands/initApp.ts +228 -0
  13. package/src/generateVscode.ts +31 -9
  14. package/templates/app/AGENTS.md +142 -0
  15. package/templates/app/README.md +62 -0
  16. package/templates/app/apps/__NAME__/index.html +36 -0
  17. package/templates/app/apps/__NAME__/package.json +30 -0
  18. package/templates/app/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  19. package/templates/app/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  20. package/templates/app/apps/__NAME__/src/App.tsx +9 -0
  21. package/templates/app/apps/__NAME__/src/main.tsx +14 -0
  22. package/templates/app/apps/__NAME__/tsconfig.json +8 -0
  23. package/templates/app/apps/__NAME__/vite.config.ts +17 -0
  24. package/templates/app/features/__NAME__/package.json +34 -0
  25. package/templates/app/features/__NAME__/src/HomeScreen.tsx +70 -0
  26. package/templates/app/features/__NAME__/src/index.ts +1 -0
  27. package/templates/app/features/__NAME__/tsconfig.json +5 -0
  28. package/templates/app/gitignore +8 -0
  29. package/templates/app/npmrc +2 -0
  30. package/templates/app/oxlintrc.json +39 -0
  31. package/templates/app/package.json +20 -0
  32. package/templates/app/packages/config/config.json +4 -0
  33. package/templates/app/packages/config/index.ts +5 -0
  34. package/templates/app/packages/config/package.json +17 -0
  35. package/templates/app/packages/config/tsconfig.json +5 -0
  36. package/templates/app/pnpm-workspace.yaml +4 -0
  37. package/templates/app/tsconfig.base.json +16 -0
  38. package/templates/universal/AGENTS.md +142 -0
  39. package/templates/universal/README.md +97 -0
  40. package/templates/universal/apps/__NAME__/app.config.js +31 -0
  41. package/templates/universal/apps/__NAME__/config/tamagui.config.ts +4 -0
  42. package/templates/universal/apps/__NAME__/package.json +61 -0
  43. package/templates/universal/apps/__NAME__/polyfills.native.ts +23 -0
  44. package/templates/universal/apps/__NAME__/polyfills.ts +3 -0
  45. package/templates/universal/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  46. package/templates/universal/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  47. package/templates/universal/apps/__NAME__/routes/_layout.tsx +22 -0
  48. package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +21 -0
  49. package/templates/universal/apps/__NAME__/routes/about.tsx +3 -0
  50. package/templates/universal/apps/__NAME__/routes/index.tsx +3 -0
  51. package/templates/universal/apps/__NAME__/src/app.ts +10 -0
  52. package/templates/universal/apps/__NAME__/tamagui.css +3 -0
  53. package/templates/universal/apps/__NAME__/tsconfig.json +8 -0
  54. package/templates/universal/apps/__NAME__/types.d.ts +9 -0
  55. package/templates/universal/apps/__NAME__/vite.config.ts +66 -0
  56. package/templates/universal/features/__NAME__/package.json +35 -0
  57. package/templates/universal/features/__NAME__/src/AboutScreen.tsx +27 -0
  58. package/templates/universal/features/__NAME__/src/AppLayout.tsx +50 -0
  59. package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +22 -0
  60. package/templates/universal/features/__NAME__/src/ContactForm.tsx +55 -0
  61. package/templates/universal/features/__NAME__/src/HomeScreen.tsx +40 -0
  62. package/templates/universal/features/__NAME__/src/index.ts +4 -0
  63. package/templates/universal/features/__NAME__/src/screens.spec.ts +12 -0
  64. package/templates/universal/features/__NAME__/tsconfig.json +5 -0
  65. package/templates/universal/features/__NAME__/vitest.config.mjs +7 -0
  66. package/templates/universal/gitignore +12 -0
  67. package/templates/universal/npmrc +3 -0
  68. package/templates/universal/oxlintrc.json +39 -0
  69. package/templates/universal/package.json +27 -0
  70. package/templates/universal/packages/config/config.json +4 -0
  71. package/templates/universal/packages/config/index.ts +5 -0
  72. package/templates/universal/packages/config/package.json +17 -0
  73. package/templates/universal/packages/config/tsconfig.json +5 -0
  74. package/templates/universal/packages/i18n/en/common.json +30 -0
  75. package/templates/universal/packages/i18n/es/common.json +30 -0
  76. package/templates/universal/packages/i18n/i18n.d.ts +16 -0
  77. package/templates/universal/packages/i18n/index.ts +26 -0
  78. package/templates/universal/packages/i18n/package.json +20 -0
  79. package/templates/universal/packages/i18n/resources.ts +13 -0
  80. package/templates/universal/packages/i18n/tsconfig.json +5 -0
  81. package/templates/universal/packages/themes/accent.ts +32 -0
  82. package/templates/universal/packages/themes/base.ts +50 -0
  83. package/templates/universal/packages/themes/fonts.ts +8 -0
  84. package/templates/universal/packages/themes/index.ts +33 -0
  85. package/templates/universal/packages/themes/package.json +23 -0
  86. package/templates/universal/packages/themes/tamagui.config.ts +14 -0
  87. package/templates/universal/packages/themes/tsconfig.json +5 -0
  88. package/templates/universal/pnpm-workspace.yaml +4 -0
  89. package/templates/universal/tsconfig.base.json +21 -0
  90. package/templates/universal/types/env.d.ts +9 -0
  91. package/types/bin/multiplatformOne.d.ts +11 -0
  92. package/types/bin/multiplatformOne.d.ts.map +1 -0
  93. package/types/commands/e2e.d.ts +14 -0
  94. package/types/commands/e2e.d.ts.map +1 -0
  95. package/types/commands/init.d.ts +16 -0
  96. package/types/commands/init.d.ts.map +1 -0
  97. package/types/commands/initApp.d.ts +25 -0
  98. package/types/commands/initApp.d.ts.map +1 -0
  99. package/types/generateVscode.d.ts +7 -0
  100. package/types/generateVscode.d.ts.map +1 -0
  101. package/types/index.d.ts +2 -0
  102. package/types/index.d.ts.map +1 -0
  103. package/types/types.d.ts +6 -0
  104. package/types/types.d.ts.map +1 -0
@@ -0,0 +1,22 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { fireEvent } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ContactForm } from "./ContactForm";
5
+
6
+ describe("ContactForm", () => {
7
+ it("renders the starter form fields", () => {
8
+ const result = renderWithProviders(<ContactForm />);
9
+ expect(result.findTextElement("Contact")).toBeDefined();
10
+ expect(result.findTextElement("Name")).toBeDefined();
11
+ expect(result.findTextElement("Message")).toBeDefined();
12
+ expect(result.getButton("Send")).toBeDefined();
13
+ });
14
+
15
+ it("shows required errors when submitting empty", () => {
16
+ const result = renderWithProviders(<ContactForm />);
17
+ const send = result.getButton("Send");
18
+ expect(send).toBeDefined();
19
+ fireEvent.click(send as Element);
20
+ expect(result.findTextElement("This field is required")).toBeDefined();
21
+ });
22
+ });
@@ -0,0 +1,55 @@
1
+ import { H2, Paragraph, YStack } from "@multiplatform.one/components";
2
+ import { Button, Input, TextArea } from "@multiplatform.one/forms";
3
+ import { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ /**
7
+ * Starter form demonstrating @multiplatform.one/forms fields (Input, TextArea,
8
+ * Button) with the canonical onChangeText/value contract, helper/error slots,
9
+ * and a submit handler. Replace the console.info with a real mutation.
10
+ */
11
+ export function ContactForm() {
12
+ const { t } = useTranslation();
13
+ const [name, setName] = useState("");
14
+ const [message, setMessage] = useState("");
15
+ const [submitted, setSubmitted] = useState(false);
16
+ const [sent, setSent] = useState(false);
17
+
18
+ const requiredError = t("screens.contact.required", "This field is required");
19
+
20
+ function handleSend() {
21
+ setSubmitted(true);
22
+ if (!name.trim() || !message.trim()) return;
23
+ // eslint-disable-next-line no-console
24
+ console.info("[__NAME__] contact form:", { name, message });
25
+ setSent(true);
26
+ }
27
+
28
+ return (
29
+ <YStack gap="$4" maxWidth={480}>
30
+ <H2>{t("screens.contact.title", "Contact")}</H2>
31
+ <Input
32
+ label={t("screens.contact.name", "Name")}
33
+ placeholder={t("screens.contact.namePlaceholder", "Your name")}
34
+ value={name}
35
+ onChangeText={setName}
36
+ required
37
+ error={submitted && !name.trim() ? requiredError : undefined}
38
+ />
39
+ <TextArea
40
+ label={t("screens.contact.message", "Message")}
41
+ placeholder={t("screens.contact.messagePlaceholder", "What do you want to say?")}
42
+ value={message}
43
+ onChangeText={setMessage}
44
+ required
45
+ error={submitted && !message.trim() ? requiredError : undefined}
46
+ />
47
+ <Button onPress={handleSend}>{t("screens.contact.send", "Send")}</Button>
48
+ {sent ? (
49
+ <Paragraph color="$color11">
50
+ {t("screens.contact.sent", "Message sent (demo) — check the console.")}
51
+ </Paragraph>
52
+ ) : null}
53
+ </YStack>
54
+ );
55
+ }
@@ -0,0 +1,40 @@
1
+ import { H1, Paragraph, Screen, Separator, YStack } from "@multiplatform.one/components";
2
+ import { useResolvedKnobs } from "@multiplatform.one/theme";
3
+ import { Link } from "one";
4
+ import { useTranslation } from "react-i18next";
5
+ import { ContactForm } from "./ContactForm";
6
+
7
+ /**
8
+ * Home screen (route: /). Keep product UI in features/<name>; the app shell
9
+ * (apps/<name>) only wires routes and providers.
10
+ */
11
+ export function HomeScreen() {
12
+ const { t } = useTranslation();
13
+ // Knob wiring: spread complete knobProps fragments — never cherry-pick
14
+ // resolved tokens (see AGENTS.md, knobs system).
15
+ const { knobProps } = useResolvedKnobs({ component: "Card" });
16
+
17
+ return (
18
+ <Screen>
19
+ <YStack
20
+ {...knobProps.surface}
21
+ {...knobProps.borderRadius}
22
+ {...knobProps.panelPadding}
23
+ {...knobProps.gap}
24
+ backgroundColor="$color2"
25
+ borderColor="$borderColor"
26
+ >
27
+ <H1 {...knobProps.heading}>{t("screens.home.welcome", "Welcome to __NAME_PASCAL__")}</H1>
28
+ <Paragraph {...knobProps.body} color="$color11">
29
+ {t(
30
+ "screens.home.message",
31
+ "One codebase — web, iOS, and Android. Edit features/__NAME__/src/HomeScreen.tsx to get started.",
32
+ )}
33
+ </Paragraph>
34
+ <Link href="/about">{t("nav.about", "About")}</Link>
35
+ </YStack>
36
+ <Separator />
37
+ <ContactForm />
38
+ </Screen>
39
+ );
40
+ }
@@ -0,0 +1,4 @@
1
+ export { AboutScreen } from "./AboutScreen";
2
+ export { AppLayout } from "./AppLayout";
3
+ export { ContactForm } from "./ContactForm";
4
+ export { HomeScreen } from "./HomeScreen";
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AboutScreen, AppLayout, ContactForm, HomeScreen } from "./index";
3
+
4
+ // Source-shape smoke: every starter screen is exported and renderable
5
+ // (component functions). Full render coverage lives next to each component.
6
+ describe("feature exports", () => {
7
+ it("exports the starter screens", () => {
8
+ for (const Component of [AboutScreen, AppLayout, ContactForm, HomeScreen]) {
9
+ expect(typeof Component).toBe("function");
10
+ }
11
+ });
12
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./src/**/*", "../../types/env.d.ts"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,7 @@
1
+ import { createVitestConfig } from "@multiplatform.one/config/vitest";
2
+
3
+ export default createVitestConfig({
4
+ tamaguiConfig: "../../packages/themes/tamagui.config.ts",
5
+ setupFiles: ["@multiplatform.one/test-utils/setup"],
6
+ include: ["src/**/*.spec.{ts,tsx}"],
7
+ });
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ .vite
5
+ .tamagui
6
+ .expo
7
+ *.log
8
+ .env
9
+ .env.local
10
+ .DS_Store
11
+ apps/__NAME__/ios
12
+ apps/__NAME__/android
@@ -0,0 +1,3 @@
1
+ auto-install-peers=true
2
+ strict-peer-dependencies=false
3
+ node-linker=hoisted
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "rules": {
4
+ "no-unused-vars": [
5
+ "warn",
6
+ {
7
+ "argsIgnorePattern": "^_",
8
+ "varsIgnorePattern": "^_"
9
+ }
10
+ ],
11
+ "no-console": "warn",
12
+ "eqeqeq": "error",
13
+ "no-var": "error",
14
+ "prefer-const": "error"
15
+ },
16
+ "overrides": [
17
+ {
18
+ "files": [
19
+ "apps/**/*.{ts,tsx,js,jsx}",
20
+ "features/**/*.{ts,tsx,js,jsx}",
21
+ "packages/**/*.{ts,tsx,js,jsx}"
22
+ ],
23
+ "rules": {
24
+ "no-restricted-imports": [
25
+ "error",
26
+ {
27
+ "paths": [
28
+ {
29
+ "name": "tamagui",
30
+ "message": "Raw tamagui is only allowed inside framework packages. Import from @multiplatform.one/components, @multiplatform.one/forms, or @multiplatform.one/theme. See AGENTS.md."
31
+ }
32
+ ]
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ ],
38
+ "ignorePatterns": ["**/node_modules/**", "**/dist/**", "**/build/**"]
39
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "__NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "__NAME__ — multiplatform.one universal app (web + iOS + Android)",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "dev": "pnpm --filter @app/__NAME__ dev",
9
+ "build": "pnpm --filter @app/__NAME__ build",
10
+ "serve": "pnpm --filter @app/__NAME__ serve",
11
+ "native": "pnpm --filter @app/__NAME__ prebuild:native",
12
+ "ios": "pnpm --filter @app/__NAME__ ios",
13
+ "android": "pnpm --filter @app/__NAME__ android",
14
+ "test": "pnpm -r test",
15
+ "lint": "oxlint -c oxlintrc.json .",
16
+ "typecheck": "pnpm -r typecheck"
17
+ },
18
+ "devDependencies": {
19
+ "@types/chrome": "^0.2.5",
20
+ "@types/firefox-webext-browser": "^143.0.0",
21
+ "oxlint": "^1.62.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "packageManager": "pnpm@10.32.1"
27
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "private": ["SECRET"],
3
+ "public": ["BASE_URL", "DEBUG", "I18N_DEFAULT_LANGUAGE"]
4
+ }
@@ -0,0 +1,5 @@
1
+ import configJson from "./config.json";
2
+
3
+ export const config = configJson;
4
+ export type AppConfig = typeof configJson;
5
+ export default config;
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@package/config",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./config.json": "./config.json"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "~5.9.3"
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./**/*"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "app": {
3
+ "name": "__NAME_PASCAL__"
4
+ },
5
+ "nav": {
6
+ "home": "Home",
7
+ "about": "About"
8
+ },
9
+ "screens": {
10
+ "home": {
11
+ "welcome": "Welcome to __NAME_PASCAL__",
12
+ "message": "One codebase — web, iOS, and Android. Edit features/__NAME__/src/HomeScreen.tsx to get started."
13
+ },
14
+ "about": {
15
+ "title": "About",
16
+ "message": "This screen lives in features/__NAME__ and is wired to the /about route by apps/__NAME__/routes/about.tsx.",
17
+ "back": "Back home"
18
+ },
19
+ "contact": {
20
+ "title": "Contact",
21
+ "name": "Name",
22
+ "namePlaceholder": "Your name",
23
+ "message": "Message",
24
+ "messagePlaceholder": "What do you want to say?",
25
+ "send": "Send",
26
+ "required": "This field is required",
27
+ "sent": "Message sent (demo) — check the console."
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "app": {
3
+ "name": "__NAME_PASCAL__"
4
+ },
5
+ "nav": {
6
+ "home": "Inicio",
7
+ "about": "Acerca de"
8
+ },
9
+ "screens": {
10
+ "home": {
11
+ "welcome": "Bienvenido a __NAME_PASCAL__",
12
+ "message": "Un solo código — web, iOS y Android. Edita features/__NAME__/src/HomeScreen.tsx para empezar."
13
+ },
14
+ "about": {
15
+ "title": "Acerca de",
16
+ "message": "Esta pantalla vive en features/__NAME__ y se conecta a la ruta /about desde apps/__NAME__/routes/about.tsx.",
17
+ "back": "Volver al inicio"
18
+ },
19
+ "contact": {
20
+ "title": "Contacto",
21
+ "name": "Nombre",
22
+ "namePlaceholder": "Tu nombre",
23
+ "message": "Mensaje",
24
+ "messagePlaceholder": "¿Qué quieres decir?",
25
+ "send": "Enviar",
26
+ "required": "Este campo es obligatorio",
27
+ "sent": "Mensaje enviado (demo) — mira la consola."
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * i18next TypeScript type augmentations.
3
+ *
4
+ * Key typing is intentionally loose (t() accepts any string; missing keys fall
5
+ * back at runtime). To restore strict typed keys, add
6
+ * `resources: { common: typeof import("./en/common.json") }` to
7
+ * CustomTypeOptions.
8
+ */
9
+ import "i18next";
10
+
11
+ declare module "i18next" {
12
+ interface CustomTypeOptions {
13
+ defaultNS: "common";
14
+ returnNull: false;
15
+ }
16
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Supported languages. Add a language by creating `<lng>/common.json` and
3
+ * listing the code here (and in resources.ts for native).
4
+ */
5
+ export const languages = ["en", "es"] as const;
6
+
7
+ /** Translation namespaces — one JSON file per namespace per language. */
8
+ export const namespaces = ["common"] as const;
9
+
10
+ /** Fallback language when a translation is missing. */
11
+ export const defaultLanguage = "en" as const;
12
+
13
+ /** Namespace used when t() calls do not specify one. */
14
+ export const defaultNamespace = "common" as const;
15
+
16
+ export type Language = (typeof languages)[number];
17
+ export type Namespace = (typeof namespaces)[number];
18
+
19
+ // Re-export framework i18n utilities
20
+ export {
21
+ createI18nConfig,
22
+ detectLanguage,
23
+ getPersistedLanguage,
24
+ persistLanguage,
25
+ } from "@multiplatform.one/i18n";
26
+ export type { I18nConfigOptions } from "@multiplatform.one/i18n";
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@package/i18n",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./resources": "./resources.ts"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@multiplatform.one/i18n": "__MPO_VERSION__"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "~5.9.3"
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ // Static resource map for native (web loads translations through the vite
2
+ // i18next loader instead — see apps/__NAME__/routes/_layout.web.tsx).
3
+ import en from "./en/common.json";
4
+ import es from "./es/common.json";
5
+
6
+ export const resources = {
7
+ en: {
8
+ common: en,
9
+ },
10
+ es: {
11
+ common: es,
12
+ },
13
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./*.ts", "./**/*.json", "../../types/env.d.ts"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,32 @@
1
+ // Framework default brand accent: solid-surface hsla(250) ramps. Replace with
2
+ // your brand color ramps to rebrand the whole app in one place.
3
+ export const accentTheme = {
4
+ darkPalette: [
5
+ "hsla(250, 50%, 35%, 1)",
6
+ "hsla(250, 50%, 38%, 1)",
7
+ "hsla(250, 50%, 41%, 1)",
8
+ "hsla(250, 50%, 43%, 1)",
9
+ "hsla(250, 50%, 46%, 1)",
10
+ "hsla(250, 50%, 49%, 1)",
11
+ "hsla(250, 50%, 52%, 1)",
12
+ "hsla(250, 50%, 54%, 1)",
13
+ "hsla(250, 50%, 57%, 1)",
14
+ "hsla(250, 50%, 60%, 1)",
15
+ "hsla(250, 50%, 90%, 1)",
16
+ "hsla(250, 50%, 95%, 1)",
17
+ ],
18
+ lightPalette: [
19
+ "hsla(250, 50%, 95%, 1)",
20
+ "hsla(250, 50%, 95%, 1)",
21
+ "hsla(250, 50%, 65%, 1)",
22
+ "hsla(250, 50%, 62%, 1)",
23
+ "hsla(250, 50%, 59%, 1)",
24
+ "hsla(250, 50%, 57%, 1)",
25
+ "hsla(250, 50%, 54%, 1)",
26
+ "hsla(250, 50%, 51%, 1)",
27
+ "hsla(250, 50%, 48%, 1)",
28
+ "hsla(250, 50%, 46%, 1)",
29
+ "hsla(250, 50%, 43%, 1)",
30
+ "hsla(250, 50%, 40%, 1)",
31
+ ],
32
+ };
@@ -0,0 +1,50 @@
1
+ // Framework default neutral ramps: Radix-mauve-shaped with a slight purple
2
+ // tint (hue 270) harmonizing with the hsla(250) accent. Steps: near-white
3
+ // surfaces (1-2), component/hover (3-5), borders (6-8), solid/muted (9-10),
4
+ // readable text (11-12). Swap these palettes to rebrand.
5
+ export const baseTheme = {
6
+ darkPalette: [
7
+ "hsla(270, 6%, 8.7%, 1)",
8
+ "hsla(270, 6%, 10.5%, 1)",
9
+ "hsla(270, 6%, 13.5%, 1)",
10
+ "hsla(270, 6%, 16%, 1)",
11
+ "hsla(270, 6%, 18.5%, 1)",
12
+ "hsla(270, 6%, 21.5%, 1)",
13
+ "hsla(270, 6%, 25.5%, 1)",
14
+ "hsla(270, 6%, 32%, 1)",
15
+ "hsla(270, 6%, 43%, 1)",
16
+ "hsla(270, 6%, 48%, 1)",
17
+ "hsla(270, 6%, 62.5%, 1)",
18
+ "hsla(270, 6%, 94%, 1)",
19
+ ],
20
+ lightPalette: [
21
+ "hsla(270, 6%, 99.5%, 1)",
22
+ "hsla(270, 6%, 98.2%, 1)",
23
+ "hsla(270, 6%, 95.2%, 1)",
24
+ "hsla(270, 6%, 92.5%, 1)",
25
+ "hsla(270, 6%, 90%, 1)",
26
+ "hsla(270, 6%, 87%, 1)",
27
+ "hsla(270, 6%, 82.5%, 1)",
28
+ "hsla(270, 6%, 75%, 1)",
29
+ "hsla(270, 6%, 56.5%, 1)",
30
+ "hsla(270, 6%, 52%, 1)",
31
+ "hsla(270, 6%, 40%, 1)",
32
+ "hsla(270, 6%, 14%, 1)",
33
+ ],
34
+ lightShadows: {
35
+ shadow1: "rgba(0,0,0,0.04)",
36
+ shadow2: "rgba(0,0,0,0.08)",
37
+ shadow3: "rgba(0,0,0,0.16)",
38
+ shadow4: "rgba(0,0,0,0.24)",
39
+ shadow5: "rgba(0,0,0,0.32)",
40
+ shadow6: "rgba(0,0,0,0.4)",
41
+ },
42
+ darkShadows: {
43
+ shadow1: "rgba(0,0,0,0.2)",
44
+ shadow2: "rgba(0,0,0,0.3)",
45
+ shadow3: "rgba(0,0,0,0.4)",
46
+ shadow4: "rgba(0,0,0,0.5)",
47
+ shadow5: "rgba(0,0,0,0.6)",
48
+ shadow6: "rgba(0,0,0,0.7)",
49
+ },
50
+ };
@@ -0,0 +1,8 @@
1
+ import { createFontLoader } from "@multiplatform.one/theme";
2
+
3
+ // Loads the default Inter faces on native (web gets them via CSS). Add custom
4
+ // fonts by listing { family, nativeModule } entries.
5
+ export const { importFonts } = createFontLoader({
6
+ fonts: [],
7
+ inter: true,
8
+ });
@@ -0,0 +1,33 @@
1
+ import { createDefaultThemeConfig } from "@multiplatform.one/theme";
2
+ import { accentTheme } from "./accent";
3
+ import { baseTheme } from "./base";
4
+
5
+ // House defaults: neutral base ramps + brand accent ramps with a normalized
6
+ // interaction ramp. Fonts fall back to the framework's Inter defaults —
7
+ // pass `fonts` here to customize.
8
+ export const themeConfig = createDefaultThemeConfig({
9
+ base: baseTheme,
10
+ accent: accentTheme,
11
+ builderOptions: {
12
+ getTheme: ({ theme }) => ({
13
+ // Interaction ramp: background 5 → 6 → 4, border 5 → 6 → 7.
14
+ backgroundPress: theme.color5,
15
+ backgroundFocus: theme.color6,
16
+ backgroundActive: theme.color4,
17
+ placeholderColor: theme.color10,
18
+ borderColor: theme.color5,
19
+ borderColorHover: theme.color6,
20
+ borderColorFocus: theme.color7,
21
+ borderColorPress: theme.color7,
22
+ // Semantic aliases for palette slots not covered by Tamagui defaults.
23
+ textMuted: theme.color10,
24
+ textSubtle: theme.color6,
25
+ borderStrong: theme.color7,
26
+ borderStrongHover: theme.color8,
27
+ borderStrongPress: theme.color6,
28
+ surfaceStrong: theme.color7,
29
+ surfaceMuted: theme.color10,
30
+ surfacePressStrong: theme.color6,
31
+ }),
32
+ },
33
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@package/themes",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./fonts": "./fonts.ts",
10
+ "./tamagui.config": "./tamagui.config.ts"
11
+ },
12
+ "scripts": {
13
+ "typecheck": "tsc --noEmit"
14
+ },
15
+ "dependencies": {
16
+ "@multiplatform.one/theme": "__MPO_VERSION__",
17
+ "@tamagui/font-inter": "2.0.0-rc.41",
18
+ "tamagui": "2.0.0-rc.41"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "~5.9.3"
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ import { themeConfig } from "./index";
2
+
3
+ export const config = themeConfig.tamagui;
4
+ export type AppConfig = typeof config;
5
+
6
+ // Module augmentation must target @tamagui/web where TamaguiCustomConfig is
7
+ // defined; keep the @tamagui/core augmentation for consumers importing types
8
+ // from there.
9
+ declare module "@tamagui/web" {
10
+ interface TamaguiCustomConfig extends AppConfig {}
11
+ }
12
+ declare module "@tamagui/core" {
13
+ interface TamaguiCustomConfig extends AppConfig {}
14
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./*.ts", "../../types/env.d.ts"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,4 @@
1
+ packages:
2
+ - apps/*
3
+ - features/*
4
+ - packages/*
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "jsx": "react-jsx",
9
+ "strict": true,
10
+ // Published @multiplatform.one/* packages currently resolve types to
11
+ // their TypeScript sources (not compiled .d.ts), so typecheck re-checks
12
+ // framework code. Implicit-any strictness is relaxed until the packages
13
+ // publish compiled declarations; the rest of strict mode stays on.
14
+ "noImplicitAny": false,
15
+ "skipLibCheck": true,
16
+ "esModuleInterop": true,
17
+ "resolveJsonModule": true,
18
+ "isolatedModules": true,
19
+ "noEmit": true
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ // Published @multiplatform.one/* packages ship TypeScript source, so package
2
+ // typechecks follow them into code that reads import.meta.env (vite-injected).
3
+ // Packages that don't load vite/client types include this minimal shape
4
+ // instead (apps/* use vite/client directly). Values are `any` to match
5
+ // vite/client's ImportMetaEnv index signature.
6
+ interface ImportMeta {
7
+ // biome-ignore lint/suspicious/noExplicitAny: mirrors vite/client typing
8
+ readonly env?: Record<string, any>;
9
+ }
@@ -0,0 +1,11 @@
1
+ /** Workspaces that have Storybook and can run visual regression (discovered from filesystem + package.json). */
2
+ export type StorybookVisualWorkspace = {
3
+ dirName: string;
4
+ absolutePath: string;
5
+ /** How to run visual test: run `make test/visual` in absolutePath, or run pnpm script by name. */
6
+ runVisual: "make" | "pnpm";
7
+ pnpmScript?: string;
8
+ /** If true, run `pnpm run build` then `pnpm exec lost-pixel` in workspace (no named script). */
9
+ runLostPixelAfterBuild?: boolean;
10
+ };
11
+ //# sourceMappingURL=multiplatformOne.d.ts.map