From f4960f2fc54af2d777899030200bf62e42693725 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Fri, 7 Aug 2026 16:47:20 +0530 Subject: [PATCH] feat(ui): build the Nav component with submenus and roving focus Replaces a scaffold that rendered bare anchors. Flat or nested to three levels, icons, badges, disabled items, aria-current on the active link, arrow-key roving focus, and a disclosure arrow that rotates on open. Three levels rather than arbitrary depth because this template language has no component recursion, so each level is written out. On a phone the bar becomes a toggle and submenus stack inline rather than floating: a hover-opened overlay cannot be reached on touch. Co-Authored-By: Claude Opus 5 --- .../scripts/showcase-profiles.mjs | 57 ++++ packages/ui/components/Nav.wrn | 312 +++++++++++++++++- packages/ui/test/ui.test.ts | 74 +++++ 3 files changed, 436 insertions(+), 7 deletions(-) diff --git a/examples/component-showcase/scripts/showcase-profiles.mjs b/examples/component-showcase/scripts/showcase-profiles.mjs index 0b6923ce..8987b29a 100644 --- a/examples/component-showcase/scripts/showcase-profiles.mjs +++ b/examples/component-showcase/scripts/showcase-profiles.mjs @@ -253,6 +253,63 @@ const DATATABLE_ROWS = '[{"id": 1, "name": "Northwind", "plan": "Scale", "owner": "A. Okafor", "seats": 6, "status": "Active", "statusHtml": "Active"}, {"id": 2, "name": "Acme Industrial", "plan": "Team", "owner": "R. Silva", "seats": 13, "status": "Trial", "statusHtml": "Trial"}, {"id": 3, "name": "Globex", "plan": "Enterprise", "owner": "M. Chen", "seats": 20, "status": "Past due", "statusHtml": "Past due"}, {"id": 4, "name": "Initech", "plan": "Starter", "owner": "J. Dubois", "seats": 27, "status": "Active", "statusHtml": "Active"}, {"id": 5, "name": "Umbrella", "plan": "Scale", "owner": "P. Novak", "seats": 34, "status": "Trial", "statusHtml": "Trial"}, {"id": 6, "name": "Stark Labs", "plan": "Team", "owner": "A. Okafor", "seats": 41, "status": "Past due", "statusHtml": "Past due"}, {"id": 7, "name": "Wayne Foods", "plan": "Enterprise", "owner": "R. Silva", "seats": 48, "status": "Active", "statusHtml": "Active"}, {"id": 8, "name": "Soylent", "plan": "Starter", "owner": "M. Chen", "seats": 55, "status": "Trial", "statusHtml": "Trial"}, {"id": 9, "name": "Hooli", "plan": "Scale", "owner": "J. Dubois", "seats": 62, "status": "Past due", "statusHtml": "Past due"}, {"id": 10, "name": "Vehement", "plan": "Team", "owner": "P. Novak", "seats": 69, "status": "Active", "statusHtml": "Active"}, {"id": 11, "name": "Massive Dynamic", "plan": "Enterprise", "owner": "A. Okafor", "seats": 76, "status": "Trial", "statusHtml": "Trial"}, {"id": 12, "name": "Cyberdyne", "plan": "Starter", "owner": "R. Silva", "seats": 83, "status": "Past due", "statusHtml": "Past due"}, {"id": 13, "name": "Tyrell", "plan": "Scale", "owner": "M. Chen", "seats": 90, "status": "Active", "statusHtml": "Active"}, {"id": 14, "name": "Aperture", "plan": "Team", "owner": "J. Dubois", "seats": 97, "status": "Trial", "statusHtml": "Trial"}, {"id": 15, "name": "Black Mesa", "plan": "Enterprise", "owner": "P. Novak", "seats": 104, "status": "Past due", "statusHtml": "Past due"}]'; export const componentProfiles = { + Nav: { + demos: [ + standard( + "Horizontal links", + "A flat link bar. The active item is marked with aria-current, and the arrow keys move between items.", + { + items: [ + { label: "Home", href: "/", value: "home", icon: "icon-[lucide--house]" }, + { label: "Inbox", href: "/inbox", value: "inbox", badge: "9" }, + { label: "Reports", href: "/reports", value: "reports" }, + { label: "Archive", href: "/archive", value: "archive", disabled: true }, + ], + active: "inbox", + }, + ), + advanced( + "Nested submenus", + "An item carrying its own items array opens a submenu on hover or focus, to a maximum of three levels. On a phone the submenus stack inline instead of floating, because a hover-opened overlay is unreachable on touch.", + { + items: [ + { label: "Home", href: "/", value: "home" }, + { + label: "Products", + value: "products", + items: [ + { label: "Overview", href: "/p", value: "p-overview" }, + { + label: "Platform", + value: "p-platform", + items: [ + { label: "Runtime", href: "/p/runtime", value: "runtime" }, + { label: "Compiler", href: "/p/compiler", value: "compiler" }, + ], + }, + ], + }, + ], + active: "p-overview", + }, + ), + standard("Vertical rail", "Vertical orientation switches the arrow keys to up and down.", { + items: [ + { label: "Dashboard", href: "/", value: "dash", icon: "icon-[lucide--gauge]" }, + { label: "Team", href: "/team", value: "team", icon: "icon-[lucide--users]" }, + { + label: "Settings", + href: "/settings", + value: "settings", + icon: "icon-[lucide--settings]", + }, + ], + active: "team", + orientation: "vertical", + collapsible: false, + }), + ], + }, Stepper: { demos: [ standard( diff --git a/packages/ui/components/Nav.wrn b/packages/ui/components/Nav.wrn index 935c0f45..52d9a481 100644 --- a/packages/ui/components/Nav.wrn +++ b/packages/ui/components/Nav.wrn @@ -1,22 +1,320 @@ +// Nav -- a navigation link list, flat or with submenus. +// +//