Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cf7f911
commit 705568a
Showing
11 changed files
with
330 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import { ComponentPropsWithoutRef, ElementType, ReactNode, useEffect, useState } from 'react' | ||
import { SlotControl, SlotControlData } from '../../../controls' | ||
|
||
import { BoxModel, getBox } from '../../../state/modules/box-models' | ||
import deepEqual from '../../../utils/deepEqual' | ||
import { Element } from '../../../runtimes/react' | ||
import { getIndexes } from '../../../components/utils/columns' | ||
import { responsiveStyle } from '../../../components/utils/responsive-style' | ||
import { useStyle } from '../use-style' | ||
import { cx } from '@emotion/css' | ||
|
||
export type SlotControlValue = ReactNode | ||
|
||
export function useSlot(data: SlotControlData, control: SlotControl | null) { | ||
// TODO(miguel): While the UI shouldn't allow the state, we should probably check that at least | ||
// one element is visible. | ||
if (data == null || data.elements.length === 0) { | ||
return <Slot.Placeholder control={control} /> | ||
} | ||
|
||
return ( | ||
<Slot control={control}> | ||
{data.elements.map((element, i) => ( | ||
<Slot.Item key={element.key} control={control} grid={data.columns} index={i}> | ||
<Element element={element} /> | ||
</Slot.Item> | ||
))} | ||
</Slot> | ||
) | ||
} | ||
|
||
type SlotProps<T extends ElementType> = { | ||
as?: T | ||
control: SlotControl | null | ||
children?: ReactNode | ||
className?: string | ||
} | ||
|
||
export function Slot<T extends ElementType = 'div'>({ | ||
as, | ||
control, | ||
children, | ||
className, | ||
...restOfProps | ||
}: SlotProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof SlotProps<T>>) { | ||
const As = as ?? 'div' | ||
const [element, setElement] = useState<Element | null>(null) | ||
const baseClassName = useStyle({ | ||
display: 'flex', | ||
flexWrap: 'wrap', | ||
width: '100%', | ||
}) | ||
|
||
useEffect(() => { | ||
if (element == null || control == null) return | ||
|
||
return pollBoxModel({ | ||
element, | ||
onBoxModelChange: boxModel => control.changeContainerBoxModel(boxModel), | ||
}) | ||
}, [element, control]) | ||
|
||
return ( | ||
<As {...restOfProps} ref={setElement} className={cx(baseClassName, className)}> | ||
{children} | ||
</As> | ||
) | ||
} | ||
|
||
Slot.Placeholder = SlotPlaceholder | ||
|
||
Slot.Item = SlotItem | ||
|
||
type SlotItemProps<T extends ElementType> = { | ||
as?: T | ||
control: SlotControl | null | ||
grid: SlotControlData['columns'] | ||
index: number | ||
children?: ReactNode | ||
className?: string | ||
} | ||
|
||
function SlotItem<T extends ElementType = 'div'>({ | ||
as, | ||
control, | ||
grid, | ||
index, | ||
children, | ||
className, | ||
...restOfProps | ||
}: SlotItemProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof SlotItemProps<T>>): JSX.Element { | ||
const As = as ?? 'div' | ||
const [element, setElement] = useState<Element | null>(null) | ||
const baseClassName = useStyle({ | ||
display: 'flex', | ||
...responsiveStyle([grid], ([{ count = 12, spans = [[12]] } = {}]) => { | ||
const [rowIndex, columnIndex] = getIndexes(spans, index) | ||
const span = spans[rowIndex][columnIndex] | ||
const flexBasis = `calc(100% * ${(span / count).toFixed(5)})` | ||
|
||
return span === 0 ? { display: 'none' } : { flexBasis, minWidth: flexBasis } | ||
}), | ||
}) | ||
|
||
useEffect(() => { | ||
if (element == null || control == null) return | ||
|
||
return pollBoxModel({ | ||
element, | ||
onBoxModelChange: boxModel => control.changeItemBoxModel(index, boxModel), | ||
}) | ||
}, [element, control, index]) | ||
|
||
return ( | ||
<As {...restOfProps} ref={setElement} className={cx(baseClassName, className)}> | ||
{children} | ||
</As> | ||
) | ||
} | ||
|
||
type SlotPlaceholderProps = { | ||
control: SlotControl | null | ||
} | ||
|
||
function SlotPlaceholder({ control }: SlotPlaceholderProps): JSX.Element { | ||
const [element, setElement] = useState<Element | null>(null) | ||
|
||
useEffect(() => { | ||
if (element == null || control == null) return | ||
|
||
return pollBoxModel({ | ||
element, | ||
onBoxModelChange: boxModel => control.changeContainerBoxModel(boxModel), | ||
}) | ||
}, [element, control]) | ||
|
||
return ( | ||
<div | ||
ref={setElement} | ||
className={useStyle({ | ||
width: '100%', | ||
background: 'rgba(161, 168, 194, 0.18)', | ||
height: '80px', | ||
})} | ||
> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="100%" | ||
height="100%" | ||
className={useStyle({ overflow: 'visible', padding: 8 })} | ||
> | ||
<rect | ||
x={0} | ||
y={0} | ||
width="100%" | ||
height="100%" | ||
strokeWidth={2} | ||
strokeDasharray="4 2" | ||
fill="none" | ||
stroke="rgba(161, 168, 194, 0.40)" | ||
rx="4" | ||
ry="4" | ||
/> | ||
</svg> | ||
</div> | ||
) | ||
} | ||
|
||
function pollBoxModel({ | ||
element, | ||
onBoxModelChange, | ||
}: { | ||
element: Element | ||
onBoxModelChange(boxModel: BoxModel): void | ||
}): () => void { | ||
let currentBoxModel: BoxModel | null = null | ||
|
||
const handleAnimationFrameRequest = () => { | ||
const measuredBoxModel = getBox(element) | ||
|
||
if (!deepEqual(currentBoxModel, measuredBoxModel)) { | ||
currentBoxModel = measuredBoxModel | ||
|
||
onBoxModelChange(currentBoxModel) | ||
} | ||
|
||
animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest) | ||
} | ||
|
||
let animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest) | ||
|
||
return () => { | ||
cancelAnimationFrame(animationFrameHandle) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.