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>
    );
  }
}