Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(SideNav): API Decision #2149

Merged
merged 26 commits into from
May 28, 2024
Merged

docs(SideNav): API Decision #2149

merged 26 commits into from
May 28, 2024

Conversation

saurabhdaware
Copy link
Member

@saurabhdaware saurabhdaware commented May 2, 2024

✨ Formatted Document ✨

Changelog of API

First Class Tooltip support as suggested by @anuraghazra

- <Tooltip content="Something">
-   <SideNavLink />
- </Tooltip>
+ <SideNavLink tooltip={{content: 'Something'}}>

as prop on SideNavLink as suggested by @kamleshchandnani

- <SideNav routerLink={NavLink}>
-  <SideNavLink />
- </SideNav>
+ <SideNav>
+  <SideNavLink as={NavLink} />
+ </SideNav>

Active state won't be handled internally

Consumers have to explicitly mark item as Active based on their router state. Checkout formatted doc for more info

<SideNavLink
   as={NavLink}
+  isActive={true}
/>

SideNavItem component addition

Instead of asking consumers to build custom item with Box, we give paddings and default styles pre-configured in SideNavItem. We can use it for Test Mode usecase now

<SideNavItem
  leading={}
  trailing={}
  title=""
/>

SideNavBody component addition

We needed to add SideNavBody to add scroll in the items

<SideNav>
-   <SideNavLink />
-   <SideNavLink />
+  <SideNavBody>
+    <SideNavLink />
+    <SideNavLink />
+  </SideNavBody>
</SideNav>
  

This comment was marked as off-topic.

This comment was marked as resolved.

This comment was marked as off-topic.

@rzpcibot

This comment was marked as off-topic.

@saurabhdaware saurabhdaware added the Review - L1 First level of review label May 3, 2024
Copy link
Contributor

github-actions bot commented May 3, 2024

✅ PR title follows Conventional Commits specification.

Comment on lines +212 to +217
<Button
href="/payouts/create"
icon={PlusIcon}
size="xsmall"
variant="tertiary"
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can put a tooltip here as well, we should update the screenshot with an example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both trailing and item itself can have tooltip so we can keep the one that we have below. I'll add tooltip example on trailing

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so all the trailing will only be visible on hover by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. And currently only button is accepted in trailing slot

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mention somewhere in documentation and here in prop table that trailing is only visible on hover

Comment on lines 251 to 257
<Tooltip content="Action Name (Cmd + P)">
<SideNavLink
icon={LayoutIcon}
title="L1 Item Name"
href="/new-item-link"
/>
</Tooltip>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause a lot of issues while iterating over the nested items, because now you have to map over the Tooltip and allow it as part of the composite element.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can instead have this as a first class feature.

`<SideNavLink tooltip={{ content: "Tooltip content" }} />

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause a lot of issues while iterating over the nested items, because now you have to map over the Tooltip and allow it as part of the composite element.

Trying out approach that doesn't require iterating over items

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`<SideNavLink tooltip={{ content: "Tooltip content" }} />

This makes sense. Lets go with this


<SideNav routerLink={NavLink}>
{/* L1 Items */}
<SideNavLink title="Home" icon={HomeIcon} href="/" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can we make a particular nav item selected by default? also, it's not just default if someone lands on a URL say one merchant sharing it with another/our support team when we land on that URL the nav item shall be selected by default so shall we also have controlled API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in office, even if we support adding isSelected or isActive prop, it will only select item and not change the route itself. With the current approach, they can move to their selected route and that will select item


<!-- prettier-ignore -->
```jsx
<Tooltip content="Action Name (Cmd + P)">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it also possible to have an info icon and show the tooltip on that? can we add an example of that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also shall we not have tooltip on the entire nav link and have on info icon? because a11y?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be there on overall item (similar to our Tooltip with Button example). Because tooltip represents what happens on click of item.

Anurag suggested having tooltip inside SideNavLink component as first-class feature so will explore that

{/* L1 Items */}
<SideNavLink title="L1 Item" />

<SideNavLink title="L2 Trigger">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if we just have NavLink and provide onClick and href as 2 props and if devs want nested levels they can use onClick and if its href that's a navigation? its the same terminology as link as button and link as nav

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently clicking item also changes the route so need href there

```jsx
<SideNav>
{/* L1 Items */}
<SideNavLink title="L1 Item" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are we deciding when to open the sub nav(L2) and when to expand it(L3)? using context?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup using context

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate more on this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically I pass level in context, then in next context I pass level + 1 so it gives the nesting of context providers from that I can tell if its L2 or L3


| **Props** | **Description** | **Type** | **Default Value** |
| --------- | ----------------------------------------------------- | -------- | ----------------- |
| title | Only applicable in L2. Becomes the title of the Level | string | |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to give a way for product teams to track analytics for every sub-component of Nav being clicked.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClick on SideNavLink should be able to cover most usecases 🤔

| --------------- | ------------------------------------------------------------ | -------- | ----------------- |
| title | title of the section | string | |
| maxVisibleItems | Number of items visible (rest go inside +x more collapsible) | number | undefined |
| children | Children slot. For SideNavLink children items | JSX | |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when a user clicks the +13 more we need to give them a trigger to track analytics

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup right. Will add this

/>
<SideNavLink as={NavLink} title="Edit" icon={UserIcon} href="/accounts/settings">
{/* L3 */}
<SideNavLevel>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to have sidelevel here? this kind of is leading to confusion because I can't have title on level 3. Also, the SideNavLink /accounts/settings you won't navigate right when you're on L2 and want to access L3 items because in this case the Edit its just acting like a collapsible item?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to have sidelevel here? this kind of is leading to confusion because I can't have title on level 3

I missed removing title prop from SideNavLevel in this example. Had removed it from props table. So yup we won't have title prop in both L2 and L3.

I kept SideNavLevel here because it then follows the same structure in L2 and L3 so people can recursively loop through the object and render items without any additional logic

Also, the SideNavLink /accounts/settings you won't navigate right when you're on L2 and want to access L3 items because in this case the Edit its just acting like a collapsible item?

This is true. Not sure what is alternative. Currently I have kept href optional so if you don't have href value here, it opens collapsible without navigation. But if you have href, then it navigates and opens collapsible at the same time (kind of like https://react.dev/reference/react-dom/client)

Comment on lines +212 to +217
<Button
href="/payouts/create"
icon={PlusIcon}
size="xsmall"
variant="tertiary"
/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so all the trailing will only be visible on hover by default?


</table>

### SideNavLevel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we will handle the edge case where people try to add L4 sidenav level and throw some meaningful error

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup added in implementation

Copy link

@vinilvasani vinilvasani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API decision doc is nicely put together and was fun to read through 🚀
Have dropped a few comments


#### Why not config-driven API?

1. Config-Driven API requires you to have a low-level compound API anyways since compound APIs give higher flexibility into adding leading, trailing, descriptions, and other items when needed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do compound APIs facilitate in providing higher flexibility?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets say you want to add leading, trailing, descriptions, etc to items. With config-driven API we'll have to keep on adding that to the config schema. So only things that we allow in our config schema are valid rest are invalid.

With compound API, on lower level you can put any JSX and that will be rendered. So if any new usecase comes, in a lot of scenarios we might be able to write custom JSX that allows adding that usecase without opting out of entire SideNav.

<SideNav>
  <Box>Random other text</Box>
</SideNav>


#### Why not this API?

1. While its the easiest to implement considering how close it is to final DOM structure 🙈, it is complex in understanding and the backend schema we have has nested L1, L2 JSON. So its more complex to loop through a data like that and render this structure

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed it's intuitional to read through the config and render the nested structure

