Skip to content

Commit

Permalink
Add full todo list.
Browse files Browse the repository at this point in the history
  • Loading branch information
thesoftwarephilosopher committed Aug 17, 2024
1 parent dd108f7 commit 0dc4896
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 2 deletions.
7 changes: 5 additions & 2 deletions site/index.html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ export default <>
<Q>Could they be convenient without a virtual dom?</Q>
<Sample which="sample4" />

<p>This idea came out of my work on <a href="https://github.com/sdegutis/imlib">imlib</a>.</p>
<p>(Also, here's a much better <a href="https://sdegutis.github.io/imlib-todolist/">imlib todo-list app</a>.)</p>
<Q>How would they manage complex state?</Q>
<Q>How could parent components react to children?</Q>
<Sample which="sample5" />

<p>This came out of my work on <a href="https://github.com/sdegutis/imlib">imlib</a>.</p>
</div>

</body>
Expand Down
124 changes: 124 additions & 0 deletions site/samples/sample5.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
export default () => <>
<TodoList />
</>;

function TodoList() {
const list = new List();

list.add('foo');
list.add('bar').toggle();
list.add('qux');

const input = <input class='next' autofocus /> as HTMLInputElement;
input.onkeydown = (e) => {
if (e.key === 'Enter' && input.value.trim().length > 0) {
list.add(input.value);
input.value = '';
}
};

return <div id='real-todolist'>
<div>{input}</div>
<div class='actions'>
<Counter list={list} />
<ActionButton onclick={() => list.clearDone()}>Clear done</ActionButton>
<ActionButton onclick={() => list.invertAll()}><i>Invert</i> all</ActionButton>
</div>
{list.ul}
</div>;
}

class List {

ul = <ul class='list' /> as HTMLUListElement;
#items: Item[] = [];

onchange?: () => void;

add(text: string) {
const item = new Item(this, text);
this.#items.push(item);
this.ul.append(item.li);
this.onchange?.();
return item;
}

rem(item: Item) {
this.#items = this.#items.filter(it => it !== item);
this.onchange?.();
}

clearDone() {
this.doneItems().forEach(it => it.remove());
}

invertAll() {
this.#items.forEach(it => it.toggle());
}

doneItems() {
return this.#items.filter(it => it.done);
}

itemChanged() {
this.onchange?.();
}

}

class Item {

public done = false;
private checkbox = <input type='checkbox' /> as HTMLInputElement;

constructor(
private list: List,
public text: string,
public li = (
<li class='item'>
{this.checkbox}
<span onclick={() => this.toggle()}>{text}</span>
<button class='close' onclick={() => this.remove()}></button>
</li> as HTMLLIElement
),
) {
this.checkbox.onclick = () => this.toggle();
}

remove() {
this.li.remove();
this.list.rem(this);
}

toggle() {
this.done = !this.done;
this.li.classList.toggle('done', this.done);
this.checkbox.checked = this.done;
this.list.itemChanged();
}

}

function Counter({ list }: { list: List }) {
const span = <span /> as HTMLSpanElement;

const updateText = () => {
span.textContent = `Done: ${list.doneItems().length}`;
};

updateText();
list.onchange = updateText;

return span;
}

function ActionButton(attrs: { onclick: () => void }, children: any) {
return <a
href='#'
class='action-button'
onclick={(e: Event) => {
e.preventDefault();
attrs.onclick();
}}
>{children}</a>;
}
87 changes: 87 additions & 0 deletions site/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,91 @@ button {
input:not([type=checkbox]):focus,
button:active {
outline-color: #19f;
}

/* Imported from other todolist repo */

#real-todolist .list {
padding-left: 0;
list-style-type: none;
}

#real-todolist button,
#real-todolist input {
appearance: none;
background-color: #000;
color: inherit;
border: none;
outline: 1px solid #2d343c;
padding: 0.5em;
font-family: inherit;
border-radius: 3px;
}

#real-todolist button {
cursor: pointer;
}

#real-todolist input.next {
width: 100%;
box-sizing: border-box;
}

#real-todolist input:not([type=checkbox]):focus,
#real-todolist button:active {
outline-color: #19f;
}

#real-todolist .item {
display: flex;
align-items: center;
}

#real-todolist .item:hover {
background-color: #0002;
}

#real-todolist .item input[type=checkbox] {
margin-left: 0.5em;
margin-right: 0.5em;
}

#real-todolist .item input[type=checkbox]:not(:checked) {
outline-color: #19f;
}

#real-todolist .item span {
flex: 1 0 auto;
}

#real-todolist .item.done span {
text-decoration: line-through #f00c 7px;

}

#real-todolist .item>:is(span, input) {
cursor: pointer;
}

#real-todolist .item.done span {
opacity: .5;
}

#real-todolist .item button.close:not(:hover) {
border: none;
outline: none;
background-color: inherit;
}

#real-todolist .actions {
background-color: #0002;
padding: 0.75em;
margin: 1em 0;
font-size: small;
display: flex;
justify-content: space-between;
}

#real-todolist .actions a.action-button:not(:hover) {
text-decoration: none;
}

0 comments on commit 0dc4896

Please sign in to comment.