Skip to content

uniweb/kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@uniweb/kit

Standard component library for Uniweb foundations. Tree-shakeable utilities, components, and hooks for building foundation components.

Installation

npm install @uniweb/kit

Tree-Shaking Benefits

Kit is designed to be bundled into your foundation (not externalized like @uniweb/core). This means:

  • Only what you use is bundled — Import 3 components? Only those 3 end up in your foundation
  • No runtime overhead — Unused code is eliminated at build time
  • Customizable — Override or extend any component without carrying dead code
  • Small foundations — A minimal foundation using just Link and useWebsite stays tiny
// vite.config.js - Kit is bundled, core is external
export default {
  build: {
    rollupOptions: {
      external: ['react', 'react-dom', 'react-router-dom', '@uniweb/core']
      // Note: @uniweb/kit is NOT in external — it gets tree-shaken
    }
  }
}

Quick Start

import { Link, Image, useWebsite } from '@uniweb/kit'

function Hero({ content }) {
  const { localize } = useWebsite()

  return (
    <div>
      <Image src={content.imgs[0]?.url} alt="Hero" />
      <h1>{localize({ en: 'Welcome', es: 'Bienvenido' })}</h1>
      <Link to="/about">Learn More</Link>
    </div>
  )
}

Exports Overview

Import Path Purpose
@uniweb/kit Core components, hooks, and utilities
@uniweb/kit/styled Pre-styled components (Section, SidebarLayout, etc.)
@uniweb/kit/search Search client and hooks (requires Fuse.js)

Components

Link

Smart link component with routing, downloads, and auto-generated accessible titles.

import { Link } from '@uniweb/kit'

<Link to="/about">About</Link>
<Link href="https://github.com">GitHub</Link>
<Link href="/files/report.pdf">Download Report</Link>
<Link href="mailto:hello@example.com">Contact</Link>
Prop Type Description
to / href string Destination URL
title string Tooltip (auto-generated if omitted)
target string Link target
download boolean Force download behavior

Image

Versatile image component with filters and profile integration.

import { Image } from '@uniweb/kit'

<Image src="/photo.jpg" alt="Photo" />
<Image src="/photo.jpg" filter={{ grayscale: 100 }} />
<Image profile={profile} type="avatar" size="lg" rounded />
Prop Type Description
src / url string Image URL
alt string Alt text
size string Preset: xs, sm, md, lg, xl, 2xl, full
rounded boolean|string Border radius
filter object CSS filters: blur, brightness, contrast, grayscale, saturate, sepia
profile object Profile for avatar/banner images
type string Image type: avatar, banner

SafeHtml

Safely render HTML with topic link resolution.

import { SafeHtml } from '@uniweb/kit'

<SafeHtml value="<p>Hello <strong>World</strong></p>" />
<SafeHtml value='<a href="topic:about">About</a>' />

Icon

SVG icon component with built-in icons and URL loading.

import { Icon } from '@uniweb/kit'

<Icon name="check" size="24" color="green" />
<Icon url="/icons/custom.svg" />
<Icon svg="<svg>...</svg>" />

Built-in: check, alert, user, heart, settings, star, close, menu, chevronDown, chevronRight, externalLink, download, play

SocialIcon

Social media platform icons with automatic detection.

import { SocialIcon, getSocialPlatform, filterSocialLinks } from '@uniweb/kit'

<SocialIcon platform="twitter" size={24} />
<SocialIcon url="https://twitter.com/example" />

// Utilities
getSocialPlatform('https://linkedin.com/in/user')  // 'linkedin'
filterSocialLinks(links)  // Filter to only social links

Supported: facebook, twitter, x, linkedin, instagram, youtube, github, medium, pinterest, tiktok, discord, mastodon, bluesky, email, phone, orcid, researchgate, googlescholar

Typography

Smart typography components for rendering semantic parser output.

import { Text, H1, H2, P, PlainText } from '@uniweb/kit'

<H1 text="Main Title" />
<H2 text={["Multi-line", "Subtitle"]} />
<P text="A paragraph of content" />
<P text={["First paragraph", "Second paragraph"]} />

// Plain text (HTML shown as text)
<PlainText text="Show <strong>tags</strong> as text" />
Prop Type Description
text string|string[] Content to render
as string Tag: 'h1'-'h6', 'p', 'div', 'span'
html boolean Render as HTML (default: true)
lineAs string Tag for array items

