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
19 changes: 17 additions & 2 deletions packages/uniwind/src/components/native/Pressable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { Pressable as RNPressable, PressableProps } from 'react-native'
import { UniwindStore } from '../../core/native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const Pressable = copyComponentProperties(RNPressable, (props: PressableProps) => {
const style = useStyle(props.className)
const style = useStyle(props.className, {
isDisabled: Boolean(props.disabled),
})

return (
<RNPressable
{...props}
style={state => [style, typeof props.style === 'function' ? props.style(state) : props.style]}
style={state => {
if (state.pressed) {
return [
UniwindStore.getStyles(
props.className,
{ isDisabled: Boolean(props.disabled), isPressed: true },
).styles,
typeof props.style === 'function' ? props.style(state) : props.style,
]
}

return [style, typeof props.style === 'function' ? props.style(state) : props.style]
}}
/>
)
})
Expand Down
16 changes: 10 additions & 6 deletions packages/uniwind/src/components/native/Switch.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Switch as RNSwitch, SwitchProps } from 'react-native'
import { useUniwindAccent } from '../../hooks'
import { ComponentState } from '../../core/types'
import { useUniwindAccent } from '../../hooks/useUniwindAccent.native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const Switch = copyComponentProperties(RNSwitch, (props: SwitchProps) => {
const style = useStyle(props.className)
const trackColorOn = useUniwindAccent(props.trackColorOnClassName)
const trackColorOff = useUniwindAccent(props.trackColorOffClassName)
const thumbColor = useUniwindAccent(props.thumbColorClassName)
const ios_backgroundColor = useUniwindAccent(props.ios_backgroundColorClassName)
const state = {
isDisabled: Boolean(props.disabled),
} satisfies ComponentState
const style = useStyle(props.className, state)
const trackColorOn = useUniwindAccent(props.trackColorOnClassName, state)
const trackColorOff = useUniwindAccent(props.trackColorOffClassName, state)
const thumbColor = useUniwindAccent(props.thumbColorClassName, state)
const ios_backgroundColor = useUniwindAccent(props.ios_backgroundColorClassName, state)

return (
<RNSwitch
Expand Down
24 changes: 21 additions & 3 deletions packages/uniwind/src/components/native/Text.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from 'react'
import { Text as RNText, TextProps } from 'react-native'
import { useUniwindAccent } from '../../hooks'
import { ComponentState } from '../../core/types'
import { useUniwindAccent } from '../../hooks/useUniwindAccent.native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

Expand All @@ -8,15 +10,31 @@ type StyleWithWebkitLineClamp = {
}

export const Text = copyComponentProperties(RNText, (props: TextProps) => {
const style = useStyle(props.className)
const selectionColor = useUniwindAccent(props.selectionColorClassName)
const [isPressed, setIsPressed] = useState(false)
const state = {
isPressed,
isDisabled: Boolean(props.disabled),
} satisfies ComponentState
const style = useStyle(props.className, state)
const selectionColor = useUniwindAccent(props.selectionColorClassName, state)

return (
<RNText
{...props}
style={[style, props.style]}
selectionColor={props.selectionColor ?? selectionColor}
numberOfLines={(style as StyleWithWebkitLineClamp).WebkitLineClamp ?? props.numberOfLines}
// Without onPress function Text is not clickable, so onPressIn and onPressOut are not working
onPress={event => props.onPress?.(event)}
suppressHighlighting={props.onPress ? props.suppressHighlighting : true}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})
Expand Down
39 changes: 32 additions & 7 deletions packages/uniwind/src/components/native/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { useState } from 'react'
import { TextInput as RNTextInput, TextInputProps } from 'react-native'
import { useUniwindAccent } from '../../hooks'
import { ComponentState } from '../../core/types'
import { useUniwindAccent } from '../../hooks/useUniwindAccent.native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const TextInput = copyComponentProperties(RNTextInput, (props: TextInputProps) => {
const style = useStyle(props.className)
const cursorColor = useUniwindAccent(props.cursorColorClassName)
const selectionColor = useUniwindAccent(props.selectionColorClassName)
const placeholderTextColor = useUniwindAccent(props.placeholderTextColorClassName)
const selectionHandleColor = useUniwindAccent(props.selectionHandleColorClassName)
const underlineColorAndroid = useUniwindAccent(props.underlineColorAndroidClassName)
const [isFocused, setIsFocused] = useState(false)
const [isPressed, setIsPressed] = useState(false)
const state = {
isDisabled: props.editable === false,
isFocused,
isPressed,
} satisfies ComponentState
const style = useStyle(props.className, state)
const cursorColor = useUniwindAccent(props.cursorColorClassName, state)
const selectionColor = useUniwindAccent(props.selectionColorClassName, state)
const placeholderTextColor = useUniwindAccent(props.placeholderTextColorClassName, state)
const selectionHandleColor = useUniwindAccent(props.selectionHandleColorClassName, state)
const underlineColorAndroid = useUniwindAccent(props.underlineColorAndroidClassName, state)

return (
<RNTextInput
Expand All @@ -20,6 +29,22 @@ export const TextInput = copyComponentProperties(RNTextInput, (props: TextInputP
placeholderTextColor={props.placeholderTextColor ?? placeholderTextColor}
selectionHandleColor={props.selectionHandleColor ?? selectionHandleColor}
underlineColorAndroid={props.underlineColorAndroid ?? underlineColorAndroid}
onFocus={event => {
setIsFocused(true)
props.onFocus?.(event)
}}
onBlur={event => {
setIsFocused(false)
props.onBlur?.(event)
}}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})
Expand Down
21 changes: 18 additions & 3 deletions packages/uniwind/src/components/native/TouchableHighlight.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import { useState } from 'react'
import { TouchableHighlight as RNTouchableHighlight, TouchableHighlightProps } from 'react-native'
import { useUniwindAccent } from '../../hooks'
import { ComponentState } from '../../core/types'
import { useUniwindAccent } from '../../hooks/useUniwindAccent.native'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight, (props: TouchableHighlightProps) => {
const style = useStyle(props.className)
const underlayColor = useUniwindAccent(props.underlayColorClassName)
const [isPressed, setIsPressed] = useState(false)
const state = {
isDisabled: Boolean(props.disabled),
isPressed,
} satisfies ComponentState
const style = useStyle(props.className, state)
const underlayColor = useUniwindAccent(props.underlayColorClassName, state)

return (
<RNTouchableHighlight
{...props}
style={[style, props.style]}
underlayColor={props.underlayColor ?? underlayColor}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})
Expand Down
36 changes: 24 additions & 12 deletions packages/uniwind/src/components/native/TouchableNativeFeedback.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { useState } from 'react'
import { TouchableNativeFeedback as RNTouchableNativeFeedback, TouchableNativeFeedbackProps } from 'react-native'
import { ComponentState } from '../../core/types'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const TouchableNativeFeedback = copyComponentProperties(
RNTouchableNativeFeedback,
(props: TouchableNativeFeedbackProps) => {
const style = useStyle(props.className)
export const TouchableNativeFeedback = copyComponentProperties(RNTouchableNativeFeedback, (props: TouchableNativeFeedbackProps) => {
const [isPressed, setIsPressed] = useState(false)
const state = {
isDisabled: Boolean(props.disabled),
isPressed,
} satisfies ComponentState
const style = useStyle(props.className, state)

return (
<RNTouchableNativeFeedback
{...props}
style={[style, props.style]}
/>
)
},
)
return (
<RNTouchableNativeFeedback
{...props}
style={[style, props.style]}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})

export default TouchableNativeFeedback
17 changes: 16 additions & 1 deletion packages/uniwind/src/components/native/TouchableOpacity.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { useState } from 'react'
import { TouchableOpacity as RNTouchableOpacity, TouchableOpacityProps } from 'react-native'
import { ComponentState } from '../../core/types'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

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

return (
<RNTouchableOpacity
{...props}
style={[style, props.style]}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { useState } from 'react'
import { TouchableWithoutFeedback as RNTouchableWithoutFeedback, TouchableWithoutFeedbackProps } from 'react-native'
import { ComponentState } from '../../core/types'
import { copyComponentProperties } from '../utils'
import { useStyle } from './useStyle'

export const TouchableWithoutFeedback = copyComponentProperties(RNTouchableWithoutFeedback, (props: TouchableWithoutFeedbackProps) => {
const style = useStyle(props.className)
const [isPressed, setIsPressed] = useState(false)
const state = {
isDisabled: Boolean(props.disabled),
isPressed,
} satisfies ComponentState
const style = useStyle(props.className, state)

return (
<RNTouchableWithoutFeedback
{...props}
style={[style, props.style]}
onPressIn={event => {
setIsPressed(true)
props.onPressIn?.(event)
}}
onPressOut={event => {
setIsPressed(false)
props.onPressOut?.(event)
}}
/>
)
})
Expand Down
8 changes: 6 additions & 2 deletions packages/uniwind/src/components/native/useStyle.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { useEffect, useMemo, useReducer } from 'react'
import { UniwindStore } from '../../core/native'
import { ComponentState } from '../../core/types'

export const useStyle = (className?: string) => {
export const useStyle = (className?: string, state?: ComponentState) => {
const [_, rerender] = useReducer(() => ({}), {})
const styleState = useMemo(() => UniwindStore.getStyles(className), [className, _])
const styleState = useMemo(
() => UniwindStore.getStyles(className, state),
[className, _, state?.isDisabled, state?.isFocused, state?.isPressed],
)

useEffect(() => {
const dispose = UniwindStore.subscribe(() => rerender(), styleState.dependencies)
Expand Down
11 changes: 7 additions & 4 deletions packages/uniwind/src/core/native/store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dimensions } from 'react-native'
import { Orientation, StyleDependency } from '../../types'
import { RNStyle, Style, StyleSheets } from '../types'
import { ComponentState, RNStyle, Style, StyleSheets } from '../types'
import { parseBoxShadow, parseFontVariant, parseTransformsMutation, resolveGradient } from './parsers'
import { UniwindRuntime } from './runtime'

Expand Down Expand Up @@ -30,7 +30,7 @@ export class UniwindStoreBuilder {
}
}

getStyles(className?: string) {
getStyles(className?: string, state?: ComponentState) {
if (className === undefined) {
return {
styles: {} as RNStyle,
Expand All @@ -56,7 +56,7 @@ export class UniwindStoreBuilder {
})
.filter(Boolean)

return this.resolveStyles(styles as Array<[string, Style]>)
return this.resolveStyles(styles as Array<[string, Style]>, state)
}

reload = () => {
Expand All @@ -67,7 +67,7 @@ export class UniwindStoreBuilder {
dependencies.forEach(dep => this.listeners[dep].forEach(listener => listener()))
}

private resolveStyles(styles: Array<[string, Style]>) {
private resolveStyles(styles: Array<[string, Style]>, state?: ComponentState) {
const dependencies = [] as Array<StyleDependency>
const filteredStyles = styles.filter(([, style]) => {
dependencies.push(...style.dependencies)
Expand All @@ -78,6 +78,9 @@ export class UniwindStoreBuilder {
|| (style.theme !== null && this.runtime.currentThemeName !== style.theme)
|| (style.orientation !== null && this.runtime.orientation !== style.orientation)
|| (style.rtl !== null && this.runtime.rtl !== style.rtl)
|| (style.active !== null && state?.isPressed !== style.active)
|| (style.focus !== null && state?.isFocused !== style.focus)
|| (style.disabled !== null && state?.isDisabled !== style.disabled)
) {
return false
}
Expand Down
9 changes: 9 additions & 0 deletions packages/uniwind/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export type Style = {
className: string
importantProperties: Array<string>
complexity: number
active: boolean | null
focus: boolean | null
disabled: boolean | null
}

export type StyleSheets = Record<string, Array<Style>>
Expand Down Expand Up @@ -77,3 +80,9 @@ declare global {
var __uniwind__hot_reload: () => void
var __uniwindThemes__: ReadonlyArray<string> | undefined
}

export type ComponentState = {
isPressed?: boolean
isDisabled?: boolean
isFocused?: boolean
}
9 changes: 5 additions & 4 deletions packages/uniwind/src/hooks/useResolveClassNames.native.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useEffect, useReducer } from 'react'
import { UniwindStore } from '../core/native'
import { ComponentState } from '../core/types'

export const useResolveClassNames = (className: string) => {
export const useResolveClassNames = (className: string, state?: ComponentState) => {
const [uniwindState, recreate] = useReducer(
() => UniwindStore.getStyles(className),
UniwindStore.getStyles(className),
() => UniwindStore.getStyles(className, state),
UniwindStore.getStyles(className, state),
)

useEffect(() => {
recreate()
}, [className])
}, [className, state?.isDisabled, state?.isPressed, state?.isFocused])

useEffect(() => {
const dispose = UniwindStore.subscribe(recreate, uniwindState.dependencies)
Expand Down
Loading