nandorojo / dripsy

🍷 Responsive, unstyled UI primitives for React Native + Web.

Home Page:https://dripsy.xyz

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Icon component example

nandorojo opened this issue · comments

I think I have a great system setup for using expo icons with Dripsy. Figured I'd share it here.

I wanted type-safe props that used my theme values for colors.

Note, you should replace useTheme with your own custom useTheme function, as detailed at #72 (comment)

Click here to see code
import React from 'react'
import Icon from '@expo/vector-icons/build/Ionicons'
import { styled, useDripsyTheme as useTheme } from 'dripsy' 

const StyledIcon = styled(Icon)({})

type Color = (string & {}) | keyof ReturnType<typeof useTheme>['colors']
type Name = React.ComponentProps<typeof StyledIcon>['name']

type Icon =
  | {
      name: Name
      size?: number
      color?: Color
    }
  | React.ReactElement
  | Name

type BaseProps = {
  size?: number
  color?: Color
  name: Name
}
export type IconProps = {
  icon: Icon
}

export type IconsBaseProps = BaseProps

export type AllIconProps = (IconProps | BaseProps) & {
  sx?: React.ComponentProps<typeof StyledIcon>['sx']
  selectable?: boolean
}

function isIconProps(props: AllIconProps): props is IconProps {
  return !!(props as IconProps).icon
}

export default function Ionicons(props: AllIconProps) {
  const { colors } = useTheme()

  let icon: Icon | undefined
  let color: Color = colors.text

  if (isIconProps(props)) {
    icon = props.icon
  } else {
    icon = {
      name: props.name,
      color: props.color,
      size: props.size,
    }
  }

  if (React.isValidElement(icon)) {
    return icon
  }
  // this exists for conditional props
  if (typeof icon === 'boolean') return null

  let name: React.ComponentProps<typeof StyledIcon>['name'] | null = null
  let size = 22
  if (typeof icon === 'string') {
    name = icon as React.ComponentProps<typeof StyledIcon>['name']
  } else if (icon?.name) {
    name = icon.name
    if (icon.size) size = icon.size
    if (icon.color) {
      color = colors?.[icon.color] ?? icon.color
    }
  }

  if (!name) return null

  return (
    <StyledIcon
      {...props}
      color={color}
      size={size}
      name={name}
      sx={props.sx}
    />
  )
}

This allows for nice, strictly-typed colors for your icons, and has intellisense for your theme's colors. It also has multiple modes of composition:

Direct props

<Ionicons color="text" name="close" />

Object props

<Ionicons icon={{ color: 'text', name: 'close' }} />

While more obscure, the object prop patterns allows you to easily compose other components that wrap the icon. For example, a Button component might have a iconPrefix prop.

type ButtonProps = {
  iconPrefix: IconProps['icon']
}

export function Button({ iconPrefix }: ButtonProps) {
  return <Ionicons icon={iconPrefix} />
}

Now, you can compose the button with ease:

<Button iconPrefix="close" />
<Button iconPrefix={{ name: 'close', size: 20 }} />
<Button iconPrefix={<CustomIconComponent />} />

Theme intellisense 🤑

Screen Shot 2021-07-26 at 5 44 34 PM

Thank you for this!

Happy to help