npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dloizides/ui-layout

v1.23.0

Published

Themable, brand-agnostic React Native (RN-web) layout primitives — Section, Heading, StatusBadge, UpgradePrompt, ModalDropdown, Modal, ConfirmDialog — sharing the @dloizides/ui-feedback UI context.

Downloads

4,820

Readme

@dloizides/ui-layout

Themable, brand-agnostic React Native (RN-web) layout primitives for the dloizides.com portfolio. Read theme + translations from the shared @dloizides/ui-feedback context (useUi).

Components

| Export | Purpose | |--------|---------| | Section | Bordered card container (themes border + surface). | | Heading | Themed section heading text. | | StatusBadge | Status pill — caller supplies label/color/backgroundColor. | | UpgradePrompt | Free-tier upgrade nudge; CTA navigates to the billing route via useUi().navigate. | | Tabs | Controlled tabbed section shell. Responsive: a horizontal role=tablist pill strip on wide web, collapsing to a real mobile MENU (a trigger + vertical section list, via ModalDropdown) below collapseBelow (default 768) or on native. The collapsed trigger presents as a caret field (default) or a ☰ hamburger via collapsedTrigger. Per-tab testIDs survive in both modes. Requires the common.tabsMenuHint key. | | TabsCollapsedTrigger | Enum (Caret / Hamburger) selecting how the collapsed Tabs menu trigger presents. Defaults to Caret. | | ModalDropdown | Generic dropdown selector. Responsive by default: an inline anchored menu on wide/desktop web, a modal on narrow/mobile — override per screen with variant. Opt into a menu affordance on the default trigger with showCaret (trailing ▾) or showHamburger (leading ☰, wins over showCaret). | | DropdownVariant | Enum (Menu / Modal) to force a ModalDropdown rendering variant. | | ModalShell | Full-screen slide-up sheet with a themed header (title + close). For a page-covering editor. Dismiss via onCancel. | | Modal | Themed centered dialog overlay: scrim + rounded card, optional header with a top-right ✕ close, scrollable body, optional footer. Dismiss via ✕ / backdrop / Escape (web) / hardware back (native); focus-trapped + scroll-locked. size = sm/md/lg. | | ConfirmDialog | Confirm/cancel popup built on Modal. Cancel + Confirm buttons (from @dloizides/ui-buttons); destructive paints the Confirm red. Cancel is focus-default (safe), and is disabled while busy (Confirm shows a spinner). Cancel / backdrop / Escape → onCancel. | | Accordion / AccordionItem | Themed expand/collapse disclosure group (replaces hand-rolled <details>/expanders). Controlled or uncontrolled, single- or multi-open, animated, keyboard + screen-reader accessible. | | useFocusTrap | Web keyboard focus-trap hook (no-op on native). |

Modal & ConfirmDialog

import { Modal, ConfirmDialog } from '@dloizides/ui-layout';

// A centered edit dialog with a top-right ✕:
<Modal visible={editing} title="Edit details" onClose={() => setEditing(false)} footer={<SaveRow />}>
  <AttendeeForm />
</Modal>

// A destructive confirmation popup:
<ConfirmDialog
  visible={confirming}
  destructive
  title="Remove attendee?"
  message="This can't be undone."
  busy={isDeleting}
  onConfirm={remove}
  onCancel={() => setConfirming(false)}
/>
  • DismissalModal: ✕ button, backdrop press, Escape (web), hardware back (native), all → onClose. ConfirmDialog: Cancel, backdrop, Escape → onCancel; it hides the ✕ so the focus trap lands on the safe Cancel action.
  • testIDsModal (default modal): ${testID}-close, ${testID}-backdrop. ConfirmDialog (default confirm-dialog): ${testID}-confirm, ${testID}-cancel, ${testID}-message.
  • i18n keys the host must provide: common.close (✕ label), common.closeDialogHint, common.dialog (fallback dialog name), and for ConfirmDialog defaults common.confirm / common.confirmHint / common.cancel / common.cancelHint. Pass confirmLabel/cancelLabel (already-localized) to override.
  • a11yrole="dialog" + aria-modal; focus trapped on web and restored to the opener on close. Note (RN-web): accessibilityHint does not reach the DOM, so the ✕'s hover tooltip carries the visible helper text.

Accordion

