490 lines
22 KiB
Markdown
490 lines
22 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
|
||
|
||
When a Penguin UI component can replace a handcoded one, we vendor its source
|
||
and then use it (instead of hand-rolling):
|
||
|
||
1. Copy the component's source **byte-for-byte** from the [Penguin UI repo](https://github.com/SalarHoushvand/penguinui-components/tree/main)
|
||
into `assets/views/penguinui/`, **mirroring the upstream repo hierarchy**
|
||
(e.g. `toast-notification/stacking-toast-notification.html`). This directory
|
||
is reserved exclusively for vendored Penguin UI components and is kept an
|
||
**exact, unmodified mirror** of upstream — demo triggers, bugs and all. It's
|
||
a reference, not the rendered markup.
|
||
2. Adapt it where it's actually used (strip docs-only demo triggers, fix obvious
|
||
upstream bugs, wire data bindings). Note the deviations in a comment next to
|
||
the adapted copy.
|
||
3. Rebuild Tailwind (`make css`) so any new utility classes get compiled.
|
||
4. Mark the section below as ✅ **DONE**.
|
||
|
||
---
|
||
|
||
## 0. Toast — ✅ DONE
|
||
|
||
**Penguin UI: `toast-notification/stacking-toast-notification.html`**
|
||
|
||
- Exact upstream mirror at `assets/views/penguinui/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 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 `assets/views/penguinui/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 `assets/views/penguinui/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 `assets/views/penguinui/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 `assets/views/penguinui/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
|
||
**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
|
||
**Penguin UI: `checkbox/` (3 variants)**
|
||
|
||
| # | Location | What it is | Size |
|
||
|---|----------|------------|------|
|
||
| 15 | `assets/views/admin/catalog/product_form.html:85-89` | "Published" checkbox | ~5 lines |
|
||
| 16 | `assets/views/admin/catalog/category_form.html:67-71` | "Published" checkbox | ~5 lines |
|
||
| 17 | `assets/views/admin/shipping/index.html:25-29` | "Enabled" checkbox | ~5 lines |
|
||
|
||
---
|
||
|
||
## 10. Text Input
|
||
**Penguin UI: `text-input/` (8 variants)**
|
||
|
||
| # | Location | What it is | Size |
|
||
|---|----------|------------|------|
|
||
| 18 | `assets/views/shop/checkout.html:37-44` | Email + name text inputs | ~8 lines |
|
||
| 19 | `assets/views/shop/checkout.html:84-99` | Address, city, ZIP text inputs | ~16 lines |
|
||
| 20 | `assets/views/admin/login.html:34-51` | Email + password inputs (with focus ring styles) | ~18 lines |
|
||
| 21 | `assets/views/admin/catalog/product_form.html:19-68` | Name, price, currency, stock, SKU, slug inputs + textarea | ~50 lines |
|
||
| 22 | `assets/views/admin/catalog/category_form.html:19-55` | Name, slug, position inputs + textarea | ~37 lines |
|
||
| 23 | `assets/views/shop/show.html:46-48` | Quantity number input | ~3 lines |
|
||
| 24 | `assets/views/shop/_cart_body.html:30-38` | Quantity number input with `@change` confirmation dialog | ~9 lines |
|
||
| 25 | `assets/views/admin/shipping/index.html:20-24` | Price text input | ~5 lines |
|
||
|
||
**Pattern: Every input is hand-styled with:**
|
||
```
|
||
w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface
|
||
focus:outline-2 focus:outline-primary
|
||
dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark
|
||
```
|
||
This exact class string appears 15+ times across the codebase.
|
||
|
||
---
|
||
|
||
## 11. Textarea
|
||
**Penguin UI: `textarea/`**
|
||
|
||
| # | Location | What it is | Size |
|
||
|---|----------|------------|------|
|
||
| 26 | `assets/views/shop/checkout.html:183-186` | Order note textarea | ~4 lines |
|
||
| 27 | `assets/views/admin/catalog/product_form.html:71-73` | Product description textarea | ~3 lines |
|
||
| 28 | `assets/views/admin/catalog/category_form.html:53-55` | Category description textarea | ~3 lines |
|
||
|
||
---
|
||
|
||
## 12. Select/Dropdown (Native)
|
||
**Penguin UI: `select/` (7 variants)**
|
||
|
||
| # | Location | What it is | Size |
|
||
|---|----------|------------|------|
|
||
| 29 | `assets/views/admin/catalog/product_form.html:53-60` | Category select | ~8 lines |
|
||
| 30 | `assets/views/admin/catalog/category_form.html:40-49` | Parent category select (tree indented with `— `) | ~10 lines |
|
||
| 31 | `assets/views/admin/orders/show.html:108-112` | Order status select | ~5 lines |
|
||
|
||
---
|
||
|
||
## 13. File Input
|
||
**Penguin UI: `file-input/`**
|
||
|
||
| # | Location | What it is | Size |
|
||
|---|----------|------------|------|
|
||
| 32 | `assets/views/admin/catalog/product_form.html:78-82` | Product image upload | ~5 lines |
|
||
| 33 | `assets/views/admin/catalog/category_form.html:58-64` | Category image upload | ~7 lines |
|
||
|
||
**Both use the same Tailwind `file:mr-3 file:...` prefix pattern for styling.**
|
||
|
||
---
|
||
|
||
## 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 `assets/views/penguinui/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 `assets/views/penguinui/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 `assets/views/penguinui/buttons/*.html` (reference only).
|
||
- One macro `ui::button(label, variant="primary", type, href, attrs, extra, pad="px-4 py-2")`
|
||
in `assets/views/macros/ui.html`. 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` is added
|
||
so `<a>`/`w-full` 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).
|
||
- **Sizes are NOT normalized**: `pad` defaults to Penguin's `px-4 py-2` but each
|
||
call site that was a different size 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).
|
||
- 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.
|
||
- Left inline (these are other Penguin variants/components, not this button):
|
||
icon-only ghost buttons (#51 gear/hamburger/chevron), compact table row-action
|
||
buttons (`edit`/`view`/`delete`, `px-3 py-1.5 text-xs`), the `text-danger`
|
||
"Remove" text link (#52), the file-input button (#13), and nav menu links.
|
||
|
||
> 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
|
||
**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`
|