redwoodjs / redwood

The App Framework for Startups
https://redwoodjs.com
MIT License
17.32k stars 994 forks source link

[RFC]: Modal based routing #5890

Open Tobbe opened 2 years ago

Tobbe commented 2 years ago

Summary

Modal based routing is when you click on a link and instead of replacing the current page with the new one it opens a modal with the new page in. And as you navigate the page inside the modal the URL updates. If you bookmark the page and come back later the page that was in a modal is now the base page.

It might sound complicated/weird, but go to some instagram account on a desktop browser and you'll see exactly what I'm talking about

Motivation

It's a common/popular UI paradigm that we might want to think about adding support for in Redwood. I already mentioned that instagram does this. Another example is https://nomadlist.com

Detailed proposal

I've never tried implementing something like this, so I'm not really sure how it would work.

I'd start by trying to find existing examples of this in React using other routers, and see if I could draw some inspiration from that.

For a little more context, see here: https://community.redwoodjs.com/t/how-to-create-modal-routing/3562

Are you interested in working on this?

sgup commented 2 years ago

Here is an example with react-router: https://v5.reactrouter.com/web/example/modal-gallery

An important thing to consider is that the Modals should not simply wrap the Page elements. Rather, they can render anything for the same route, including Sets / Alternate Pages.

mcking49 commented 2 years ago

Trello is another great example

Tobbe commented 2 years ago

I've been thinking more about a potential future "mobile" side in Redwood. And with that I think modal based routing is going to be a more requested feature. So definitely still interested in adding support for this. But also still don't know how to do it.

An important thing to consider is that the Modals should not simply wrap the Page elements. Rather, they can render anything for the same route, including Sets / Alternate Pages.

<Set>s are going to be interesting. A common use case for sets is to render a layout, often with a navigation bar to the left or at the top. And normally when we render a page we also render any layout(s) wrapping that page. Now, if a page is rendered inside a modal, how do we handle the sets? We have the base page that is already rendered together with its set(s). When I render this new page in a modal, should I replace the base page set? Render both sets at the base page level? Render the new set inside the modal? Not render the set at all? I don't know the answer to this question right now. Keen to hear what the rest of you think 🙂

sgup commented 2 years ago

If we consider a very simple example of a website with a gallery of images, and if you click one a modal which can navigate between images:

Screen Shot 2022-07-21 at 4 10 54 PM Screen Shot 2022-07-21 at 4 13 13 PM

We'd want 2 Sets: one for the main website, and one for the modal navigation/other elements.

And, the URL being changed with every prev/next of the image, and if we go directly to the changed url, it would show the full image without the modal/prev/next arrows, for example.

Here's a very rough idea of what the end-user code could look like:

// Routes.tsx

const ImageModalRouter = () => (
  <Router>
    <Set wrap={[ImageModalLayout]}> {/* ModalLayout can inspect current route and do stuff with it */}
      <ImageView path="/images/{id:Int}" component={ImageComponent} />
    </Set>
  </Router>
)

const Routes = () => (
  <Router>
    <Set wrap={SiteLayout}>
      <Route path="/" page={HomePage} />
      <Route path="/recent" page={RecentImagesPage} modals={[ImageModalRouter]}/>
      <Route path="/profile/{id:Int}" page={ProfileImagesPage} modals={[ImageModalRouter]} />
      <ImageDetail path="/images/{id:Int}" page={ImagePage} /> {/* image detail page */}
    </Set>
  </Router>
)
sgup commented 2 years ago

React-Navigation is also worth looking into: https://reactnavigation.org/docs/nesting-navigators

Tobbe commented 2 years ago

@sgup Thanks for the sketches and code samples. That makes this easier to talk about 🙂

We've worked really hard to keep the routes file as flat and simple as possible. With just a single in a single file and no nesting of routes.

If at all possible we'd really like to keep it that way even when adding this feature.

