/* eslint-disable react/display-name */
import React, {
ComponentProps,
ComponentType,
createContext,
createElement,
forwardRef,
JSXElementConstructor,
ReactElement,
ReactNode,
useContext,
} from 'react'
import { ViewStyle, TextStyle, ImageStyle, StyleProp } from 'react-native'
/**
* Types definition
*
* @internal
*/
/**
* For overlap `DefaultTheme` use:
*
* styl.d.ts
*
* declare module 'react-native-styl' {
* export interface DefaultTheme {
* colors: {
* main: string;
* secondary: string;
* };
* }
* }
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DefaultTheme {}
/**
* Theme
*/
type StyleProperties = ViewStyle | TextStyle | ImageStyle
type StylesWithTheme
= (args: {
props: P
theme: DefaultTheme
}) => StyleProperties
type Styles
= StylesWithTheme
| StyleProperties
/**
* Props
*/
type DefaultProps = object & {
as?: ComponentType
children?: ReactNode
}
type Merge = Omit & P2
type ForwardRefExoticComponent = React.ForwardRefExoticComponent<
Merge<
E extends React.ElementType ? React.ComponentPropsWithRef : never,
OwnProps & { as?: E }
>
>
/**
* Polymorphic
*/
interface Polymorphic<
IntrinsicElement extends JSXElementConstructor,
OwnProps = {}
> extends ForwardRefExoticComponent {
| undefined>(
props: As extends JSXElementConstructor
? Merge
: Merge, OwnProps>
): ReactElement | null
}
/**
* Context
*
* Create a context to provide the theme,
* its initialize with an empty theme
*
* @internal
*/
const Context = createContext({ theme: {} })
/**
* Provider
*
* It receives a theme as argument, to provide custom
* variables to entire react component tree via context
*
* Usage:
* ```jsx
* const ThemeA: React.FC = ({ children }) => (
*
* {children}
*
* )
* ```
*/
const Provider: React.FC> = ({
children,
theme,
}) => createElement(Context.Provider, { value: { theme }, children })
/**
* useTheme
*
* Expose the `theme` as a React hook
*/
const useTheme = (): DefaultTheme => {
const { theme } = useContext(Context)
return theme
}
/**
* styl
*
* Given a component as first argument, it return a function
* which receives a callback with `theme` (from context) and `props`
* from component and should be returned a CSSProperties which
* will be passes as `style` attribute
*
* @example
* ```tsx
* const Title = styl(Text)(({ theme, props }) => ({
* paddingLeft: props.padding,
* color: theme.primary,
* }));
*
* const BigTitle = styl(Title)({ fontSize: 40 })
* ```
*/
const styl = >(Component: Comp) => <
Props extends DefaultProps = DefaultProps
>(
stylesProp: Styles
): Polymorphic => {
return forwardRef(function ForwardedComponent(props: Props, ref) {
// Get theme from context
const { theme } = useContext(Context)
// Spread props and inline styles
const { style: inlineStyles = {}, as, ...restProps } = props as Props & {
style: StyleProp
}
// Check type of argument
const styles =
typeof stylesProp === 'function'
? stylesProp({ props, theme })
: stylesProp
// Create component
return createElement(as || Component, {
...restProps,
ref,
style: [
styles,
...(Array.isArray(inlineStyles) ? inlineStyles : [inlineStyles]),
],
})
}) as any
}
export { styl, Provider, useTheme }