Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/expo-example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ module.exports = withUniwindConfig(config, {
extraThemes: ['premium'],
polyfills: { rem: 14 },
debug: true,
isTV: false,
})
1 change: 0 additions & 1 deletion packages/uniwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"format": "dprint fmt",
"prepublishOnly": "bun run build",
"circular:check": "dpdm --no-warning --no-tree -T --exit-code circular:1 'src/**/*.ts' 'src/**/*.tsx'",
"build:css": "bun run src/css/index.ts",
"test:native": "jest --config jest.config.native.js",
"test:web": "jest --config jest.config.web.js"
},
Expand Down
12 changes: 12 additions & 0 deletions packages/uniwind/src/common/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const enum Platform {
Android = 'android',
iOS = 'ios',
Web = 'web',
Native = 'native',
TV = 'tv',
AndroidTV = 'android-tv',
AppleTV = 'apple-tv',
}
Comment thread
Brentlok marked this conversation as resolved.

export const UNIWIND_PLATFORM_VARIABLES = '__uniwind-platform-'
export const UNIWIND_THEME_VARIABLES = '__uniwind-theme-'
10 changes: 8 additions & 2 deletions packages/uniwind/src/components/native/Pressable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { UniwindStore } from '../../core/native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

declare module 'react-native' {
interface PressableStateCallbackType {
focused?: boolean
}
}

export const Pressable = copyComponentProperties(RNPressable, (props: PressableProps) => {
const style = useStyle(
props.className,
Expand All @@ -18,12 +24,12 @@ export const Pressable = copyComponentProperties(RNPressable, (props: PressableP
<RNPressable
{...props}
style={state => {
if (state.pressed) {
if (state.pressed || state.focused) {
return [
UniwindStore.getStyles(
props.className,
props,
{ isDisabled: Boolean(props.disabled), isPressed: true },
{ isDisabled: Boolean(props.disabled), isPressed: state.pressed, isFocused: state.focused },
uniwindContext,
).styles,
typeof props.style === 'function' ? props.style(state) : props.style,
Expand Down
10 changes: 10 additions & 0 deletions packages/uniwind/src/components/native/TouchableHighlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { useStyle } from './useStyle'

export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight, (props: TouchableHighlightProps) => {
const [isPressed, setIsPressed] = useState(false)
const [isFocused, setIsFocused] = useState(false)
const state = {
isDisabled: Boolean(props.disabled),
isPressed,
isFocused,
} satisfies ComponentState
const style = useStyle(props.className, props, state)
const underlayColor = useStyle(props.underlayColorClassName, props, state).accentColor
Expand All @@ -26,6 +28,14 @@ export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight,
setIsPressed(false)
props.onPressOut?.(event)
}}
onFocus={event => {
setIsFocused(true)
Comment thread
Brentlok marked this conversation as resolved.
props.onFocus?.(event)
}}
onBlur={event => {
setIsFocused(false)
props.onBlur?.(event)
}}
/>
)
})
Expand Down
10 changes: 10 additions & 0 deletions packages/uniwind/src/components/native/TouchableOpacity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { useStyle } from './useStyle'

export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (props: TouchableOpacityProps) => {
const [isPressed, setIsPressed] = useState(false)
const [isFocused, setIsFocused] = useState(false)
const state = {
isDisabled: Boolean(props.disabled),
isPressed,
isFocused,
} satisfies ComponentState
const style = useStyle(props.className, props, state)

Expand All @@ -24,6 +26,14 @@ export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (pro
setIsPressed(false)
props.onPressOut?.(event)
}}
onFocus={event => {
setIsFocused(true)
props.onFocus?.(event)
}}
onBlur={event => {
setIsFocused(false)
props.onBlur?.(event)
}}
/>
)
})
Expand Down
26 changes: 24 additions & 2 deletions packages/uniwind/src/core/native/store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable max-depth */
import { Dimensions, Platform } from 'react-native'
import { Platform as UniwindPlatform, UNIWIND_PLATFORM_VARIABLES, UNIWIND_THEME_VARIABLES } from '../../common/consts'
import { Orientation, StyleDependency } from '../../types'
import { UniwindListener } from '../listener'
import { ComponentState, GenerateStyleSheetsCallback, RNStyle, Style, StyleSheets, ThemeName, UniwindContextType } from '../types'
Expand Down Expand Up @@ -61,7 +62,14 @@ class UniwindStoreBuilder {
reinit = (generateStyleSheetCallback: GenerateStyleSheetsCallback, themes: Array<string>) => {
const config = generateStyleSheetCallback(this.runtime)
const { scopedVars, stylesheet, vars } = config
const platformVars = scopedVars[`__uniwind-platform-${Platform.OS}`]
const platform = this.getCurrentPlatform()
const commonPlatform = platform.includes('tv') ? UniwindPlatform.TV : UniwindPlatform.Native
const commonPlatformVars = scopedVars[`${UNIWIND_PLATFORM_VARIABLES}${commonPlatform}`]
const platformVars = scopedVars[`${UNIWIND_PLATFORM_VARIABLES}${platform}`]

if (commonPlatformVars) {
Object.defineProperties(vars, Object.getOwnPropertyDescriptors(commonPlatformVars))
}

if (platformVars) {
Object.defineProperties(vars, Object.getOwnPropertyDescriptors(platformVars))
Expand All @@ -70,7 +78,7 @@ class UniwindStoreBuilder {
this.stylesheet = stylesheet
this.vars = Object.fromEntries(themes.map(theme => {
const clonedVars = cloneWithAccessors(vars)
const themeVars = scopedVars[`__uniwind-theme-${theme}`]
const themeVars = scopedVars[`${UNIWIND_THEME_VARIABLES}${theme}`]

if (themeVars) {
Object.defineProperties(clonedVars, Object.getOwnPropertyDescriptors(themeVars))
Expand Down Expand Up @@ -276,6 +284,20 @@ class UniwindStoreBuilder {

return true
}

private getCurrentPlatform() {
const platform = Platform.OS

if (platform === 'android') {
return Platform.isTV ? UniwindPlatform.AndroidTV : UniwindPlatform.Android
}

if (platform === 'ios') {
return Platform.isTV ? UniwindPlatform.AppleTV : UniwindPlatform.iOS
}

return platform
}
Comment thread
Brentlok marked this conversation as resolved.
}

export const UniwindStore = new UniwindStoreBuilder()
Expand Down
2 changes: 1 addition & 1 deletion packages/uniwind/src/css/variants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const variants = ['ios', 'android', 'web', 'native']
const variants = ['ios', 'android', 'web', 'native', 'tv', 'android-tv', 'apple-tv']

export const generateCSSForVariants = () => {
let css = ''
Expand Down
12 changes: 9 additions & 3 deletions packages/uniwind/src/metro/addMetaToStylesTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Platform } from '../common/consts'
import { StyleDependency } from '../types'
import { ProcessorBuilder } from './processor'
import { Platform, StyleSheetTemplate } from './types'
import { StyleSheetTemplate } from './types'
import { isDefined, serialize, toCamelCase } from './utils'

const extractVarsFromString = (value: string) => {
Expand Down Expand Up @@ -53,8 +54,13 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
.flatMap(([property, value]) => Processor.RN.cssToRN(property, value))
.map(([property, value]) => [`"${property}"`, `function() { return ${serialize(value)} }`])

if (platform && platform !== Platform.Native && platform !== currentPlatform) {
return null
if (platform) {
const isTV = currentPlatform === Platform.AndroidTV || currentPlatform === Platform.AppleTV
const commonPlatform = isTV ? Platform.TV : Platform.Native

if (platform !== commonPlatform && platform !== currentPlatform) {
return null
}
}

if (entries.length === 0) {
Expand Down
3 changes: 2 additions & 1 deletion packages/uniwind/src/metro/compileVirtual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { compile } from '@tailwindcss/node'
import { Scanner } from '@tailwindcss/oxide'
import { transform } from 'lightningcss'
import path from 'path'
import { Platform } from '../common/consts'
import { UniwindCSSVisitor } from '../css-visitor'
import { addMetaToStylesTemplate } from './addMetaToStylesTemplate'
import { Logger } from './logger'
import { ProcessorBuilder } from './processor'
import { Platform, Polyfills } from './types'
import { Polyfills } from './types'
import { serializeJSObject } from './utils'

type CompileVirtualConfig = {
Expand Down
1 change: 1 addition & 0 deletions packages/uniwind/src/metro/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type UniwindConfig = {
dtsFile?: string
polyfills?: Polyfills
debug?: boolean
isTV?: boolean
}

export declare function withUniwindConfig(config: MetroConfig, options: UniwindConfig): MetroConfig
21 changes: 19 additions & 2 deletions packages/uniwind/src/metro/metro-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import fs from 'fs'
import type { JsTransformerConfig, JsTransformOptions } from 'metro-transform-worker'
import path from 'path'
import { name } from '../../package.json'
import { Platform } from '../common/consts'
import { compileVirtual } from './compileVirtual'
import { injectThemes } from './injectThemes'
import { Platform, UniwindConfig } from './types'
import { UniwindConfig } from './types'

let worker: typeof import('metro-transform-worker')

Expand Down Expand Up @@ -52,14 +53,30 @@ export const transform = async (
return worker.transform(config, projectRoot, filePath, data, options)
}

const getPlatform = () => {
if (!config.uniwind.isTV) {
return options.platform as Platform
}

if (options.platform === Platform.Android) {
return Platform.AndroidTV
}

if (options.platform === Platform.iOS) {
return Platform.AppleTV
}

throw new Error(`Platform ${options.platform} not supported`)
}

const cssPath = path.join(process.cwd(), config.uniwind.cssEntryFile)
const injectedThemesCode = await injectThemes({
input: cssPath,
themes: config.uniwind.themes,
dtsPath: config.uniwind.dtsFile,
})
const css = fs.readFileSync(filePath, 'utf-8')
const platform = options.platform as Platform
const platform = getPlatform()
const virtualCode = await compileVirtual({
css,
platform,
Expand Down
14 changes: 12 additions & 2 deletions packages/uniwind/src/metro/processor/mq.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MediaQuery, QueryFeatureFor_MediaFeatureId } from 'lightningcss'
import { Platform } from '../../common/consts'
import { ColorScheme, Orientation } from '../../types'
import { MediaQueryResolver, Platform } from '../types'
import { MediaQueryResolver } from '../types'
import type { ProcessorBuilder } from './processor'

export class MQ {
Expand All @@ -12,7 +13,16 @@ export class MQ {
mediaQueries.forEach(mediaQuery => {
const { condition, mediaType } = mediaQuery

if ([Platform.Android, Platform.iOS, Platform.Native].includes(mediaType as Platform)) {
if (
[
Platform.Android,
Platform.iOS,
Platform.Native,
Platform.AndroidTV,
Platform.AppleTV,
Platform.TV,
].includes(mediaType as Platform)
) {
mq.platform = mediaType as Platform

return
Expand Down
5 changes: 3 additions & 2 deletions packages/uniwind/src/metro/processor/processor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Declaration, MediaQuery, Rule, transform } from 'lightningcss'
import { UNIWIND_PLATFORM_VARIABLES, UNIWIND_THEME_VARIABLES } from '../../common/consts'
import { Polyfills, ProcessMetaValues } from '../types'
import { Color } from './color'
import { CSS } from './css'
Expand Down Expand Up @@ -64,7 +65,7 @@ export class ProcessorBuilder {
}

if (mq.platform !== null) {
const platformKey = `__uniwind-platform-${mq.platform}`
const platformKey = `${UNIWIND_PLATFORM_VARIABLES}${mq.platform}`
this.scopedVars[platformKey] ??= {}

return this.scopedVars[platformKey]
Expand All @@ -74,7 +75,7 @@ export class ProcessorBuilder {
return this.vars
}

const themeKey = `__uniwind-theme-${this.declarationConfig.theme}`
const themeKey = `${UNIWIND_THEME_VARIABLES}${this.declarationConfig.theme}`
this.scopedVars[themeKey] ??= {}

return this.scopedVars[themeKey]
Expand Down
9 changes: 2 additions & 7 deletions packages/uniwind/src/metro/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
TokenOrValue,
UnresolvedColor,
} from 'lightningcss'
import { Platform } from '../common/consts'
import { ColorScheme, Orientation } from '../types'

export type Polyfills = {
Expand All @@ -24,6 +25,7 @@ export type UniwindConfig = {
dtsFile?: string
polyfills?: Polyfills
debug?: boolean
isTV?: boolean
}

export type MediaQueryResolver = {
Expand All @@ -42,13 +44,6 @@ export type MediaQueryResolver = {
dataAttributes: Record<string, string> | null
}

export const enum Platform {
Android = 'android',
iOS = 'ios',
Web = 'web',
Native = 'native',
}

type TakeArray<T> = T extends Array<any> ? T : never

export type DeclarationValues =
Expand Down
3 changes: 2 additions & 1 deletion packages/uniwind/src/metro/withUniwindConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { MetroConfig } from 'metro-config'
import { Platform } from '../common/consts'
import { cacheStore, patchMetroGraphToSupportUncachedModules } from './metro-css-patches'
import { nativeResolver, webResolver } from './resolvers'
import { Platform, UniwindConfig } from './types'
import { UniwindConfig } from './types'
import { uniq } from './utils'

export const withUniwindConfig = <T extends MetroConfig>(
Expand Down
2 changes: 1 addition & 1 deletion packages/uniwind/tests/setup.native.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { readFileSync } from 'fs'
import { resolve } from 'path'
import { Dimensions } from 'react-native'
import { Platform } from '../src/common/consts'
import { compileVirtual } from '../src/metro/compileVirtual'
import { Platform } from '../src/metro/types'
import { SAFE_AREA_INSET_BOTTOM, SAFE_AREA_INSET_TOP, SCREEN_HEIGHT, SCREEN_WIDTH } from './consts'

jest.spyOn(Dimensions, 'get').mockReturnValue({ width: SCREEN_WIDTH, height: SCREEN_HEIGHT, scale: 1, fontScale: 1 })
Expand Down
7 changes: 5 additions & 2 deletions packages/uniwind/uniwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
@custom-variant android (@media android);
@custom-variant web (html &);
@custom-variant native (@media native);
@custom-variant tv (@media tv);
@custom-variant android-tv (@media android-tv);
@custom-variant apple-tv (@media apple-tv);

@utility h-screen-safe {
height: calc(100vh - (env(safe-area-inset-top) + env(safe-area-inset-bottom)));
Expand Down Expand Up @@ -396,7 +399,7 @@
}

@media (prefers-color-scheme: light) {
&:not(:where(.light, .light *, .dark, .dark *, .premium, .premium *)) {
&:not(:where(.light, .light *, .dark, .dark *)) {
@slot;
}
}
Expand All @@ -408,7 +411,7 @@
}

@media (prefers-color-scheme: dark) {
&:not(:where(.light, .light *, .dark, .dark *, .premium, .premium *)) {
&:not(:where(.light, .light *, .dark, .dark *)) {
@slot;
}
}
Expand Down