Skip to content

feat(ui): native-HTML rewrite of stateful primitives + shadcn-API sweep#5

Merged
vivek7405 merged 1 commit into
mainfrom
feat/ui-native-html
May 18, 2026
Merged

feat(ui): native-HTML rewrite of stateful primitives + shadcn-API sweep#5
vivek7405 merged 1 commit into
mainfrom
feat/ui-native-html

Conversation

@vivek7405
Copy link
Copy Markdown
Collaborator

Summary

  • Adopts native HTML primitives across 8 stateful UI components: <dialog>.showModal() for dialog/alert-dialog; popover attribute for tooltip/hover-card/dropdown-menu; <button popovertarget> + <div popover> + CSS Anchor Positioning for popover; <details>/<summary> (+ name=\"\") for accordion/collapsible.
  • Drops the custom elements entirely for popover, accordion, and collapsible — they become Tier-1 class-helper components on native HTML. Tier-2 count shrinks from 12 → 9.
  • Full shadcn-API parity sweep across every touched component: alignOffset everywhere, skip-delay-duration on tooltip, text-value typeahead on dropdown-menu, disabled on accordion/collapsible triggers.
  • Web-standards-first: accordion orientation=\"horizontal\" deliberately not supported (fights <details>); disabled ships as visual-only with a JSDoc recommendation to combine with the native inert attribute.

What changed in each component

Component Tier Internals New shadcn API surface
dialog 2 (slim) Wraps content in a native <dialog> and calls showModal(). Native focus trap + Tab cycle + Escape + ::backdrop replace ~250 lines of hand-rolled JS. — (already at parity)
alert-dialog 2 (slim) Same <dialog> strategy; native Escape cancelled via the cancel event; no backdrop-click dismissal. — (already at parity)
popover 1 popover attribute + popovertarget invoker + CSS Anchor Positioning. Implicit anchor removes inline anchor-name / position-anchor for the common case. popoverContentClass({ side, align, sideOffset, alignOffset })
accordion 1 <details name=\"...\"> + <summary>; native exclusive-open via name. accordionTriggerClass({ disabled }). Documents orientation=\"horizontal\" as not supported.
collapsible 1 <details> + <summary>. collapsibleTriggerClass({ disabled })
tooltip 2 Content opts into popover=\"manual\" for top-layer + z-index-free rendering. skip-delay-duration, align-offset
hover-card 2 Content opts into popover=\"manual\". align-offset
dropdown-menu 2 Content + sub-content opt into popover=\"manual\". align-offset, text-value (typeahead)

positionFloating(trigger, content, opts) (shared utility used by the tier-2 floating components) extended once to take alignOffset.

Other updates

  • _lib/tier.tsTIER_2_NAMES shrinks from 12 to 9.
  • examples.ts, component-api.ts, docs/page.ts, page.ts — per-component code samples + prop tables refreshed.
  • AGENTS.md, README.md — inventory + tier descriptions updated.
  • AGENTS.md merge-workflow rule clarified: "merge to main" always means gh pr create + gh pr merge, never a local git merge into main.

Test plan

  • npm test --workspace=@webjskit/ui — 128/128 pass (5 new tests cover new options on popover, the disabled + inert recommendation, align-offset wiring across tier-2 components, tooltip skip-delay-duration, and dropdown-menu typeahead).
  • Dev server boots; the 8 touched docs pages (popover, accordion, collapsible, dialog, alert-dialog, tooltip, hover-card, dropdown-menu) all SSR with HTTP 200.
  • Tailwind 4 scanner sees every required literal: 12 [position-area:...], 36 [margin-{dir}:Npx], 36 translate-{x|y}-[±Npx] classes baked into source.
  • Interactive smoke-test in a real browser (open dialog, toggle accordion via name="", click popover, hover tooltip) — recommended before merging.

Eight registry components now lean on native HTML primitives instead of
hand-rolled JS. Three drop their custom elements entirely; five keep a
thin custom-element wrapper around the platform feature.

Tier-2 -> Tier-1 (custom element removed; native HTML + class helpers):

- popover     -> <button popovertarget=...> + <div popover> + CSS Anchor
                 Positioning. popoverContentClass({ side, align,
                 sideOffset, alignOffset }) emits literal Tailwind 4
                 arbitrary classes ([position-area:...], translate, etc).
                 Implicit anchor via popovertarget removes the inline
                 anchor-name / position-anchor styles for the common case.
- accordion   -> <details name=...> + <summary>. The native name
                 attribute provides exclusive-open ("type=single").
                 accordionTriggerClass({ disabled }) renders the visual
                 disabled state; docs recommend native inert for full
                 keyboard prevention. orientation=horizontal is
                 deliberately not supported (fights <details>).
- collapsible -> <details> + <summary>. collapsibleTriggerClass({
                 disabled }) matches the accordion approach.

Tier-2 simplified internals (custom element stays, native does the work):

- dialog        -> wraps <ui-dialog-content> in a programmatic <dialog>
                   and calls .showModal(). Native focus trap, Tab cycle,
                   Escape close, focus restore, and ::backdrop replace
                   ~250 lines of hand-rolled JS. We retain body scroll
                   lock + auto-injected close X.
- alert-dialog  -> same native <dialog> strategy; native Escape close
                   cancelled via the cancel event; no backdrop-click
                   dismissal (user must choose Cancel or Action).
- tooltip       -> content opts into popover="manual" for top-layer +
                   z-index-free rendering. Added skip-delay-duration
                   attribute (module-level last-hide tracker, matches
                   shadcn TooltipProvider.skipDelayDuration). Added
                   align-offset attribute.
- hover-card    -> content opts into popover="manual". Added
                   align-offset attribute.
- dropdown-menu -> content + sub-content opt into popover="manual".
                   Added align-offset attribute and typeahead behavior
                   (accumulated buffer cleared after 500ms inactivity,
                   text-value attribute overrides textContent). Existing
                   arrow-nav, submenu chain, outside-click, and Escape
                   are unchanged.

positionFloating(trigger, content, opts) — shared by all three tier-2
positioned components — extended to accept alignOffset.

Tier classification + docs:

- packages/ui/packages/website/app/_lib/tier.ts: TIER_2_NAMES shrinks
  from 12 to 9.
- examples.ts: per-component code samples regenerated for the new APIs
  (native popovertarget, native details/summary, dialog wrappers).
- component-api.ts: prop tables refreshed for every touched component;
  the orientation=horizontal gap on accordion is documented inline.
- AGENTS.md + README.md: inventory and tier descriptions updated.

Also: AGENTS.md merge-workflow rule clarified — "merge to main" always
means gh pr create + gh pr merge via the GitHub CLI, never a local
git merge into main.

Tests:

- 128/128 ui package tests pass (5 new tests cover the new options on
  popover, the disabled+inert recommendation on accordion/collapsible,
  the align-offset wiring on tier-2 components, tooltip skip-delay, and
  dropdown-menu typeahead).
- All 8 touched docs pages SSR with HTTP 200.
@vivek7405 vivek7405 merged commit 283b9ad into main May 18, 2026
@vivek7405 vivek7405 deleted the feat/ui-native-html branch May 18, 2026 10:47
vivek7405 added a commit that referenced this pull request May 21, 2026
…ep (#5)

Eight registry components now lean on native HTML primitives instead of
hand-rolled JS. Three drop their custom elements entirely; five keep a
thin custom-element wrapper around the platform feature.

Tier-2 -> Tier-1 (custom element removed; native HTML + class helpers):

- popover     -> <button popovertarget=...> + <div popover> + CSS Anchor
                 Positioning. popoverContentClass({ side, align,
                 sideOffset, alignOffset }) emits literal Tailwind 4
                 arbitrary classes ([position-area:...], translate, etc).
                 Implicit anchor via popovertarget removes the inline
                 anchor-name / position-anchor styles for the common case.
- accordion   -> <details name=...> + <summary>. The native name
                 attribute provides exclusive-open ("type=single").
                 accordionTriggerClass({ disabled }) renders the visual
                 disabled state; docs recommend native inert for full
                 keyboard prevention. orientation=horizontal is
                 deliberately not supported (fights <details>).
- collapsible -> <details> + <summary>. collapsibleTriggerClass({
                 disabled }) matches the accordion approach.

Tier-2 simplified internals (custom element stays, native does the work):

- dialog        -> wraps <ui-dialog-content> in a programmatic <dialog>
                   and calls .showModal(). Native focus trap, Tab cycle,
                   Escape close, focus restore, and ::backdrop replace
                   ~250 lines of hand-rolled JS. We retain body scroll
                   lock + auto-injected close X.
- alert-dialog  -> same native <dialog> strategy; native Escape close
                   cancelled via the cancel event; no backdrop-click
                   dismissal (user must choose Cancel or Action).
- tooltip       -> content opts into popover="manual" for top-layer +
                   z-index-free rendering. Added skip-delay-duration
                   attribute (module-level last-hide tracker, matches
                   shadcn TooltipProvider.skipDelayDuration). Added
                   align-offset attribute.
- hover-card    -> content opts into popover="manual". Added
                   align-offset attribute.
- dropdown-menu -> content + sub-content opt into popover="manual".
                   Added align-offset attribute and typeahead behavior
                   (accumulated buffer cleared after 500ms inactivity,
                   text-value attribute overrides textContent). Existing
                   arrow-nav, submenu chain, outside-click, and Escape
                   are unchanged.

positionFloating(trigger, content, opts) — shared by all three tier-2
positioned components — extended to accept alignOffset.

Tier classification + docs:

- packages/ui/packages/website/app/_lib/tier.ts: TIER_2_NAMES shrinks
  from 12 to 9.
- examples.ts: per-component code samples regenerated for the new APIs
  (native popovertarget, native details/summary, dialog wrappers).
- component-api.ts: prop tables refreshed for every touched component;
  the orientation=horizontal gap on accordion is documented inline.
- AGENTS.md + README.md: inventory and tier descriptions updated.

Also: AGENTS.md merge-workflow rule clarified — "merge to main" always
means gh pr create + gh pr merge via the GitHub CLI, never a local
git merge into main.

Tests:

- 128/128 ui package tests pass (5 new tests cover the new options on
  popover, the disabled+inert recommendation on accordion/collapsible,
  the align-offset wiring on tier-2 components, tooltip skip-delay, and
  dropdown-menu typeahead).
- All 8 touched docs pages SSR with HTTP 200.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant