Giter Club home page Giter Club logo

dripsy's Introduction

dripsy's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dripsy's Issues

Support jsx pragma for custom components

I think I know how this could work, but I have to look into the theme ui code to be sure.

The hurdle is that we need to hook into the component, not just pass a raw className prop.

theme.customFonts doesn't seem to work

First of all, I am in love with this project! :)

I am trying to use custom fonts, and it seems, by defining font in the theme.customFonts, and then referencing it in the fonts doesn't seem to do its job.

So let's consider the following:

customFonts: {
    inter: {
      bold: "Inter-Bold",
      default: "Inter-Regular",
      normal: "Inter-Regaulr",
      "400": "Inter-Regular",
      "300": "Inter-Thin",
      "500": "Inter-Medium",
      "600": "Inter-Medium",
      "700": "Inter-SemiBold",
    },
  },
  fonts: {
    root: "inter",
  },

Of course, the assets are loaded, using the Font.loadAsync(fonts) in a custom provider.

This throws the following error:

fontFamily "inter" is not a system font and has not been loaded through Font.loadAsync.
If you intended to use a system font, make sure you typed the name correctly and that it is supported by your device operating system.

The weird thing, if i change it like this:

fonts: {
    root: "Inter-Bold",
  },

The font changes to bold. This works though both ob the device and in the browser, so that's good.

Any idea how to get around on this?

Thank you in advance!

Pseudo selectors

Is there a current plan to support pseudo selectors like hover, focus, etc. I know they aren't currently supported by react-native-web and are usually handled with react hooks similar to how the breakpoints are currently being handled. I think this could be a handy feature but I'm not 100% sure the best way this is currently being handled but I did read an article by Evan Bacon about handling them elegantly.

I think that a similar approach could be used to hijack the sx object being passed into the components that would key off of the basic pseudo-selectors (:hover, :focus, :active) and use the proper logic to allow for the desired effect.

Also is there any idea for what components should be implemented? I think it might be helpful to create a top-level outline of the different components that might be implemented and what it's in the scope of the design system from a library perspective.

Support custom breakpoints

The upcoming fresnel version doesn't currently support custom breakpoints. Rather than include it there, I figured it would be cleaner to get the fresnel branch merged, and then get this in separately.

Files to change:

  1. src/provider
  2. src/css/ssr-component
  3. Create src/utils/is-dev

1. src/provider

Here we change the API to look more like fresnel. People using dripsy will initialize it at the root of their app like this:

const { Provider, SSRStyleReset, theme } = createDripsy({ theme: myTheme })

This is a breaking change. Not ideal, so I want to make sure fresnel is the way we're going first. The reason this works is because we now share the SSRMediaQuery component through the app's context, rather than as a generalized import. This is necessary, since this component is created based on our custom breakpoints.

Code for src/provider (untested, just the idea):

TODO pass these breakpoints down to the CSS function in create-themed-component.

import React, { ComponentProps, ComponentType, useContext } from 'react'
import { ThemeProvider } from 'theme-ui'
import { createMedia } from '@artsy/fresnel'
import { Platform } from 'react-native'
import { defaultBreakpoints } from '../css/breakpoints'
import { remToPixels } from 'lib/typescript/utils/rem-to-pts'
import { MediaProps } from '@artsy/fresnel/dist/Media'
import { isDev } from '../utils/is-dev'

type DripsyOptions = {
  /**
   * DEPRECATED
   */
  ssr?: boolean
}

type Props = ComponentProps<typeof ThemeProvider>

export const dripsyOptions: DripsyOptions = {
  ssr: false,
}

type Context = {
  SSRMediaQuery: ComponentType<MediaProps<string | number, never>>
} & DripsyOptions

const DripsyContext = React.createContext<Context>(dripsyOptions as Context)

type CreateDripsyOptions = {
  theme: Props['theme']
}

export function createDripsy({ theme }: CreateDripsyOptions) {
  const getWebBreakpoints = () => {
    const breakpoints =
      (typeof theme === 'object' && theme?.breakpoints) || defaultBreakpoints
    const finalBreakpoints: number[] = []
    if (isDev()) {
      if (!Array.isArray(breakpoints)) {
        throw new Error(
          `[createDripsy]: if you want to pass custom theme breakpoints, please pass an array. Received: ${JSON.stringify(
            breakpoints
          )}`
        )
      }
    }
    breakpoints.forEach((breakpoint: number | string, index: number) => {
      if (typeof breakpoint === 'number') {
        finalBreakpoints[index] = breakpoint
      } else if (typeof breakpoint === 'string') {
        if (breakpoint.includes('rem') || breakpoint.includes('em')) {
          finalBreakpoints[index] = remToPixels(breakpoint)
        } else if (breakpoint.includes('px')) {
          finalBreakpoints[index] = Number(breakpoint.replace('px', ''))
        }
      } else if (isDev()) {
        console.warn(
          `[createDripsy] invalid breakpoint value: ${breakpoint}. Please pass a number, rem value, em value.`
        )
      }
    })

    const asDictionary: { [key: string]: number } = finalBreakpoints.reduce(
      (acc, next, index) => {
        return {
          ...acc,
          [`${index}`]: next,
        }
      },
      {}
    )

    return asDictionary
  }

  const { MediaContextProvider, Media, createMediaStyle } = createMedia({
    breakpoints: getWebBreakpoints(),
  })

  const ssrStyleResetCss = createMediaStyle()

  const SSRStyleReset = () => (
    <style
      type="text/css"
      dangerouslySetInnerHTML={{ __html: ssrStyleResetCss }}
    />
  )

  const Provider = (props: Omit<Props, 'theme'>) => {
    const ResponsiveContextProvider =
      Platform.OS === 'web' ? MediaContextProvider : React.Fragment
    const SSRMediaQuery = Platform.OS === 'web' ? Media : React.Fragment

    return (
      <ResponsiveContextProvider>
        <DripsyContext.Provider value={{ SSRMediaQuery }}>
          <ThemeProvider {...props} theme={theme} />
        </DripsyContext.Provider>
      </ResponsiveContextProvider>
    )
  }

  return {
    Provider,
    SSRStyleReset,
    theme,
    ssrStyleResetCss
  }
}

