Files
kompress_eshop/hardcoded-inventory.md
2026-06-18 10:51:07 +02:00

23 KiB
Raw Blame History

Handcoded UI Components — Penguin UI Replacement Index

Scope: Every handcoded UI component. Each item maps to a Penguin UI component that duplicates the same purpose with fewer lines and better accessibility.

Vendoring convention

The full library is now vendored locally at repo-root penguinui-components/ (177 component .html files, moved there 2026-06-18). Read components from there — NO network/curl/WebFetch needed anymore.

HARD RULE — read-only, never edit

penguinui-components/ is a read-only third-party library, NOT our code. Never edit, never {% include %}, never adapt in place. It is reference only; copy markup OUT of it and adapt at the use-site.

When a Penguin UI component can replace a handcoded one:

  1. Find the component in the local penguinui-components/ directory.
  2. Copy its markup out into the appropriate assets/views/ location, adapting it where used (strip docs-only demo triggers, fix obvious upstream bugs, wire data bindings, map Penguin classes to our design tokens). Note the deviations in a comment next to the adapted copy.
  3. Keep the original penguinui-components/ file untouched — it stays as a byte-for-byte reference snapshot.
  4. Rebuild Tailwind (make css) so any new utility classes get compiled.
  5. Mark the section below as DONE.

Why it's at repo root + the build guard

Moved OUT of assets/views/ to repo root because Tailwind v4 auto-detects sources from the project root — so assets/css/app.css carries @source not "../../penguinui-components"; to explicitly exclude it from the build. If the build ever balloons, check that exclusion is intact.


0. Toast — DONE

Penguin UI: toast-notification/stacking-toast-notification.html

  • Exact upstream mirror at penguinui-components/toast-notification/stacking-toast-notification.html (reference only)
  • Adapted/rendered copy lives inline in assets/views/base.html (demo triggers removed; the upstream dismiss-button <svg> quote bugs fixed)
  • The global toast('message') JS helper now dispatches the component's notify event ({ variant: 'success', message }), so existing callsites (shop/show.html, shop/_card.html) keep working unchanged.

1. Navbar

Penguin UI: navbar/

# Location What it is Size
1 assets/views/base.html:63-191 Full site navbar: brand, desktop nav links, cart icon+badge, settings dropdown, mobile hamburger → mobile panel ~130 lines
2 assets/views/admin/base.html:102-114 Admin top bar: hamburger toggle + breadcrumb text ~13 lines

Details for #1 (site navbar):

  • Brand/logo: base.html:74-77 — plain <a> with text
  • Desktop nav links: base.html:80-92<ul> with 45 items, manual aria-current routing
  • Cart icon + badge: base.html:96-109 — hand-rolled SVG cart icon + an Alpine x-data badge that reads document.cookie directly
  • Settings dropdown: base.html:110-162 — gear-icon trigger + language-switcher <form> + theme tristate (system/light/dark)
  • Mobile hamburger: base.html:164-172 — hamburger SVG button
  • Mobile menu panel: base.html:175-190 — dropdown <ul> with duplicated nav links

Penguin navbar variants: default-navbar.html, with-call-to-action.html, with-search.html, with-user-profile.html


2. Sidebar (Admin) — DONE

