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

@getwajiha/theme-sdk

v1.2.0

Published

Theme SDK for building Wajiha storefront themes

Readme

@getwajiha/theme-sdk

Theme SDK for building Wajiha storefront themes. Provides React hooks, components, utilities, and TypeScript types that connect your theme to the platform's data layer.

Install

npm install @getwajiha/theme-sdk

Peer dependencies: react (18 or 19), react-dom (18 or 19).

Quick Start

Every theme is wrapped in a ThemeProvider by the platform at render time. Inside your theme components, use hooks to access tenant data, products, navigation, locale, settings, and more.

import { useLocale, useTenant, useProducts } from '@getwajiha/theme-sdk'
import { Container, ProductGrid, LanguageSwitcher } from '@getwajiha/theme-sdk/components'

export default function HomePage() {
  const tenant = useTenant()
  const products = useProducts()
  const { t, isRTL } = useLocale()

  return (
    <Container maxWidth="xl">
      <header>
        {tenant.logo && <img src={tenant.logo} alt={tenant.name} />}
        <h1>{t('welcome')}</h1>
        <LanguageSwitcher />
      </header>
      <ProductGrid products={products} columns={3} />
    </Container>
  )
}

Exports

The package exposes four entry points:

| Import path | Contents | |---|---| | @getwajiha/theme-sdk | Provider, hooks, types, settings utilities, ThemeTokens type | | @getwajiha/theme-sdk/components | Pre-built UI components | | @getwajiha/theme-sdk/utils | cn, getTranslation, formatDate, formatCurrency, clsx | | @getwajiha/theme-sdk/tokens.css | Optional Wajiha DS CSS tokens (--ds-*, --status-*, system scales) |


Tokens

Wajiha ships a canonical CSS design token layer that themes may opt into. The token surface mirrors what the platform itself uses for the docs canvas — surface colours (--ds-bg, --ds-surface), foreground (--ds-fg, --ds-fg-muted), accent (--ds-accent, defers to per-tenant --brand-primary-hex), system scales (radius / spacing / elevation / motion), and the canonical status palette (--status-ok, --status-info, etc.).

/* In your theme's stylesheet (e.g. assets/styles/theme.css) */
@import '@getwajiha/theme-sdk/tokens.css';

.my-card {
  background: var(--ds-surface);
  color: var(--ds-fg);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  padding: var(--ds-space-5);
  box-shadow: var(--ds-elevation-e2);
}

.my-card:focus-within {
  box-shadow: var(--ds-focus-ring);
}

Light + dark. Tokens are scoped to :root (light) and .dark (dark). Toggle dark mode by adding the dark class to a wrapper element. The accent colour automatically inherits from the per-tenant brand override (--brand-primary-hex) when set by the platform.

TypeScript contract. A ThemeTokens interface mirrors every token name. Useful when building admin UIs that override individual tokens, or for typing the optional tokens field on ThemeConfig:

import type { ThemeTokens } from '@getwajiha/theme-sdk'

const overrides: Partial<ThemeTokens> = {
  '--ds-accent': '#7c3aed',
  '--ds-radius-lg': '12px',
}

Opt-out. If your theme prefers its own design language, simply do not import tokens.css. Themes are self-contained CSS bundles served from S3 — there is no requirement to consume these tokens.


ThemeProvider

Wraps your theme and injects the ThemePageData context. The platform handles this automatically -- you do not need to render it yourself.

import { ThemeProvider } from '@getwajiha/theme-sdk'

<ThemeProvider data={pageData}>
  <YourTheme />
</ThemeProvider>

Props:

| Prop | Type | Description | |---|---|---| | data | ThemePageData | Full page data object injected by the platform | | children | ReactNode | Theme component tree |


Hooks

All hooks must be called inside a ThemeProvider. They throw if called outside one.

useThemeData

Returns the entire ThemePageData object. Use the more specific hooks below when you only need a slice.

import { useThemeData } from '@getwajiha/theme-sdk'

const data = useThemeData()
// data.tenant, data.products, data.locale, data.settings, ...

Returns: ThemePageData


useTenant

Returns tenant branding information.

import { useTenant } from '@getwajiha/theme-sdk'

const tenant = useTenant()
// tenant.name, tenant.domain, tenant.logo, tenant.favicon, tenant.description