export const useIsSSR = () => !!useContext(DripsyContext).ssr
export const useSSRMediaQuery = () => useContext(DripsyContext).SSRMediaQuery

2. src/css/ssr-component

We use the context hook to access the connected SSRMediaQuery component.

import React, { ComponentProps, ComponentType } from 'react'
import { useSSRMediaQuery } from '../provider'
import { ResponsiveSSRStyles } from '.'

type Props<T> = {
  Component: ComponentType<T>
  responsiveStyles: ResponsiveSSRStyles
  style: unknown
}

export const SSRComponent = React.forwardRef(function SSRComponent<T>(
  { responsiveStyles, Component, style, ...props }: Props<T>,
  ref: T
) {
  const SSRMediaQuery = useSSRMediaQuery()
  return (
    <>
      {responsiveStyles.map((breakpointStyle = {}, breakpointIndex) => {
        const responsiveProps: Omit<ComponentProps<typeof SSRMediaQuery>, 'children'> = {}
        if (breakpointIndex === responsiveStyles.length - 1) {
          // for the last item in the array, it should go from here until larger sizes
          responsiveProps.greaterThanOrEqual = `${breakpointIndex}` as typeof responsiveProps.greaterThanOrEqual
        } else {
          responsiveProps.at = `${breakpointIndex}` as typeof responsiveProps.at
        }
        return (
          <SSRMediaQuery key={`ssr-media-query-${Component.displayName}-${breakpointIndex}`} {...responsiveProps}>
            <Component {...(props as unknown) as T} ref={ref} style={[style, breakpointStyle]} />
          </SSRMediaQuery>
        )
      })}
    </>
  )
})
// src/utils/is-dev

export const isDev = () =>
  typeof __DEV__ !== 'undefined'
    ? __DEV__
    : typeof process !== 'undefined'
    ? process.env.NODE_ENV !== 'production'
    : true

I'll make a separate PR for this after merging fresnel.

Consider Size component

I'm wondering if there should be a Size component, whose explicit role is to let us size items consistently without using webContainerSx. I made something like it in my app, and I'm wondering if it should be included in Dripsy directly.

Examples

For flex columns:

import { Size } from 'dripsy'

<Row>
  <Size flex={[null, 1]}>
    <Column />
  </Size>
  <Size flex={[null, 1]}>
    <Column />
  </Size>
</Row>

For widths:

<Size width={['50%', '33%']} />

Why

At the moment, the same thing is achieved by passing a webContainerSx prop. It's a bit cumersome and repetitive. I think it's a nice escape hatch prop to have, but for sizing, it makes more sense to have a specific solution.

Since the entire sx prop would not be supported, style props would be inlined on the Size component.

Code

On native, it would export a View from dripsy, like normal (with added props.) Nothing special happening on native.

size.tsx

import View from './view'

type SizeProps = Pick<SxStyleProp, 'flex' | 'width' | ...etc>

const Size = (props: SizeProps) => {
  return <View sx={props} />
}

On web, however, we can render a native div, and use sx with the JSX pragma from theme-ui.

size.web.tsx

/** @jsx jsx */
import { jsx } from 'theme-ui'

const RNWStyleReset = {...}

const Size = (props: SizeProps) => {
  return <div sx={{ ...RNWStyleReset, ...props }} />
}

Custom theme-based TypeScript Intellisense

Another goal of this library is to have a script that detects your theme, and builds TypeScript intellisense into your components based on your custom theme values. But this isn't made yet. I'd love help with it.

For example, if you have a theme like this:

export default {
  colors: {
    primary: 'blue',
    muted: '#e8e8e8',
  },
};

Then you would get intellisense like this:

^ The gif might take sec to load.

I think this could be achieved with some TypeScript wizardry, where a command line takes in the theme file and generates a typescript hidden file in your repository. This hidden file would globally declare values that override the sx prop, injecting in type suggestions. The command would probably be placed in a given project's postinstall, so that it runs every time you run yarn or npm install

@slorber recommended I look here.

I think generating something like this would work?

Xiy9U6kv

RN version mismatch in Expo 38

Hey,
when using Expo 38 I am getting

console.error: React Native version mismatch.

JavaScript version: 0.61.5
Native version: 0.62.2

This is caused because Dripsy uses older RN version than Expo.

Can you please bump RN version to 0.62.2?
Or is there some other way to avoid this error?

Thanks

Flatlist Generics

Hey,