Another thing I'd like to think about now is animation support. Currently with our router it's very difficult (impossible?) to animate route transitions. That's something I want to change.

Right now the router can only ever render a single Page at a time. To be able to do animations and also to be able to do modal based routing we might have to change that so that two pages can be rendered simultaneously

sgup commented 2 years ago

@sgup Thanks for the sketches and code samples. That makes this easier to talk about 🙂

of course! I need my visual aids haha.

We've worked really hard to keep the routes file as flat and simple as possible. With just a single in a single file and no nesting of routes.

If at all possible we'd really like to keep it that way even when adding this feature.

Then perhaps a way to define with modal routes/sets, and simply being able to call upon them from any page:

// Routes.tsx

const Routes = () => (
  <Router>
    <Set wrap={SiteLayout}>
      <Route path="/" page={HomePage} name="home" />
      <Route path="/recent" page={RecentImagesPage} name="recentImages" />
      <Route path="/profile/{id:Int}" page={ProfileImagesPage} name="profileImages" />
      <ImageDetail path="/images/{id:Int}" page={ImagePage} name="imageDetail" /> {/* image detail page */}
    </Set>

    <Modal name="imageModal">
      <Set wrap={[ImageModalLayout]}> {/* ModalLayout can inspect current route and do stuff with it */}
        <ImageView path="/images/{id:Int}" component={ImageComponent} name="imageView" />
      </Set>
    </Modal>
  </Router>
)

// RecentImagesPage.tsx

() => {
  const { openModal } = useNavigation()

  return (
    <div className="grid">
      {images.map(i => <img src={i.src} onClick={openModal(routes.imageView({ id: i.id }))} />)}
    </div>
  )
}

Another thing I'd like to think about now is animation support. Currently with our router it's very difficult (impossible?) to animate route transitions. That's something I want to change.

I would love that! I was actually trying to animate my modal sub-views (in my hacked together modal routing solution) to be able to slide between each other. I think it would make the most sense as an animate option:

navigate(routes.home(), { ..., animation: AnimationOptions })

And actually thinking about NavigateOptions.. maybe the modal stuff can be in that too:

navigate(routes.imageView({id: 1}), { asModal: true, ... } )
Tobbe commented 2 years ago

And actually thinking about NavigateOptions.. maybe the modal stuff can be in that too:

navigate(routes.imageView({id: 1}), { asModal: true, ... } )

Yeah! That's not a bad idea.

Another option is to introduce a new <ModalRoute>

<const Routes = () => (
  <Router>
    <Set wrap={SiteLayout}>
      <Route path="/" page={HomePage} name="home" />
      <Route path="/recent" page={RecentImagesPage} name="recentImages" />
      <Route path="/profile/{id:Int}" page={ProfileImagesPage} name="profileImages" />
      <Set wrap={ImageModalLayout}>
        <ModalRoute path="/images/{id:Int}" page={ImagePage} name="imageDetail" />
      </Set>
    </Set>
)

And whenever you navigate to a <ModalRoute> using navigate() or <Link> it'd open in a modal, but if you typed in the full url or had a bookmark or something it'd just show that as a full page

My main concern is that we're building something too specific here. If we could make something more generic, that could solve/support this use case but also other use cases people might have in the future that'd feel better. But I guess that also depends on how big this change is in the code. If the code that's unique to this feature is relatively small and self contained it's not so bad. If it makes the entire router code base even more difficult to understand that's not so great 😅

I would love that! I was actually trying to animate my modal sub-views (in my hacked together modal routing solution) to be able to slide between each other. I think it would make the most sense as an animate option:

Great! Let's think some more about this together then 🙂

navigate(routes.home(), { ..., animation: AnimationOptions })

Like for the modal routing solution a concern here is if this is too "single minded" and/or too integrated with the framework. My first idea was to rebuild the router so a user could plug in react-transition-group or framer-motion. But thinking about it some more maybe we should have built in support for animating between routes. I know Svelte has built in support for animation, and they get a lot of love for that. Maybe animation is one of our router's USPs, just like Sets. And speaking of Sets, I think it would be cool if those could be leveraged to set up animation. Either by dedicated props for it (if we go for an integrated solution), or if you could add some kind of component to the list of wrapper components that took care of animation for you.

