Duckweed counter demo

http://bit.ly/duckweed-framework

by Hajime Yamasaki Vukelic

HTML

<script src="https://npm-cdn.herokuapp.com/duckweed@1.0.0/dist/duckweed.js"></script>
<div id="app">

</div>

CSS

body {
  font-family: Arial, sans-serif;
  font-size: 18px;
  text-align: center;
  padding: 1rem;
}

.root {
  padding: 10px;
  background: #ddd;
  display: inline-block;
  border-radius: 0.5rem;
  margin-right: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}

.inc,
.dec {
  vertical-align: middle;
  border: 0;
  padding: 0.6rem 1rem;
  width: 3rem;
  height: 3rem;
  font-size: 120%;
  margin: 0 0.4rem;
  color: white;
  cursor: pointer;
  border-radius: 5rem;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  outline: 0;
}

.inc {
  background: #a9c11d;
}

.dec {
  background: #ea4141;
}

.disp {
  display: inline-block;
  margin: 0 0.4rem;
  font-size: 160%;
  vertical-align: middle;
  font-weight: bold;
  color: #44aaee;
}

.sum {
  font-size: 160%;
}

img {
  width: 200px;
}

Babel + JSX

/** @jsx duckweed.html */

// We'll simulate modules using IIFE and module pattern

const wrap = obj => ({
  map: fn => wrap(fn(obj)),
  unwrap: () => obj,
});

const counter = (() => {
  const init = () => ({count: 0});
  
  const ACTIONS = {
    increment: 'increment',
    decrement: 'decrement',
  };
  
  const addCount = (n, model) => ({...model, count: model.count + n});
  
  const update = (model, address, ...args) => {
    switch (address) {
      case ACTIONS.increment:
        return addCount(1, model);
        
      case ACTIONS.decrement:
        return addCount(-1, model);
        
      default:
        return model;
    }
  };
  
  const view = ({model, act, transitionDelay = 0}) => (
    <div class="root" style={{opacity: 0, delayed: {opacity: 1}, transition: `opacity ${transitionDelay}s`}}>
      <button class="dec" on-click={act("decrement")}>-</button>
      <span class="disp">{"" + model.count}</span>
      <button class="inc" on-click={act("increment")}>+</button>
    </div>
  );
  
  return {
    init,
    update,
    view,
  };
})();

const list = (() => {
	const init = () => ({
    counters: [
      counter.init(),
      counter.init(),
      counter.init(),
    ],
    sum: 0,
    avg: 0,
  });
  
  const update = (model, counterId, ...args) =>
    wrap(model)
      .map(duckweed.scoped.transform.bind(null,
        ['counters', counterId],
        (counterModel) => counter.update(counterModel, ...args)
      ))
      .map(model1 => 
      	(sum => ({
          ...model1,
          sum: sum,
          avg: (sum / model1.counters.length).toFixed(2),
        }))(model1.counters.reduce((tot, cnt) => tot + cnt.count, 0)))
      .unwrap();
  
  const view = ({model, act}) => (
  	<div class="view">
  	  <h1>Duckweed counters</h1>
      <div>
        {model.counters.map((cnt, id) =>
          <counter.view model={cnt} act={act.bind(null, id)} transitionDelay={1 + id * 2} />
        )}
      </div>
      <p class="sum">
       ...