# Uhuru UI

Uhuru UI is Digitwhale Innovations' React component library for Uhuru products.

It is intended for external React apps as well as internal product work.

## Can A New App Use It?

Yes.

A brand-new React app can install the published package and use it immediately, as long as:

- the app uses React 17, 18, or 19
- the app has a modern bundler such as Vite, Next.js, or another build tool that can consume ESM/TypeScript-style package source
- you import the Uhuru stylesheet once
- you wrap the app in `UhuruProvider`

## Install

```bash
npm install @digitwhale_innovations/uhuru-ui
```

If you want to pin a specific release:

```bash
npm install @digitwhale_innovations/uhuru-ui@0.1.5
```

## Minimal React Setup

```tsx
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { UhuruProvider } from "@digitwhale_innovations/uhuru-ui";
import "@digitwhale_innovations/uhuru-ui/styles.css";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <UhuruProvider theme="light" density="compact">
      <App />
    </UhuruProvider>
  </React.StrictMode>,
);
```

```tsx
// src/App.tsx
import { Button, Card, CardBody, CardHeader } from "@digitwhale_innovations/uhuru-ui";

export default function App() {
  return (
    <main style={{ padding: 24 }}>
      <Card>
        <CardHeader
          eyebrow="Workspace"
          title="Uhuru Billing"
          description="A clean external React setup."
        />
        <CardBody>
          <Button>Save changes</Button>
        </CardBody>
      </Card>
    </main>
  );
}
```

## What You Need To Import

Always import components from the package root:

```tsx
import { Button, Card, TextField } from "@digitwhale_innovations/uhuru-ui";
```

Always import styles once:

```tsx
import "@digitwhale_innovations/uhuru-ui/styles.css";
```

## Core Concepts

- `UhuruProvider` controls theme, density, radius, preset accent selection, and shared app tokens.
- `UhuruProvider` can also configure the built-in alert system globally.
- Use `presetAccent` to switch between the bundled accent presets or a custom preset key.
- Use `accentPresets` to add or replace preset options without rebuilding component styles.
- Use `colors` when a product needs to refine or replace any token after the preset is resolved.
- The package ships source-first exports, so a modern React bundler is the safest consumer setup.
- Styles live in a separate stylesheet and should be loaded once near the app entry.

Accent presets are theme-aware. A preset can define light and dark overrides, while `colors` remains the final escape hatch for product-specific token tweaks.
- The design system now ships with preset accents such as `black`, `brown`, `blue`, `teal`, `rose`, and `amber`.

## Markdown And Advanced Blocks

Use `Markdown` for assistant responses, user-authored content, documentation,
and formatted product copy. It renders code, math, diagrams, tables, lists,
media, files, and embeds with Uhuru surfaces. Code, math, and diagram blocks
include copy controls automatically.

```tsx
import { Markdown } from "@digitwhale_innovations/uhuru-ui";

<Markdown
  value={content}
  onCopy={(value) => navigator.clipboard.writeText(value)}
/>
```

Markdown supports these automatic advanced forms:

````markdown
```steps
1. Install Uhuru | Add the published package.
2. Add the provider | Configure the shared defaults.
```

```checklist
- [x] Install package
- [ ] Add provider
```

```key-value
Theme: dark
Density: compact
```

```timeline
2026-08-09 | Released | Advanced Markdown blocks.
```

```file
guide.md | Markdown guide | /guide.md
```

```media
image | /product.png | Product preview
```

```embed
Open guide | https://example.com/guide
```
````

The exported block components are `CodeBlock`, `MathBlock`, `DiagramBlock`,
`CalloutBlock`, `QuoteBlock`, `TableBlock`, `StepsBlock`, `ChecklistBlock`,
`KeyValueBlock`, `TimelineBlock`, `FileBlock`, `MediaBlock`, and `EmbedBlock`.
Use the components directly for typed API data and use fenced formats when
the source is already Markdown text.

Override semantic colors without replacing the defaults. Values can be shared
across themes or supplied per theme:

```tsx
<UhuruProvider
  variant="glass"
  presetAccent="teal"
  accentPresets={{
    graphite: {
      accentSolid: "#334155",
      accentStrong: "#1f2937",
      actionPrimary: "#334155",
      actionPrimaryHover: "#1f2937",
      borderAccent: "#334155",
    },
  }}
  colors={{
    light: { bgBase: "#f7faf9" },
    dark: { bgBase: "#071211", surfaceDefault: "#0d1f1d" },
  }}
>
  {children}
</UhuruProvider>
```

