Skip to content

Commit

Permalink
feat(core): new insert menu for arrays with filtering, type preview s…
Browse files Browse the repository at this point in the history
…upport (#6853)

* chore(core): add test for array field with multiple options

* feat(core, types): support new, experimental array InsertMenuOptions

Instead of using a `MenuButton` the "array of objects" now renders the insert
menu using the `InsertMenu` from the new `@sanity/insert-menu` package. This
insert menu allows more configuration in the form of `InsertMenuOptions` which
are exposed on the `ArrayOptions` interface.

* chore(page-building): test new array insert menu options

* chore(page-building): add more placeholder block types

* refactor(core): abstract InsertMenuPopover

* refactor(core): replace InsertMenuPopover with useInsertMenuPopover

* deps: upgrade @sanity/insert-menu

* deps: upgrade @sanity/insert-menu

* refactor(core): simplify popoverReducer

* refactor(core): remove unneeded export

* refactor(core): adjust insert-menu i18n namespace

* chore(page-building): adjust insertMenu options according to adjusted API

* deps: upgrade @sanity/insert-menu
  • Loading branch information
christianhg authored Jun 17, 2024
1 parent 339128d commit 203f135
Show file tree
Hide file tree
Showing 12 changed files with 2,399 additions and 480 deletions.
14 changes: 12 additions & 2 deletions dev/page-building-studio/schemaTypes/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import {hero} from './hero'
import {logoCarousel} from './logoCarousel'
import {page} from './page'
import {byTheNumbers, page, pageOneBlockType, productCombos, threeVideos} from './page'
import {testimonial, testimonials} from './testimonials'

export const schemaTypes = [page, hero, logoCarousel, testimonial, testimonials]
export const schemaTypes = [
page,
pageOneBlockType,
hero,
logoCarousel,
testimonial,
testimonials,
threeVideos,
productCombos,
byTheNumbers,
]
91 changes: 90 additions & 1 deletion dev/page-building-studio/schemaTypes/page.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {FiBookOpen} from 'react-icons/fi'
import {MdVideoFile} from 'react-icons/md'
import {TbNumber123} from 'react-icons/tb'
import {defineField, defineType} from 'sanity'

export const page = defineType({
Expand All @@ -14,7 +17,93 @@ export const page = defineType({
name: 'blocks',
title: 'Blocks',
type: 'array',
of: [{type: 'hero'}, {type: 'logo-carousel'}, {type: 'testimonials'}],
of: [
{type: 'hero'},
{type: 'logo-carousel'},
{type: 'testimonials'},
{type: 'threeVideos'},
{type: 'byTheNumbers'},
{type: 'productCombos'},
],
options: {
insertMenu: {
filter: 'on',
views: [
{name: 'grid', previewImageUrl: (name) => `/static/preview-${name}.png`},
{name: 'list'},
],
groups: [
{
name: 'intro',
title: 'Intro',
of: ['hero'],
},
{
name: 'storytelling',
title: 'Storytelling',
},
{
name: 'upsell',
title: 'Upsell',
of: ['testimonials', 'hero'],
},
],
},
},
}),
],
})

export const pageOneBlockType = defineType({
type: 'document',
name: 'pageOneBlockType',
title: 'Page (One Block Type)',
groups: [{name: 'meta'}, {name: 'blocks'}],
fields: [
defineField({
type: 'string',
name: 'title',
title: 'Title',
group: 'meta',
}),
defineField({
name: 'blocks',
title: 'Blocks',
type: 'array',
group: 'blocks',
of: [{type: 'hero'}],
options: {
insertMenu: {
views: [
{name: 'grid', previewImageUrl: (name) => `/static/preview-${name}.png`},
{name: 'list'},
],
},
},
}),
],
})

export const byTheNumbers = defineType({
type: 'object',
icon: TbNumber123,
name: 'byTheNumbers',
title: 'By the Numbers',
fields: [defineField({type: 'string', name: 'foo'})],
})

export const threeVideos = defineType({
type: 'object',
icon: MdVideoFile,
name: 'threeVideos',
title: 'Three Videos',
fields: [defineField({type: 'string', name: 'foo'})],
})

export const productCombos = defineType({
type: 'object',
icon: FiBookOpen,
name: 'productCombos',
title: 'Product Combos',
fields: [defineField({type: 'string', name: 'foo'})],
})
Binary file added dev/page-building-studio/static/preview-hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/@sanity/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"devDependencies": {
"@jest/globals": "^29.7.0",
"@repo/package.config": "workspace:*",
"@sanity/insert-menu": "1.0.5",
"rimraf": "^3.0.2"
}
}
4 changes: 4 additions & 0 deletions packages/@sanity/types/src/schema/definition/type/array.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {type InsertMenuOptions} from '@sanity/insert-menu'

import {type FieldReference} from '../../../validation'
import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder'
import {type InitialValueProperty, type SchemaValidationValue} from '../../types'
Expand All @@ -18,6 +20,8 @@ export interface ArrayOptions<V = unknown> extends SearchConfiguration {
direction?: 'horizontal' | 'vertical'
sortable?: boolean
modal?: {type?: 'dialog' | 'popover'; width?: number | 'auto'}
/** @alpha This API may change */
insertMenu?: InsertMenuOptions
}

/** @public */
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"@sanity/icons": "^3.0.0",
"@sanity/image-url": "^1.0.2",
"@sanity/import": "^3.37.3",
"@sanity/insert-menu": "1.0.5",
"@sanity/logos": "^2.1.4",
"@sanity/migrate": "3.46.1",
"@sanity/mutator": "3.46.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,25 @@
/* eslint-disable react/jsx-no-bind */
import {AddIcon} from '@sanity/icons'
import {type ArraySchemaType} from '@sanity/types'
import {Grid, Menu} from '@sanity/ui'
import {useCallback, useId} from 'react'
import {Grid} from '@sanity/ui'
import {useCallback, useState} from 'react'

import {
Button,
MenuButton,
type MenuButtonProps,
MenuItem,
Tooltip,
} from '../../../../../ui-components'
import {Button, Tooltip} from '../../../../../ui-components'
import {useTranslation} from '../../../../i18n'
import {type ArrayInputFunctionsProps, type ObjectItem} from '../../../types'
import {getSchemaTypeIcon} from './getSchemaTypeIcon'

const POPOVER_PROPS: MenuButtonProps['popover'] = {
constrainSize: true,
portal: true,
fallbackPlacements: ['top', 'bottom'],
}
import {useInsertMenuPopover} from './InsertMenuPopover'

/**
* @hidden
* @beta */
export function ArrayOfObjectsFunctions<
Item extends ObjectItem,
SchemaType extends ArraySchemaType,
>(props: ArrayInputFunctionsProps<Item, SchemaType>) {
TSchemaType extends ArraySchemaType,
>(props: ArrayInputFunctionsProps<Item, TSchemaType>) {
const {schemaType, readOnly, children, onValueCreate, onItemAppend} = props
const menuButtonId = useId()
const {t} = useTranslation()
const [gridElement, setGridElement] = useState<HTMLDivElement | null>(null)
const [popoverToggleElement, setPopoverToggleElement] = useState<HTMLButtonElement | null>(null)

const insertItem = useCallback(
(itemType: any) => {
Expand All @@ -51,64 +41,64 @@ export function ArrayOfObjectsFunctions<
? 'inputs.array.action.add-item-select-type'
: 'inputs.array.action.add-item'

const insertButtonProps: React.ComponentProps<typeof Button> = {
icon: AddIcon,
mode: 'ghost',
size: 'large',
text: t(addItemI18nKey),
}

const insertMenu = useInsertMenuPopover({
insertMenuProps: {
...props.schemaType.options?.insertMenu,
schemaTypes: props.schemaType.of,
onSelect: insertItem,
},
popoverProps: {
placement: 'bottom',
fallbackPlacements: ['top'],
matchReferenceWidth: props.schemaType.options?.insertMenu?.filter === 'on',
referenceBoundary: gridElement,
referenceElement: popoverToggleElement,
},
})

if (readOnly) {
return (
<Tooltip portal content={t('inputs.array.read-only-label')}>
<Grid>
<Button
icon={AddIcon}
mode="ghost"
disabled
size="large"
data-testid="add-read-object-button"
text={t(addItemI18nKey)}
/>
<Button {...insertButtonProps} data-testid="add-read-object-button" disabled />
</Grid>
</Tooltip>
)
}

return (
<Grid gap={1} style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}>
<Grid
ref={setGridElement}
gap={1}
style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}
>
{schemaType.of.length === 1 ? (
<Button
icon={AddIcon}
mode="ghost"
{...insertButtonProps}
onClick={handleAddBtnClick}
size="large"
data-testid="add-single-object-button"
text={t(addItemI18nKey)}
/>
) : (
<MenuButton
button={
<Button
icon={AddIcon}
mode="ghost"
size="large"
data-testid="add-multiple-object-button"
text={t(addItemI18nKey)}
/>
}
id={menuButtonId || ''}
menu={
<Menu>
{schemaType.of.map((memberDef, i) => {
return (
<MenuItem
key={i}
text={memberDef.title || memberDef.type?.name}
onClick={() => insertItem(memberDef)}
icon={getSchemaTypeIcon(memberDef)}
/>
)
})}
</Menu>
}
popover={POPOVER_PROPS}
/>
<>
<Button
{...insertButtonProps}
data-testid="add-multiple-object-button"
selected={insertMenu.state.open}
onClick={() => {
insertMenu.send({type: 'toggle'})
}}
ref={setPopoverToggleElement}
/>
{insertMenu.popover}
</>
)}

{children}
</Grid>
)
Expand Down
Loading

0 comments on commit 203f135

Please sign in to comment.