,
+ B extends Breakpoints,
+>({
+ tokens,
+ properties: userProperties = {},
+ macros: userMacros = {},
+ breakpoints: userBreakpoints = {},
+}: {
+ tokens: T
+ properties?: Partial
+ macros?: Partial
+ breakpoints?: Partial
+}): Theme => {
+ const properties = Object.assign({}, propertyMapping, userProperties) as P
+ const macros = userMacros as M
+ const breakpoints = Object.entries(userBreakpoints)
+ .sort(([_a, a], [_b, b]) => {
+ return a - b
+ })
+ .reduce((breakpoints, [key, value]) => {
+ breakpoints[key as keyof B] = value
+ return breakpoints
+ }, {} as B)
+
+ type InnerTheme = Theme<
+ typeof tokens,
+ typeof properties,
+ typeof macros,
+ typeof breakpoints
+ >
+
+ function _applyStyleProperty(styles: StyleObject, prop: string, value: any) {
+ for (const _prop of properties[prop]) {
+ // @ts-ignore no index sig, it's fine
+ const t = specialTokenMapping[_prop]
+ ? // @ts-ignore no index sig, it's fine
+ tokens[specialTokenMapping[_prop]]
+ : tokens[_prop]
+ styles[_prop] = t?.[value] || value
+ }
+ }
+
+ const style: InnerTheme['style'] = (rawProps, activeBreakpoints = []) => {
+ let styles: StyleObject = {}
+ const props: any = {}
+
+ Object.keys(rawProps).forEach(prop => {
+ // @ts-ignore no index sig, it's fine
+ const value = rawProps[prop]
+
+ if (value === undefined) return
+
+ if (properties[prop]) {
+ _applyStyleProperty(styles, prop, value)
+ } else if (macros[prop]) {
+ styles = {
+ ...styles,
+ // @ts-ignore no index sig, it's fine
+ ...(rawProps[prop] === true ? macros[prop](value, tokens) : {}),
+ }
+ } else if (breakpoints[prop]) {
+ for (const b of Object.keys(breakpoints)) {
+ if (activeBreakpoints.includes(b)) {
+ // @ts-ignore no index sig, it's fine
+ const breakpointStyles = rawProps[b] || {}
+ const r = style(breakpointStyles, activeBreakpoints)
+ styles = {...styles, ...r.styles}
+ } else {
+ // @ts-ignore no index sig, it's fine
+ delete rawProps[b]
+ }
+ }
+ } else {
+ // @ts-ignore no index sig, it's fine
+ props[prop] = value
+ }
+ })
+
+ return {
+ styles: StyleSheet.create({sheet: styles}).sheet,
+ props: props,
+ }
+ }
+
+ function getActiveBreakpoints({width}: {width: number}) {
+ const active: (keyof typeof breakpoints)[] = Object.keys(
+ breakpoints,
+ ).filter(breakpoint => width >= breakpoints[breakpoint])
+
+ return {
+ active,
+ current: active[active.length - 1],
+ }
+ }
+
+ return {
+ config: {
+ tokens,
+ properties,
+ macros,
+ breakpoints,
+ },
+ style,
+ getActiveBreakpoints,
+ }
+}
diff --git a/src/view/nova/lib/utils.ts b/src/view/nova/lib/utils.ts
new file mode 100644
index 0000000000..544f5480b6
--- /dev/null
+++ b/src/view/nova/lib/utils.ts
@@ -0,0 +1,25 @@
+import {Platform} from 'react-native'
+
+export function web(value: any) {
+ return Platform.select({
+ web: value,
+ })
+}
+
+export function ios(value: any) {
+ return Platform.select({
+ ios: value,
+ })
+}
+
+export function android(value: any) {
+ return Platform.select({
+ android: value,
+ })
+}
+
+export function native(value: any) {
+ return Platform.select({
+ native: value,
+ })
+}
diff --git a/src/view/nova/themes.ts b/src/view/nova/themes.ts
new file mode 100644
index 0000000000..a4d586dc8c
--- /dev/null
+++ b/src/view/nova/themes.ts
@@ -0,0 +1,113 @@
+import {createTheme} from './lib/theme'
+
+const palette = {
+ white: '#FFFFFF',
+ black: '#0A0B0D',
+ gray1: '#F7F9FC',
+ gray2: '#E4E8EE',
+ gray3: '#CED5DE',
+ gray4: '#828A99',
+ gray5: '#4D5564',
+ gray6: '#1D2126',
+ blue: '#1185FE',
+ green: '#54D469',
+ red: '#FB4566',
+}
+
+export const light = createTheme({
+ tokens: {
+ color: {
+ primary: palette.blue,
+ l1: palette.white,
+ l2: palette.gray1,
+ l3: palette.gray2,
+ l4: palette.gray3,
+ l5: palette.gray4,
+ l6: palette.gray5,
+ l7: palette.gray6,
+ l8: palette.black,
+ },
+ fontSize: {
+ xxs: 10,
+ xs: 12,
+ s: 14,
+ m: 16,
+ l: 18,
+ xl: 22,
+ xxl: 26,
+ },
+ lineHeight: {
+ xs: 12,
+ s: 14,
+ m: 16,
+ l: 18,
+ xl: 22,
+ },
+ },
+ properties: {
+ w: ['width'],
+ h: ['height'],
+ c: ['color'],
+ bg: ['backgroundColor'],
+ ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
+ mt: ['marginTop'],
+ mb: ['marginBottom'],
+ ml: ['marginLeft'],
+ mr: ['marginRight'],
+ my: ['marginTop', 'marginBottom'],
+ mx: ['marginLeft', 'marginRight'],
+ /**
+ * Alias for `padding`, maps to all padding properties e.g. `paddingTop`,
+ * `paddingBottom`, etc.
+ */
+ pa: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
+ pt: ['paddingTop'],
+ pb: ['paddingBottom'],
+ pl: ['paddingLeft'],
+ pr: ['paddingRight'],
+ py: ['paddingTop', 'paddingBottom'],
+ px: ['paddingLeft', 'paddingRight'],
+ z: ['zIndex'],
+ radius: ['borderRadius'],
+ },
+ macros: {
+ row: (_: boolean) => ({flexDirection: 'row'}),
+ column: (_: boolean) => ({flex: 1}),
+ abs: (_: boolean) => ({position: 'absolute'}),
+ cover: (_: boolean) => ({
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ }),
+ caps: (_: boolean) => ({textTransform: 'uppercase'}),
+ fontSize(value: 'xs' | 's' | 'm' | 'l' | 'xl', tokens) {
+ return {
+ fontSize: tokens.fontSize[value],
+ lineHeight: tokens.lineHeight[value],
+ }
+ },
+ },
+ breakpoints: {
+ gtMobile: 800,
+ gtTablet: 1300,
+ },
+})
+
+export const dark = createTheme({
+ ...light.config,
+ tokens: {
+ ...light.config.tokens,
+ color: {
+ ...light.config.tokens.color,
+ l1: palette.black,
+ l2: palette.gray6,
+ l3: palette.gray5,
+ l4: palette.gray4,
+ l5: palette.gray3,
+ l6: palette.gray2,
+ l7: palette.gray1,
+ l8: palette.white,
+ },
+ },
+})