Skip to content

Commit f5db5f5

Browse files
authored
feat: tv selectors, focus: selector for pressables (#425)
* feat: pressable focused support * feat: support tv selectors * feat: support focus in TouchableOpacity and TouchableHighlight * feat: platform vars full supports * chore: update css file
1 parent 8d3df73 commit f5db5f5

18 files changed

Lines changed: 122 additions & 27 deletions

File tree

apps/expo-example/metro.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ module.exports = withUniwindConfig(config, {
1919
extraThemes: ['premium'],
2020
polyfills: { rem: 14 },
2121
debug: true,
22+
isTV: false,
2223
})

packages/uniwind/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"format": "dprint fmt",
1818
"prepublishOnly": "bun run build",
1919
"circular:check": "dpdm --no-warning --no-tree -T --exit-code circular:1 'src/**/*.ts' 'src/**/*.tsx'",
20-
"build:css": "bun run src/css/index.ts",
2120
"test:native": "jest --config jest.config.native.js",
2221
"test:web": "jest --config jest.config.web.js"
2322
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const enum Platform {
2+
Android = 'android',
3+
iOS = 'ios',
4+
Web = 'web',
5+
Native = 'native',
6+
TV = 'tv',
7+
AndroidTV = 'android-tv',
8+
AppleTV = 'apple-tv',
9+
}
10+
11+
export const UNIWIND_PLATFORM_VARIABLES = '__uniwind-platform-'
12+
export const UNIWIND_THEME_VARIABLES = '__uniwind-theme-'

packages/uniwind/src/components/native/Pressable.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { UniwindStore } from '../../core/native'
44
import { copyComponentProperties } from '../utils'
55
import { useStyle } from './useStyle'
66

7+
declare module 'react-native' {
8+
interface PressableStateCallbackType {
9+
focused?: boolean
10+
}
11+
}
12+
713
export const Pressable = copyComponentProperties(RNPressable, (props: PressableProps) => {
814
const style = useStyle(
915
props.className,
@@ -18,12 +24,12 @@ export const Pressable = copyComponentProperties(RNPressable, (props: PressableP
1824
<RNPressable
1925
{...props}
2026
style={state => {
21-
if (state.pressed) {
27+
if (state.pressed || state.focused) {
2228
return [
2329
UniwindStore.getStyles(
2430
props.className,
2531
props,
26-
{ isDisabled: Boolean(props.disabled), isPressed: true },
32+
{ isDisabled: Boolean(props.disabled), isPressed: state.pressed, isFocused: state.focused },
2733
uniwindContext,
2834
).styles,
2935
typeof props.style === 'function' ? props.style(state) : props.style,

packages/uniwind/src/components/native/TouchableHighlight.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { useStyle } from './useStyle'
66

77
export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight, (props: TouchableHighlightProps) => {
88
const [isPressed, setIsPressed] = useState(false)
9+
const [isFocused, setIsFocused] = useState(false)
910
const state = {
1011
isDisabled: Boolean(props.disabled),
1112
isPressed,
13+
isFocused,
1214
} satisfies ComponentState
1315
const style = useStyle(props.className, props, state)
1416
const underlayColor = useStyle(props.underlayColorClassName, props, state).accentColor
@@ -26,6 +28,14 @@ export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight,
2628
setIsPressed(false)
2729
props.onPressOut?.(event)
2830
}}
31+
onFocus={event => {
32+
setIsFocused(true)
33+
props.onFocus?.(event)
34+
}}
35+
onBlur={event => {
36+
setIsFocused(false)
37+
props.onBlur?.(event)
38+
}}
2939
/>
3040
)
3141
})

packages/uniwind/src/components/native/TouchableOpacity.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { useStyle } from './useStyle'
66

77
export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (props: TouchableOpacityProps) => {
88
const [isPressed, setIsPressed] = useState(false)
9+
const [isFocused, setIsFocused] = useState(false)
910
const state = {
1011
isDisabled: Boolean(props.disabled),
1112
isPressed,
13+
isFocused,
1214
} satisfies ComponentState
1315
const style = useStyle(props.className, props, state)
1416

@@ -24,6 +26,14 @@ export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (pro
2426
setIsPressed(false)
2527
props.onPressOut?.(event)
2628
}}
29+
onFocus={event => {
30+
setIsFocused(true)
31+
props.onFocus?.(event)
32+
}}
33+
onBlur={event => {
34+
setIsFocused(false)
35+
props.onBlur?.(event)
36+
}}
2737
/>
2838
)
2939
})

packages/uniwind/src/core/native/store.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable max-depth */
22
import { Dimensions, Platform } from 'react-native'
3+
import { Platform as UniwindPlatform, UNIWIND_PLATFORM_VARIABLES, UNIWIND_THEME_VARIABLES } from '../../common/consts'
34
import { Orientation, StyleDependency } from '../../types'
45
import { UniwindListener } from '../listener'
56
import { ComponentState, GenerateStyleSheetsCallback, RNStyle, Style, StyleSheets, ThemeName, UniwindContextType } from '../types'
@@ -61,7 +62,14 @@ class UniwindStoreBuilder {
6162
reinit = (generateStyleSheetCallback: GenerateStyleSheetsCallback, themes: Array<string>) => {
6263
const config = generateStyleSheetCallback(this.runtime)
6364
const { scopedVars, stylesheet, vars } = config
64-
const platformVars = scopedVars[`__uniwind-platform-${Platform.OS}`]
65+
const platform = this.getCurrentPlatform()
66+
const commonPlatform = platform.includes('tv') ? UniwindPlatform.TV : UniwindPlatform.Native
67+
const commonPlatformVars = scopedVars[`${UNIWIND_PLATFORM_VARIABLES}${commonPlatform}`]
68+
const platformVars = scopedVars[`${UNIWIND_PLATFORM_VARIABLES}${platform}`]
69+
70+
if (commonPlatformVars) {
71+
Object.defineProperties(vars, Object.getOwnPropertyDescriptors(commonPlatformVars))
72+
}
6573

6674
if (platformVars) {
6775
Object.defineProperties(vars, Object.getOwnPropertyDescriptors(platformVars))
@@ -70,7 +78,7 @@ class UniwindStoreBuilder {
7078
this.stylesheet = stylesheet
7179
this.vars = Object.fromEntries(themes.map(theme => {
7280
const clonedVars = cloneWithAccessors(vars)
73-
const themeVars = scopedVars[`__uniwind-theme-${theme}`]
81+
const themeVars = scopedVars[`${UNIWIND_THEME_VARIABLES}${theme}`]
7482

7583
if (themeVars) {
7684
Object.defineProperties(clonedVars, Object.getOwnPropertyDescriptors(themeVars))
@@ -276,6 +284,20 @@ class UniwindStoreBuilder {
276284

277285
return true
278286
}
287+
288+
private getCurrentPlatform() {
289+
const platform = Platform.OS
290+
291+
if (platform === 'android') {
292+
return Platform.isTV ? UniwindPlatform.AndroidTV : UniwindPlatform.Android
293+
}
294+
295+
if (platform === 'ios') {
296+
return Platform.isTV ? UniwindPlatform.AppleTV : UniwindPlatform.iOS
297+
}
298+
299+
return platform
300+
}
279301
}
280302

281303
export const UniwindStore = new UniwindStoreBuilder()

packages/uniwind/src/css/variants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const variants = ['ios', 'android', 'web', 'native']
1+
const variants = ['ios', 'android', 'web', 'native', 'tv', 'android-tv', 'apple-tv']
22

33
export const generateCSSForVariants = () => {
44
let css = ''

packages/uniwind/src/metro/addMetaToStylesTemplate.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { Platform } from '../common/consts'
12
import { StyleDependency } from '../types'
23
import { ProcessorBuilder } from './processor'
3-
import { Platform, StyleSheetTemplate } from './types'
4+
import { StyleSheetTemplate } from './types'
45
import { isDefined, serialize, toCamelCase } from './utils'
56

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

56-
if (platform && platform !== Platform.Native && platform !== currentPlatform) {
57-
return null
57+
if (platform) {
58+
const isTV = currentPlatform === Platform.AndroidTV || currentPlatform === Platform.AppleTV
59+
const commonPlatform = isTV ? Platform.TV : Platform.Native
60+
61+
if (platform !== commonPlatform && platform !== currentPlatform) {
62+
return null
63+
}
5864
}
5965

6066
if (entries.length === 0) {

packages/uniwind/src/metro/compileVirtual.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { compile } from '@tailwindcss/node'
22
import { Scanner } from '@tailwindcss/oxide'
33
import { transform } from 'lightningcss'
44
import path from 'path'
5+
import { Platform } from '../common/consts'
56
import { UniwindCSSVisitor } from '../css-visitor'
67
import { addMetaToStylesTemplate } from './addMetaToStylesTemplate'
78
import { Logger } from './logger'
89
import { ProcessorBuilder } from './processor'
9-
import { Platform, Polyfills } from './types'
10+
import { Polyfills } from './types'
1011
import { serializeJSObject } from './utils'
1112

1213
type CompileVirtualConfig = {

0 commit comments

Comments
 (0)