```jsx
import { NavLink } from 'react-router-dom';

<SideNav routerLink={NavLink}>{/* children */}</SideNav>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have a nav item change/select handler on the top-level component that can listen to selections by the user and can then be used for something like triggering relevant analytics events?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have onClick on items so that can be used for analytics. E.g. If onClick on L2 trigger item is called then its L2 open event for analytics

| rel | anchor tag rel attribute [rel - MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#rel) | AnchorRelType | target === ' \_blank ' ? ' noreferrer noopener ' : undefined |
| onClick | Click handler on item | (e: React.MouseEvent) => void | |
| icon | Blade's Icon Component | IconComponent | |
| trailing | Trailing Slot of Item. Can be used for adding Quick Shortcut Button, Trailing Text | JSX | |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will trailing and titleSuffix sit together?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the screenshots in table below. titleSuffix will come next to title and trailing will be on the right. However depending on usecase and how it looks, size of title, etc. Designers can take call whether they really want to show both or if just one is fine

| onClick | Click handler on item | (e: React.MouseEvent) => void | |
| icon | Blade's Icon Component | IconComponent | |
| trailing | Trailing Slot of Item. Can be used for adding Quick Shortcut Button, Trailing Text | JSX | |
| titleSuffix | Slot after the title to add Badge, Counter | JSX | |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this slot has a designated function of displaying badges and counters, instead of titleSuffix can we call it something on the lines of supplementSlot

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

titleSuffix is the standard name we follow in components like AccordionHeader, Card, DropdownHeader, etc to make API more predictable across blade so following the same thing here

{/* L1 Items */}
<SideNavLink title="L1 Item" />

<SideNavLink title="L2 Trigger">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a use case where we need to mark the L2 trigger as active? Won't one of the nested nav items inside of L2 be active?
Uploading Screenshot 2024-05-16 at 2.29.28 AM.png…

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image When one of the item inside is marked active, its parent is also marked active in L1

| **Props** | **Description** | **Type** | **Default Value** |
| --------------- | ------------------------------------------------------------ | -------- | ----------------- |
| title | title of the section | string | |
| maxVisibleItems | Number of items visible (rest go inside +x more collapsible) | number | undefined |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if maxVisibleItems is not provided will it be in expanded state by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup in that case all items are always visible and that "Show More" link itself won't exist


## Accessibility

1. All items should be accessible by `TAB`. Including going between levels L1, L2, L3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the SideNav mounts will the initially active item be in focus by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no the focus will be controlled on page level so assuming the top-nav would have first focus and then sidenav

## Accessibility

1. All items should be accessible by `TAB`. Including going between levels L1, L2, L3
2. We should use Blade's `SkipNav` utility to provide option of skipping nav and going to content

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the SkipNav utility can you share more context about it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this utility in blade - https://blade.razorpay.com/?path=/docs/components-accessibility-skipnav--docs

So if you start tabbing, you see a Skip to content link which you can click to skip navigation and go to content directly

Earlier we started the API by saying that marking of the link as active based on route will be handled internally in blade component. There are some implemetation constraints when we go with this approach of handling active link internally

1. We don't have access to hooks like `useLocation` or utilities like `matchPath` inside Blade since blade is library indepedent of the routing logic
2. Although React Router's NavLink automatically adds `aria-current="page"` to active link which can be used to change colors in CSS, it doesn't give us access to handling state internally for our L1 -> L2 navigations [Here's the related proposal I created in React Router's Repo](https://github.com/remix-run/react-router/discussions/11543).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NavLink internally maintains the isActive based on route defined right? Can we not make use of that?

it doesn't give us access to handling state internally for our L1 -> L2 navigations

can you please elaborate on this a little?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NavLink internally maintains the isActive based on route defined right? Can we not make use of that?

It doesn't give any isActive in JS / React state. It adds aria-current="page" to link which can be used for CSS styling but because we have multiple menus where lets say you want to open L2 from L1, you need a way to know which item is Active in the react state (Added a bit more context in that React Router's proposal)

NavLink internally maintains the isActive based on route defined right

Basically the issue is its "only" internally maintaining that active state inside React Router's NavLink. Its not giving us a way to access that active state in our component's state

Comment on lines +37 to +39
// sets the submenu as active
isActive={true}
href="/accounts"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does a submenu has an href?

Shouldn't the href of the first children of this submenu be auto selected?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell which is the first element without doing React.Children.map which we can't do because people will create wrapper NavLink which handles react router state

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so what happens if the Submenu href and the first items href doesn't match?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still works. Just L2 menu won't have any selection and it will take you to submenu's href

@saurabhdaware saurabhdaware merged commit a81c85a into master May 28, 2024
14 checks passed
@saurabhdaware saurabhdaware deleted the sidenav/api branch May 28, 2024 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Review - L1 First level of review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants