ItsJonQ / g2

✨ An experimental reimagining of WordPress components
http://g2-components.com/
MIT License
105 stars 12 forks source link

Sidebar Navigation Mechanics #198

Closed ItsJonQ closed 3 years ago

ItsJonQ commented 3 years ago

This post goes over the mechanics of a Sidebar Navigation UI that can be constructed with G2 Components.

A live example of this UI would be the "Global Styles Sidebar" prototype, as seen here ✨ https://g2-components.xyz/iframe.html?id=examples-wip-globalstylessidebar--default&viewMode=story

☸️ The Navigator

This setup begins and ends with the Navigator component. This component is responsible for:

From a technical perspective, this is essentially a fork of React Router but modified specifically to accommodate Component System use cases. That being, it relies on a localized context, rather than a single top-level app (which is what React Router does).

The easiest way to understand how this works is to imagine <Navigator /> as being a tiny web-browser (that's because, well, it sorta is!)

Something that can...

Note: Navigator and it's sub-components do not render styles (e.g. a dark background). This is by design. These components are effectively (smart) layout components. They are designed to focus on functionality. Aesthetic rendering should be provided by enhancing the sub-components and from the content itself.

📦 The Parts

[Navigator] provides a handful of flexible sub-components and hooks to help construct custom UI like a Sidebar Navigation.

The primary parts we need would be:

The rendered setup can be visualized like this:

Screen Shot 2020-12-12 at 11 22 16 AM
Navigator

This top-level component contains all of the sidebar's content. This is required as it is essentially the "brains" of this UI.

Header

For our desired Sidebar Navigation UI, we would like persistent header that can adapt its UI and content based on the current context. For example, it would render a title of "Buttons" if we're currently on the Buttons settings page.

This header can become "context" aware by using the useNavigatorHistory and useNavigatorLocation, with information being fed down by the <Navigator /> (aka. "brains").

Screens + Content

A "Screen" (<NavigatorScreen />) basically contains whatever content we need to render for a particular page.

For example...

The main "home" screen would contain "links" to all of the sub-settings. The "Buttons settings" screen would contain controls to adjust button settings. etc...

↔️ Screens and Transitions

Every single "Screen" would be rendered in a container "Screens" component. Something like this:

<NavigatorScreens>
    <NavigatorScreen {...props} />
    <NavigatorScreen {...props} />
    <NavigatorScreen {...props} />
    ...
    <NavigatorScreen {...props} />
</NavigatorScreens>

(The above could be simplified using loops)

The main job of <NavigatorScreens /> is to coordinate the rendering and transitions of all the individual child Screen components.

This can be visualized like this:

Screen Shot 2020-12-12 at 11 22 22 AM

This is important because <NavigatorScreens /> handles the mounting/unmounting and animation timings of the inner Screen components. It's also directionally aware (aka, animating backwards/forwards) based on uses of Links and "Back" links.

Note: Navigation direction can be programatically controlled, if needed.

🧭 The Setup

With the fundamental parts and mechanics established, we can go over the "setup" to construct a Sidebar Navigation UI that's similar to our Global Styles Sidebar example.

Screens

Let's create a collection of our screens. For this example, we'll only add a couple:

const screens = [
    {
        component: GlobalStylesScreen,
        path: 'GlobalStyles',
        title: 'Global Styles',
    },
    { component: ColorsScreen, path: 'Colors', title: 'Colors' },
    { component: TypographyScreen, path: 'Typography', title: 'Typography' },
    { component: ButtonsScreen, path: 'Buttons', title: 'Buttons' },
];

Our screens need 3 pieces of data:

With our screens defined, let's render the Navigator:

const Example = (props) => {
    const initialPath = 'GlobalStyles';
    return (
        <Navigator initialPath={initialPath}>
            <GlobalStylesHeader />
            <NavigatorScreens css={[ui.frame.height('auto')]}>
                {screens.map((screen) => (
                    <NavigatorScreen
                        {...screen}
                        animationEnterDelay={0}
                        animationEnterDuration={ANIMATION_SPEED}
                        animationExitDuration={ANIMATION_SPEED}
                        key={screen.path}
                    />
                ))}
            </NavigatorScreens>
        </Navigator>
    );
};

In this example, we've set GlobalStyles as our "home" page in Navigator. We've also looped through all of our screens to render within the NavigatorScreens container.

Screen

Individual screen components are pretty simple! For example, a Buttons settings screen may look like this:

const ButtonsScreen = () => {
    return (
        <View>
            ...
        </View>
    );
};

It's just a component that renders content! The Navigator (and it's sub-components) have taken care of the rest.


The examples above are a merely a simplified overview. To get a better sense of actual code implementation, check out the GlobalStylesSidebar prototype's story:

https://github.com/ItsJonQ/g2/blob/d7e09fe5656ea1e90cef0e6b60850b8329ca077d/packages/components/src/__stories__/WIP/GlobalStylesSidebar.stories.js

Please don't copy/paste this code exactly for use in production. You can certainly use it for reference or as a starting out. This code was created (quickly) for the purposes of prototyping.