Follow @fernandotherojo
nandorojo / dripsy Goto Github PK
View Code? Open in Web Editor NEW๐ท Responsive, unstyled UI primitives for React Native + Web.
Home Page: https://dripsy.xyz
License: MIT License
๐ท Responsive, unstyled UI primitives for React Native + Web.
Home Page: https://dripsy.xyz
License: MIT License
Follow @fernandotherojo
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.
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!
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.
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:
src/provider
src/css/ssr-component
src/utils/is-dev
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
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.
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.
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%']} />
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.
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 }} />
}
Wondering if you would consider native support for this in the component? (works with RN Web too). Am trialing it already with Dripsy to success, but native support would be a win i think.
For example when applying as="section" to a component. This automatically applies display="block" styling to the section and overrides the default view styles.
One potential solution may be to leverage a component creation method similar to @expo/html-elements or use them directly.
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?
This solves #18 by warning the user that using a string as a prop can cause unintended style effects from the user agent stylesheet.
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
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?
This issue is more of a RFC.
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.
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 }} />
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.
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.
Small one, should the docs read
space: [10, 12, 14]
rather than
spacing: [10, 12, 14]
Can be tied with the current useBreakpointIndex
hook in src/css
.
Reference: https://github.com/system-ui/theme-ui/tree/master/packages/match-media
The docs reference being able to use <P>
html-elements but it looks like it's not exported from components
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 ๐ท ๐ท
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
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}
/>
Hey, @nandorojo ! Where does the SSRStyleReset
named export get exported from in the codebase? Is it coming from another package?? I can't seem to find seem to find it in dripsy
codebase.
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.
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 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.
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.
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.
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?
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 div
s, and the div
s 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!
styled
functionThis 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
.
themeKey
to componentsconst theme = {
cards: {
primary: {...}
}
}
...
<View
themeKey="cards"
variant="primary"
/>
Before, we had to use the createThemedComponent
to give a component a themeKey
.
ThemeProvider
renamed to DripsyProvider
Change this in your imports after updating.
style
propI'm also looking into custom theme key typescript suggestions still.
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.
Just want to make sure that dripsy
works with vanilla React Native considering this package is advertised as compatible with Expo & React Native Web. Thanks!
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.
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.
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.
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
.
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
}}
/>
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.
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.
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`]
}}
/>
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.
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>
fontSize
is changed to a string value e.i "2px"
I get the same behavior.Some style features will require editing the Sx prop typescript object, as well as the css
file for react native.
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.
When using the styled API inputs lose focus preventing the user from typing.
This does not however happen with the createThemeComponent API.
I'm currently using Formik to handle forms with React Native. I'm not sure of the impact of this on the bug.
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",
});
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.
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:
styled
components type approach, and create a styled
function:import { styled, Box } from 'dripsy'
const Page = styled(Box)(props => ({
backgroundColor: ['primary', 'secondary']
}))
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.
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.
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.
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
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 }} />
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.
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.
Component:
<Container
webContainerSx={{ flex: 1 }}
sx={{ justifyContent: "center", alignItems: "center", flex: 1 }}
>
<Text>Here</Text>
</Container>
Theme value:
container: {
px: ["base", "large", "huge"],
},
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'
}
}
}
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>
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.
RNW 0.14 is changing the default flex basis for View
to match the web spec, since RN is not yet compliant. I wonder if, once expo supports RNW 0.14, the view dripsy exports should change its default. This would ensure cross platform consistency without extra changes.
See: necolas/react-native-web#1640 & necolas/react-native-web#1719
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.
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.
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.
Hi there, such great work on this! Any chance of adding a useThemeUI
type hook?
Not urgent, but useful.
import { ThemePreset, mergeThemes } from 'dripsy'
import theme from './theme
export default mergeThemes(ThemePreset.Google, theme)
Library currently not working
web Failed to compile. /Users/eddyhdzg/Desktop/my-react-app/node_modules/@theme-ui/core/dist/index.esm.js Module not found: Can't resolve '@theme-ui/css/dist/types' in '/Users/eddyhdzg/Desktop/my-react-app/node_modules/@theme-ui/core/dist'
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
Here's a non-comprehensive outline of potential components that could be included in Dripsy:
HTML Elements - @expo/html-elements
React Native
Theme-UI Inspired
Additional
Potential Questions
Note that this list is non-comprehensive and any feedback on to what it should be added/removed is welcomed to improve the list.
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.
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.
I'm learning more about Tailwind and am curious how it might fit into the future of RN styling. I wonder if it could play a role with Dripsy somehow. I'm still getting to know it better.
Some interesting projects:
https://github.com/OwenMelbz/babel-plugin-tailwind-rn
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.