From c918674d0c06fbde237b708763df56e333d08d4d Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Mon, 19 Jun 2023 07:59:21 +0300 Subject: [PATCH] todos-preact --- docs/examples/todos-preact/app.js | 20 ++++ docs/examples/todos-preact/index.html | 13 +++ docs/examples/todos-preact/index.js | 132 ++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 docs/examples/todos-preact/app.js create mode 100644 docs/examples/todos-preact/index.html create mode 100644 docs/examples/todos-preact/index.js diff --git a/docs/examples/todos-preact/app.js b/docs/examples/todos-preact/app.js new file mode 100644 index 0000000..e9e77dd --- /dev/null +++ b/docs/examples/todos-preact/app.js @@ -0,0 +1,20 @@ +import {render} from 'https://unpkg.com/preact?module'; + +let state, component, root + +export const createApp = initial => { + /* if state is already initialized then preserve it */ + state = state ?? initial.initialState + component = initial.component + root = initial.root + do_render() +} + +export const handler = fn => (...args) => { + state = fn(state, ...args) + do_render() +} + +export const connect = comp => props => comp(props, state) + +const do_render = () => render(component(), root) diff --git a/docs/examples/todos-preact/index.html b/docs/examples/todos-preact/index.html new file mode 100644 index 0000000..a204468 --- /dev/null +++ b/docs/examples/todos-preact/index.html @@ -0,0 +1,13 @@ + + + + Todos Example + + + + + diff --git a/docs/examples/todos-preact/index.js b/docs/examples/todos-preact/index.js new file mode 100644 index 0000000..af15975 --- /dev/null +++ b/docs/examples/todos-preact/index.js @@ -0,0 +1,132 @@ +import {h, render} from 'https://unpkg.com/preact?module'; + +// external +import {createApp, handler, connect} from './app.js' + +// Components + +const App = () => ( + h('div', null, + h(AddTodo), + h(TodoList), + h(Footer), + ) +) + +const Footer = () => ( + h('div', null, + h('span', null, 'Show: '), + h(FilterLink, {filter: 'ALL'}, 'All'), + h(FilterLink, {filter: 'ACTIVE'}, 'Active'), + h(FilterLink, {filter: 'COMPLETED'}, 'Completed'), + ) +) + +const FilterLink = connect(({filter, children}, state) => { + const disabled = state.filter == filter + return h('button', { + onClick: handler(changeFilter.bind(null, filter)), + disabled, + style:{ + marginLeft: '4px', + } + }, children) +}) + +const TodoList = connect( (_, state) => + h('ul', null, + visibleTodos(state).map(todo => + h(Todo, { todo }) + ) + ) +) + +const Todo = ({ onClick, todo }) => ( + h('li', { + onClick: handler(toggleTodo.bind(null, todo)), + style: { + textDecoration: todo.completed ? 'line-through' : 'none' + }, + }, todo.text) +) + +const AddTodo = connect((_, state) => { + return ( + h('div', null, + h('form', { + onSubmit: handler(createTodo), + }, + h('input', { + value: state.text, + onChange: handler(changeText), + autoFocus: true, + }), + h('button', {type: 'submit'}, 'Add Todo') + ) + ) + ) +}) + + +// Selectors + +function visibleTodos(state) { + if(state.filter == 'ALL') { + return state.todos + } else if (state.filter == 'ACTIVE') { + return state.todos.filter(t => !t.completed) + } else if(state.filter == 'COMPLETED') { + return state.todos.filter(t => t.completed) + } else { + throw new Error('unknown filter') + } +} + +// Reducers + +function changeText(state, e) { + return {...state, text: e.target.value} +} + +function changeFilter(filter, state) { + return {...state, filter} +} + +function createTodo(state, e) { + e.preventDefault() + + if(!state.text.trim()) { + return state + } + + return { + ...state, + todos: [...state.todos, {text: state.text}], + text: '', + } +} + +function toggleTodo(todo, state) { + return { + ...state, + todos: state.todos.map(t => + (t == todo) + ? {...todo, completed: !todo.completed} + : t + ) + } +} + +createApp({ + + initialState: { + todos: [], + text: '', + filter: 'ALL', + }, + + component: App, + + root: document.body, +}) +