ladderschool commented 2 years ago

Either by dedicated props for it (if we go for an integrated solution), or if you could add some kind of component to the list of wrapper components that took care of animation for you.

As someone who just recently began using Redwood and loving it so far, I think this is a legitimate question to ask at this point. Personally, what I love about the router is that it's lightweight and if you organize it well, it can look bomb {/**/}, put roles in arrays/objects, etc. I don't think it should be cluttered with additional props to do this? I don't remember a lot about Context when going through the tutorial, but why not add a horizontal abstraction to the 'Layout' idea and make a generator for it as well, call it an "Animate" wherein the code is defined and imported just like a Layout. Leave it to the devs to decide what kind and how many Animate components they create. Something simple like:

<const Routes = () => (
  <Router>
    <Set wrap={BubblesLayout} animate={BubblyTransition}>
      <Route path="/" page={HomePage} name="home" />
      <Route path="/bubbles" page={BubblesPage} name="bubbles" />
    </Set>
  </Router>
)

Didn't mean to barge into the discussion, just got interested in the topic and I apologize if I'm shooting way past the point going on here. I wonder if this discussion also touches on something like https://reactjs.org/docs/context.html (setting UI theme globally, etc.)

Tobbe commented 2 years ago

Didn't mean to barge into the discussion

Not at all! I very much welcome all the input here 🙂 That animate prop on the Set does look nice 👍 Maybe that's how we should do modal routing as well

<const Routes = () => (
  <Router>
    <Set wrap={SiteLayout}>
      <Route path="/" page={HomePage} name="home" />
      <Route path="/recent" page={RecentImagesPage} name="recentImages" />
      <Route path="/profile/{id:Int}" page={ProfileImagesPage} name="profileImages" />
      <Set modal wrap={ImageModalLayout} animate={SlideTransition}>
        <Route path="/images/add" page={AddImagePage} name="addImage" />
        <Route path="/images/{id:Int}" page={ImagePage} name="imageDetail" />
      </Set>
    </Set>
  </Router>
)
ladderschool commented 2 years ago

Looks clean and it fits into the current useage of 'Set' with things such as 'private'

pkaramagi commented 2 years ago

@Tobbe @sgup any update on this issue?

Tobbe commented 2 years ago

@pkaramagi It's on the Core Team's roadmap. But we're all busy with other things right now, so it'll be a few more weeks, maybe months, until we get started on it.

But we're definitely here to support and guide if someone from the community wants to start experimenting before we get to it 🙂

pkaramagi commented 1 year ago

@Tobbe @sgup any new updates on this ??

Tobbe commented 1 year ago

Thanks for not giving up on us @pkaramagi 🙏 Unfortunately no progress has been made on this.

Tobbe commented 1 year ago

Next.js just released support for this https://nextjs.org/blog/next-13-3#parallel-routes-and-interception

sgup commented 1 year ago

Any updates on this? Here are the docs on NextJS for this: https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals

image

thedavidprice commented 1 year ago

It's on the public roadmap for the current Bighorn Epoch. However, it's not on the near-term schedule nor has there been any implementation design work outside this Issue: http://redwoodjs.com/roadmap

taivo commented 1 year ago

Hi guys, +1 on this issue, in particular the parallel route feature that is probably needed to enable modal based routing. Having parallel routes would make it trivial to encapsulate CRUD views within a card or a modal.

Tobbe commented 1 year ago

Implementing support for React server components is going to require us to do some work on the router. I'll make sure to also keep this feature request in mind to see what we can do.

Tobbe commented 11 months ago

I added the p3 label to this issue. It just means we won't look at this specific issue as a priority right now. But it still definitely is something our router should support in the future!