Skip to content

Commit

Permalink
feat(core): document layout (#5340)
Browse files Browse the repository at this point in the history
* test(core): components API

* refactor(core): add `useMiddlewareComponents` JSDoc

* feat(core): introduce `document.components.layout`

* feat(desk): use document layout from Components API

* feat(comments): use `document.components.layout` and rename components

* test(core): fix e2e tests

* fix(core): prefix document layout component with `unstable`

* fix(comments): remove redundant provider

* refactor(comments): simplify comments enabled check
  • Loading branch information
hermanwikner authored Dec 18, 2023
1 parent 0c6a32e commit adaae0e
Show file tree
Hide file tree
Showing 45 changed files with 841 additions and 463 deletions.
16 changes: 16 additions & 0 deletions dev/studio-e2e-testing/components-api/DocumentLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Flex} from '@sanity/ui'
import {DocumentLayoutProps} from 'sanity'

export function DocumentLayout(props: DocumentLayoutProps & {testId: string}) {
const {testId} = props

if (props.documentType !== 'formComponentsApi') {
return props.renderDefault(props)
}

return (
<Flex data-testid={testId} direction="column" flex={1} height="fill" overflow="hidden">
{props.renderDefault(props)}
</Flex>
)
}
8 changes: 8 additions & 0 deletions dev/studio-e2e-testing/components-api/FormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Stack} from '@sanity/ui'
import {FieldProps} from 'sanity'

export function FormField(props: FieldProps & {testId: string}) {
const {testId} = props

return <Stack data-testid={testId}>{props.renderDefault(props)}</Stack>
}
10 changes: 10 additions & 0 deletions dev/studio-e2e-testing/components-api/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {Stack} from '@sanity/ui'
import {InputProps} from 'sanity'

export function FormInput(props: InputProps & {testId: string}) {
const {testId} = props

if (props.id === 'root') return props.renderDefault(props)

return <Stack data-testid={testId}>{props.renderDefault(props)}</Stack>
}
12 changes: 12 additions & 0 deletions dev/studio-e2e-testing/components-api/StudioLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Flex} from '@sanity/ui'
import {LayoutProps} from 'sanity'

export function StudioLayout(props: LayoutProps & {testId: string}) {
const {testId} = props

return (
<Flex data-testid={testId} direction="column" height="fill" overflow="hidden">
{props.renderDefault(props)}
</Flex>
)
}
9 changes: 9 additions & 0 deletions dev/studio-e2e-testing/components-api/StudioLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Box} from '@sanity/ui'
import React from 'react'
import {LogoProps} from 'sanity'

export function StudioLogo(props: LogoProps & {testId: string}) {
const {testId} = props

return <Box data-testid={testId}>{props.renderDefault(props)}</Box>
}
8 changes: 8 additions & 0 deletions dev/studio-e2e-testing/components-api/StudioNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Stack} from '@sanity/ui'
import {NavbarProps} from 'sanity'

export function StudioNavbar(props: NavbarProps & {testId: string}) {
const {testId} = props

return <Stack data-testid={testId}>{props.renderDefault(props)}</Stack>
}
8 changes: 8 additions & 0 deletions dev/studio-e2e-testing/components-api/StudioToolMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Stack} from '@sanity/ui'
import {ToolMenuProps} from 'sanity'

export function StudioToolMenu(props: ToolMenuProps & {testId: string}) {
const {testId} = props

return <Stack data-testid={testId}>{props.renderDefault(props)}</Stack>
}
68 changes: 68 additions & 0 deletions dev/studio-e2e-testing/components-api/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {definePlugin} from 'sanity'
import {StudioLayout} from './StudioLayout'
import {StudioNavbar} from './StudioNavbar'
import {StudioLogo} from './StudioLogo'
import {StudioToolMenu} from './StudioToolMenu'
import {FormInput} from './FormInput'
import {FormField} from './FormField'
import {DocumentLayout} from './DocumentLayout'

const childComponents = definePlugin({
name: 'child-components',

document: {
components: {
unstable_layout: (props) => (
<DocumentLayout {...props} testId="child-parent-config-document-layout" />
),
},
},

form: {
components: {
input: (props) => <FormInput {...props} testId="child-parent-config-form-input" />,
field: (props) => <FormField {...props} testId="child-parent-config-form-field" />,
},
},

studio: {
components: {
layout: (props) => <StudioLayout {...props} testId="child-parent-config-studio-layout" />,
logo: (props) => <StudioLogo {...props} testId="child-parent-config-studio-logo" />,
navbar: (props) => <StudioNavbar {...props} testId="child-parent-config-studio-navbar" />,
toolMenu: (props) => (
<StudioToolMenu {...props} testId="child-parent-config-studio-tool-menu" />
),
},
},
})

export const customComponents = definePlugin({
name: 'custom-components',

document: {
components: {
unstable_layout: (props) => (
<DocumentLayout {...props} testId="parent-config-document-layout" />
),
},
},

form: {
components: {
input: (props) => <FormInput {...props} testId="parent-config-form-input" />,
field: (props) => <FormField {...props} testId="parent-config-form-field" />,
},
},

studio: {
components: {
layout: (props) => <StudioLayout {...props} testId="parent-config-studio-layout" />,
logo: (props) => <StudioLogo {...props} testId="parent-config-studio-logo" />,
navbar: (props) => <StudioNavbar {...props} testId="parent-config-studio-navbar" />,
toolMenu: (props) => <StudioToolMenu {...props} testId="parent-config-studio-tool-menu" />,
},
},

plugins: [childComponents()],
})
10 changes: 0 additions & 10 deletions dev/studio-e2e-testing/components/Branding.tsx

This file was deleted.

9 changes: 3 additions & 6 deletions dev/studio-e2e-testing/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {copyAction} from 'sanity-test-studio/fieldActions/copyAction'
import {assistFieldActionGroup} from 'sanity-test-studio/fieldActions/assistFieldActionGroup'
import {customInspector} from 'sanity-test-studio/inspectors/custom'
import {pasteAction} from 'sanity-test-studio/fieldActions/pasteAction'
import {Branding} from './components/Branding'
import {schemaTypes} from './schemas'
import {customComponents} from './components-api'

