/* 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 }