i'm trying to use dripsys FlatList with Typescript. The following is typed correctly when importing FlatList from react-native but not from dripsy itself:

<FlatList
  data={stacks}
  renderItem={({ item }: { item: Stack }) => (
    <></>
  )}
  keyExtractor={(item: Stack) => item.id}
/>

Type '(item: Stack) => string' is not assignable to type '(item: unknown, index: number) => string'.

The problem seems to be that dripsies FlatList is explicitly declared with a generic unknown. Is there anything i'm missing tho?

[RFC] Universal animation component library

This issue is more of a RFC.

What

There is an opportunity to make something like react-native-animatable that is powered by Reanimated 2.x. I think it would follow an API similar to CSS transitions. I'm looking to framer/motion for inspiration on that, since it's super clean and easy to use.

How

My top priority would be to achieve transitions at the component level, without any hooks, and with the least amount of code. It should be as simple as possible โ€“ no config. This seems like a great DX:

const color = loading ? 'blue' : 'green'

<Text transitionProperty={'color'} sx={{ color }}/>

Under the hood, it would only use reanimated on native. Components would intelligently transition properties you tell it to. I don't have experience with Reanimated 2 yet, but it seems like they provide hooks that would make this remarkably doable. We could even default components to have transitionProperty: all, such that all you have to write is this:

const color = loading ? 'blue' : 'green'

<Text sx={{ color }}/>

...and you get smooth transitions. Not sure if that's desired, but just spit balling.

We could use CSS transitions and keyframes on web, since RNW supports that. I'm not married to a specific method of solving this problem, though, so maybe Reanimated will work on web too.

In addition to property transitions, animations would be great:

<View from={{ opacity: 1 }} to={{ opacity: 0 }} />

Today

With react native web, CSS animations are really straightforward:

const animationKeyframes = { from: { opacity: 0 }, to: { opacity: 1 } }
<View 
  sx={{ animationKeyframes }}
/>

Same goes with transitions:

const color = loading ? 'blue' : 'green'

<Text sx={{ color, transitionProperty: 'color' }}/>

The problem is that this code is not universal. It only works on web. In my opinion, using Platform.select or Platform.OS === 'web' is an anti-pattern when it comes to styles. That logic should all be handled by a low-level design system. Dripsy has made strides for responsive styles in this regard. Smooth transitions with a similar DX would be a big addition.

Final point: good defaults

In the spirit of zero configuration, this library should have great defaults. It should know the right amount of time for the ideal transition in milliseconds. I would look to well-designed websites to see what the best kind of easing function is, and this would be the default.

There is a plethora of "unopinionated" animation projects for React Native. There are few that are dead-simple, highly-performant, and have professional presets from the get go. I don't want to create an animation library. Only a small component library that animates what you want, smoothly, without any setup.

Spacing in docs

Small one, should the docs read

space: [10, 12, 14]

rather than

spacing: [10, 12, 14]

SyntaxError: Cannot use import statement outside a module at wrapSafe (internal/modules/cjs/loader.js:1117:16)

Hi hi

I'm getting this error at build in a clean project bootstrapped with Expo + NextJS... ๐Ÿ’ฆ

import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1117:16)
    at Module._compile (internal/modules/cjs/loader.js:1165:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1221:10)
    at Module.load (internal/modules/cjs/loader.js:1050:32)
    at Function.Module._load (internal/modules/cjs/loader.js:938:14)
    at Module.require (internal/modules/cjs/loader.js:1090:19)
    at require (internal/modules/cjs/helpers.js:75:18)

    at Object.<anonymous> (/mnt/c/Users/Eon/dev/dripsy/e10-app/node_modules/dripsy/lib/commonjs/css/index.js:1:958)
    at Module._compile (internal/modules/cjs/loader.js:1201:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1221:10)
    at Module.load (internal/modules/cjs/loader.js:1050:32)
    at Function.Module._load (internal/modules/cjs/loader.js:938:14)
    at Module.require (internal/modules/cjs/loader.js:1090:19)
    at require (internal/modules/cjs/helpers.js:75:18)

    at Object.<anonymous> (/mnt/c/Users/Eon/dev/dripsy/e10-app/node_modules/dripsy/lib/commonjs/css/create-native-themed-component.js:1:492)
    at Module._compile (internal/modules/cjs/loader.js:1201:30)

Any thoughts? ๐Ÿค”

Here's my package:

{
  "name": "dripsy-test",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "next",
    "eject-next": "next-expo customize",
    "eject": "expo eject"
  },
  "dependencies": {
    "dripsy": "^0.5.2",
    "expo": "~37.0.3",
    "next": "^9.5.2",
    "next-compose-plugins": "^2.2.0",
    "next-transpile-modules": "^4.1.0",
    "react": "~16.11.0",
    "react-dom": "~16.11.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
    "react-native-web": "~0.12.0"
  },
  "devDependencies": {
    "@expo/next-adapter": "^2.1.25"
  },
  "resolutions": {
    "@theme-ui/core": "0.4.0-rc.1",
    "@theme-ui/css": "0.4.0-rc.1",
    "@theme-ui/mdx": "0.4.0-rc.1"
  }
}

The resolutions are there as a temporary workaround to issue #26 as theme-ui is broken as of this writing. ๐Ÿฆ‘

Nando: Amazing project! Thank you ๐Ÿท ๐Ÿท

Combine multiple variants/modifiers

The option to use multiple variants / modifiers has been discussed at length on theme-ui. However, to my understanding, this hasn't been implemented anywhere yet. While I like to stay within the theme-ui spec for this library, I'm not sure if it's worth waiting for an implementation on their end.

I think this issue could serve as a way to think through the best way to do multiple variants (or modifiers) on components. An array syntax is a bit tricky, since it's already used for responsive styles. An alternative could be comma-separated strings, but it's not pretty, and there's no type support there (unless we use some intense TS 4.1 template literals.)

Related discussions from theme UI: system-ui/theme-ui#403

Since we're considering building opinionated UI libraries on top of Dripsy, this seems like essential functionality to avoid tons of boilerplate.

To illustrate the need, consider the difficulty of building all the buttons on this page with multiple modifiers vs. without: https://react.geist-ui.dev/en-us/components/button

FlatList doesn't properly infer types

Ex.

This works:

import { FlatList } from "react-native";
const CreateOptions = [
  {
    id: "1",
    title: "Supplement",
    background: {
      uri:
        "https://images.unsplash.com/photo-1565071783280-719b01b29912?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1650&q=80",
    },
  },
];

<FlatList
        showsHorizontalScrollIndicator={false}
        horizontal
        data={CreateOptions}
        renderItem={({ item, index }) => (
          <Pressable sx={{ m: "md" }}>
            <Image
              sx={{ height: 180, width: 120, borderRadius: "base" }}
              source={item.background}
            />
          </Pressable>
        )}
        keyExtractor={(item) => item.title}
 />

This produces the following, for the object item typescript errors with Object is of type 'unknown':

import { FlatList } from "dripsy";
const CreateOptions = [
  {
    id: "1",
    title: "Supplement",
    background: {
      uri:
        "https://images.unsplash.com/photo-1565071783280-719b01b29912?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1650&q=80",
    },
  },
];

<FlatList
        showsHorizontalScrollIndicator={false}
        horizontal
        data={CreateOptions}
        renderItem={({ item, index }) => (
          <Pressable sx={{ m: "md" }}>
            <Image
              sx={{ height: 180, width: 120, borderRadius: "base" }}
              source={item.background}
            />
          </Pressable>
        )}
        keyExtractor={(item) => item.title}
 />

Do you see this turning into a full fledged component library/design system? Steps for contributing?

Really love the idea of this and would love to help in any way shape or form if there are plans to turn this into a component library with a design-system or not. Do you have any steps for contributing? Would love to help when there needs to be more things done.

I'm planning on using this for a gatsby project currently so I'm also curious how that will work. I saw you have instructions on Next.js integration.

Are color modes actually supported on native?

The readme mentions that dark mode / color modes are supported, but I can't seem to make it work. Dripsy doesn't export a useColorMode hook and initialColorModeName and useColorSchemeMediaQuery seem to have no effect. Am I doing something wrong?

I think full support should be doable by now, using the Appearance API.

Dripsy v1 release notes

Dripsy v1 has a number of exciting additions, some minor breaking changes, and styling differences. I'm calling it v1 so that it's clear that there are major changes to the expected web behavior โ€“ namely that it works with SSR and has an escape hatch.

To use it before it's released, use yarn add dripsy@fresnel-2. Please test it out and report any issues you find which aren't specified in this doc.

Dripsy for iOS and Android is basically untouched by this update. If you use Dripsy on web, this update is very relevant.

I recommend reading #19 and #14 for context.

[feature] SSR Support

This section describes how the new web behavior works on Dripsy, and why it works for server side rendering. At the very least, check out "changes in web behavior" below.

Dripsy now fully supports SSR. Please refer to the readme for the simple installation steps.

  • Next.js tested
  • Normal RNW tested
  • Gatsby tested (coming soon)

Ever since React Native Web deprecated the classname, the path to responsive styles built in React Native has been a challenge. Without the className prop, we can only use JS dimensions to determine which media breakpoint to display. This is the right direction for the ecosystem in general โ€“ JS-based styles that are consistent on all platforms.

However, JS-only dimensions don't work with SSR, since the server doesn't know the screen size with certainty.

The solution used in this update is to wrap every responsive element rendered on the web with a div at each breakpoint.

To see how this is achieved, please refer to the docs for Fresnel.

If your app has 4 breakpoints, then your component will be rendered 4 times next to each other, each time with a div wrapping it. We require a div so that we can use CSS.

Each div's children conditionally render depending on the how big the screen size is. The first paint, which can occur on the server, renders every breakpoint. We rely on the CSS className for each div to hide ones from the wrong breakpoint. Once the div has mounted, it unmounts its children if their styles don't fit the current breakpoint. This is achieved thanks to Fresnel, and was tested extensively at #14, #19, #13.

The benefit of this approach is that there is no flash of unstyled content. We use CSS on the first paint, and the match-media API afterwards.

Even though each component renders multiple times, the div containing it unmounts its children when it needs to. This means that useEffect and other asynchronous effects still only run once, as intended. While this does result in many more DOM nodes, it's been stress tested in real-world apps with great performance.

[important] Changes in web behavior

This section is very important, if you're upgrading I recommend reading it.

This section discusses the webContainerSx prop addition. This prop is an escape hatch for the occasional unintended web behavior for flexbox and percentage widths.

