Skip to content

Commit

Permalink
feat(core): add navbar actions (@internal) (#5968)
Browse files Browse the repository at this point in the history
* feat(core): add navbar actions (`@internal`)

* refactor(tasks): use navbar actions
  • Loading branch information
hermanwikner authored and juice49 committed Mar 13, 2024
1 parent 463d1b6 commit 5c9fb85
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 45 deletions.
19 changes: 17 additions & 2 deletions packages/sanity/src/core/config/studio/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {type ComponentType, type ReactElement, type ReactNode} from 'react'
import {type ComponentType, type ReactElement} from 'react'

import {type Tool} from '../types'

Expand All @@ -18,15 +18,30 @@ export interface LogoProps {
renderDefault: (props: LogoProps) => ReactElement
}

/**
* @internal
* @beta
* An internal API for defining actions in the navbar.
*/
export interface NavbarAction {
icon?: React.ComponentType
location: 'topbar' | 'sidebar'
name: string
onAction: () => void
selected: boolean
title: string
}

/**
* @hidden
* @beta */
export interface NavbarProps {
renderDefault: (props: NavbarProps) => ReactElement

/**
* @internal
* @beta */
__internal_rightSectionNode?: ReactNode
__internal_actions?: NavbarAction[]
}

/**
Expand Down
38 changes: 32 additions & 6 deletions packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {SearchProvider} from './search/contexts/search/SearchProvider'
import {UserMenu} from './userMenu'
import {WorkspaceMenuButton} from './workspace'

const EMPTY_ARRAY: [] = []

const RootLayer = styled(Layer)`
min-height: auto;
position: relative;
Expand All @@ -60,8 +62,11 @@ const NavGrid = styled(Grid)`
* @hidden
* @beta */
export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
// eslint-disable-next-line camelcase
const {__internal_rightSectionNode = null} = props
const {
// eslint-disable-next-line camelcase
__internal_actions: actions = EMPTY_ARRAY,
} = props

const {name, tools} = useWorkspace()
const routerState = useRouterState()
const mediaIndex = useMediaIndex()
Expand Down Expand Up @@ -153,6 +158,25 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
setDrawerOpen(true)
}, [])

const actionNodes = useMemo(() => {
if (!shouldRender.tools) return null

return actions
?.filter((v) => v.location === 'topbar')
?.map((action) => {
return (
<Button
iconRight={action?.icon}
key={action.name}
mode="bleed"
onClick={action?.onAction}
selected={action.selected}
text={action.title}
/>
)
})
}, [actions, shouldRender.tools])

