diff --git a/.gitignore b/.gitignore
index 577395af..ffcb0f99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,3 +68,4 @@ apps/docs/.next
# TypeScript
packages/**/tsconfig.tsbuildinfo
tsconfig.tsbuildinfo
+dist
diff --git a/apps/examples/app/_layout.tsx b/apps/examples/app/_layout.tsx
index fb72e5ae..db36ffe0 100644
--- a/apps/examples/app/_layout.tsx
+++ b/apps/examples/app/_layout.tsx
@@ -1,12 +1,16 @@
-import { ThemeProvider } from 'react-native-ficus-ui';
+
import { Stack } from 'expo-router';
+import { Box, ThemeProvider as ThemeProviderV2 } from '@ficus-ui/native'
+import { ThemeProvider } from 'react-native-ficus-ui';
export default function RootLayout() {
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/apps/examples/app/components-v2/Box.tsx b/apps/examples/app/components-v2/Box.tsx
new file mode 100644
index 00000000..693a6a19
--- /dev/null
+++ b/apps/examples/app/components-v2/Box.tsx
@@ -0,0 +1,251 @@
+import { Pressable, SafeAreaView, ScrollView, Text, View } from "react-native";
+import { Box } from "@ficus-ui/native";
+import ExampleSection from "@/src/ExampleSection";
+import { Link, useRouter } from "expo-router";
+
+const BoxComponent = () => {
+ const router = useRouter();
+ return (
+
+
+ Box component
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ none
+
+
+ xs
+
+
+ sm
+
+
+ md
+
+
+ lg
+
+
+ xl
+
+
+ 2xl
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default BoxComponent;
diff --git a/apps/examples/app/components-v2/index.tsx b/apps/examples/app/components-v2/index.tsx
new file mode 100644
index 00000000..26f5c70e
--- /dev/null
+++ b/apps/examples/app/components-v2/index.tsx
@@ -0,0 +1,5 @@
+import StackV2 from '@/src/v2';
+
+export default function Components() {
+ return ;
+}
diff --git a/apps/examples/app/components/index.tsx b/apps/examples/app/components/index.tsx
new file mode 100644
index 00000000..4fe0569b
--- /dev/null
+++ b/apps/examples/app/components/index.tsx
@@ -0,0 +1,5 @@
+import StackV1 from '@/src/v1';
+
+export default function Components() {
+ return ;
+}
diff --git a/apps/examples/app/items-v2.ts b/apps/examples/app/items-v2.ts
new file mode 100644
index 00000000..99730320
--- /dev/null
+++ b/apps/examples/app/items-v2.ts
@@ -0,0 +1,14 @@
+import { ComponentType } from 'react';
+import BoxComponent from './components-v2/Box';
+
+type ExampleComponentType = {
+ onScreenName: string;
+ navigationPath: string;
+ component: ComponentType;
+};
+
+export const components: ExampleComponentType[] = [
+
+ { navigationPath: 'Box', onScreenName: 'Box', component: BoxComponent },
+
+];
diff --git a/apps/examples/package.json b/apps/examples/package.json
index f65f1214..ac561216 100644
--- a/apps/examples/package.json
+++ b/apps/examples/package.json
@@ -18,6 +18,7 @@
},
"dependencies": {
"@expo/vector-icons": "14.0.2",
+ "@ficus-ui/native": "workspace:*",
"@react-native-community/slider": "4.5.4",
"@react-navigation/native": "7.0.0-rc.21",
"@shopify/flash-list": "1.5.0",
diff --git a/apps/examples/src/HomeScreen.tsx b/apps/examples/src/HomeScreen.tsx
index 598acd55..9b73e4ad 100644
--- a/apps/examples/src/HomeScreen.tsx
+++ b/apps/examples/src/HomeScreen.tsx
@@ -6,7 +6,6 @@ import {
SafeAreaBox,
} from 'react-native-ficus-ui';
-import { components } from '@/app/items';
import { useRouter } from 'expo-router';
import { ScrollView } from 'react-native';
@@ -21,21 +20,26 @@ const HomeScreen = () => {
Components
-
- {components.map((item, index) => (
-
- router.push(`/components/${item.navigationPath}`)
+ router.push(`/components`)
}
mt={10}
w="100%"
py={10}
>
- {item.onScreenName}
-
- ))}
-
+ V1
+
+
+ router.push(`/components-v2`)
+ }
+ mt={10}
+ w="100%"
+ py={10}
+ >
+ V2
+
diff --git a/apps/examples/src/v1/index.tsx b/apps/examples/src/v1/index.tsx
new file mode 100644
index 00000000..594a1fac
--- /dev/null
+++ b/apps/examples/src/v1/index.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import {
+ Box,
+ Text,
+ TouchableOpacity,
+ SafeAreaBox,
+} from 'react-native-ficus-ui';
+
+import { components } from '@/app/items';
+import { useRouter } from 'expo-router';
+import { ScrollView } from 'react-native';
+
+const StackV1 = () => {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ router.back()
+ }
+ mt={10}
+ w="100%"
+ py={10}
+ >
+ Back
+
+
+ Components
+
+
+ {components.map((item, index) => (
+
+ router.push(`/components/${item.navigationPath}`)
+ }
+ mt={10}
+ w="100%"
+ py={10}
+ >
+ {item.onScreenName}
+
+ ))}
+
+
+
+
+ >
+ );
+};
+
+export default StackV1;
diff --git a/apps/examples/src/v2/index.tsx b/apps/examples/src/v2/index.tsx
new file mode 100644
index 00000000..b681a696
--- /dev/null
+++ b/apps/examples/src/v2/index.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import {
+ Box,
+ Text,
+ TouchableOpacity,
+ SafeAreaBox,
+} from 'react-native-ficus-ui';
+
+import { components } from '@/app/items-v2';
+import { useRouter } from 'expo-router';
+import { ScrollView } from 'react-native';
+
+const StackV2 = () => {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ router.back()
+ }
+ mt={10}
+ w="100%"
+ py={10}
+ >
+ Back
+
+
+ Components
+
+
+ {components.map((item, index) => (
+
+ router.push(`/components-v2/${item.navigationPath}`)
+ }
+ mt={10}
+ w="100%"
+ py={10}
+ >
+ {item.onScreenName}
+
+ ))}
+
+
+
+
+ >
+ );
+};
+
+export default StackV2;
diff --git a/apps/examples/tsconfig.json b/apps/examples/tsconfig.json
index 7001c0d3..3e7a6770 100644
--- a/apps/examples/tsconfig.json
+++ b/apps/examples/tsconfig.json
@@ -7,7 +7,8 @@
"@/*": ["./*"],
"react-native-ficus-ui": [
"../../packages/react-native-ficus-ui/src/index"
- ]
+ ],
+ "@ficus-ui/native": ["../../packages/components/src/index"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
diff --git a/package.json b/package.json
index 424832ff..56db0adc 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"typecheck": "pnpm -C ./packages/react-native-ficus-ui run typecheck",
"test": "jest --config ./jest.config.cjs",
"ci": "pnpm typecheck && pnpm lint && pnpm test",
- "clean": "del ./**/node_modules ./**/lib ./**/.next ./**/.expo",
+ "clean": "del ./**/node_modules ./**/lib ./**/dist ./**/.next ./**/.expo",
"release": "release-it",
"format:check": "prettier --check packages --cache",
"format:fix": "prettier --write packages --cache"
diff --git a/packages/components/.eslintrc b/packages/components/.eslintrc
new file mode 100644
index 00000000..2b6d558d
--- /dev/null
+++ b/packages/components/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "extends": "../../.eslintrc",
+ "rules": {
+ "react/react-in-jsx-scope": "off",
+ },
+}
diff --git a/packages/components/package.json b/packages/components/package.json
new file mode 100644
index 00000000..ce332d1b
--- /dev/null
+++ b/packages/components/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@ficus-ui/native",
+ "version": "2.0.0-alpha",
+ "scripts": {
+ "dev": "tsc --watch",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@chakra-ui/utils": "2.2.2",
+ "@ficus-ui/style-system": "workspace:*",
+ "@ficus-ui/theme": "workspace:*"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "main": "dist/cjs/index.cjs",
+ "module": "dist/esm/index.mjs",
+ "types": "dist/types/index.d.ts",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/esm/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/cjs/index.cjs"
+ }
+ },
+ "./config": {
+ "import": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/esm/config/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/cjs/config/index.cjs"
+ }
+ }
+ }
+}
diff --git a/packages/components/src/box/index.tsx b/packages/components/src/box/index.tsx
new file mode 100644
index 00000000..43531361
--- /dev/null
+++ b/packages/components/src/box/index.tsx
@@ -0,0 +1,12 @@
+import { type NativeFicusProps, ficus } from '../system';
+
+// TODO: Improve Box as Image API from V1
+export interface BoxProps extends NativeFicusProps<'View'> {}
+
+export const Box = ficus('View');
+
+export const Circle = ficus('View', {
+ baseStyle: {
+ borderRadius: 'full',
+ },
+});
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
new file mode 100644
index 00000000..7604f697
--- /dev/null
+++ b/packages/components/src/index.ts
@@ -0,0 +1,2 @@
+export * from './box';
+export { ThemeProvider } from '@ficus-ui/theme';
diff --git a/packages/components/src/system/base-elements.ts b/packages/components/src/system/base-elements.ts
new file mode 100644
index 00000000..a0cc63fa
--- /dev/null
+++ b/packages/components/src/system/base-elements.ts
@@ -0,0 +1,24 @@
+import {
+ Image as RNImage,
+ Pressable as RNPressable,
+ ScrollView as RNScrollView,
+ Text as RNText,
+ View as RNView,
+} from 'react-native';
+
+/**
+ * These are the basic react native elements that are supported with the `ficus.View` notation.
+ */
+export const baseRNElements = {
+ View: RNView,
+ Image: RNImage,
+ Text: RNText,
+ ScrollView: RNScrollView,
+ Pressable: RNPressable,
+} as const;
+
+export type BaseRNElements = keyof typeof baseRNElements;
+
+export type BaseRNElementProps = React.ComponentProps<
+ (typeof baseRNElements)[T]
+>;
diff --git a/packages/components/src/system/factory.ts b/packages/components/src/system/factory.ts
new file mode 100644
index 00000000..710573f1
--- /dev/null
+++ b/packages/components/src/system/factory.ts
@@ -0,0 +1,30 @@
+import type { BaseRNElements } from './base-elements';
+import { FicusStyledOptions, NativeFicusComponents, styled } from './system';
+import { FicusComponent } from './system.types';
+import { RNElementType } from './system.utils';
+
+interface FicusFactory {
+ (
+ component: T,
+ options?: FicusStyledOptions
+ ): FicusComponent;
+}
+
+function factory() {
+ const cache = new Map>();
+
+ return new Proxy(styled, {
+ apply(_target, _thisArg, argArray: [RNElementType, FicusStyledOptions]) {
+ return styled(...argArray);
+ },
+
+ get(_, element: BaseRNElements) {
+ if (!cache.has(element)) {
+ cache.set(element, styled(element));
+ }
+ return cache.get(element);
+ },
+ }) as FicusFactory & NativeFicusComponents;
+}
+
+export const ficus = factory();
diff --git a/packages/components/src/system/forward-ref.ts b/packages/components/src/system/forward-ref.ts
new file mode 100644
index 00000000..78b1558f
--- /dev/null
+++ b/packages/components/src/system/forward-ref.ts
@@ -0,0 +1,29 @@
+/**
+ * Retrieved from Chakra UI.
+ *
+ * All credit goes to Chance (Reach UI), Haz (Reakit) and (fluentui)
+ * for creating the base type definitions upon which we improved on
+ *
+ */
+import { forwardRef as forwardReactRef } from 'react';
+
+import { ComponentWithAs, PropsOf, RightJoinProps } from './system.types';
+import { RNElementType } from './system.utils';
+
+export function forwardRef<
+ Props extends object,
+ Component extends RNElementType,
+>(
+ component: React.ForwardRefRenderFunction<
+ any,
+ RightJoinProps, Props> & {
+ as?: RNElementType;
+ }
+ >
+) {
+ // @ts-ignore
+ return forwardReactRef(component) as unknown as ComponentWithAs<
+ Component,
+ Props
+ >;
+}
diff --git a/packages/components/src/system/index.ts b/packages/components/src/system/index.ts
new file mode 100644
index 00000000..253ec725
--- /dev/null
+++ b/packages/components/src/system/index.ts
@@ -0,0 +1,3 @@
+export * from './system';
+export * from './forward-ref';
+export * from './factory';
diff --git a/packages/components/src/system/system.ts b/packages/components/src/system/system.ts
new file mode 100644
index 00000000..caf6b8f8
--- /dev/null
+++ b/packages/components/src/system/system.ts
@@ -0,0 +1,103 @@
+import { createElement, forwardRef } from 'react';
+
+import { assignAfter, compact, runIfFn, splitProps } from '@chakra-ui/utils';
+import {
+ StyleProps,
+ SystemStyleObject,
+ isStyleProp,
+ styleSheet,
+} from '@ficus-ui/style-system';
+import { useTheme } from '@ficus-ui/theme';
+import type { StyleProp } from 'react-native';
+
+import { BaseRNElements } from './base-elements';
+import type {
+ AsProps,
+ FicusComponent,
+ FicusProps,
+ PropsOf,
+} from './system.types';
+import { RNElementType, getComponent } from './system.utils';
+
+type Dict = Record;
+
+type GetStyleObject = (options: { baseStyle?: SystemStyleObject }) => (
+ propsWithTheme: {
+ theme: Dict;
+ style: StyleProp;
+ } & Dict
+) => StyleProp;
+
+export const toStyleSheetObject: GetStyleObject =
+ ({ baseStyle }) =>
+ (props) => {
+ const { theme, style, __styles, ...styleProps } = props;
+
+ const finalBaseStyle = runIfFn(baseStyle, props);
+ const finalStyles = assignAfter(
+ {},
+ __styles,
+ finalBaseStyle,
+ compact(styleProps),
+ style
+ );
+
+ return styleSheet(finalStyles)(theme);
+ };
+
+export interface FicusStyledOptions extends Dict {
+ label?: string;
+ baseStyle?: SystemStyleObject; // Base styles applied to the component.
+}
+
+export function styled<
+ T extends React.ComponentType | RNElementType,
+ P extends object = {},
+>(component: T, options?: FicusStyledOptions) {
+ const { baseStyle } = options ?? {};
+ const styleObject = toStyleSheetObject({ baseStyle });
+
+ const Component = getComponent(component);
+
+ const ficusComponent = forwardRef(
+ function FicusComponent(props, ref) {
+ const { children, style, as, ...rest } = props;
+ const { theme } = useTheme();
+
+ const restProps = splitProps(rest, isStyleProp)[1];
+
+ const AsComponent = as ? getComponent(as) : Component;
+
+ const propsWithTheme = {
+ style,
+ theme,
+ ...rest,
+ };
+
+ const computedStyle = styleObject(propsWithTheme);
+
+ return createElement(
+ AsComponent,
+ {
+ ref,
+ style: computedStyle,
+ ...restProps,
+ },
+ children
+ );
+ }
+ );
+
+ return ficusComponent as FicusComponent;
+}
+
+export type NativeFicusComponents = {
+ [Tag in BaseRNElements]: FicusComponent;
+};
+
+export type NativeFicusProps = Omit<
+ PropsOf,
+ 'ref' | keyof StyleProps
+> &
+ FicusProps &
+ AsProps;
diff --git a/packages/components/src/system/system.types.ts b/packages/components/src/system/system.types.ts
new file mode 100644
index 00000000..3a9753e3
--- /dev/null
+++ b/packages/components/src/system/system.types.ts
@@ -0,0 +1,75 @@
+import {
+ SystemProps as BaseSystemProps,
+ SystemStyleObject,
+ TextStyleProps,
+} from '@ficus-ui/style-system';
+
+import { NativeElementProps, RNElementType } from './system.utils';
+
+export type FicusProps = SystemProps & {
+ /**
+ * Used for internal style management (like theme styles)
+ * @private
+ */
+ __styles?: SystemStyleObject;
+};
+
+export type SystemProps = T extends 'Text'
+ ? BaseSystemProps // Ensure BaseSystemProps always gets a type argument
+ : BaseSystemProps>; // Provide a default type argument
+
+export interface AsProps {
+ as?: T;
+}
+
+// Props for a component with `as` prop to dynamically change the component type
+export type PropsOf = Omit<
+ NativeElementProps,
+ 'ref'
+> &
+ AsProps;
+
+// Omit common props like `as` or any additional props you define
+export type OmitCommonProps<
+ Target,
+ OmitAdditionalProps extends keyof any = never,
+> = Omit;
+
+// Utility type to merge props of the base component and the `as` component
+export type RightJoinProps<
+ SourceProps extends object = {},
+ OverrideProps extends object = {},
+> = OmitCommonProps & OverrideProps;
+
+// Utility type to merge props of two components, with `as` dynamic resolution
+export type MergeWithAs<
+ ComponentProps extends object,
+ AsCompProps extends object,
+ AdditionalProps extends object = {},
+ AsComponent extends RNElementType = RNElementType,
+> = (
+ | RightJoinProps
+ | RightJoinProps
+) & { as?: AsComponent };
+
+// Component type with `as` prop for dynamic component rendering
+export type ComponentWithAs<
+ Component extends RNElementType,
+ Props extends object = {},
+> = {
+ (
+ props: MergeWithAs<
+ NativeElementProps,
+ NativeElementProps,
+ Props,
+ AsComponent
+ >
+ ): JSX.Element;
+
+ displayName?: string;
+ defaultProps?: Partial;
+ id?: string;
+};
+
+export interface FicusComponent
+ extends ComponentWithAs & P> {}
diff --git a/packages/components/src/system/system.utils.ts b/packages/components/src/system/system.utils.ts
new file mode 100644
index 00000000..4fb4b7b5
--- /dev/null
+++ b/packages/components/src/system/system.utils.ts
@@ -0,0 +1,26 @@
+import {
+ type BaseRNElementProps,
+ type BaseRNElements,
+ baseRNElements,
+} from './base-elements';
+
+/**
+ * To allow user to extend custom component with ficus properties
+ */
+type CustomNativeElementProps> =
+ React.ComponentProps;
+
+export type RNElementType = BaseRNElements | React.ComponentType;
+
+export type NativeElementProps =
+ T extends BaseRNElements
+ ? BaseRNElementProps
+ : T extends React.ComponentType
+ ? CustomNativeElementProps
+ : never;
+
+export function getComponent(component: T) {
+ return typeof component === 'string'
+ ? baseRNElements[component as BaseRNElements]
+ : component;
+}
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
new file mode 100644
index 00000000..90f87c4e
--- /dev/null
+++ b/packages/components/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "jsx": "react-native",
+ "outDir": "./dist/types"
+ }
+}
diff --git a/packages/style-system/package.json b/packages/style-system/package.json
new file mode 100644
index 00000000..14df2c0f
--- /dev/null
+++ b/packages/style-system/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@ficus-ui/style-system",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "tsc --watch",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "typecheck": "tsc --noEmit",
+ "build": "tsc"
+ },
+ "keywords": [],
+ "author": {
+ "email": "noe@bearstudio.fr",
+ "name": "NoƩ Tatoud"
+ },
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "@chakra-ui/utils": "2.2.2"
+ },
+ "types": "dist/types/src/index.d.ts",
+ "main": "dist/types/src/index.js",
+ "module": "dist/types/src/index.js",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/esm/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/cjs/index.cjs"
+ }
+ },
+ "./config": {
+ "import": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/esm/config/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/cjs/config/index.cjs"
+ }
+ }
+ }
+}
diff --git a/packages/style-system/src/config/background.ts b/packages/style-system/src/config/background.ts
new file mode 100644
index 00000000..4e0777f5
--- /dev/null
+++ b/packages/style-system/src/config/background.ts
@@ -0,0 +1,10 @@
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+
+export const background: Config = {
+ bg: t.colors('backgroundColor'),
+};
+
+export interface BackgroundProps {
+ bg?: string;
+}
diff --git a/packages/style-system/src/config/border.ts b/packages/style-system/src/config/border.ts
new file mode 100644
index 00000000..d77e19cb
--- /dev/null
+++ b/packages/style-system/src/config/border.ts
@@ -0,0 +1,37 @@
+import { ViewStyle } from 'react-native';
+
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const border: Config = {
+ borderWidth: t.borderWidths('borderWidth'),
+ borderTopWidth: t.borderWidths('borderTopWidth'),
+ borderRightWidth: t.borderWidths('borderRightWidth'),
+ borderBottomWidth: t.borderWidths('borderBottomWidth'),
+ borderLeftWidth: t.borderWidths('borderLeftWidth'),
+ borderStartWidth: t.borderWidths('borderStartWidth'),
+ borderEndWidth: t.borderWidths('borderEndWidth'),
+ borderColor: t.colors('borderColor'),
+ borderTopColor: t.colors('borderTopColor'),
+ borderRightColor: t.colors('borderRightColor'),
+ borderBottomColor: t.colors('borderBottomColor'),
+ borderLeftColor: t.colors('borderLeftColor'),
+ borderStyle: t.borderStyles('borderStyle'),
+};
+
+export interface BorderProps {
+ borderWidth?: ResponsiveValue;
+ borderTopWidth?: ResponsiveValue;
+ borderRightWidth?: ResponsiveValue;
+ borderBottomWidth?: ResponsiveValue;
+ borderLeftWidth?: ResponsiveValue;
+ borderStartWidth?: ResponsiveValue;
+ borderEndWidth?: ResponsiveValue;
+ borderColor?: ResponsiveValue;
+ borderTopColor?: ResponsiveValue;
+ borderRightColor?: ResponsiveValue;
+ borderBottomColor?: ResponsiveValue;
+ borderLeftColor?: ResponsiveValue;
+ borderStyle?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/colors.ts b/packages/style-system/src/config/colors.ts
new file mode 100644
index 00000000..0d8a2683
--- /dev/null
+++ b/packages/style-system/src/config/colors.ts
@@ -0,0 +1,16 @@
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+
+export const color: Config = {
+ color: t.colors('color'),
+ textColor: t.colors('color'),
+ overlayColor: t.colors('overlayColor'),
+ shadowColor: t.colors('shadowColor'),
+};
+
+export interface ColorProps {
+ color?: string;
+ textColor?: string;
+ overlayColor?: string;
+ shadowColor?: string;
+}
diff --git a/packages/style-system/src/config/effect.ts b/packages/style-system/src/config/effect.ts
new file mode 100644
index 00000000..cbf3f91f
--- /dev/null
+++ b/packages/style-system/src/config/effect.ts
@@ -0,0 +1,25 @@
+import { ViewStyle } from 'react-native';
+
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const effect: Config = {
+ boxShadow: true,
+ shadow: t.shadows(),
+ shadowOpacity: true,
+ shadowOffset: t.shadows('shadowOffset'),
+ shadowRadius: t.radius('shadowRadius'),
+ opacity: true,
+ filter: true,
+};
+
+export interface EffectProps {
+ boxShadow?: ViewStyle['boxShadow'];
+ shadow?: string;
+ shadowOpacity?: ResponsiveValue;
+ shadowOffset?: ResponsiveValue;
+ shadowRadius?: ResponsiveValue;
+ opacity?: ResponsiveValue;
+ filter?: ViewStyle['shadowOffset'];
+}
diff --git a/packages/style-system/src/config/flexbox.ts b/packages/style-system/src/config/flexbox.ts
new file mode 100644
index 00000000..08f71a8a
--- /dev/null
+++ b/packages/style-system/src/config/flexbox.ts
@@ -0,0 +1,48 @@
+import { ViewStyle } from 'react-native';
+
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const flexbox: Config = {
+ flex: true,
+ flexDirection: true,
+ direction: t.flexbox('flexDirection'),
+ flexWrap: true,
+ wrap: t.flexbox('flexWrap'),
+ flexBasis: true,
+ basis: t.flexbox('flexBasis'),
+ flexGrow: true,
+ grow: t.flexbox('flexGrow'),
+ flexShrink: true,
+ shrink: t.flexbox('flexShrink'),
+ justifyContent: true,
+ justify: t.flexbox('justifyContent'),
+ alignSelf: t.flexbox('alignItems'),
+ alignItems: true,
+ align: t.flexbox('alignItems'),
+};
+
+type FlexDirection = ViewStyle['flexDirection'];
+type FlexWrap = ViewStyle['flexWrap'];
+type FlexJustify = ViewStyle['justifyContent'];
+type FlexAlignSelf = ViewStyle['alignSelf'];
+type FlexAlignItems = ViewStyle['alignItems'];
+export interface FlexboxProps {
+ flex?: ResponsiveValue;
+ flexDirection?: ResponsiveValue;
+ direction?: ResponsiveValue;
+ flexWrap?: ResponsiveValue;
+ wrap?: ResponsiveValue;
+ flexBasis?: ResponsiveValue;
+ basis?: ResponsiveValue;
+ flexGrow?: ResponsiveValue;
+ grow?: ResponsiveValue;
+ flexShrink?: ResponsiveValue;
+ shrink?: ResponsiveValue;
+ justifyContent?: ResponsiveValue;
+ justify?: ResponsiveValue;
+ alignSelf?: ResponsiveValue;
+ alignItems?: ResponsiveValue;
+ align?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/index.ts b/packages/style-system/src/config/index.ts
new file mode 100644
index 00000000..705c9289
--- /dev/null
+++ b/packages/style-system/src/config/index.ts
@@ -0,0 +1,12 @@
+export * from './background';
+export * from './border';
+export * from './colors';
+export * from './effect';
+export * from './flexbox';
+export * from './interactivity';
+export * from './interactivity';
+export * from './layout';
+export * from './position';
+export * from './radius';
+export * from './space';
+export * from './text';
diff --git a/packages/style-system/src/config/interactivity.ts b/packages/style-system/src/config/interactivity.ts
new file mode 100644
index 00000000..d9e3644e
--- /dev/null
+++ b/packages/style-system/src/config/interactivity.ts
@@ -0,0 +1,13 @@
+import { TextStyle, ViewStyle } from 'react-native';
+
+import { Config } from '../utils/prop-config';
+
+export const interactivty: Config = {
+ pointerEvents: true,
+ userSelect: true,
+};
+
+export interface InteractivityProps {
+ pointerEvents?: ViewStyle['pointerEvents'];
+ userSelect?: TextStyle['userSelect'];
+}
diff --git a/packages/style-system/src/config/layout.ts b/packages/style-system/src/config/layout.ts
new file mode 100644
index 00000000..fa9e4d32
--- /dev/null
+++ b/packages/style-system/src/config/layout.ts
@@ -0,0 +1,31 @@
+import { ViewStyle } from 'react-native';
+
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const layout: Config = {
+ minH: t.sizes('minHeight'),
+ minW: t.sizes('minWidth'),
+ maxH: t.sizes('maxHeight'),
+ maxW: t.sizes('maxWidth'),
+ h: t.sizes('height'),
+ w: t.sizes('width'),
+ overflow: true,
+ display: true,
+ aspectRatio: true,
+};
+
+type Overflow = ViewStyle['overflow'];
+type Display = ViewStyle['display'];
+export interface LayoutProps {
+ minH?: ResponsiveValue;
+ minW?: ResponsiveValue;
+ maxH?: ResponsiveValue;
+ maxW?: ResponsiveValue;
+ h?: ResponsiveValue;
+ w?: ResponsiveValue;
+ overflow?: ResponsiveValue;
+ display?: ResponsiveValue;
+ aspectRatio?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/position.ts b/packages/style-system/src/config/position.ts
new file mode 100644
index 00000000..ef8e4d07
--- /dev/null
+++ b/packages/style-system/src/config/position.ts
@@ -0,0 +1,23 @@
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const position: Config = {
+ position: true,
+ top: t.space('top'),
+ right: t.space('top'),
+ bottom: t.space('top'),
+ left: t.space('top'),
+ zIndex: t.space('top'),
+ elevation: t.space('top'),
+};
+
+export interface PositionProps {
+ position?: ResponsiveValue<'absolute' | 'relative'>;
+ top?: ResponsiveValue;
+ right?: ResponsiveValue;
+ bottom?: ResponsiveValue;
+ left?: ResponsiveValue;
+ zIndex?: ResponsiveValue;
+ elevation?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/radius.ts b/packages/style-system/src/config/radius.ts
new file mode 100644
index 00000000..e1626263
--- /dev/null
+++ b/packages/style-system/src/config/radius.ts
@@ -0,0 +1,33 @@
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const radius: Config = {
+ borderRadius: t.radius('borderRadius'),
+ borderTopLeftRadius: t.radius('borderTopLeftRadius'),
+ borderTopRightRadius: t.radius('borderTopRightRadius'),
+ borderBottomLeftRadius: t.radius('borderBottomLeftRadius'),
+ borderBottomRightRadius: t.radius('borderBottomRightRadius'),
+ borderTopRadius: t.radius(['borderTopLeftRadius', 'borderTopRightRadius']),
+ borderLeftRadius: t.radius(['borderTopLeftRadius', 'borderBottomLeftRadius']),
+ borderRightRadius: t.radius([
+ 'borderTopRightRadius',
+ 'borderBottomRightRadius',
+ ]),
+ borderBottomRadius: t.radius([
+ 'borderBottomLeftRadius',
+ 'borderBottomRightRadius',
+ ]),
+};
+
+export interface RadiusProps {
+ borderRadius?: ResponsiveValue;
+ borderTopLeftRadius?: ResponsiveValue;
+ borderTopRightRadius?: ResponsiveValue;
+ borderBottomLeftRadius?: ResponsiveValue;
+ borderBottomRightRadius?: ResponsiveValue;
+ borderTopRadius?: ResponsiveValue;
+ borderLeftRadius?: ResponsiveValue;
+ borderRightRadius?: ResponsiveValue;
+ borderBottomRadius?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/space.ts b/packages/style-system/src/config/space.ts
new file mode 100644
index 00000000..cc8c7a63
--- /dev/null
+++ b/packages/style-system/src/config/space.ts
@@ -0,0 +1,47 @@
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { ResponsiveValue } from '../utils/types';
+
+export const space: Config = {
+ m: t.space('margin'),
+ mt: t.space('marginTop'),
+ mr: t.space('marginRight'),
+ mb: t.space('marginBottom'),
+ ml: t.space('marginLeft'),
+ mx: t.space('marginHorizontal'),
+ my: t.space('marginVertical'),
+ ms: t.space('marginStart'),
+ p: t.space('padding'),
+ pt: t.space('paddingTop'),
+ pr: t.space('paddingRight'),
+ pb: t.space('paddingBottom'),
+ pl: t.space('paddingLeft'),
+ px: t.space('paddingHorizontal'),
+ py: t.space('paddingVertical'),
+ ps: t.space('paddingStart'),
+ gap: t.space('gap'),
+ gapX: t.space('columnGap'),
+ gapY: t.space('rowGap'),
+};
+
+export interface SpaceProps {
+ m?: ResponsiveValue;
+ mt?: ResponsiveValue;
+ mr?: ResponsiveValue;
+ mb?: ResponsiveValue;
+ ml?: ResponsiveValue;
+ mx?: ResponsiveValue;
+ my?: ResponsiveValue;
+ ms?: ResponsiveValue;
+ p?: ResponsiveValue;
+ pt?: ResponsiveValue;
+ pr?: ResponsiveValue;
+ pb?: ResponsiveValue;
+ pl?: ResponsiveValue;
+ px?: ResponsiveValue;
+ py?: ResponsiveValue;
+ ps?: ResponsiveValue;
+ gap?: ResponsiveValue;
+ gapX?: ResponsiveValue;
+ gapY?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/config/text.ts b/packages/style-system/src/config/text.ts
new file mode 100644
index 00000000..d538f67a
--- /dev/null
+++ b/packages/style-system/src/config/text.ts
@@ -0,0 +1,55 @@
+import { TextStyle } from 'react-native';
+
+import { t } from '../utils';
+import { Config } from '../utils/prop-config';
+import { transforms } from '../utils/transform-functions';
+import { ResponsiveValue } from '../utils/types';
+
+export const text: Config = {
+ fontSize: t.prop('fontSize', 'fontSizes', transforms.getThemeProp),
+ textDecorLine: t.prop('textDecorationLine'),
+ textDecorStyle: t.prop('textDecorationStyle'),
+ fontStyle: true,
+ textDecorColor: t.colors('textDecorationColor'),
+ fontWeight: t.prop('fontWeight', 'fonts', transforms.getFontWeight, [
+ 'fontFamily',
+ ]),
+ fontFamily: t.prop('fontFamily', 'fonts', transforms.getThemeFontFamily, [
+ 'fontWeight',
+ ]),
+ lineHeight: true,
+ textAlign: true,
+ textTransform: true,
+ letterSpacing: true,
+ textAlignVertical: true,
+ textDecorationLine: true,
+ textDecorationStyle: true,
+ textDecorationColor: t.colors('textDecorationColor'),
+ textShadowColor: t.colors('shadowColor'),
+ textShadowOffset: t.shadows('textShadowOffset'),
+ textShadowRadius: t.radius('textShadowRadius'),
+};
+
+/**
+ * Only for React native Text Component
+ */
+export interface TextStyleProps {
+ fontSize?: ResponsiveValue;
+ textDecorLine?: ResponsiveValue;
+ textDecorStyle?: ResponsiveValue;
+ fontStyle?: ResponsiveValue;
+ textDecorColor?: ResponsiveValue;
+ fontWeight?: ResponsiveValue;
+ fontFamily?: ResponsiveValue<'heading' | 'mono' | (string & {})>;
+ lineHeight?: ResponsiveValue;
+ textAlign?: ResponsiveValue;
+ textTransform?: ResponsiveValue;
+ letterSpacing?: ResponsiveValue;
+ textAlignVertical?: ResponsiveValue;
+ textDecorationLine?: ResponsiveValue;
+ textDecorationStyle?: ResponsiveValue;
+ textDecorationColor?: ResponsiveValue;
+ textShadowColor?: ResponsiveValue;
+ textShadowOffset?: ResponsiveValue;
+ textShadowRadius?: ResponsiveValue;
+}
diff --git a/packages/style-system/src/define-style.ts b/packages/style-system/src/define-style.ts
new file mode 100644
index 00000000..7afbc0fb
--- /dev/null
+++ b/packages/style-system/src/define-style.ts
@@ -0,0 +1,56 @@
+import { SystemStyleObject } from './system.types';
+import { Dict } from './utils/types';
+
+export type StyleFunctionProps = {
+ colorScheme: string;
+ colorMode: 'light' | 'dark';
+ orientation?: 'horizontal' | 'vertical';
+ theme: Dict;
+ [key: string]: any;
+};
+
+export type SystemStyleFunction = (
+ props: StyleFunctionProps
+) => SystemStyleObject;
+
+export type SystemStyleInterpolation = SystemStyleObject | SystemStyleFunction;
+
+// ------------------------------------------------------------------ //
+
+export function defineStyle(styles: T) {
+ return styles;
+}
+
+// ------------------------------------------------------------------ //
+
+type DefaultProps = {
+ size?: string;
+ variant?: string;
+ colorScheme: string;
+};
+
+export type StyleConfig = {
+ baseStyle?: SystemStyleInterpolation;
+ sizes?: { [size: string]: SystemStyleInterpolation };
+ variants?: { [variant: string]: SystemStyleInterpolation };
+ defaultProps: DefaultProps;
+};
+
+export function defineStyleConfig<
+ BaseStyle extends SystemStyleInterpolation,
+ Sizes extends Dict,
+ Variants extends Dict,
+>(config: {
+ baseStyle?: BaseStyle;
+ sizes: Sizes;
+ variants: Variants;
+ defaultProps?: {
+ size?: keyof Sizes;
+ variant?: keyof Variants;
+ colorScheme?: string;
+ };
+}) {
+ return config;
+}
+
+// ------------------------------------------------------------------ //
diff --git a/packages/style-system/src/index.ts b/packages/style-system/src/index.ts
new file mode 100644
index 00000000..fa11ee69
--- /dev/null
+++ b/packages/style-system/src/index.ts
@@ -0,0 +1,8 @@
+export * from './system.types';
+export * from './system';
+export * from './theme.types';
+export * from './theming-props';
+export * from './define-style';
+export * from './style-sheet';
+export * from './utils';
+export * from './config';
diff --git a/packages/style-system/src/style-sheet.ts b/packages/style-system/src/style-sheet.ts
new file mode 100644
index 00000000..368b77ea
--- /dev/null
+++ b/packages/style-system/src/style-sheet.ts
@@ -0,0 +1,108 @@
+import { Dict, isObject, mergeWith, runIfFn } from '@chakra-ui/utils';
+
+import { systemProps } from './system';
+import { SystemStyleObject } from './system.types';
+import { expandResponsive } from './utils/expand-responsive';
+import { Config, PropConfig } from './utils/prop-config';
+
+interface GetStyleSheetOptions {
+ theme: { [key: string]: any };
+ configs?: Config;
+}
+
+/**
+ * This is the function that will be used to convert all the props to react native StyleSheet
+ */
+export function getStyleSheet({ configs = {}, theme }: GetStyleSheetOptions) {
+ const styleSheet = (stylesOrFn: Record) => {
+ const _styles = runIfFn(stylesOrFn, theme);
+
+ const styles = expandResponsive(_styles)(theme);
+
+ let computedStyles: Dict = {};
+
+ for (let key in styles) {
+ let value = styles[key];
+
+ let config = configs[key];
+
+ if (config === true) {
+ config = { property: key } as PropConfig;
+ }
+
+ // if (isObject(value)) {
+ // computedStyles[key] = computedStyles[key] ?? {};
+ // computedStyles[key] = mergeWith(
+ // {},
+ // computedStyles[key],
+ // styleSheet(value, true)
+ // );
+ // continue;
+ // }
+
+ /**
+ * Add the peer properties to help for computing the value
+ * e.g. fontWeight needs fontFamily
+ */
+ if (config?.peerProperties) {
+ const peerProperties = config.peerProperties;
+
+ for (let i in peerProperties) {
+ const peerProperty = peerProperties[i]!;
+ value = {
+ [key]: value, // Preserve original value
+ [peerProperty]: styles[peerProperty] ?? undefined,
+ };
+ }
+ }
+
+ /**
+ * The value of the property after transformation
+ * */
+ let rawValue =
+ config?.transform?.(
+ value,
+ config.scope ? theme[config.scope] : theme
+ ) ?? value;
+
+ /**
+ * The StyleSheet property or property array this prop style key maps to
+ */
+ const configProperty = config?.property;
+
+ if (configProperty && Array.isArray(configProperty)) {
+ for (const property of configProperty) {
+ computedStyles[property] = rawValue;
+ }
+ continue;
+ }
+
+ // We know configProperty is not an array
+ if (configProperty) {
+ computedStyles[configProperty as string] = rawValue;
+
+ continue;
+ }
+
+ if (isObject(rawValue)) {
+ computedStyles = mergeWith({}, computedStyles, rawValue);
+ continue;
+ }
+
+ computedStyles[key] = rawValue;
+ }
+
+ return computedStyles;
+ };
+
+ return styleSheet;
+}
+
+export const styleSheet = (styles: SystemStyleObject) => (theme: any) => {
+ const styleSheetFn = getStyleSheet({
+ theme,
+ configs: systemProps,
+ });
+
+ return styleSheetFn(styles);
+};
diff --git a/packages/style-system/src/system.ts b/packages/style-system/src/system.ts
new file mode 100644
index 00000000..a0faadf8
--- /dev/null
+++ b/packages/style-system/src/system.ts
@@ -0,0 +1,32 @@
+import { mergeWith } from '@chakra-ui/utils';
+
+import {
+ background,
+ border,
+ color,
+ effect,
+ flexbox,
+ interactivty,
+ layout,
+ position,
+ radius,
+ space,
+ text,
+} from './config';
+
+export const systemProps = mergeWith(
+ {},
+ background,
+ border,
+ color,
+ effect,
+ flexbox,
+ interactivty,
+ layout,
+ position,
+ radius,
+ space,
+ text
+);
+
+export const isStyleProp = (prop: string) => prop in systemProps;
diff --git a/packages/style-system/src/system.types.ts b/packages/style-system/src/system.types.ts
new file mode 100644
index 00000000..36aee5ab
--- /dev/null
+++ b/packages/style-system/src/system.types.ts
@@ -0,0 +1,70 @@
+import {
+ BackgroundProps,
+ BorderProps,
+ ColorProps,
+ EffectProps,
+ FlexboxProps,
+ InteractivityProps,
+ LayoutProps,
+ PositionProps,
+ RadiusProps,
+ SpaceProps,
+} from './config';
+import { RNStyleSheet, RNStyleSheetProperties } from './utils/prop-config';
+import { Dict, ResponsiveValue } from './utils/types';
+
+// Define the base interface for style props with generic ExtraProps that extends Config
+
+export interface StyleProps
+ extends BackgroundProps,
+ BorderProps,
+ ColorProps,
+ EffectProps,
+ FlexboxProps,
+ InteractivityProps,
+ LayoutProps,
+ PositionProps,
+ RadiusProps,
+ SpaceProps {}
+
+export interface SystemStyleSheetProperties
+ extends RNStyleSheet,
+ Omit {}
+
+export type ThemeThunk = T | ((theme: Record) => T);
+
+type PropertyValue = ThemeThunk<
+ ResponsiveValue
+>;
+
+export type StyleSheetWithMultiValues = {
+ [K in keyof SystemStyleSheetProperties]?: K extends keyof StyleProps
+ ? StyleProps[K] | PropertyValue
+ : PropertyValue;
+};
+
+type StyleSheetDefinition =
+ | D
+ | string
+ | RecursiveStyleSheetSelector;
+
+export interface RecursiveStyleSheetSelector {
+ [selector: string]: StyleSheetDefinition & D;
+}
+
+export type RecursiveStyleSheetObject = D &
+ (D | RecursiveStyleSheetSelector);
+
+export type SystemStyleObject =
+ RecursiveStyleSheetObject;
+
+/**
+ * We might need to extend SystemProps.
+ * For example for Text
+ */
+export type SystemProps = StyleProps & ExtraProps;
+
+/**
+ * Extensible style props
+ */
+export type { TextStyleProps } from './config';
diff --git a/packages/style-system/src/theme.types.ts b/packages/style-system/src/theme.types.ts
new file mode 100644
index 00000000..fd24799a
--- /dev/null
+++ b/packages/style-system/src/theme.types.ts
@@ -0,0 +1,37 @@
+const tokens = [
+ /** These are part of the theme */
+ 'breakpoints',
+ 'fonts',
+ 'fontSizes',
+ 'fontWeights',
+ 'colors',
+ 'radius',
+ 'shadows',
+ 'sizes',
+ 'space',
+ /** These are not part of the theme */
+ 'borderStyles',
+ 'borderWidths',
+ 'flexbox',
+] as const;
+
+export type ThemeScope = (typeof tokens)[number];
+
+export interface ThemeTypings {
+ breakpoints: string;
+ fonts: string;
+ fontSizes: string;
+ fontWeights: string;
+ colors: string;
+ colorSchemes: string;
+ radius: string;
+ sizes: string;
+ space: string;
+ shadows: string;
+ components: {
+ [componentName: string]: {
+ sizes: string;
+ variants: string;
+ };
+ };
+}
diff --git a/packages/style-system/src/theming-props.ts b/packages/style-system/src/theming-props.ts
new file mode 100644
index 00000000..d47c1f4e
--- /dev/null
+++ b/packages/style-system/src/theming-props.ts
@@ -0,0 +1,25 @@
+import { omit } from '@chakra-ui/utils';
+
+import { ThemeTypings } from './theme.types';
+import { Dict, ResponsiveValue } from './utils/types';
+
+export interface ThemingProps {
+ variant?: ResponsiveValue<
+ ResponsiveValue<
+ ThemeComponent extends keyof ThemeTypings['components']
+ ? ThemeTypings['components'][ThemeComponent]['variants']
+ : string
+ >
+ >;
+ size?: ResponsiveValue<
+ ThemeComponent extends keyof ThemeTypings['components']
+ ? ThemeTypings['components'][ThemeComponent]['sizes']
+ : string
+ >;
+ colorScheme?: ThemeTypings['colorSchemes'];
+ styleConfig?: Dict;
+}
+
+export function omitThemingProps(props: T) {
+ return omit(props, ['size', 'variant', 'colorScheme', 'styleConfig']);
+}
diff --git a/packages/style-system/src/utils/create-transform.ts b/packages/style-system/src/utils/create-transform.ts
new file mode 100644
index 00000000..a9380757
--- /dev/null
+++ b/packages/style-system/src/utils/create-transform.ts
@@ -0,0 +1,13 @@
+import { Transform } from './types';
+
+interface CreateTransformOptions {
+ transform?: Transform;
+}
+
+export function createTransform({
+ transform,
+}: CreateTransformOptions): Transform {
+ return (value, theme) => {
+ return transform?.(value, theme) ?? value;
+ };
+}
diff --git a/packages/style-system/src/utils/expand-responsive.ts b/packages/style-system/src/utils/expand-responsive.ts
new file mode 100644
index 00000000..1fd2cb09
--- /dev/null
+++ b/packages/style-system/src/utils/expand-responsive.ts
@@ -0,0 +1,70 @@
+import { isObject } from '@chakra-ui/utils';
+
+import { Dict } from './types';
+
+/**
+ * Resolve all the responsive property values as a single value depending on current device's windowWidth
+ */
+export const expandResponsive = (styles: Dict) => (theme: Dict) => {
+ const windowWidth = theme?.__windowWidth;
+ const computedStyles: Dict = {};
+
+ for (const key in styles) {
+ let value = styles[key];
+
+ computedStyles[key] = value;
+
+ if (value === null) {
+ continue;
+ }
+
+ /**
+ * For responsive values with `{base: 0, xs: 2}` format
+ */
+ if (isObject(value)) {
+ for (const breakpoint in value) {
+ if (
+ theme?.breakpoints[breakpoint] === undefined ||
+ theme?.breakpoints[breakpoint] === null
+ ) {
+ continue;
+ }
+
+ const currentBreakpointValue = theme?.breakpoints[breakpoint];
+
+ if (windowWidth >= currentBreakpointValue) {
+ computedStyles[key] = value[breakpoint];
+ }
+ }
+ continue;
+ }
+
+ /**
+ * Breakpoints ordered by ascending size
+ */
+ const breakpoints = theme?.__breakpoints;
+
+ /**
+ * For responsive values with `[0, 2]` format
+ */
+ if (Array.isArray(value)) {
+ // Defaults to the first value of the array
+ let matchedValue = value[0];
+
+ for (let i = 0; i < breakpoints.length; i++) {
+ const [, minWidth] = breakpoints[i]!;
+
+ if (windowWidth >= minWidth) {
+ matchedValue =
+ value[i] !== undefined && value[i] !== null
+ ? value[i]
+ : matchedValue;
+ }
+ }
+
+ computedStyles[key] = matchedValue;
+ }
+ }
+
+ return computedStyles;
+};
diff --git a/packages/style-system/src/utils/index.ts b/packages/style-system/src/utils/index.ts
new file mode 100644
index 00000000..3e469513
--- /dev/null
+++ b/packages/style-system/src/utils/index.ts
@@ -0,0 +1,31 @@
+import { ThemeScope } from '../theme.types';
+import { createTransform } from './create-transform';
+import { PropConfig, toConfig } from './prop-config';
+import { transforms } from './transform-functions';
+
+export const t = {
+ colors: toConfig('colors', transforms.getThemeColor),
+ borderWidths: toConfig('borderWidths'),
+ borderStyles: toConfig('borderStyles'),
+ radius: toConfig('radius', transforms.getThemeProp),
+ space: toConfig('space', transforms.getThemeProp),
+ shadows: toConfig('shadows', transforms.getThemeProp),
+ sizes: toConfig('sizes', transforms.getThemeProp),
+ flexbox: toConfig('flexbox'),
+ prop(
+ property: PropConfig['property'],
+ scope?: ThemeScope,
+ transform?: PropConfig['transform'],
+ peerProperties?: PropConfig['peerProperties']
+ ) {
+ return {
+ property,
+ scope,
+ transform: createTransform({ transform }),
+ peerProperties,
+ };
+ },
+};
+
+export * from './prop-config';
+export * from './types';
diff --git a/packages/style-system/src/utils/prop-config.ts b/packages/style-system/src/utils/prop-config.ts
new file mode 100644
index 00000000..d7e3e3da
--- /dev/null
+++ b/packages/style-system/src/utils/prop-config.ts
@@ -0,0 +1,49 @@
+import { FlexStyle, ImageStyle, TextStyle, ViewStyle } from 'react-native';
+
+import { ThemeScope } from '../theme.types';
+import { createTransform } from './create-transform';
+import { Transform } from './types';
+
+export type RNStyleSheet = TextStyle & ViewStyle & FlexStyle & ImageStyle;
+
+export type RNStyleSheetProperties = keyof RNStyleSheet;
+
+type MaybeArray = T | T[];
+
+export interface PropConfig {
+ /**
+ * The theme scope this maps to
+ */
+ scope?: ThemeScope;
+ /**
+ * StyleSheet or React Native prop
+ * This can be undefined in case the property maps to a theme aware style that includes several properties.
+ * E.g. `shadow` prop which maps to the theme scope `shadows`
+ */
+ property?: MaybeArray;
+ /**
+ * Function to transform the value passed
+ */
+ transform?: Transform;
+ /**
+ * Array of properties that may be needed to transform the current prop
+ * e.g. fontWeight needs fontFamily
+ */
+ peerProperties?: string[];
+}
+
+export type Config = Record<
+ T extends Record ? keyof T : string,
+ PropConfig | true // True if ficus prop and style prop have same name and do not need transformation
+>;
+
+export function toConfig(scope: ThemeScope, transform?: Transform) {
+ return (property?: T | T[]) => {
+ const result: PropConfig = { property, scope };
+ result.transform = createTransform({
+ transform,
+ });
+
+ return result;
+ };
+}
diff --git a/packages/style-system/src/utils/transform-functions.ts b/packages/style-system/src/utils/transform-functions.ts
new file mode 100644
index 00000000..a2d30339
--- /dev/null
+++ b/packages/style-system/src/utils/transform-functions.ts
@@ -0,0 +1,98 @@
+import { Dict } from './types';
+
+// NOTE: This function are taken from Magnus and could probably be improved
+export const transforms = {
+ getThemeProp(value: any, themeScope: Dict | undefined) {
+ if (themeScope?.[value]) {
+ return themeScope[value];
+ }
+
+ return value;
+ },
+ getFontWeight(
+ value: {
+ fontWeight: string | number;
+ fontFamily?: string;
+ },
+ themeScope: Dict
+ ) {
+ const fontFamilyKey = value.fontFamily ?? '';
+ const fontFamily = themeScope.fontFamily?.[fontFamilyKey];
+
+ if (fontFamily) {
+ return 'normal';
+ }
+
+ const resolvedFontWeight = resolveFontWeight(value.fontWeight, themeScope);
+
+ return resolvedFontWeight;
+ },
+ getThemeFontFamily(
+ value: {
+ fontFamily: string;
+ fontWeight?: string | number;
+ },
+ themeScope: Dict
+ ): string | undefined {
+ const rawFontWeight = value.fontWeight;
+ const rawFontFamily = value.fontFamily;
+
+ const fontFamily = themeScope.fontFamily?.[rawFontFamily] ?? {};
+
+ const resolvedFontWeight = resolveFontWeight(rawFontWeight, themeScope);
+
+ return fontFamily[resolvedFontWeight] ?? rawFontFamily;
+ },
+ getThemeColor(value: any, theme: Dict | undefined) {
+ let colorValueResult: string | String = value as string;
+
+ if (theme && value) {
+ // Check if color value is a valid theme color
+ if (
+ theme.hasOwnProperty(value) &&
+ (typeof theme[value] === 'string' || theme[value] instanceof String)
+ ) {
+ const colorValue: string | String = theme[value] as string;
+ return colorValue as string;
+ }
+
+ // If color value contains dots, check into theme sub objects if it's a valid theme color
+ if (value.includes('.')) {
+ const keyParts = value.split('.');
+ let subPropertyValue: any = theme;
+ for (const part of keyParts) {
+ if (subPropertyValue && part) {
+ subPropertyValue = subPropertyValue[part];
+ }
+ }
+ if (
+ typeof subPropertyValue === 'string' ||
+ subPropertyValue instanceof String
+ ) {
+ colorValueResult = subPropertyValue;
+ }
+ }
+ }
+
+ return isValidColor(colorValueResult as string)
+ ? (colorValueResult as string)
+ : 'transparent';
+ },
+};
+
+export const isValidColor = (color: string): boolean => {
+ // TODO
+ return typeof color === 'string';
+};
+
+const resolveFontWeight = (
+ fontWeight: string | number = 'normal',
+ themeScope: Dict
+) => {
+ return (
+ // If font weight is a keyword (`light `, `normal`, ...) map it to a number (300, 400)
+ themeScope.fontWeights[fontWeight] ??
+ // If font weight is already number (300, 400)
+ fontWeight
+ );
+};
diff --git a/packages/style-system/src/utils/types.ts b/packages/style-system/src/utils/types.ts
new file mode 100644
index 00000000..b29ad0a6
--- /dev/null
+++ b/packages/style-system/src/utils/types.ts
@@ -0,0 +1,5 @@
+export type ResponsiveValue = T | Record | T[];
+
+export type Dict = { [key: string]: T };
+
+export type Transform = (value: any, themeScope: { [key: string]: any }) => any;
diff --git a/packages/style-system/tsconfig.json b/packages/style-system/tsconfig.json
new file mode 100644
index 00000000..4445f0d4
--- /dev/null
+++ b/packages/style-system/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": "./dist/types"
+ }
+}
diff --git a/packages/theme/package.json b/packages/theme/package.json
new file mode 100644
index 00000000..af2caa02
--- /dev/null
+++ b/packages/theme/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@ficus-ui/theme",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "tsc --watch",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@ficus-ui/style-system": "workspace:*",
+ "deepmerge": "4.2.2"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "types": "dist/types/src/index.d.ts",
+ "main": "dist/types/src/index.js",
+ "module": "dist/types/src/index.js",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/esm/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/cjs/index.cjs"
+ }
+ },
+ "./config": {
+ "import": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/esm/config/index.mjs"
+ },
+ "require": {
+ "types": "./dist/types/config/index.d.ts",
+ "default": "./dist/cjs/config/index.cjs"
+ }
+ }
+ }
+}
diff --git a/packages/theme/src/components/badge.ts b/packages/theme/src/components/badge.ts
new file mode 100644
index 00000000..a1dd6591
--- /dev/null
+++ b/packages/theme/src/components/badge.ts
@@ -0,0 +1 @@
+export const badgeTheme = {};
diff --git a/packages/theme/src/components/index.ts b/packages/theme/src/components/index.ts
new file mode 100644
index 00000000..67615dd8
--- /dev/null
+++ b/packages/theme/src/components/index.ts
@@ -0,0 +1,7 @@
+import { badgeTheme } from './badge';
+
+export { badgeTheme as Badge } from './badge';
+
+export const components = {
+ Badge: badgeTheme,
+};
diff --git a/packages/theme/src/context.ts b/packages/theme/src/context.ts
new file mode 100644
index 00000000..66b4f867
--- /dev/null
+++ b/packages/theme/src/context.ts
@@ -0,0 +1,25 @@
+import { createContext } from 'react';
+
+import { theme } from './theme.default';
+import { FicusTheme } from './theme.types';
+
+export interface FicusThemeWithMetadata extends FicusTheme {
+ /**
+ * Current device window width. Useful for responsive layouts
+ */
+ __windowWidth?: number;
+ /**
+ * Breakpoints from the theme ordered by ascending size
+ */
+ __breakpoints?: [string, number][];
+}
+
+export interface ThemeContext {
+ theme: FicusThemeWithMetadata;
+ setTheme: (theme: FicusThemeWithMetadata) => void;
+}
+
+export const ThemeContext = createContext({
+ theme: Object.assign(theme, { __windowWidth: 0 }),
+ setTheme: (_theme: FicusThemeWithMetadata) => {},
+});
diff --git a/packages/theme/src/foundations/breakpoints.ts b/packages/theme/src/foundations/breakpoints.ts
new file mode 100644
index 00000000..8ccea1a8
--- /dev/null
+++ b/packages/theme/src/foundations/breakpoints.ts
@@ -0,0 +1,9 @@
+const breakpoints = {
+ base: 0,
+ sm: 480,
+ md: 768,
+ lg: 992,
+ xl: 1280,
+};
+
+export default breakpoints;
diff --git a/packages/theme/src/foundations/colors.ts b/packages/theme/src/foundations/colors.ts
new file mode 100644
index 00000000..c831c259
--- /dev/null
+++ b/packages/theme/src/foundations/colors.ts
@@ -0,0 +1,164 @@
+const colors = {
+ transparent: 'transparent',
+ current: 'currentColor',
+ black: '#000000',
+ white: '#FFFFFF',
+
+ whiteAlpha: {
+ 50: 'rgba(255, 255, 255, 0.04)',
+ 100: 'rgba(255, 255, 255, 0.06)',
+ 200: 'rgba(255, 255, 255, 0.08)',
+ 300: 'rgba(255, 255, 255, 0.16)',
+ 400: 'rgba(255, 255, 255, 0.24)',
+ 500: 'rgba(255, 255, 255, 0.36)',
+ 600: 'rgba(255, 255, 255, 0.48)',
+ 700: 'rgba(255, 255, 255, 0.64)',
+ 800: 'rgba(255, 255, 255, 0.80)',
+ 900: 'rgba(255, 255, 255, 0.92)',
+ },
+
+ blackAlpha: {
+ 50: 'rgba(0, 0, 0, 0.04)',
+ 100: 'rgba(0, 0, 0, 0.06)',
+ 200: 'rgba(0, 0, 0, 0.08)',
+ 300: 'rgba(0, 0, 0, 0.16)',
+ 400: 'rgba(0, 0, 0, 0.24)',
+ 500: 'rgba(0, 0, 0, 0.36)',
+ 600: 'rgba(0, 0, 0, 0.48)',
+ 700: 'rgba(0, 0, 0, 0.64)',
+ 800: 'rgba(0, 0, 0, 0.80)',
+ 900: 'rgba(0, 0, 0, 0.92)',
+ },
+
+ gray: {
+ 50: '#F7FAFC',
+ 100: '#EDF2F7',
+ 200: '#E2E8F0',
+ 300: '#CBD5E0',
+ 400: '#A0AEC0',
+ 500: '#718096',
+ 600: '#4A5568',
+ 700: '#2D3748',
+ 800: '#1A202C',
+ 900: '#171923',
+ },
+
+ red: {
+ 50: '#FFF5F5',
+ 100: '#FED7D7',
+ 200: '#FEB2B2',
+ 300: '#FC8181',
+ 400: '#F56565',
+ 500: '#E53E3E',
+ 600: '#C53030',
+ 700: '#9B2C2C',
+ 800: '#822727',
+ 900: '#63171B',
+ },
+
+ orange: {
+ 50: '#FFFAF0',
+ 100: '#FEEBC8',
+ 200: '#FBD38D',
+ 300: '#F6AD55',
+ 400: '#ED8936',
+ 500: '#DD6B20',
+ 600: '#C05621',
+ 700: '#9C4221',
+ 800: '#7B341E',
+ 900: '#652B19',
+ },
+
+ yellow: {
+ 50: '#FFFFF0',
+ 100: '#FEFCBF',
+ 200: '#FAF089',
+ 300: '#F6E05E',
+ 400: '#ECC94B',
+ 500: '#D69E2E',
+ 600: '#B7791F',
+ 700: '#975A16',
+ 800: '#744210',
+ 900: '#5F370E',
+ },
+
+ green: {
+ 50: '#F0FFF4',
+ 100: '#C6F6D5',
+ 200: '#9AE6B4',
+ 300: '#68D391',
+ 400: '#48BB78',
+ 500: '#38A169',
+ 600: '#2F855A',
+ 700: '#276749',
+ 800: '#22543D',
+ 900: '#1C4532',
+ },
+
+ teal: {
+ 50: '#E6FFFA',
+ 100: '#B2F5EA',
+ 200: '#81E6D9',
+ 300: '#4FD1C5',
+ 400: '#38B2AC',
+ 500: '#319795',
+ 600: '#2C7A7B',
+ 700: '#285E61',
+ 800: '#234E52',
+ 900: '#1D4044',
+ },
+
+ blue: {
+ 50: '#ebf8ff',
+ 100: '#bee3f8',
+ 200: '#90cdf4',
+ 300: '#63b3ed',
+ 400: '#4299e1',
+ 500: '#3182ce',
+ 600: '#2b6cb0',
+ 700: '#2c5282',
+ 800: '#2a4365',
+ 900: '#1A365D',
+ },
+
+ cyan: {
+ 50: '#EDFDFD',
+ 100: '#C4F1F9',
+ 200: '#9DECF9',
+ 300: '#76E4F7',
+ 400: '#0BC5EA',
+ 500: '#00B5D8',
+ 600: '#00A3C4',
+ 700: '#0987A0',
+ 800: '#086F83',
+ 900: '#065666',
+ },
+
+ purple: {
+ 50: '#FAF5FF',
+ 100: '#E9D8FD',
+ 200: '#D6BCFA',
+ 300: '#B794F4',
+ 400: '#9F7AEA',
+ 500: '#805AD5',
+ 600: '#6B46C1',
+ 700: '#553C9A',
+ 800: '#44337A',
+ 900: '#322659',
+ },
+
+ pink: {
+ 50: '#FFF5F7',
+ 100: '#FED7E2',
+ 200: '#FBB6CE',
+ 300: '#F687B3',
+ 400: '#ED64A6',
+ 500: '#D53F8C',
+ 600: '#B83280',
+ 700: '#97266D',
+ 800: '#702459',
+ 900: '#521B41',
+ },
+};
+
+export default colors;
diff --git a/packages/theme/src/foundations/index.ts b/packages/theme/src/foundations/index.ts
new file mode 100644
index 00000000..1c0baf6f
--- /dev/null
+++ b/packages/theme/src/foundations/index.ts
@@ -0,0 +1,15 @@
+import breakpoints from './breakpoints';
+import colors from './colors';
+import radius from './radius';
+import shadows from './shadows';
+import space from './space';
+import typography from './typography';
+
+export const foundations = {
+ breakpoints,
+ colors,
+ radius,
+ shadows,
+ space,
+ ...typography,
+};
diff --git a/packages/theme/src/foundations/radius.ts b/packages/theme/src/foundations/radius.ts
new file mode 100644
index 00000000..db05ba9b
--- /dev/null
+++ b/packages/theme/src/foundations/radius.ts
@@ -0,0 +1,12 @@
+const radius = {
+ none: 0,
+ xs: 2,
+ sm: 4,
+ md: 6,
+ lg: 8,
+ xl: 12,
+ '2xl': 16,
+ full: 99999,
+};
+
+export default radius;
diff --git a/packages/theme/src/foundations/shadows.ts b/packages/theme/src/foundations/shadows.ts
new file mode 100644
index 00000000..00f9a5f3
--- /dev/null
+++ b/packages/theme/src/foundations/shadows.ts
@@ -0,0 +1,71 @@
+const shadows = {
+ none: {},
+ xs: {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 1,
+ },
+ shadowOpacity: 0.18,
+ shadowRadius: 1.0,
+
+ elevation: 1,
+ },
+ sm: {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.23,
+ shadowRadius: 2.62,
+
+ elevation: 4,
+ },
+ md: {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 4,
+ },
+ shadowOpacity: 0.3,
+ shadowRadius: 4.65,
+
+ elevation: 8,
+ },
+ lg: {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 6,
+ },
+ shadowOpacity: 0.37,
+ shadowRadius: 7.49,
+
+ elevation: 12,
+ },
+ xl: {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 8,
+ },
+ shadowOpacity: 0.44,
+ shadowRadius: 10.32,
+
+ elevation: 16,
+ },
+ '2xl': {
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 10,
+ },
+ shadowOpacity: 0.51,
+ shadowRadius: 13.16,
+
+ elevation: 20,
+ },
+};
+
+export default shadows;
diff --git a/packages/theme/src/foundations/space.ts b/packages/theme/src/foundations/space.ts
new file mode 100644
index 00000000..13e7cead
--- /dev/null
+++ b/packages/theme/src/foundations/space.ts
@@ -0,0 +1,19 @@
+const space = {
+ none: 0,
+ xs: 4,
+ sm: 6,
+ md: 8,
+ lg: 12,
+ xl: 24,
+ '2xl': 32,
+ '3xl': 64,
+ '-xs': -4,
+ '-sm': -6,
+ '-md': -8,
+ '-lg': -12,
+ '-xl': -24,
+ '-2xl': -32,
+ '-3xl': -64,
+};
+
+export default space;
diff --git a/packages/theme/src/foundations/typography.ts b/packages/theme/src/foundations/typography.ts
new file mode 100644
index 00000000..bc651d40
--- /dev/null
+++ b/packages/theme/src/foundations/typography.ts
@@ -0,0 +1,31 @@
+const typography = {
+ fontSizes: {
+ xs: 11,
+ sm: 12,
+ md: 13,
+ lg: 15,
+ xl: 17,
+ '2xl': 19,
+ '3xl': 21,
+ '4xl': 24,
+ '5xl': 27,
+ '6xl': 32,
+ },
+
+ fonts: {
+ fontWeights: {
+ hairline: 100,
+ thin: 200,
+ light: 300,
+ normal: 400,
+ medium: 500,
+ semibold: 600,
+ bold: 700,
+ extrabold: 800,
+ black: 900,
+ },
+ fontFamily: {},
+ },
+};
+
+export default typography;
diff --git a/packages/theme/src/hook.ts b/packages/theme/src/hook.ts
new file mode 100644
index 00000000..fc322ee2
--- /dev/null
+++ b/packages/theme/src/hook.ts
@@ -0,0 +1,13 @@
+import { useContext } from 'react';
+
+import { ThemeContext } from './context';
+
+export const useTheme = () => {
+ const themeContext = useContext(ThemeContext);
+
+ if (!themeContext) {
+ throw new Error('useTheme must be used within ficus ThemeProvider');
+ }
+
+ return themeContext;
+};
diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts
new file mode 100644
index 00000000..351bbf91
--- /dev/null
+++ b/packages/theme/src/index.ts
@@ -0,0 +1,3 @@
+export { theme, type Theme } from './theme.default';
+export { ThemeProvider } from './provider';
+export * from './hook';
diff --git a/packages/theme/src/provider.tsx b/packages/theme/src/provider.tsx
new file mode 100644
index 00000000..cdd9fac4
--- /dev/null
+++ b/packages/theme/src/provider.tsx
@@ -0,0 +1,48 @@
+import { FC, ReactNode, useMemo, useState } from 'react';
+
+import deepmerge from 'deepmerge';
+import { useWindowDimensions } from 'react-native';
+
+import { FicusThemeWithMetadata, ThemeContext } from './context';
+import { theme } from './theme.default';
+
+export interface ThemeProviderProps {
+ theme?: FicusThemeWithMetadata;
+ children: ReactNode;
+}
+
+export const ThemeProvider: FC = (props) => {
+ const { theme: customTheme = {}, children } = props;
+
+ // Get Metadata
+ const { width: windowWidth } = useWindowDimensions();
+ const breakpoints = theme?.breakpoints
+ ? Object.entries(theme.breakpoints).sort((a, b) => a[1] - b[1])
+ : [];
+
+ // Add metadata to the theme
+ const themeWithMetadata: FicusThemeWithMetadata = Object.assign(theme, {
+ __windowWidth: windowWidth,
+ __breakpoints: breakpoints,
+ });
+
+ const [themeState, setThemeState] = useState(
+ deepmerge(themeWithMetadata, customTheme)
+ );
+
+ const setTheme = (newTheme: FicusThemeWithMetadata) => {
+ const mergedTheme = deepmerge(themeWithMetadata, newTheme);
+ setThemeState(mergedTheme);
+ };
+
+ const contextValue = useMemo(
+ () => ({ theme: themeState, setTheme }),
+ [themeWithMetadata]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/theme/src/theme.default.ts b/packages/theme/src/theme.default.ts
new file mode 100644
index 00000000..a2e3935d
--- /dev/null
+++ b/packages/theme/src/theme.default.ts
@@ -0,0 +1,9 @@
+import { components } from './components';
+import { foundations } from './foundations';
+
+export const theme = {
+ components,
+ ...foundations,
+};
+
+export type Theme = typeof theme;
diff --git a/packages/theme/src/theme.types.ts b/packages/theme/src/theme.types.ts
new file mode 100644
index 00000000..5d12f3ef
--- /dev/null
+++ b/packages/theme/src/theme.types.ts
@@ -0,0 +1,62 @@
+import type {
+ SystemStyleInterpolation,
+ ThemingProps,
+} from '@ficus-ui/style-system';
+
+type Dict = Record;
+
+export type RecursiveProperty = RecursiveObject | T;
+
+export interface RecursiveObject {
+ [property: string]: RecursiveProperty;
+}
+
+export interface ColorHues {
+ 50: string;
+ 100: string;
+ 200: string;
+ 300: string;
+ 400: string;
+ 500: string;
+ 600: string;
+ 700: string;
+ 800: string;
+ 900: string;
+ 950: string;
+}
+
+export type Colors = RecursiveObject> | string>;
+
+export interface ComponentDefaultProps extends ThemingProps, Dict {}
+
+export interface ComponentStyleConfig {
+ baseStyle?: SystemStyleInterpolation;
+ sizes?: Dict;
+ variants?: Dict;
+ defaultProps?: any;
+}
+
+export interface ThemeComponents {
+ [componentName: string]: ComponentStyleConfig;
+}
+
+interface Typography {
+ fontSizes?: RecursiveObject;
+ // FIXME
+ fonts?: {
+ fontWeights?: RecursiveObject;
+ } & RecursiveObject;
+}
+
+interface Foundations extends Typography {
+ borders?: RecursiveObject;
+ breakpoints?: Dict;
+ colors?: Colors;
+ radius?: RecursiveObject;
+ shadows?: RecursiveObject;
+ space?: RecursiveObject;
+}
+
+export interface FicusTheme extends Foundations {
+ components?: ThemeComponents;
+}
diff --git a/packages/theme/tsconfig.json b/packages/theme/tsconfig.json
new file mode 100644
index 00000000..4445f0d4
--- /dev/null
+++ b/packages/theme/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": "./dist/types"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e062480f..7bd0a258 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -181,6 +181,9 @@ importers:
'@expo/vector-icons':
specifier: 14.0.2
version: 14.0.2
+ '@ficus-ui/native':
+ specifier: workspace:*
+ version: link:../../packages/components
'@react-native-community/slider':
specifier: 4.5.4
version: 4.5.4
@@ -291,6 +294,18 @@ importers:
specifier: 5.3.3
version: 5.3.3
+ packages/components:
+ dependencies:
+ '@chakra-ui/utils':
+ specifier: 2.2.2
+ version: 2.2.2(react@18.3.1)
+ '@ficus-ui/style-system':
+ specifier: workspace:*
+ version: link:../style-system
+ '@ficus-ui/theme':
+ specifier: workspace:*
+ version: link:../theme
+
packages/react-native-ficus-ui:
dependencies:
color:
@@ -400,6 +415,21 @@ importers:
specifier: 5.1.6
version: 5.1.6
+ packages/style-system:
+ dependencies:
+ '@chakra-ui/utils':
+ specifier: 2.2.2
+ version: 2.2.2(react@18.3.1)
+
+ packages/theme:
+ dependencies:
+ '@ficus-ui/style-system':
+ specifier: workspace:*
+ version: link:../style-system
+ deepmerge:
+ specifier: 4.2.2
+ version: 4.2.2
+
packages:
'@alloc/quick-lru@5.2.0':
@@ -1188,6 +1218,11 @@ packages:
'@braintree/sanitize-url@6.0.4':
resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
+ '@chakra-ui/utils@2.2.2':
+ resolution: {integrity: sha512-jUPLT0JzRMWxpdzH6c+t0YMJYrvc5CLericgITV3zDSXblkfx3DsYXqU11DJTSGZI9dUKzM1Wd0Wswn4eJwvFQ==}
+ peerDependencies:
+ react: 18.3.1
+
'@commitlint/cli@17.8.1':
resolution: {integrity: sha512-ay+WbzQesE0Rv4EQKfNbSMiJJ12KdKTDzIt0tcK4k11FdsWmtwP0Kp1NWMOUswfIWo6Eb7p7Ln721Nx9FLNBjg==}
engines: {node: '>=v14'}
@@ -2245,6 +2280,12 @@ packages:
'@types/keyv@3.1.4':
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
+ '@types/lodash.mergewith@4.6.9':
+ resolution: {integrity: sha512-fgkoCAOF47K7sxrQ7Mlud2TH023itugZs2bUg8h/KzT+BnZNrR2jAOmaokbLunHNnobXVWOezAeNn/lZqwxkcw==}
+
+ '@types/lodash@4.17.15':
+ resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
+
'@types/mdast@3.0.15':
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
@@ -9969,6 +10010,12 @@ snapshots:
'@braintree/sanitize-url@6.0.4': {}
+ '@chakra-ui/utils@2.2.2(react@18.3.1)':
+ dependencies:
+ '@types/lodash.mergewith': 4.6.9
+ lodash.mergewith: 4.6.2
+ react: 18.3.1
+
'@commitlint/cli@17.8.1':
dependencies:
'@commitlint/format': 17.8.1
@@ -10031,7 +10078,7 @@ snapshots:
'@types/node': 20.5.1
chalk: 4.1.2
cosmiconfig: 8.3.6(typescript@5.3.3)
- cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.3.3))(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.1.6))(typescript@5.3.3)
+ cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.1.6))(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.1.6))(typescript@5.3.3)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -11806,6 +11853,12 @@ snapshots:
dependencies:
'@types/node': 22.13.1
+ '@types/lodash.mergewith@4.6.9':
+ dependencies:
+ '@types/lodash': 4.17.15
+
+ '@types/lodash@4.17.15': {}
+
'@types/mdast@3.0.15':
dependencies:
'@types/unist': 2.0.11
@@ -13210,7 +13263,7 @@ snapshots:
dependencies:
layout-base: 1.0.2
- cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.3.3))(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.1.6))(typescript@5.3.3):
+ cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.1.6))(ts-node@10.9.2(@types/node@22.13.1)(typescript@5.1.6))(typescript@5.3.3):
dependencies:
'@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@5.3.3)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 49987984..9ac09c98 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,3 @@
packages:
- - "packages/*"
- - "apps/*"
\ No newline at end of file
+ - "packages/**/**"
+ - "apps/**"
diff --git a/tsconfig.json b/tsconfig.json
index 5fea11b5..d08d05d4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -28,5 +28,5 @@
"types": ["node", "jest"]
},
"include": ["packages", "__tests__"],
- "exclude": ["node_modules", "apps"]
+ "exclude": ["node_modules", "apps", "dist"]
}