Returns: TenantInfo

| Field | Type | |---|---| | name | string | | domain | string | | logo | string \| null | | favicon | string \| null | | description | string \| null |


useProducts

Returns the product list for the current page. Only populated if the template declares products in its DataRequirements. Returns an empty array if no products are available.

import { useProducts } from '@getwajiha/theme-sdk'

const products = useProducts()

products.map(p => (
  <div key={p.id}>
    <h3>{p.name}</h3>
    <p>{p.description}</p>
  </div>
))

Returns: Product[]

| Field | Type | |---|---| | id | string | | name | string | | slug | string | | description | string \| null | | logo | string \| null | | status | string | | isPublic | boolean | | publishedAt | string \| null |


useLocale

Returns locale information plus a t() translation function and an isRTL flag.

import { useLocale } from '@getwajiha/theme-sdk'

const { current, available, direction, isRTL, t } = useLocale()

// Translate a key from the theme's locale messages
<h1>{t('hero_title')}</h1>

// With parameter interpolation
<p>{t('greeting', { name: 'Ahmed' })}</p>
// Message "Hello, {name}!" becomes "Hello, Ahmed!"

Returns: UseLocaleReturn

| Field | Type | Description | |---|---|---| | current | string | Active locale code (e.g. "en", "ar") | | available | string[] | All configured locale codes | | direction | 'ltr' \| 'rtl' | Text direction | | messages | Record<string, string> | Raw message map | | isRTL | boolean | true when direction is 'rtl' | | t | (key: string, params?: Record<string, string>) => string | Translation function with parameter interpolation |


useNavigation

Returns the tenant's navigation menus. Menu items are nested (children support sub-menus).

import { useNavigation } from '@getwajiha/theme-sdk'

const { main, footer } = useNavigation()

main.map(item => (
  <a key={item.id} href={item.url}>{item.label}</a>
))

Returns: Navigation

| Field | Type | |---|---| | main | MenuItem[] | | footer | MenuItem[] |

Each MenuItem:

| Field | Type | |---|---| | id | string | | label | string | | url | string | | sortOrder | number | | children | MenuItem[] |


useSettings

Returns theme settings configured by the tenant admin. Settings are defined in wajiha.theme.json and their values are set through the admin panel.

import { useSettings } from '@getwajiha/theme-sdk'

const settings = useSettings()

// Direct access
const heroTitle = settings.hero_title as string

// Type-safe access with fallback
const columns = settings.get<number>('grid_columns', 3)
const accent = settings.get<string>('accent_color', '#3b82f6')

Returns: Record<string, unknown> & { get: <T>(key: string, fallback: T) => T }

The get method returns the setting value if it exists, otherwise returns the provided fallback.


useCurrentPage

Returns metadata about the current page being rendered.

import { useCurrentPage } from '@getwajiha/theme-sdk'

const page = useCurrentPage()
// page.type  -> 'index' | 'page' | 'products' | 'error'
// page.slug  -> URL slug (if applicable)
// page.meta  -> { title, description, ogImage? }
// page.content -> HTML content (for CMS pages)

Returns: PageInfo


useUser

Returns the current user session. Returns an anonymous user object (isAuthenticated: false) when no user is logged in.

import { useUser } from '@getwajiha/theme-sdk'

const user = useUser()

if (user.isAuthenticated) {
  return <span>Welcome, {user.name}</span>
}
return <a href="/login">Sign in</a>

Returns: UserInfo

| Field | Type | |---|---| | id | string | | name | string | | email | string | | avatar | string \| undefined | | isAuthenticated | boolean |


Components

Import from @getwajiha/theme-sdk/components. All components accept a className prop for custom styling.

Navigation

NavLink -- Renders an anchor with active state support.

<NavLink href="/products" isActive={true} activeClassName="font-bold">
  Products
</NavLink>

NavMenu -- Renders a recursive navigation menu from MenuItem[]. Supports custom item rendering.

const { main } = useNavigation()

<NavMenu
  items={main}
  currentPath="/products"
  itemClassName="nav-item"
  activeItemClassName="nav-item--active"
  renderItem={(item, isActive) => (
    <a href={item.url} className={isActive ? 'active' : ''}>{item.label}</a>
  )}
