refactor: migrate templates to tailwind utilities

This commit is contained in:
2026-04-15 00:07:56 +02:00
parent 01c738588f
commit 0a5f64c604
23 changed files with 374 additions and 1614 deletions

View File

@@ -15,37 +15,37 @@ type AnimeCardProps struct {
templ AnimeCard(props AnimeCardProps) {
if props.CurrentNode {
<div class={ defaultString(props.Class, "catalog-item") }>
<div class={ defaultString(props.Class, "min-w-0") }>
if props.ImageURL != "" {
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "catalog-thumb") } loading="lazy"/>
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "block aspect-[2/3] max-h-[var(--poster-max-height)] w-full object-cover object-center") } loading="lazy"/>
} else {
<div class="no-image">No image</div>
<div class="flex aspect-[2/3] max-h-[var(--poster-max-height)] w-full items-end justify-center overflow-hidden text-[0] text-transparent">No image</div>
}
<div class={ defaultString(props.TitleClass, "catalog-title") }>
<div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-[0.86rem] leading-[1.3] text-[var(--text)]") }>
{ props.Title }
</div>
{ children... }
</div>
} else {
<a href={ templ.URL(fmt.Sprintf("/anime/%d", props.ID)) } class={ props.Class }>
<a href={ templ.URL(fmt.Sprintf("/anime/%d", props.ID)) } class={ defaultString(props.Class, "flex flex-col bg-transparent text-inherit no-underline") }>
if props.Class == "notification-card" || props.Class == "schedule-card" {
<div class={ defaultString(props.ImgClass, "schedule-card-image") }>
<div class={ defaultString(props.ImgClass, "flex aspect-[2/3] max-h-[var(--poster-max-height)] w-full items-end justify-center overflow-hidden") }>
if props.ImageURL != "" {
<img src={ props.ImageURL } alt={ props.Title } loading="lazy"/>
<img src={ props.ImageURL } alt={ props.Title } class="block h-full w-full object-cover object-center" loading="lazy"/>
} else {
<div class="no-image">No image</div>
<div class="flex aspect-[2/3] max-h-[var(--poster-max-height)] w-full items-end justify-center overflow-hidden text-[0] text-transparent">No image</div>
}
</div>
} else {
if props.ImageURL != "" {
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "catalog-thumb") } loading="lazy"/>
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "block aspect-[2/3] max-h-[var(--poster-max-height)] w-full object-cover object-center") } loading="lazy"/>
} else {
<div class="no-image">No image</div>
<div class="flex aspect-[2/3] max-h-[var(--poster-max-height)] w-full items-end justify-center overflow-hidden text-[0] text-transparent">No image</div>
}
}
if props.Class != "notification-card" && props.Class != "schedule-card" {
<div class={ defaultString(props.TitleClass, "catalog-title") }>
<div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-[0.86rem] leading-[1.3] text-[var(--text)]") }>
{ props.Title }
</div>
}

View File

@@ -7,12 +7,12 @@ import (
templ InfiniteAnimeList(animes []jikan.Anime, hasNext bool, nextURL string, containerID string) {
for _, anime := range animes {
<div class="catalog-item" data-id={ fmt.Sprintf("%d", anime.MalID) }>
<div class="min-w-0" data-id={ fmt.Sprintf("%d", anime.MalID) }>
@CatalogItem(anime)
</div>
}
if hasNext {
<div class="scroll-trigger full-span-trigger" hx-get={ nextURL } hx-trigger="revealed" hx-swap="outerHTML"></div>
<div class="col-span-full h-px w-full" hx-get={ nextURL } hx-trigger="revealed" hx-swap="outerHTML"></div>
}
<script data-container={ containerID }>
(function() {
@@ -20,7 +20,7 @@ templ InfiniteAnimeList(animes []jikan.Anime, hasNext bool, nextURL string, cont
const currentScript = scripts[scripts.length - 1];
const actualID = currentScript.getAttribute('data-container');
const container = document.getElementById(actualID) || document;
const items = container.querySelectorAll('.catalog-item[data-id]');
const items = container.querySelectorAll('[data-id]');
const seen = new Set();
items.forEach(item => {
const id = item.getAttribute('data-id');

View File

@@ -1,9 +1,9 @@
package ui
templ EmptyState(title string) {
<div class="empty-state py-4">
<div class="empty-state-title mb-2 text-base">{ title }</div>
<div class="empty-state-text text-sm text-[var(--color-text-muted)]">
<div class="py-4">
<div class="mb-2 text-base">{ title }</div>
<div class="text-sm text-[var(--text-muted)]">
{ children... }
</div>
</div>

View File

@@ -1,10 +1,10 @@
package ui
templ LoadingIndicator(text string) {
<div class="loading-indicator inline-flex items-center gap-2 text-sm text-[var(--color-text-muted)]">
<div class="loading-dot h-1.5 w-1.5 rounded-full bg-[var(--color-text-faint)]"></div>
<div class="loading-dot h-1.5 w-1.5 rounded-full bg-[var(--color-text-faint)]"></div>
<div class="loading-dot h-1.5 w-1.5 rounded-full bg-[var(--color-text-faint)]"></div>
<div class="inline-flex items-center gap-2 text-sm text-[var(--text-muted)]">
<div class="h-1.5 w-1.5 rounded-full bg-[var(--text-faint)]"></div>
<div class="h-1.5 w-1.5 rounded-full bg-[var(--text-faint)]"></div>
<div class="h-1.5 w-1.5 rounded-full bg-[var(--text-faint)]"></div>
<span>{ text }</span>
</div>
}

View File

@@ -8,23 +8,23 @@ type SortFilterOptions struct {
}
templ SortFilter(opts SortFilterOptions) {
<div class="sort-filter">
<div class="sort-filter-group">
<label for="sort-select">Sort by</label>
<select id="sort-select" class="sort-filter-select" onchange="document.getElementById('sort-input').value = this.value; document.getElementById('sort-form').submit()">
<div class="mb-4 flex flex-wrap items-center gap-3 bg-[var(--panel)] p-3 max-[860px]:flex-col max-[860px]:items-start max-[860px]:gap-2">
<div class="flex items-center gap-2">
<label for="sort-select" class="text-[0.78rem] text-[var(--text-muted)]">Sort by</label>
<select id="sort-select" class="h-[30px] bg-[var(--surface-select)] px-2 text-[0.78rem] text-[var(--text)]" onchange="document.getElementById('sort-input').value = this.value; document.getElementById('sort-form').submit()">
<option value="date" selected?={ opts.Sort == "date" }>Date added</option>
<option value="title" selected?={ opts.Sort == "title" }>Title</option>
</select>
</div>
<div class="sort-filter-group">
<label for="order-select">Order</label>
<select id="order-select" class="sort-filter-select" onchange="document.getElementById('order-input').value = this.value; document.getElementById('sort-form').submit()">
<div class="flex items-center gap-2">
<label for="order-select" class="text-[0.78rem] text-[var(--text-muted)]">Order</label>
<select id="order-select" class="h-[30px] bg-[var(--surface-select)] px-2 text-[0.78rem] text-[var(--text)]" onchange="document.getElementById('order-input').value = this.value; document.getElementById('sort-form').submit()">
<option value="desc" selected?={ opts.Order == "desc" }>Descending</option>
<option value="asc" selected?={ opts.Order == "asc" }>Ascending</option>
</select>
</div>
</div>
<form id="sort-form" method="get" class="is-hidden">
<form id="sort-form" method="get" class="hidden">
<input type="hidden" name="sort" id="sort-input" value={ opts.Sort }/>
<input type="hidden" name="order" id="order-input" value={ opts.Order }/>
if opts.View != "" {