Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve typings #12

Merged
merged 4 commits into from
Oct 6, 2023
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
4 changes: 2 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import { UnistylesTheme } from 'react-native-unistyles'
import { ExampleScreen } from './ExampleScreen'
import * as Examples from './examples'
import { theme } from './styles'

export const App: React.FunctionComponent = () => (
<UnistylesTheme theme={theme}>
<ExampleScreen />
<Examples.Extreme />
</UnistylesTheme>
)
33 changes: 33 additions & 0 deletions example/src/examples/Breakpoints.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyles, useStyles } from '../styles'

// Use breakpoints for some values
export const Breakpoints: React.FunctionComponent = () => {
const { styles } = useStyles(stylesheet)

return (
<View style={styles.dynamicContainer}>
<Text>
Breakpoint demo, resize me :)
</Text>
<Text>
Row or column?
</Text>
</View>
)
}

const stylesheet = createStyles(theme => ({
dynamicContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: {
// hints for breakpoints are available here
xs: 'row',
md: 'column'
},
backgroundColor: theme.colors.sky
}
}))
46 changes: 46 additions & 0 deletions example/src/examples/Extreme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyles, useStyles } from '../styles'

// Edge cases
export const Extreme: React.FunctionComponent = () => {
const { styles } = useStyles(stylesheet)

return (
<View style={styles.dynamicContainer(1)}>
<Text style={styles.text}>
Edge cases
</Text>
</View>
)
}

const stylesheet = createStyles(theme => ({
// dynamic function with hints
dynamicContainer: (flex: number) => ({
flex,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: {
xs: theme.colors.oak,
md: theme.colors.sky
}
}),
text: {
height: 100,
shadowOpacity: {
xs: 20
},
transform: [
{
scale: 2
},
{
translateX: {
sm: 10,
md: 20
}
}
]
}
}))
36 changes: 36 additions & 0 deletions example/src/examples/MediaQueries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyles, useStyles } from '../styles'

// Media queries
export const MediaQueries: React.FunctionComponent = () => {
const { styles } = useStyles(stylesheet)

return (
<View style={styles.dynamicContainer}>
<Text>
Media queries, resize me :)
</Text>
<Text>
Row of column
</Text>
</View>
)
}

const stylesheet = createStyles(theme => ({
dynamicContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: {
xs: 'row',
// mixed queries
':w[400]': 'column'
},
backgroundColor: {
':w[, 300]:h[100]': theme.colors.oak,
':w[301]': theme.colors.barbie
}
}
}))
25 changes: 25 additions & 0 deletions example/src/examples/Minimal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import { Text, View, StyleSheet } from 'react-native'
import { useStyles } from '../styles'

// Compatibility between StyleSheet.create and useStyles
// access theme from useStyles
export const Minimal: React.FunctionComponent = () => {
const { styles, theme } = useStyles(stylesheet)

return (
<View style={styles.container}>
<Text>
I'm just a minimal example with {theme.colors.barbie} color
</Text>
</View>
)
}

const stylesheet = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
35 changes: 35 additions & 0 deletions example/src/examples/MinimalWithCreateStyles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyles, useStyles } from '../styles'

// createStyles with StyleSheet.create compatible Object
export const MinimalWithCreateStyles: React.FunctionComponent = () => {
const { styles } = useStyles(stylesheet)

return (
<View style={styles.container}>
<Text style={styles.text}>
I'm just a minimal example with createStyles
</Text>
</View>
)
}

const stylesheet = createStyles({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignContent: 'space-between',
resizeMode: 'contain'
},
text: {
borderWidth: 1,
borderColor: 'purple',
padding: 20,
flex: {
xs: 2,
md: 1
}
}
})
13 changes: 5 additions & 8 deletions example/src/ExampleScreen.tsx → example/src/examples/Theme.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyles, useStyles } from './styles'
import { createStyles, useStyles } from '../styles'