/>

Content & Layout

| Component | Props | Description | |---|---|---| | Container | maxWidth?: 'sm'\|'md'\|'lg'\|'xl'\|'2xl'\|'full' | Centered container with max-width | | Section | id?: string | Semantic <section> wrapper | | Grid | columns?: number, gap?: string | CSS Grid layout | | Stack | direction?: 'horizontal'\|'vertical', gap?: string, align?: 'start'\|'center'\|'end'\|'stretch' | Flexbox stack layout | | RichText | content: string | Renders sanitized HTML content | | Image | src, alt, width?, height?, loading? | Image with lazy loading default | | Video | src, poster?, controls?, autoPlay?, muted?, loop? | Video element | | Icon | name: string, size?: number | Icon placeholder (override with your icon library) |

Product

ProductCard -- Displays a single product with optional link and badge.

<ProductCard
  product={product}
  href={`/products/${product.slug}`}
  renderBadge={(p) => <ProductBadge status={p.status} />}
/>

ProductGrid -- Renders products in a CSS grid. Supports custom product rendering.

<ProductGrid
  products={products}
  columns={3}
  gap="2rem"
  renderProduct={(product) => <MyCustomCard product={product} />}
/>

ProductBadge -- Status badge. Renders the status string with a data-status attribute for styling.

i18n

TranslatedText -- Renders a multilingual field value in the current locale.

// product.name might be { en: "Widget", ar: "..." }
<TranslatedText value={product.name} as="h2" />

LanguageSwitcher -- Renders links for each available locale.

<LanguageSwitcher
  itemClassName="lang-link"
  activeClassName="lang-link--active"
/>

RTLProvider -- Wraps children with the correct dir attribute based on the current locale.

<RTLProvider>
  <main>{children}</main>
</RTLProvider>

Interactive

Accordion -- Collapsible sections with single or multi-expand support.

<Accordion
  items={[
    { id: '1', title: 'Getting Started', content: <p>...</p> },
    { id: '2', title: 'Configuration', content: <p>...</p> },
  ]}
  allowMultiple={true}
  defaultOpen={['1']}
/>

Tabs -- Tabbed interface with ARIA roles.

<Tabs
  items={[
    { id: 'overview', label: 'Overview', content: <div>...</div> },
    { id: 'api', label: 'API', content: <div>...</div> },
  ]}
  defaultTab="overview"
/>

Modal -- Dialog overlay. Closes on backdrop click.

const [open, setOpen] = useState(false)

<Modal isOpen={open} onClose={() => setOpen(false)} title="Details">
  <p>Modal content</p>
</Modal>

SEO

SEOHead -- Renders <title> and Open Graph meta tags. Place in <head>.

<SEOHead meta={page.meta} siteName={tenant.name} locale="en" />

StructuredData -- Renders a JSON-LD <script> tag.

<StructuredData data={{ "@context": "https://schema.org", "@type": "Organization", name: tenant.name }} />

Utilities

Import from @getwajiha/theme-sdk/utils.

| Function | Signature | Description | |---|---|---| | cn | (...inputs: ClassValue[]) => string | Merge class names (wraps clsx) | | getTranslation | (value: unknown, locale: string, fallbackLocale?: string) => string | Extract a locale value from a multilingual field (string or Record<string, string>) | | formatDate | (dateStr: string \| Date, locale?: string, options?: Intl.DateTimeFormatOptions) => string | Locale-aware date formatting | | formatCurrency | (amount: number, currency?: string, locale?: string) => string | Locale-aware currency formatting | | clsx | Re-exported from clsx | Conditional class names |

import { getTranslation, formatDate, cn } from '@getwajiha/theme-sdk/utils'

const name = getTranslation(product.name, 'ar', 'en')
const date = formatDate(product.publishedAt, 'en')
const classes = cn('card', isActive && 'card--active')

Settings Utilities

Imported from the main entry point. Useful when building admin tooling or validating theme configs.

| Function | Description | |---|---| | validateSettingValue(definition, value) | Validates a value against a ThemeSettingDefinition. Returns the validated value or the default. | | mergeSettingsWithDefaults(schema, values) | Merges saved setting values with schema defaults. New fields get defaults, removed fields are pruned. | | getDefaultSettings(schema) | Extracts default values from a settings schema. | | validateThemeConfig(config) | Validates a wajiha.theme.json config object. Returns an array of error strings (empty if valid). |