Aliases: H1, H2, H3, H4, H5, H6, P, Span, Div, PlainText

Media

Video player for YouTube, Vimeo, and local videos.

import { Media } from '@uniweb/kit'

<Media src="https://youtube.com/watch?v=abc123" />
<Media src="/videos/intro.mp4" controls />
<Media src="https://youtube.com/..." thumbnail="/poster.jpg" facade />

FileLogo

File type icons based on filename.

import { FileLogo } from '@uniweb/kit'

<FileLogo filename="report.pdf" size="32" />

MediaIcon

Platform icons (YouTube, Vimeo, etc.).

import { MediaIcon } from '@uniweb/kit'

<MediaIcon type="youtube" size="24" />

Asset

File preview with download functionality.

import { Asset } from '@uniweb/kit'

<Asset value="document.pdf" profile={profile} />

Hooks

useWebsite

Access website instance and utilities.

import { useWebsite } from '@uniweb/kit'

function MyComponent() {
  const {
    website,      // Website instance
    localize,     // Localize multilingual values
    makeHref,     // Transform hrefs (topic:, locale prefixes)
    getLanguage,  // Current language code
    getLanguages  // Available languages
  } = useWebsite()

  return <div>{localize({ en: 'Hello', fr: 'Bonjour' })}</div>
}

useActiveRoute

Detect active navigation state.

import { useActiveRoute } from '@uniweb/kit'

function NavLink({ page }) {
  const { isActive, isActiveOrAncestor } = useActiveRoute()

  return (
    <Link
      to={page.route}
      className={isActiveOrAncestor(page) ? 'font-bold' : ''}
    >
      {page.title}
    </Link>
  )
}

useScrolled

Detect scroll position for sticky headers.

import { useScrolled } from '@uniweb/kit'

function Header() {
  const scrolled = useScrolled(50)  // Threshold in pixels

  return (
    <header className={scrolled ? 'shadow-md' : ''}>
      ...
    </header>
  )
}

useMobileMenu

Mobile menu state management.

import { useMobileMenu } from '@uniweb/kit'

function Navbar() {
  const { isOpen, toggle, close } = useMobileMenu()

  return (
    <>
      <button onClick={toggle}>Menu</button>
      {isOpen && <MobileMenu onClose={close} />}
    </>
  )
}

useAccordion

Accordion/FAQ state management.

import { useAccordion } from '@uniweb/kit'

function FAQ({ items }) {
  const { isOpen, toggle } = useAccordion()

  return items.map((item, i) => (
    <div key={i}>
      <button onClick={() => toggle(i)}>{item.question}</button>
      {isOpen(i) && <p>{item.answer}</p>}
    </div>
  ))
}

useInView

Viewport intersection detection for lazy loading and animations.

import { useInView, useIsInView } from '@uniweb/kit'

function AnimatedSection() {
  const { ref, inView } = useInView({ threshold: 0.2, once: true })

  return (
    <div ref={ref} className={inView ? 'animate-fade-in' : 'opacity-0'}>
      Content appears when scrolled into view
    </div>
  )
}

// Simple boolean version
function LazyImage({ src }) {
  const [ref, isInView] = useIsInView()
  return <div ref={ref}>{isInView && <img src={src} />}</div>
}

useGridLayout

Responsive grid utilities.

import { useGridLayout, getGridClasses } from '@uniweb/kit'

function Gallery({ items }) {
  const { columns } = useGridLayout(items.length, { maxColumns: 4 })

  return (
    <div className={getGridClasses(columns)}>
      {items.map(item => <Card key={item.id} {...item} />)}
    </div>
  )
}

Theme Hooks

Access site theming data at runtime.

import {
  useThemeData,
  useThemeColor,
  useThemeColorVar,
  useColorContext,
  useAppearance
} from '@uniweb/kit'

function ThemedComponent({ block }) {
  // Full theme access
  const theme = useThemeData()
  const palettes = theme?.getPaletteNames()  // ['primary', 'secondary', ...]

  // Get specific color
  const primaryColor = useThemeColor('primary', 500)  // '#3b82f6'
  const primaryVar = useThemeColorVar('primary', 600)  // 'var(--primary-600)'

  // Context-aware (light/medium/dark sections)
  const context = useColorContext(block)  // 'light' | 'medium' | 'dark'

  // Dark mode
  const { scheme, toggle, canToggle } = useAppearance()

  return (
    <div style={{ color: primaryColor }}>
      {canToggle && (
        <button onClick={toggle}>
          {scheme === 'dark' ? 'Light' : 'Dark'}
        </button>
      )}
    </div>
  )
}

Search (@uniweb/kit/search)

Full-text search powered by Fuse.js. Requires fuse.js as a peer dependency in your foundation.

npm install fuse.js

useSearch

Main search hook with debouncing and state management.

import { useSearch } from '@uniweb/kit/search'

function SearchComponent() {
  const { website } = useWebsite()
  const { query, results, isLoading, isEnabled, preload } = useSearch(website)

  if (!isEnabled) return null

  return (
    <div>
      <input onChange={e => query(e.target.value)} placeholder="Search..." />
      {isLoading && <span>Searching...</span>}
      {results.map(r => (
        <a key={r.id} href={r.href}>{r.title}</a>
      ))}
    </div>
  )
}

useSearchWithIntent

Intent-based preloading — loads search index on hover/focus instead of page load.

import { useSearchWithIntent, useSearchShortcut } from '@uniweb/kit/search'

function SearchButton({ onClick }) {
  const { website } = useWebsite()
  const { triggerPreload, intentProps } = useSearchWithIntent(website)

  // Cmd/Ctrl+K shortcut with preload
  useSearchShortcut({
    onOpen: onClick,
    onPreload: triggerPreload,
  })

  return (
    <button onClick={onClick} {...intentProps}>
      Search
    </button>
  )
}

This saves bandwidth — the search index only loads when users show intent to search.

useSearchShortcut

Keyboard shortcut for opening search.

import { useSearchShortcut } from '@uniweb/kit/search'

// Simple
useSearchShortcut(() => setSearchOpen(true))

// With preload on shortcut
useSearchShortcut({
  onOpen: () => setSearchOpen(true),
  onPreload: () => searchClient.preload()
})

createSearchClient

Low-level search client for advanced use.

import { createSearchClient } from '@uniweb/kit/search'

const client = createSearchClient(website, {
  fuseOptions: { threshold: 0.3 },
  defaultLimit: 10
})

// Query
const results = await client.query('authentication', { limit: 5 })

// Preload index
await client.preload()

// Check status
client.isEnabled()
client.getIndexUrl()

Styled Components (@uniweb/kit/styled)

Pre-styled components with Tailwind CSS. Import separately to keep core kit dependency-free.

import { Section, SidebarLayout, Disclaimer } from '@uniweb/kit/styled'

<Section width="lg" padding="md" className="bg-gray-50">
  <h1>Welcome</h1>
</Section>

<SidebarLayout sidebar={<Nav />} sidebarPosition="left">
  <main>Content</main>
</SidebarLayout>

<Disclaimer
  title="Terms of Service"
  content="<p>Please read our terms...</p>"
  triggerText="View Terms"
/>

Utilities

import { cn, stripTags, isExternalUrl, isFileUrl, detectMediaType } from '@uniweb/kit'

// Merge Tailwind classes (uses tailwind-merge)
cn('px-4 py-2', 'bg-blue-500', condition && 'opacity-50')

// Strip HTML tags
stripTags('<p>Hello</p>')  // "Hello"

// URL utilities
isExternalUrl('https://google.com')  // true
isFileUrl('/files/doc.pdf')          // true
detectMediaType('https://youtube.com/...')  // 'youtube'

Architecture

┌─────────────────────────────────────────────────────────────┐
│  Foundation (your code)                                     │
│    ├── imports @uniweb/kit (bundled, tree-shaken)           │
│    └── @uniweb/core marked as external                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  @uniweb/runtime (browser)                                  │
│    ├── Loads foundation dynamically                         │
│    ├── Provides @uniweb/core singleton                      │
│    └── Orchestrates React rendering                         │
└─────────────────────────────────────────────────────────────┘

Why bundle kit but externalize core?

  • Kit: Different foundations may use different subsets of kit. Tree-shaking ensures each foundation only includes what it uses.

  • Core: Contains the Website, Page, and Block classes that must be singletons. The runtime provides these — foundations reference them via the external import.

License

Apache-2.0

About

Toolkit to create and manage Uniweb projects.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •