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

@acms-subir/native

v0.1.3

Published

React Native / Expo hooks and components for building your own app on an ACMS site.

Readme

@acms-subir/native

React Native / Expo building blocks for your own app on an ACMS site: a provider, TanStack Query hooks over @acms-subir/client, theme tokens that follow the site's Design in light and dark mode, and a few components. ACMS does not ship an app; the fastest start is the blank starter:

npx acms add mobile            # from your site folder → mobile/
cd mobile && npm install && cd ..
npx acms mobile dev            # site + Expo on an address your phone can reach
npx acms mobile recipe add events   # also: news, booking, push

Manual install in an existing Expo app:

npx expo install @acms-subir/native @acms-subir/client @tanstack/react-query

Peers: react, react-native (0.74+), @tanstack/react-query v5 and @acms-subir/client. Optional peers (the starter installs the first two):

| Package | Enables | | --- | --- | | react-native-webview | Showing the site's own coded sections as the website renders them on iOS/Android (the web uses an iframe) | | react-native-svg | Icons in the feature grid section | | @react-native-async-storage/async-storage, @tanstack/query-async-storage-persister | The offline cache |

Without them everything still bundles and runs (Expo's Metro treats these requires as optional): icons are omitted and unknown sections use the generic view.

Provider

import { AcmsProvider, Sections } from '@acms-subir/native';

export default function App() {
  return (
    <AcmsProvider baseUrl={process.env.EXPO_PUBLIC_ACMS_URL!}>
      <Sections page="home" />
    </AcmsProvider>
  );
}

| Prop | | | --- | --- | | baseUrl | Site origin or its /api/v1 URL | | locale | Initial language; defaults to the site default | | client, clientOptions | Your own client, or options (fetch, cache, headers, timeout) | | queryClient | Your own TanStack QueryClient (default: 60s stale time, 7-day gc) | | persist | Offline cache; see below | | colorScheme | Force light/dark instead of the device setting | | loadedFonts | Custom font families you loaded with expo-font; theme fonts use only these | | onNavigate | (path, url) => void for taps on links to site pages (sections, rich text, web-rendered sections), e.g. to open your own screen. Default: open the URL in the browser. External, mailto: and tel: links always leave the app | | webSectionFallback | false turns off the web view for sections without a native component (default true) |

The provider preloads /site and /theme. Query keys start with ['acms', baseUrl, locale], so switching language or site never mixes data.

Hooks

| Hook | Returns | | --- | --- | | useAcms() | The typed client bound to the current language | | useSite() | Query: name, menu, languages, features | | useTheme() | Resolved tokens: scheme, colors (+ onAccent), radius (px, max 12), fonts, styles (space(n), spacing, text.*, card, button, …), ready | | useLocale() | { locale, defaultLocale, languages, setLocale } | | usePage(slug = 'home') | Query: a published page with sections | | usePosts(query) | Infinite query (fetchNextPage), plus flattened, deduplicated items. q gives ranked search results (keeps the previous matches while loading) | | useSearch(text, { types, collection, limit, debounceMs }) | Query: top matches in posts, pages and events (posts, pages, events, more), debounced; blank text fetches nothing. Also term and pending | | useDebounced(value, ms = 250) | value once it stops changing, for search boxes | | useLinks() | Query: the links page (profile, socials, blocks) | | useForm(id) / useSubmitForm(id) | Query: a published form's questions; mutation that sends answers (the same answers reuse one Idempotency-Key until it succeeds) | | useManagedBooking(token) | Query: the booker's own booking from its manageToken (can says what they may change); never saved offline | | useCancelBooking(token) / useRescheduleBooking(token) / useRescheduleAvailability(token, query \| null) | Mutations to cancel or move it, and the open times to move it to | | useGuest({ storage }) | Guest sign-in: status, email, signOut(), and two mutations, requestCode.mutate(email) and signIn.mutate({ email, code }) (use isPending/error on each for UI state). The token lives in expo-secure-store (sessionStorage on web), one entry per site, and is only ever sent to the site that issued it | | useMyBookings({ when }) | Query: the signed-in guest's bookings and RSVPs (each with a manageToken); signs out by itself when the session ends | | usePost(slug, { collection }) | Query: post detail (slug lookup scans post lists) | | useEvents(query) | Query: occurrences in a window (from, to, calendar, q, limit) | | useEvent(slug, query) | Query: event found among occurrences in the window | | useResources() | Query: public resources and booking links | | useAvailability(query \| null) | Query: open slots; disabled until resource and link are set; never cached offline | | useBooking({ createKey }) | Mutation. Same details reuse one Idempotency-Key until success; changed details start a new intent. reset() starts over | | useRefresh() | { refreshing, refresh } for pull to refresh | | useLayout() | { width, wide, columns, gutter, maxWidth, contentWidth } for phone/tablet layouts | | useOpenLink() | open(href) for links from section data: site pages go through onNavigate | | usePushRegistration({ topics, askOnMount }) | { status, token, topics, error, register, setTopics, unregister } for push notifications (needs expo-notifications; see below) | | usePushTopics() | Query: topics the site offers as notification toggles | | useNotificationTaps(handler?) | Opens the page a tapped notification points to (also the tap that launched the app) |

Pass createKey (for example expo-crypto's randomUUID) for cryptographic idempotency keys; the default uses crypto.randomUUID when available. Never put a booking's manage token or key in a URL or log; use expo-secure-store.

Sections

<Sections page="home" registry={sections} /> renders a page's published sections top to bottom. Each section uses the first match of:

  1. your registry: a type ("hero-banner") or type and variant ("hero-banner:centered") mapped to a component; variant keys win, and an array of registries is searched in order;
  2. the packaged library (library, default on): native versions of the 11 library sections;
  3. the website's rendering (webFallback, default on): the section's /embed/section/... document in react-native-webview (an iframe on the web), sized to its content, with link taps sent to onNavigate;
  4. fallback: GenericSection (heading, text, items, links) by default; null hides the section.

| Prop | | | --- | --- | | page | Page slug; "home" is the homepage | | registry | Your components; they win over everything else | | library | false to skip the packaged components, or a registry to replace them | | webFallback | false to use the generic view instead of the web view | | fallback | Last resort component, or null | | onNavigate | Overrides the provider's handler for this list | | loading, renderError, style | Loading/error UI and the content column style |

import { defineSectionRegistry, useTheme, type SectionComponentProps } from '@acms-subir/native';
import type { TeamData } from '@acms-subir/native/sections';

function Team({ data, variant }: SectionComponentProps<TeamData>) { /* ... */ }
export const sections = defineSectionRegistry({ team: Team, 'hero-banner:cover': MyCover });

Library sections (@acms-subir/native/sections)

| Type | Component | Variants | | --- | --- | --- | | hero-banner | HeroBanner | split, centered, cover | | service-times | ServiceTimes | cards, strip | | upcoming-events | UpcomingEvents | cards, list | | feature-grid | FeatureGrid | three-up, two-up | | team | Team | grid, compact | | pricing | Pricing | default | | gallery | Gallery | grid, strip | | questions | Questions | accordion, two-column | | cta-band | CtaBand | accent, quiet | | contact | Contact | split, stacked | | latest-posts | LatestPosts | cards, list |

librarySections is the registry <Sections> uses. The components mirror the website versions (same fields, variants and empty-field behaviour), follow useTheme() in light and dark mode, and lay out from the window width: multi-column grids on tablets (like the web's auto-fill/auto-fit grids and media steps), stacked on phones, no fixed widths. Buttons and links are at least 44pt tall; corners are rectangular (10–12px at most). Data types (HeroBannerData, TeamData, EventItem, LinkValue, …) and the building blocks they use (Grid, FocalImage, SectionHeader, ActionButton, TextLink, FeatureIcon, useSectionMetrics, useSiteUrl) are exported from the same entry point for your own sections.

To change one, copy it into your app and edit it there. From the site folder:

npx acms section add team --theme <slug> --native    # → mobile/sections/Team.tsx, registered

Images and focal points. Image fields come with <key>Focus ("30% 20%", as in CSS object-position). FocalImage crops like object-fit: cover at that point using React Native's Image and Image.getSize (no extra package). If you prefer expo-image, pass contentPosition={focusToContentPosition(data.imageFocus)}.

Web view for the site's own sections

Coded sections without a native component (a theme's custom sections/*.astro) show as the website renders them: WebSectionFallback loads <site>/embed/section/<page>/<section id>?locale=&scheme=, a chrome-less document with the site's theme CSS. It posts its height (the view resizes, for example when a question opens) and hands link taps to onNavigate instead of navigating; the web view refuses any other top-level navigation. If the page can't load, the generic view is shown. Turn it off per list (webFallback={false}) or app-wide (<AcmsProvider webSectionFallback={false}>).

Other components

  • <RichText html> renders sanitized API HTML (paragraphs, line breaks, bold, italic, links, lists, headings, blockquotes) as native text. Only http(s), mailto and tel links are kept; links use onNavigate unless you pass onLinkPress.
  • <EventDate start end allDay timezone> formats in the event's timezone and the current language.
  • Button, ErrorView, GenericSection are small themed helpers.

Section links and media can be root-relative; resolve them with resolveUrl(href, baseUrl) or open them with useOpenLink().

Push notifications

Works when the site turns push on (acms({ mobile: { push: true } })) and the app installs the optional peers:

npx expo install expo-notifications expo-device

Add "expo-notifications" to plugins in app.json and an EAS project id (npx eas-cli init, stored in extra.eas.projectId); Expo push tokens need it.

const push = usePushRegistration({ topics: ['news', 'events'] }); // starting topics for new devices
// push.status: checking | idle | registering | registered | denied | unsupported | error
<Button label="Turn on notifications" onPress={push.register} />

const { data } = usePushTopics();              // toggles: data.topics
push.setTopics(['events']);                     // saved on the site
useNotificationTaps();                          // taps open the page (via onNavigate)

Without expo-notifications, on web and on simulators the status is unsupported and nothing else runs. Once permission is granted the device re-registers silently on each start (keeping it on the site's list) and sends the app's language, so notices arrive in the reader's language when the site has a translation. unregister() opts out.

Offline cache

import { offlinePersister } from '@acms-subir/native/persist';
<AcmsProvider baseUrl={url} persist={offlinePersister({ buster: '1' })}>

Successful content reads are saved to AsyncStorage (7 days by default) and shown on the next start while fresh data loads. Availability, bookings and errors are never saved. Any object with TanStack's persister shape also works.

Pure helpers

parseHtml, htmlToText, resolveTheme, resolveScheme, fontFamily, contrastText, resolveSection, chooseSection, genericSectionView, classifyLink, sectionEmbedUrl, parseEmbedMessage, parseFocus, focalCrop, focusToContentPosition, gridColumns, cellWidth, stepColumns, fluid, contentWidth, formatEventDate, formatClock and localDay have no React Native dependency and are unit tested:

npm run build -w packages/native
npm run test -w packages/native

Editor apps: @acms-subir/native/editor

Building blocks for the site's staff app (a separate app from the one visitors use): sign in through the site (OAuth 2.0 + PKCE in the system browser; tokens in secure storage, refreshed automatically), hooks over the bearer-only /api/v1/admin/* API, and inputs for every section field type.

npx acms add mobile editor          # from the site folder → mobile-editor/
import { EditorProvider, completeAuthRedirect, redirectUriFor, useAuth } from '@acms-subir/native/editor';

completeAuthRedirect(); // web: lets the sign-in popup hand back its result

export default function App() {
  return (
    <EditorProvider baseUrl={process.env.EXPO_PUBLIC_ACMS_URL!} clientId="org.grace.staff" redirectUri={redirectUriFor('org.grace.staff')}>
      <Gate />
    </EditorProvider>
  );
}
function Gate() {
  const auth = useAuth();
  return auth.signedIn ? <Home /> : <Button title="Sign in" onPress={() => auth.signIn()} />;
}

Optional peers, loaded only when installed: expo-secure-store, expo-web-browser, expo-crypto, expo-image-picker, expo-device (expo-auth-session is not required). Hooks: useAuth, useSiteInfo, useMe, useDocuments, useCreateDocument, useDocument (optimistic concurrency with conflict / keepMine() / keepTheirs()), usePublish, useSectionLayouts, useSectionEditor (canEdit(fieldKey), changed-fields-only saves, publish one section), useTranslations, useTranslationQueue, useMedia (pickAndUpload), useBookingsInbox, useEditorClient. Components: FieldInput, SectionFields, MediaPicker, DocumentPicker and Inset Paper building blocks (EditorButton, EditorInput, Segmented, Notice, StatusText, ListRow, Card, Sheet, OptionGrid; useEditorTheme()). Pure helpers (field rules shared with the server, permissions, list rows, markdown-lite rich text, token storage, the session controller) are exported for your own UI and tests. Full guide: npx acms docs mobile → Editor apps.