-
Notifications
You must be signed in to change notification settings - Fork 34
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
feat: adds app switcher lab component #2416
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added notes from meeting with @KevinGhadyani-Okta
const renderAppIconLink = useCallback( | ||
(muiProps: MuiPropsContextType) => { | ||
return ( | ||
<NavRailAppLinkComponent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add aria-current
> | ||
<OktaAura /> | ||
</NavRailOktaAuraWrapperComponent> | ||
{appIcons.map((appIcon) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add nav
(Add to the NavRail wrapper), ul
(Add a wrapper over all the apps), li
(Add to NavRailApp component)
shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", | ||
})(({ odysseyDesignTokens }: { odysseyDesignTokens: DesignTokens }) => ({ | ||
width: "100%", | ||
height: NAV_RAIL_WIDTH, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See if we can import a shared value between top nav
const APP_SIDE_LENGTH = "36px"; | ||
const APP_ICON_SIDE_LENGTH = "32px"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to rem
const NAV_RAIL_WIDTH = "64px"; | ||
|
||
export type NavRailProps = { | ||
appIcons?: Omit<NavRailAppProps, "selectedAppName">[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer using Pick
instead of Omit
(muiProps: MuiPropsContextType) => { | ||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implicit return
> | ||
<NavRailAppImgComponent | ||
src={isSelected ? appIconSelectedUrl : appIconDefaultUrl} | ||
alt="" // Tell screen reader to ignore the image; the Tooltip describes the link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
role=presentation
|
||
const NAV_RAIL_WIDTH = "64px"; | ||
|
||
export type NavRailProps = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add visibilityState = invisible | loading | loaded
How should we handle the loading state?
Skeleton
- Enduser: Skeleton shows, then disappears (pop in during load)
- Admin: Skeleton shows, then gets populated
No Skeleton ✅
- Enduser: Skeleton does not show, then disappears
- Admin: Skeleton does not show, then nav rail pops in
- Could we mitigate this via storing in local storage to prevent pop in? Would pop in the first load, but would not pop in after that.
- Also consider that this is just for the MVP where it is only shown for the admin. In the future, when it is shown to all users, we could switch to skeleton.
Full screen loading screen (prevents pop in)
- Would block showing the app until we get the data. Unacceptable to downstream teams.
Keep side nav the same width regardless of app switcher
- No, would break design assumptions
play: async ({ canvasElement, step }: PlaywrightProps<NavRailProps>) => { | ||
const canvas = within(canvasElement); | ||
|
||
await step("Nav rail successfully loads", async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shows all items
play: async ({ canvasElement, step }: PlaywrightProps<NavRailProps>) => { | ||
const canvas = within(canvasElement); | ||
|
||
await step("Nav rail does not load", async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hides when no items
6485e4e
to
503f6d7
Compare
} from "./AppSwitcherApp"; | ||
import { TOP_NAV_HEIGHT } from "../TopNav"; | ||
|
||
const APP_SWITCHER_WIDTH = `${64 / 14}rem`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaeltamaki-okta Is this value meant to scale with the font size? Or, should it be a static px value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it should be a rem value, we should probably use one of our spacing tokens here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be Spacing9 I think. The same as top nav.
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
marginBottom: odysseyDesignTokens.Spacing4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marginBottom: odysseyDesignTokens.Spacing4, | |
marginBlockEnd: odysseyDesignTokens.Spacing4, |
|
||
const APP_SIDE_LENGTH_VAL = 36; | ||
const APP_SIDE_LENGTH_REM = `${APP_SIDE_LENGTH_VAL / 14}rem`; | ||
const APP_ICON_SIDE_LENGTH = `${32 / 14}rem`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be using token values for these. If we don't have token values for these sizes already, the sizes are likely incorrect. We'd have to go back to design and tell them we're using a token that already exists
alignItems: "center", | ||
justifyContent: "center", | ||
margin: 0, | ||
width: APP_SIDE_LENGTH_REM, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The img
size + the padding should be setting the width/height on these. We shouldn't need to set an explicit size here, I wouldn't think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not feel qualified to review the core component, but the changes UiShell looks exactly like I hoped/expected (and that's primarily what my approval relates to)! Great work!
</AppSwitcherOktaAuraWrapperComponent> | ||
<AppSwitcherAppIconULComponent> | ||
{isLoading | ||
? [...Array(3)].map(() => <AppSwitcherAppSkeleton />) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer this syntax:
? [...Array(3)].map(() => <AppSwitcherAppSkeleton />) | |
? Array(3).fill().map(() => <AppSwitcherAppSkeleton />) |
I don't like using [...something]
when we could use a semantic method like .slice()
or .concat
instead. Your intent is more clear that way.
selectedAppName, | ||
}: AppSwitcherAppProps) => { | ||
const odysseyDesignTokens = useOdysseyDesignTokens(); | ||
const isSelected = appName === selectedAppName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You got selectedAppName
in the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd have to include this as part of renderUiShell
, it is not returned in the API response
await expect(canvas.getAllByRole("navigation")).toHaveLength(1); | ||
await expect(canvas.queryAllByRole("listitem")).toHaveLength(0); | ||
await expect(canvas.queryAllByRole("link")).toHaveLength(0); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can check on the navigation .toBeVisible()
which is more accurate to your intent. You don't care if it's in the DOM.
I wonder if navigations should have an aria label because someone's not gonna know what they're selecting. I think there's a difference between primary and secondary navigations in ARIA. Something you'll wanna check into.
If so, then you'd do getByRole("navigation", { name: "YOUR_LABEL" })
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I made a few small comments.
3f532de
3f532de
to
305c19d
Compare
OKTA-825118
Summary
feat: adds app switcher lab component
Building out the presentation layer of the "App Switcher" https://odyssey.okta.design/latest/components/app-switcher/design-mla5Wn5G (now called "Nav Rail").
Figma: https://www.figma.com/design/zh7A9gjaDlI50Hctdvd1OB/Odyssey-Components?node-id=22141-142254&node-type=frame&t=Qc7MjiWUFNtOh9Vd-0
Playground: https://6485e4e33982ca4977fff8d81ef98f680e713a9c.ods.dev/?path=/docs/labs-components-appswitcher--docs
Component checklist
Code structure
Accessibility implementation
Internationalization (i18n) implementation
Cross-browser compatibility
Chrome
Safari
Firefox
Documentation
Finalize code
QA
Testing and validation