Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pseudo selectors #7

Closed
cmaycumber opened this issue Jul 16, 2020 · 34 comments
Closed

Pseudo selectors #7

cmaycumber opened this issue Jul 16, 2020 · 34 comments
Assignees

Comments

@cmaycumber
Copy link
Contributor

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.

@nandorojo
Copy link
Owner

Interesting proposal, I was actually playing around with this the other day.

Re pseudo selectors: It might make sense to follow the plans for the new Pressable component introduced by react native 0.63. The sx prop could optionally be a pure function that receives pressed, hovered, etc. as an argument. We could use selector keys, but I kind of like the idea of a JS approach so that it feels like it's supporting every platform universally.

  • Haven't thought this part through, but it could also be interesting to have an animated value as one of the arguments passed by this function, like animatedHover, to let you use animated/reanimated styles based on the pseudo selector states.

@nandorojo
Copy link
Owner

nandorojo commented Jul 16, 2020

Good idea re: the outline, would you mind moving that to a separate issue?

I think a good start could be following what Theme UI does: export JSX versions of primitive components, and then add styled H1, H2, etc..., as well as a base button, and such. I like the way theme UI isn't too opinionated, but it also has all the base components you need to build great UIs out of the box.

@cmaycumber
Copy link
Contributor Author

I agree the new pressable component would definitely be the way to follow. I'm all for the JS approach I think that's a great idea. The animated value is also an interesting proposition; I think that it would be especially helpful with the addition of the pressable component as well if you wanted to animate the button on press states along with the hover.

Yes! I'll move this into a new issue and just throw some components up there in a checklist and we can add/remove items from there.

@cmaycumber cmaycumber changed the title Pseudo selectors and future component considerations Pseudo selectors Jul 16, 2020
@nandorojo
Copy link
Owner

Just came across https://github.com/Sharcoux/rn-css, which has an interesting approach to pseudo selectors like hover, as well as web units like em and such.

Figured I'd put any other relevant repos / resources here as I figure out the optimal next steps.

@nandorojo
Copy link
Owner

One concern for this solution is to make sure we don't trigger unnecessary re-renders from touches, hovers, etc. necolas/react-native-web#205 (comment)

@nandorojo
Copy link
Owner

While we're at it, the sx prop function could also pass the window's height and width, since we're already computing this to build breakpoints. That way, you can use relative styles without needing to set up any hooks. We already update the state based on these, so we get it for free.

@nandorojo
Copy link
Owner

Another thing I like about designing in web is responsive font size. It might be out of the scope of this prop, but a responsiveFontSize function that lets you set min, max, and % of viewport size font sizes would be useful. It probably makes more sense to export this as a separate function from the library, but it could also be passed from the sx prop to keep all styles confined in just this prop.

@cmaycumber
Copy link
Contributor Author

That's a helpful Github issue. Re-renders would be bad especially if it was listening for hover events on mobile devices as well.

I'm a big fan of responsive font sizes as well. I think that if we can create an elegant solution for that it would be a big plus.

I'm going to start experimenting with some of these ideas and potentially get a POC or at least a better understanding of some of the obstacles for the psuedo selectors.

@nandorojo
Copy link
Owner

Could setNativeProps be the solution here? It seems like we could have hover listeners that directly set the Native props instead of triggering a rerender from a state update.

From react docs: https://reactnative.dev/docs/direct-manipulation

@nandorojo nandorojo self-assigned this Aug 23, 2020
@nandorojo
Copy link
Owner

Looks like the creator of RNW is chiming in on this here...necolas/react-native-web#1708

@nandorojo
Copy link
Owner

I'll be sticking to using react-native-web-hooks for now, and will migrate to Pressable after the next expo update includes RNW 14's hover style for Pressable.

@nandorojo
Copy link
Owner

I'm not sure what the move is for now on this. Maybe we can support a hover pseudo selector on all items, and use the useHover hook. However, this means adding a decent amount of weight to every component.

On the other hand, if we wrap every element with Pressable, we are introducing yet another wrapper around every element. I would like Dripsy to handle pseudo selectors better than it does now, but I'm not exactly sure how it would work. For now, I'm probably going to stick to using Pressable and wrapping dripsy components with that.

Maybe the best we could do is re-export pressable, and theme-ify the style prop function?

@cmaycumber
Copy link
Contributor Author

What do you think about adding a boolean to the createThemedComponent/styled function that wraps it in a Pressable?

@nandorojo
Copy link
Owner

nandorojo commented Nov 3, 2020

That's an interesting idea, a simple hoverable Boolean could work. Not sure if this is a good idea, but maybe it could be a prop of the components too.

I like that it would make it explicitly clear that we have hover styles.

@cmaycumber
Copy link
Contributor Author

That's an interesting idea, a simple hoverable Boolean could work. Not sure if this is a good idea, but maybe it could be a prop of the components too.

I like that it would make it explicitly clear that we have hover styles.

I think that the prop passed could be a good idea. The component could inherit most of the props from pressable so it would be super convenient to access the hovered state. For example, in a button component, the label styles could be updated from the prop that's passed.

I could definitely see this replacing some of my Pressable components atm.

@nandorojo
Copy link
Owner

True, I think that could work. I'm already using RNW 0.14, but since it has some breaking changes and many probably aren't using it yet, maybe we wait for expo to support it by default to keep dripsy backwards compatible with expo. I'll play around with it.

@nandorojo
Copy link
Owner

nandorojo commented Dec 20, 2020

I think I know how we could add hover support. I'll make it happen after merging the monorepo.

The API could be a whileHover prop like framer-ui has.

@nandorojo nandorojo reopened this Dec 20, 2020
@cmaycumber
Copy link
Contributor Author

I think I know how we could add hover support. I'll make it happen after merging the monorepo.

The API could be a whileHover prop like framer-ui has.

That would be super sweet. I'm currently trying to figure out the best way to support disabled, pressed, and hovered through the theme entirely right now and can't find a perfect solution.

It would be cool if we could handle some of these under the hood in dripsy for sure.

@nandorojo
Copy link
Owner

Some ideas come to mind for how we could do that. I'll look at the monorepo today, hopefully we can get that published to production.

@macieklad
Copy link

In our recent project, we've came up with the following solution and it works fine for now, it is heavily inspired by the material ui library - in each component you call makeStyles or makeInteractiveStyles which provides type support and returns function that can be used to create styles based on params (it also memoizes the result).

With the "makeInteractiveStyles" variant, it also calls a hook underneath that generates all handlers required for the pressable component, and connects those states to the props inside the function, so you have dynamic, changable stylesheets, that take all of the themed values. Even better, it is fully typed so you get props and names inside the returned object, eg. classes.root shows typehints and so on. I think that connects all those problems in a great way without coupling things to dripsy itself.

export const Tag: React.FC<TagProps> = ({ onPress, children, ...props }) => {
  const { classes, handlers } = useStyles({ onPress })

  return (
    <Pressable sx={classes.root} {...(onPress && handlers)} {...props} accessibilityRole="button">
      <Text sx={classes.text}>{children}</Text>
    </Pressable>
  )
}

const useStyles = makeInteractiveStyles(
  ({ isHovered, onPress }: PropsWithInteractions<TagProps>) => ({
    root: {
      backgroundColor: pickValue('primary-400', isHovered && 'primary-600'),
      cursor: pickValue('default', onPress && 'pointer'),
      borderRadius: 100,
      px: [3, null, 4],
      py: 2,
    },
    text: {
      color: 'white',
      fontWeight: 'semibold',
      fontSize: [1, null, 2],
      lineHeight: [0, null, 2],
    },
  })
)`

@cmaycumber
Copy link
Contributor Author

In our recent project, we've came up with the following solution and it works fine for now, it is heavily inspired by the material ui library - in each component you call makeStyles or makeInteractiveStyles which provides type support and returns function that can be used to create styles based on params (it also memoizes the result).

With the "makeInteractiveStyles" variant, it also calls a hook underneath that generates all handlers required for the pressable component, and connects those states to the props inside the function, so you have dynamic, changable stylesheets, that take all of the themed values. Even better, it is fully typed so you get props and names inside the returned object, eg. classes.root shows typehints and so on. I think that connects all those problems in a great way without coupling things to dripsy itself.

export const Tag: React.FC<TagProps> = ({ onPress, children, ...props }) => {
  const { classes, handlers } = useStyles({ onPress })

  return (
    <Pressable sx={classes.root} {...(onPress && handlers)} {...props} accessibilityRole="button">
      <Text sx={classes.text}>{children}</Text>
    </Pressable>
  )
}

const useStyles = makeInteractiveStyles(
  ({ isHovered, onPress }: PropsWithInteractions<TagProps>) => ({
    root: {
      backgroundColor: pickValue('primary-400', isHovered && 'primary-600'),
      cursor: pickValue('default', onPress && 'pointer'),
      borderRadius: 100,
      px: [3, null, 4],
      py: 2,
    },
    text: {
      color: 'white',
      fontWeight: 'semibold',
      fontSize: [1, null, 2],
      lineHeight: [0, null, 2],
    },
  })
)`

This is awesome. Thanks for sharing. I might use some of this logic in one of my projects.