TLDR: If you find that your flex or width values aren't working properly on web, try adding the styles to the webContainerSx prop.

Ok, onto the explanation.

It is important to understand what it means that we will be using @artsy/fresnel, as mentioned in the SSR explanation above. When we have responsive styles on web, we are wrapping our React Native element with a div. This means that sometimes, your styles might not show work.

Basically, this only matters for things like percentage widths and flexbox stretching. The times it matters are rare, but they're non-zero, so I'm going to explain in detail.

Take this example. What do you think this should look like?

<View sx={{ flexDirection: 'row', height: 500 }}>
  <View sx={{ bg: 'blue', height: '100%', flex: [1] }} />
  <View sx={{ bg: 'red', height: '100%', flex: [1] }} />
</View>

You might expect that it would look like this, right?

Frame 1 (6)

Well, turns out it doesn't, exactly.

This short video explains why: https://www.loom.com/share/997c9af614ea4b5392831bfc6db54cd0

Basically, any time you use an array style (in this case, width: [50%],) Dripsy wraps your item in a div. While wrapping your item in a div doesn't usually matter, in the case of percentage widths, flex: 1, etc., it does. Since our blue and red boxes are both wrapped with divs, and the divs don't know to stretch the container, they don't. Thus, our View is 50% width and 100% height of a div whose size is unspecified.

In those cases, you can style the container div with the webContainerSx prop, as seen in the video.

This is the solution for the example above:

<View sx={{ flexDirection: 'row', height: 500 }}>
  <View webContainerSx={{ flex: [1] }} sx={{ bg: 'blue', height: '100%', flex: [1] }} />
  <View webContainerSx={{ flex: [1] }} sx={{ bg: 'red', height: '100%', flex: [1] }} />
</View>

Here we styled the container div for web. All set!

If you never use responsive array styles, then this won't affect you. Once a component has a single array style, though, this does start to affect how it is rendered.

Rely on webContainerSx if you ever find a responsive width/responsive height/flexbox value doesn't work properly on web. And that is all!

[feature] Add styled function

This is a nicer API than createThemedComponent in my mind, and with a cooler name. They do the exact same thing though, so no need to change if you're already using createThemedComponent.

import { View } from 'react-native'
import { styled } from 'dripsy' 

const StyledView = styled(View)({
  flex: 1,
  bg: 'primary'
})

Recursively styling Dripsy components doesn't work yet, see #33 for that. For now, you can only pass native components to styled.

[feature] Add themeKey to components

const theme = {
  cards: {
   primary: {...}
  }
}

...

<View 
  themeKey="cards"
  variant="primary"
/>

Before, we had to use the createThemedComponent to give a component a themeKey.

[minor] ThemeProvider renamed to DripsyProvider

Change this in your imports after updating.

[breaking] changes

  • See the web behavior above.
  • There will soon be breaking changes for how Dripsy is initialized at the root. This will be necessary to allow custom breakpoints. Keep an eye out for this at #25

[coming soon]

  • Custom breakpoints (this will require a breaking change)
  • JSX pragma that allows any component to be styled
  • Custom style props for custom components that accept more than just the style prop
  • Recursively create dripsy components

I'm also looking into custom theme key typescript suggestions still.

Will this also work across mobile and web projects?

Will the library work with both rnw and normal react-native? Would love to get a project going that has a bunch of code reuse and this seems like the way to go. Thanks for creating this, I was looking for a similar solution for a while with no avail.

On a side note would love to help work on the library if you need any.

Allow custom components w/ multiple style props to accept sx styles

It's common for custom components to receive multiple style props.

<Chat 
  bubbleStyle={{ bg: 'primary' }}
  textStyle={{ color: 'text' }}
/>

It would be nice to have an API that lets these custom components take advantage of the sx prop.

How

Option 1: sx hook.

One option is to just expose the sx via a hook, which has been discussed on #12 (I believe). It'd just be a connected css function.

const sx = useSx()

<Chat 
  bubbleStyle={sx({ bg: ['primary', 'secondary'] })}
  textStyle={sx({ color: 'text' })}
/>

Unfortunately, it looks like this would break the implementation of Fresnel on web, unless we had some serious magic going on with a custom pragma. I think the behavior would just get confusing if the pragma is required with a hook.

Option 2: createThemedComponent

I haven't thought through what it would actually look like, but maybe createThemedComponent could receive an array of prop keys that should also receive sx styles.

const ThemedChat = createThemedComponent(Chat, {
  sxProps: ['bubbleStyle', 'textStyle'] // keyof ComponentProps<typeof Chat>
})

...
<ThemedChat 
  bubbleStyle={{ bg: 'primary' }}
  textStyle={{ color: 'text' }}
/>

Is there a way we could edit the props on TypeScript such that you still get typed suggestions for bubbleStyle and textStyle? I'm sure there's a way...


I wanted to write down my thoughts before forgetting.

[bug] Style value conflicts with alias

What do we do if the style value we want to pass conflicts with the key for one of our aliases?

const theme = {
  space: [0, 4, 8, 16, 32, 64, 128, 256, 512]
}

If we have the above theme, then calling space[1] = 4, and space[6] = 128.

Situation 1:

Let's say I didn't want to use the 128 value, but instead, I just wanted to pass padding of 6 pixels.

<View
  sx={{
    padding: 6 // returns 128
  }}
/>

So, how do I get 6 in there?

The answer: I could just pass the correct value to style instead of sx. However, style currently is treated as an sx prop. Since this doesn't make much sense, I should get rid of this line and just use the raw style object.

<View
  style={{
    padding: 6 // returns 128, should return 6 after refactor
  }}
/>

Situation 2:

While that solves situation 1, what if I want to use theme values to calculate 6px?

In my case, I want to grab space[1] (i.e. 4px), and multiply it by 1.5 to get 6px.

<View
  sx={{
    // this is 4 * 1.5 = 6
    // but wait, it gives 128, from the theme!
    padding: (theme) => theme.space[1] * 1.5
  }}
/>

Alright, so once again, I end up with 128px, not 6px.

To borrow from situation 1:

const { theme } = useThemeUI()

<View
  style={{
    padding: theme.space[1] * 1.5 // this would solve it after the refactor of changing sx -> style
  }}
/>

Hmm...there's still one edge case, though. What if I wanted to use that value in a responsive array?

const { theme } = useThemeUI()
<View
  sx={{
    padding: [theme.space[1] * 1.5, theme.space[2] * 1.5]
  }}
/>

In this case, the only way to use responsive arrays is via the sx prop. But if I use the sx prop, I am stuck with theme values, and can't use the 6px work-around from web.

What does theme-ui do?

This problem is easily solved in theme-ui. You can just pass a raw '6px' string, and boom, you're done.

But React Native doesn't allow '6px', because it only uses numbers.

Solution

Could we edit the css function to check for <number>px values, and turn these into numbers to avoid making RN angry?

If so, then this would work:

<View
  sx={{
    padding: theme => [`${theme.space[1] * 1.5}px`, `${theme.space[2] * 1.5}px`]
  }}
/>

Update dimensions listener?

Right now, I'm using a useDimensions hook that updates whenever the screen size is updated. It might make more sense to set up a custom listener in an effect, and only update the state when the actual breakpoint has changed, rather than for every pixel change.

Not super urgent, but it could help prevent re-renders if you have tons of responsive components setup.

Responsive variants take precedence over inline sx styles

Issue

Works as intended. DripText gets the styles fonts-size: 2px:

const theme = {
  text: {
    primary: {
      fontSize: 40,
    },
  },
}

...

<DripText variant="primary" sx={{ fontSize: 2 }}>
    joi
</DripText>

Doesn't work as intended. DripText maintains the responsive styles e.i 40px, 50px, 60px:

const theme = {
  text: {
    primary: {
      fontSize: [40, 50, 60],
    },
  },
}

...

<DripText variant="primary" sx={{ fontSize: 2 }}>
    joi
</DripText>

Other notes

  • When inline sx fontSize is changed to a string value e.i "2px" I get the same behavior.
  • Reproduced on example app and in my own applications

Support box-shadow

Some style features will require editing the Sx prop typescript object, as well as the css file for react native.

Doesn't work with Next.js

If you try to use this in an expo + Next.js project, you see an error stating that the prop differed on server and client. This appears to be a next.js issue.

Using styled API causes children Inputs to lose focus (May be from re-renders)

Description

When using the styled API inputs lose focus preventing the user from typing.

This does not however happen with the createThemeComponent API.

Notes

I'm currently using Formik to handle forms with React Native. I'm not sure of the impact of this on the bug.

Example

Causes inputs to lose focus:

const Card = styled(View, {
  themeKey: "cards",
  defaultVariant: "primary",
})({});

Doesn't cause inputs to lose focus:

const Card = createThemedComponent(View, {
  themeKey: "cards",
  defaultVariant: "primary",
});

Tree shaking

Using next's webpack analyzer, I noticed that all of RNW is getting imported into every one of my pages. I still need to investigate this further, but I wonder if dripsy is contributing to RNW's tree shaking not working properly.

Styled components faces this issue: styled-components/styled-components#2797 (comment)

Does RNW's Babel plugin work on node module imports too? For instance, when we import X from react-native in dripsy, does it pick that up? I'm not familiar with Babel/webpack etc.

For now, I'll add sideEffects: false to the package.json to see if that does anything.

sx prop re-renders

I really like the API of being able to pass styles directly to components inline using sx.

The one downside is that it can trigger re-renders on every parent render, since the styles create a new sx object prop.

A few possible solutions:

  1. Follow a styled components type approach, and create a styled function:
import { styled, Box } from 'dripsy'

const Page = styled(Box)(props => ({
  backgroundColor: ['primary', 'secondary']
}))
  1. Could there be some sort of smart memoization / deep comparison that checks the sx prop, and doesn't re-render if it matches? I am usually very, very way of deep comparison, as it's not recommended by react in almost all cases (see here.)

One option could be to JSON.stringify the sx prop and compare if this changed. The problem with that approach is that styles can a) contain animated values, and b) have functions:

// not JSON serializable...
<View sx={{ height: theme => theme.sizes[1] }} />

Basically, it would be nice if there were no concerns about passing style props inline for performance. I haven't done extensive testing on this, but I imagine it could have some downsides.

Publish packages on pull request

Not sure what the best CI solution is, but it would be nice to automatically publish to NPM from a Github action or something like that.

transform doesn't work

Since the transform style prop is an array, it's breaking on usage. One solution will be to ignore the style prop altogether, so I'll fix this shortly.

Recursively creating themed component doesn't work

What

There's a bug with createThemedComponent (and thus styled), where passing a component made by dripsy to it doesn't work.

import { styled, View } from 'dripsy'
import * as Native from 'react-native'

const Container = styled(View) // ๐Ÿšจerror
const Wrapper = styled(Native.View) // ๐Ÿ‘ works

Same goes for createThemedComponent:

import { createThemedComponent, View } from 'dripsy'
import * as Native from 'react-native'

const Container = createThemedComponent(View) // ๐Ÿšจerror
const Wrapper = createThemedComponent(Native.View) // ๐Ÿ‘ works

Why

First, this bug needs to be fixed for the custom pragma from #31 to get merged.

Second, I've really enjoyed using the styled function from the fresnel-2 branch in my real-world app. styled is a wrapper around createThemedComponent with a cooler name and syntax.

While using the sx prop is the central API, I would like to treat styled syntax as a first-class citizen, too.

import { styled } from 'dripsy'

const Container = styled(View)({
  maxWidth: 10
})

I've enjoyed the API of naming UI elements outside of the component scope. It prevents cluttering the return statement with styles.

With styled-components, this was pretty annoying, because I had to pass a function to my styles, redeclare prop types, and it was just a mess. But with dripsy, it's easy; I can still pass an sx prop to the component returned by styled if it needs render-specific variables in its styles, like so:

const Container = styled(View)({
  maxWidth: 10
})

const Box = ({ height }) => <Container sx={{ height }} />

How

I'm not fully sure why this bug is exists, but I could see issues with recursively wrapping components in HOCs.

I think an easy solution would be to add something like a static isDripsyComponent field to any component returned by createThemedComponent. Then, in createThemedComponent, we check if it already is a dripsy component. If it is, we just return the component itself and forward the props down โ€“ nothing special.

Web Container Styled being applied to all fresnel divs when using responsive values

Description

When using the dripsy fresnel-2 branch the webContainerSx prop is applying styles to all fresnel divs, not just the current breakpoint when multiple fresnel divs a present.

Example

Component:

<Container
      webContainerSx={{ flex: 1 }}
      sx={{ justifyContent: "center", alignItems: "center", flex: 1 }}
    >
    <Text>Here</Text>
</Container>

Theme value:

container: {
  px: ["base", "large", "huge"],
},

Add improved custom font support

Custom fonts in React Native are a pain. In CSS, you can do this:

body {
  font-family: arial;
}

In React Native, you have to set the custom font on each component. This is a terrible DX.

As of Dripsy 1.3.1, there are marked improvements for this. You can simply set a global text font by editing the text.body field in your theme:

export default {
  text: {
    body: {
      fontFamily: 'arial'
    },
    h1: {
      fontFamily: 'arialBold'
    }
  }
}

Or you could do it this way:

export default {
  fontFamilies: {
    body: 'arial',
    heading: 'arialBold'
  },
  text: {
    body: {
      // as of 1.3.1, text.body edits all <Text /> by default
      fontFamily: 'body'
    },
    h1: {
      fontFamily: 'heading'
    }
  }
}

Open issue

But what about fontWeight: bold? Well, there is no concept of @fontface in react native. What does this mean? Rather than have a bold version of arial, if you want a fontWeight to apply to a custom font, you have to spell out the custom font name.

// works...
<Text sx={{ fontFamily: 'arialBold' }}></Text>
// doesn't work.
<Text sx={{ fontFamily: 'arial', fontWeight: 'bold' }}></Text>

I don't think this makes any sense. What if you want to change from arial to something else? Should you really have to change the string arialBold everywhere in your app?

I think a nice solution could be to have a customFonts value in your theme that, given a font family name and fontWeight, returns a specific font.

const theme = {
  customFonts: {
    arial: {
      bold: 'arialBold',
      '500': 'arialSemiBold',
    }
  }
}

Then, you could just do this:

<Text sx={{ fontFamily: 'arial', fontWeight: 'bold' }}></Text>

...or, even better:

const theme = {
  customFonts: {
    arial: {
      bold: 'arialBold',
      '400': 'arial',
      default: '400' // if no font family is specified, we use this, or fallback to 400
    }
  },
  fontFamilies: {
    body: 'arial',
    heading: 'arialBold'
  },
  text: {
    body: {
      fontFamily: 'body',
      fontWeight: '400'
    }
  }
}

And then you use it in your components like normal!

<Text sx={{ fontWeight: 'bold' }}>This uses arialBold!</Text>

Heading components (H1,H2,H3,H4,H5,H6) are not mapping as per documentation

Hi there,

In the documentation on the README.MD file we have the following section:

export default {
  colors: {
    text: '#000',
    background: '#fff',
    primary: 'tomato',
  },
  spacing: [10, 12, 14],
  fontSize: [16, 20, 24],
  text: {
    h1: {
      fontSize: 3, // this is 24px, taken from `fontSize` above
    },
    p: {
      fontSize: 1, // & this is 16px, taken from `fontSize` above
    },
  },
}

After testing this, I found that:

h1: {
      fontSize: 3, // this is 24px, taken from `fontSize` above
    },

For me, the 3 is used as the font size in pixel (3 pixels) and not mapping to the fontSize set (fontSize: [16, 20, 24],) above.

