Skip to content

Commit

Permalink
feat: improve responsiveness for navigation [CFISO-1555] (#2805)
Browse files Browse the repository at this point in the history
* refactor: render navbar complete as fullscreen on storybook

* feat: add mobile navigation to complete story

* refactor: improve responsiveness on navbar

* feat: add separate story for responsiveness

* chore: update to latest version

* feat: hide secondary nav children except search

* docs: use only navbar compound elements

* feat: prevent overflow of submenu on mobile

* feat: prevent header overflow with long space names

* feat: avoid overflow on small desktop screens

* feat: increase icon size for mobile devices

---------

Co-authored-by: Kathrin <[email protected]>
  • Loading branch information
massao and Lelith committed Jul 8, 2024
1 parent 8b7eaca commit de25286
Show file tree
Hide file tree
Showing 15 changed files with 380 additions and 31 deletions.
4 changes: 3 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/components/navbar/src/CompoundNavbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { NavbarItem, NavbarItemSkeleton } from './NavbarItem';
import { NavbarMenuItem, NavbarMenuItemSkeleton } from './NavbarMenuItem';
import { NavbarSwitcher, NavbarSwitcherSkeleton } from './NavbarSwitcher';
import { NavbarBadge } from './NavbarBadge/NavbarBadge';
import { NavbarSubmenu } from './NavbarSubmenu/NavbarSubmenu';

type CompoundNavbar = typeof OriginalNavbar & {
Item: typeof NavbarItem;
Expand All @@ -15,6 +16,7 @@ type CompoundNavbar = typeof OriginalNavbar & {
MenuItemSkeleton: typeof NavbarMenuItemSkeleton;
MenuDivider: typeof MenuDivider;
MenuSectionTitle: typeof MenuSectionTitle;
Submenu: typeof NavbarSubmenu;
Switcher: typeof NavbarSwitcher;
SwitcherSkeleton: typeof NavbarSwitcherSkeleton;
Account: typeof NavbarAccount;
Expand All @@ -29,6 +31,7 @@ Navbar.MenuItem = NavbarMenuItem;
Navbar.MenuItemSkeleton = NavbarMenuItemSkeleton;
Navbar.MenuDivider = MenuDivider;
Navbar.MenuSectionTitle = MenuSectionTitle;
Navbar.Submenu = NavbarSubmenu;
Navbar.Switcher = NavbarSwitcher;
Navbar.SwitcherSkeleton = NavbarSwitcherSkeleton;
Navbar.Account = NavbarAccount;
Expand Down
47 changes: 44 additions & 3 deletions packages/components/navbar/src/Navbar.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,57 @@ export const getNavbarStyles = (maxWidth: string, variant: string) => ({
width: '100%',
}),
logo: css({
height: '28px',
width: '28px',
display: 'none',
[mqs.small]: {
display: 'block',
height: '28px',
width: '28px',
},
}),

navigation: css({
width: '100%',
maxWidth: variant === 'wide' ? '1524px' : maxWidth,
padding: `${tokens.spacingS} ${tokens.spacingM}`,
minHeight: tokens.spacingL,
[mqs.medium]: {
[mqs.small]: {
padding: `${tokens.spacingM} ${tokens.spacingL}`,
},
}),

mainNavigation: css({
display: 'none',
[mqs.small]: {
display: 'flex',
},
}),

mobileNavigationButton: css({
display: 'flex',
height: '36px',
borderRadius: '10px',
[mqs.small]: {
display: 'none',
},
}),
mobileNavigationIcon: css({
heigt: '20px',
width: '20px',
}),

secondaryNavigationWrapper: css({
'> *:not(:first-child)': {
display: 'none',
[mqs.xsmall]: {
display: 'flex',
},
},
}),

account: css({
display: 'none',
[mqs.xsmall]: {
display: 'flex',
},
}),
});
57 changes: 47 additions & 10 deletions packages/components/navbar/src/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import React from 'react';
import { getNavbarStyles } from './Navbar.styles';
import { ContentfulLogoIcon } from './icons';
import { cx } from 'emotion';
import { Button } from '@contentful/f36-button';
import { ListIcon } from '@contentful/f36-icons';
import { NavbarMenu } from './NavbarMenu/NavbarMenu';

type NavbarOwnProps = CommonProps & {
/**
Expand All @@ -23,6 +26,9 @@ type NavbarOwnProps = CommonProps & {
/** User Account Component */
account?: React.ReactNode;

/** Navigation displayed on mobile versions */
mobileNavigation?: React.ReactNode;

/**
* Defines the max-width of the content inside the navbar.
* @default '100%'
Expand All @@ -48,6 +54,7 @@ function _Navbar(props: ExpandProps<NavbarProps>, ref: React.Ref<HTMLElement>) {
mainNavigation,
secondaryNavigation,
account,
mobileNavigation,
className,
contentMaxWidth = '100%',
testId = 'cf-ui-navbar',
Expand All @@ -68,25 +75,55 @@ function _Navbar(props: ExpandProps<NavbarProps>, ref: React.Ref<HTMLElement>) {
as="nav"
className={styles.navigation}
justifyContent="space-between"
gap="spacingXs"
>
<Flex alignItems="center" gap="spacingL">
{logo || <ContentfulLogoIcon className={styles.logo} />}
{mobileNavigation && (
<NavbarMenu
trigger={
<Button
className={styles.mobileNavigationButton}
startIcon={<ListIcon size="medium" />}
>
Menu
</Button>
}
>
{mobileNavigation}
</NavbarMenu>
)}
{mainNavigation && (
<Flex aria-label="Main Navigation" gap="spacingXs">
<Flex
className={styles.mainNavigation}
aria-label="Main Navigation"
gap="spacingXs"
>
{mainNavigation}
</Flex>
)}
</Flex>
<Flex alignItems="center" gap="spacingXs">
{switcher}
{secondaryNavigation && (
<Flex aria-label="Secondary Navigation">{secondaryNavigation}</Flex>
)}
{account && (
<Flex aria-label="Account Navigation" gap="spacingXs">
{account}
</Flex>
)}
<Flex alignItems="center">{switcher}</Flex>
<Flex alignItems="center" gap="spacingXs">
{secondaryNavigation && (
<Flex
className={styles.secondaryNavigationWrapper}
aria-label="Secondary Navigation"
>
{secondaryNavigation}
</Flex>
)}
{account && (
<Flex
className={styles.account}
aria-label="Account Navigation"
gap="spacingXs"
>
{account}
</Flex>
)}
</Flex>
</Flex>
</Flex>
</Flex>
Expand Down
16 changes: 14 additions & 2 deletions packages/components/navbar/src/NavbarItem/NavbarItem.styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from 'emotion';
import tokens from '@contentful/f36-tokens';
import { hexToRGBA } from '@contentful/f36-utils';
import { getGlowOnFocusStyles, increaseHitArea } from '../utils.styles';
import { getGlowOnFocusStyles, increaseHitArea, mqs } from '../utils.styles';

export const getNavbarItemActiveStyles = () =>
css({
Expand All @@ -22,7 +22,7 @@ const commonItemStyles = {
gap: tokens.spacing2Xs,
};

export const getNavbarItemStyles = () => ({
export const getNavbarItemStyles = ({ title }) => ({
navbarItem: css(
commonItemStyles,
{
Expand Down Expand Up @@ -76,6 +76,18 @@ export const getNavbarItemStyles = () => ({
paddingRight: tokens.spacingXs,
}),
isActive: getNavbarItemActiveStyles(),
icon: css({
height: '20px',
width: '20px',
display: !title ? 'block' : 'none',
[mqs.small]: {
height: '16px',
width: '16px',
},
[mqs.large]: {
display: 'block',
},
}),
});

export const getNavbarItemSkeletonStyles = () => ({
Expand Down
10 changes: 8 additions & 2 deletions packages/components/navbar/src/NavbarItem/NavbarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function _NavbarItem(
onClose,
...otherProps
} = props;
const styles = getNavbarItemStyles();
const styles = getNavbarItemStyles({ title });
const isMenuTrigger = isNavbarItemHasMenu(props);
const item = (
<Comp
Expand All @@ -71,7 +71,13 @@ function _NavbarItem(
)}
aria-label={title ? '' : label}
>
{icon && <NavbarItemIcon icon={icon} isActive={isActive} />}
{icon && (
<NavbarItemIcon
className={styles.icon}
icon={icon}
isActive={isActive}
/>
)}
{title && <span>{title}</span>}
{title && isMenuTrigger && <CaretIcon size="tiny" isActive={isActive} />}
</Comp>
Expand Down
11 changes: 8 additions & 3 deletions packages/components/navbar/src/NavbarItemIcon/NavbarItemIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import { cx } from 'emotion';

export type NavbarItemIconProps = {
icon: React.ReactElement<IconProps>;
className?: string;
} & Partial<Pick<IconProps, 'isActive'>>;

export const NavbarItemIcon = ({ icon, isActive }: NavbarItemIconProps) => {
const { className, size, ...rest } = icon.props;
export const NavbarItemIcon = ({
icon,
isActive,
className,
}: NavbarItemIconProps) => {
const { className: iconClassName, size, ...rest } = icon.props;
const styles = getNavbarItemIconStyles();

return React.cloneElement(icon, {
className: cx(className, styles.navbarItemIcon),
className: cx(iconClassName, styles.navbarItemIcon, className),
size: size || 'small',
isActive,
...rest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { css } from 'emotion';
import { mqs } from '../utils.styles';

export const getNavbarMenuStyles = () => ({
menuList: css({
minWidth: '250px',
minWidth: 0,
[mqs.xsmall]: {
minWidth: '250px',
},
}),
});
21 changes: 21 additions & 0 deletions packages/components/navbar/src/NavbarSubmenu/NavbarMenu.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import tokens from '@contentful/f36-tokens';
import { css } from 'emotion';
import { mqs } from '../utils.styles';

export const getNavbarSubmenuStyles = () => ({
navbarMenuItem: css({
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
gap: tokens.spacingXs,
}),
menuList: css({
minWidth: 0,
marginLeft: '-24px',
marginTop: '10px',
[mqs.xsmall]: {
minWidth: '250px',
margin: 0,
},
}),
});
41 changes: 41 additions & 0 deletions packages/components/navbar/src/NavbarSubmenu/NavbarSubmenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { Menu, type MenuListProps, type MenuProps } from '@contentful/f36-menu';
import { getNavbarSubmenuStyles } from './NavbarMenu.styles';
import {
NavbarItemIcon,
type NavbarItemIconProps,
} from '../NavbarItemIcon/NavbarItemIcon';
import { Flex } from '@contentful/f36-core';

export type NavbarSubmenuProps = {
title: string;
icon?: NavbarItemIconProps['icon'];
children?: React.ReactNode;
} & Pick<MenuListProps, 'testId'> &
Pick<MenuProps, 'onOpen' | 'onClose'>;

export const NavbarSubmenu = (props: NavbarSubmenuProps) => {
const {
title,
icon,
children,
testId = 'cf-ui-navbar-submenu-list',
onOpen,
onClose,
} = props;
const styles = getNavbarSubmenuStyles();

return (
<Menu.Submenu onOpen={onOpen} onClose={onClose}>
<Menu.SubmenuTrigger>
<Flex className={styles.navbarMenuItem}>
{icon && <NavbarItemIcon icon={icon} />}
<span>{title}</span>
</Flex>
</Menu.SubmenuTrigger>
<Menu.List className={styles.menuList} testId={testId}>
{children}
</Menu.List>
</Menu.Submenu>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import tokens from '@contentful/f36-tokens';
export type NavbarEnvVariantProps = Pick<
NavbarSwitcherProps,
'isAlias' | 'envVariant'
>;
> & {
className?: string;
};

export function NavbarEnvVariant({
isAlias,
envVariant,
className,
}: NavbarEnvVariantProps) {
const color = envVariant === 'master' ? tokens.green600 : tokens.orange500;

return isAlias ? (
<EnvironmentAliasIcon color={color} size="tiny" />
<EnvironmentAliasIcon color={color} className={className} size="medium" />
) : (
<EnvironmentIcon color={color} size="tiny" />
<EnvironmentIcon color={color} className={className} size="medium" />
);
}
Loading

0 comments on commit de25286

Please sign in to comment.