wp-packages/internal/http/templates/components.html
Ben Word 154cb2f376
Migrate templates from html/template to Jet v6
Replace Go's html/template with CloudyKit/jet/v6 for server-rendered
templates. Jet provides first-class parameterized blocks, template
inheritance via extends/yield, and C-like expressions — eliminating
the dict helper workarounds that html/template required for reusable
components.

Add components.html with four reusable Jet blocks:
- shell(command, variant, class) — copyable command boxes (replaces
  ~16 duplicated instances across 6 templates)
- notice(variant, class) with yield content — alert boxes with
  variant-based styling and content slots
- input(icon, name, ...) — form inputs with icon and keyboard hint
- pagination(pager, class) — pagination controls driven by
  pre-computed buildPagination() data

Key changes:
- templates.go: embed.FS loader for Jet, function registration via
  AddGlobal, buildPagination() helper, render() using VarMap
- handlers.go: simplified render calls (no more template combinations)
- router.go: *jet.Set replaces *templateSet
- All 13 templates converted to Jet syntax (extends/block/yield)
- Tailwind scanning unaffected (templates remain .html files)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:05:25 -05:00

37 lines
8.2 KiB
HTML

{{block shell(command, variant="default", class="")}}
<div class="flex items-center gap-3 rounded-xl border {{if variant == "amber"}}border-amber-200 bg-amber-100/50 hover:bg-amber-100/80{{else}}border-gray-200/60 bg-gray-100/30 hover:border-gray-300{{end}} px-4 py-2.5 cursor-pointer transition-colors{{if class != ""}} {{class}}{{end}}" onclick="copyCmd(this,'{{command}}')">
<span class="{{if variant == "amber"}}text-amber-600{{else}}text-gray-500{{end}} text-sm font-mono select-none shrink-0">$</span>
<code class="flex-1 text-sm font-mono {{if variant == "amber"}}text-amber-900{{else}}text-gray-900{{end}} truncate">{{command}}</code>
<button aria-label="Copy to clipboard" class="shrink-0 {{if variant == "amber"}}text-amber-600 hover:text-amber-900{{else}}text-gray-400 hover:text-gray-900{{end}} transition-colors cursor-pointer">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9.75a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"/></svg>
</button>
</div>
{{end}}
{{block notice(variant="warning", class="")}}
<div class="inline-flex items-start gap-2 rounded-lg border {{if variant == "caution"}}border-red-200 bg-red-50 text-red-900{{else if variant == "warning"}}border-amber-200 bg-amber-50 text-amber-900{{else if variant == "tip"}}border-green-200 bg-green-50 text-green-900{{else if variant == "important"}}border-purple-200 bg-purple-50 text-purple-900{{else}}border-blue-200 bg-blue-50 text-blue-900{{end}} px-4 py-2 text-sm{{if class != ""}} {{class}}{{end}}">
{{if variant == "caution"}}<svg class="w-5 h-5 text-red-600 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9.75v6.75m0 0a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-9.303-3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126Z"/></svg>
{{else if variant == "warning"}}<svg class="w-5 h-5 text-amber-600 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/></svg>
{{else if variant == "tip"}}<svg class="w-5 h-5 text-green-600 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg>
{{else if variant == "important"}}<svg class="w-5 h-5 text-purple-600 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/></svg>
{{else}}<svg class="w-5 h-5 text-blue-600 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"/></svg>
{{end}}
<span class="text-balance">{{yield content}}</span>
</div>
{{end}}
{{block input(icon, name, value="", placeholder="", type="text", showShortcut=false, id="", autocomplete="")}}
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">{{if icon == "search"}}<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/>{{else if icon == "user"}}<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/>{{else if icon == "filter"}}<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z"/>{{else if icon == "sort"}}<path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5"/>{{end}}</svg>
<input type="{{type}}" name="{{name}}" value="{{value}}" placeholder="{{placeholder}}"{{if id != ""}} id="{{id}}"{{end}}{{if autocomplete != ""}} autocomplete="{{autocomplete}}"{{end}} class="w-full pl-9 {{if showShortcut}}pr-12{{else}}pr-8{{end}} h-10 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
{{if showShortcut}}<kbd class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none hidden sm:inline-flex h-5 items-center rounded border border-gray-200/80 bg-gray-100/80 px-1.5 text-[10px] font-mono text-gray-500">/</kbd>{{end}}
{{end}}
{{block pagination(pager, class="")}}
{{if pager}}
<div class="flex justify-center items-center gap-1{{if class != ""}} {{class}}{{end}}">
{{if pager.PrevURL}}<a href="{{pager.PrevURL}}" hx-get="{{pager.PrevPartial}}" hx-target="{{pager.Target}}" hx-swap="innerHTML show:{{pager.SwapTarget}}" hx-include="" class="inline-flex items-center gap-1 px-3 py-2 border border-gray-200 rounded-lg text-sm text-gray-500 hover:bg-gray-50 hover:text-gray-900 transition-colors cursor-pointer"><svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg>Previous</a>{{else}}<span class="inline-flex items-center gap-1 px-3 py-2 border border-gray-200 rounded-lg text-sm text-gray-300 cursor-default"><svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg>Previous</span>{{end}}
{{range _, pg := pager.Pages}}{{if pg.Number == 0}}<span class="min-w-9 h-9 px-2 inline-flex items-center justify-center text-sm text-gray-400">...</span>{{else if pg.Number == pager.Page}}<span class="min-w-9 h-9 px-2 inline-flex items-center justify-center border border-gray-200 rounded-lg text-sm font-medium text-gray-900 bg-gray-50">{{pg.Number}}</span>{{else}}<a href="{{pg.URL}}" hx-get="{{pg.PartialURL}}" hx-target="{{pager.Target}}" hx-swap="innerHTML show:{{pager.SwapTarget}}" hx-include="" class="min-w-9 h-9 px-2 inline-flex items-center justify-center border border-gray-200 rounded-lg text-sm text-gray-500 hover:bg-gray-50 hover:text-gray-900 transition-colors cursor-pointer">{{pg.Number}}</a>{{end}}{{end}}
{{if pager.NextURL}}<a href="{{pager.NextURL}}" hx-get="{{pager.NextPartial}}" hx-target="{{pager.Target}}" hx-swap="innerHTML show:{{pager.SwapTarget}}" hx-include="" class="inline-flex items-center gap-1 px-3 py-2 border border-gray-200 rounded-lg text-sm text-gray-500 hover:bg-gray-50 hover:text-gray-900 transition-colors cursor-pointer">Next<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/></svg></a>{{else}}<span class="inline-flex items-center gap-1 px-3 py-2 border border-gray-200 rounded-lg text-sm text-gray-300 cursor-default">Next<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/></svg></span>{{end}}
</div>
{{end}}
{{end}}