Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): document layout #5340

Merged
merged 9 commits into from
Dec 18, 2023
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