add htmx-alpine hybrid optimistic ui

This commit is contained in:
2025-12-12 02:56:51 +01:00
parent aa8410c723
commit 72741c9027
10 changed files with 421 additions and 0 deletions
@@ -0,0 +1,14 @@
{{ $data := .Data }} {{ $depth := .Depth }} {{ $mapa := $data | toMap }} {{
$depth | indent }} {{ if $mapa }} {{ $nextDepth := add $depth 1 }}
<ul style="list-style: none">
{{ range $key, $value := $mapa }}
<li>
{{ $nextDepth | indent }}
<strong>{{ $key }}:</strong>
{{ template "fragments/inspector" (dict "Data" $value "Depth" $nextDepth) }}
</li>
{{ end }}
</ul>
{{ else }} {{ $data }} {{ end }}
@@ -0,0 +1,43 @@
<form
id="create-todo-form"
class="flex gap-2 items-start w-full"
hx-post="/todo"
hx-swap="afterbegin"
hx-target="#todo-list-body"
hx-target-error="this"
x-data="{
hasError: {{ if .Error }}true{{ else }}false{{ end }}
}"
@htmx:after-request="
if($event.detail.successful && $event.detail.target.id === 'todo-list-body') {
$el.reset();
hasError = false;
creating = false;
}"
>
<div class="flex-grow form-control space-y-1.5">
<input
type="text"
name="name"
class="input input-sm input-bordered w-full"
:class="{ 'input-error': hasError }"
value="{{ .Name }}"
placeholder="Escribe tu tarea..."
x-init="$el.focus()"
@input="hasError = false"
/>
{{ if .Error }}
<p class="text-error text-xs" x-show="hasError">{{ .Error }}</p>
{{ end }}
</div>
<button
type="button"
class="btn btn-sm btn-ghost"
@click="creating = false; hasError = false; $el.form.reset();"
>
Cancelar
</button>
<button type="submit" class="btn btn-sm btn-primary">Confirmar</button>
</form>
@@ -0,0 +1,72 @@
<li
id="todo-{{.ID}}"
class="card bg-base-200 shadow-sm"
x-data="{
isEditing: false,
form: {
name: '{{ .Name }}'
},
cancelEdit() {
this.isEditing = false;
this.form.name = '{{ .Name }}';
}
}"
>
<div
class="card-body p-4 flex-row items-center justify-between gap-4"
x-show="!isEditing"
>
<div class="flex items-center gap-2 flex-grow">
<span class="cursor-pointer"> {{ .Name }} </span>
</div>
<div class="flex gap-2">
{{if not .Completed}}
<button class="btn btn-sm btn-ghost" @click="isEditing = true">
Editar
</button>
<button
class="btn btn-sm btn-secondary"
hx-patch="/todo/{{.ID}}/completed"
hx-target="closest li"
hx-swap="outerHTML"
>
Completar
</button>
{{else}}
<span
class="badge badge-success badge-sm text-white cursor-pointer"
hx-patch="/todo/{{.ID}}/completed"
hx-target="closest li"
hx-swap="outerHTML"
hx-trigger="dblclick"
>Completado</span
>
{{ end }}
</div>
</div>
<div
class="card-body p-4 flex-row items-center justify-between gap-4"
x-show="isEditing"
style="display: none"
>
<form
class="flex w-full gap-2"
hx-put="/todo/{{.ID}}"
hx-target="closest li"
hx-swap="outerHTML"
>
<input
type="text"
name="name"
class="input input-sm input-bordered flex-grow"
x-model="form.name"
/>
<button type="button" class="btn btn-sm btn-ghost" @click="cancelEdit()">
Cancelar
</button>
<button type="submit" class="btn btn-sm btn-primary">Guardar</button>
</form>
</div>
</li>
@@ -0,0 +1,26 @@
<div
class="card bg-base-100 shadow-xl w-1/3"
x-data="{
creating: false,
}"
>
<div class="card-body">
<div class="flex justify-between items-center mb-4">
<h2 class="card-title text-2xl font-medium">Listado de tareas</h2>
<button type="button" class="btn btn-primary" @click="creating = true">
Nueva tarea
</button>
</div>
<div x-show="creating" class="w-full pb-4">
{{ template "fragments/todo-row-form" .}}
</div>
<ol id="todo-list-body" class="space-y-3">
{{ range .Todo }} {{ template "fragments/todo-row" . }} {{ end }}
</ol>
</div>
</div>
{{ template "fragments/inspector" (dict "Data" . "Depth" 0) }}