porting to the use of penguinui

This commit is contained in:
Priec
2026-06-18 11:08:10 +02:00
parent 9a3c68eae5
commit e8c6035eeb
7 changed files with 256 additions and 93 deletions

View File

@@ -50,9 +50,15 @@ from the build. If the build ever balloons, check that exclusion is intact.
---
## 1. Navbar
## 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 |
@@ -117,8 +123,19 @@ from the build. If the build ever balloons, check that exclusion is intact.
---
## 5. Country / Phone Combobox
**Penguin UI: `text-input/` + custom dropdown list**
## 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 |
|---|----------|------------|------|
@@ -149,37 +166,38 @@ from the build. If the build ever balloons, check that exclusion is intact.
---
## 7. Product Image Gallery
**Penguin UI: `carousel/` (3 variants)**
## 7. Product Image Gallery — ✅ DONE
**Penguin UI: `carousel/default-carousel.html`**
| # | 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
- 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
**Penguin UI: radio (part of form inputs)**
## 8. Radio-Button Groups — ✅ DONE
**Penguin UI: `radio/radio-with-container.html`**
| # | 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
- 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.
---
@@ -229,8 +247,13 @@ from the build. If the build ever balloons, check that exclusion is intact.
---
## 14. Table
**Penguin UI: `table/` (7 variants)**
## 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 |
|---|----------|------------|------|
@@ -324,8 +347,14 @@ This is copy-pasted 5 times.
---
## 18. Toggle / Switch
**Penguin UI: `toggle/` (2 variants)**
## 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 |
|---|----------|------------|------|
@@ -339,9 +368,14 @@ This is copy-pasted 5 times.
---
## 19. Inline SVG Icons
## 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 |
@@ -357,8 +391,12 @@ All are raw inline `<svg>` with hardcoded `<path d="...">` — no icon library,
---
## 20. Empty State
**Penguin UI: no direct component, but table empty states exist in Penguin tables**
## 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 |
|---|----------|------------|------|
@@ -370,8 +408,13 @@ All are raw inline `<svg>` with hardcoded `<path d="...">` — no icon library,
---
## 21. Dashboard Navigation Cards
**Penguin UI: `card/`**
## 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 |
|---|----------|------------|------|
@@ -383,8 +426,13 @@ All are raw inline `<svg>` with hardcoded `<path d="...">` — no icon library,
---
## 22. Checkout Order Summary
**Penguin UI: `card/` (ecommerce-summary style)**
## 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 |
|---|----------|------------|------|
@@ -398,8 +446,14 @@ All are raw inline `<svg>` with hardcoded `<path d="...">` — no icon library,
---
## 23. Login Card
**Penguin UI: `card/`**
## 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 |
|---|----------|------------|------|
@@ -407,9 +461,15 @@ All are raw inline `<svg>` with hardcoded `<path d="...">` — no icon library,
---
## 24. Checkout Fieldset Cards
## 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 |
@@ -421,9 +481,14 @@ Each fieldset uses `<fieldset>` + `<legend>` with the same `rounded-radius borde
---
## 25. Order Detail Info Panel
## 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 |
@@ -432,18 +497,27 @@ Each fieldset uses `<fieldset>` + `<legend>` with the same `rounded-radius borde
---
## 26. Shipping Method Settings Row
## 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
## 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 |
@@ -453,43 +527,99 @@ 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 | 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 |
| # | 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 |
**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`
**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.**