Skip to content

Commit

Permalink
feat: Nested navigation items (#204)
Browse files Browse the repository at this point in the history
* Some stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* More stuff

* Added a changeset file

* Fix CI
  • Loading branch information
patricklafrance authored Sep 23, 2024
1 parent 159d0b3 commit d3f7b9c
Show file tree
Hide file tree
Showing 61 changed files with 3,251 additions and 2,168 deletions.
23 changes: 23 additions & 0 deletions .changeset/chilly-beers-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@squide/firefly-webpack-configs": minor
"@squide/module-federation": minor
"@squide/webpack-configs": minor
"@squide/react-router": minor
"@squide/firefly": minor
"@squide/i18next": minor
"@squide/fakes": minor
"@squide/core": minor
"@squide/msw": minor
---

The `registerNavigationItem` function now accepts a `sectionId` option to nest the item under a specific navigation section:

```ts
runtime.registerNavigationItem({
$id: "link",
$label: "Link",
to: "/link"
}, {
sectionId: "some-section"
});
```
2 changes: 1 addition & 1 deletion docs/getting-started/create-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export function RootLayout() {
}
```

The `RootLayout` component created in the previous sample will serves as the default layout for the homepage as well as for every page (route) registered by a module that are not nested under a parent route with either the [parentPath](../reference/runtime/runtime-class.md#register-nested-routes-under-an-existing-route) or the [parentName](../reference/runtime/runtime-class.md#register-a-named-route) option.
The `RootLayout` component created in the previous sample will serves as the default layout for the homepage as well as for every page (route) registered by a module that are not nested under a parent route with either the [parentPath](../reference/runtime/runtime-class.md#register-nested-routes) or the [parentName](../reference/runtime/runtime-class.md#register-a-named-route) option.

### Homepage

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/create-local-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "local-page",
$id: "local-page",
$label: "Local/Page",
to: "/local/page"
});
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/create-remote-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "remote-page",
$id: "remote-page",
$label: "Remote/Page",
to: "/remote/page"
});
Expand Down
49 changes: 49 additions & 0 deletions docs/getting-started/learn-the-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,55 @@ const myPlugin = usePlugin(MyPlugin.name) as MyPlugin;

A plugin can also be retrieved from the [FireflyRuntime](/reference/runtime/runtime-class.md#retrieve-a-plugin) instance.

> By default, the `FireflyRuntime` registers Squide's [MSW plugin](../guides/setup-msw.md). An optional [i18next plugin](../guides/setup-i18next.md) is available.
## TanStack Query

Hooks are available to retrieve global application data using [TanStack Query](https://tanstack.com/query/latest). To fetch public data, use the [usePublicDataQueries](../reference/tanstack-query/usePublicDataQueries.md) hook:

```tsx
import { usePublicDataQueries } from "@squide/firefly";

const [featureFlags] = usePublicDataQueries([
{
queryKey: ["/api/feature-flags"],
queryFn: async () => {
const response = await fetch("/api/feature-flags");

return response.json();
}
}
]);
```

To retrieve protected data, use the [useProtectedDataQueries](../reference/tanstack-query/useProtectedDataQueries.md) hook instead:

```tsx
import { useProtectedDataQueries } from "@squide/firefly";
import { ApiError } from "@sample/shared";

const [session, subscription] = useProtectedDataQueries([
{
queryKey: ["/api/session"],
queryFn: async () => {
const response = await fetch("/api/session");

return response.json();
}
},
{
queryKey: ["/api/subscription"],
queryFn: async () => {
const response = await fetch("/api/subscription");

await response.json();
}
}
], error => isApiError(error) && error.status === 401);
```

If an unmanaged error occur while retrieving the data, a [GlobalDataQueriesError](../reference/tanstack-query/isGlobalDataQueriesError.md) is thrown.

## Fakes

Take a look at the [fake implementations](../reference/default.md#fakes). These implementations are designed to facilitate the set up of a module isolated environment.
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/fetch-global-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = async runtime =>
});

runtime.registerNavigationItem({
$key: "global-data",
$id: "global-data",
$label: "Global data Page",
to: "/global-data"
});
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/fetch-page-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -96,7 +96,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -135,7 +135,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -172,7 +172,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down
22 changes: 12 additions & 10 deletions docs/guides/migrate-to-firefly-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand All @@ -78,7 +78,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
return ({ featureFlags }) => {
if (featureFlags?.featureB) {
runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -249,25 +249,25 @@ export function App() {
- Deferred registration functions now receives a new [operations](../reference/registration/registerLocalModules.md#use-the-deferred-registration-operation-argument) argument.
- Navigation items now include a [$canRender](../reference/runtime/runtime-class.md#conditionally-render-a-navigation-item) option, enabling modules to control whether a navigation item should be rendered.

### New `$key` option for navigation items
### New `$id` option for navigation items

Navigation items now supports a new `$key` option. Previously, most navigation item React elements used a `key` property generated by concatenating the item's `level` and `index`, which goes against React's best practices:
Navigation items now supports a new `$id` option. Previously, most navigation item React elements used a `key` property generated by concatenating the item's `level` and `index`, which goes against React's best practices:

```tsx
<li key={`${level}-${index}`}>
```

It wasn't that much of a big deal since navigation items never changed once the application was bootstrapped. Now, with the deferred registration functions re-executing when the global data changes, the registered navigation items can be updated post-bootstrapping. The new `$key` option allows the navigation item to be configured with a unique key at registration.
It wasn't that much of a big deal since navigation items never changed once the application was bootstrapped. Now, with the deferred registration functions re-executing when the global data changes, the registered navigation items can be updated post-bootstrapping. The new `$id` option allows the navigation item to be configured with a unique key at registration, preventing UI shifts.

```tsx !#2
runtime.registerNavigationItem({
$key: "page-1",
$id: "page-1",
$label: "Page 1",
to: "/page-1"
});
```

The configured `$key` option is then passed as an argument to the [useRenderedNavigationItems](../reference/routing/useRenderedNavigationItems.md) rendering functions:
The configured `$id` option is then passed as a `key` argument to the [useRenderedNavigationItems](../reference/routing/useRenderedNavigationItems.md) rendering functions:

```tsx !#1,5,13,15
const renderItem: RenderItemFunction = (item, key) => {
Expand All @@ -293,6 +293,8 @@ const renderSection: RenderSectionFunction = (elements, key) => {
const navigationElements = useRenderedNavigationItems(navigationItems, renderItem, renderSection);
```

> If no `$id` is configured for a navigation item, the `key` argument will be a concatenation of the `level` and `index` argument.

## Migrate an host application

!!!info
Expand All @@ -312,10 +314,10 @@ The `v9` release introduces several breaking changes affecting the host applicat
4. Remove the `sessionAccessor` option from the `FireflyRuntime` instance. Update the `BootstrappingRoute` component to create a `TanStackSessionManager` instance and share it down the component tree using a `SessionManagedContext` provider. [View example](./add-authentication.md#fetch-the-session)
5. Add or update the `AuthenticationBoundary` component to use the new `useIsAuthenticated` hook. Global data fetch request shouldn't be throwing 401 error anymore when the user is not authenticated. [View example](./add-authentication.md#add-an-authentication-boundary)
6. Update the `AuthenticatedLayout` component to use the session manager instance to clear the session. Retrieve the session manager instance from the context defined in the `BootstrappingRoute` component using the `useSessionManager` hook. [View example](./add-authentication.md#define-an-authenticated-layout)
7. Update the `AuthenticatedLayout` component to use the new `$key` option of the navigation item. [View example](#new-key-option-for-navigation-items)
7. Update the `AuthenticatedLayout` component to use the new `key` argument. [View example](#new-id-option-for-navigation-items)
8. Replace the `ManagedRoutes` placeholder with the new [PublicRoutes](../reference/routing/publicRoutes.md) and [ProtectedRoutes](../reference/routing/protectedRoutes.md) placeholders. [View example](../getting-started/create-host.md#homepage)
9. Convert all deferred routes into static routes. [View example](#removed-support-for-deferred-routes)
10. Add a `$key` option to the navigation item registrations. [View example](#new-key-option-for-navigation-items)
10. Add an `$id` option to the navigation item registrations. [View example](#new-id-option-for-navigation-items)

### Root error boundary

Expand Down Expand Up @@ -374,7 +376,7 @@ A migration example from v8 to v9 is available for the [wl-squide-monorepo-templ
The changes in `v9` have minimal impact on module code. To migrate an existing module, follow these steps:

1. Convert all deferred routes into static routes. [View example](#removed-support-for-deferred-routes)
2. Add a `$key` option to the navigation item registrations. [View example](#new-key-option-for-navigation-items)
2. Add a `$id` option to the navigation item registrations. [View example](#new-id-option-for-navigation-items)

### Isolated development

Expand Down
6 changes: 3 additions & 3 deletions docs/guides/use-feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -218,7 +218,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
// Only register the "Page" navigation items if "featureB" is activated.
if (featureFlags?.featureB) {
runtime.registerNavigationItem({
$key: "page",
$id: "page",
$label: "Page",
to: "/page"
});
Expand Down Expand Up @@ -304,7 +304,7 @@ export function App() {
!!!info
A key feature of [TanStack Query](https://tanstack.com/query/latest) is its ability to keep the frontend state synchronized with the server state. To fully leverage this, whenever the data passed to `useDeferredRegistrations` changes, all deferred registration functions are re-executed.
Remember to use [useMemo](https://react.dev/reference/react/useMemo) for your deferred registration data and to specify the `$key` option for your navigation items!
Remember to use [useMemo](https://react.dev/reference/react/useMemo) for your deferred registration data and to specify the `$id` option for your navigation items!
!!!
## Try it :rocket:
Expand Down
14 changes: 7 additions & 7 deletions docs/guides/use-modular-tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "tabs",
$id: "tabs",
$label: "tabs",
to: "/tabs"
});
Expand All @@ -76,7 +76,7 @@ It is recommended to define the shared layouts in a standalone package as it's d

## Create the tab routes

Next, let's add the actual tabs to the modules. To do so, we'll use the [parentPath](../reference/runtime/runtime-class.md#register-nested-routes-under-an-existing-route) option of the [registerRoute](../reference/runtime/runtime-class.md#register-routes) function to register the routes under the `TabsLayout` component:
Next, let's add the actual tabs to the modules. To do so, we'll use the [parentPath](../reference/runtime/runtime-class.md#register-nested-routes) option of the [registerRoute](../reference/runtime/runtime-class.md#register-routes) function to register the routes under the `TabsLayout` component:

```tsx !#7,10 remote-module-1/src/register.tsx
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
Expand Down Expand Up @@ -173,7 +173,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "tab-1",
$id: "tab-1",
$label: "Tab 1",
to: "/tabs"
}, {
Expand All @@ -199,7 +199,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "tab-2",
$id: "tab-2",
$label: "Tab 2",
to: "/tabs/tab-2"
}, {
Expand All @@ -225,7 +225,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "tab-3",
$id: "tab-3",
$label: "Tab 3",
to: "/tabs/tab-3"
}, {
Expand Down Expand Up @@ -289,7 +289,7 @@ export function TabsLayout() {

## Change the display order of the tabs

Similarly to how the display order of regular navigation items can be configured, a modular tab position can be affected with the [priority](http://localhost:5000/wl-squide/reference/runtime/runtime-class/#sort-registered-navigation-items) property.
Similarly to how the display order of regular navigation items can be configured, a modular tab position can be affected with the [priority](http://localhost:5000/wl-squide/reference/runtime/runtime-class/#sort-registered-navigation-items) option.

To force `Tab 3` to be positioned first, we'll give him a priority of `999`:

Expand All @@ -306,7 +306,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "tab-3",
$id: "tab-3",
$label: "Tab 3",
// Highest priority goes first.
$priority: 999,
Expand Down
10 changes: 5 additions & 5 deletions docs/reference/registration/registerLocalModules.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
});

runtime.registerNavigationItem({
$key: "about",
$id: "about",
$label: "About",
to: "/about"
});
Expand Down Expand Up @@ -159,7 +159,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
});

runtime.registerNavigationItem({
$key: "about",
$id: "about",
$label: "About",
to: "/about"
});
Expand All @@ -178,7 +178,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
// Only register the "feature-a" route and navigation item if the feature is active.
if (featureFlags.featureA) {
runtime.registerNavigationItem({
$key: "feature-a",
$id: "feature-a",
$label: "Feature A",
to: "/feature-a"
});
Expand All @@ -204,7 +204,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
});

runtime.registerNavigationItem({
$key: "about",
$id: "about",
$label: "About",
to: "/about"
});
Expand All @@ -223,7 +223,7 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
// Only register the "feature-a" route and navigation item if the feature is active.
if (featureFlags.featureA) {
runtime.registerNavigationItem({
$key: "feature-a",
$id: "feature-a",
$label: operation === "register" ? "Feature A" : "Feature A updated",
to: "/feature-a"
});
Expand Down
Loading

0 comments on commit d3f7b9c

Please sign in to comment.