export const ExampleScreen: React.FunctionComponent = () => {
// Injected theme to createStyles
export const Theme: React.FunctionComponent = () => {
const { styles } = useStyles(stylesheet)

return (
<View style={styles.container}>
<Text>
Resize me :)
Theme example
</Text>
</View>
)
Expand All @@ -19,10 +20,6 @@ const stylesheet = createStyles(theme => ({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: {
sm: theme.colors.oak,
md: theme.colors.aloes,
':w[800]': theme.colors.fog
}
backgroundColor: theme.colors.oak
}
}))
6 changes: 6 additions & 0 deletions example/src/examples/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { Minimal } from './Minimal'
export { MinimalWithCreateStyles } from './MinimalWithCreateStyles'
export { Theme } from './Theme'
export { Breakpoints } from './Breakpoints'
export { MediaQueries } from './MediaQueries'
export { Extreme } from './Extreme'
10 changes: 8 additions & 2 deletions src/createUnistyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import { getBreakpointFromScreenWidth, proxifyFunction, parseStyle, sortAndValid
import { useDimensions } from './hooks'

export const createUnistyles = <B extends Record<string, number>, T = {}>(breakpoints: B) => {
const sortedBreakpoints = sortAndValidateBreakpoints(breakpoints) as B
const sortedBreakpoints = sortAndValidateBreakpoints(breakpoints)

return {
createStyles: <S extends CustomNamedStyles<S, B>>(styles: S | CreateStylesFactory<S, T>) => styles as S,
createStyles: <S extends CustomNamedStyles<S, B>, X>(styles: S | CustomNamedStyles<S, B> | X | ((theme: T) => X | CustomNamedStyles<X, B>)): S | X => {
if (typeof styles === 'function') {
return styles as X
}

return styles as S
},
useStyles: <ST extends CustomNamedStyles<ST, B>>(stylesheet?: ST | CreateStylesFactory<ST, T>) => {
const theme = useContext(UnistylesContext) as T
const dimensions = useDimensions()
Expand Down
84 changes: 64 additions & 20 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,80 @@
import type { ImageStyle, TextStyle, TransformsStyle, ViewStyle } from 'react-native'
import type { CSSProperties } from 'react'
import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'
import type {
MatrixTransform,
PerpectiveTransform,
RotateTransform,
RotateXTransform,
RotateYTransform,
RotateZTransform,
ScaleTransform,
ScaleXTransform,
ScaleYTransform,
SkewXTransform,
SkewYTransform,
TranslateXTransform,
TranslateYTransform
} from 'react-native/Libraries/StyleSheet/StyleSheetTypes'

export type ScreenSize = {
width: number,
height: number
}

export type CreateStylesFactory<T, Theme> = (theme: Theme) => T
export type CreateStylesFactory<ST, Theme> = (theme: Theme) => ST

type StyleProperty<T, B extends Record<string, number>> = {
[key in keyof T]?: {
[innerKey in keyof B]?: T[key]
[K in keyof T]: {
[innerKey in keyof B]?: T[K]
} | {
[innerKey in `:w${string}` | `:h${string}`]?: T[key]
} | T[key]
[innerKey in string]?: T[K]
} | T[K]
}

export type CustomNamedStyles<T, B extends Record<string, number>> = {
[P in keyof T]:
| ViewStyle
| TextStyle
| ImageStyle
| TransformsStyle
| CSSProperties
| StyleProperty<ViewStyle, B>
| StyleProperty<ImageStyle, B>
| StyleProperty<TextStyle, B>
| (
(...args: Array<never>) => ViewStyle | TextStyle | ImageStyle | TransformsStyle | CSSProperties | StyleProperty<ViewStyle, B> | StyleProperty<ImageStyle, B> | StyleProperty<TextStyle, B>
)
type ShadowOffsetProps<B extends Record<string, number>> = {
shadowOffset: {
width: number | {
[innerKey in keyof B]?: number
},
height: number | {
[innerKey in keyof B]?: number
}
}
}

type TransformStyles<B extends Record<string, number>> =
PerpectiveTransform | StyleProperty<PerpectiveTransform, B>
| RotateTransform | StyleProperty<RotateTransform, B>
| RotateXTransform | StyleProperty<RotateXTransform, B>
| RotateYTransform | StyleProperty<RotateYTransform, B>
| RotateZTransform | StyleProperty<RotateZTransform, B>
| ScaleTransform | StyleProperty<ScaleTransform, B>
| ScaleXTransform | StyleProperty<ScaleXTransform, B>
| ScaleYTransform | StyleProperty<ScaleYTransform, B>
| TranslateXTransform | StyleProperty<TranslateXTransform, B>
| TranslateYTransform | StyleProperty<TranslateYTransform, B>
| SkewXTransform | StyleProperty<SkewXTransform, B>
| SkewYTransform | StyleProperty<SkewYTransform, B>
| MatrixTransform | StyleProperty<MatrixTransform, B>

type TransformProps<B extends Record<string, number>> = {
transform: Array<TransformStyles<B>>
}

type UnistyleView = Omit<Omit<ViewStyle, 'shadowOffset'>, 'transform'>
type UnistyleText = Omit<Omit<TextStyle, 'shadowOffset'>, 'transform'>
type UnistyleImage = Omit<Omit<ImageStyle, 'shadowOffset'>, 'transform'>

export type StaticStyles<B extends Record<string, number>> =
| (UnistyleView | StyleProperty<UnistyleView, B>)
| (UnistyleText | StyleProperty<UnistyleText, B>)
| (UnistyleImage | StyleProperty<UnistyleImage, B>)
& TransformProps<B> & ShadowOffsetProps<B>

export type CustomNamedStyles<T, B extends Record<string, number>> = {
[K in keyof T]: T[K] extends (...args: infer A) => unknown
? (...args: A) => StaticStyles<B>
: StaticStyles<B>
}
export type ExtractBreakpoints<T, B extends Record<string, number>> = T extends Partial<Record<keyof B & string, infer V>>
? V
: T extends (...args: infer A) => infer R
Expand Down
19 changes: 14 additions & 5 deletions src/utils/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export const parseStyle = <T, B extends Record<string, number>>(
screenSize: ScreenSize,
breakpoints: B
): T => {
const entries = Object.entries(style) as [[keyof T, CustomNamedStyles<T, B>]]
const entries = Object.entries(style) as [[
keyof T,
CustomNamedStyles<T, B> | Record<keyof B & string, string | number | undefined>]
]

return Object
.fromEntries(entries
Expand All @@ -72,7 +75,7 @@ export const parseStyle = <T, B extends Record<string, number>>(
if (isNestedStyle) {
return [
key,
parseStyle(value, breakpoint, screenSize, breakpoints)
parseStyle(value as CustomNamedStyles<T, B>, breakpoint, screenSize, breakpoints)
]
}

Expand All @@ -92,9 +95,15 @@ export const parseStyle = <T, B extends Record<string, number>>(
return [key, value]
}

const valueWithBreakpoint = value as Record<keyof B & string, string | number>

return [key, getValueForBreakpoint<B>(valueWithBreakpoint, breakpoint, screenSize, breakpoints)]
return [
key,
getValueForBreakpoint<B>(
value as Record<keyof B & string, string | number | undefined>,
breakpoint,
screenSize,
breakpoints
)
]
})
)
}
Loading