A compound component: an Accordion container that owns the open state + AccordionItem children that read it via context. Each item is a pressable header (title left, optional right adornment/badge slot, a rotating chevron) over a collapsible body region.

  • Controlled — pass openIds + onOpenChange.
  • Uncontrolled — pass defaultOpenIds on the container and/or defaultOpen per item.
  • allowMultiple (default true) keeps any number of items open; false = single-open.
  • Per-item disabled.
  • a11y — header is an accessible button (accessibilityRole="button", accessibilityState={{ expanded }}, web aria-expanded + aria-controls); the body is a role="region". The header renders as a native <button>, so Enter / Space toggle it.
  • Themed entirely from the active useUi() theme (border / surface / text colours). Renders borderless so it drops cleanly inside a Section / Card.
  • Animated open/close via guarded LayoutAnimation (a no-op where unavailable — never errors on web or native) plus an Animated chevron rotation.
  • testIDs — container testID (default accordion); per item the header is testID (default accordion-item-<id>), the body <header>-body, the chevron <header>-chevron.
import { Accordion, AccordionItem, Section, StatusBadge } from '@dloizides/ui-layout';

// 1) A form "More options" disclosure (single collapsible section, starts closed):
<Section>
  <Accordion>
    <AccordionItem id="more" title="More options">
      <FuzzinessSlider />
      <DatasetPicker />
    </AccordionItem>
  </Accordion>
</Section>

// 2) An expandable list row per matched candidate (single-open, badge in the right slot):
<Accordion allowMultiple={false} onOpenChange={(ids) => setOpenRow(ids[0])}>
  {candidates.map((c) => (
    <AccordionItem
      key={c.id}
      id={c.id}
      title={c.name}
      right={<StatusBadge label={c.tier} color={c.fg} backgroundColor={c.bg} />}
    >
      <CandidateDetail candidate={c} />
    </AccordionItem>
  ))}
</Accordion>

// Controlled variant:
<Accordion openIds={openIds} onOpenChange={setOpenIds}>{/* …items… */}</Accordion>

The apps supply the header hint string via the shared translate: key common.accordionToggleHint.

ModalDropdown variants

ModalDropdown picks its presentation automatically:

  • Wide / desktop web (viewport ≥ 768px) → an inline anchored menu that opens directly under the trigger (native <select> feel): dismissible via click-outside / Esc, keyboard-navigable (↑/↓, Home/End, Enter), scrolls when long, and never pushes layout. It is portalled to document.body so no ancestor stacking context or overflow can trap or clip it, and it stays glued to its trigger: it re-anchors on scroll (in any ancestor scroll container), on resize, and on layout changes that fire no scroll event at all, then closes once the trigger is no longer meaningfully visible. Visibility is measured against the viewport intersected with every clipping ancestor — RN-web apps scroll an inner ScrollView, never the document, so a viewport-only test would never fire.
  • Narrow / mobile, or any native platform → the original modal / bottom-sheet.

Pass variant to force either behaviour regardless of viewport — the prop overrides the auto choice:

import { ModalDropdown, DropdownVariant } from '@dloizides/ui-layout';

// Responsive (default): inline menu on desktop, modal on mobile.
<ModalDropdown testID="risk" accessibilityLabel="Risk" accessibilityHint="Pick a risk level"
  value={risk} options={riskOptions} onChange={setRisk} />

// Force the inline menu everywhere:
<ModalDropdown /* …props… */ variant={DropdownVariant.Menu} />

// Force the modal everywhere:
<ModalDropdown /* …props… */ variant={DropdownVariant.Modal} />

accessibilityHint reaches assistive tech on both platforms: natively via accessibilityHint, and on web via a visually-hidden node referenced by the trigger's aria-describedby (react-native-web does not map the RN prop, so the hint would otherwise be silently dropped).

The public API is additive — callers that never pass variant keep working and now get the inline-on-desktop menu for free.

Install

npm install @dloizides/ui-layout @dloizides/ui-feedback

Peer deps: @dloizides/ui-feedback >= 1.1.0, react >= 18, react-native >= 0.74.

Mount a FeedbackUiProvider / UiProvider at the app root so the components pick up your theme, translations, and navigation.

License

MIT

i18n contract (required)

The kit is fallback-free: the neutral default t returns the key itself, so a host that forgets a key does not silently show English — it renders the raw dotted name to users, or announces it to screen readers. LAYOUT_I18N is the machine-readable manifest of every key this package resolves; bind your missing-key guard to it exactly as you already do for TABLE_I18N / FILTERS_I18N from @dloizides/ui-tables, and an upgrade can never add a key your app silently fails to define.

import { LAYOUT_I18N } from '@dloizides/ui-layout';
import { TABLE_I18N } from '@dloizides/ui-tables';

// Every value is a translation key this package will call `t(...)` with.
const REQUIRED_KIT_KEYS = [...Object.values(LAYOUT_I18N), ...Object.values(TABLE_I18N)];

The manifest cannot drift: every t(...) call in the package references LAYOUT_I18N, and the test suite fails on a raw key literal in a component or a stale entry in the map.