From d49b8a87b53613e0d921a66cb6f539c62440ee21 Mon Sep 17 00:00:00 2001 From: Stephen Watkins Date: Tue, 7 Nov 2023 11:09:17 -0500 Subject: [PATCH 1/8] add spec --- documentation/specs/VerticalNav.md | 157 +++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 documentation/specs/VerticalNav.md diff --git a/documentation/specs/VerticalNav.md b/documentation/specs/VerticalNav.md new file mode 100644 index 00000000..686cd617 --- /dev/null +++ b/documentation/specs/VerticalNav.md @@ -0,0 +1,157 @@ +# `VerticalNav` Component Specification + +## Overview + +`VerticalNav` renders a vertical list of navigation links. + +### Prior Art + +- [Primer ``](https://primer.style/components/nav-list) + +--- + +## Design + +`VerticalNav` will be a compound component consisting of `VerticalNav`, `VerticalNav.Item`, and `VerticalNav.Subnav`. + +A vertical list of links with expandable top-level items can be rendered with an `ExpandableVerticalNav`. + +`VerticalNav` uses React Stately's `useListState` hook behind the scenes. This not only handles managing the selection of items but provides consistency with other Easy UI component APIs. `ExpandableVerticalNav` uses React Stately's `useTreeState` hook to support expansion. + +`VerticalNav.Item` controls rendering individual links within a list. It contains props for the link's `label`, `icon`, and optionally a `children` prop to render nested subnavigation. + +`VerticalNav.Item` is a polymorphic component. By default, it renders as an anchor element—``—but it can be rendered as a custom element using the `as` prop. e.g. ``. + +Selection and expansion is controlled by the consumer through the API provided by React Stately's `useListState` and `useTreeState` hooks—`selectedKeys`, `expandedKeys`, and `onExpandedChange`. Note that while the React Stately API accepts an array of `selectedKeys`, the `selectionMode` is set to `single`, so only one key is read. + +The API is flexible for limiting surface area and accommodating potential future use cases. Current constraints will be enforced at runtime to meet design requirements—e.g. limiting the levels of sub navigation. + +### API + +```ts +type BaseVerticalNavProps = AriaLabelingProps & { + children: ReactNode; + renderLogo?: () => ReactNode; +}; + +export type VerticalNavProps = BaseVerticalNavProps & ListProps; + +export type ExpandableVerticalNavProps = BaseVerticalNavProps & + TreeProps; + +export type VerticalNavItemProps = + ComponentProps & { + as?: T; + label: string; + icon?: IconSymbol; + children?: ReactNode; + }; + +export type VerticalNavSubnavProps = ListProps; +``` + +### Example Usage + +_Simple vertical nav:_ + +```tsx +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( + + + + + + + ); +} +``` + +_Dense vertical nav:_ + +```tsx +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( + + + + + + + + + + + + ); +} +``` + +_Expandable vertical nav:_ + +```tsx +import { + ExpandableVerticalNav, + VerticalNav, +} from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + const [expandedKeys, setExpandedKeys] = useState(["1"]); + return ( + { + setExpandedKeys(keys); + }} + > + + + + + + + + + + + + + + + + ); +} +``` + +_Custom link component:_ + +```tsx +import Link from "next/link"; +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( + + + + + + + ); +} +``` + +--- + +## Behavior + +### Accessibility + +- `VerticalNav` items are links and should avoid being buttons or other clickable elements. +- `VerticalNav` should be labeled with `aria-label`. From 7c3f8a406992809d24adb59584742cc01080b239 Mon Sep 17 00:00:00 2001 From: Stephen Watkins Date: Tue, 7 Nov 2023 11:38:21 -0500 Subject: [PATCH 2/8] add supplementary action support --- documentation/specs/VerticalNav.md | 79 ++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/documentation/specs/VerticalNav.md b/documentation/specs/VerticalNav.md index 686cd617..7eb6ddb1 100644 --- a/documentation/specs/VerticalNav.md +++ b/documentation/specs/VerticalNav.md @@ -12,12 +12,14 @@ ## Design -`VerticalNav` will be a compound component consisting of `VerticalNav`, `VerticalNav.Item`, and `VerticalNav.Subnav`. - -A vertical list of links with expandable top-level items can be rendered with an `ExpandableVerticalNav`. +`VerticalNav` will be a compound component consisting of `VerticalNav`, `VerticalNav.Item`, and `VerticalNav.Subnav`. A vertical list of links with expandable top-level items can be rendered with an `ExpandableVerticalNav`. `VerticalNav` uses React Stately's `useListState` hook behind the scenes. This not only handles managing the selection of items but provides consistency with other Easy UI component APIs. `ExpandableVerticalNav` uses React Stately's `useTreeState` hook to support expansion. +`VerticalNav` can optionally render a logo at the top of the navigation with the `renderLogo` render prop. `VerticalNav` can optionally render a banner at the top of the navigation with the `renderBanner` render prop. + +`VerticalNav` supports rendering a supplementary action at the bottom of the navigation container using the `supplementaryAction` prop. `supplementaryAction` accepts a `text` prop for the action text, along with a `render` prop for supplying the action's underlying element. This pattern allows the behavior of the action to be controlled by the consumer (e.g. link or button) while preserving the action's content design requirements within `VerticalNav`. + `VerticalNav.Item` controls rendering individual links within a list. It contains props for the link's `label`, `icon`, and optionally a `children` prop to render nested subnavigation. `VerticalNav.Item` is a polymorphic component. By default, it renders as an anchor element—``—but it can be rendered as a custom element using the `as` prop. e.g. ``. @@ -31,7 +33,12 @@ The API is flexible for limiting surface area and accommodating potential future ```ts type BaseVerticalNavProps = AriaLabelingProps & { children: ReactNode; + renderBanner?: () => ReactNode; renderLogo?: () => ReactNode; + supplementaryAction?: { + text: string; + render: (props: { children: ReactNode }) => ReactNode; + }; }; export type VerticalNavProps = BaseVerticalNavProps & ListProps; @@ -147,6 +154,72 @@ function Sidebar() { } ``` +_Rendering a logo:_ + +```tsx +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( + } + > + + + + + + ); +} +``` + +_Rendering a banner:_ + +```tsx +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( + } + selectedKeys={["1"]} + > + + + + + + ); +} +``` + +_Rendering a supplementary action:_ + +```tsx +import { VerticalNav } from "@easypost/easy-ui/VerticalNav"; + +function Sidebar() { + return ( +