const sharedSettings = definePlugin({
name: 'sharedSettings',
Expand All @@ -28,11 +28,7 @@ const sharedSettings = definePlugin({
assetSources: [imageAssetSource],
},
},
studio: {
components: {
logo: Branding,
},
},

document: {
actions: documentActions,
inspectors: (prev, ctx) => {
Expand All @@ -52,6 +48,7 @@ const sharedSettings = definePlugin({
newDocumentOptions,
},
plugins: [
customComponents(),
deskTool({
icon: BookIcon,
name: 'content',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,27 @@ function _createMiddlewareComponent<T extends {}>(
}
}

/** @internal */
/**
* @internal
* This hook returns a component based on the Components API middleware chain.
*
* - The `pick` function is used to select a component from the provided plugin options in the configuration.
* - The `defaultComponent` is the default component that gets rendered with `renderDefault`.
* The `renderDefault` function is added to the props of the middleware components so that they can render the default
* component and continue the middleware chain.
*
* @example
* Example usage of:
*
* ```ts
* const StudioLayout = useMiddlewareComponents({
* pick: (plugin) => plugin.studio?.components?.layout,
* defaultComponent: StudioLayout,
* })
*
* return <StudioLayout />
*```
*/
export function useMiddlewareComponents<T extends {}>(props: {
pick: (plugin: PluginOptions) => ComponentType<T>
defaultComponent: ComponentType<T>
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/src/core/config/form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types'
16 changes: 16 additions & 0 deletions packages/sanity/src/core/config/form/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {ComponentType} from 'react'
import {PreviewProps} from '../../components'
import {InputProps, FieldProps, ItemProps, BlockProps, BlockAnnotationProps} from '../../form'

/**
* @hidden
* @beta */
export interface FormComponents {
annotation?: ComponentType<BlockAnnotationProps>
block?: ComponentType<BlockProps>
field?: ComponentType<FieldProps>
inlineBlock?: ComponentType<BlockProps>
input?: ComponentType<InputProps>
item?: ComponentType<ItemProps>
preview?: ComponentType<PreviewProps>
}
1 change: 1 addition & 0 deletions packages/sanity/src/core/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useConfigContextFromSource'
export * from './components'
export * from './studio'
export * from './flattenConfig'
export * from './form'
71 changes: 46 additions & 25 deletions packages/sanity/src/core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,9 @@ import type {
import type {ComponentType, ReactNode} from 'react'
import type {Observable} from 'rxjs'
import type {i18n} from 'i18next'
import type {
BlockAnnotationProps,
BlockProps,
FieldProps,
FormBuilderCustomMarkersComponent,
FormBuilderMarkersComponent,
InputProps,
ItemProps,
} from '../form'
import type {FormBuilderCustomMarkersComponent, FormBuilderMarkersComponent} from '../form'
import type {LocalePluginOptions, LocaleSource} from '../i18n/types'
import type {InitialValueTemplateItem, Template, TemplateItem} from '../templates'
import type {PreviewProps} from '../components/previews'
import type {AuthStore} from '../store'
import type {StudioTheme} from '../theme'
import type {SearchFilterDefinition} from '../studio/components/navbar/search/definitions/filters'
Expand All @@ -38,6 +29,7 @@ import type {
DocumentFieldActionsResolverContext,
DocumentInspector,
} from './document'
import {FormComponents} from './form'
import type {Router, RouterState} from 'sanity/router'

/**
Expand All @@ -61,19 +53,14 @@ export interface SanityFormConfig {
CustomMarkers?: FormBuilderCustomMarkersComponent
Markers?: FormBuilderMarkersComponent
}

/**
* Components for the form.
* @hidden
* @beta
*/
components?: {
input?: ComponentType<InputProps>
field?: ComponentType<FieldProps>
item?: ComponentType<ItemProps>
preview?: ComponentType<PreviewProps>
block?: ComponentType<BlockProps>
inlineBlock?: ComponentType<BlockProps>
annotation?: ComponentType<BlockAnnotationProps>
}
components?: FormComponents

file?: {
/**
* @hidden
Expand Down Expand Up @@ -273,6 +260,13 @@ export type NewDocumentCreationContext =
export interface DocumentPluginOptions {
badges?: DocumentBadgeComponent[] | DocumentBadgesResolver
actions?: DocumentActionComponent[] | DocumentActionsResolver

/**
* Components for the document.
* @internal
*/
components?: DocumentComponents

/** @internal */
unstable_fieldActions?: DocumentFieldAction[] | DocumentFieldActionsResolver
/** @hidden @beta */
Expand Down Expand Up @@ -360,9 +354,16 @@ export interface PluginOptions {
document?: DocumentPluginOptions
tools?: Tool[] | ComposableOption<Tool[], ConfigContext>
form?: SanityFormConfig

studio?: {
/**
* Components for the studio.
* @hidden
* @beta
*/
components?: StudioComponentsPluginOptions
}

/** @beta @hidden */
i18n?: LocalePluginOptions
}
Expand Down Expand Up @@ -496,6 +497,24 @@ export type PartialContext<TContext extends ConfigContext> = Pick<
Exclude<keyof TContext, keyof ConfigContext>
>

/** @internal*/
export interface DocumentLayoutProps {
/**
* The ID of the document. This is a read-only property and changing it will have no effect.
*/
documentId: string
/**
* The type of the document. This is a read-only property and changing it will have no effect.
*/
documentType: string
renderDefault: (props: DocumentLayoutProps) => React.ReactElement
}

interface DocumentComponents {
/** @internal */
unstable_layout?: ComponentType<DocumentLayoutProps>
}

/** @public */
export interface SourceClientOptions {
/**
Expand Down Expand Up @@ -559,6 +578,12 @@ export interface Source {
*/
badges: (props: PartialContext<DocumentActionsContext>) => DocumentBadgeComponent[]

/**
* Components for the document.
* @internal
*/
components?: DocumentComponents

/** @internal */
unstable_fieldActions: (
props: PartialContext<DocumentFieldActionsResolverContext>,
Expand Down Expand Up @@ -634,12 +659,7 @@ export interface Source {
* @hidden
* @beta
*/
components?: {
input?: ComponentType<Omit<InputProps, 'renderDefault'>>
field?: ComponentType<Omit<FieldProps, 'renderDefault'>>
item?: ComponentType<Omit<ItemProps, 'renderDefault'>>
preview?: ComponentType<Omit<PreviewProps, 'renderDefault'>>
}
components?: FormComponents

/**
* these have not been migrated over and are not merged by the form builder
Expand All @@ -659,6 +679,7 @@ export interface Source {
*/
studio?: {
/**
* Components for the studio.
* @hidden
* @beta
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function StudioLogo(props: LogoProps) {
const {title} = props

return (
<Box padding={3}>
<Box padding={3} data-testid="studio-logo">
<Text weight="bold">{title}</Text>
</Box>
)
Expand Down
Loading

2 comments on commit adaae0e

@vercel
Copy link

@vercel vercel bot commented on adaae0e Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

performance-studio – ./

performance-studio-git-next.sanity.build
performance-studio.sanity.build

@vercel
Copy link

@vercel vercel bot commented on adaae0e Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

test-studio – ./

test-studio.sanity.build
test-studio-git-next.sanity.build

Please sign in to comment.