navbar is now penguinui
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -106,7 +106,16 @@
|
||||
<!-- content column -->
|
||||
<div class="flex min-h-screen flex-col md:ml-60">
|
||||
<header class="sticky top-0 z-20 flex h-16 items-center gap-4 border-b border-outline bg-surface/95 px-4 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
||||
{{ ui::icon_button(aria_label=t(key='menu', lang=lang | default(value='sk')), attrs='@click="showSidebar = !showSidebar" :aria-expanded="showSidebar"', extra="md:hidden", icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>') }}
|
||||
<!-- Penguin animated hamburger (bars ↔ X) in our ghost-square shell -->
|
||||
<button type="button" @click="showSidebar = !showSidebar" :aria-expanded="showSidebar" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 shrink-0 items-center justify-center rounded-radius bg-transparent text-secondary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 md:hidden dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">
|
||||
<svg x-show="!showSidebar" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
<svg x-cloak x-show="showSidebar" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<span class="text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{% block crumb %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock crumb %}
|
||||
|
||||
@@ -73,19 +73,19 @@
|
||||
{{ t(key="brand", lang=lang | default(value='sk')) }}
|
||||
</a>
|
||||
|
||||
<!-- desktop links -->
|
||||
<ul class="ml-2 hidden items-center gap-1 md:flex">
|
||||
<li><a href="/" data-nav="/" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/shop" data-nav="/shop" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</a></li>
|
||||
<!-- desktop links — Penguin navbar link treatment via ui::nav_link -->
|
||||
<ul class="ml-2 hidden items-center gap-6 md:flex">
|
||||
<li>{{ ui::nav_link(label=t(key="nav-home", lang=lang | default(value='sk')), href="/", data_nav="/") }}</li>
|
||||
<li>{{ ui::nav_link(label=t(key="nav-shop", lang=lang | default(value='sk')), href="/shop", data_nav="/shop") }}</li>
|
||||
{% if logged_in_admin %}
|
||||
<li><a href="/admin/dashboard" hx-boost="false" data-nav="/admin" class="rounded-radius px-3 py-1.5 text-sm font-medium text-warning transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>{{ ui::nav_link(label=t(key="admin-title", lang=lang | default(value='sk')), href="/admin/dashboard", data_nav="/admin", variant="warning", attrs='hx-boost="false"') }}</li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout" hx-boost="false">
|
||||
<button type="submit" class="rounded-radius px-3 py-1.5 text-sm font-medium text-danger transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
<button type="submit" class="text-sm font-medium text-danger underline-offset-2 transition hover:opacity-75 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/admin/login" data-nav="/admin/login" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>{{ ui::nav_link(label=t(key="nav-admin", lang=lang | default(value='sk')), href="/admin/login", data_nav="/admin/login") }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@@ -109,24 +109,34 @@
|
||||
{% include "partials/settings_dropdown.html" %}
|
||||
</div>
|
||||
|
||||
<!-- mobile hamburger -->
|
||||
{{ ui::icon_button(aria_label=t(key='menu', lang=lang | default(value='sk')), attrs='@click="mobile = !mobile" :aria-expanded="mobile"', extra="md:hidden", icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>') }}
|
||||
<!-- mobile hamburger — Penguin animated icon swap (bars ↔ X), kept in
|
||||
our ghost-square icon-button shell for consistency with cart/gear -->
|
||||
<button type="button" @click="mobile = !mobile" :aria-expanded="mobile" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 shrink-0 items-center justify-center rounded-radius bg-transparent text-secondary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 md:hidden dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">
|
||||
<svg x-show="!mobile" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
<svg x-cloak x-show="mobile" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- mobile menu panel -->
|
||||
<!-- mobile menu panel — Penguin sidebar-style menu rows (hover:bg-primary/5,
|
||||
underline focus), active state via data-nav + markActiveNav() -->
|
||||
<ul x-show="mobile" x-cloak @click.outside="mobile = false" x-transition
|
||||
class="absolute inset-x-0 top-full mx-4 mt-2 flex flex-col gap-1 rounded-radius border border-outline bg-surface p-2 shadow-lg md:hidden dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<li><a href="/" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/shop" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/" data-nav="/" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/shop" data-nav="/shop" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% if logged_in_admin %}
|
||||
<li><a href="/admin/dashboard" hx-boost="false" class="block rounded-radius px-3 py-2 text-sm font-medium text-warning hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/dashboard" hx-boost="false" data-nav="/admin" class="block rounded-radius px-3 py-2 text-sm font-medium text-warning underline-offset-2 transition hover:bg-primary/5 focus:outline-hidden focus-visible:underline">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout" hx-boost="false">
|
||||
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger underline-offset-2 transition hover:bg-primary/5 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/admin/login" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/login" data-nav="/admin/login" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -176,3 +176,16 @@ border-t border-outline dark:border-outline-dark
|
||||
{% macro th(label, align="") -%}
|
||||
<th class="px-4 py-3 font-semibold{% if align %} {{ align }}{% endif %}">{{ label }}</th>
|
||||
{%- endmacro th %}
|
||||
|
||||
{# Top-nav link. Penguin navbar/default-navbar.html link treatment: text-only,
|
||||
underline on focus, hover:text-primary, active (aria-current=page, set by
|
||||
markActiveNav() via data-nav) = font-semibold + primary. Matches the ported
|
||||
sidebars. variant ∈ default | warning (admin) | danger (logout-style links).
|
||||
Logout itself stays an inline <form><button> (not an <a>, so not this macro). #}
|
||||
{% macro nav_link(label, href, data_nav="", variant="default", attrs="") -%}
|
||||
{%- if variant == "warning" -%}{% set c = "text-warning hover:opacity-75 dark:text-warning" -%}
|
||||
{%- elif variant == "danger" -%}{% set c = "text-danger hover:opacity-75 dark:text-danger" -%}
|
||||
{%- else -%}{% set c = "text-on-surface hover:text-primary aria-[current=page]:font-semibold aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark" -%}
|
||||
{%- endif -%}
|
||||
<a href="{{ href }}"{% if data_nav %} data-nav="{{ data_nav }}"{% endif %} class="text-sm font-medium underline-offset-2 transition focus:outline-hidden focus-visible:underline {{ c }}" {{ attrs | safe }}>{{ label }}</a>
|
||||
{%- endmacro nav_link %}
|
||||
|
||||
@@ -50,29 +50,35 @@ from the build. If the build ever balloons, check that exclusion is intact.
|
||||
|
||||
---
|
||||
|
||||
## 1. Navbar — PENDING
|
||||
**Penguin UI: `navbar/`**
|
||||
## 1. Navbar — ✅ DONE
|
||||
**Penguin UI: `navbar/default-navbar.html`**
|
||||
|
||||
> **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.
|
||||
- Exact upstream mirror at `penguinui-components/navbar/default-navbar.html` (reference only)
|
||||
- **Link treatment** adopted from Penguin (matches the already-ported sidebars): the
|
||||
desktop nav links lost the pill-hover (`hover:bg-surface-alt` + `px-3 py-1.5`) for
|
||||
Penguin's text-only `underline-offset-2 hover:text-primary focus-visible:underline
|
||||
focus:outline-hidden`, active (`aria-current=page`, set by `markActiveNav()` via
|
||||
`data-nav`) = `font-semibold` + primary. Centralized into a `ui::nav_link(label,
|
||||
href, data_nav, variant, attrs)` macro in `macros/ui.html` (variant ∈ default |
|
||||
warning admin | danger). Logout stays an inline `<form><button>` (not an `<a>`).
|
||||
- **Hamburger animation** adopted: both the site mobile-menu button and the admin
|
||||
sidebar toggle now swap bars ↔ X (`x-show="!open"`/`x-show="open"`, Penguin X-path
|
||||
`M6 18 18 6M6 6l12 12`), kept inside our ghost-square icon-button shell for
|
||||
consistency with the cart/gear buttons.
|
||||
- **Mobile menu panel**: kept our compact dropdown (better for this app's dense top
|
||||
bar than Penguin's full-screen `fixed inset-x-0 top-0 pt-20` overlay, which would
|
||||
cover the cart/settings/category-toggle). Items now use the sidebar menu-row
|
||||
treatment (`hover:bg-primary/5`, underline focus) + `data-nav` so they show the
|
||||
active state too.
|
||||
- **Preserved intact** (the integration risks flagged here): cart icon + live
|
||||
cookie-read badge, the `partials/settings_dropdown.html` include (language switcher
|
||||
+ theme tristate), the mobile category-drawer toggle, and all Alpine toggles
|
||||
(`mobile`, `cats`, `showSidebar`).
|
||||
|
||||
| # | 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`
|
||||
| # | Location | What it is |
|
||||
|---|----------|------------|
|
||||
| 1 | `assets/views/base.html` | Full site navbar (brand, links, cart badge, settings, mobile menu) |
|
||||
| 2 | `assets/views/admin/base.html` | Admin top bar: animated hamburger + breadcrumb + settings |
|
||||
|
||||
---
|
||||
|
||||
@@ -546,7 +552,7 @@ Both are wrapped in a single card-style `<form>`.
|
||||
|
||||
| # | Component | Penguin match | Est. effort | Lines saved |
|
||||
|---|-----------|---------------|-------------|-------------|
|
||||
| 1 | Navbar | `navbar/default-navbar.html` | Large | ~143 |
|
||||
| ~~1~~ | ~~Navbar~~ | ✅ DONE — `navbar/default-navbar.html` (link treatment + animated hamburger; `ui::nav_link`) | Large | ~143 |
|
||||
|
||||
### Phase 3 — LOW (mostly already Penguin, or no good match)
|
||||
|
||||
@@ -563,11 +569,12 @@ Both are wrapped in a single card-style `<form>`.
|
||||
| 26 | Shipping Settings Row | Already fully uses Penguin macros |
|
||||
| 27 | Form Wrappers | Already fully uses Penguin macros |
|
||||
|
||||
### Already DONE (16 of 27)
|
||||
### Already DONE (17 of 27)
|
||||
|
||||
| # | Component |
|
||||
|---|-----------|
|
||||
| 0 | Toast |
|
||||
| 1 | Navbar |
|
||||
| 2 | Sidebar (Admin) |
|
||||
| 3 | Sidebar (Category Accordion) |
|
||||
| 4 | Dropdown (Settings) |
|
||||
@@ -584,9 +591,9 @@ Both are wrapped in a single card-style `<form>`.
|
||||
| 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.
|
||||
**No real ports remain.** #5 Combobox is a conscious WON'T-PORT (Alpine Focus
|
||||
plugin dependency). All Phase-3 items (#18–27) are already internally
|
||||
Penguin-adapted or have no applicable component — leave as-is.
|
||||
|
||||
---
|
||||
|
||||
@@ -595,7 +602,7 @@ already internally Penguin-adapted or have no applicable component.
|
||||
| # | Component | Penguin UI Directory | Status | Lines |
|
||||
|---|-----------|---------------------|--------|-------|
|
||||
| 0 | Toast | `toast-notification/` | ✅ DONE | — |
|
||||
| 1 | Navbar | `navbar/` | PENDING (MED) | ~143 |
|
||||
| 1 | Navbar | `navbar/` | ✅ DONE | ~143 |
|
||||
| 2 | Sidebar (admin) | `sidebar/` | ✅ DONE | ~46 |
|
||||
| 3 | Sidebar (category accordion) | `sidebar/` | ✅ DONE | ~62 |
|
||||
| 4 | Dropdown (settings) | `dropdowns/` | ✅ DONE | ~103 |
|
||||
@@ -623,7 +630,7 @@ already internally Penguin-adapted or have no applicable component.
|
||||
| 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.**
|
||||
**Status: 17 of 27 components fully ported to Penguin UI. No real ports remain.
|
||||
#5 Combobox is a conscious WON'T-PORT (Alpine Focus plugin dependency). The
|
||||
remaining Phase-3 items (#18–27) are already internally Penguin-adapted or have
|
||||
no applicable match — the migration is effectively complete.**
|
||||
|
||||
Reference in New Issue
Block a user