I'm still curious if there might be a way to work in variants inside of the theme and be able to pass them to the hook to produce results. The part I'm currently struggling with is being able to theme pseudo styles including animations with dripsy and now moti or reanimated.

@nandorojo
Copy link
Owner

Interesting approach! As I mentioned at #69 (comment), I've opted to just use Pressable + <MotiView>, since I don't think pseudoevents should live in Dripsy's primitives. So this kind of solution is interesting, since it's an abstraction where users implement their own pseudoelements.

@miikebar
Copy link

miikebar commented Apr 23, 2021

I may have found something interesting regarding not only pseudo selectors but also media queries. Recently I've been looking for alternatives to Fresnel as the responsive style solution, as I'm not really keen on the idea of generating all breakpoints on the server side and sending them to client. I've decided to try Emotion and react-responsive, with the idea of estimating the client screen dimensions on the server side based on client-hints and user-agent, and populating the media query state with it. After some time I've encountered a bug related to SSR with @emotion/native and react-native-web, which led me to the idea of using separate packages on web and native. It turned out to be working quite well, generating media queries and SSR styles on the web without the need for rendering each breakpoint as a DOM node, while on native responsive styles are handled by JS. It seems like it would be possible to use the same approach to use pseudo selectors on web, and handle them using JS on native (maybe wrapping with Pressable and passing interaction state as a prop - isHovered, isFocused?). Here's the issue in detail with an example repo: emotion-js/emotion#2356 (comment). Maybe we could a similar approach with Dripsy?

@nandorojo
Copy link
Owner

nandorojo commented Apr 23, 2021

Yeah I hear you, and I did try that.

The problem is, I still want to wrap React Native components, such as View, Text, and any custom ones. But React Native Web doesn't forward the className prop, which is what allows us to use those kinds of styles, such as media queries.

@nandorojo
Copy link
Owner

The best solution I've come up with for 95% of the time is a Div component I made. It renders a plain div on web with CSS media queries, and a Dripsy View on native. It only accepts children and the SX prop, which is mostly fine. I'll be doing the same with a Span for text. It uses media queries like theme UI on web, and renders a plain span on web. I should be able to release this next version, I've been using it successfully in my app.

@miikebar
Copy link

miikebar commented Apr 23, 2021

That's true, I had to use the patch from SnackUI which brings back the className to get @emotion/styled to work with React Native components through React Native Web. Not sure if using this patch for a library would be ideal, but it fixes a lot of problems

@nandorojo
Copy link
Owner

Yeah, I'm concerned about a patch for something so fundamental, though. While I really wish RNW had a className escape hatch (like, I am shocked it doesn't), it seems like this is the best we can do. I don't want a design system to force people to change such a fundamental thing about RNW.

I tried to discuss this on the RNW repo, along with many others, and got nowhere. Nicolas doesn't seem interested in allowing class names, and doesn't think CSS media queries should be used. Instead, he recommends conditional rendering altogether.

🤷🏼‍♂️

@nandorojo
Copy link
Owner

I'll probably release a major version (v2) with the Div and Span as the recommended API. It's really a bummer, since I don't want to entirely circumvent RNW, and it feels like doing exactly that.

The alternative, and recommended approach by RNW, is to just drop SSR support and use the Dimensions API. This is also worth considering.

@nandorojo
Copy link
Owner

I tried my best here: necolas/react-native-web#1318 (comment)

@miikebar
Copy link

miikebar commented Apr 23, 2021

Yeah, I've seen the discussion around the className is quite heated 😁. Guess I'll continue using the patch for now as it seems to be the simplest and most efficient solution in my case.

@nandorojo
Copy link
Owner

Yeah it's really tough.

@jeffscottward
Copy link

Is this the best solution at this point?

// * We have extracted colors to an external refrence for use in
// * react native element's `style={({ hovered, pressed }) => ({`
// * because Dripsy doesn't support hover states through `sx` and `&:hover`
// * https://github.com/nandorojo/dripsy/issues/7

Screenshot 2023-04-20 at 1 37 37 PM

Screenshot 2023-04-20 at 1 37 55 PM

Screenshot 2023-04-20 at 1 39 03 PM

@nandorojo
Copy link
Owner

nandorojo commented Apr 20, 2023

I ultimately decided to leave this up to moti/interactions, this way I could control all hover / press styles with performant animations.

@jeffscottward
Copy link

I ultimately decided to leave this up to moti/interactions, this way I could control all hover / press styles with performant animations.

Gotcha, so this is fine if I need theme values, just that if I want to change or animate anything, I'm gonna have to explicitly pull those in.

I'm not so in need of animations, I may upgrade later though.

I need the responsive stuff most of all so Dripsy takes precedence. Although there is probably a hook way of doing that without SX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants