Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[suggestions] .npmrc and .lint-staged #2

Closed
FranciscoKloganB opened this issue May 6, 2024 · 9 comments
Closed

[suggestions] .npmrc and .lint-staged #2

FranciscoKloganB opened this issue May 6, 2024 · 9 comments

Comments

@FranciscoKloganB
Copy link

FranciscoKloganB commented May 6, 2024

I'm pretty new to nx and yarn berry, but here are two suggestions:

Suggestion #1:

  • I read somewhere in the official Yarn documentation that .npmrc is ignored, so perhaps you don't need to keep it in the template?

Suggestion #2:

  • With biome you do not need lint-staged. The good thing about keeping lint-staged is possibly run eslint routines for languages/meta-frameworks not yet supported by Biome, but eslint provides a very good --cache option which pretty much does what link-staged does.
  • With biome you can also add the flag --changed (which equates to nx affected tbh) or --staged to the commands which does exactly what lint-staged does.
  • TLDR: Since this repository is React/React Native based you can get away with just:
    • eslint --cache and biome --changed
@guillempuche
Copy link
Owner

Great the suggestions!

  • With biome you do not need lint-staged. The good thing about keeping lint-staged is possibly run eslint routines for languages/meta-frameworks not yet supported by Biome, but eslint provides a very good --cache option which pretty much does what link-staged does.

Sometimes files change (new imports...) without Biome linting. lint-staged is called when git commit -m "message" via Husky (.husky). Why is it not needed?

@guillempuche
Copy link
Owner

guillempuche commented May 7, 2024

Suggestions 1 and 2 (cached) pushed.

And also fixed the initialization of Vite with Tamagui thanks to @itsmelion (tamagui/tamagui#2569)

@FranciscoKloganB
Copy link
Author

FranciscoKloganB commented May 7, 2024

Great work. 💪

It is that currently you do: yarn pre-commit -> callslint-staged -> calls biome. However, it is the same as doing yarn pre-commit -> call biome. The lint-staged package tipically passes changed files as inputs to linter/formatter to avoid running them on the entire project. However, since biome already has a flag that selects changed files when compared to the main branch, you can reduce one dependency. I see no problem in keeping it either.

ps: I am doing these suggestions, because I'm using your template as reference for a private monorepo I am setting up and because I see the template as a promising alternative to Takeout which is Turbo based. Truth be told, yours does pretty much what I need, but I need to go through the learning curve in order to understand the ecosystem. #i-am-just-a-fullstack-web-dev. :)

@guillempuche
Copy link
Owner

guillempuche commented May 8, 2024

Good explanation!

Sometimes Android and iOS set up can be tricky. Check the README guide.

I'm very happy this repo helps you. The JS community is very into Turbo (a free product from Vercel) 😔, but Nx is the only product of Nx company. They're constantly improving the open source packages.

I suggest you to try the Nx IDE extension, it's really helpful to generate new packages (it takes several tries to get used to it). It also automatically uses the --dry command to simulate the task before you execute it.

@guillempuche
Copy link
Owner

Expo team suggests using build instead of Expo Go. For this, we need EAS build scripts https://github.com/guillempuche/nx-expo-next-tamagui/blob/main/tools/scripts/ (the ones Nx starter template provides), but with Yarn it doesn't work (I still need to test it with npm).

I opened an issue nrwl/nx#22631. You can make some noise to increase the priority.

@guillempuche
Copy link
Owner

I have another project that uses Clean Architecture. I use Nx extension to generate the structure. My root/tsconfig.base.json looks like:

		"paths": {
			"@xiroi/library/domain": [
				"packages/xiroi-bc/library/domain/src/index.ts"
			],
			"@xiroi/library/infrastructure/database": [
				"packages/xiroi-bc/library/infrastructure/database/src/index.ts"
			],
			"@xiroi/library/infrastructure/graphql": [
				"packages/xiroi-bc/library/infrastructure/graphql/src/index.ts"
			],
			"@xiroi/library/ui/components": [
				"packages/xiroi-bc/library/ui/components/src/index.ts"
			],
			"@xiroi/library/usecases": [
				"packages/xiroi-bc/library/usecases/src/index.ts"
			],
			"@xiroi/shared/domain": [
				"packages/xiroi-shared/domain/src/index.ts"
			],
			"@xiroi/shared/ui/components": [
				"packages/xiroi-shared/ui/components/src/index.ts"
			],
			"@xiroi/shared/ui/core/*": [
				"packages/xiroi-shared/ui/core/src/*"
			],
			"@xiroi/shared/ui/icons": [
				"packages/xiroi-shared/ui/icons/src/index.ts"
			],
			"@xiroi/shared/ui/localization": [
				"packages/xiroi-shared/ui/localization/src/index.ts"
			],
			"@xiroi/shared/usecases": [
				"packages/xiroi-shared/usecases/src/index.ts"
			],
			"@xiroi/ui-pages/library": [
				"packages/xiroi-ui-pages/library/src/index.ts"
			],
			"@xiroi/ui-pages/login": [
				"packages/xiroi-ui-pages/login/src/index.ts"
			],
			"@xiroi/ui-pages/settings": [
				"packages/xiroi-ui-pages/settings/src/index.ts"
			]
		}
...

Then each project.json has a similar name as shown in the extension:

imatge

An other very cool Nx feature is the relationship graph of all the packages and apps. You can check the dependencies of the Clean Architecture.

imatge

@FranciscoKloganB
Copy link
Author

Hey @guillempuche. Thanks for all the tips!

Regarding Tamagui

We will certainly apply some of them. However, I came to the decision of not using Tamagui. At some point, I decided to create a small side project just to try the framework more in-depth before going all in on the main project (within the monorepo) and I discovered that I did not like the development experience, as much as I would have expected.

We knew Tamagui's learning curve would be intense, pretty much like PandaCSS for web was since it involves design-system concepts and basically provides an opinionated way of achieving a good design-system implementation. However, not only was it intense, but I also ended up not enjoying some architectural decisions and mostly public APIs provided by the lib. Most importantly I felt like the documentation needed lots of "digging" around to find what I wanted and the truth is the application we will be developing won't need all the power Tamagui was going to offer.

We ended up opting for something more barebones, closer to the React Native original API, and most importantly less opinionated. That choice was: react-native-unistyles. We will still be able to implement design-system tokens, but we won't be constrained by the learning curve that Tamagui demands without providing (in our opinion) the necessary tools to overcome that curve.

Regarding expo

We tried using PNPM initially, but failed miserably, because eas ended up trying to use Yarn during builds, so we moved to Yarn berry. We got past that initial problems but faced others. It might be just a skill issue. I'm the only full stack developer, but I was always full we. The rest is full on backenders.

Basically, we are able to create development and debug builds, using build and gradle, but we are unable to run eas builds. We validated this through detox.

Expo team suggests using build instead of Expo Go. For this, we need EAS build scripts https://github.com/guillempuche/nx-expo-next-tamagui/blob/main/tools/scripts/ (the ones Nx starter template provides), but with Yarn it doesn't work (I still need to test it with npm).

I opened an issue nrwl/nx#22631. You can make some noise to increase the priority.

I'll follow this thread. 💪

@guillempuche
Copy link
Owner

guillempuche commented May 13, 2024

Yes, it isn't easy.

Some tips with a simpler than Takeout but ready for Material Design tokens https://m3.material.io/ (except deep concepts like state layers)

Config package

// packages/config/src/index.ts

export * from "./tamagui.config";

// packages/config/src/tamagui.config.ts

import { config as configV3 } from "@tamagui/config/v3";
import { createMedia } from "@tamagui/react-native-media-driver";
import { shorthands } from "@tamagui/shorthands";
import {
	size,
	space,
	radius as tamaguiRadius,
	themes,
	zIndex,
} from "@tamagui/themes/v3-themes";
import { createTamagui, createTokens } from "tamagui";

import { dark } from "@tamagui/themes/types/generated-v2";
import { animations } from "./animations";
import {
	palette,
	themeDark,
	themeDarkContrastMedium,
	themeLight,
	themeLightContrastHigh,
	themeLightContrastMedium,
} from "./colors";
import { fonts } from "./fonts";
import { borderRadius, media } from "./sizes";

export const tamaguiConfig = createTamagui({
	...configV3,
	...themes,
	animations,
	defaultFont: "body",
	fonts,
	media,
	shorthands,
	themes: {
		light: themeLight,
		lightContrastMedium: themeLightContrastMedium,
		lightContrastHigh: themeLightContrastHigh,
		dark: themeDark,
		darkContrastMedium: themeDarkContrastMedium,
		darkContrastHigh: themeDarkContrastMedium,
	},
	// List of size, space, and radius values here https://tamagui.dev/docs/intro/tokens.
	// Don't include Tamagui colors in the tokens object.
	tokens: createTokens({
		zIndex,
		color: palette,
		radius: {
			...borderRadius,
			...tamaguiRadius,
		},
		size: {
			// ...size,
			...size,
		},
		space,
	}),
	themeClassNameOnRoot: true,
});

export type Conf = typeof tamaguiConfig;
declare module "tamagui" {
	interface TamaguiCustomConfig extends Conf {}
}

// For the compiler to find it in Expo, and NextJS
export default tamaguiConfig;

// packages/config/src/colors.ts

/**
 * Colors and themes from https://www.figma.com/file/ljFUVoXDzf8zvObp8T5osT/Cites?type=design&mode=design&t=cu6CnoAB5T8ryF2B-0
 *
 * List of token names at https://github.com/material-foundation/material-tokens
 */

export const palette = {
	// Primary shades
	primary0: "#000000",
	primary5: "#00131F",
	primary10: "#001E2F",
	primary15: "#00293E",
	primary20: "#00344D",
	primary25: "#00405E",
	primary30: "#004C6E",
	primary35: "#13587C",
	primary40: "#256489",
	primary50: "#437DA3",
	primary60: "#5E97BE",
	primary70: "#79B1DA",
	primary80: "#94CDF7",
	primary90: "#C9E6FF",
	primary95: "#E5F2FF",
	primary98: "#F6FAFF",
	primary99: "#FCFCFF",
	primary100: "#FFFFFF",

	// Secondary shades
	secondary0: "#000000",
	secondary5: "#0F0047",
	secondary10: "#190064",
	secondary15: "#230C75",
	secondary20: "#2E1D7F",
	secondary25: "#3A2A8A",
	secondary30: "#453796",
	secondary35: "#5144A3",
	secondary40: "#5D50B0",
	secondary50: "#766ACB",
	secondary60: "#9084E7",
	secondary70: "#AB9FFF",
	secondary80: "#C8BFFF",
	secondary90: "#E5DEFF",
	secondary95: "#F4EEFF",
	secondary98: "#FDF8FF",
	secondary99: "#FFFBFF",
	secondary100: "#FFFFFF",

	// Tertiary shades
	tertiary0: "#000000",
	tertiary5: "#001501",
	tertiary10: "#002202",
	tertiary15: "#002D04",
	tertiary20: "#003906",
	tertiary25: "#004609",
	tertiary30: "#00530D",
	tertiary35: "#0F6017",
	tertiary40: "#206C22",
	tertiary50: "#3C8639",
	tertiary60: "#56A150",
	tertiary70: "#70BD68",
	tertiary80: "#8AD981",
	tertiary90: "#B8F1AD",
	tertiary95: "#C9FFBD",
	tertiary98: "#ECFFE3",
	tertiary99: "#F6FFEF",
	tertiary100: "#FFFFFF",

	// Error shades
	error0: "#000000",
	error10: "#410002",
	error20: "#690005",
	error25: "#7E0007",
	error30: "#93000A",
	error35: "#A80710",
	error40: "#BA1A1A",
	error50: "#DE3730",
	error60: "#FF5449",
	error70: "#FF897D",
	error80: "#FFB4AB",
	error90: "#FFDAD6",
	error95: "#FFEDea",
	error98: "#FFF8F7",
	error99: "#FFFBFF",
	error100: "#FFFFFF",

	// Neutral shades
	neutral0: "#000000",
	neutral5: "#101113",
	neutral10: "#1A1C1D",
	neutral15: "#242627",
	neutral20: "#2F3032",
	neutral25: "#3A3B3D",
	neutral30: "#464748",
	neutral35: "#515254",
	neutral40: "#5D5E60",
	neutral50: "#767779",
	neutral60: "#909092",
	neutral70: "#ABABAD",
	neutral80: "#C6C6C8",
	neutral90: "#E3E2E4",
	neutral95: "#F1F0F2",
	neutral98: "#FAF9FB",
	neutral99: "#FDFCFE",
	neutral100: "#FFFFFF",

	// Neutral variant shades
	neutralVariant0: "#000000",
	neutralVariant5: "#0D1115",
	neutralVariant10: "#181C20",
	neutralVariant15: "#22262A",
	neutralVariant20: "#2D3135",
	neutralVariant25: "#383C40",
	neutralVariant30: "#43474B",
	neutralVariant35: "#4F5357",
	neutralVariant40: "#5B5F63",
	neutralVariant50: "#74777C",
	neutralVariant60: "#8D9196",
	neutralVariant70: "#A8ABB0",
	neutralVariant80: "#C3C7CC",
	neutralVariant90: "#DFE3E8",
	neutralVariant95: "#EEF1F6",
	neutralVariant98: "#F7F9FE",
	neutralVariant99: "#FCFCFF",
	neutralVariant100: "#FFFFFF",
};

export const themeLight = {
	primary: palette.primary30, // Assuming #003B57 is palette.primary30
	surfaceTint: "#256489", // Hex value directly as not in provided palette list
	onPrimary: palette.primary100,
	primaryContainer: "#1E5F83", // Direct hex value
	onPrimaryContainer: palette.primary100,
	secondary: palette.secondary30, // Assuming #342484 is palette.secondary30
	onSecondary: palette.secondary100,
	secondaryContainer: "#574AA9", // Direct hex value
	onSecondaryContainer: palette.secondary100,
	tertiary: palette.tertiary30, // Assuming #004208 is palette.tertiary30
	onTertiary: palette.tertiary100,
	tertiaryContainer: "#1B681E", // Direct hex value
	onTertiaryContainer: palette.tertiary100,
	error: palette.error40, // Assuming #BA1A1A is palette.error40
	onError: palette.error100,
	errorContainer: "#FFDAD6", // Direct hex value
	onErrorContainer: "#410002", // Direct hex value
	background: "#F8F9FD", // Direct hex value
	onBackground: palette.neutral10,
	surface: "#F8F9FD", // Direct hex value
	onSurface: palette.neutral10,
	surfaceVariant: palette.neutralVariant80, // Assuming #DDE3EB is palette.neutralVariant80
	onSurfaceVariant: palette.neutralVariant30, // Assuming #41484E is palette.neutralVariant30
	outline: "#71787F", // Direct hex value
	outlineVariant: "#C0C7CF", // Direct hex value
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral20,
	inverseOnSurface: palette.neutral95,
	inversePrimary: palette.primary80, // Assuming #94CDF7 is palette.primary80
	primaryFixed: palette.primary90, // Assuming #C9E6FF is palette.primary90
	onPrimaryFixed: palette.primary20, // Assuming #001E2F is palette.primary20
	primaryFixedDim: palette.primary80, // Assuming #94CDF7 is palette.primary80
	onPrimaryFixedVariant: palette.primary30, // Assuming #004C6E is palette.primary30
	secondaryFixed: palette.secondary90, // Assuming #E5DEFF is palette.secondary90
	onSecondaryFixed: palette.secondary10, // Assuming #190064 is palette.secondary10
	secondaryFixedDim: palette.secondary80, // Assuming #C8BFFF is palette.secondary80
	onSecondaryFixedVariant: palette.secondary30, // Assuming #453796 is palette.secondary30
	tertiaryFixed: "#A5F69A", // Direct hex value
	onTertiaryFixed: palette.tertiary10, // Assuming #002202 is palette.tertiary10
	tertiaryFixedDim: "#8AD981", // Direct hex value
	onTertiaryFixedVariant: palette.tertiary30, // Assuming #00530D is palette.tertiary30
	surfaceDim: "#D9DADD", // Direct hex value
	surfaceBright: "#F8F9FD", // Direct hex value
	surfaceContainerLowest: palette.primary100,
	surfaceContainerLow: "#F2F3F7", // Direct hex value
	surfaceContainer: "#EDEEF1", // Direct hex value
	surfaceContainerHigh: "#E7E8EB", // Direct hex value
	surfaceContainerHighest: "#E1E2E6", // Direct hex value
};

export const themeLightContrastMedium = {
	primary: palette.primary30, // Assuming #003B57 is palette.primary30
	surfaceTint: "#256489", // Hex value directly as not in provided palette list
	onPrimary: palette.primary100,
	primaryContainer: "#1E5F83", // Direct hex value
	onPrimaryContainer: palette.primary100,
	secondary: palette.secondary30, // Assuming #342484 is palette.secondary30
	onSecondary: palette.secondary100,
	secondaryContainer: "#574AA9", // Direct hex value
	onSecondaryContainer: palette.secondary100,
	tertiary: palette.tertiary30, // Assuming #004208 is palette.tertiary30
	onTertiary: palette.tertiary100,
	tertiaryContainer: "#1B681E", // Direct hex value
	onTertiaryContainer: palette.tertiary100,
	error: "#8C0009", // Direct hex value
	onError: palette.error100,
	errorContainer: "#DA342E", // Direct hex value
	onErrorContainer: palette.error100,
	background: "#F8F9FD", // Direct hex value
	onBackground: palette.neutral10,
	surface: "#F8F9FD", // Direct hex value
	onSurface: palette.neutral10,
	surfaceVariant: palette.neutralVariant80, // Assuming #DDE3EB is palette.neutralVariant80
	onSurfaceVariant: "#3D444A", // Direct hex value
	outline: "#596066", // Direct hex value
	outlineVariant: "#757B82", // Direct hex value
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral20,
	inverseOnSurface: palette.neutral95,
	inversePrimary: palette.primary80, // Assuming #94CDF7 is palette.primary80
};

export const themeLightContrastHigh = {
	primary: "#002539", // Direct hex value
	surfaceTint: "#256489", // Direct hex value
	onPrimary: palette.primary100,
	primaryContainer: "#004768", // Direct hex value
	onPrimaryContainer: palette.primary100,
	secondary: "#200571", // Direct hex value
	onSecondary: palette.secondary100,
	secondaryContainer: "#413392", // Direct hex value
	onSecondaryContainer: palette.secondary100,
	tertiary: "#002903", // Direct hex value
	onTertiary: palette.tertiary100,
	tertiaryContainer: "#004F0C", // Direct hex value
	onTertiaryContainer: palette.tertiary100,
	error: "#4E0002", // Direct hex value
	onError: palette.error100,
	errorContainer: "#8C0009", // Direct hex value
	onErrorContainer: palette.error100,
	background: "#F8F9FD", // Direct hex value
	onBackground: palette.neutral10,
	surface: "#F8F9FD", // Direct hex value
	onSurface: palette.neutral0, // Assuming #000000 is palette.neutral0
	surfaceVariant: palette.neutralVariant80, // Assuming #DDE3EB is palette.neutralVariant80
	onSurfaceVariant: "#1E252A", // Direct hex value
	outline: "#3D444A", // Direct hex value
	outlineVariant: "#3D444A", // Direct hex value
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral20,
	inverseOnSurface: palette.neutral100,
	inversePrimary: "#DCEEFF", // Direct hex value
};

export const themeDark = {
	primary: palette.primary80,
	surfaceTint: palette.primary80,
	onPrimary: "#00344D", // Direct hex value
	primaryContainer: "#004464", // Direct hex value
	onPrimaryContainer: "#ACDBFF", // Direct hex value
	secondary: palette.secondary80,
	onSecondary: "#2E1D7F", // Direct hex value
	secondaryContainer: "#3E308F", // Direct hex value
	onSecondaryContainer: "#D7CFFF", // Direct hex value
	tertiary: palette.tertiary80,
	onTertiary: "#003906", // Direct hex value
	tertiaryContainer: "#004C0B", // Direct hex value
	onTertiaryContainer: "#99E98E", // Direct hex value
	error: palette.error80,
	onError: "#690005", // Direct hex value
	errorContainer: "#93000A", // Direct hex value
	onErrorContainer: "#FFDAD6",
	background: "#111416", // Direct hex value
	onBackground: palette.neutral100,
	surface: "#111416", // Direct hex value
	onSurface: palette.neutral100,
	surfaceVariant: palette.neutralVariant30,
	onSurfaceVariant: palette.neutralVariant80,
	outline: "#8B9198", // Direct hex value
	outlineVariant: palette.neutralVariant40,
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral100,
	inverseOnSurface: palette.neutral20,
	inversePrimary: "#256489", // Direct hex value
	primaryFixed: palette.primary90,
	onPrimaryFixed: "#001E2F", // Direct hex value
	primaryFixedDim: palette.primary80,
	onPrimaryFixedVariant: "#004C6E", // Direct hex value
	secondaryFixed: palette.secondary90,
	onSecondaryFixed: "#190064", // Direct hex value
	secondaryFixedDim: palette.secondary80,
	onSecondaryFixedVariant: palette.secondary30,
	tertiaryFixed: "#A5F69A", // Direct hex value
	onTertiaryFixed: "#002202", // Direct hex value
	tertiaryFixedDim: "#8AD981", // Direct hex value
	onTertiaryFixedVariant: "#00530D", // Direct hex value
	surfaceDim: "#111416", // Direct hex value
	surfaceBright: "#37393C", // Direct hex value
	surfaceContainerLowest: "#0C0E11", // Direct hex value
	surfaceContainerLow: "#191C1E", // Direct hex value
	surfaceContainer: "#1D2022", // Direct hex value
	surfaceContainerHigh: "#282A2D", // Direct hex value
	surfaceContainerHighest: "#323538", // Direct hex value
};

export const themeDarkContrastMedium = {
	primary: "#99D1FB", // Direct hex value
	surfaceTint: palette.primary80, // Assuming #94CDF7 is palette.primary80
	onPrimary: "#001827", // Direct hex value
	primaryContainer: "#5E97BE", // Direct hex value
	onPrimaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	secondary: "#CCC4FF", // Direct hex value
	onSecondary: "#140056", // Direct hex value
	secondaryContainer: "#9084E7", // Direct hex value
	onSecondaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	tertiary: "#8EDD84", // Direct hex value
	onTertiary: "#001C02", // Direct hex value
	tertiaryContainer: "#56A150", // Direct hex value
	onTertiaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	error: "#FFBAB1", // Direct hex value
	onError: "#370001", // Direct hex value
	errorContainer: "#FF5449", // Direct hex value
	onErrorContainer: palette.primary0, // Assuming #000000 is palette.primary0
	background: "#111416", // Direct hex value
	onBackground: palette.neutral100,
	surface: "#111416", // Direct hex value
	onSurface: "#FAFBFE", // Direct hex value
	surfaceVariant: palette.neutralVariant30,
	onSurfaceVariant: "#C5CBD3", // Direct hex value
	outline: "#9DA4AB", // Direct hex value
	outlineVariant: "#7D848B", // Direct hex value
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral100,
	inverseOnSurface: "#282A2D", // Direct hex value
	inversePrimary: "#004D70", // Direct hex value
};

export const themeDarkContrastHigh = {
	primary: "#F9FBFF", // Direct hex value
	surfaceTint: palette.primary80, // Assuming #94CDF7 is palette.primary80
	onPrimary: palette.primary0, // Assuming #000000 is palette.primary0
	primaryContainer: "#99D1FB", // Direct hex value
	onPrimaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	secondary: "#FEF9FF", // Direct hex value
	onSecondary: palette.primary0, // Assuming #000000 is palette.primary0
	secondaryContainer: "#CCC4FF", // Direct hex value
	onSecondaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	tertiary: "#F1FFE9", // Direct hex value
	onTertiary: palette.primary0, // Assuming #000000 is palette.primary0
	tertiaryContainer: "#8EDD84", // Direct hex value
	onTertiaryContainer: palette.primary0, // Assuming #000000 is palette.primary0
	error: "#FFF9F9", // Direct hex value
	onError: palette.primary0, // Assuming #000000 is palette.primary0
	errorContainer: "#FFBAB1", // Direct hex value
	onErrorContainer: palette.primary0, // Assuming #000000 is palette.primary0
	background: "#111416", // Direct hex value
	onBackground: palette.neutral100,
	surface: "#111416", // Direct hex value
	onSurface: "#FFFFFF", // Direct hex value
	surfaceVariant: palette.neutralVariant30,
	onSurfaceVariant: "#F9FBFF", // Direct hex value
	outline: "#C5CBD3", // Direct hex value
	outlineVariant: "#C5CBD3", // Direct hex value
	shadow: palette.primary0,
	scrim: palette.primary0,
	inverseSurface: palette.neutral100,
	inverseOnSurface: palette.primary0, // Assuming #000000 is palette.primary0
	inversePrimary: "#002D44", // Direct hex value
};

// packages/config/src/size.ts

import { createMedia } from "tamagui";

// For site responsive demo
export const media = createMedia({
	xs: { maxWidth: 660 },
	gtXs: { minWidth: 660 + 1 },
	sm: { maxWidth: 800 },
	gtSm: { minWidth: 800 + 1 },
	md: { maxWidth: 1020 },
	gtMd: { minWidth: 1020 + 1 },
	lg: { maxWidth: 1280 },
	gtLg: { minWidth: 1280 + 1 },
	short: { maxHeight: 820 },
	tall: { minHeight: 820 },
	// hoverNone: { hover: "none" },
	// pointerCoarse: { pointer: "coarse" },
});
//  {
// 	tiny: { maxWidth: 500 },
// 	gtTiny: { minWidth: 500 + 1 },
// 	small: { maxWidth: 620 },
// 	gtSmall: { minWidth: 620 + 1 },
// 	medium: { maxWidth: 780 },
// 	gtMedium: { minWidth: 780 + 1 },
// 	large: { maxWidth: 900 },
// 	gtLarge: { minWidth: 900 + 1 },
// };

export const borderRadius = {
	brSm: 8,
	brMd: 16,
	brLg: 24,
};

// .../animations.ts

import { createAnimations } from "@tamagui/animations-css";

export const animations = createAnimations({
	bouncy: "800ms cubic-bezier(0.34, 1.56, 0.64, 1) both",
	fast: "ease-in 150ms",
	medium: "ease-in 300ms",
	slow: "ease-in 450ms",
});

// .../animations.native.ts

import { createAnimations } from "@tamagui/animations-react-native";

export const animations = createAnimations({
	bouncy: {
		type: "spring",
		damping: 10,
		mass: 0.9,
		stiffness: 100,
	},
	fast: {
		damping: 20,
		mass: 1.2,
		stiffness: 250,
	},
	medium: {
		damping: 10,
		mass: 0.9,
		stiffness: 100,
	},
	slow: {
		damping: 20,
		stiffness: 60,
	},
});

// .../fonts.ts

import { createInterFont } from "@tamagui/font-inter";

const sans = createInterFont({
	// family: 'System',
	// size: {},
	// transform: {},
	// weight: {},
	// color: {},
	// letterSpacing: {},
	face: {
		400: { normal: "Inter" },
		500: { normal: "Inter" },
		700: { normal: "InterBold" },
	},
});

const mono = createInterFont(
	{
		// size: {},
		// transform: {},
		// weight: {},
		// color: {},
		// letterSpacing: {},
		family:
			'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;',
		face: {
			400: { normal: "Inter" },
			500: { normal: "Inter" },
			700: { normal: "InterBold" },
		},
	},
	// {
	// sizeSize: (size) => Math.round(size * 1.1),
	// sizeLineHeight: (size) => Math.round(size * 1.1 + (size > 20 ? 10 : 10)),
	// },
);

// Tamagui requires body and heading. More here https://tamagui.dev/docs/core/configuration
export const fonts = {
	body: sans,
	heading: sans,
	mono,
};

Components package

// packages/components/tsconfig.json

{
  "extends": "../../tsconfig.base",
  "include": [
    "**/*.ts",
    "**/*.tsx",
    "../config/src/tamagui.config.ts" // I think this helps to import as `tamagui` in the package, but it might be useless.
  ],
  "compilerOptions": {
    "composite": true,
    "jsx": "react-jsx"
  },
  "references": []
}

// packages/components/src/text.ts

/**
 * Inspired in https://tamagui.dev/ui/headings and https://github.com/status-im/status-web/blob/main/packages/components/src/text/text.tsx
 */

import type { ReactNode } from "react";
import { SizableText, styled } from "tamagui";

type Props = {
	children: ReactNode;
	lowercase?: boolean;
	select?: false;
	truncate?: boolean;
	uppercase?: boolean;
	wrap?: false;
};

/**
 * Text based on Material Design https://m3.material.io/styles/typography/
 */
export const Text = (
	{ color = "$onBackground", ...props }: Props,
	// ref: Ref<RNText>,
) => {
	// const { color = "$onBackground", ref, ...props } = props;
	// return <Base {...props} color={color} ref={ref} />;
	return <Base {...props} color={color} />;
};

const Base = styled(SizableText, {
	name: "Text",

	color: "$onBackground",
	userSelect: "auto",
	whiteSpace: "normal",

	variants: {
		// "display-l": {
		// 	true: {
		// 		accessibilityRole: "header",
		// 		fontSize: 57,
		// 		fontWeight: "400",
		// 		letterSpacing: -0.25,
		// 		lineHeight: 64,
		// 		role: "heading",
		// 		tag: "h1",
		// 	},
		// },
		"display-m": {
			true: {
				accessibilityRole: "header",
				fontSize: 45,
				fontWeight: "400",
				letterSpacing: 0,
				lineHeight: 52,
				role: "heading",
				tag: "h2",
			},
		},
		"display-sm": {
			true: {
				fontSize: 36,
				fontWeight: "400",
				letterSpacing: 0,
				lineHeight: 44,
			},
		},
		"headline-l": {
			true: {
				accessibilityRole: "header",
				fontSize: 32,
				fontWeight: "400",
				letterSpacing: 0,
				lineHeight: 40,
				role: "heading",
				tag: "h3",
			},
		},
		"headline-m": {
			true: {
				accessibilityRole: "header",
				fontSize: 28,
				fontWeight: "400",
				letterSpacing: 0,
				lineHeight: 36,
				role: "heading",
				tag: "h4",
			},
		},
		// "headline-sm": {
		// 	true: {
		// 		fontSize: 24,
		// 		fontWeight: "400",
		// 		letterSpacing: 0,
		// 		lineHeight: 32,
		// 	},
		// },
		title: {
			true: {
				accessibilityRole: "header",
				fontSize: 22,
				fontWeight: "400",
				letterSpacing: 0,
				lineHeight: 28,
				role: "heading",
				tag: "h5",
			},
		},
		// "title-m": {
		// 	true: {
		// 		fontSize: 16,
		// 		fontWeight: "500",
		// 		letterSpacing: 0.15,
		// 		lineHeight: 24,
		// 	},
		// },
		"body-l": {
			true: {
				fontSize: 16,
				fontWeight: "400",
				letterSpacing: 0.5,
				lineHeight: 24,
				tag: "span",
			},
		},
		"body-m": {
			true: {
				fontSize: 14,
				fontWeight: "400",
				letterSpacing: 0.25,
				lineHeight: 20,
				tag: "span",
			},
		},
		"label-l": {
			true: {
				fontSize: 14,
				fontWeight: "500",
				letterSpacing: 0.1,
				lineHeight: 20,
				tag: "span",
			},
		},
		"label-m": {
			true: {
				fontSize: 12,
				fontWeight: "500",
				letterSpacing: 0.5,
				lineHeight: 16,
			},
		},

		bold: {
			true: {
				fontWeight: "700",
			},
		},

		lowercase: {
			true: {
				textTransform: "lowercase",
			},
		},
		uppercase: {
			true: {
				textTransform: "uppercase",
			},
		},

		wrap: {
			false: {
				whiteSpace: "nowrap",
			},
		},

		truncate: {
			true: {
				overflow: "hidden",
				textOverflow: "ellipsis",
				whiteSpace: "nowrap",
				wordWrap: "normal",
				maxWidth: "100%",
				minWidth: 0,
			},
		},

		// select: {
		// 	false: {
		// 		userSelect: "none",
		// 	},
		// },
	} as const,

	defaultVariants: {
		"body-l": true,
	},
});

// const _Text = forwardRef(Text);
// export { _Text as Text };
// export type { Props as TextProps };

// packages/components/src/button.tsx

/**
 * Inspired in https://tamagui.dev/bento/elements/buttons
 */

import type { ReactNode } from "react";
import { cloneElement } from "react";
import {
	AnimatePresence,
	Button as ButtonTamagui,
	Spinner,
	styled,
} from "tamagui";

/**
 * ButtonText based on [Material Design](https://m3.material.io/styles/typography/)
 */
export const ButtonText = ({
	children,
	fullWidth,
	icon,
	loading,
	...props
}: {
	children: string;
	fullWidth?: boolean;
	icon?: ReactNode;
	loading?: boolean;
}) => {
	// export const ButtonText = (props: Props, ref: Ref<View>) => {

	return (
		<Base
			{...props}
			pl={icon || loading ? 16 : 24} // TODO: support RTL
			pr={24}
			// ref={ref}
			// width={fullWidth ? "100%" : "auto"}
		>
			{loading ? (
				<AnimatePresence>
					<ButtonTamagui.Icon>
						<Spinner
							animation="bouncy"
							enterStyle={{
								scale: 0,
							}}
							exitStyle={{
								scale: 0,
							}}
						/>
					</ButtonTamagui.Icon>
				</AnimatePresence>
			) : null}

			{icon && !loading ? (
				<ButtonTamagui.Icon>
					{cloneElement(icon, {
						size: 18,
					})}
				</ButtonTamagui.Icon>
			) : null}
			<ButtonTamagui.Text>{children}</ButtonTamagui.Text>
		</Base>
	);
};

// It doesn't use the same values as in Material Desing guideline because it
// doesn't have the state layer.
const Base = styled(ButtonTamagui, {
	name: "ButtonText",

	br: "$6",
	userSelect: "none",
	size: 40,

	variants: {
		filled: {
			true: {
				bc: "$primary",
				color: "$onPrimary",
				hoverStyle: {
					bc: "$primary",
					opacity: 0.88,
				},
				pressStyle: {
					bc: "$primary",
					opacity: 0.76,
				},
			},
		},
		"filled-disabled": {
			true: {
				bc: "$onSurface",
				color: "$surface",
				opacity: 0.12,
				pointerEvents: "none",
			},
		},
		outlined: {
			true: {
				bc: "transparent",
				borderWidth: 1,
				borderColor: "$primary",
				color: "$primary",
				hoverStyle: { bc: "$surfaceContainer" },
				pressStyle: { bc: "$surfaceContainerHigh" },
			},
		},
		"outlined-disabled": {
			true: {
				bc: "transparent",
				borderWidth: 1,
				borderColor: "$surfaceContainerHighest",
				color: "$surfaceContainerHighest",
				pointerEvents: "none",
			},
		},
		text: {
			true: {
				bc: "transparent",
				color: "$primary",
				hoverStyle: { bc: "$surfaceContainerHigh" },
				pressStyle: { bc: "$surfaceContainerHighest" },
			},
		},
		"text-disabled": {
			true: {
				bc: "transparent",
				color: "$onSurface",
				opacity: 0.12,
				pointerEvents: "none",
			},
		},
	} as const,
	defaultVariants: {
		text: true,
	},
});

NextJS

You don't need to create tamagui.config.ts in apps/nextjs root folder. But you let know where to find Tamagui config file:

// next.config.js

/** @type {import('next').NextConfig} */
const { withTamagui } = require("@tamagui/next-plugin");
const { join } = require("path");

const boolVals = {
	true: true,
	false: false,
};

const disableExtraction =
	boolVals[process.env.DISABLE_EXTRACTION] ??
	process.env.NODE_ENV === "development";

console.log(`

Welcome to Tamagui!

You can update this monorepo to the latest Tamagui release just by running:

yarn upgrade:tamagui

We've set up a few things for you.

See the "excludeReactNativeWebExports" setting in next.config.js, which omits these
from the bundle: Switch, ProgressBar Picker, CheckBox, Touchable. To save more,
you can add ones you don't need like: AnimatedFlatList, FlatList, SectionList,
VirtualizedList, VirtualizedSectionList.

🐣

Remove this log in next.config.js.

`);

const plugins = [
	withTamagui({
		config: "../../packages/config/src/tamagui.config.ts",
		components: ["tamagui", "@my/ui"],
		importsWhitelist: ["constants.js", "colors.js"],
		outputCSS:
			process.env.NODE_ENV === "production" ? "./public/tamagui.css" : null,
		logTimings: true,
		disableExtraction,
		shouldExtract: (path) => {
			if (path.includes(join("packages", "app"))) {
				return true;
			}
		},
		excludeReactNativeWebExports: [
			"Switch",
			"ProgressBar",
			"Picker",
			"CheckBox",
			"Touchable",
		],
	}),
];

module.exports = () => {
	/** @type {import('next').NextConfig} */
	let config = {
		typescript: {
			ignoreBuildErrors: true,
		},
		modularizeImports: {
			"@tamagui/lucide-icons": {
				transform: `@tamagui/lucide-icons/dist/esm/icons/{{kebabCase member}}`,
				skipDefaultConversion: true,
			},
		},
		transpilePackages: [
			"solito",
			"react-native-web",
			"expo-linking",
			"expo-constants",
			"expo-modules-core",
		],
		experimental: {
			scrollRestoration: true,
		},
	};

	for (const plugin of plugins) {
		config = {
			...config,
			...plugin(config),
		};
	}

	return config;
};

@guillempuche
Copy link
Owner

guillempuche commented May 13, 2024

Yes, PNPM or Yarn in symlink mode (no real node_modules in the Nx repo) doesn't work in React Native https://yarnpkg.com/getting-started/qa#how-easy-should-you-expect-the-migration-from-classic-to-modern-to-be.

I don't know PNPM, but Yarn 4 has PnP (aka symlinks) by default, you need to disable in nodeLinker.

// .yarnrc.yml

enableGlobalCache: false

enableTelemetry: false

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.1.1.cjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants