Subcomponents
Subcomponents demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import Split from './common/Split';
import { Picker } from './Picker';
export default () => (
<div className="container">
<h1>Subcomponents</h1>
<p>
Picker owns the data and the selection logic; its PascalCase methods are
seams a subclass overrides to change only how things look. Fruit replaces{' '}
<code>Item</code> alone and inherits the rest. Color replaces{' '}
<code>Summary</code> too, adding a readout Fruit has no use for.
</p>
<Split>
<FruitPicker />
<PalettePicker />
</Split>
</div>
);
class FruitPicker extends Picker {
name = 'Fruit';
names = ['Apple', 'Banana', 'Cherry'];
Item({ index }: { index: number }) {
return (
<>
{index === this.selected ? '🍎 ' : '🍏 '}
{this.names[index]}
</>
);
}
}
class PalettePicker extends Picker {
name = 'Color';
className = 'palette';
colors: Record<string, string> = {
Coral: '#ff6f61',
Sky: '#4dabf7',
Mint: '#51cf66'
};
names = Object.keys(this.colors);
Item({ index }: { index: number }) {
return <span className="swatch" style={{ background: this.colors[this.names[index]] }} />;
}
Summary() {
const name = this.names[this.selected];
return (
<small>
{name} <code>{this.colors[name]}</code>
</small>
);
}
}
App.css
.picker {
margin-bottom: var(--s5);
}
.picker h2 {
font-size: var(--t-lg);
margin-bottom: var(--s3);
}
li.active {
background: var(--accent);
color: var(--accent-fg);
}
/* Palette variant: lay the items out as a horizontal row of swatches. */
.palette ul {
flex-direction: row;
justify-content: center;
}
.palette .swatch {
display: block;
width: 2rem;
height: 2rem;
border-radius: var(--r-sm);
}
.palette li.active {
background: none;
outline: 2px solid var(--accent);
}
Picker.tsx
import { Component, set } from '@expressive/react';
export class Picker extends Component {
name = '';
names = [] as string[];
selected = 0;
className = set(() => this.name.toLowerCase());
choose(index: number) {
this.selected = index;
}
Item({ index }: { index: number }) {
return <>{this.names[index]}</>;
}
Summary() {
return <small>Selected: {this.names[this.selected]}</small>;
}
render() {
return (
<div className={`picker ${this.className}`}>
{this.name && <h2>Choose {this.name}</h2>}
<ul>
{this.names.map((item, i) => (
<li
key={item}
className={i === this.selected ? 'active' : ''}
onClick={() => this.choose(i)}>
<this.Item index={i} />
</li>
))}
</ul>
<this.Summary />
</div>
);
}
}