return (
<FreeTrialProvider>
<RootLayer zOffset={100} data-search-open={searchFullscreenOpen}>
Expand Down Expand Up @@ -236,10 +260,13 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
</BoundaryElementProvider>
</SearchProvider>
</LayerProvider>

{shouldRender.tools && <FreeTrial type="topbar" />}
{shouldRender.configIssues && <ConfigIssuesButton />}
{shouldRender.resources && <ResourcesButton />}

<PresenceMenu />

{/* Search button (mobile) */}
{shouldRender.searchFullscreen && (
<SearchButton
Expand All @@ -248,11 +275,9 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
/>
)}

{
// eslint-disable-next-line camelcase
__internal_rightSectionNode
}
{actionNodes}
</Flex>

{shouldRender.tools && (
<Box flex="none" marginLeft={1}>
<UserMenu />
Expand All @@ -265,6 +290,7 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {

{!shouldRender.tools && (
<NavDrawer
__internal_actions={actions}
activeToolName={activeToolName}
isOpen={drawerOpen}
onClose={handleCloseDrawer}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {CloseIcon, LeaveIcon} from '@sanity/icons'
import {Box, Card, Flex, Layer, Stack, Text} from '@sanity/ui'
import {AnimatePresence, motion, type Transition, type Variants} from 'framer-motion'
import {type KeyboardEvent, memo, useCallback} from 'react'
import {type KeyboardEvent, memo, useCallback, useMemo} from 'react'
import TrapFocus from 'react-focus-lock'
import styled from 'styled-components'

import {Button} from '../../../../../ui-components'
import {UserAvatar} from '../../../../components'
import {type Tool} from '../../../../config'
import {type NavbarAction, type Tool} from '../../../../config'
import {useTranslation} from '../../../../i18n'
import {useColorSchemeSetValue} from '../../../colorScheme'
import {useToolMenuComponent} from '../../../studio-components-hooks'
Expand Down Expand Up @@ -73,14 +73,15 @@ const InnerCardMotion = styled(motion(Card))`
`

interface NavDrawerProps {
__internal_actions?: NavbarAction[]
activeToolName?: string
isOpen: boolean
onClose: () => void
tools: Tool[]
}

export const NavDrawer = memo(function NavDrawer(props: NavDrawerProps) {
const {activeToolName, isOpen, onClose, tools} = props
const {__internal_actions: actions, activeToolName, isOpen, onClose, tools} = props

const setScheme = useColorSchemeSetValue()
const {auth, currentUser} = useWorkspace()
Expand All @@ -98,6 +99,35 @@ export const NavDrawer = memo(function NavDrawer(props: NavDrawerProps) {
[onClose],
)

const handleActionClick = useCallback(
(action: () => void) => {
action?.()
onClose()
},
[onClose],
)

const actionNodes = useMemo(() => {
return actions
?.filter((v) => v.location === 'sidebar')
?.map((action) => {
return (
<Button
icon={action?.icon}
justify="flex-start"
key={action.name}
mode="bleed"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleActionClick(action.onAction)}
selected={action.selected}
size="large"
text={action.title}
width="fill"
/>
)
})
}, [actions, handleActionClick])

return (
<AnimatePresence>
{isOpen && (
Expand Down Expand Up @@ -172,6 +202,12 @@ export const NavDrawer = memo(function NavDrawer(props: NavDrawerProps) {
</Card>

<Flex direction="column">
{actionNodes && (
<Card flex="none" padding={2}>
<Stack space={1}>{actionNodes}</Stack>
</Card>
)}

{setScheme && <AppearanceMenu setScheme={setScheme} />}
<LocaleMenu />
<ManageMenu />
Expand Down
49 changes: 44 additions & 5 deletions packages/sanity/src/tasks/plugin/TasksStudioNavbar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
import {PanelRightIcon, TaskIcon} from '@sanity/icons'
import {useCallback, useMemo} from 'react'
import {type NavbarProps} from 'sanity'

import {TasksNavbarButton, useTasksEnabled} from '../src'
import {useTasks, useTasksEnabled} from '../src'

export function TasksStudioNavbar(props: NavbarProps) {
const {enabled} = useTasksEnabled()
const EMPTY_ARRAY: [] = []

function TasksStudioNavbarInner(props: NavbarProps) {
const {toggleOpen, isOpen} = useTasks()

const handleAction = useCallback(() => {
toggleOpen()
}, [toggleOpen])

const actions = useMemo((): NavbarProps['__internal_actions'] => {
return [
...(props?.__internal_actions || EMPTY_ARRAY),
{
icon: PanelRightIcon,
location: 'topbar',
name: 'tasks-topbar',
onAction: handleAction,
selected: isOpen,
title: 'Tasks',
},
{
icon: TaskIcon,
location: 'sidebar',
name: 'tasks-sidebar',
onAction: handleAction,
selected: isOpen,
title: 'Tasks',
},
]
}, [handleAction, isOpen, props?.__internal_actions])

if (!enabled) return props.renderDefault(props)
return props.renderDefault({
...props,
// eslint-disable-next-line camelcase
__internal_rightSectionNode: <TasksNavbarButton />,
__internal_actions: actions,
})
}

export function TasksStudioNavbar(props: NavbarProps) {
const {enabled} = useTasksEnabled()

if (!enabled) {
return props.renderDefault(props)
}

return <TasksStudioNavbarInner {...props} />
}
1 change: 0 additions & 1 deletion packages/sanity/src/tasks/src/tasks/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './form'
export * from './navbar'
export * from './sidebar'

This file was deleted.

This file was deleted.

0 comments on commit 5c9fb85

Please sign in to comment.