The vanilla alternative for writing JSX-based React applications
git clone git://soophie.de/figure
1
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&family=Noto+Sans">3
<div id="app"></div>5
<style>7
* {8
font-family: 'Noto Sans', sans-serif;9
}11
body {12
margin: 0;13
}15
</style>17
<script type="module">19
import {useState, createElement} from 'https://cdn.skypack.dev/react';20
import {render} from 'https://cdn.skypack.dev/react-dom';7
import {createTlx} from './lib.js';21
import {createTlx, css} from './lib.js';23
const tlx = createTlx(createElement);25
const Button = (props, $slot) => {26
const styles = {13
minWidth: '40px',14
height: '40px',15
border: 'none',16
outline: 'none',17
border: '1px solid #a3a3a3',18
backgroundColor: props.disabled ? '#f5f5f5' : '#e5e5e5',19
cursor: props.disabled ? 'default' : 'pointer',20
fontSize: '1rem',21
fontFamily: 'Inter',27
...css`28
min-width: 40px;29
height: 40px;30
border: none;31
outline: none;32
border: 1px solid #a3a3a3;33
background-color: ${props.disabled ? '#f5f5f5' : '#e5e5e5'};34
cursor: ${props.disabled ? 'default' : 'pointer'};35
font-size: 1rem;36
`,37
...props.styles38
};39
return tlx`25
<button disabled="${props.disabled}" style="${styles}" @click="${() => props.onClick()}">${$slot}</button>40
<button41
disabled="${props.disabled}"42
style="${styles}"43
@click="${() => props.onClick()}"44
>45
${$slot}46
</button>47
`;48
}
59
<div style="display: inline-flex; width: 100%; border: 1px solid #a3a3a3; border-radius: 4px; overflow: hidden; height: 40px; box-sizing: border-box;">60
${Button({61
onClick: () => setValue(value - 1),41
styles: {42
fontSize: '1.2rem',43
borderWidth: '0px 1px 0px 0px'44
},62
styles: css`63
font-size: 1.2rem;64
border-width: 0px 1px 0px 0px;65
`,66
disabled: value <= min67
}, '-')}68
<input type="text" @input="${(event) => validate(event.target.value)}" value="${value}" style="font-size: 1rem; text-align: center; border: none; flex: 1; min-width: 0; height: 40px; outline: none;">69
${Button({70
onClick: () => setValue(value + 1),50
styles: {51
fontSize: '1.2rem',52
borderWidth: '0px 0px 0px 1px'53
},71
styles: css`72
font-size: 1.2rem;73
border-width: 0px 0px 0px 1px;74
`,75
disabled: value >= max76
}, '+')}77
</div>
85
<div style="margin: 100px; display: flex; width: 150px; flex-direction: column; gap: 15px; align-items: flex-start;">86
${Counter({ value: quantity, setValue: setQuantity })}87
${Button({67
styles: {68
backgroundColor: isDisabled ? '#f5f5f5' : '#f97316',69
borderColor: isDisabled ? '#d4d4d4' : '#c2410c',70
color: isDisabled ? '#a3a3a3' : 'white',71
borderRadius: '4px',72
width: '100%'73
},88
styles: css`89
background-color: ${isDisabled ? '#f5f5f5' : '#f97316'};90
border-color: ${isDisabled ? '#d4d4d4' : '#c2410c'};91
color: ${isDisabled ? '#a3a3a3' : '#ffffff'};92
border-radius: 4px;93
width: 100%;94
`,95
onClick: () => alert(`Added ${quantity} ${quantity > 1 ? 'items' : 'item'} to the cart`),96
disabled: isDisabled97
}, 'Add to cart')}
68
(node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));69
return [createElement(tag, attributes, ...children)];70
}72
return tlx;73
}72
function apply(value, refs) {73
let values = [];74
const expr = /\$tlx-\d+/g;75
export const css = (parts, ...props) => {76
const [css, refs] = ltr(parts, props);77
const styles = {};78
for(const item of css.split(';')) {79
if(item.trim().length === 0) {80
break;81
}82
let [key, value] = item.split(':');83
key = key.trim();84
let match = null;76
let last = 0;77
while((match = expr.exec(value)) !== null) {78
const index = match.index;79
values.push(value.substring(last, index));80
values.push(refs[match[0]]);81
last = index + match[0].length;85
if((match = /-(\w)/.exec(key)) !== null) {86
const char = match[1];87
key = key.replace(`-${char}`, char.toUpperCase());88
}83
values.push(value.substring(last));84
return values;89
value = value.trim();90
styles[key] = apply(value, refs).join('');91
}92
return styles;93
}87
function ltr(parts, props) {88
let string = '';89
const refs = {};90
for(let i = 0; i < parts.length; i++) {91
string += parts[i];92
if(props[i] !== undefined) {93
const id = `$tlx-${counter}`;94
refs[id] = props[i];95
string += id ?? '';96
counter++;97
}95
function apply(value, refs) {96
let values = [];97
const expr = /\$tlx-\d+/g;98
let match = null;99
let last = 0;100
while((match = expr.exec(value)) !== null) {101
const index = match.index;102
values.push(value.substring(last, index));103
values.push(refs[match[0]]);104
last = index + match[0].length;105
}106
values.push(value.substring(last));107
return values;108
}110
function ltr(parts, props) {111
let string = '';112
const refs = {};113
for(let i = 0; i < parts.length; i++) {114
string += parts[i];115
if(props[i] !== undefined) {116
const id = `$tlx-${counter}`;117
refs[id] = props[i];118
string += id ?? '';119
counter++;120
}99
return [string.trim(), refs];121
}102
return tlx;122
return [string.trim(), refs];123
}