).props.children)
11 | );
12 | }
13 | flatChildren.push(child);
14 | return flatChildren;
15 | }, []);
16 | }
17 |
--------------------------------------------------------------------------------
/example/src/styles/helpers.ts:
--------------------------------------------------------------------------------
1 | import { theme, Theme } from './styled';
2 |
3 | type ThemeKey = keyof Theme;
4 |
5 | export function themeProp(
6 | prop: P,
7 | themeKey: T,
8 | getStyles: (token: string) => any
9 | ) {
10 | return Object.values(theme[themeKey]).reduce(
11 | (acc, { token }) => {
12 | acc[prop][token] = getStyles(`$${token}`);
13 | return acc;
14 | },
15 | { [prop]: {} }
16 | ) as {
17 | [prop in P]: { [token in keyof Theme[T]]: any }; // TODO: fix any
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/example/src/styles/index.ts:
--------------------------------------------------------------------------------
1 | import * as stitches from './styled';
2 |
3 | const { styled, css, createTheme, useTheme, theme, darkTheme, ThemeProvider } = stitches; // prettier-ignore
4 |
5 | export { Theme } from './styled';
6 | export { themeProp } from './helpers';
7 | export { styled, css, createTheme, useTheme, theme, darkTheme, ThemeProvider };
8 |
--------------------------------------------------------------------------------
/example/src/styles/styled.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { getDeviceTypeAsync, DeviceType } from 'expo-device';
3 | import { createStitches } from 'stitches-native';
4 | import type * as Stitches from 'stitches-native';
5 |
6 | import {
7 | size,
8 | shadow,
9 | typography,
10 | flexCenter,
11 | absoluteFill,
12 | remFunction,
13 | } from './utils';
14 |
15 | const media = {
16 | // You can provide boolean values for breakpoints when you just need to
17 | // distinguish between phone and tablet devices
18 | phone: true,
19 | tablet: false,
20 |
21 | // If you are not using Expo you should use react-native-device-info
22 | // to get the device type synchronously
23 | /*
24 | phone: true, // !DeviceInfo.isTablet()
25 | tablet: false, // DeviceInfo.isTablet()
26 | */
27 |
28 | // You can also define min width based media queries that overlap each other
29 | // which is a commonly used technique in web development
30 | // NOTE: make sure the keys are ordered from smallest to largest screen size!
31 | md: '(width >= 750px)',
32 | lg: '(width >= 1080px)',
33 | xl: '(width >= 1284px)',
34 | xxl: '(width >= 1536px)',
35 |
36 | // It's also possible to specify ranges that don't overlap if you want to be
37 | // very precise with your media queries and don't prefer the min width based approach
38 | /*
39 | sm: '(width <= 750px)', // Small phone, eg. iPhone SE
40 | md: '(750px < width <= 1080px)', // Regular phone, eg. iPhone 6/7/8 Plus
41 | lg: '(1080px < width <= 1284px)', // Large phone, eg. iPhone 12 Pro Max
42 | xl: '(1284px < width <= 1536px)', // Regular tablet, eg. iPad Pro 9.7
43 | xxl: '(width > 1536px)', // Large tablet
44 | */
45 | };
46 |
47 | // This is a bit hacky but Expo doesn't have a sync way to get the device type
48 | getDeviceTypeAsync().then((deviceType) => {
49 | media.phone = deviceType === DeviceType.PHONE;
50 | media.tablet = deviceType === DeviceType.TABLET;
51 | });
52 |
53 | const { styled, css, createTheme, config, theme, useTheme, ThemeProvider } =
54 | createStitches({
55 | theme: {
56 | colors: {
57 | // Main palette (these should not be used directly but via aliases instead)
58 | blue100: '#ab9cf7',
59 | blue500: '#301b96',
60 | blue900: '#0D0630',
61 | green100: '#d9fff6',
62 | green500: '#8BBEB2',
63 | green900: '#384d48',
64 | black: '#000000',
65 | white: '#ffffff',
66 | gray50: '#f2f2f7',
67 | gray100: '#e5e5ea',
68 | gray200: '#d1d1d6',
69 | gray300: '#c7c7cc',
70 | gray400: '#aeaeb2',
71 | gray500: '#8e8e93',
72 | gray600: '#636366',
73 | gray700: '#48484a',
74 | gray800: '#3a3a3c',
75 | gray850: '#2c2c2e',
76 | gray900: '#1d1d1f',
77 |
78 | // Brand colors
79 | primary: '$blue500',
80 | primaryText: '$blue900',
81 | primaryMuted: '$blue100',
82 | secondary: '$green500',
83 | secondaryText: '$green900',
84 | secondaryMuted: '$green100',
85 |
86 | // Informative colors
87 | info: '#3B82F6',
88 | infoText: '#0A45A6',
89 | infoMuted: '#cfdef7',
90 | success: '#10B981',
91 | successText: '#06734E',
92 | successMuted: '#cee8df',
93 | warn: '#FBBF24',
94 | warnText: '#8a6200',
95 | warnMuted: '#f3ead1',
96 | error: '#EF4444',
97 | errorText: '#8C0606',
98 | errorMuted: '#f3d2d3',
99 |
100 | // General colors
101 | text: '$black',
102 | textInverted: '$white',
103 | border: 'rgba(150, 150, 150, 0.3)',
104 | backdrop: 'rgba(0,0,0,0.5)',
105 | background: '$white',
106 | surface: '$white',
107 | elevated: '$white',
108 | muted1: '$gray500',
109 | muted2: '$gray400',
110 | muted3: '$gray300',
111 | muted4: '$gray200',
112 | muted5: '$gray100',
113 | muted6: '$gray50',
114 | },
115 | fontWeights: {
116 | bold: '700',
117 | semibold: '500',
118 | normal: '400',
119 | },
120 | borderStyles: {
121 | solid: 'solid',
122 | },
123 | borderWidths: {
124 | thin: StyleSheet.hairlineWidth,
125 | normal: 1,
126 | thick: 2,
127 | },
128 | fontSizes: {
129 | xxs: 10,
130 | xs: 14,
131 | sm: 16,
132 | md: 18,
133 | lg: 20,
134 | xl: 24,
135 | xxl: 32,
136 | },
137 | lineHeights: {
138 | xxs: 12,
139 | xs: 16,
140 | sm: 18,
141 | md: 20,
142 | lg: 24,
143 | xl: 28,
144 | xxl: 36,
145 | },
146 | letterSpacings: {
147 | tight: 0.1,
148 | sparse: 1,
149 | },
150 | zIndices: {
151 | modal: 1000,
152 | },
153 | space: {
154 | none: 0,
155 | 1: 4,
156 | 2: 8,
157 | 3: 16,
158 | 4: 24,
159 | 5: 32,
160 | 6: 40,
161 | 7: 56,
162 | 8: 72,
163 | 9: 96,
164 | max: '$9' as const,
165 | },
166 | sizes: {
167 | hairlineWidth: StyleSheet.hairlineWidth,
168 | },
169 | radii: {
170 | sm: 4,
171 | md: 8,
172 | lg: 24,
173 | full: 999,
174 | },
175 | },
176 | utils: {
177 | size,
178 | shadow,
179 | typography,
180 | flexCenter,
181 | absoluteFill,
182 | fontSizeRem: remFunction('fontSize'),
183 | widthRem: remFunction('width'),
184 | heightRem: remFunction('height'),
185 | lineHeightRem: remFunction('lineHeight'),
186 | minWidthRem: remFunction('minWidth'),
187 | minHeightRem: remFunction('minHeight'),
188 | maxWidthRem: remFunction('maxWidth'),
189 | maxHeightRem: remFunction('maxHeight'),
190 | marginRightRem: remFunction('marginRight'),
191 | marginLeftRem: remFunction('marginLeft'),
192 | marginTopRem: remFunction('marginTop'),
193 | marginBottomRem: remFunction('marginBottom'),
194 | paddingRightRem: remFunction('paddingRight'),
195 | paddingLeftRem: remFunction('paddingLeft'),
196 | paddingTopRem: remFunction('paddingTop'),
197 | paddingBottomRem: remFunction('paddingBottom'),
198 | borderRadiusRem: remFunction('borderRadius'),
199 | },
200 | media,
201 | });
202 |
203 | const darkTheme = createTheme({
204 | colors: {
205 | // Brand colors
206 | primary: '$blue500',
207 | primaryText: '$blue100',
208 | primaryMuted: '$blue900',
209 | secondary: '$green500',
210 | secondaryText: '$green100',
211 | secondaryMuted: '$green900',
212 |
213 | // Informative colors
214 | info: '#3B82F6',
215 | infoText: '#81aef7',
216 | infoMuted: '#1b2940',
217 | success: '#10B981',
218 | successText: '#1ee8a5',
219 | successMuted: '#193328',
220 | warn: '#FBBF24',
221 | warnText: '#ffc93d',
222 | warnMuted: '#40351a',
223 | error: '#EF4444',
224 | errorText: '#ff7070',
225 | errorMuted: '#3e1c1d',
226 |
227 | // General colors
228 | text: '$white',
229 | textInverted: '$black',
230 | background: '$black',
231 | backdrop: 'rgba(0,0,0,0.5)',
232 | surface: '$gray800',
233 | elevated: '$gray600',
234 | muted1: '$gray500',
235 | muted2: '$gray600',
236 | muted3: '$gray700',
237 | muted4: '$gray800',
238 | muted5: '$gray850',
239 | muted6: '$gray900',
240 | },
241 | });
242 |
243 | export {
244 | styled,
245 | css,
246 | createTheme,
247 | useTheme,
248 | config,
249 | theme,
250 | darkTheme,
251 | ThemeProvider,
252 | };
253 |
254 | export type CSS = Stitches.CSS;
255 | export type Theme = typeof theme;
256 |
--------------------------------------------------------------------------------
/example/src/styles/utils.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import type * as Stitches from 'stitches-native';
4 |
5 | export type TypographyVariant =
6 | | 'body'
7 | | 'bodySmall'
8 | | 'bodyExtraSmall'
9 | | 'title1'
10 | | 'title2'
11 | | 'title3';
12 |
13 | type TypographyVariantVar = `$${TypographyVariant}`;
14 |
15 | // TODO: is there a way to type tokens? Using `CSS` from `styled.ts` doesn't work
16 | // because it causes a circular type dependency since `typography` is used in `utils`.
17 | const typographyVariants: {
18 | [variant in TypographyVariantVar]: CSSProperties;
19 | } = {
20 | $title1: {
21 | fontSize: '$xxl',
22 | fontWeight: '$bold',
23 | },
24 | $title2: {
25 | fontSize: '$xl',
26 | fontWeight: '$bold',
27 | },
28 | $title3: {
29 | fontSize: '$lg',
30 | fontWeight: '$bold',
31 | },
32 | $body: {
33 | fontSize: '$md',
34 | fontWeight: '$normal',
35 | },
36 | $bodySmall: {
37 | fontSize: '$sm',
38 | fontWeight: '$normal',
39 | },
40 | $bodyExtraSmall: {
41 | fontSize: '$xs',
42 | fontWeight: '$semibold',
43 | },
44 | };
45 |
46 | export const typography = (value: TypographyVariantVar) => {
47 | return typographyVariants[value];
48 | };
49 |
50 | export const size = (value: Stitches.PropertyValue<'width'>) => ({
51 | width: value,
52 | height: value,
53 | });
54 |
55 | export const shadow = (level: 'none' | 'small' | 'medium' | 'large') => {
56 | return {
57 | none: {
58 | elevation: 0,
59 | shadowOffset: { width: 0, height: 0 },
60 | shadowRadius: 0,
61 | shadowOpacity: 0,
62 | shadowColor: '#000',
63 | },
64 | small: {
65 | elevation: 2,
66 | shadowOffset: { width: 0, height: 1 },
67 | shadowRadius: 3,
68 | shadowOpacity: 0.1,
69 | shadowColor: '#000',
70 | },
71 | medium: {
72 | elevation: 5,
73 | shadowOffset: { width: 0, height: 3 },
74 | shadowRadius: 6,
75 | shadowOpacity: 0.2,
76 | shadowColor: '#000',
77 | },
78 | large: {
79 | elevation: 10,
80 | shadowOffset: { width: 0, height: 6 },
81 | shadowRadius: 12,
82 | shadowOpacity: 0.4,
83 | shadowColor: '#000',
84 | },
85 | }[level];
86 | };
87 |
88 | export const flexCenter = (
89 | value?: Stitches.PropertyValue<'flexDirection'>
90 | ) => ({
91 | flexDirection: value || 'column',
92 | justifyContent: 'center',
93 | alignItems: 'center',
94 | });
95 |
96 | export const absoluteFill = () => ({
97 | ...StyleSheet.absoluteFillObject,
98 | });
99 |
100 | export const generateSameMediaProperty = <
101 | Property extends keyof CSSProperties,
102 | Value
103 | >(
104 | property: Property,
105 | value: Value
106 | ) => {
107 | return {
108 | '@xl': {
109 | [property]: value,
110 | },
111 | '@lg': {
112 | [property]: value,
113 | },
114 | '@md': {
115 | [property]: value,
116 | },
117 | '@sm': {
118 | [property]: value,
119 | },
120 | '@xsm': {
121 | [property]: value,
122 | },
123 | '@xxsm': {
124 | [property]: value,
125 | },
126 | };
127 | };
128 |
129 | const fontSizes = {
130 | xxs: 10,
131 | xs: 14,
132 | sm: 16,
133 | md: 18,
134 | lg: 20,
135 | xl: 24,
136 | xxl: 32,
137 | };
138 |
139 | export const remFunction =
140 | (property: Property) =>
141 | (rValue: number) => {
142 | return {
143 | '@xxl': {
144 | [property]: fontSizes.xxl * rValue,
145 | },
146 | '@xl': {
147 | [property]: fontSizes.xl * rValue,
148 | },
149 | '@lg': {
150 | [property]: fontSizes.lg * rValue,
151 | },
152 | '@md': {
153 | [property]: fontSizes.md * rValue,
154 | },
155 | '@sm': {
156 | [property]: fontSizes.sm * rValue,
157 | },
158 | '@xs': {
159 | [property]: fontSizes.xs * rValue,
160 | },
161 | '@xxs': {
162 | [property]: fontSizes.xs * rValue,
163 | },
164 | } as Record<`@${keyof typeof fontSizes}`, CSSProperties>;
165 | };
166 |
--------------------------------------------------------------------------------
/example/src/type-test.tsx:
--------------------------------------------------------------------------------
1 | import { styled, css } from './styles';
2 |
3 | const View = styled('View', {});
4 |
5 | const csstest = css({
6 | fontSize: 16,
7 | });
8 |
9 | export const Test = ;
10 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "stitches-native": ["../src/types/index.d.ts"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const createExpoWebpackConfigAsync = require('@expo/webpack-config');
3 | const path = require('path');
4 |
5 | module.exports = async function (env, argv) {
6 | const config = await createExpoWebpackConfigAsync(env, argv);
7 |
8 | Object.assign(config.resolve.alias, {
9 | react: path.join(__dirname, 'node_modules', 'react'),
10 | 'react-native': path.join(__dirname, 'node_modules', 'react-native-web'),
11 | 'react-native-web': path.join(__dirname, 'node_modules', 'react-native-web'), // prettier-ignore
12 | });
13 |
14 | return config;
15 | };
16 |
--------------------------------------------------------------------------------
/media/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Temzasse/stitches-native/bd795a8d62007aa505463fdaa8e0d5258f668026/media/logo.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stitches-native",
3 | "description": "The modern CSS-in-JS library for React Native",
4 | "version": "0.4.0",
5 | "license": "MIT",
6 | "author": "Teemu Taskula",
7 | "repository": "https://github.com/Temzasse/stitches-native",
8 | "main": "lib/commonjs/index.js",
9 | "module": "lib/module/index.js",
10 | "types": "src/types/index.d.ts",
11 | "typesVersions": {
12 | ">= 4.1": {
13 | "*": [
14 | "types/index.d.ts"
15 | ]
16 | }
17 | },
18 | "exports": {
19 | "./package.json": "./package.json",
20 | ".": {
21 | "require": "./lib/commonjs/index.js",
22 | "import": "./lib/module/index.js",
23 | "types": "./src/types/index.d.ts"
24 | }
25 | },
26 | "files": [
27 | "src/types",
28 | "lib"
29 | ],
30 | "engines": {
31 | "node": ">=12"
32 | },
33 | "scripts": {
34 | "build": "bob build",
35 | "prepare": "yarn build",
36 | "release": "np --any-branch",
37 | "watch": "npm-watch",
38 | "test": "jest",
39 | "lint": "yarn lint:lib & yarn lint:example",
40 | "lint:lib": "eslint ./src --ext .js --config .eslintrc",
41 | "lint:lib:fix": "eslint ./src --ext .js --config .eslintrc --fix",
42 | "lint:example": "eslint ./example --ext .ts,.tsx --config .eslintrc",
43 | "lint:example:fix": "eslint ./example --ext .ts,.tsx --config .eslintrc --fix",
44 | "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
45 | "format:write": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\""
46 | },
47 | "dependencies": {
48 | "lodash.merge": "4.6.2"
49 | },
50 | "devDependencies": {
51 | "@babel/core": "7.14.8",
52 | "@babel/runtime": "7.14.8",
53 | "@testing-library/jest-native": "4.0.1",
54 | "@testing-library/react-native": "7.2.0",
55 | "@types/jest": "26.0.24",
56 | "@types/react": "^18.0.24",
57 | "@types/react-native": "^0.70.6",
58 | "@typescript-eslint/eslint-plugin": "4.6.1",
59 | "@typescript-eslint/parser": "4.6.1",
60 | "babel-jest": "27.0.6",
61 | "eslint": "7.31.0",
62 | "eslint-config-prettier": "8.3.0",
63 | "eslint-config-standard": "16.0.3",
64 | "eslint-plugin-import": "2.23.4",
65 | "eslint-plugin-node": "11.1.0",
66 | "eslint-plugin-prettier": "3.4.0",
67 | "eslint-plugin-promise": "5.1.0",
68 | "eslint-plugin-react": "7.24.0",
69 | "eslint-plugin-react-hooks": "4.2.0",
70 | "eslint-plugin-standard": "5.0.0",
71 | "husky": "6.0.0",
72 | "jest": "27.0.6",
73 | "metro-react-native-babel-preset": "^0.73.3",
74 | "npm-watch": "^0.11.0",
75 | "prettier": "2.3.2",
76 | "react": "18.0.0",
77 | "react-native": "0.69.6",
78 | "react-native-builder-bob": "0.18.1",
79 | "react-test-renderer": "18.0.0",
80 | "typescript": "4.7.3"
81 | },
82 | "peerDependencies": {
83 | "react": ">=16",
84 | "react-native": "*"
85 | },
86 | "husky": {
87 | "hooks": {
88 | "pre-commit": "yarn lint"
89 | }
90 | },
91 | "jest": {
92 | "preset": "react-native",
93 | "modulePathIgnorePatterns": [
94 | "/example/node_modules",
95 | "/lib/"
96 | ],
97 | "setupFilesAfterEnv": [
98 | "@testing-library/jest-native/extend-expect"
99 | ]
100 | },
101 | "react-native-builder-bob": {
102 | "source": "src/internals",
103 | "output": "lib",
104 | "targets": [
105 | "commonjs",
106 | "module"
107 | ]
108 | },
109 | "watch": {
110 | "build": {
111 | "patterns": [
112 | "src"
113 | ],
114 | "extensions": "js,jsx,ts,tsx"
115 | }
116 | },
117 | "eslintIgnore": [
118 | "node_modules/",
119 | "lib/"
120 | ],
121 | "prettier": {
122 | "printWidth": 80,
123 | "semi": true,
124 | "singleQuote": true,
125 | "tabWidth": 2,
126 | "trailingComma": "es5"
127 | },
128 | "np": {
129 | "yarn": true
130 | },
131 | "keywords": [
132 | "android",
133 | "ios",
134 | "react",
135 | "react-native",
136 | "native",
137 | "component",
138 | "components",
139 | "create",
140 | "css",
141 | "css-in-js",
142 | "javascript",
143 | "js",
144 | "object",
145 | "object-oriented",
146 | "oo",
147 | "oocss",
148 | "oriented",
149 | "style",
150 | "styled",
151 | "styles",
152 | "stylesheet",
153 | "stylesheets",
154 | "theme",
155 | "themes",
156 | "theming",
157 | "token",
158 | "tokens",
159 | "type",
160 | "typed",
161 | "types",
162 | "ts",
163 | "jsx",
164 | "tsx"
165 | ]
166 | }
167 |
--------------------------------------------------------------------------------
/src/internals/constants.js:
--------------------------------------------------------------------------------
1 | export const COLOR_PROPERTIES = {
2 | backgroundColor: 'colors',
3 | border: 'colors',
4 | borderBottomColor: 'colors',
5 | borderColor: 'colors',
6 | borderEndColor: 'colors',
7 | borderLeftColor: 'colors',
8 | borderRightColor: 'colors',
9 | borderStartColor: 'colors',
10 | borderTopColor: 'colors',
11 | color: 'colors',
12 | overlayColor: 'colors',
13 | shadowColor: 'colors',
14 | textDecoration: 'colors',
15 | textShadowColor: 'colors',
16 | tintColor: 'colors',
17 | };
18 |
19 | export const RADII_PROPERTIES = {
20 | borderBottomLeftRadius: 'radii',
21 | borderBottomRightRadius: 'radii',
22 | borderBottomStartRadius: 'radii',
23 | borderBottomEndRadius: 'radii',
24 | borderRadius: 'radii',
25 | borderTopLeftRadius: 'radii',
26 | borderTopRightRadius: 'radii',
27 | borderTopStartRadius: 'radii',
28 | borderTopEndRadius: 'radii',
29 | };
30 |
31 | export const SPACE_PROPERTIES = {
32 | bottom: 'space',
33 | left: 'space',
34 | margin: 'space',
35 | marginBottom: 'space',
36 | marginEnd: 'space',
37 | marginHorizontal: 'space',
38 | marginLeft: 'space',
39 | marginRight: 'space',
40 | marginStart: 'space',
41 | marginTop: 'space',
42 | marginVertical: 'space',
43 | padding: 'space',
44 | paddingBottom: 'space',
45 | paddingEnd: 'space',
46 | paddingHorizontal: 'space',
47 | paddingLeft: 'space',
48 | paddingRight: 'space',
49 | paddingStart: 'space',
50 | paddingTop: 'space',
51 | paddingVertical: 'space',
52 | right: 'space',
53 | top: 'space',
54 | };
55 |
56 | export const SIZE_PROPERTIES = {
57 | flexBasis: 'sizes',
58 | height: 'sizes',
59 | maxHeight: 'sizes',
60 | maxWidth: 'sizes',
61 | minHeight: 'sizes',
62 | minWidth: 'sizes',
63 | width: 'sizes',
64 | };
65 |
66 | export const FONT_PROPERTIES = {
67 | fontFamily: 'fonts',
68 | };
69 |
70 | export const FONT_SIZE_PROPERTIES = {
71 | fontSize: 'fontSizes',
72 | };
73 |
74 | export const FONT_WEIGHT_PROPERTIES = {
75 | fontWeight: 'fontWeights',
76 | };
77 |
78 | export const LINE_HEIGHT_PROPERTIES = {
79 | lineHeight: 'lineHeights',
80 | };
81 |
82 | export const LETTER_SPACING_PROPERTIES = {
83 | letterSpacing: 'letterSpacings',
84 | };
85 |
86 | export const Z_INDEX_PROPERTIES = {
87 | zIndex: 'zIndices',
88 | };
89 |
90 | export const BORDER_WIDTH_PROPERTIES = {
91 | borderWidth: 'borderWidths',
92 | borderTopWidth: 'borderWidths',
93 | borderRightWidth: 'borderWidths',
94 | borderBottomWidth: 'borderWidths',
95 | borderLeftWidth: 'borderWidths',
96 | borderEndWidth: 'borderWidths',
97 | borderStartWidth: 'borderWidths',
98 | };
99 |
100 | export const BORDER_STYLE_PROPERTIES = {
101 | borderStyle: 'borderStyles',
102 | };
103 |
104 | export const DEFAULT_THEME_MAP = {
105 | ...BORDER_STYLE_PROPERTIES,
106 | ...BORDER_WIDTH_PROPERTIES,
107 | ...COLOR_PROPERTIES,
108 | ...FONT_PROPERTIES,
109 | ...FONT_SIZE_PROPERTIES,
110 | ...FONT_WEIGHT_PROPERTIES,
111 | ...LETTER_SPACING_PROPERTIES,
112 | ...LINE_HEIGHT_PROPERTIES,
113 | ...RADII_PROPERTIES,
114 | ...SIZE_PROPERTIES,
115 | ...SPACE_PROPERTIES,
116 | ...Z_INDEX_PROPERTIES,
117 | };
118 |
119 | export const THEME_VALUES = {
120 | borderStyles: null,
121 | borderWidths: null,
122 | colors: null,
123 | fonts: null,
124 | fontSizes: null,
125 | fontWeights: null,
126 | letterSpacings: null,
127 | lineHeights: null,
128 | radii: null,
129 | sizes: null,
130 | space: null,
131 | zIndices: null,
132 | };
133 |
134 | export const EMPTY_THEME = {
135 | definition: {
136 | __ID__: 'theme-0',
137 | ...THEME_VALUES,
138 | },
139 | values: {
140 | ...THEME_VALUES,
141 | },
142 | };
143 |
144 | export const THEME_PROVIDER_MISSING_MESSAGE =
145 | 'Your app should have a ThemeProvider in order to access the theme';
146 |
--------------------------------------------------------------------------------
/src/internals/index.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash.merge';
2 | import { PixelRatio, useWindowDimensions } from 'react-native';
3 |
4 | import React, {
5 | createContext,
6 | createElement,
7 | forwardRef,
8 | memo,
9 | useMemo,
10 | useContext,
11 | } from 'react';
12 |
13 | import * as utils from './utils';
14 | import * as constants from './constants';
15 |
16 | /** @typedef {import('../types').__Stitches__} Stitches */
17 | /** @typedef {import('../types').CreateStitches} CreateStitches */
18 |
19 | // eslint-disable-next-line
20 | const ReactNative = require('react-native');
21 |
22 | /** @type {CreateStitches} */
23 | export function createStitches(config = {}) {
24 | const themes = [];
25 |
26 | if (config.theme) {
27 | const processedTheme = utils.processTheme(config.theme);
28 | processedTheme.definition.__ID__ = 'theme-1';
29 |
30 | themes.push(processedTheme);
31 | } else {
32 | themes.push(constants.EMPTY_THEME);
33 | }
34 |
35 | /** @type {Stitches['createTheme']} */
36 | function createTheme(theme) {
37 | const newTheme = utils.processTheme(
38 | Object.entries(config.theme || {}).reduce((acc, [key, val]) => {
39 | acc[key] = { ...val, ...theme[key] };
40 | return acc;
41 | }, {})
42 | );
43 |
44 | newTheme.definition.__ID__ = `theme-${themes.length + 1}`;
45 |
46 | themes.push(newTheme);
47 |
48 | return newTheme.definition;
49 | }
50 |
51 | const defaultTheme = themes[0].definition;
52 | const ThemeContext = createContext(defaultTheme);
53 |
54 | /** @type {Stitches['ThemeProvider']} */
55 | function ThemeProvider({ theme = defaultTheme, children }) {
56 | return (
57 | {children}
58 | );
59 | }
60 |
61 | function useThemeInternal() {
62 | const themeDefinition = useContext(ThemeContext);
63 |
64 | if (!themeDefinition) {
65 | throw new Error(constants.THEME_PROVIDER_MISSING_MESSAGE);
66 | }
67 |
68 | return themes.find((x) => x.definition.__ID__ === themeDefinition.__ID__);
69 | }
70 |
71 | /** @type {Stitches['useTheme']} */
72 | function useTheme() {
73 | const themeDefinition = useContext(ThemeContext);
74 |
75 | if (!themeDefinition) {
76 | throw new Error(constants.THEME_PROVIDER_MISSING_MESSAGE);
77 | }
78 |
79 | return themes.find((x) => x.definition.__ID__ === themeDefinition.__ID__).values; // prettier-ignore
80 | }
81 |
82 | /** @type {Stitches['styled']} */
83 | function styled(component, ...styleObjects) {
84 | const styleObject = styleObjects.reduce((a, v) => merge(a, v), {});
85 |
86 | const {
87 | variants = {},
88 | compoundVariants = [],
89 | defaultVariants = {},
90 | ..._styles
91 | } = styleObject;
92 |
93 | const styles = _styles;
94 |
95 | const styleSheets = {};
96 |
97 | let attrsFn;
98 |
99 | let Comp = forwardRef((props, ref) => {
100 | const theme = useThemeInternal();
101 |
102 | const styleSheet = useMemo(() => {
103 | const _styleSheet = styleSheets[theme.definition.__ID__];
104 | if (_styleSheet) {
105 | return _styleSheet;
106 | }
107 | styleSheets[theme.definition.__ID__] = utils.createStyleSheet({
108 | styles,
109 | config,
110 | theme,
111 | variants,
112 | compoundVariants,
113 | });
114 | return styleSheets[theme.definition.__ID__];
115 | }, [theme]);
116 |
117 | const { width: windowWidth } = useWindowDimensions();
118 |
119 | let variantStyles = [];
120 | let compoundVariantStyles = [];
121 |
122 | const { mediaKey, breakpoint } = useMemo(() => {
123 | if (typeof config.media === 'object') {
124 | const correctedWindowWidth =
125 | PixelRatio.getPixelSizeForLayoutSize(windowWidth);
126 |
127 | // TODO: how do we quarantee the order of breakpoint matches?
128 | // The order of the media key value pairs should be constant
129 | // but is that guaranteed? So if the keys are ordered from
130 | // smallest screen size to largest everything should work ok...
131 | const _mediaKey = utils.resolveMediaRangeQuery(
132 | config.media,
133 | correctedWindowWidth
134 | );
135 |
136 | return {
137 | mediaKey: _mediaKey,
138 | breakpoint: _mediaKey && `@${_mediaKey}`,
139 | };
140 | }
141 |
142 | return {};
143 | }, [windowWidth]);
144 |
145 | if (variants) {
146 | variantStyles = Object.keys(variants)
147 | .map((prop) => {
148 | let propValue = props[prop];
149 |
150 | if (propValue === undefined) {
151 | propValue = defaultVariants[prop];
152 | }
153 |
154 | let styleSheetKey = `${prop}_${propValue}`;
155 |
156 | // Handle responsive prop value
157 | // NOTE: only one media query will be applied since the `styleSheetKey`
158 | // is being rewritten by the last matching media query and defaults to `@initial`
159 | if (
160 | typeof propValue === 'object' &&
161 | typeof config.media === 'object'
162 | ) {
163 | // `@initial` acts as the default value if none of the media query values match
164 | // It's basically the as setting `prop="value"`, eg. `color="primary"`
165 | if (typeof propValue['@initial'] === 'string') {
166 | styleSheetKey = `${prop}_${propValue['@initial']}`;
167 | }
168 |
169 | if (breakpoint && propValue[breakpoint] !== undefined) {
170 | const val = config.media[mediaKey];
171 |
172 | if (val === true || typeof val === 'string') {
173 | styleSheetKey = `${prop}_${propValue[breakpoint]}`;
174 | }
175 | }
176 | }
177 |
178 | const extractedStyle = styleSheetKey
179 | ? styleSheet[styleSheetKey]
180 | : undefined;
181 |
182 | if (extractedStyle && breakpoint in extractedStyle) {
183 | // WARNING: lodash merge modifies the first argument reference or skips if object is frozen.
184 | return merge({}, extractedStyle, extractedStyle[breakpoint]);
185 | }
186 |
187 | return extractedStyle;
188 | })
189 | .filter(Boolean);
190 | }
191 |
192 | if (compoundVariants) {
193 | compoundVariantStyles = compoundVariants
194 | .map((compoundVariant) => {
195 | // eslint-disable-next-line
196 | const { css: _css, ...compounds } = compoundVariant;
197 | const compoundEntries = Object.entries(compounds);
198 |
199 | if (
200 | compoundEntries.every(([prop, value]) => {
201 | const propValue = props[prop] ?? defaultVariants[prop];
202 | return propValue === value;
203 | })
204 | ) {
205 | const key = utils.getCompoundKey(compoundEntries);
206 | const extractedStyle = styleSheet[key];
207 |
208 | if (extractedStyle && breakpoint in extractedStyle) {
209 | // WARNING: lodash merge modifies the first argument reference or skips if object is frozen.
210 | return merge({}, extractedStyle, extractedStyle[breakpoint]);
211 | }
212 |
213 | return extractedStyle;
214 | }
215 | })
216 | .filter(Boolean);
217 | }
218 |
219 | let cssStyles = props.css
220 | ? utils.processStyles({
221 | styles: props.css || {},
222 | theme: theme.values,
223 | config,
224 | })
225 | : {};
226 |
227 | if (cssStyles && breakpoint in cssStyles) {
228 | // WARNING: lodash merge modifies the first argument reference or skips if object is frozen.
229 | cssStyles = merge({}, cssStyles, cssStyles[breakpoint]);
230 | }
231 |
232 | const mediaStyle = styleSheet.base[breakpoint] || {};
233 |
234 | const stitchesStyles = [
235 | styleSheet.base,
236 | mediaStyle,
237 | ...variantStyles,
238 | ...compoundVariantStyles,
239 | cssStyles,
240 | ];
241 |
242 | const allStyles =
243 | typeof props.style === 'function'
244 | ? (...rest) =>
245 | [props.style(...rest), ...stitchesStyles].filter(Boolean)
246 | : [...stitchesStyles, props.style].filter(Boolean);
247 |
248 | let attrsProps = {};
249 |
250 | if (typeof attrsFn === 'function') {
251 | attrsProps = attrsFn({ ...props, theme: theme.values });
252 | }
253 |
254 | const propsWithoutVariant = { ...props };
255 |
256 | for (const variantKey of Object.keys(variants)) {
257 | delete propsWithoutVariant[variantKey];
258 | }
259 |
260 | const componentProps = {
261 | ...attrsProps,
262 | ...propsWithoutVariant,
263 | style: allStyles,
264 | ref,
265 | };
266 |
267 | if (typeof component === 'string') {
268 | return createElement(ReactNative[component], componentProps);
269 | } else if (
270 | typeof component === 'object' ||
271 | typeof component === 'function'
272 | ) {
273 | return createElement(component, componentProps);
274 | }
275 |
276 | return null;
277 | });
278 |
279 | Comp = memo(Comp);
280 |
281 | Comp.attrs = (cb) => {
282 | attrsFn = cb;
283 | return Comp;
284 | };
285 |
286 | return Comp;
287 | }
288 |
289 | /** @type {Stitches['css']} */
290 | function css(...cssObjects) {
291 | return cssObjects.reduce((a, v) => merge(a, v), {});
292 | }
293 |
294 | return {
295 | styled,
296 | css,
297 | theme: themes[0].definition,
298 | createTheme,
299 | useTheme,
300 | ThemeProvider,
301 | config,
302 | media: config.media,
303 | utils: config.utils,
304 | };
305 | }
306 |
307 | export const { styled, css } = createStitches();
308 |
309 | export const defaultThemeMap = constants.DEFAULT_THEME_MAP;
310 |
311 | export default createStitches;
312 |
--------------------------------------------------------------------------------
/src/internals/utils.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import merge from 'lodash.merge';
3 | import { DEFAULT_THEME_MAP, THEME_VALUES } from './constants';
4 |
5 | export function getCompoundKey(compoundEntries) {
6 | // Eg. `color_primary+size_small`
7 | return (
8 | compoundEntries
9 | // Sort compound entries alphabetically
10 | .sort((a, b) => {
11 | if (a[0] < b[0]) return -1;
12 | if (a[0] > b[0]) return 1;
13 | return 0;
14 | })
15 | .reduce((keyAcc, [prop, value]) => {
16 | return keyAcc + `${prop}_${value}+`;
17 | }, '')
18 | .slice(0, -1)
19 | ); // Remove last `+` character
20 | }
21 |
22 | const validSigns = ['<=', '<', '>=', '>'];
23 |
24 | function matchMediaRangeQuery(query, windowWidth) {
25 | const singleRangeRegex = /^\(width\s+([><=]+)\s+([0-9]+)px\)$/;
26 | const multiRangeRegex = /^\(([0-9]+)px\s([><=]+)\swidth\s+([><=]+)\s+([0-9]+)px\)$/; // prettier-ignore
27 | const singleRangeMatches = query.match(singleRangeRegex);
28 | const multiRangeMatches = query.match(multiRangeRegex);
29 |
30 | let result;
31 |
32 | if (multiRangeMatches && multiRangeMatches.length === 5) {
33 | const [, _width1, sign1, sign2, _width2] = multiRangeMatches;
34 | const width1 = parseInt(_width1, 10);
35 | const width2 = parseInt(_width2, 10);
36 |
37 | if (validSigns.includes(sign1) && validSigns.includes(sign2)) {
38 | result = eval(
39 | `${width1} ${sign1} ${windowWidth} && ${windowWidth} ${sign2} ${width2}`
40 | );
41 | }
42 | } else if (singleRangeMatches && singleRangeMatches.length === 3) {
43 | const [, sign, _width] = singleRangeMatches;
44 | const width = parseInt(_width, 10);
45 |
46 | if (validSigns.includes(sign)) {
47 | result = eval(`${windowWidth} ${sign} ${width}`);
48 | }
49 | }
50 |
51 | if (result === undefined) return false;
52 |
53 | if (typeof result !== 'boolean') {
54 | console.warn(
55 | `Unexpected media query result. Expected a boolean but got ${result}. Please make sure your media query syntax is correct.`
56 | );
57 | }
58 |
59 | return result;
60 | }
61 |
62 | export function resolveMediaRangeQuery(media, windowWidth) {
63 | const entries = Object.entries(media);
64 | let result;
65 |
66 | for (let i = 0; i < entries.length; i++) {
67 | const [breakpoint, queryOrFlag] = entries[i];
68 |
69 | // TODO: handle boolean flag
70 | if (typeof queryOrFlag !== 'string') continue;
71 |
72 | const match = matchMediaRangeQuery(queryOrFlag, windowWidth);
73 |
74 | if (match) {
75 | result = breakpoint;
76 | }
77 | }
78 |
79 | return result;
80 | }
81 |
82 | export function processThemeMap(themeMap) {
83 | const definition = {};
84 |
85 | Object.keys(themeMap).forEach((token) => {
86 | const scale = themeMap[token];
87 |
88 | if (!definition[scale]) {
89 | definition[scale] = {};
90 | }
91 |
92 | definition[scale][token] = scale;
93 | });
94 |
95 | return definition;
96 | }
97 |
98 | export function processTheme(theme) {
99 | const definition = {};
100 | const values = {};
101 |
102 | Object.keys(theme).forEach((scale) => {
103 | if (!definition[scale]) definition[scale] = {};
104 | if (!values[scale]) values[scale] = {};
105 |
106 | Object.keys(theme[scale]).forEach((token) => {
107 | let value = theme[scale][token];
108 |
109 | if (typeof value === 'string' && value.length > 1 && value[0] === '$') {
110 | value = theme[scale][value.replace('$', '')];
111 | }
112 |
113 | values[scale][token] = value;
114 |
115 | definition[scale][token] = {
116 | token,
117 | scale,
118 | value,
119 | toString: () => `$${token}`,
120 | };
121 | });
122 | });
123 |
124 | return { definition, values };
125 | }
126 |
127 | function getThemeKey(theme, themeMap, key) {
128 | return Object.keys(THEME_VALUES).find((themeKey) => {
129 | return key in (themeMap[themeKey] || {}) && theme?.[themeKey];
130 | });
131 | }
132 |
133 | export function processStyles({ styles, theme, config }) {
134 | const { utils, themeMap: customThemeMap } = config;
135 | const themeMap = processThemeMap(customThemeMap || DEFAULT_THEME_MAP);
136 |
137 | return Object.entries(styles).reduce((acc, [key, val]) => {
138 | if (utils && key in utils) {
139 | // NOTE: Deep merge for media properties.
140 | acc = merge(
141 | acc,
142 | processStyles({ styles: utils[key](val), theme, config })
143 | );
144 | } else if (typeof val === 'string' && val.indexOf('$') !== -1) {
145 | // Handle theme tokens, eg. `color: "$primary"` or `color: "$colors$primary"`
146 | const arr = val.split('$');
147 | const token = arr.pop();
148 | const scaleOrSign = arr.pop();
149 | const maybeSign = arr.pop(); // handle negative values
150 | const scale = scaleOrSign !== '-' ? scaleOrSign : undefined;
151 | const sign = scaleOrSign === '-' || maybeSign === '-' ? -1 : undefined;
152 |
153 | if (scale && theme[scale]) {
154 | acc[key] = theme[scale][token];
155 | } else {
156 | const themeKey = getThemeKey(theme, themeMap, key);
157 | if (themeKey) {
158 | acc[key] = theme[themeKey][token];
159 | }
160 | }
161 | if (typeof acc[key] === 'number' && sign) {
162 | acc[key] *= sign;
163 | }
164 | } else if (typeof val === 'object' && val.value !== undefined) {
165 | // Handle cases where the value comes from the `theme` returned by `createStitches`
166 | acc[key] = val.value;
167 | } else if (typeof acc[key] === 'object' && typeof val === 'object') {
168 | // Handle cases where media object value comes from top of a style prop and variants' ones
169 | acc[key] = merge(acc[key], val);
170 | } else {
171 | acc[key] = val;
172 | }
173 |
174 | return acc;
175 | }, {});
176 | }
177 |
178 | export function createStyleSheet({
179 | theme,
180 | styles,
181 | config,
182 | variants,
183 | compoundVariants,
184 | }) {
185 | return StyleSheet.create({
186 | base: styles ? processStyles({ styles, config, theme: theme.values }) : {},
187 | // Variant styles
188 | ...Object.entries(variants).reduce(
189 | (variantsAcc, [variantProp, variantValues]) => {
190 | Object.entries(variantValues).forEach(
191 | ([variantName, variantStyles]) => {
192 | // Eg. `color_primary` or `size_small`
193 | const key = `${variantProp}_${variantName}`;
194 |
195 | variantsAcc[key] = processStyles({
196 | styles: variantStyles,
197 | config,
198 | theme: theme.values,
199 | });
200 | }
201 | );
202 | return variantsAcc;
203 | },
204 | {}
205 | ),
206 | // Compound variant styles
207 | ...compoundVariants.reduce((compoundAcc, compoundVariant) => {
208 | const { css, ...compounds } = compoundVariant;
209 | const compoundEntries = Object.entries(compounds);
210 |
211 | if (compoundEntries.length > 1) {
212 | const key = getCompoundKey(compoundEntries);
213 |
214 | compoundAcc[key] = processStyles({
215 | styles: css || {},
216 | config,
217 | theme: theme.values,
218 | });
219 | }
220 |
221 | return compoundAcc;
222 | }, {}),
223 | });
224 | }
225 |
--------------------------------------------------------------------------------
/src/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react-native';
3 | import { createStitches as _createStitches } from '../internals';
4 | import type { CreateStitches } from '../types';
5 |
6 | // NOTE: the JSDoc types from the internal `createStitches` are not working properly
7 | const createStitches = _createStitches as CreateStitches;
8 |
9 | describe('Basic', () => {
10 | it('Functionality of styled()', () => {
11 | const { styled } = createStitches();
12 |
13 | const Comp = styled('View', {
14 | backgroundColor: 'red',
15 | height: 100,
16 | width: 100,
17 | });
18 |
19 | const { toJSON } = render();
20 | const result = toJSON();
21 |
22 | expect(result?.type).toEqual('View');
23 | expect(result?.props.style[0]).toMatchObject({
24 | backgroundColor: 'red',
25 | height: 100,
26 | width: 100,
27 | });
28 | });
29 |
30 | it('Functionality of styled() should not trigger recompute when a runtime theme is not used', () => {
31 | const { styled, createTheme } = createStitches({
32 | theme: {
33 | sizes: { demoWidth: 100 },
34 | },
35 | });
36 |
37 | const Comp = styled('View', {
38 | backgroundColor: 'red',
39 | height: 100,
40 | width: '$demoWidth',
41 | });
42 |
43 | render();
44 |
45 | createTheme({ sizes: { demoWidth: 10 } });
46 |
47 | const { toJSON } = render();
48 |
49 | const result = toJSON();
50 |
51 | expect(result?.type).toEqual('View');
52 | expect(result?.props.style[0]).toMatchObject({
53 | backgroundColor: 'red',
54 | height: 100,
55 | width: 100,
56 | });
57 | });
58 | });
59 |
60 | describe('Runtime', () => {
61 | it('Functionality of ThemeProvider', () => {
62 | const { styled, createTheme, ThemeProvider } = createStitches({
63 | theme: {
64 | sizes: { demoWidth: 100 },
65 | },
66 | });
67 |
68 | const Comp = styled('View', {
69 | backgroundColor: 'red',
70 | height: 100,
71 | width: '$demoWidth',
72 | });
73 |
74 | const newTheme = createTheme({ sizes: { demoWidth: 30 } });
75 |
76 | const { toJSON } = render(
77 |
78 |
79 |
80 | );
81 |
82 | const result = toJSON();
83 |
84 | expect(result?.type).toEqual('View');
85 | expect(result?.props.style[0]).toMatchObject({
86 | backgroundColor: 'red',
87 | height: 100,
88 | width: 30,
89 | });
90 | });
91 |
92 | it('Functionality of ThemeProvider should use new theme when a runtime theme is added', () => {
93 | const { styled, createTheme, ThemeProvider } = createStitches({
94 | theme: {
95 | sizes: { demoWidth: 100 },
96 | },
97 | });
98 |
99 | const Comp = styled('View', {
100 | backgroundColor: 'red',
101 | height: 100,
102 | width: '$demoWidth',
103 | });
104 |
105 | const newTheme = createTheme({ sizes: { demoWidth: 10 } });
106 |
107 | const { toJSON } = render(
108 |
109 |
110 |
111 | );
112 |
113 | const result = toJSON();
114 |
115 | expect(result?.type).toEqual('View');
116 | expect(result?.props.style[0]).toMatchObject({
117 | backgroundColor: 'red',
118 | height: 100,
119 | width: 10,
120 | });
121 | });
122 |
123 | it('Functionality of ThemeProvider should trigger recompute when a runtime theme is added', () => {
124 | const { styled, createTheme, ThemeProvider } = createStitches({
125 | theme: {
126 | sizes: { demoWidth: 100 },
127 | },
128 | });
129 |
130 | const Comp = styled('View', {
131 | backgroundColor: 'red',
132 | height: 100,
133 | width: '$demoWidth',
134 | });
135 |
136 | render();
137 |
138 | const newTheme = createTheme({ sizes: { demoWidth: 10 } });
139 |
140 | const { toJSON } = render(
141 |
142 |
143 |
144 | );
145 |
146 | const result = toJSON();
147 |
148 | expect(result?.type).toEqual('View');
149 | expect(result?.props.style[0]).toMatchObject({
150 | backgroundColor: 'red',
151 | height: 100,
152 | width: 10,
153 | });
154 | });
155 | });
156 |
--------------------------------------------------------------------------------
/src/types/config.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import type * as CSSUtil from './css-util';
3 | import type Stitches from './stitches';
4 |
5 | /** Configuration Interface */
6 | declare namespace ConfigType {
7 | /** Media interface. */
8 | export type Media = {
9 | [name in keyof T]: T[name] extends string ? T[name] : string | boolean;
10 | };
11 |
12 | /** Theme interface. */
13 | export type Theme = {
14 | borderStyles?: { [token in number | string]: boolean | number | string };
15 | borderWidths?: { [token in number | string]: boolean | number | string };
16 | colors?: { [token in number | string]: boolean | number | string };
17 | fonts?: { [token in number | string]: boolean | number | string };
18 | fontSizes?: { [token in number | string]: boolean | number | string };
19 | fontWeights?: { [token in number | string]: boolean | number | string };
20 | letterSpacings?: { [token in number | string]: boolean | number | string };
21 | lineHeights?: { [token in number | string]: boolean | number | string };
22 | radii?: { [token in number | string]: boolean | number | string };
23 | sizes?: { [token in number | string]: boolean | number | string };
24 | space?: { [token in number | string]: boolean | number | string };
25 | zIndices?: { [token in number | string]: boolean | number | string };
26 | } & {
27 | [Scale in keyof T]: {
28 | [Token in keyof T[Scale]]: T[Scale][Token] extends
29 | | boolean
30 | | number
31 | | string
32 | ? T[Scale][Token]
33 | : boolean | number | string;
34 | };
35 | };
36 |
37 | /** ThemeMap interface. */
38 | export type ThemeMap = {
39 | [Property in keyof T]: T[Property] extends string ? T[Property] : string;
40 | };
41 |
42 | /** Utility interface. */
43 | export type Utils = {
44 | [Property in keyof T]: T[Property] extends (value: infer V) => {}
45 | ?
46 | | T[Property]
47 | | ((value: V) => {
48 | [K in keyof CSSUtil.CSSProperties]?: CSSUtil.CSSProperties[K] | V;
49 | })
50 | : never;
51 | };
52 | }
53 |
54 | /** Default ThemeMap. */
55 | export interface DefaultThemeMap {
56 | backgroundColor: 'colors';
57 | border: 'colors';
58 | borderBottomColor: 'colors';
59 | borderColor: 'colors';
60 | borderEndColor: 'colors';
61 | borderLeftColor: 'colors';
62 | borderRightColor: 'colors';
63 | borderStartColor: 'colors';
64 | borderTopColor: 'colors';
65 | color: 'colors';
66 | overlayColor: 'colors';
67 | shadowColor: 'colors';
68 | textDecoration: 'colors';
69 | textShadowColor: 'colors';
70 | tintColor: 'colors';
71 |
72 | borderBottomLeftRadius: 'radii';
73 | borderBottomRightRadius: 'radii';
74 | borderBottomStartRadius: 'radii';
75 | borderBottomEndRadius: 'radii';
76 | borderRadius: 'radii';
77 | borderTopLeftRadius: 'radii';
78 | borderTopRightRadius: 'radii';
79 | borderTopStartRadius: 'radii';
80 | borderTopEndRadius: 'radii';
81 |
82 | bottom: 'space';
83 | left: 'space';
84 | margin: 'space';
85 | marginBottom: 'space';
86 | marginEnd: 'space';
87 | marginHorizontal: 'space';
88 | marginLeft: 'space';
89 | marginRight: 'space';
90 | marginStart: 'space';
91 | marginTop: 'space';
92 | marginVertical: 'space';
93 | padding: 'space';
94 | paddingBottom: 'space';
95 | paddingEnd: 'space';
96 | paddingHorizontal: 'space';
97 | paddingLeft: 'space';
98 | paddingRight: 'space';
99 | paddingStart: 'space';
100 | paddingTop: 'space';
101 | paddingVertical: 'space';
102 | right: 'space';
103 | top: 'space';
104 |
105 | flexBasis: 'sizes';
106 | height: 'sizes';
107 | maxHeight: 'sizes';
108 | maxWidth: 'sizes';
109 | minHeight: 'sizes';
110 | minWidth: 'sizes';
111 | width: 'sizes';
112 |
113 | fontFamily: 'fonts';
114 |
115 | fontSize: 'fontSizes';
116 |
117 | fontWeight: 'fontWeights';
118 |
119 | lineHeight: 'lineHeights';
120 |
121 | letterSpacing: 'letterSpacings';
122 |
123 | zIndex: 'zIndices';
124 |
125 | borderWidth: 'borderWidths';
126 | borderTopWidth: 'borderWidths';
127 | borderRightWidth: 'borderWidths';
128 | borderBottomWidth: 'borderWidths';
129 | borderLeftWidth: 'borderWidths';
130 | borderStartWidth: 'borderWidths';
131 | borderEndWidth: 'borderWidths';
132 |
133 | borderStyle: 'borderStyles';
134 | }
135 |
136 | /** Returns a function used to create a new Stitches interface. */
137 | export type CreateStitches = {
138 | <
139 | Media extends {} = {},
140 | Theme extends {} = {},
141 | ThemeMap extends {} = DefaultThemeMap,
142 | Utils extends {} = {}
143 | >(config?: {
144 | media?: ConfigType.Media;
145 | theme?: ConfigType.Theme;
146 | themeMap?: ConfigType.ThemeMap;
147 | utils?: ConfigType.Utils;
148 | }): Stitches;
149 | };
150 |
--------------------------------------------------------------------------------
/src/types/css-util.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import type * as Native from './react-native';
3 | import type * as Config from './config';
4 | import type * as Util from './util';
5 | import type * as ThemeUtil from './theme';
6 |
7 | export { Native };
8 |
9 | /** CSS style declaration object. */
10 | export interface CSSProperties extends Native.ReactNativeProperties {}
11 |
12 | type ValueByPropertyName =
13 | PropertyName extends keyof CSSProperties
14 | ? CSSProperties[PropertyName]
15 | : never;
16 |
17 | type TokenByPropertyName =
18 | PropertyName extends keyof ThemeMap
19 | ? TokenByScaleName
20 | : never;
21 |
22 | type TokenByScaleName = ScaleName extends keyof Theme
23 | ? Util.Prefixed<'$', keyof Theme[ScaleName]>
24 | : never;
25 |
26 | /** Returns a Style interface, leveraging the given media and style map. */
27 | export type CSS<
28 | Media = {},
29 | Theme = {},
30 | ThemeMap = Config.DefaultThemeMap,
31 | Utils = {}
32 | > = {
33 | // nested at-rule css styles
34 | [K in Util.Prefixed<'@', keyof Media>]?: CSS;
35 | } &
36 | // known property styles
37 | {
38 | [K in keyof CSSProperties]?:
39 | | ValueByPropertyName
40 | | TokenByPropertyName
41 | | ThemeUtil.ScaleValue
42 | | undefined;
43 | } &
44 | // known utility styles
45 | {
46 | [K in keyof Utils as K extends keyof CSSProperties
47 | ? never
48 | : K]?: Utils[K] extends (arg: infer P) => any
49 | ?
50 | | (P extends any[]
51 | ?
52 | | ($$PropertyValue extends keyof P[0]
53 | ?
54 | | ValueByPropertyName
55 | | TokenByPropertyName<
56 | P[0][$$PropertyValue],
57 | Theme,
58 | ThemeMap
59 | >
60 | | ThemeUtil.ScaleValue
61 | | undefined
62 | : $$ScaleValue extends keyof P[0]
63 | ?
64 | | TokenByScaleName
65 | | { scale: P[0][$$ScaleValue] }
66 | | undefined
67 | : never)[]
68 | | P
69 | : $$PropertyValue extends keyof P
70 | ?
71 | | ValueByPropertyName
72 | | TokenByPropertyName
73 | | undefined
74 | : $$ScaleValue extends keyof P
75 | ?
76 | | TokenByScaleName
77 | | { scale: P[$$ScaleValue] }
78 | | undefined
79 | : never)
80 | | P
81 | : never;
82 | } &
83 | // known theme styles
84 | {
85 | [K in keyof ThemeMap as K extends keyof CSSProperties
86 | ? never
87 | : K extends keyof Utils
88 | ? never
89 | : K]?: Util.Index | undefined;
90 | } & {
91 | // unknown css declaration styles
92 | /** Unknown property. */
93 | [K: string]:
94 | | number
95 | | string
96 | | CSS
97 | | {}
98 | | undefined;
99 | };
100 |
101 | /** Unique symbol used to reference a property value. */
102 | export declare const $$PropertyValue: unique symbol;
103 |
104 | /** Unique symbol used to reference a property value. */
105 | export type $$PropertyValue = typeof $$PropertyValue;
106 |
107 | /** Unique symbol used to reference a token value. */
108 | export declare const $$ScaleValue: unique symbol;
109 |
110 | /** Unique symbol used to reference a token value. */
111 | export type $$ScaleValue = typeof $$ScaleValue;
112 |
113 | export declare const $$ThemeValue: unique symbol;
114 |
115 | export type $$ThemeValue = typeof $$ThemeValue;
116 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import type Stitches from './stitches';
3 | import type * as Config from './config';
4 | import type * as CSSUtil from './css-util';
5 | import type * as StyledComponent from './styled-component';
6 |
7 | export type CreateStitches = Config.CreateStitches;
8 | export type CSSProperties = CSSUtil.CSSProperties;
9 | export type DefaultThemeMap = Config.DefaultThemeMap;
10 | export type __Stitches__ = Stitches;
11 |
12 | /** Returns a Style interface from a configuration, leveraging the given media and style map. */
13 |
14 | export type CSS<
15 | Config extends {
16 | media?: {};
17 | theme?: {};
18 | themeMap?: {};
19 | utils?: {};
20 | } = {
21 | media: {};
22 | theme: {};
23 | themeMap: {};
24 | utils: {};
25 | }
26 | > = CSSUtil.CSS<
27 | Config['media'],
28 | Config['theme'],
29 | Config['themeMap'],
30 | Config['utils']
31 | >;
32 |
33 | /** Returns the properties, attributes, and children expected by a component. */
34 | export type ComponentProps = Component extends (
35 | ...args: any[]
36 | ) => any
37 | ? Parameters[0]
38 | : never;
39 |
40 | /** Returns a type that expects a value to be a kind of CSS property value. */
41 | export type PropertyValue = {
42 | readonly [CSSUtil.$$PropertyValue]: K;
43 | };
44 |
45 | /** Returns a type that expects a value to be a kind of theme scale value. */
46 | export type ScaleValue = { readonly [CSSUtil.$$ScaleValue]: K };
47 |
48 | /** Returns a type that suggests variants from a component as possible prop values. */
49 | export type VariantProps =
50 | StyledComponent.TransformProps<
51 | Component[StyledComponent.$$StyledComponentProps],
52 | Component[StyledComponent.$$StyledComponentMedia]
53 | >;
54 |
55 | /** Map of CSS properties to token scales. */
56 | export declare const defaultThemeMap: DefaultThemeMap;
57 |
58 | /** Returns a library used to create styles. */
59 | export declare const createStitches: CreateStitches;
60 |
61 | /** Returns an object representing a theme. */
62 | export declare const createTheme: Stitches['createTheme'];
63 |
64 | /** Returns a function that applies styles and variants for a specific class. */
65 | export declare const css: Stitches['css'];
66 |
67 | /** Returns a function that applies styles and variants for a specific class. */
68 | export declare const styled: Stitches['styled'];
69 |
--------------------------------------------------------------------------------
/src/types/react-native.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | ButtonProps,
5 | FlatListProps,
6 | ImageBackgroundProps,
7 | ImageProps,
8 | ImageStyle,
9 | InputAccessoryViewProps,
10 | KeyboardAvoidingViewProps,
11 | OpaqueColorValue,
12 | PressableProps,
13 | ScrollViewProps,
14 | SectionListProps,
15 | TextInputProps,
16 | TextProps,
17 | TextStyle,
18 | TouchableHighlightProps,
19 | TouchableNativeFeedbackProps,
20 | TouchableOpacityProps,
21 | TouchableWithoutFeedbackProps,
22 | ViewProps,
23 | ViewStyle,
24 | VirtualizedListProps,
25 | } from 'react-native';
26 |
27 | type StringProperty = string & {};
28 | type ColorProperty = OpaqueColorValue | (string & {});
29 | type SimpleNumberProperty = number | (string & {});
30 | type NumberProperty = TLength | number | (string & {});
31 | // TODO: do we need both `SimpleNumberProperty` and `NumberProperty`?
32 |
33 | export interface ReactNativeProperties {
34 | alignContent: ViewStyle['alignContent'];
35 | alignItems: ViewStyle['alignItems'];
36 | alignSelf: ViewStyle['alignSelf'];
37 | aspectRatio: SimpleNumberProperty;
38 | backfaceVisibility: ViewStyle['backfaceVisibility'];
39 | backgroundColor: ColorProperty;
40 | borderBottomColor: ColorProperty;
41 | borderBottomEndRadius: SimpleNumberProperty;
42 | borderBottomLeftRadius: SimpleNumberProperty;
43 | borderBottomRightRadius: SimpleNumberProperty;
44 | borderBottomStartRadius: SimpleNumberProperty;
45 | borderBottomWidth: NumberProperty;
46 | borderColor: ColorProperty;
47 | borderEndColor: ColorProperty;
48 | borderEndWidth: NumberProperty;
49 | borderLeftColor: ColorProperty;
50 | borderLeftWidth: NumberProperty;
51 | borderRadius: SimpleNumberProperty;
52 | borderRightColor: ColorProperty;
53 | borderRightWidth: NumberProperty;
54 | borderStartColor: ColorProperty;
55 | borderStartWidth: NumberProperty;
56 | borderStyle: ViewStyle['borderStyle'];
57 | borderTopColor: ColorProperty;
58 | borderTopEndRadius: SimpleNumberProperty;
59 | borderTopLeftRadius: SimpleNumberProperty;
60 | borderTopRightRadius: SimpleNumberProperty;
61 | borderTopStartRadius: SimpleNumberProperty;
62 | borderTopWidth: NumberProperty;
63 | borderWidth: NumberProperty;
64 | bottom: NumberProperty;
65 | color: ColorProperty;
66 | direction: ViewStyle['direction'];
67 | display: ViewStyle['display'];
68 | elevation: NumberProperty;
69 | end: SimpleNumberProperty;
70 | flex: NumberProperty;
71 | flexBasis: NumberProperty;
72 | flexDirection: ViewStyle['flexDirection'];
73 | flexGrow: NumberProperty;
74 | flexShrink: NumberProperty;
75 | flexWrap: ViewStyle['flexWrap'];
76 | fontFamily: StringProperty;
77 | fontSize: NumberProperty;
78 | fontStyle: TextStyle['fontStyle'];
79 | fontVariant: TextStyle['fontVariant'];
80 | fontWeight: TextStyle['fontWeight'];
81 | height: NumberProperty;
82 | includeFontPadding: TextStyle['includeFontPadding'];
83 | justifyContent: ViewStyle['justifyContent'];
84 | left: NumberProperty;
85 | letterSpacing: NumberProperty;
86 | lineHeight: NumberProperty;
87 | margin: NumberProperty;
88 | marginBottom: NumberProperty;
89 | marginEnd: NumberProperty;
90 | marginHorizontal: NumberProperty;
91 | marginLeft: NumberProperty;
92 | marginRight: NumberProperty;
93 | marginStart: NumberProperty;
94 | marginTop: NumberProperty;
95 | marginVertical: NumberProperty;
96 | maxHeight: NumberProperty;
97 | maxWidth: NumberProperty;
98 | minHeight: NumberProperty;
99 | minWidth: NumberProperty;
100 | opacity: SimpleNumberProperty;
101 | overflow: ViewStyle['overflow'];
102 | overlayColor: ColorProperty;
103 | padding: NumberProperty;
104 | paddingBottom: NumberProperty;
105 | paddingEnd: NumberProperty;
106 | paddingHorizontal: NumberProperty;
107 | paddingLeft: NumberProperty;
108 | paddingRight: NumberProperty;
109 | paddingStart: NumberProperty;
110 | paddingTop: NumberProperty;
111 | paddingVertical: NumberProperty;
112 | position: ViewStyle['position'];
113 | resizeMode: ImageStyle['resizeMode'];
114 | right: NumberProperty;
115 | shadowColor: ColorProperty;
116 | shadowOffset: ViewStyle['shadowOffset'];
117 | shadowOpacity: SimpleNumberProperty;
118 | shadowRadius: SimpleNumberProperty;
119 | start: NumberProperty;
120 | textAlign: TextStyle['textAlign'];
121 | textDecorationColor: ColorProperty;
122 | textDecorationLine: TextStyle['textDecorationLine'];
123 | textDecorationStyle: TextStyle['textDecorationStyle'];
124 | textShadowColor: ColorProperty;
125 | textShadowOffset: TextStyle['textShadowOffset'];
126 | textShadowRadius: SimpleNumberProperty;
127 | textTransform: TextStyle['textTransform'];
128 | tintColor: ColorProperty;
129 | top: NumberProperty;
130 | width: NumberProperty;
131 | writingDirection: TextStyle['writingDirection'];
132 | zIndex: SimpleNumberProperty;
133 | }
134 |
135 | type PropsWithChildren = T & { children?: React.ReactNode };
136 |
137 | export type ReactNativeElements = {
138 | Button: PropsWithChildren;
139 | FlatList: FlatListProps;
140 | Image: PropsWithChildren;
141 | ImageBackground: PropsWithChildren;
142 | InputAccessoryView: PropsWithChildren;
143 | KeyboardAvoidingView: PropsWithChildren;
144 | Pressable: PropsWithChildren;
145 | SafeAreaView: PropsWithChildren;
146 | ScrollView: PropsWithChildren;
147 | SectionList: SectionListProps;
148 | Text: PropsWithChildren;
149 | TextInput: PropsWithChildren;
150 | TouchableHighlight: PropsWithChildren;
151 | TouchableNativeFeedback: PropsWithChildren;
152 | TouchableOpacity: PropsWithChildren;
153 | TouchableWithoutFeedback: PropsWithChildren;
154 | View: PropsWithChildren;
155 | VirtualizedList: VirtualizedListProps;
156 | };
157 |
158 | export type ReactNativeElementsKeys = keyof ReactNativeElements;
159 |
160 | // prettier-ignore
161 | export type ReactNativeElementType =
162 | | { [K in ReactNativeElementsKeys]: P extends ReactNativeElements[K] ? K : never }[ReactNativeElementsKeys]
163 | | React.ComponentType
;
164 |
165 | type ReactNativeComponentProps<
166 | T extends ReactNativeElementsKeys | React.JSXElementConstructor
167 | > = T extends React.JSXElementConstructor
168 | ? P
169 | : T extends ReactNativeElementsKeys
170 | ? ReactNativeElements[T]
171 | : {};
172 |
173 | export type ReactNativeComponentPropsWithRef =
174 | T extends React.ComponentClass
175 | ? React.PropsWithoutRef & React.RefAttributes>
176 | : React.PropsWithRef>;
177 |
--------------------------------------------------------------------------------
/src/types/stitches.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import type * as React from 'react';
3 | import type * as CSSUtil from './css-util';
4 | import type * as StyledComponent from './styled-component';
5 | import type * as Native from './react-native';
6 | import type * as Util from './util';
7 | import type * as ThemeUtil from './theme';
8 |
9 | /** Stitches interface. */
10 | export default interface Stitches<
11 | Media extends {} = {},
12 | Theme extends {} = {},
13 | ThemeMap extends {} = {},
14 | Utils extends {} = {}
15 | > {
16 | config: {
17 | media: Media;
18 | theme: Theme;
19 | themeMap: ThemeMap;
20 | utils: Utils;
21 | };
22 | createTheme: {
23 | <
24 | Arg extends {
25 | [Scale in keyof Theme]?: {
26 | [Token in keyof Theme[Scale]]?: boolean | number | string;
27 | };
28 | } &
29 | {
30 | [scale in string]: {
31 | [token in number | string]: boolean | number | string;
32 | };
33 | }
34 | >(
35 | arg: Arg
36 | ): string & ThemeTokens;
37 | };
38 | theme: string &
39 | {
40 | [Scale in keyof Theme]: {
41 | [Token in keyof Theme[Scale]]: ThemeUtil.Token<
42 | Extract,
43 | string,
44 | Extract
45 | >;
46 | };
47 | };
48 | useTheme: () => {
49 | [Scale in keyof Theme]: {
50 | [Token in keyof Theme[Scale]]: Theme[Scale][Token] extends string
51 | ? ThemeUtil.AliasedToken extends never
52 | ? string
53 | : Theme[Scale][ThemeUtil.AliasedToken]
54 | : Theme[Scale][Token];
55 | };
56 | };
57 | ThemeProvider: React.FunctionComponent<{
58 | theme?: any;
59 | children: React.ReactNode;
60 | }>; // TODO: fix `any`
61 | css: {
62 | <
63 | Composers extends (
64 | | string
65 | | React.ExoticComponent
66 | | React.JSXElementConstructor
67 | | Util.Function
68 | | { [name: string]: unknown }
69 | )[],
70 | CSS = CSSUtil.CSS
71 | >(
72 | ...composers: {
73 | [K in keyof Composers]: string extends Composers[K] // Strings, React Components, and Functions can be skipped over
74 | ? Composers[K]
75 | : Composers[K] extends
76 | | string
77 | | React.ExoticComponent
78 | | React.JSXElementConstructor
79 | | Util.Function
80 | ? Composers[K]
81 | : CSS & {
82 | /** The **variants** property lets you set a subclass of styles based on a key-value pair.
83 | *
84 | * [Read Documentation](https://stitches.dev/docs/variants)
85 | */
86 | variants?: {
87 | [Name in string]: {
88 | [Pair in number | string]: CSS;
89 | };
90 | };
91 | /** The **compoundVariants** property lets you to set a subclass of styles based on a combination of active variants.
92 | *
93 | * [Read Documentation](https://stitches.dev/docs/variants#compound-variants)
94 | */
95 | compoundVariants?: (('variants' extends keyof Composers[K]
96 | ? {
97 | [Name in keyof Composers[K]['variants']]?:
98 | | Util.Widen
99 | | Util.String;
100 | } &
101 | Util.WideObject
102 | : Util.WideObject) & {
103 | css: CSS;
104 | })[];
105 | /** The **defaultVariants** property allows you to predefine the active key-value pairs of variants.
106 | *
107 | * [Read Documentation](https://stitches.dev/docs/variants#default-variants)
108 | */
109 | defaultVariants?: 'variants' extends keyof Composers[K]
110 | ? {
111 | [Name in keyof Composers[K]['variants']]?:
112 | | Util.Widen
113 | | Util.String;
114 | }
115 | : Util.WideObject;
116 | } & {
117 | [K2 in keyof Composers[K]]: K2 extends
118 | | 'compoundVariants'
119 | | 'defaultVariants'
120 | | 'variants'
121 | ? unknown
122 | : K2 extends keyof CSS
123 | ? CSS[K2]
124 | : unknown;
125 | };
126 | }
127 | ): CSS;
128 | }; // TODO: `variants` inside `css` break TS...
129 | styled: {
130 | <
131 | Type extends
132 | | Native.ReactNativeElementsKeys
133 | | React.ComponentType
134 | | Util.Function,
135 | Composers extends (
136 | | string
137 | | React.ComponentType
138 | | Util.Function
139 | | { [name: string]: unknown }
140 | )[],
141 | CSS = CSSUtil.CSS
142 | >(
143 | type: Type,
144 | ...composers: {
145 | [K in keyof Composers]: string extends Composers[K] // Strings and Functions can be skipped over
146 | ? Composers[K]
147 | : Composers[K] extends
148 | | string
149 | | React.ComponentType
150 | | Util.Function
151 | ? Composers[K]
152 | : CSS & {
153 | /** The **variants** property lets you set a subclass of styles based on a key-value pair.
154 | *
155 | * [Read Documentation](https://stitches.dev/docs/variants)
156 | */
157 | variants?: {
158 | [Name in string]: {
159 | [Pair in number | string]: CSS;
160 | };
161 | };
162 | /** The **compoundVariants** property lets you to set a subclass of styles based on a combination of active variants.
163 | *
164 | * [Read Documentation](https://stitches.dev/docs/variants#compound-variants)
165 | */
166 | compoundVariants?: (('variants' extends keyof Composers[K]
167 | ? {
168 | [Name in keyof Composers[K]['variants']]?:
169 | | Util.Widen
170 | | Util.String;
171 | } &
172 | Util.WideObject
173 | : Util.WideObject) & {
174 | css: CSS;
175 | })[];
176 | /** The **defaultVariants** property allows you to predefine the active key-value pairs of variants.
177 | *
178 | * [Read Documentation](https://stitches.dev/docs/variants#default-variants)
179 | */
180 | defaultVariants?: 'variants' extends keyof Composers[K]
181 | ? {
182 | [Name in keyof Composers[K]['variants']]?:
183 | | Util.Widen
184 | | Util.String;
185 | }
186 | : Util.WideObject;
187 | } & {
188 | [K2 in keyof Composers[K]]: K2 extends
189 | | 'compoundVariants'
190 | | 'defaultVariants'
191 | | 'variants'
192 | ? unknown
193 | : K2 extends keyof CSS
194 | ? CSS[K2]
195 | : unknown;
196 | };
197 | }
198 | ): StyledComponent.StyledComponent<
199 | Type,
200 | StyledComponent.StyledComponentProps,
201 | Media,
202 | CSSUtil.CSS
203 | > & {
204 | attrs: (
205 | cb: (
206 | props: {
207 | theme: Theme;
208 | } & StyledComponent.StyledComponentProps
209 | ) => Type extends
210 | | Native.ReactNativeElementsKeys
211 | | React.ComponentType
212 | ? Partial>
213 | : {}
214 | ) => StyledComponent.StyledComponent<
215 | Type,
216 | StyledComponent.StyledComponentProps,
217 | Media,
218 | CSSUtil.CSS
219 | >;
220 | };
221 | };
222 | }
223 |
224 | type ThemeTokens = {
225 | [Scale in keyof Values]: {
226 | [Token in keyof Values[Scale]]: ThemeUtil.Token<
227 | Extract,
228 | Values[Scale][Token],
229 | Extract
230 | >;
231 | };
232 | };
233 |
--------------------------------------------------------------------------------
/src/types/styled-component.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import type * as React from 'react';
3 | import type * as Native from './react-native';
4 | import type * as Util from './util';
5 |
6 | /** Returns a new Styled Component. */
7 | export interface StyledComponent<
8 | Type = 'View',
9 | Props = {},
10 | Media = {},
11 | CSS = {}
12 | > extends React.ForwardRefExoticComponent<
13 | Util.Assign<
14 | Type extends Native.ReactNativeElementsKeys | React.ComponentType
15 | ? Native.ReactNativeComponentPropsWithRef
16 | : never,
17 | TransformProps & { css?: CSS }
18 | >
19 | > {
20 | (
21 | props: Util.Assign<
22 | Type extends Native.ReactNativeElementsKeys | React.ComponentType
23 | ? Native.ReactNativeComponentPropsWithRef
24 | : {},
25 | TransformProps & {
26 | as?: never;
27 | css?: CSS;
28 | ref?: any; // TODO: remove this hack and fix the real `ref` type
29 | }
30 | >
31 | ): React.ReactElement | null;
32 |
33 | <
34 | C extends CSS,
35 | As extends string | React.ComponentType = Type extends
36 | | string
37 | | React.ComponentType
38 | ? Type
39 | : never
40 | >(
41 | props: Util.Assign<
42 | React.ComponentPropsWithRef<
43 | As extends Native.ReactNativeElementsKeys | React.ComponentType
44 | ? As
45 | : never
46 | >,
47 | TransformProps & {
48 | as?: As;
49 | css?: {
50 | [K in keyof C]: K extends keyof CSS ? CSS[K] : never;
51 | };
52 | }
53 | >
54 | ): React.ReactElement | null;
55 |
56 | [$$StyledComponentType]: Type;
57 | [$$StyledComponentProps]: Props;
58 | [$$StyledComponentMedia]: Media;
59 | }
60 |
61 | /** Returns a new CSS Component. */
62 | export interface CssComponent {
63 | (
64 | props?: TransformProps & {
65 | css?: CSS;
66 | } & {
67 | [name in number | string]: any;
68 | }
69 | ): string & {
70 | props: {};
71 | };
72 |
73 | [$$StyledComponentType]: Type;
74 | [$$StyledComponentProps]: Props;
75 | [$$StyledComponentMedia]: Media;
76 | }
77 |
78 | export type TransformProps = {
79 | [K in keyof Props]:
80 | | Props[K]
81 | | ({
82 | [KMedia in Util.Prefixed<'@', 'initial' | keyof Media>]?: Props[K];
83 | } &
84 | {
85 | [KMedia in string]: Props[K];
86 | });
87 | };
88 |
89 | /** Unique symbol used to reference the type of a Styled Component. */
90 | export declare const $$StyledComponentType: unique symbol;
91 |
92 | /** Unique symbol used to reference the type of a Styled Component. */
93 | export type $$StyledComponentType = typeof $$StyledComponentType;
94 |
95 | /** Unique symbol used to reference the props of a Styled Component. */
96 | export declare const $$StyledComponentProps: unique symbol;
97 |
98 | /** Unique symbol used to reference the props of a Styled Component. */
99 | export type $$StyledComponentProps = typeof $$StyledComponentProps;
100 |
101 | /** Unique symbol used to reference the media passed into a Styled Component. */
102 | export declare const $$StyledComponentMedia: unique symbol;
103 |
104 | /** Unique symbol used to reference the media passed into a Styled Component. */
105 | export type $$StyledComponentMedia = typeof $$StyledComponentMedia;
106 |
107 | /** Returns a narrowed JSX element from the given tag name. */
108 | type IntrinsicElement = TagName extends Native.ReactNativeElementsKeys
109 | ? TagName
110 | : never;
111 |
112 | /** Returns a ForwardRef component. */
113 | type ForwardRefExoticComponent = React.ForwardRefExoticComponent<
114 | Util.Assign<
115 | Type extends React.ElementType ? React.ComponentPropsWithRef : never,
116 | Props & { as?: Type }
117 | >
118 | >;
119 |
120 | /** Returns the first Styled Component type from the given array of compositions. */
121 | export type StyledComponentType = T[0] extends never
122 | ? 'View'
123 | : T[0] extends string
124 | ? T[0]
125 | : T[0] extends (props: any) => any
126 | ? T[0]
127 | : T[0] extends { [$$StyledComponentType]: unknown }
128 | ? T[0][$$StyledComponentType]
129 | : T extends [lead: any, ...tail: infer V]
130 | ? StyledComponentType
131 | : never;
132 |
133 | /** Returns the cumulative variants from the given array of compositions. */
134 | export type StyledComponentProps =
135 | ($$StyledComponentProps extends keyof T[0]
136 | ? T[0][$$StyledComponentProps]
137 | : T[0] extends { variants: { [name: string]: unknown } }
138 | ? {
139 | [K in keyof T[0]['variants']]?: Util.Widen;
140 | }
141 | : {}) &
142 | (T extends [lead: any, ...tail: infer V] ? StyledComponentProps : {});
143 |
--------------------------------------------------------------------------------
/src/types/theme.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export type AliasedToken =
4 | T extends `${infer Head}${infer Tail}`
5 | ? Head extends '$'
6 | ? Tail
7 | : never
8 | : never;
9 |
10 | export interface ScaleValue {
11 | token: number | string;
12 | value: number | string;
13 | scale: string;
14 | }
15 |
16 | export interface Token<
17 | /** Token name. */
18 | NameType extends number | string = string,
19 | /** Token value. */
20 | ValueType extends number | string = string,
21 | /** Token scale. */
22 | ScaleType extends string | void = void
23 | > extends ScaleValue {
24 | new (name: NameType, value: ValueType, scale?: ScaleType): this;
25 |
26 | /** Name of the token. */
27 | token: NameType;
28 |
29 | /** Value of the token. */
30 | value: ValueType;
31 |
32 | /** Category of interface the token applies to. */
33 | scale: ScaleType extends string ? ScaleType : '';
34 |
35 | /** Returns variable prefixed with `$` representing the token. */
36 | toString(): `$(${this['token']})`;
37 | }
38 |
--------------------------------------------------------------------------------
/src/types/util.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | /* Utilities */
4 | /* ========================================================================== */
5 |
6 | /** Returns a string with the given prefix followed by the given values. */
7 | export type Prefixed = `${K}${Extract<
8 | T,
9 | boolean | number | string
10 | >}`;
11 |
12 | /** Returns an object from the given object assigned with the values of another given object. */
13 | export type Assign = Omit & T2;
14 |
15 | /** Returns a widened value from the given value. */
16 | export type Widen = T extends number
17 | ? `${T}` | T
18 | : T extends 'true'
19 | ? boolean | T
20 | : T extends 'false'
21 | ? boolean | T
22 | : T extends `${number}`
23 | ? number | T
24 | : T;
25 |
26 | /** Narrowed string. */
27 | export type String = string & Record;
28 |
29 | /** Narrowed number or string. */
30 | export type Index = (number | string) & Record;
31 |
32 | /** Narrowed function. */
33 | export type Function = (...args: any[]) => unknown;
34 |
35 | /** Widened object. */
36 | export type WideObject = {
37 | [name in number | string]: boolean | number | string | undefined | WideObject;
38 | };
39 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules", "example", "lib"],
3 | "include": ["src/index", "src/types"],
4 | "compilerOptions": {
5 | "allowJs": true,
6 | "allowUnreachableCode": false,
7 | "allowUnusedLabels": false,
8 | "baseUrl": ".",
9 | "declaration": true,
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "importHelpers": true,
13 | "importsNotUsedAsValues": "error",
14 | "jsx": "react",
15 | "lib": ["esnext", "dom"],
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "noFallthroughCasesInSwitch": true,
19 | "noImplicitReturns": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "resolveJsonModule": true,
23 | "rootDir": "./src",
24 | "skipLibCheck": true,
25 | "sourceMap": true,
26 | "strict": true,
27 | "target": "esnext",
28 | "paths": {
29 | "stitches-native": ["./src/index"]
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------