Theme Config (wajiha.theme.json)

Every theme must include a wajiha.theme.json at its root:

{
  "name": "my-theme",
  "version": "1.0.0",
  "sdk_version": "1.0.0",
  "locales": ["en", "ar"],
  "author": "Your Name",
  "description": "A custom storefront theme",
  "settings": [
    {
      "key": "accent_color",
      "type": "color",
      "label": { "en": "Accent Color", "ar": "لون التمييز" },
      "default": "#3b82f6"
    },
    {
      "key": "show_hero",
      "type": "boolean",
      "label": { "en": "Show Hero Section", "ar": "إظهار قسم البطل" },
      "default": true
    },
    {
      "key": "grid_columns",
      "type": "number",
      "label": { "en": "Product Grid Columns", "ar": "أعمدة شبكة المنتجات" },
      "default": 3,
      "min": 1,
      "max": 6
    },
    {
      "key": "layout",
      "type": "select",
      "label": { "en": "Layout", "ar": "التخطيط" },
      "default": "wide",
      "options": [
        { "value": "wide", "label": { "en": "Wide", "ar": "عريض" } },
        { "value": "boxed", "label": { "en": "Boxed", "ar": "محاصر" } }
      ]
    }
  ],
  "routes": {
    "/": "HomePage",
    "/products": "ProductsPage"
  }
}

Setting Types

| Type | Value | Constraints | |---|---|---| | text | string | maxLength | | color | string (hex/rgb) | -- | | boolean | boolean | -- | | number | number | min, max | | select | string | options (array of { value, label }) | | image | string \| null (URL) | -- | | array | unknown[] | maxItems, item_type | | link | object | -- |


Type Reference

Core Data

interface ThemePageData {
  tenant: TenantInfo
  navigation: Navigation
  locale: LocaleInfo
  settings: Record<string, unknown>
  appearance: Appearance        // { colorMode: 'light' | 'dark' }
  page: PageInfo
  user?: UserInfo
  products?: Product[]
  error?: ErrorInfo             // { code, message, description? }
}

Component Props

interface TemplateProps {
  data: ThemePageData
}

interface LayoutProps {
  children: ReactNode
  data: ThemePageData
}

interface SectionProps {
  data: ThemePageData
  [key: string]: unknown
}

Data Requirements

Declare what data your template needs. Attach to your template component.

interface DataRequirements {
  products?: ProductDataRequirement | boolean
  navigation?: boolean
}

interface ProductDataRequirement {
  featured?: boolean
  limit?: number
}

Full Theme Example

// templates/HomePage.tsx
import {
  useTenant,
  useProducts,
  useLocale,
  useSettings,
  useNavigation,
  useCurrentPage,
} from '@getwajiha/theme-sdk'
import {
  Container,
  NavMenu,
  ProductGrid,
  LanguageSwitcher,
  RTLProvider,
  SEOHead,
} from '@getwajiha/theme-sdk/components'

export default function HomePage() {
  const tenant = useTenant()
  const products = useProducts()
  const { main, footer } = useNavigation()
  const { t, isRTL } = useLocale()
  const settings = useSettings()
  const page = useCurrentPage()

  const showHero = settings.get<boolean>('show_hero', true)
  const accentColor = settings.get<string>('accent_color', '#3b82f6')
  const columns = settings.get<number>('grid_columns', 3)

  return (
    <RTLProvider>
      <SEOHead meta={page.meta} siteName={tenant.name} />

      <header>
        {tenant.logo && <img src={tenant.logo} alt={tenant.name} />}
        <NavMenu items={main} />
        <LanguageSwitcher />
      </header>

      <main>
        {showHero && (
          <section style={{ borderColor: accentColor }}>
            <h1>{t('hero_title')}</h1>
            <p>{t('hero_subtitle')}</p>
          </section>
        )}

        <Container>
          <h2>{t('products_heading')}</h2>
          <ProductGrid products={products} columns={columns} />
        </Container>
      </main>

      <footer>
        <NavMenu items={footer} />
        <p>{tenant.name}</p>
      </footer>
    </RTLProvider>
  )
}