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

626 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handcoded UI Components — Penguin UI Replacement Index
> **Scope**: Every handcoded UI component.
> Each item maps to a [Penguin UI](https://github.com/SalarHoushvand/penguinui-components/tree/main) 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 — PENDING
**Penguin UI: `navbar/`**
> **Priority: MEDIUM** | ~143 lines across2 sites.
> Penguin match: `navbar/default-navbar.html` — provides mobile-responsive structure,
> hamburger animation, and link treatment that would replace our hand-rolled `<nav>`.
> **Watch out:** tight integration needed for cart badge, settings dropdown partial,
> language switcher, and Alpine mobile toggle — don't lose any of those.
| # | 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 — ⛔ WON'T PORT (conscious deviation)
**Penguin UI: `combobox/phone-number-input-with-country-code-dropdown.html`**
> **Decision 2026-06-18:** keep our lightweight hand-rolled comboboxes; do NOT
> port the Penguin one. The Penguin combobox depends on the **Alpine Focus
> plugin** (`x-trap`, `$focus.wrap().next()`) which this build does not bundle
> (same reason the settings dropdown & category accordion deviate), ships a
> 240-country `allOptions` list + `flagcdn.com` remote flag images, and a search
> field — far heavier than our deliberate 9-prefix / 6-country editable inputs.
> Our versions already use the Penguin design tokens (`bg-surface`, `border-outline`,
> `focus:outline-primary`) and emoji flags, so they look on-brand. Net: porting
> would add a JS dependency and external image loads for negative UX value.
> Revisit only if we adopt the Alpine Focus plugin project-wide.
| # | 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.
---
## 7. Product Image Gallery — ✅ DONE
**Penguin UI: `carousel/default-carousel.html`**
- Exact upstream mirror at `penguinui-components/carousel/default-carousel.html` (reference only)
- Adapted at use-site in `assets/views/shop/show.html`: added Penguin's overlay
prev/next arrow buttons (`bg-surface/40` rounded, verbatim chevron SVGs) and
`x-transition.opacity.duration.300ms` fade between images. Added `prev()`/`next()`
with wraparound to the gallery `x-data`; arrows + transitions only render when
`images | length > 1`.
- Deviations: kept our **product thumbnail strip** (more useful than carousel
dot indicators for a product page) and our **0-based `active`** index (Penguin
uses 1-based `currentSlideIndex`); main images switched to `absolute inset-0`
so the fade cross-dissolves inside the `aspect-square` frame. New i18n keys
`gallery-prev`/`gallery-next` (sk + en) for the arrow `aria-label`s.
---
## 8. Radio-Button Groups — ✅ DONE
**Penguin UI: `radio/radio-with-container.html`**
- Exact upstream mirror at `penguinui-components/radio/radio-with-container.html` (reference only)
- New `ui::radio(name, value, id, checked, attrs, extra)` macro in `macros/ui.html`
emits **only** the Penguin custom radio-dot `<input>` (verbatim `appearance-none`
+ `before:` dot + `checked:bg-primary`). Callers keep their own card-style
`<label>` wrapper — we kept our `has-[:checked]:border-primary` card highlight,
which is richer than Penguin's plain `bg-surface-alt` container.
- Adopted at `shop/checkout.html`: both **payment** radios (`ui::radio` with
`attrs='required x-model="paymentMethod"'`). The **carrier** radio (in the
`{% for m in shipping_methods %}` loop) keeps the same Penguin dot class **inline**
because its `@change="carrier='{{ m.code }}'; …"` mixes nested single+double
quotes that can't pass through a Tera macro arg (same convention as the cart
qty input). Native `text-primary` radios are gone.
---
## 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 — PENDING
**Penguin UI: `table/` (5 variants)**
> **Priority: HIGH** | ~196 lines across5 instances.
> The same class structure is copy-pasted5 times. Penguin match:
> `table/default-table.html` for basic tables, `table-with-action.html` for
> row actions. Consider a `ui::table_header()` + `ui::table_row()` macro.
| # | 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.html` →
> `shop/_cart_body.html`). In an `{% extends %}` child the import must sit
> directly after `{% extends %}` with no comment/content before it.
---
## 18. Toggle / Switch — LOW PRIORITY (de-duplication, not replacement)
**Penguin UI: `toggle/` (3 variants)**
> **Priority: LOW** | ~36 lines,100% duplicated between `base.html` and `admin/base.html`.
> This is JavaScript theme-switching logic (`applyTheme`, `setTheme`, `matchMedia`),
> not a CSS toggle component. Penguin's `toggle/default-toggle.html` is a visual
> on/off switch — not applicable here.
> **Action:** de-duplicate the JS into a shared partial rather than porting.
| # | 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 — LOW PRIORITY
**Penguin UI: none (Penguin uses Heroicons-equivalent inline SVGs)**
> **Priority: LOW** | ~50 lines,8 distinct icons.
> Penguin doesn't provide an icon library. Icons are already inlined where used.
> Possible follow-up: extract into a `ui::icon(name)` macro for dedup, but
> this is purely cosmetic — no functional gain.
| # | 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 — LOW PRIORITY
**Penguin UI: no dedicated empty-state component**
> **Priority: LOW** | ~22 lines across5 sites.
> These are simple `<div>` messages, often with a CTA button already using
> `ui::button`. Nothing to port — already consistent with project styling.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/default-card.html` or `card/card-with-button.html`**
> **Priority: LOW** | ~16 lines.
> Already uses card styling (`rounded-radius border border-outline hover:border-primary`).
> Penguin's `default-card.html` adds a structured header/body layout — adopt if
> cards ever grow beyond a title+description link.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/`**
> **Priority: LOW** | ~29 lines.
> Already uses card-like styling with `tabular-nums`. All internal buttons use
> `ui::button`. The only handcoded part is the outer `<div>` wrapper and line-item
> layout. Penguin doesn't have an ecommerce-specific summary component.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/default-card.html`**
> **Priority: LOW** | ~56 lines.
> Already fully uses Penguin macros inside: `ui::input`, `ui::button`,
> `ui::badge`, `ui::alert_danger`. Only the outer card wrapper (border,
> bg-surface-alt, shadow-sm) is handcoded. Adopting `default-card.html`
> would add visual polish but little functional gain.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/`**
> **Priority: LOW** | ~142 lines across4 fieldsets.
> Already uses card styling (`rounded-radius border border-outline bg-surface p-6`)
> and all internal form controls use Penguin macros (`ui::input`, `ui::textarea`,
> `ui::button`). Only the `<fieldset>` + `<legend>` wrapping is handcoded.
> Low value in replacing — fieldset semantics are correct here.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/`**
> **Priority: LOW** | ~64 lines across3 panels.
> Already card-styled (`rounded-radius border border-outline bg-surface p-5`)
> with Penguin macros inside. Deviating from this simple structure would
> make the dense info layout harder to scan.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/`**
> **Priority: LOW** | ~21 lines.
> Already fully uses Penguin macros: `ui::input`, `ui::checkbox`, `ui::button`.
> The card wrapper is the same pattern as other admin panels. Nothing to port.
| # | 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 — LOW PRIORITY
**Penguin UI: `card/`**
> **Priority: LOW** | ~150 lines across2 forms.
> Already fully uses Penguin macros: `ui::input`, `ui::textarea`, `ui::select`,
> `ui::file_input`, `ui::checkbox`, `ui::button`. The `<form>` card wrapper is
> the same border/bg pattern. Penguin doesn't have a form-specific layout component.
| # | 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>`.
---
## Porting Roadmap (priority order)
### Phase 1 — HIGH (direct Penguin matches, clear win)
| # | Component | Penguin match | Est. effort | Lines saved |
|---|-----------|---------------|-------------|-------------|
| ~~5~~ | ~~Country/Phone Combobox~~ | ⛔ WON'T PORT — needs Alpine Focus plugin; our lightweight version kept | — | — |
| ~~7~~ | ~~Image Gallery~~ | ✅ DONE — `carousel/default-carousel.html` | Small | ~19 |
| ~~8~~ | ~~Radio Groups~~ | ✅ DONE — `radio/radio-with-container.html` | Small | ~47 |
| 14 | Table | `table/default-table.html` | Medium | ~196 |
### Phase 2 — MEDIUM (good match, more integration risk)
| # | Component | Penguin match | Est. effort | Lines saved |
|---|-----------|---------------|-------------|-------------|
| 1 | Navbar | `navbar/default-navbar.html` | Large | ~143 |
### Phase 3 — LOW (mostly already Penguin, or no good match)
| # | Component | Action |
|---|-----------|--------|
| 18 | Toggle/Switch | De-duplicate JS into shared partial (not a Penguin port) |
| 19 | Inline SVG Icons | Optional: extract `ui::icon(name)` macro |
| 20 | Empty State | Already fine — nothing to port |
| 21 | Dashboard Cards | Adopt `card/default-card.html` if cards grow |
| 22 | Checkout Summary | Already fine — nothing to port |
| 23 | Login Card | Already fine — only outer wrapper is handcoded |
| 24 | Checkout Fieldsets | Already fine — only `<fieldset>` wrapper is handcoded |
| 25 | Order Info Panels | Already fine — only card wrappers are handcoded |
| 26 | Shipping Settings Row | Already fully uses Penguin macros |
| 27 | Form Wrappers | Already fully uses Penguin macros |
### Already DONE (15 of 27)
| # | Component |
|---|-----------|
| 0 | Toast |
| 2 | Sidebar (Admin) |
| 3 | Sidebar (Category Accordion) |
| 4 | Dropdown (Settings) |
| 6 | Product Card |
| 7 | Image Gallery |
| 8 | Radio Groups |
| 9 | Checkbox |
| 10 | Text Input |
| 11 | Textarea |
| 12 | Select/Dropdown |
| 13 | File Input |
| 15 | Alert / Error Banner |
| 16 | Badge / Status Pill |
| 17 | Buttons |
**Remaining real ports: just #14 Table (~196 lines, Medium) and #1 Navbar
(~143 lines, Large).** #5 Combobox is a conscious WON'T-PORT (Alpine Focus
plugin dependency). The Phase-3 items are already internally Penguin-adapted
or have no applicable component.
---
## Summary
| # | Component | Penguin UI Directory | Status | Lines |
|---|-----------|---------------------|--------|-------|
| 0 | Toast | `toast-notification/` | ✅ DONE | — |
| 1 | Navbar | `navbar/` | PENDING (MED) | ~143 |
| 2 | Sidebar (admin) | `sidebar/` | ✅ DONE | ~46 |
| 3 | Sidebar (category accordion) | `sidebar/` | ✅ DONE | ~62 |
| 4 | Dropdown (settings) | `dropdowns/` | ✅ DONE | ~103 |
| 5 | Country/Phone combobox | `combobox/` | ⛔ WON'T PORT | ~51 |
| 6 | Product card | `card/` | ✅ DONE | ~30 |
| 7 | Image gallery | `carousel/` | ✅ DONE | ~19 |
| 8 | Radio groups | `radio/` | ✅ DONE | ~47 |
| 9 | Checkbox | `checkbox/` | ✅ DONE | ~15 |
| 10 | Text input | `text-input/` | ✅ DONE | ~146 |
| 11 | Textarea | `text-area/` | ✅ DONE | ~10 |
| 12 | Select | `select/` | ✅ DONE | ~23 |
| 13 | File input | `file-input/` | ✅ DONE | ~12 |
| 14 | Table | `table/` | **HIGH** | ~196 |
| 15 | Alert/Error | `alert/` | ✅ DONE | ~9 |
| 16 | Badge/Pill | `badge/` | ✅ DONE | ~17 |
| 17 | Button | `buttons/` | ✅ DONE | ~200+ |
| 18 | Toggle (theme) | `toggle/` | LOW (dedup) | ~36 |
| 19 | Inline SVG icons | N/A | LOW | ~50 |
| 20 | Empty state | N/A | LOW | ~22 |
| 21 | Dashboard cards | `card/` | LOW | ~16 |
| 22 | Checkout summary | `card/` | LOW | ~29 |
| 23 | Login card | `card/` | LOW | ~56 |
| 24 | Checkout fieldsets | `card/` | LOW | ~142 |
| 25 | Order info panels | `card/` | LOW | ~64 |
| 26 | Shipping settings row | `card/` | LOW | ~21 |
| 27 | Form wrappers | `card/` | LOW | ~150 |
**Status: 15 of 27 components fully ported to Penguin UI. Only 2 real ports
remain — #14 Table (HIGH) and #1 Navbar (MED). #5 Combobox is a conscious
WON'T-PORT (Alpine Focus plugin dependency). The remaining Phase-3 items are
already internally Penguin-adapted or have no applicable match.**