This project demonstrates how to create a custom Navigation component for use in the Admin UI. It builds on the Task Manager starter project.
To run this project, clone the Keystone repository locally, run yarn
at the root of the repository then navigate to this directory and run:
yarn dev
This will start the Admin UI at localhost:3000.
You can use the Admin UI to create items in your database.
You can also access a GraphQL Playground at localhost:3000/api/graphql, which allows you to directly run GraphQL queries and mutations.
The project contains an config.ts
file in the /admin
directory at its root. Keystone looks for this file for component customisations, and expects the following export.
export const components = {
Logo,
Navigation,
};
The exported components object is expected to have the following type signature:
{
Logo?: (props: {}) => ReactElement;
Navigation?: (props: NavigationProps) => ReactElement;
}
Keystone conveniently exports an AdminConfig type for DX.
import { AdminConfig } from '@keystone-6/core/types';
export const components: AdminConfig['components'] = {
Logo,
Navigation,
};
Keystone passes the following props to your custom Navigation component:
NavigationProps = {
authenticatedItem: AuthenticatedItem,
lists: ListMeta[]
}
The authenticatedItem prop is a Union
representative of the following possible authentication states:
type AuthenticatedItem =
| { state: 'unauthenticated' }
| { state: 'authenticated'; label: string; id: string; listKey: string }
| { state: 'loading' }
| { state: 'error'; error: Error | readonly [GraphQLError, ...GraphQLError[]] };
You will need to reasonably account for all of these states if you would like to role your own AuthenticatedItem component but that's out of the scope for this particular example.
The lists
prop is an array of keystone list objects.
type ListMeta = {
/** Used for optimising the generated list of NavItems in React */
key: string;
/** Used as the href for each list generated NavItem */
path: string;
/** Used as the label for each list generated NavItem */
label: string;
/** Other properties exists, but these are the ones that are relevant to the Navigation implementation */
};
type Lists = ListMeta[];
Keystone exports the following components to make creating your own custom Navigation component easier. These are:
- NavigationContainer: A generic container element that also includes rendering logic for the passed in authenticatedItem.
- ListNavItems: This component renders out a list of Nav Items with preconfigured route matching logic optimised for lists.
- NavItem: A thin styling and accessibility wrapper around the Next.js
Link
component.
The NavigationContainer is responsible for rendering semantic markup and styles for the containing element around your navigation items.
It's also responsible for rendering out the authenticatedItem
should it exist. It has the following prop signature:
type NavigationContainerProps = {
authenticatedItem: AuthenticatedItem;
children: ReactNode;
};
Pass it the authenticatedItem
from the NavigationProps
for it to handle rendering logic around user signin.
const CustomNavigation = ({ authenticatedItem }) => {
return (
<NavigationContainer authenticatedItem={authenticatedItem}>
{/* The rest of your Nav goes here */}
</NavigationContainer>
);
};
type ListNavItemsProps = {
lists: ListMeta[];
include?: string[];
};
The ListNavItems component expects lists
an array of list objects and renders a list of NavItems. It also optionally takes include
, an array of strings. If this array is passed to this component, only lists with a key
property that matches an element in the include
array will be rendered. If this array is not passed into the component, all lists will be rendered.
type ListNavItemProps = {
list: ListMeta;
};
The ListNavItem component takes a single list
object and renders a NavItem
. This is a thin wrapper around NavItem
, that automates the association of list properties to NavItem. It also adds a custom isSelected
expression optimised for keystone lists.
The idea behind ListNavItem
is to have a higher-level abstraction that makes it easier to render navigation links for lists consistently.
This is especially relevant for future feature additions to the nav. (i.e. if we add icon support, that would work out of the box from a dependency bump for users of ListNavItem, whereas you'd have to add that icon prop manually with a lower level component.)
The NavItem component has the following type signature:
type NavItemProps = {
children: ReactNode;
href: string;
isSelected?: boolean;
};
By default the isSelected
value if left undefined, will be evaluated by the condition router.pathname === href
, pass in isSelected
if you have a custom condition or would like more granular control over the selected state of Navigation items.
See also the Custom Navigation guide.
You can play with this example online in a web browser using the free codesandbox.io service. To launch this example, open the URL https://githubbox.com/keystonejs/keystone/tree/main/examples/custom-admin-ui-navigation. You can also fork this sandbox to make your own changes.