Composing components
A frame or a button background wraps other markup. Declare a children prop
and place {children} in your markup:
export const SunkenBox = ({ children }: { children?: Children }): Component => ( <layer w="fill" h="fill"> <rect w="fill" h="fill" color={Colors.WELL} fill transparency={80} /> <rect w="fill" h="fill" color={Colors.BLACK} /> <rect x="center" y="center" w={{ fill: 2 }} h={{ fill: 2 }} color={0x474745} /> <layer w="fill" h="fill">{children}</layer> </layer>);Anything written between the tags is built into that inner layer:
<SunkenBox> … <layer ref={list} x={2} y={2} w={{ fill: 22 }} h={{ fill: 4 }} scrollHeight={QUESTS.length * ROW_H}>{children} appears once, inside an element.
Forwarding children
Section titled “Forwarding children”A wrapper can hand its children to another component instead of having a slot of its own:
export const StoneButton = ({ children }: { children?: Children }): Component => ( <NineSlice topLeft={StoneSprites.TOP_LEFT} topRight={StoneSprites.TOP_RIGHT} bottomLeft={StoneSprites.BOTTOM_LEFT} bottomRight={StoneSprites.BOTTOM_RIGHT} left={StoneSprites.LEFT} top={StoneSprites.TOP} right={StoneSprites.RIGHT} bottom={StoneSprites.BOTTOM} > {children} </NineSlice>);StoneButton and StoneButtonPressed are two skins over one NineSlice,
whose slot is where the content ends up.
Props reach through a wrapper
Section titled “Props reach through a wrapper”A prop the wrapper does not declare falls through to the first element
underneath. ClaimButton declares only its label:
const ClaimButton = ({ label }: { label: String }): Component => ( <StoneButton> <text w="fill" h="fill" text={label} font={Fonts.BOLD} align="center" valign="middle" shadow color={Colors.AMBER} /> </StoneButton>);<ClaimButton label="Claim" right={0} bottom={0} w={74} h={22} options={['Claim']} />The position, size and options land on NineSlice’s <layer>. See
Passing props.
Composing rather than configuring
Section titled “Composing rather than configuring”When a prop would switch a component’s appearance, write two components and choose between them:
{tab === i ? ( <StoneButtonPressed> <text w="fill" h="fill" text={label} font={Fonts.BOLD} align="center" valign="middle" shadow color={Colors.WHITE} /> </StoneButtonPressed> ) : ( <StoneButton> <text w="fill" h="fill" text={label} font={Fonts.BOLD} align="center" valign="middle" shadow color={Colors.AMBER} /> </StoneButton> )}Naming a piece of markup
Section titled “Naming a piece of markup”Markup is a value, so you can bind a piece of it to a const and splice it in
by name:
export const Panel = ({ warn }: { warn: Boolean }): Component => { const banner = ( <layer w="fill" h={20}> <rect w="fill" h="fill" color={0x8b0000} fill /> </layer> );
return <layer w="fill" h="fill">{warn && banner}<Contents /></layer>;};A name is not a tag: {banner} works, <banner /> does not. Declare a
component when you want a tag or props.
Building a kit
Section titled “Building a kit”Reusable components go in their own file, exported by name:
import { StoneButton, StoneButtonPressed, SunkenBox, Window } from './chrome';Take the art as props, so one frame serves every panel:
const NineSlice = ({ topLeft, topRight, bottomLeft, bottomRight, left, top, right, bottom, children,}: FrameSprites & { children?: Children }): Component => ( <layer w="fill" h="fill"> <sprite x="left" y="top" w={CORNER} h={CORNER} sprite={topLeft} tiling /> {/* … the other seven … */} <layer w="fill" h="fill">{children}</layer> </layer>);Reaching a component the caller built
Section titled “Reaching a component the caller built”To act on something the caller owns, take its Ref as a prop and write to it,
as the scrollbar does to the list it scrolls:
export const Scrollbar = ({ content }: { content: Ref }): Component => { // … and it binds the wheel on the thing it was given: content.onScrollWheel = (event) => { content.scrollY = content.scrollY + event.mouseY * WHEEL; trackScroll(gutter, thumb, capTop, capBottom, content); }; // …};Refs has the rest.