630 lines
31 KiB
Markdown
630 lines
31 KiB
Markdown
# 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 4–5 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 — ✅ DONE
|
||
**Penguin UI: `table/default-table.html`**
|
||
|
||
- Exact upstream mirror at `penguinui-components/table/default-table.html` (reference only)
|
||
- The same wrapper/thead/tbody/row/tfoot class structure was copy-pasted across all
|
||
5 tables (orders index, order detail, products, categories, cart body). Centralized
|
||
into **class-string macros** in `macros/ui.html`: `ui::table_wrap_cls()`,
|
||
`ui::table_cls()`, `ui::thead_cls()`, `ui::tbody_cls()`, `ui::row_cls()` (hover),
|
||
`ui::tfoot_cls()`, plus an element macro `ui::th(label, align="")` for header cells.
|
||
- **Why class-string macros, not full row macros:** Tera has no slot/`{% raw %}{% call %}{% endraw %}`
|
||
mechanism, and the cells are heterogeneous (product image+name, htmx quantity input
|
||
with inline Alpine `@change`, badges, action-button forms), so rows stay inline. The
|
||
macros centralize only the drift-prone chrome styling — `class="{{ ui::thead_cls() }}"`.
|
||
- **Penguin improvement adopted:** the wrapper now carries `w-full overflow-x-auto`
|
||
(from `default-table.html`) so wide tables scroll horizontally on mobile instead of
|
||
overflowing. Our `text-xs uppercase` thead + `px-4 py-3` cells were kept (deliberate,
|
||
richer than Penguin's `text-sm`/`p-4`).
|
||
- Interactive lists (orders/products/categories) use `ui::row_cls()` for the hover
|
||
highlight; non-interactive rows (order items, cart) omit it. `tfoot` (order detail +
|
||
cart totals) uses `ui::tfoot_cls()`.
|
||
|
||
| # | Location | What it is |
|
||
|---|----------|------------|
|
||
| 34 | `assets/views/admin/orders/index.html` | Orders table |
|
||
| 35 | `assets/views/admin/orders/show.html` | Order items table + tfoot summary |
|
||
| 36 | `assets/views/admin/catalog/products.html` | Products table (image+name, status pill, actions) |
|
||
| 37 | `assets/views/admin/catalog/categories.html` | Categories table (tree-indented, actions) |
|
||
| 38 | `assets/views/shop/_cart_body.html` | Cart table (htmx qty input, remove) + tfoot total |
|
||
|
||
---
|
||
|
||
## 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~~ | ✅ DONE — `table/default-table.html` (class-string macros) | 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 (16 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 |
|
||
| 14 | Table |
|
||
| 15 | Alert / Error Banner |
|
||
| 16 | Badge / Status Pill |
|
||
| 17 | Buttons |
|
||
|
||
**Remaining real port: just #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/` | ✅ DONE | ~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: 16 of 27 components fully ported to Penguin UI. Only 1 real port
|
||
remains — #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.**
|