Skip to content

Zeego 2.0

Latest
Compare
Choose a tag to compare
@nandorojo nandorojo released this 18 Sep 15:38
· 18 commits to v2 since this release

A brand new zeego is here πŸͺ„

Zeego 2.0 drops react-native-web as a dependency. It now works with zero special configuration on Web. On iOS and Android, nothing has changed.

On Web, a lot has changed. Besides a few Zeego-only components, such as ItemIcon, ItemImage, ItemTitle, and ItemSubtitle, all components return Radix UI components directly with no custom UI or React Native Web wrappers. Previously, props were highly restricted. Now, you can use any DOM props on your Zeego components.

On iOS, Zeego still wraps react-native-ios-context-menu. You can safely upgrade that library as well as react-native-ios-utilities.

The same goes for Android and @react-native-menu/menu. I have hopes to someday drop this library in favor of a better looking menu for Android, while ideally keeping the implementation with Kotlin.

What's New?

  1. Zeego's web implementation is now an incredibly thin layer on top of Radix. In fact, almost every web component is simply a Radix component with the displayName set on it.
  2. You can use any props that Radix's components accept, including className, which React Native Web did not support.
  3. Zeego now supports Tailwind styles on Web.
  4. As a result Zeego is now compatible with @shadcn UI. But remember to always wrap custom components with create() for them to work on native.
  5. Zeego now works with zero config in any web project.
    a. If you aren't sure if you'll want to add native support in the future, just use Zeego anyway instead of Radix for menus, and then native just will work in the future.
    b. The main change here was turning .index.web.tsx files into the base index.tsx files, and using .ios and .android extensions for native projects.
    c. For React Native users/library authors, take note of this. Make your base index.tsx files the web one, and use platform file extensions for native. Why? Because every React Native app supports platform extensions (such as .native.tsx, .ios.tsx, etc.) On the other hand, Vite, Next.js, Remix, and the like have no idea that they should be looking for .web files. So setting that up suddenly requires adding file extensions to your build step.
    d. Going forward, I will try to implement this for any of my universal libraries: web-first, even if they're meant for React Native.
    e. This decision is the culmination of tons of pain trying to build Solito and getting React Native libraries to work on Web. It's usually full of config hell.

Breaking Changes

If you've kept all your styles for your menus in one place, upgrading should be somewhat easy. I'll do my best to be entirely comprehensive about any breaking changes.

  • Components no longer set any default styles on Web. Previously, they were wrapped with View from react-native-web.
    • To preserve the same styles from Zeego v1, you can apply the style reset from react-native-web on your components.
    • For example, React Native Web defaults components to display: flex and flexDirection: column. This behavior is gone. Web is truly unstyled.
  • Zeego component style props no longer accepts the result of StyleSheet.create. Please refactor these to plain JS objects.
  • Zeego component style props no longer accept arrays of styles. Please refactor these to spreading objects.
    • style={[style, isFocused && focusedStyle]} β†’ style={{ ...style, ...isFocused && focusedStyle }}
  • The style prop on most components has changed from ViewStyle to React.CSS properties. This aligns with React Native's plan on supporting web styles.
  • Trigger will now render a button on Web (since Radix does this) unless you pass asChild.
    • I recommend passing asChild and ensuring you have a valid React element as the direct descendant of Trigger.
    • <Trigger asChild
    • The one component where I considered an exception to unstyled elements is Trigger, since Trigger is the only UI element that renders on both web and native. But I plan on keeping the TypeScript type for its style as React.CSSProperties, and keeping it unstyled on Web. This may result in small inconsistencies between and Web and Native, since native will set the default styles for Trigger to display: flex, etc. You likely won't notice it, but it's worth mentioning.
    • To preserve the same styles from Zeego v1, you can apply the style reset from react-native-web on your Trigger by default. Or, even better, pass your own element as the direct child and use the asChild prop.
  • ItemImage
    • No longer uses Image from React Native, instead opting for img directly. You may need to adjust the styles accordingly.
    • No longer has a fadeDuration prop.
    • Expo Web Users: ItemImage longer works on out of the box Web with local images imported using require() or import, unless your Web bundler supports this for plain img tags. Under the hood, ItemImage simply renders img src=. To fix this, see the bottom of the release notes.
      • This does not affect Vite or Next.js.
  • The following components are now fully unstyled and render a plain span on Web. Previously, they rendered a Text from react-native-web, which applied default styles. These styles have been removed, including the default font: 'System 14px':
    • ItemTitle
    • Label
    • ItemSubtitle
    • To preserve the same styles that they had before, you can apply the Text style reset from React Native Web.
    • If you use lineHeight to style any of these components, be sure to change it to a string. react-native-web was turning numbers into strings ending in px, but this will no longer happen, resulting in line height numbers being treated as em instead of px.
  • If you used the rare animationKeyframes for your menu's animations, this will no longer work, as this is a react-native-web only feature.
  • menuify has been removed. This was deprecated in v1. Use DropdownMenu.create or ContextMenu.create instead.

Migration Guide

1. Update Trigger

On Web, Radix's Trigger component is a <button>. Chances are, this will render something which you don't want. To mitigate this, you can use a custom create with asChild.

import * as ContextMenu from 'zeego/context-menu'

// BEFORE: if you had this...
const ContextMenuTrigger = ContextMenu.Trigger

// AFTER: you might want to do this instead...
const ContextMenuTrigger = ContextMenu.create<
  React.ComponentProps<typeof ContextMenu.Trigger>
>(
  (props) => (
    <ContextMenu.Trigger {...props} asChild>
      <View aria-role="button">{props.children}</View>
    </ContextMenu.Trigger>
  ),
  'Trigger'
)

You can put anything you want inside of the custom ContextMenuTrigger there. The code above will preserve the prior behavior of not rendering a button.

2. Remove StyleSheet.create

If you're passing any styles directly to zeego components using StyleSheet.create, you should remove this and turn them into plain style objects.

3. Add styles for text components

If you render ItemTitle, ItemSubtitle, or Label, you may need to apply base styles to them. As long as you followed the docs previously and had all the components in one file, this should be easy to update.

Also, be sure to turn any style arrays into objects.

4. Update Item styles

You may want to preserve the v1 approach where Item rendered a View. To do this, simply create a custom component for Item.

const DropdownMenuItem = DropdownMenu.create(
  (props: ComponentProps<typeof DropdownMenu.Item>) => {
    return (
      <DropdownMenu.Item {...props}>
        <View
          style={
            {
              ...dropdownStyles.item, // you can pass your default styles here
              ...props.style,
            } as object
          }
        >
          {props.children}
        </View>
      </DropdownMenu.Item>
    )
  },
  'Item'
)

Please refer to the breaking changes guide for more info.

5. Update ItemImage (if you're using Expo Web / Metro Web)

If you are using Solito, Vite, Next.js, or most web-only frameworks, then this does not apply to you.

However, as of Zeego v2, locally-imported images will not work as-is with Metro Web/Expo Web.

To fix this, you should create a custom ItemImage component which wraps Image from react-native:

import { Image } from 'react-native'
import * as ContextMenu from 'zeego/context-menu'

const ItemImage = ContextMenu.create<
  React.ComponentProps<typeof ContextMenu.ItemImage>
>((props) => {
  return <Image {...props as any} />
}, 'ItemImage')