Is this expected? When I avoid putting the H fontSize styling into my theme, the defaults are working correctly when switching H1 to H2, to H3 and so on...

If this is expected, how would one map other text components if Heading (H) components are just defaulting.

Responsive styles from multiple places don't merge properly

What

Responsive style arrays work totally fine. However, if you use them in styled, and then in the sx prop too, they don't merge properly.

const Box = styled(View)({
  height: [100]
})

<Box sx={{ height: 150 }} />

The example above should end up with a height of 150, but it doesn't. If you do this, however, it does work:

<Box sx={{ height: [150] }} />

Why? Because sx is the dominant style input, but responsive styles take precedent at the moment. So it goes normal styles, merged in order -> any responsive styles, merged in order. The intended behavior is normal styles + responsive styles => merged in order.

What's happening

It looks like the merging of responsiveSSRStyles on web isn't merging the different breakpoints, but rather just using the senior-most set of responsive styles. That is, if you use any responsive styles in sx, they will currently override all responsive styles declared in styled. This is of course not intended, and should be fixed. Styles should merge in order in general.

Solution

The problem is that mapPropsToStyledComponent simply spreads the objects. Instead, it should explicitly create a responsiveSSRStyles array which matches the length of the total number of possible breakpoints. Then, for each index, it should spread the responsive styles from each style input.

This all goes down at https://github.com/nandorojo/dripsy/blob/master/src/css/index.tsx#L546.

Add theme presets

Not urgent, but useful.

import { ThemePreset, mergeThemes } from 'dripsy'
import theme from './theme

export default mergeThemes(ThemePreset.Google, theme)

Button styles not applied

Seems like non of the sx properties are applied to the exposed Button component of dripsy.

import { Button } from 'dripsy'
<RNButton
  title={title}
  onPress={onPress}
  sx={{
    color: 'text',
    backgroundColor: 'primary',
    height: 40,
  }}
/>

None of those properties is applied.

"primary" is not a valid color or brush
- node_modules/react-native/Libraries/LogBox/LogBox.js:117:10 in registerWarning
- node_modules/react-native/Libraries/LogBox/LogBox.js:63:8 in warnImpl
- node_modules/react-native/Libraries/LogBox/LogBox.js:36:4 in console.warn
- node_modules/expo/build/environment/react-native-logs.fx.js:18:4 in warn
* http://10.0.10.54:19001/src/App.tsx.bundle?platform=ios&dev=true&hot=false&minify=false:221741:16 in extractBrush
- node_modules/react-native-svg/src/lib/extract/extractFill.ts:20:4 in extractFill
- node_modules/react-native-svg/src/lib/extract/extractProps.ts:77:2 in extractProps
- node_modules/react-native-svg/src/lib/extract/extractProps.ts:150:24 in extract
- node_modules/react-native-svg/src/elements/Path.tsx:14:27 in render
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:12478:21 in finishClassComponent
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:12403:43 in updateClassComponent
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:19181:22 in beginWork$1
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:18085:22 in performUnitOfWork
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:18013:38 in workLoopSync
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:17977:18 in renderRootSync
- node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:17674:33 in performSyncWorkOnRoot

Component Outline

Here's a non-comprehensive outline of potential components that could be included in Dripsy:

HTML Elements - @expo/html-elements

  • H1
  • H2
  • H3
  • H4
  • H5
  • H6
  • H6
  • A
  • P
  • Others?

React Native

  • View
  • ScrollView
  • FlatList
  • Text
  • SafeAreaView
  • ActivityIndicator
  • ImageBackground
  • Image
  • KeyboardAvoidingView
  • Picker
  • Pressable - Needs RN 63.0
  • SectionList
  • StatusBar
  • TouchableHighlight
  • TouchableOpacity
  • TouchableWithoutFeedback
  • VirtualizedList
  • Do we want the entire library or only specific ones?

Theme-UI Inspired

  • Flex
  • Checkbox
  • Slider
  • Switch
  • Text
  • Heading
  • Badge
  • Avatar
  • Divider
  • Progress
  • Link
  • Container
  • Select
  • Textarea
  • TextInput
  • SVG

Additional

  • Segmented Control
  • Modal
  • Additional Components TBD

Potential Questions

  • How much should we rely on outside dependencies? (e.i @react-native-community/checkbox, etc.) A lot might be included in Expo by default.
  • Can we add a lot of components without making it too opinionated?
  • Should we have heading + HTML elements, Link + A, or consolidate them somehow?

Note that this list is non-comprehensive and any feedback on to what it should be added/removed is welcomed to improve the list.

Add Pressable Component

Would be nice if dripsy could export the Pressable component from react-native.

Sometimes the A component is not sufficient and one would like to use the Pressable.
If i get it right, its basically a View component.

import { Pressable as RNPressable } from 'react-native'
import { createThemedComponent } from 'dripsy'

export const Pressable = createThemedComponent(RNPressable)

works fine for me.

Make a separate website for docs

Add recipes, component list, examples, and such on a separate website.

If anyone has input on what the best way to manage OS docs is, let me know here. I'm liking dokz.site. Ideally, it would be on Gatsby or Next.js, so we can use MDX to show the components.

Add a styles.create() function

Add an API similar to React Native's StyleSheet.create() to let you create styles outside of a component. This way, you still get intellisense / theme integration in styles, but you don't need to declare them in your component, which could result in unnecessary re-renders.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.