Skip to content

Commit 8336c9f

Browse files
authoredMar 12, 2024
feat(core): add navbar actions (@internal) (#5968)
* feat(core): add navbar actions (`@internal`) * refactor(tasks): use navbar actions
1 parent a1237d6 commit 8336c9f

File tree

7 files changed

+132
-45
lines changed

7 files changed

+132
-45
lines changed
 

‎packages/sanity/src/core/config/studio/types.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {type ComponentType, type ReactElement, type ReactNode} from 'react'
1+
import {type ComponentType, type ReactElement} from 'react'
22

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

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

21+
/**
22+
* @internal
23+
* @beta
24+
* An internal API for defining actions in the navbar.
25+
*/
26+
export interface NavbarAction {
27+
icon?: React.ComponentType
28+
location: 'topbar' | 'sidebar'
29+
name: string
30+
onAction: () => void
31+
selected: boolean
32+
title: string
33+
}
34+
2135
/**
2236
* @hidden
2337
* @beta */
2438
export interface NavbarProps {
2539
renderDefault: (props: NavbarProps) => ReactElement
40+
2641
/**
2742
* @internal
2843
* @beta */
29-
__internal_rightSectionNode?: ReactNode
44+
__internal_actions?: NavbarAction[]
3045
}
3146

3247
/**

‎packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx

+32-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {SearchProvider} from './search/contexts/search/SearchProvider'
3535
import {UserMenu} from './userMenu'
3636
import {WorkspaceMenuButton} from './workspace'
3737

38+
const EMPTY_ARRAY: [] = []
39+
3840
const RootLayer = styled(Layer)`
3941
min-height: auto;
4042
position: relative;
@@ -60,8 +62,11 @@ const NavGrid = styled(Grid)`
6062
* @hidden
6163
* @beta */
6264
export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
63-
// eslint-disable-next-line camelcase
64-
const {__internal_rightSectionNode = null} = props
65+
const {
66+
// eslint-disable-next-line camelcase
67+
__internal_actions: actions = EMPTY_ARRAY,
68+
} = props
69+
6570
const {name, tools} = useWorkspace()
6671
const routerState = useRouterState()
6772
const mediaIndex = useMediaIndex()
@@ -153,6 +158,25 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
153158
setDrawerOpen(true)
154159
}, [])
155160

161+
const actionNodes = useMemo(() => {
162+
if (!shouldRender.tools) return null
163+
164+
return actions
165+
?.filter((v) => v.location === 'topbar')
166+
?.map((action) => {
167+
return (
168+
<Button
169+
iconRight={action?.icon}
170+
key={action.name}
171+
mode="bleed"
172+
onClick={action?.onAction}
173+
selected={action.selected}
174+
text={action.title}
175+
/>
176+
)
177+
})
178+
}, [actions, shouldRender.tools])
179+
156180
return (
157181
<FreeTrialProvider>
158182
<RootLayer zOffset={100} data-search-open={searchFullscreenOpen}>
@@ -236,10 +260,13 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
236260
</BoundaryElementProvider>
237261
</SearchProvider>
238262
</LayerProvider>
263+
239264
{shouldRender.tools && <FreeTrial type="topbar" />}
240265
{shouldRender.configIssues && <ConfigIssuesButton />}
241266
{shouldRender.resources && <ResourcesButton />}
267+
242268
<PresenceMenu />
269+
243270
{/* Search button (mobile) */}
244271
{shouldRender.searchFullscreen && (
245272
<SearchButton
@@ -248,11 +275,9 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
248275
/>
249276
)}
250277

251-
{
252-
// eslint-disable-next-line camelcase
253-
__internal_rightSectionNode
254-
}
278+
{actionNodes}
255279
</Flex>
280+
256281
{shouldRender.tools && (
257282
<Box flex="none" marginLeft={1}>
258283
<UserMenu />
@@ -265,6 +290,7 @@ export function StudioNavbar(props: Omit<NavbarProps, 'renderDefault'>) {
265290

266291
{!shouldRender.tools && (
267292
<NavDrawer
293+
__internal_actions={actions}
268294
activeToolName={activeToolName}
269295
isOpen={drawerOpen}
270296
onClose={handleCloseDrawer}

‎packages/sanity/src/core/studio/components/navbar/navDrawer/NavDrawer.tsx

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {CloseIcon, LeaveIcon} from '@sanity/icons'
22
import {Box, Card, Flex, Layer, Stack, Text} from '@sanity/ui'
33
import {AnimatePresence, motion, type Transition, type Variants} from 'framer-motion'
4-
import {type KeyboardEvent, memo, useCallback} from 'react'
4+
import {type KeyboardEvent, memo, useCallback, useMemo} from 'react'
55
import TrapFocus from 'react-focus-lock'
66
import styled from 'styled-components'
77

88
import {Button} from '../../../../../ui-components'
99
import {UserAvatar} from '../../../../components'
10-
import {type Tool} from '../../../../config'
10+
import {type NavbarAction, type Tool} from '../../../../config'
1111
import {useTranslation} from '../../../../i18n'
1212
import {useColorSchemeSetValue} from '../../../colorScheme'
1313
import {useToolMenuComponent} from '../../../studio-components-hooks'
@@ -73,14 +73,15 @@ const InnerCardMotion = styled(motion(Card))`
7373
`
7474

7575
interface NavDrawerProps {
76+
__internal_actions?: NavbarAction[]
7677
activeToolName?: string
7778
isOpen: boolean
7879
onClose: () => void
7980
tools: Tool[]
8081
}
8182

8283
export const NavDrawer = memo(function NavDrawer(props: NavDrawerProps) {
83-
const {activeToolName, isOpen, onClose, tools} = props
84+
const {__internal_actions: actions, activeToolName, isOpen, onClose, tools} = props
8485

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

102+
const handleActionClick = useCallback(
103+
(action: () => void) => {
104+
action?.()
105+
onClose()
106+
},
107+
[onClose],
108+
)
109+
110+
const actionNodes = useMemo(() => {
111+
return actions
112+
?.filter((v) => v.location === 'sidebar')
113+
?.map((action) => {
114+
return (
115+
<Button
116+
icon={action?.icon}
117+
justify="flex-start"
118+
key={action.name}
119+
mode="bleed"
120+
// eslint-disable-next-line react/jsx-no-bind
121+
onClick={() => handleActionClick(action.onAction)}
122+
selected={action.selected}
123+
size="large"
124+
text={action.title}
125+
width="fill"
126+
/>
127+
)
128+
})
129+
}, [actions, handleActionClick])
130+
101131
return (
102132
<AnimatePresence>
103133
{isOpen && (
@@ -172,6 +202,12 @@ export const NavDrawer = memo(function NavDrawer(props: NavDrawerProps) {
172202
</Card>
173203

174204
<Flex direction="column">
205+
{actionNodes && (
206+
<Card flex="none" padding={2}>
207+
<Stack space={1}>{actionNodes}</Stack>
208+
</Card>
209+
)}
210+
175211
{setScheme && <AppearanceMenu setScheme={setScheme} />}
176212
<LocaleMenu />
177213
<ManageMenu />
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,53 @@
1+
import {PanelRightIcon, TaskIcon} from '@sanity/icons'
2+
import {useCallback, useMemo} from 'react'
13
import {type NavbarProps} from 'sanity'
24

3-
import {TasksNavbarButton, useTasksEnabled} from '../src'
5+
import {useTasks, useTasksEnabled} from '../src'
46

5-
export function TasksStudioNavbar(props: NavbarProps) {
6-
const {enabled} = useTasksEnabled()
7+
const EMPTY_ARRAY: [] = []
8+
9+
function TasksStudioNavbarInner(props: NavbarProps) {
10+
const {toggleOpen, isOpen} = useTasks()
11+
12+
const handleAction = useCallback(() => {
13+
toggleOpen()
14+
}, [toggleOpen])
15+
16+
const actions = useMemo((): NavbarProps['__internal_actions'] => {
17+
return [
18+
...(props?.__internal_actions || EMPTY_ARRAY),
19+
{
20+
icon: PanelRightIcon,
21+
location: 'topbar',
22+
name: 'tasks-topbar',
23+
onAction: handleAction,
24+
selected: isOpen,
25+
title: 'Tasks',
26+
},
27+
{
28+
icon: TaskIcon,
29+
location: 'sidebar',
30+
name: 'tasks-sidebar',
31+
onAction: handleAction,
32+
selected: isOpen,
33+
title: 'Tasks',
34+
},
35+
]
36+
}, [handleAction, isOpen, props?.__internal_actions])
737

8-
if (!enabled) return props.renderDefault(props)
938
return props.renderDefault({
1039
...props,
1140
// eslint-disable-next-line camelcase
12-
__internal_rightSectionNode: <TasksNavbarButton />,
41+
__internal_actions: actions,
1342
})
1443
}
44+
45+
export function TasksStudioNavbar(props: NavbarProps) {
46+
const {enabled} = useTasksEnabled()
47+
48+
if (!enabled) {
49+
return props.renderDefault(props)
50+
}
51+
52+
return <TasksStudioNavbarInner {...props} />
53+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from './form'
2-
export * from './navbar'
32
export * from './sidebar'

‎packages/sanity/src/tasks/src/tasks/components/navbar/TasksNavbarButton.tsx

-27
This file was deleted.

‎packages/sanity/src/tasks/src/tasks/components/navbar/index.ts

-1
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.