Unset values continue using Uhuru's built-in light and dark palettes.

Use `variant="glass"` to apply Uhuru's translucent surface system across
cards, panels, controls, overlays, and data surfaces:

```tsx
<UhuruProvider variant="glass" theme="dark" density="comfortable">
  <App />
</UhuruProvider>
```

The glass variant keeps the same component APIs, semantic colors, radius, and
density controls. It adds translucent surfaces, backdrop blur, softer borders,
and a subtle atmosphere. Use `variant="default"` or omit the prop for the
standard opaque Uhuru treatment.

## Backgrounds

Use `background` on `UhuruProvider` when the application needs a consistent
pattern or media layer behind the interface. Uhuru includes four built-in
patterns: `grid`, `cross`, `circuit`, and `dots`. A background uses one source
at a time: video takes precedence over an image, an image takes precedence
over the bundled logo, and any media source disables the pattern. Backgrounds
are fixed to the viewport by default; use `fixed: false` when the asset should
move with the document.

```tsx
<UhuruProvider
  background={{
    pattern: "circuit",
    patternColor: "rgba(255, 255, 255, 0.06)",
    patternOpacity: 0.9,
  }}
>
  <App />
</UhuruProvider>
```

For a branded image layer, use an application asset or the built-in Uhuru
logo. `blur`, `assetOpacity`, `overlay`, `overlayOpacity`, `position`, and
`size` control the visual treatment without requiring custom page wrappers:

```tsx
<UhuruProvider
  background={{
    logo: true,
    logoBlur: 10,
    logoOpacity: 0.14,
    overlay: "#101111",
    overlayOpacity: 0.72,
  }}
>
  <App />
</UhuruProvider>
```

Use a video instead of a pattern or image when the background should move:

```tsx
<UhuruProvider
  background={{
    video: { src: "/media/ambient.mp4", poster: "/media/ambient-poster.jpg" },
    blur: 6,
    assetOpacity: 0.35,
    overlay: "#101111",
    overlayOpacity: 0.58,
  }}
>
  <App />
</UhuruProvider>
```

## Side Panels

Use `UhuruPanel` when you want to control panel content yourself.
Render panel content through the matching `leftPanel` or `rightPanel` slot of
`UhuruAppLayout`:

```tsx
import {
  UhuruAppLayout,
  UhuruPanel,
} from "@digitwhale_innovations/uhuru-ui";

<UhuruAppLayout
  leftPanel={
    <UhuruPanel
      side="left"
      config={{ header: <strong>Workspace</strong>, footer: <small>Signed in</small> }}
    >
      <nav>{/* your navigation */}</nav>
    </UhuruPanel>
  }
>
  <main>{/* page content */}</main>
</UhuruAppLayout>;
```

For grouped navigation, use `UhuruGroupedPanel` in the `leftPanel` slot.

### AI panel

Use `aiPanelConfig` for a temporary AI workspace that can take over either side
of the app layout. When it closes, any normal panel on that side is restored
unchanged. The configuration extends `PanelConfig`, so resize, rail, collapse,
hover expansion, header, footer, and min/max sizing all behave consistently.

```tsx
import {
  AiPanel,
  Button,
  UhuruAppLayout,
  useAiPanel,
} from "@digitwhale_innovations/uhuru-ui";

function CreateAgentAction() {
  const aiPanel = useAiPanel();
  return <Button onClick={aiPanel.open}>Create with AI</Button>;
}

<UhuruAppLayout
  aiPanelConfig={{
    enabled: true,
    position: "left", // Defaults to left.
    content: (
      <AiPanel
        messages={messages}
        value={draft}
        onChange={setDraft}
        onSubmit={createAgent}
      />
    ),
    ariaLabel: "AI agent builder",
    allowResize: true,
    collapsible: true,
    defaultSize: 360,
    minSize: 300,
    maxSize: 560,
    showRail: false,
    header: <strong>Build an agent</strong>,
  }}
  leftPanel={<WorkspaceNavigation />}
>
  <CreateAgentAction />
</UhuruAppLayout>;
```

`useAiPanel()` returns `open`, `close`, `toggle`, `isOpen`, `enabled`, and
`position`. Calling it without an enabled, fully configured AI panel shows an
accessible error toast instead of throwing. On mobile, opening the AI panel
automatically opens the matching drawer.

Ungrouped custom navigation can restore route-owned content by passing a route
registry to `UhuruPanel`. The panel keeps rendering your own children; the
registry tells the layout which element to restore on first load:

```tsx
<UhuruPanel
  side="left"
  defaultRoute="/dashboard/orders"
  routes={[
    { path: "/dashboard/orders", element: <OrdersPage /> },
    { path: "/dashboard/customers", element: <CustomersPage /> },
  ]}
>
  <nav>{/* your native links or buttons */}</nav>
</UhuruPanel>
```

On refresh, the current browser path wins over `defaultRoute`. If no path
matches, the configured default or first route is opened. Omit `routes` when a
router such as React Router owns navigation.

```tsx
import { useState } from "react";
import {
  UhuruGroupedPanel,
  UhuruAppLayout,
} from "@digitwhale_innovations/uhuru-ui";

function WorkspaceLayout() {
  const [activeId, setActiveId] = useState("overview");

  const groups = [
    {
      id: "workspace",
      label: "Workspace",
      items: [
        { id: "overview", label: "Overview" },
        { id: "activity", label: "Activity", badge: "3" },
      ],
    },
    {
      id: "projects",
      label: "Projects",
      items: [
        {
          id: "uhuru",
          label: "Uhuru",
          children: [
            { id: "billing", label: "Billing" },
            { id: "settings", label: "Settings" },
          ],
        },
      ],
    },
  ];

  return (
    <UhuruAppLayout
      leftPanel={
        <UhuruGroupedPanel
          activeId={activeId}
          groups={groups}
          onItemSelect={(item) => setActiveId(item.id)}
        />
      }
    >
      <main>{/* render the active page here */}</main>
    </UhuruAppLayout>
  );
}
```

`activeId` is controlled by the consuming page. Each item needs a unique `id`,
and selecting an item calls `onItemSelect`, where the app can update the
active page, route, or local view. For route-owned items, set
`defaultRoute="/overview"` or `defaultRoute="overview"`; without it, the
first item with both `path` and `element` opens automatically. No route opens
when the panel is empty, `activeId` is controlled, or a route is already active.
When the browser is refreshed on a matching item path, such as
`/dashboard/orders`, that path is restored before the default route is used.
The panel itself only renders navigation; it does not decide what the main
content should be for non-route items.

For route-level pages, panel items can own their route with `path` and
`element`. Put `UhuruAppLayout` outside the route content and the layout stays
mounted while the panel changes the active page:

Protect one route by wrapping its `element`:

```tsx
const ordersRoute = {
  path: "/dashboard/orders",
  element: (
    <UhuruRouteGuard
      check={() => Boolean(session?.user)}
      fallback={<SignInPage />}
      redirectTo="/sign-in"
    >
      <OrdersPage />
    </UhuruRouteGuard>
  ),
};
```

Protect a whole group or shell by placing the guard around that composition
instead. `allow` is useful for a known boolean; `check` is useful for custom
logic. Use `onDenied` for logging or cleanup. The guard is synchronous and
router-agnostic; use a loading state in the parent while an asynchronous
session check is being resolved.

```tsx
import { BrowserRouter } from "react-router-dom";

<BrowserRouter>
  <UhuruAppLayout
    leftPanel={
      <UhuruGroupedPanel
        groups={[
          {
            id: "workspace",
            items: [
              {
                id: "overview",
                label: "Overview",
                path: "/overview",
                element: <OverviewPage />,
              },
              {
                id: "activity",
                label: "Activity",
                path: "/activity",
                element: <ActivityPage />,
              },
            ],
          },
        ]}
      />
    }
  >
    <main />
  </UhuruAppLayout>
</BrowserRouter>;
```

Set `route: false` for an item that should run its callback or toggle a local
control without becoming a panel route. Use `href` instead of `path` when the
router or browser should own navigation. The UI kit does not require a router
dependency.

The flat `Sidebar` accepts the same route entry shape through `items`:

```tsx
<Sidebar
  items={[
    {
      value: "overview",
      label: "Overview",
      path: "/overview",
      element: <OverviewPage />,
    },
    {
      value: "new-item",
      label: "New item",
      route: false,
      onSelect: openCreateDialog,
    },
  ]}
/>
```