Penguin UI: sidebar/simple-sidebar.html

  • Exact upstream mirror at penguinui-components/sidebar/simple-sidebar.html (reference only)
  • Adapted at use-site in assets/views/admin/base.html: the nav links + bottom exit/logout now use Penguin's link treatment (hover:bg-primary/5, underline-offset-2 focus-visible:underline focus:outline-hidden) and the subtle active state (bg-primary/10 + text-on-surface-strong) mapped onto our data-nav/aria-current so markActiveNav() still drives it.
  • The fixed-rail translate-X show/hide mechanics + mobile overlay (#4) are layout scaffolding, kept as-is. Icons were intentionally not added (no verified icon set yet) — possible follow-up.

3. Sidebar (Category Accordion) — DONE

Penguin UI: sidebar/sidebar-with-collapsible-menus.html

  • Exact upstream mirror at penguinui-components/sidebar/sidebar-with-collapsible-menus.html (reference only)
  • Adapted at use-site in assets/views/shop/_sidebar.html: Penguin link treatment + active state + chevron-down rotation (rotate-180); child items now sit in a bordered/indented list instead of the old padding-left:28px + . Kept our htmx partial, data-driven category_groups, auto-expand x-init, and data-nav/markActiveNav() active routing.
  • Deviations: group row keeps our link + chevron-toggle split (categories are navigable, not just expandable); uses x-show/x-transition instead of upstream's x-collapse (that Alpine plugin isn't bundled in our build).
  • The <aside> drawer + mobile overlay (#6) in base.html are layout scaffolding, kept as-is.

4. Dropdown (Settings) — DONE

Penguin UI: dropdowns/dropdown-with-click.html

  • Exact upstream mirror at penguinui-components/dropdowns/dropdown-with-click.html (reference only)
  • De-duplicated: the ~103-line copy-paste is now one shared partial assets/views/partials/settings_dropdown.html, included by both base.html and admin/base.html (each host keeps its own positioning wrapper <div x-data="{ open:false }" class="relative [ml-auto]">).
  • Adopts Penguin's dropdown menu container + item treatment. Deviations: kept our gear icon-only trigger and core-Alpine open/@click.outside toggle (upstream's x-trap/$focus need the Alpine Focus plugin we don't bundle); item hover uses bg-primary/5 for consistency with the rest of the UI.

5. Country / Phone Combobox

Penguin UI: text-input/ + custom dropdown list

# Location What it is Size
9 assets/views/shop/checkout.html:49-74 Phone prefix combobox (+421, +420, …, +33) ~25 lines
10 assets/views/shop/checkout.html:102-127 Country combobox (SK, CZ, AT, DE, PL, HU) ~26 lines

Details for #9:

  • Alpine x-data with prefix, prefixOpen, opts array of { v, l } (9 country codes)
  • Manual filtered computed property
  • Inline chevron SVG that rotates via :class="prefixOpen && 'rotate-180'"
  • Dropdown list with <template x-for> and @click selection

Details for #10:

  • Same pattern as #9 but with translate-able country names (6 countries)
  • Includes +421 prefix shortcut

6. Product Card — DONE

Penguin UI: card/ecommerce-product-card.html

  • Exact upstream mirror at penguinui-components/card/ecommerce-product-card.html (reference only)
  • Adapted/rendered copy is assets/views/shop/_card.html: <article> shell + Penguin image/title/price layout and the cart-icon add-to-cart button, wired to our product data + i18n + htmx hx-post add-to-cart + toast(). Demo-only rating stars, hardcoded content and max-w-sm (fights the shop grid) were dropped; whole card links to the product page; out-of-stock badge kept.

Penguin UI: carousel/ (3 variants)

# Location What it is Size
12 assets/views/shop/show.html:8-26 Image gallery with main image + thumbnail strip, Alpine x-data="{ active: 0 }" ~19 lines

Details:

  • Main image: x-show="active === {{ loop.index0 }}" with object-cover
  • Thumbnail buttons: border changes to indicate active state
  • No transition/animation between images — just x-show toggling

8. Radio-Button Groups

Penguin UI: radio (part of form inputs)

# Location What it is Size
13 assets/views/shop/checkout.html:133-165 Carrier selection radio group (each option shows name + price) ~33 lines
14 assets/views/shop/checkout.html:167-180 Payment method radio group (COD + bank transfer) ~14 lines

Details for #13:

  • {% for m in shipping_methods %} loop
  • Each <label> is a styled card with has-[:checked]:border-primary border highlight
  • Radio input triggers @change to update Alpine state (carrier, carrierPrice, requiresPoint)
  • Pickup-point sub-panel shown via x-show="requiresPoint"

Details for #14:

  • Two hardcoded radio options: COD and bank_transfer
  • x-model="paymentMethod" binding

9. Checkbox — DONE

Penguin UI: checkbox/default-checkbox.html

  • Exact upstream mirror at penguinui-components/checkbox/default-checkbox.html (reference only)
  • ui::checkbox(name, label, id, value="on", checked, attrs) macro in macros/ui.html (full Penguin control: custom box + check-icon + label, has-checked:/peer variants).
  • Adopted: product/category "Published" + shipping "Enabled".

10. Text Input — DONE

Penguin UI: text-input/default-text-input.html

  • Exact upstream mirror at penguinui-components/text-input/default-text-input.html (reference only)
  • ui::input(name, type, id, value, placeholder, required, autocomplete, attrs, extra, width="w-full") macro — verbatim Penguin classes (bg-surface-alt, focus-visible:outline-*). Adopted at every text/email/number/password input: login (2), checkout (email, name, phone, address, city, zip), product form (6), category form (3), product detail quantity, shipping price (width="w-28").
  • The cart-body quantity input keeps its complex @change handler inline with the same Penguin classes (mixed single/double quotes can't pass through a macro arg).
  • Note: padding is Penguin's px-2 py-2 (was px-3) and bg is bg-surface-alt (was bg-surface) — the real Penguin look.

11. Textarea — DONE

Penguin UI: text-area/default-textarea.html

  • Exact upstream mirror at penguinui-components/text-area/default-textarea.html (reference only)
  • ui::textarea(name, id, value, rows, placeholder, required, attrs, extra) macro.
  • Adopted: checkout note, product & category description.

12. Select/Dropdown (Native) — DONE

Penguin UI: select/default-select.html

  • Exact upstream mirror at penguinui-components/select/default-select.html (reference only)
  • Adopted inline (3 sites: product category, category parent, order status) — Penguin appearance-none select on bg-surface-alt wrapped in relative with the chevron SVG. Inline rather than a macro because the <option> set is caller-specific.

13. File Input — DONE

Penguin UI: file-input/default-file-input.html

  • Exact upstream mirror at penguinui-components/file-input/default-file-input.html (reference only)
  • ui::file_input(name, id, accept, attrs, extra) macro (verbatim Penguin file: styling).
  • Adopted: product & category image upload.

14. Table

Penguin UI: table/ (7 variants)

# Location What it is Size
34 assets/views/admin/orders/index.html:11-36 Orders table: number, customer, status pill, total, "View" link ~26 lines
35 assets/views/admin/orders/show.html:20-44 Order items table: product, quantity, line total + tfoot summary ~25 lines
36 assets/views/admin/catalog/products.html:20-70 Products table: image+name+category, price, stock, status pill, edit/view/delete actions ~51 lines
37 assets/views/admin/catalog/categories.html:20-59 Categories table: tree-indented name, product count, status pill, edit/delete ~40 lines
38 assets/views/shop/_cart_body.html:6-59 Cart table: product link, price, quantity input, line total, remove button + tfoot total ~54 lines

Pattern: Every table uses the same class structure:

<table class="w-full text-left text-sm">
<thead class="border-b border-outline bg-surface-alt text-xs uppercase tracking-wide text-on-surface/70">
<tbody class="divide-y divide-outline">
<tr class="hover:bg-surface-alt">

This is copy-pasted 5 times.


15. Alert / Error Banner — DONE

Penguin UI: alert/default-alert.html

  • Exact upstream mirror at penguinui-components/alert/default-alert.html (reference only)
  • Adapted into the ui::alert_danger(message, extra="") macro in assets/views/macros/ui.html (compact one-line danger alert + danger icon).
  • Adopted at both sites: admin/login.html (login error) and admin/orders/show.html (ship error).

16. Badge / Status Pill — DONE

Penguin UI: badge/soft-color-badge.html

  • Exact upstream mirror at penguinui-components/badge/soft-color-badge.html (reference only)
  • Adapted into the ui::badge(label, variant) macro in assets/views/macros/ui.html (variants: success | danger | warning | info | primary | neutral).
  • Adopted at the status-pill sites: "Auth" badge (admin/login.html), order status (orders/index.html, neutral), Published/Draft pills (products.html + categories.html, success/neutral).
  • Intentionally left inline (not soft-color pills): the cart item-count notification badge in base.html (count bubble, a different Penguin badge type) and the block-style "out of stock" notice in _card.html.

17. Buttons — DONE

Penguin UI: buttons/default-button.html, outline-button.html, ghost-button.html, button-with-icon.html

  • Exact upstream mirrors at penguinui-components/buttons/*.html (reference only).
  • Macros in assets/views/macros/ui.html: ui::button(label, variant="primary", type, href, attrs, extra, icon, size="px-4 py-2 text-sm") and ui::icon_button(icon, variant="ghost-secondary", aria_label, attrs, …). The per-variant class strings are the verbatim Penguin variants (solid primary|secondary|danger|success|warning|info, outline-*, ghost-*) — only inline-flex items-center justify-center gap-2 is added so <a>/w-full/icon render, and upstream's text-onDanger/text-onSuccess… token typos are fixed to our real text-on-* tokens. href<a> else <button>; attrs is raw (htmx / :disabled / name / value); icon is a raw <svg> rendered before the label (Penguin button-with-icon).
  • Sizes are NOT normalized: size defaults to Penguin's px-4 py-2 text-sm but each call site that differed keeps it (px-3 py-2 form-header cancels & order back, px-5 py-2 add-to-cart / cart-checkout / order-confirmed continue, px-6 py-2.5 checkout place-order, px-3 py-1.5 text-xs table actions).
  • Adopted across every standard filled/outline/submit button: login, product & category forms (save / cancel = outline-secondary), products/categories "new" + empty-state CTAs, orders detail (back/ship/status), shipping save, cart (continue/checkout/empty), checkout place-order (:disabled via attrs), product detail add-to-cart, order-confirmed continue.
  • Icon-only buttons now use ui::icon_button(icon, variant="ghost-secondary", aria_label, attrs, …) — Penguin ghost treatment, square. Converted: settings gear, both hamburgers (site + admin), admin sidebar toggle, mobile category toggle. The cart link (live x-init badge) and the category-accordion chevron keep the same Penguin ghost classes inline only because their markup mixes single+double quotes that can't be passed through a Tera macro arg — visually identical to icon_button.
  • Table row-actions (edit/view/delete/View/label) → ui::button outline-secondary / outline-danger at size="px-3 py-1.5 text-xs"; cart "Remove" → ghost-danger; card add-to-cart → ui::button with the cart icon.
  • Still genuinely not this component (tracked elsewhere): toast dismiss/Reply buttons (part of the vendored toast mirror, already Penguin), settings dropdown menu items (Penguin dropdown items), gallery thumbnail buttons (carousel), sidebar logout/exit (Penguin sidebar link treatment), and navbar nav-menu links/logout (belong to §1 Navbar). The file-input button is §13.

Gotcha for future macro use: Tera renders {% include %} in the includer's macro scope, so a template that includes a partial which calls ui:: must also {% import "macros/ui.html" as ui %} itself (see shop/cart.htmlshop/_cart_body.html). In an {% extends %} child the import must sit directly after {% extends %} with no comment/content before it.


18. Toggle / Switch

Penguin UI: toggle/ (2 variants)

# Location What it is Size
53 assets/views/base.html:13-30 Theme toggle (dark/light/system) — inline <script> JavaScript ~18 lines
54 assets/views/admin/base.html:13-30 Exact duplicate of the theme toggle JS ~18 lines

Details:

  • applyTheme(), setTheme(), currentTheme() — reads/writes localStorage
  • matchMedia('prefers-color-scheme: dark') listener
  • All hand-written vanilla JS, duplicated twice (36 lines total)

19. Inline SVG Icons

Penguin UI: none (Penguin uses Heroicons-equivalent inline SVGs)

# Location Icon Occurrences
55 base.html:70-72,168-170 Hamburger (3-line menu) 2
56 base.html:104-105 Shopping cart 1
57 base.html:116-121 Gear/cog (settings) 1
58 base.html:220-221 Checkmark (toast success) removed — now in vendored toast component
59 checkout.html:62-64,115-117 Chevron-down (dropdown arrow) 2
60 _sidebar.html:30-33 Chevron-right (accordion expand) 1
61 admin/base.html:106-108 Hamburger (admin sidebar toggle) 1
62 admin/base.html:121-125 Gear/cog (admin settings) 1

All are raw inline <svg> with hardcoded <path d="..."> — no icon library, no partials.


20. Empty State

Penguin UI: no direct component, but table empty states exist in Penguin tables

# Location What it is Size
63 assets/views/admin/orders/index.html:38-39 "No orders" message ~2 lines
64 assets/views/admin/catalog/products.html:72-78 "No products" with CTA button ~7 lines
65 assets/views/admin/catalog/categories.html:61-67 "No categories" with CTA button ~7 lines
66 assets/views/shop/_cart_body.html:67-70 "Cart empty" with CTA button ~4 lines
67 assets/views/shop/_sidebar.html:58-59 "No categories" message ~2 lines

21. Dashboard Navigation Cards

Penguin UI: card/

# Location What it is Size
68 assets/views/admin/index.html:12-27 3 dashboard link cards (Products, Categories, Orders) ~16 lines

Details:

  • Each card is an <a> styled with border, hover effect, and nested title+description
  • Same hover pattern: hover:border-primary

22. Checkout Order Summary

Penguin UI: card/ (ecommerce-summary style)

# Location What it is Size
69 assets/views/shop/checkout.html:190-218 Cart summary aside: item list, subtotal, shipping, total, place-order button ~29 lines

Details:

  • Item list with name × quantity + line total
  • Subtotal + shipping + total with tabular-nums
  • Dynamic shipping price from Alpine carrierPrice
  • Disabled submit button when !canSubmit

23. Login Card

Penguin UI: card/

# Location What it is Size
70 assets/views/admin/login.html:6-61 Full login form: header with auth badge, email + password inputs, error alert, submit button ~56 lines

24. Checkout Fieldset Cards

Penguin UI: card/

# Location What it is Size
71 assets/views/shop/checkout.html:34-79 Contact info fieldset (email, name, phone+prefix) ~46 lines
72 assets/views/shop/checkout.html:82-130 Shipping address fieldset (address, city, zip, country) ~49 lines
73 assets/views/shop/checkout.html:133-165 Carrier selection fieldset ~33 lines
74 assets/views/shop/checkout.html:167-180 Payment method fieldset ~14 lines

Each fieldset uses <fieldset> + <legend> with the same rounded-radius border border-outline bg-surface p-6 styling.


25. Order Detail Info Panel

Penguin UI: card/

# Location What it is Size
75 assets/views/admin/orders/show.html:49-77 Customer + shipping + payment info panel ~29 lines
76 assets/views/admin/orders/show.html:79-103 Fulfillment panel (tracking, label link, ship button) ~25 lines
77 assets/views/admin/orders/show.html:106-115 Status update form panel ~10 lines

26. Shipping Method Settings Row

Penguin UI: card/

# Location What it is Size
78 assets/views/admin/shipping/index.html:14-34 Per-carrier settings: name label, price input, enabled checkbox, save button ~21 lines

27. Product/Category Form Wrapper

Penguin UI: card/

# Location What it is Size
79 assets/views/admin/catalog/product_form.html:15-99 Full product edit/create form with all fields ~84 lines
80 assets/views/admin/catalog/category_form.html:15-81 Full category edit/create form with all fields ~66 lines

Both are wrapped in a single card-style <form>.


Summary

# Component Penguin UI Directory Handcoded Instances Total Lines
1 Navbar navbar/ 2 ~143
2 Sidebar (admin) sidebar/ 2 ~46
3 Sidebar (category accordion) sidebar/ 2 ~62
4 Dropdown (settings) dropdown-menu/ 2 duplicates ~103
5 Country/Phone combobox text-input/ 2 ~51
6 Product card card/ 1 ~30
7 Image gallery carousel/ 1 ~19
8 Radio groups (form inputs) 2 ~47
9 Checkbox checkbox/ 3 ~15
10 Text input text-input/ 8 ~146
11 Textarea textarea/ 3 ~10
12 Select select/ 3 ~23
13 File input file-input/ 2 ~12
14 Table table/ 5 ~196
15 Alert/Error alert/ 2 ~9
16 Badge/Pill badge/ 6 ~17
17 Button buttons/ 50+ occurrences ~200+
18 Toggle (theme) toggle/ 2 duplicates ~36
19 Inline SVG icons N/A 8 distinct icons ~50
20 Empty state (table variants) 5 ~22
21 Dashboard cards card/ 1 ~16
22 Checkout summary card/ 1 ~29
23 Login card card/ 1 ~56
24 Checkout fieldsets card/ 4 ~142
25 Order info panels card/ 3 ~64
26 Shipping settings row card/ 1 ~21
27 Form wrappers card/ 2 ~150

Grand total: ~27 distinct handcoded UI component types across ~80 instances, representing approximately 1,600+ lines of handcoded HTML/Tailwind/Alpine that could be replaced by Penguin UI components.

Duplication hotspots:

  • Settings dropdown (base.html:110-162 and admin/base.html:117-166) — 100% copy-paste
  • Theme toggle JS (base.html:13-30 and admin/base.html:13-30) — 100% copy-paste
  • Text input class string — same 80-character Tailwind string appears 15+ times
  • Table class strings (thead, tbody, tr) — copy-pasted 5 times
  • Button variants — inconsistent hover:opacity-75 vs hover:opacity-90