Skip to content

Commit

Permalink
feat(core): initial navbar pass (#5142)
Browse files Browse the repository at this point in the history
* feat(navbar): started studio ui navbar work

* fix(core): studio navbar work

* feat: first pass of updated navbar search, deprecate components.logo, remove previous placeholder search components

* fix: revert CollapseMenuButton changes

* fix: remove duplicate studio navbar border

* fix: re-add user avatar color

---------

Co-authored-by: Robin Pyon <[email protected]>
  • Loading branch information
ninaandal and robinpyon committed Nov 18, 2023
1 parent 0d369e4 commit f3c467f
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 305 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {forwardRef} from 'react'
import {TooltipProps, Button, ButtonProps} from '../../../ui'
import {Button, ButtonProps, TooltipProps} from '../../../ui'

/** @internal */
export interface CommonProps extends Omit<ButtonProps, 'text' | 'iconRight'> {
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/src/core/config/studio/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface StudioComponents {
* @beta */
export interface StudioComponentsPluginOptions {
layout?: React.ComponentType<LayoutProps>
/** @deprecated Use logoMark instead */
logo?: React.ComponentType<LogoProps>
navbar?: React.ComponentType<NavbarProps>
toolMenu?: React.ComponentType<ToolMenuProps>
Expand Down
19 changes: 18 additions & 1 deletion packages/sanity/src/core/studio/StudioLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ const SearchFullscreenPortalCard = styled(Card)`
/** @internal */
export interface NavbarContextValue {
onSearchFullscreenOpenChange: (open: boolean) => void
onSearchOpenChange: (open: boolean) => void
searchFullscreenOpen: boolean
searchFullscreenPortalEl: HTMLElement | null
searchOpen: boolean
}

/** @internal */
export const NavbarContext = createContext<NavbarContextValue>({
onSearchFullscreenOpenChange: () => '',
onSearchOpenChange: () => '',
searchFullscreenOpen: false,
searchFullscreenPortalEl: null,
searchOpen: false,
})

/**
Expand Down Expand Up @@ -81,6 +85,7 @@ export function StudioLayout() {
const [searchFullscreenPortalEl, setSearchFullscreenPortalEl] = useState<HTMLDivElement | null>(
null,
)
const [searchOpen, setSearchOpen] = useState<boolean>(false)

const documentTitle = useMemo(() => {
const mainTitle = title || startCase(name)
Expand All @@ -104,13 +109,25 @@ export function StudioLayout() {
setSearchFullscreenOpen(open)
}, [])

const handleSearchOpenChange = useCallback((open: boolean) => {
setSearchOpen(open)
}, [])

const navbarContextValue = useMemo(
() => ({
searchFullscreenOpen,
searchFullscreenPortalEl,
searchOpen,
onSearchFullscreenOpenChange: handleSearchFullscreenOpenChange,
onSearchOpenChange: handleSearchOpenChange,
}),
[searchFullscreenOpen, searchFullscreenPortalEl, handleSearchFullscreenOpenChange],
[
searchFullscreenOpen,
searchFullscreenPortalEl,
searchOpen,
handleSearchFullscreenOpenChange,
handleSearchOpenChange,
],
)

const Navbar = useNavbarComponent()
Expand Down
27 changes: 27 additions & 0 deletions packages/sanity/src/core/studio/components/navbar/SanityLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'

export function SanityLogo() {
return (
<svg
width="100%"
height="100%"
viewBox="0 0 33 33"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100%" height="100%" fill="#EC5446" />
<path
d="M10.1637 8.5498C10.1637 11.3854 11.9461 13.0725 15.5143 13.9622L19.2955 14.8235C22.6725 15.5855 24.729 17.4783 24.729 20.5619C24.7553 21.9053 24.3107 23.2168 23.4698 24.2765C23.4698 21.1999 21.8499 19.5376 17.9425 18.538L14.2299 17.7086C11.257 17.0423 8.9623 15.4863 8.9623 12.1368C8.94666 10.8433 9.36932 9.58143 10.1637 8.5498Z"
fill="white"
/>
<path
d="M21.1463 19.8069C22.7591 20.8206 23.4662 22.2384 23.4662 24.2729C22.1313 25.953 19.7861 26.8958 17.0297 26.8958C12.3899 26.8958 9.1427 24.6521 8.42111 20.7533H12.8769C13.4506 22.5432 14.9695 23.3726 16.9972 23.3726C19.4722 23.3726 21.1175 22.0753 21.1499 19.7998"
fill="#F8B1AA"
/>
<path
d="M12.4801 12.7536C11.7436 12.3236 11.1394 11.7057 10.7316 10.9656C10.3238 10.2255 10.1276 9.3907 10.1638 8.54984C11.4518 6.88396 13.6923 5.8667 16.4235 5.8667C21.1499 5.8667 23.8848 8.31945 24.5595 11.7717H20.2732C19.8006 10.4107 18.6172 9.35089 16.4596 9.35089C14.1541 9.35089 12.5811 10.6694 12.4909 12.7536"
fill="#F8B1AA"
/>
</svg>
)
}
174 changes: 93 additions & 81 deletions packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {MenuIcon, SearchIcon} from '@sanity/icons'
import {MenuIcon} from '@sanity/icons'
import {
BoundaryElementProvider,
Box,
Expand All @@ -9,6 +9,7 @@ import {
PortalProvider,
useMediaIndex,
useRootTheme,
Text,
} from '@sanity/ui'
import React, {useCallback, useState, useMemo, useEffect, useRef, useContext} from 'react'
import {startCase} from 'lodash'
Expand All @@ -19,20 +20,25 @@ import {Button} from '../../../../ui'
import {useColorScheme} from '../../colorScheme'
import {useWorkspaces} from '../../workspaces'
import {NavbarContext} from '../../StudioLayout'
import {useLogoComponent, useToolMenuComponent} from '../../studio-components-hooks'
import {useToolMenuComponent} from '../../studio-components-hooks'
import {StudioTheme} from '../../../theme'
import {useActiveWorkspace} from '../../activeWorkspaceMatcher'
import {UserMenu} from './userMenu'
import {NewDocumentButton, useNewDocumentOptions} from './new-document'
import {PresenceMenu} from './presence'
import {NavDrawer} from './NavDrawer'
import {WorkspaceMenuButton} from './workspace'
import {ConfigIssuesButton} from './configIssues/ConfigIssuesButton'
import {LogoButton} from './LogoButton'
import {SearchDialog, SearchField} from './search'
import {SearchButton, SearchDialog} from './search'
import {SearchProvider} from './search/contexts/search/SearchProvider'
import {ResourcesButton} from './resources/ResourcesButton'
import {SanityLogo} from './SanityLogo'
import {SearchPopover} from './search/components/SearchPopover'
import {RouterState, useRouterState, useStateLink} from 'sanity/router'

const LOGO_MARK_SIZE = 33 // width and height, px

const RootLayer = styled(Layer)`
min-height: auto;
position: relative;
Expand All @@ -47,8 +53,12 @@ const RootCard = styled(Card)`
line-height: 0;
`

const LeftFlex = styled(Flex)`
width: max-content;
const LogoMarkContainer = styled(Card).attrs({
overflow: 'hidden',
radius: 2,
})`
height: ${LOGO_MARK_SIZE}px;
width: ${LOGO_MARK_SIZE}px;
`

/**
Expand All @@ -66,10 +76,14 @@ export function StudioNavbar() {

const newDocumentOptions = useNewDocumentOptions()

const {onSearchFullscreenOpenChange, searchFullscreenOpen, searchFullscreenPortalEl} =
useContext(NavbarContext)
const {
onSearchFullscreenOpenChange,
onSearchOpenChange,
searchFullscreenOpen,
searchFullscreenPortalEl,
searchOpen,
} = useContext(NavbarContext)

const Logo = useLogoComponent()
const ToolMenu = useToolMenuComponent()

const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
Expand All @@ -90,14 +104,16 @@ export function StudioNavbar() {
const [drawerButtonEl, setDrawerButtonEl] = useState<HTMLButtonElement | null>(null)
const [searchOpenButtonEl, setSearchOpenButtonEl] = useState<HTMLButtonElement | null>(null)

const {activeWorkspace} = useActiveWorkspace()

const shouldRender = useMemo(
() => ({
brandingCenter: mediaIndex <= 1,
resources: mediaIndex > 1,
collapsedPresenceMenu: mediaIndex <= 1,
loginStatus: mediaIndex > 1,
searchFullscreen: mediaIndex <= 1,
configIssues: mediaIndex > 1 && isDev,
newDocumentFullscreen: mediaIndex <= 1,
workspaces: mediaIndex >= 3 && workspaces.length > 1,
tools: mediaIndex >= 3,
}),
Expand All @@ -117,10 +133,18 @@ export function StudioNavbar() {
}
}, [onSearchFullscreenOpenChange, shouldRender.searchFullscreen])

const handleOpenSearch = useCallback(() => {
onSearchOpenChange(true)
}, [onSearchOpenChange])

const handleOpenSearchFullscreen = useCallback(() => {
onSearchFullscreenOpenChange(true)
}, [onSearchFullscreenOpenChange])

const handleCloseSearch = useCallback(() => {
onSearchOpenChange(false)
}, [onSearchOpenChange])

const handleCloseSearchFullscreen = useCallback(() => {
onSearchFullscreenOpenChange(false)
searchOpenButtonEl?.focus()
Expand All @@ -137,48 +161,63 @@ export function StudioNavbar() {

return (
<RootLayer zOffset={100} data-search-open={searchFullscreenOpen}>
<RootCard
data-testid="navbar"
data-ui="Navbar"
padding={2}
scheme="dark"
shadow={theme.__legacy || scheme === 'dark' ? 1 : undefined}
sizing="border"
>
<RootCard borderBottom data-testid="navbar" data-ui="Navbar" padding={2} sizing="border">
{/** Left flex */}
<Flex align="center" justify="space-between">
<LeftFlex align="center" flex={shouldRender.brandingCenter ? undefined : 1}>
{!shouldRender.tools && (
<Box marginRight={1}>
<Flex align="center" gap={2}>
<Flex align="center" gap={1}>
{/* Menu button */}
{!shouldRender.tools && (
<Button
mode="bleed"
icon={MenuIcon}
onClick={handleOpenDrawer}
ref={setDrawerButtonEl}
/>
</Box>
)}
)}

{!shouldRender.brandingCenter && (
<Box marginRight={1}>
<LogoButton href={rootHref} onClick={handleRootClick} title={title}>
<Logo title={title} />
</LogoButton>
</Box>
)}
{/* Logo mark */}
<LogoButton href={rootHref} onClick={handleRootClick} title={title}>
<Flex align="center">
<LogoMarkContainer>
<Flex align="center" height="fill" justify="center">
<SanityLogo />
</Flex>
</LogoMarkContainer>
<Box paddingX={2}>
<Text size={1} weight="medium">
{activeWorkspace.title}
</Text>
</Box>
</Flex>
</LogoButton>

{shouldRender.workspaces && (
<Box marginRight={2}>
<WorkspaceMenuButton collapsed />
</Box>
)}
{/* Workspace menu button */}
{shouldRender.workspaces && <WorkspaceMenuButton collapsed />}
</Flex>

{/* New document button */}
<NewDocumentButton
{...newDocumentOptions}
modal={shouldRender.newDocumentFullscreen ? 'dialog' : 'popover'}
/>
</Flex>

<Box marginRight={shouldRender.brandingCenter ? undefined : 2}>
<NewDocumentButton
{...newDocumentOptions}
modal={shouldRender.brandingCenter ? 'dialog' : 'popover'}
{/** Center flex */}
<Flex align="center">
{shouldRender.tools && (
<ToolMenu
activeToolName={activeToolName}
closeSidebar={handleCloseDrawer}
context="topbar"
isSidebarOpen={false}
tools={tools}
/>
</Box>
)}
</Flex>

{/** Right flex */}
<Flex align="center" gap={3} style={{flexShrink: 0}}>
{/* Search */}
<LayerProvider>
<SearchProvider fullscreen={shouldRender.searchFullscreen}>
Expand All @@ -192,55 +231,28 @@ export function StudioNavbar() {
/>
)}
</PortalProvider>
{!shouldRender.searchFullscreen && <SearchField />}
<SearchPopover
onClose={handleCloseSearch}
onOpen={handleOpenSearch}
open={searchOpen}
/>
</BoundaryElementProvider>
</SearchProvider>
</LayerProvider>

{shouldRender.tools && (
<Card flex={1} marginX={2} overflow="visible" paddingRight={1}>
<ToolMenu
activeToolName={activeToolName}
closeSidebar={handleCloseDrawer}
context="topbar"
isSidebarOpen={false}
tools={tools}
/>
</Card>
{/* Search button (desktop) */}
{!shouldRender.searchFullscreen && (
<SearchButton onClick={handleOpenSearch} ref={setSearchOpenButtonEl} />
)}
</LeftFlex>

{shouldRender.brandingCenter && (
<Box marginX={1}>
<LogoButton href={rootHref} onClick={handleRootClick} title={title}>
<Logo title={title} />
</LogoButton>
</Box>
)}

<Flex gap={2} align="center">
{(shouldRender.configIssues || shouldRender.resources) && (
<Card borderRight>
<Flex gap={1} paddingX={2}>
{shouldRender.configIssues && <ConfigIssuesButton />}
{shouldRender.resources && <ResourcesButton />}
</Flex>
</Card>
{shouldRender.configIssues && <ConfigIssuesButton />}
{shouldRender.resources && <ResourcesButton />}
<PresenceMenu />
{shouldRender.tools && <UserMenu />}
{/* Search button (mobile) */}
{shouldRender.searchFullscreen && (
<SearchButton onClick={handleOpenSearchFullscreen} ref={setSearchOpenButtonEl} />
)}

<Flex align="center" gap={1}>
<PresenceMenu collapse={shouldRender.collapsedPresenceMenu} />
{shouldRender.tools && <UserMenu />}
{shouldRender.searchFullscreen && (
<Button
aria-label="Open search"
icon={SearchIcon}
mode="bleed"
onClick={handleOpenSearchFullscreen}
ref={setSearchOpenButtonEl}
/>
)}
</Flex>
</Flex>
</Flex>
</RootCard>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback, useMemo, useState} from 'react'
import {Text, useClickOutside, Stack, TextInput, TextInputProps, Card, Flex} from '@sanity/ui'
import {ComposeIcon, SearchIcon} from '@sanity/icons'
import {AddIcon, ChevronDownIcon, ComposeIcon, SearchIcon} from '@sanity/icons'
import ReactFocusLock from 'react-focus-lock'
import {InsufficientPermissionsMessage} from '../../../../components'
import {useCurrentUser} from '../../../../store'
Expand Down Expand Up @@ -131,11 +131,14 @@ export function NewDocumentButton(props: NewDocumentButtonProps) {
() => ({
'aria-label': title,
disabled: disabled || loading,
icon: ComposeIcon,
mode: 'bleed',
iconRight: ChevronDownIcon,
icon: AddIcon,
text: 'Create',
mode: 'ghost',
onClick: handleToggleOpen,
ref: setButtonElement,
selected: open,
size: 'small',
}),
[disabled, handleToggleOpen, loading, open, title],
)
Expand Down
Loading

0 comments on commit f3c467f

Please sign in to comment.