Resize behavior can be enabled independently:

```tsx
<UhuruAppLayout
  header={
    <UhuruAppLayoutHeader
      variant="classic"
      profile={<Avatar src={user.image} name={user.name} size="md" />}
      title="Uhuru Workspace"
      description={workspace.name}
      search={<SearchInput aria-label="Global search" placeholder="Search workspace" />}
      actions={<WorkspaceActions />}
      mobileActionItems={workspaceMenuItems}
      leftTogglePlacement="header"
      rightTogglePlacement="header"
      height={56}
    />
  }
  leftPanel={<UhuruGroupedPanel groups={groups} />}
  leftPanelConfig={{
    allowResize: true,
    collapsible: true,
    expandIcon: <CaretDoubleRight />,
    maxSize: 420,
    minSize: 240,
  }}
  rightPanel={<UhuruPanel config={{ header: "Details" }} side="right">...</UhuruPanel>}
  rightPanelConfig={{ allowResize: false, collapsible: true }}
>
  {children}
</UhuruAppLayout>
```

`UhuruAppLayoutHeader` has two structural variants. `classic` orders profile,
identity, search, and actions from left to right. `standard` places actions
first and the identity/profile group at the right. Panel toggles remain at the
outer edges in both variants. On mobile, search and non-panel actions move into
one overflow popover while both enabled panel toggles remain directly
accessible. Use `mobileActionItems` for menu-style actions or `mobileActions`
for custom popup content. Compact mode defaults to `1023px`; customize it with
`compactBreakpoint`. The legacy `leading`, `center`, and `trailing` props remain
aliases for `profile`, `search`, and `actions`.

`leftPanelConfig` and `rightPanelConfig` share the same `PanelConfig` interface,
so each panel can independently opt into resizing, collapsing, sizing, and
custom icons without a second set of layout props. Set `expandOnHover` to
temporarily open a collapsed side rail while the pointer is over it, then
collapse it again when the pointer leaves. While temporarily expanded, the
collapse button icon and `aria-expanded` state also switch to match the visible
panel.

Use `UhuruPanel` for a standalone panel. Its required `side` prop accepts
`left`, `right`, or `bottom`:

```tsx
import { UhuruPanel } from "@digitwhale_innovations/uhuru-ui";

<UhuruPanel side="bottom" config={{ header: "Terminal", collapsible: true }}>
  {/* terminal content */}
</UhuruPanel>
```

## Header

Use `UhuruHeader` for a flexible responsive application header. The desktop
navigation is hidden on small screens and `mobileMenu` is revealed with an
icon button:

```tsx
<UhuruHeader
  brand={<strong>Uhuru Finance</strong>}
  navigation={<><a href="/overview">Overview</a><a href="/reports">Reports</a></>}
  actions={<Button size="compact">Create</Button>}
  mobileMenu={<nav className="grid gap-2"><a href="/overview">Overview</a><a href="/reports">Reports</a></nav>}
  sticky
/>
```

Pass the header to `UhuruAppLayout` with its `header` prop when it should span
the application shell.

## Flutter-Style Layout Primitives

Uhuru includes small web equivalents of Flutter's everyday composition
widgets. They render regular HTML elements and accept normal `className`,
`style`, and DOM props:

- `Form`: semantic `<form>` with vertical spacing between fields.
- `Row`: horizontal flex layout.
- `Column`: vertical flex layout.
- `Padding`: edge-inset wrapper.
- `Margin`: outer-spacing wrapper.
- `Container`: existing centered width container.

`Row` and `Column` use Flutter's axis terminology. `mainAxisSize="min"` sizes
the layout to its content; the default `"max"` fills the available axis.

```tsx
import {
  Button,
  Column,
  Form,
  Padding,
  Row,
  TextField,
} from "@digitwhale_innovations/uhuru-ui";

<Form onSubmit={handleSubmit}>
  <Column gap={12}>
    <TextField label="Workspace name" />
    <Row
      crossAxisAlignment="center"
      mainAxisAlignment="spaceBetween"
      wrap
    >
      <Padding padding={{ vertical: 4 }}>
        <span>Required fields</span>
      </Padding>
      <Button type="submit">Save changes</Button>
    </Row>
  </Column>
</Form>;
```

`Padding` and `Margin` accept a number, CSS size, or edge-inset object:

```tsx
<Padding padding={{ all: 16 }} />
<Padding padding={{ horizontal: 24, vertical: 12 }} />
<Margin margin={{ top: 8, bottom: 24 }} />
```

Available `mainAxisAlignment` values are `start`, `center`, `end`,
`spaceBetween`, `spaceAround`, and `spaceEvenly`. Available
`crossAxisAlignment` values are `start`, `center`, `end`, `stretch`, and
`baseline`.

Semantic spacing values are supported anywhere a layout spacing value is
accepted: `xs` (4px), `sm` (8px), `md` (12px), `lg` (16px), `xl` (24px), and
`2xl` (32px). Numeric values are pixels and CSS strings such as `1rem` or
`clamp(8px, 2vw, 24px)` remain supported.

```tsx
<Column gap="lg">
  <Row gap="sm" mainAxisAlignment="spaceBetween" crossAxisAlignment="center">
    <Text>Orders</Text>
    <Button>New order</Button>
  </Row>
  <Padding padding={{ horizontal: "md", vertical: "sm" }}>
    Content
  </Padding>
</Column>
```

Additional Flutter-guided helpers are available for composing responsive
surfaces:

```tsx
import {
  AspectRatio,
  Button,
  EmptyState,
  Expanded,
  Positioned,
  SizedBox,
  Spacer,
  Stack,
  Visibility,
  Wrap,
} from "@digitwhale_innovations/uhuru-ui";

<Row>
  <Expanded>Primary content</Expanded>
  <SizedBox width={12} />
  <Spacer />
  <Visibility visible={showActions}>
    <Button>Continue</Button>
  </Visibility>
</Row>

<Stack className="min-h-48">
  <div>Base layer</div>
  <Positioned right={16} top={16}>
    <Badge>Live</Badge>
  </Positioned>
</Stack>

<Wrap spacing={12} runSpacing={8}>
  {tags.map((tag) => <Badge key={tag}>{tag}</Badge>)}
</Wrap>

<AspectRatio ratio={16 / 9}>Responsive content</AspectRatio>;
```

`Expanded`, `Flexible`, and `Spacer` are intended as children of `Row` or
`Column`. `Positioned` is intended as a child of `Stack`. `Visibility` can
either remove hidden content from layout or preserve its size with
`maintainSize`.

## Media

`Image` and `VideoPlayer` provide responsive media frames while keeping the
native HTML props and events available:

```tsx
import { Image, VideoPlayer } from "@digitwhale_innovations/uhuru-ui";

<Image
  alt="Workspace preview"
  aspectRatio={16 / 9}
  fit="cover"
  src="/workspace-preview.jpg"
  fallback={<EmptyState title="Preview unavailable" />}
/>

<VideoPlayer
  controls
  fit="contain"
  poster="/workspace-poster.jpg"
  sources={[
    { src: "/workspace.webm", type: "video/webm" },
    { src: "/workspace.mp4", type: "video/mp4" },
  ]}
/>;
```

Both components support responsive sizing, aspect ratios, object-fit modes,
fallback content, and standard browser loading/error behavior.

Text inputs, `SearchInput`, `TextArea`, and `Select` accept `prefixIcon` and
`suffixIcon` React nodes. The package uses Phosphor for its internal icons,
while consumers can provide any compatible icon node:

```tsx
import { MagnifyingGlass, User } from "@phosphor-icons/react";

<TextField prefixIcon={<User />} suffixIcon={<MagnifyingGlass />} />
<Select prefixIcon={<User />} options={[{ label: "Admin", value: "admin" }]} />
```

`SearchInput` can render a lightweight result surface with `searchDropArea`.
Pass result objects for the default keyboard-accessible list, a React node for
fixed custom content, or a render function for query-aware custom content. The
surface uses `FloatingPortal`, stays inside the viewport, and renders above
card and panel stacking contexts.

```tsx
<SearchInput
  value={query}
  onChange={(event) => setQuery(event.target.value)}
  searchDropArea={[
    { id: "button", label: "Button", description: "Action component" },
    { id: "layout", label: "UhuruAppLayout", description: "Application shell" },
  ]}
  onSearchResultSelect={openResult}
/>
```

`Input` and `TextField` pass through native HTML input types such as `email`,
`password`, `number`, `date`, `url`, `tel`, `search`, `time`, and file-related
types. Uhuru also provides `number-with-options`, which renders a numeric
input with a native datalist:

```tsx
<Input
  label="Budget"
  type="number-with-options"
  options={[
    { label: "Starter", value: "100" },
    { label: "Team", value: "500" },
  ]}
  prefixIcon={<CurrencyDollar />}
  suffixIcon={<Calculator />}
/>
```

For per-instance custom rows, provide `renderItem`. The panel still owns
selection, active state, nesting, and accessibility:

```tsx
<UhuruAppLayout
  leftPanel={
    <UhuruGroupedPanel
      activeId="overview"
      onItemSelect={(item) => navigate(`/workspace/${item.id}`)}
      renderItem={(item, { active }) => (
        <>
          <span className={active ? "font-semibold" : ""}>{item.label}</span>
          {item.badge ? <span>{item.badge}</span> : null}
        </>
      )}
      groups={[
        {
          id: "workspace",
          label: "Workspace",
          items: [{ id: "overview", label: "Overview", badge: "New" }],
        },
      ]}
    />
  }
>
  <main>{/* page content */}</main>
</UhuruAppLayout>;
```

Use the `rightPanel` slot for right-side content. No hook is required because
the layout slot determines where the panel renders, while `UhuruPanel` owns the
panel frame, scrolling, header, and footer.

## Alerts

Use `useUhuruAlert()` to trigger app-level alerts from any component inside `UhuruProvider`.

```tsx
import { Button, useUhuruAlert } from "@digitwhale_innovations/uhuru-ui";

export function SaveButton() {
  const { alert } = useUhuruAlert();

  return (
    <Button
      onClick={() =>
        alert({
          title: "Saved",
          description: "Your changes were applied successfully.",
          tone: "success",
          dismissible: true,
        })
      }
    >
      Save changes
    </Button>
  );
}
```

You can also configure the alert system at the provider level:

```tsx
import { UhuruProvider } from "@digitwhale_innovations/uhuru-ui";

<UhuruProvider
  alertConfig={{
    dismissible: false,
    position: "top-right",
    minWidth: 280,
    maxWidth: 420,
    offset: 24,
    gap: 12,
    duration: 5000,
    stackLimit: 3,
    zIndex: 70,
  }}
>
  <App />
</UhuruProvider>
```

Command alerts use the same `Alert` component and stylesheet as inline alerts.
The trailing X button is optional and is hidden by default. Enable it for one
alert with `dismissible: true`, or set `alertConfig.dismissible` to `true` to
enable it for all alerts from that provider.

For inline alerts, use the same prop directly:

```tsx
<Alert dismissible onDismiss={() => setVisible(false)} title="Heads up">
  System updates are available.
</Alert>
```

## React Compatibility

Supported React peer range:

- React 17
- React 18
- React 19

## Available Component Areas

### Actions

- `Button`
- `IconButton`
- `LinkButton`
- `SplitButton`
- `Menu`
- `DropdownMenu`
- `UhuruSignInWithDigitwhale`

### Inputs

- `TextField`
- `TextArea`
- `SearchInput`
- `Select`
- `Combobox`
- `PasswordInput`
- `OtpInput`
- `DatePicker`
- `TimePicker`
- `FileUpload`
- `AIInput`
- `FormField`
- `NumberInput`

`AIInput` supports a full composer and a compact launch/search treatment. Use
`variant="compact"` with up to two `preTrailingActions` and one
`mainTrailingAction`; all actions are typed descriptors with a label, optional
leading icon, disabled state, and callback. File picking and submit payloads
are shared with the full composer.

Task progress uses the same bounded, theme-aware surface as the composer's
queue and question UI. Pass task records with `id`, `title`, `detail`, and a
`status` of `pending`, `in_progress`, `completed`, or `failed`. The task
surface renders above the queue, starts expanded, and collapses to one summary
row before expanding into the individual task list. The state is presentation
state; update `tasks` from the consuming app as work progresses.

```tsx
import { AIInput, type AIInputTask } from "@digitwhale_innovations/uhuru-ui";

const tasks: AIInputTask[] = [
  {
    detail: "Review the requested change before acting.",
    id: "task-1",
    status: "in_progress",
    title: "Review the requested change",
  },
  {
    detail: "Confirm the outcome matches the intended result.",
    id: "task-2",
    status: "pending",
    title: "Verify the result",
  },
];

<AIInput
  onSubmit={({ prompt }) => send(prompt)}
  placeholder="Ask Uhuru AI anything..."
  queue={queuedItems}
  queueEnabled
  tasks={tasks}
  showTaskProgress
/>;
```

Set `showTaskProgress={false}` when a consumer only wants the task selector
behavior. Existing selector tasks remain compatible with the older
`label`/`description` shape; records are included in the progress surface when
they provide `title`, `detail`, or `status`.

### Selection

- `Checkbox`
- `Radio`
- `Switch`
- `SegmentedControl`
- `Slider`

### Feedback

- `Alert`
- `EmptyState`
- `EmptyStateScreen`
- `NotFoundScreen`
- `Skeleton`
- `Spinner`
- `Tooltip`
- `ProgressBar`

### Navigation

- `Breadcrumbs`
- `Pagination`
- `Sidebar`
- `Stepper`
- `Tabs`
- `TopNav`

### Data Display

- `Table`
- `DataGrid`
- `List`
- `Timeline`
- `StatCard`

`Table` and `DataGrid` accept optional `title` and `subtitle` content so a
dataset can explain itself without a separate wrapper:

```tsx
<Table
  title="Recent orders"
  subtitle="Orders received across all storefronts."
  leadingIcon={<ClipboardText />}
  showPagination
  currentPage={page}
  itemCount={124}
  columns={columns}
  data={rows}
  onSortChange={setSort}
/>

<DataGrid
  title="Workspace entries"
  subtitle="Searchable and paginated records."
  leadingIcon={<ChartBar />}
  showPagination={false}
  columns={columns}
  data={rows}
  currentPage={page}
  itemCount={124}
/>
```

### Overlay

- `Modal`
- `Drawer`
- `Popover`
- `ConfirmDialog`
- `CommandPalette`
- `Toast`
- `useOverlay`

### Layout

- `Stack`
- `Expanded`
- `Flexible`
- `Spacer`
- `SizedBox`
- `Wrap`
- `AspectRatio`
- `Visibility`
- `Positioned`
- `Grid`
- `Center`
- `Form`
- `Row`
- `Column`
- `Padding`
- `Margin`
- `Container`
- `Navbar`
- `Panel`
- `UhuruPanel`
- `Divider`
- `PageHeader`

### Media

- `Image`
- `VideoPlayer`

### Typography and Status

- `Heading`
- `Text`
- `Label`
- `Caption`
- `Code`
- `Link`
- `MonoDataText`
- `Badge`
- `Tag`
- `Avatar`
- `PresenceIndicator`

`Avatar` supports initials, image URLs, custom assets, and an optional editable
file-picker mode:

```tsx
<Avatar
  accept=".png,.jpg,.jpeg,.webp"
  editable
  initials="AL"
  maxFileSize={5 * 1024 * 1024}
  name="Ada Lovelace"
  onEdit={(file) => uploadAvatar(file)}
  onEditError={({ message }) => showError(message)}
  src={profile.avatarUrl}
/>
```

### App Shell and Foundations

- `UhuruAppLayout`
- `UhuruSectionHeading`
- `tokens`
- `cn`
- `showCode`
- `useUhuruCode`

## Example Usage

```tsx
import {
  Badge,
  Button,
  Card,
  CardBody,
  CardHeader,
  Modal,
  useOverlay,
  UhuruProvider,
} from "@digitwhale_innovations/uhuru-ui";

export function WorkspacePanel() {
  const modal = useOverlay();

  return (
    <UhuruProvider theme="light" density="compact">
      <Card>
        <CardHeader eyebrow="Workspace" title="Uhuru Billing" description="Current production workspace." />
        <CardBody className="flex flex-wrap gap-3">
          <Badge tone="accent">Live</Badge>
          <Button onClick={modal.open}>Open modal</Button>
        </CardBody>
      </Card>

      <Modal isOpen={modal.isOpen} title="Review changes">
        Content goes here.
      </Modal>
    </UhuruProvider>
  );
}
```

## Notes For External Consumers

- This package is published and can be installed in a new React app like any other dependency.
- For the smoothest setup, use a modern toolchain such as Vite.
- If your bundler does not transpile TypeScript or ESM from dependencies, you may need to adjust that configuration.
- Do not rely on the local workspace source paths inside this repository when consuming the published package externally.

## More Setup Help

For a step-by-step external React setup, see [REACT_SETUP.md](./REACT_SETUP.md).

## Repository

Source and issues: [Whales-Group/digitwhale_uhuru_ui_kit](https://github.com/Whales-Group/digitwhale_uhuru_ui_kit)
## Authentication Pages

Uhuru provides auth UI as reusable pages, not as a backend or session manager.
Use `AuthFlow` when the application wants one complete coordinated flow, or use
the individual pages when each screen has its own route and loader state.

```tsx
import {
  AuthFlow,
  CreateAccountPage,
  ForgotPasswordPage,
  LoginPage,
  OtpVerificationPage,
  PasskeyPage,
} from "@digitwhale_innovations/uhuru-ui";

<AuthFlow
  initialPage="login"
  onForgotPassword={(email) => api.requestPasswordReset(email)}
  onOtpSubmit={(code) => api.verifyOtp(code)}
  onPasskey={() => api.signInWithPasskey()}
  onCreateAccount={(data) => api.createAccount(data)}
/>

<LoginPage
  onSubmit={({ email, password }) => api.signIn(email, password)}
  onForgotPassword={(email) => navigate(`/forgot-password?email=${email}`)}
  onCreateAccount={() => navigate("/create-account")}
  onPasskey={() => navigate("/passkey")}
/>
```

The standalone pages are `LoginPage`, `ForgotPasswordPage`,
`OtpVerificationPage`, `PasskeyPage`, and `CreateAccountPage`. The account page
is stepped automatically: name, email/password, then date of birth and phone.
Its `onSubmit` callback receives `{ firstName, lastName, email, password,
dateOfBirth, phone }`. Auth components intentionally leave navigation, API
requests, WebAuthn, rate limiting, tokens, and session storage to the consumer.

The coordinator is configurable end to end:

```tsx
<AuthFlow
  initialPage="otp"
  frame={{ animation: "slide-up", variant: "quiet" }}
  otp={{ channel: "your phone", length: 6, loading: isVerifying, error: verifyError }}
  passkey={{ loading: isStartingPasskey, error: passkeyError }}
  createAccount={{ defaultValues: { email: inviteEmail } }}
  onScreenChange={setAuthScreen}
  onLoginSubmit={() => "two-factor"}
  renderPage={(screen, navigation) =>
    screen === "login"
      ? <CustomLogin onCreateAccount={() => navigation.go("create-account")} />
      : undefined
  }
/>
```

`initialPage` selects `login`, `forgot-password`, `otp`, `passkey`, or
`create-account`. `initialScreen` is a compatibility alias. Per-page objects
accept the standalone page props. `renderPage` replaces a built-in screen and
receives `screen`, `go`, and `back`. Custom auth pages should use Uhuru
`TextField`, `PasswordInput`, and `OtpInput` to preserve labels, icons, focus
states, obscuring, validation, and theme behavior.

`TwoFactorPage` adds `authenticator`, `email-otp`, and `recovery-key` methods.
Pass `availableMethods` to control which methods are shown, `defaultMethod` or
controlled `method` to select one, and handle `onSubmit({ method, value })`.
`animation` supports `none`, `fade`, `slide-up`, and `scale`; `variant` supports
`default`, `minimal`, and `quiet`. `error`, `loading`, `onBack`, `onResend`,
`onMethodChange`, and submit callbacks are available for the relevant view.
`onLoginSubmit` and `onRecoverySubmit` receive the submitted value plus flow
navigation and may return `"two-factor"` or another auth screen to transition
seamlessly. `onTwoFactorSubmit` supports the same transition pattern after
verification.

AuthFlow is route-aware. By default it exposes the focused `login` and `otp`
views at `/login` and `/verify-code`. Configure a larger flow explicitly:

```tsx
<AuthFlow
  views={["login", "otp", "two-factor"]}
  routes={{ login: "/login", otp: "/verify-code", "two-factor": "/2fa" }}
  onLoginSubmit={() => "otp"}
  onTwoFactorSubmit={verifyTwoFactor}
  onRouteChange={(path) => router.navigate(path)}
  currentPath={location.pathname}
/>
```

Every screen has a routable path. If the current path is unknown, or maps to a
screen not listed in `views`, Uhuru renders an explicit route error instead of
silently showing an unrelated page. Use `enabledViews` as an alias for
`views`.
