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

Slot control #168

Merged
merged 3 commits into from Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/light-hounds-type.md
@@ -0,0 +1,34 @@
---
'@makeswift/runtime': patch
---

Add Slot control.

This is one of the most important controls in Makeswift as it allows you to compose React components
together. It's powered by the same technology used in our most important component, the Box. This
means you can now build your own Box-like components with the intuitive Makeswift layout experience.

Here's how simple it is to build a custom Box that can have elements dropped into it:

```jsx
function MyBox({ children, className }) {
return <div className={className}>{children}</div>
}

ReactRuntime.registerComponent(MyBox, {
type: 'my-box'
label: 'My Box',
props: {
children: Slot(),
className: Style()
}
})
```

There's a lot more you can do with the Slot. Here's some ideas:

- Custom animations for elements passed via Slot
- Passing data between components using React context and Slot (i.e., a component with Slot provides
a context value and any component dropped inside it can read that context)

Read more about the Slot control in our [docs](https://www.makeswift.com/docs/controls/slot).
5 changes: 5 additions & 0 deletions .changeset/poor-rivers-run.md
@@ -0,0 +1,5 @@
---
"@makeswift/next-plugin": patch
---

Set compiler.styledComponents to true in Makeswift Next.js plugin
26 changes: 18 additions & 8 deletions packages/makeswift-next-plugin/index.js
Expand Up @@ -3,11 +3,21 @@ const withTmInitializer = require('next-transpile-modules')
const NEXT_IMAGE_DOMAINS = ['s.mkswft.com']
const NEXT_TRANSPILE_MODULES_MODULES = ['@makeswift/runtime']

module.exports = ({ resolveSymlinks } = {}) => (nextConfig = {}) =>
withTmInitializer(NEXT_TRANSPILE_MODULES_MODULES, { resolveSymlinks })({
...nextConfig,
images: {
...nextConfig.images,
domains: [...(nextConfig.images?.domains ?? []), ...NEXT_IMAGE_DOMAINS],
},
})
module.exports =
({ resolveSymlinks } = {}) =>
(nextConfig = {}) => {
/** @type {import('next').NextConfig} */
const enhancedConfig = {
...nextConfig,
compiler: {
...nextConfig.compiler,
styledComponents: true,
},
images: {
...nextConfig.images,
domains: [...(nextConfig.images?.domains ?? []), ...NEXT_IMAGE_DOMAINS],
},
}

return withTmInitializer(NEXT_TRANSPILE_MODULES_MODULES, { resolveSymlinks })(enhancedConfig)
}
2 changes: 1 addition & 1 deletion packages/runtime/src/box-model.ts
@@ -1,2 +1,2 @@
export { createBox, getBox, parse } from './state/modules/box-models'
export type { BoxModelHandle } from './state/modules/box-models'
export type { BoxModelHandle, BoxModel } from './state/modules/box-models'
1 change: 1 addition & 0 deletions packages/runtime/src/controls/index.ts
Expand Up @@ -8,6 +8,7 @@ export * from './list'
export * from './number'
export * from './select'
export * from './shape'
export * from './slot'
export * from './style'
export * from './text-area'
export * from './text-input'
52 changes: 52 additions & 0 deletions packages/runtime/src/controls/slot.ts
@@ -0,0 +1,52 @@
import { PropController } from '../prop-controllers/instances'
import { BoxModel } from '../state/modules/box-models'
import { Element } from '../state/react-page'
import { ResponsiveValue } from './types'

type SlotControlColumnData = { count: number; spans: number[][] }

export type SlotControlData = {
elements: Element[]
columns: ResponsiveValue<SlotControlColumnData>
}

export const SlotControlType = 'makeswift::controls::slot'

export type SlotControlDefinition = {
type: typeof SlotControlType
}

export function Slot(): SlotControlDefinition {
return { type: SlotControlType }
}

export const SlotControlMessageType = {
CONTAINER_BOX_MODEL_CHANGE: 'makeswift::controls::slot::message::container-box-model-change',
ITEM_BOX_MODEL_CHANGE: 'makeswift::controls::slot::message::item-box-model-change',
} as const

type SlotControlContainerBoxModelChangeMessage = {
type: typeof SlotControlMessageType.CONTAINER_BOX_MODEL_CHANGE
payload: { boxModel: BoxModel }
}

type SlotControlItemBoxModelChangeMessage = {
type: typeof SlotControlMessageType.ITEM_BOX_MODEL_CHANGE
payload: { index: number; boxModel: BoxModel }
}

export type SlotControlMessage =
| SlotControlContainerBoxModelChangeMessage
| SlotControlItemBoxModelChangeMessage

export class SlotControl extends PropController<SlotControlMessage> {
recv(): void {}

changeContainerBoxModel(boxModel: BoxModel): void {
this.send({ type: SlotControlMessageType.CONTAINER_BOX_MODEL_CHANGE, payload: { boxModel } })
}

changeItemBoxModel(index: number, boxModel: BoxModel): void {
this.send({ type: SlotControlMessageType.ITEM_BOX_MODEL_CHANGE, payload: { index, boxModel } })
}
}
2 changes: 2 additions & 0 deletions packages/runtime/src/prop-controllers/descriptors.ts
Expand Up @@ -15,6 +15,7 @@ import {
ListControlDefinition,
SelectControlDefinition,
ShapeControlDefinition,
SlotControlDefinition,
TextAreaControlDefinition,
TextInputControlDefinition,
} from '../controls'
Expand Down Expand Up @@ -964,6 +965,7 @@ export type Descriptor<T extends Data = Data> =
| ShapeControlDefinition
| ListControlDefinition
| LinkControlDefinition
| SlotControlDefinition

export type PanelDescriptorType =
| typeof Types.Backgrounds
Expand Down
12 changes: 10 additions & 2 deletions packages/runtime/src/prop-controllers/instances.ts
Expand Up @@ -4,6 +4,7 @@ import { OnChangeParam } from 'slate-react'
import { Descriptor, RichTextDescriptor, TableFormFieldsDescriptor, Types } from './descriptors'
import { BuilderEditMode } from '../utils/constants'
import { BoxModel } from '../state/modules/box-models'
import { SlotControl, SlotControlMessage, SlotControlType } from '../controls'

export const RichTextPropControllerMessageType = {
CHANGE_BUILDER_EDIT_MODE: 'CHANGE_BUILDER_EDIT_MODE',
Expand Down Expand Up @@ -47,9 +48,12 @@ export type RichTextPropControllerMessage =
| UndoRichTextPropControllerMessage
| RedoRichTextPropControllerMessage

export type PropControllerMessage = RichTextPropControllerMessage | TableFormFieldsMessage
export type PropControllerMessage =
| RichTextPropControllerMessage
| TableFormFieldsMessage
| SlotControlMessage

type Send<T = PropControllerMessage> = (message: T) => void
export type Send<T = PropControllerMessage> = (message: T) => void

export abstract class PropController<T = PropControllerMessage> {
protected send: Send<T>
Expand Down Expand Up @@ -168,6 +172,7 @@ type AnyPropController =
| DefaultPropController
| RichTextPropController
| TableFormFieldsPropController
| SlotControl

export function createPropController(
descriptor: RichTextDescriptor,
Expand All @@ -189,6 +194,9 @@ export function createPropController<T extends PropControllerMessage>(
case Types.TableFormFields:
return new TableFormFieldsPropController(send as Send<TableFormFieldsMessage>)

case SlotControlType:
return new SlotControl(send as Send<SlotControlMessage>)

default:
return new DefaultPropController(send as Send)
}
Expand Down
12 changes: 11 additions & 1 deletion packages/runtime/src/prop-controllers/introspection.ts
Expand Up @@ -16,7 +16,14 @@ import {
Types,
} from './descriptors'
import { Data, Element } from '../state/react-page'
import { ColorControlData, ColorControlType, ImageControlData, ImageControlType } from '../controls'
import {
ColorControlData,
ColorControlType,
ImageControlData,
ImageControlType,
SlotControlData,
SlotControlType,
} from '../controls'

export function getElementChildren<T extends Data>(
descriptor: Descriptor<T>,
Expand All @@ -28,6 +35,9 @@ export function getElementChildren<T extends Data>(
case Types.Grid:
return (prop as GridValue).elements

case SlotControlType:
return (prop as SlotControlData).elements

default:
return []
}
Expand Down
26 changes: 25 additions & 1 deletion packages/runtime/src/runtimes/react/controls.tsx
@@ -1,6 +1,6 @@
import { useMemo, useRef } from 'react'

import { useStore } from '.'
import { useDocumentKey, useSelector, useStore } from '.'
import * as ReactPage from '../../state/react-page'
import { Props } from '../../prop-controllers'
import {
Expand All @@ -26,13 +26,16 @@ import {
NumberControlType,
SelectControlType,
ShapeControlType,
SlotControl,
SlotControlType,
StyleControlType,
TextAreaControlType,
TextInputControlType,
} from '../../controls'
import { useFormattedStyle } from './controls/style'
import { ControlValue } from './controls/control'
import { RenderHook } from './components'
import { useSlot } from './controls/slot'

export type ResponsiveColor = ResponsiveValue<ColorValue>

Expand Down Expand Up @@ -84,6 +87,13 @@ export function PropsValue({ element, children }: PropsValueProps): JSX.Element
ReactPage.getComponentPropControllerDescriptors(store.getState(), element.type) ?? {},
)
const props = element.props as Record<string, any>
const documentKey = useDocumentKey()

const propControllers = useSelector(state => {
if (documentKey == null) return null

return ReactPage.getPropControllers(state, documentKey, element.key)
})

return Object.entries(propControllerDescriptorsRef.current).reduceRight(
(renderFn, [propName, descriptor]) =>
Expand Down Expand Up @@ -117,6 +127,20 @@ export function PropsValue({ element, children }: PropsValueProps): JSX.Element
</RenderHook>
)

case SlotControlType: {
const control = (propControllers?.[propName] ?? null) as SlotControl | null

return (
<RenderHook
key={descriptor.type}
hook={useSlot}
parameters={[props[propName], control]}
>
{value => renderFn({ ...propsValue, [propName]: value })}
</RenderHook>
)
}

case Props.Types.Width:
return (
<RenderHook
Expand Down