Skip to content

Icon component example #112

Description

@nandorojo

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

Activity

  1. changed the title [-]Icon components[/-] [+]Icon component example[/+] on Jul 26, 2021
  2. nandorojo commented on Jul 26, 2021

    @nandorojo
    OwnerAuthor

    Theme intellisense 🤑

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

  3. norman-ags commented on Oct 27, 2021

    @norman-ags

    Thank you for this!

  4. nandorojo commented on Oct 27, 2021

    @nandorojo
    OwnerAuthor

    Happy to help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions