For sure, here is an example of a component that builds some UI. My thought was that by building the UI via script and putting the script into a module, I can avoid copy pasting it into each project and change tweak it in one place.
import * as ecs from '@8thwall/ecs'
// Default background image for popup
const popupBgImage = require('../assets/img/popup_bg.png')
const closeButtonImage = require('../assets/img/close_button.png')
ecs.registerComponent({
name: 'ui-popup',
schema: {
popupId: ecs.i32,
contentImageUrl: ecs.string, // URL for the image
contentText: ecs.string, // Text to display in the popup
ctaText: ecs.string, // Label for the call-to-action button
ctaEvent: ecs.string, // Event name to dispatch on CTA click
},
schemaDefaults: {
popupId: 0,
contentImageUrl: '',
contentText: 'Your popup message here',
ctaText: 'Take Action',
ctaEvent: 'popupAction',
},
add: (world, component) => {
const {eid} = component
const {popupId, contentImageUrl, contentText, ctaText, ctaEvent} = component.schema
// helper functions for show/hide
const showPopup = () => ecs.Ui.set(world, eid, {display: 'flex'})
const hidePopup = () => ecs.Ui.set(world, eid, {display: 'none'})
// Base popup overlay (initially hidden)
ecs.Ui.set(world, eid, {
type: 'overlay',
width: '100%',
height: '584px',
image: popupBgImage,
display: 'none',
position: 'absolute',
// top: '9%',
flexDirection: 'column',
alignItems: 'center',
})
// Content container (centers inner elements and constrains width)
const content = world.createEntity()
ecs.Ui.set(world, content, {
type: 'overlay',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '120px 80px 80px',
width: '100%',
height: '100%',
})
world.setParent(content, eid)
// Configurable content image
if (contentImageUrl) {
const img = world.createEntity()
ecs.Ui.set(world, img, {
type: 'overlay',
image: contentImageUrl,
width: '100px',
height: '100px',
})
world.setParent(img, content)
}
// 4) Text block (configurable)
const textBlock = world.createEntity()
ecs.Ui.set(world, textBlock, {
type: 'overlay',
text: contentText,
fontSize: 16,
color: '#FFFFFF',
marginTop: '12px',
marginBottom: '12px',
width: '100%',
})
world.setParent(textBlock, content)
// Call-to-action button (configurable)
if (ctaText && ctaEvent) {
const cta = world.createEntity()
ecs.Ui.set(world, cta, {
type: 'overlay',
text: ctaText,
width: '100%',
height: '40px',
background: '#FFFFFF',
color: '#000000',
borderRadius: 10,
backgroundOpacity: 1.0,
alignItems: 'center',
justifyContent: 'center',
})
world.setParent(cta, content)
world.events.addListener(cta, 'click', () => {
console.log(ctaText, 'clicked')
world.events.dispatch(world.events.globalId, ctaEvent, {popupId})
})
}
// Close button (centered with circular semi-transparent background)
const closeBtn = world.createEntity()
ecs.Ui.set(world, closeBtn, {
type: 'overlay',
image: closeButtonImage,
width: '36px',
height: '36px',
position: 'absolute',
bottom: '62px',
alignItems: 'center',
justifyContent: 'center',
})
world.setParent(closeBtn, content)
world.events.addListener(closeBtn, 'click', hidePopup)
// Global event listeners
world.events.addListener(world.events.globalId, 'openPopup', (e) => {
console.log('ui-popup received openPopup for', e.data.popupId)
if (e.data && e.data.popupId === popupId) showPopup()
})
world.events.addListener(world.events.globalId, 'hidePopup', (e) => {
console.log('ui-popup received hidePopup for', e.data.popupId)
if (e.data && e.data.popupId === popupId) hidePopup()
})
// Debug: show popup
showPopup()
},
remove: (world, component) => {
// Cleanup if needed
},
})