adk-ui 2.1.0

Dynamic UI generation for ADK-Rust agents - render forms, cards, tables, charts and more
Documentation
import React, { createContext, useContext } from 'react';
import type { Component } from './types';

export type UiKitStatus = 'draft' | 'approved' | 'deprecated';
export type UiKitAssetKind = 'logo' | 'icon' | 'illustration' | 'image' | 'font' | 'model_3d';
export type UiKitDensity = 'compact' | 'comfortable' | 'spacious';
export type UiKitRadius = 'none' | 'sm' | 'md' | 'lg' | 'xl';

export interface UiKitAsset {
    id: string;
    kind: UiKitAssetKind;
    uri: string;
    mimeType: string;
    alt: string;
    sha256?: string;
    license?: string;
    width?: number;
    height?: number;
    byteSize?: number;
}

export interface UiKitSemanticColors {
    primary: string;
    primaryForeground: string;
    secondary: string;
    secondaryForeground: string;
    background: string;
    foreground: string;
    surface: string;
    surfaceForeground: string;
    muted: string;
    mutedForeground: string;
    border: string;
    input: string;
    ring: string;
    destructive: string;
    destructiveForeground: string;
    success: string;
    warning: string;
    info: string;
    chart: string[];
}

export interface UiKitManifest {
    schemaVersion: string;
    id: string;
    name: string;
    version: string;
    status: UiKitStatus;
    provenance?: {
        source: 'imported' | 'generated' | 'hybrid';
        sourceUri?: string;
        generatedBy?: string;
        reviewedBy?: string;
        approvedAt?: string;
    };
    brand: {
        vibe: string;
        industry?: string;
    };
    assets: UiKitAsset[];
    tokens: {
        colors: UiKitSemanticColors;
        typography: {
            bodyFamily: string;
            headingFamily: string;
            monoFamily: string;
            scale?: string;
        };
        density: UiKitDensity;
        radius: UiKitRadius;
        spacing: number;
        motionDuration: number;
    };
    components?: Record<string, {
        component: string;
        variant?: string;
        assetId?: string;
        slots?: Record<string, string>;
    }>;
    templates?: Array<{
        id: string;
        description?: string;
        requiredComponents?: string[];
    }>;
}

export interface UiKitSlotProps {
    component: Component;
    renderDefault: () => React.ReactNode;
}

export type UiKitSlots = Partial<Record<Component['type'], React.ComponentType<UiKitSlotProps>>>;

export const defaultUiKit: UiKitManifest = {
    schemaVersion: '1.0',
    id: 'zavora.ai:adk-ui/extended@0.2.0',
    name: 'ADK UI',
    version: '0.2.0',
    status: 'approved',
    provenance: {
        source: 'imported',
        reviewedBy: 'adk-ui maintainers',
        approvedAt: '2026-08-14',
    },
    brand: {
        vibe: 'clear, capable, calm',
        industry: 'agentic software',
    },
    assets: [],
    tokens: {
        colors: {
            primary: '#0f766e',
            primaryForeground: '#ffffff',
            secondary: '#e7f4f1',
            secondaryForeground: '#173f3a',
            background: '#f7f9f8',
            foreground: '#17211f',
            surface: '#ffffff',
            surfaceForeground: '#17211f',
            muted: '#edf2f1',
            mutedForeground: '#5b6b68',
            border: '#d8e1df',
            input: '#c7d3d0',
            ring: '#0f766e',
            destructive: '#b42318',
            destructiveForeground: '#ffffff',
            success: '#18794e',
            warning: '#a15c00',
            info: '#175cd3',
            chart: ['#0f766e', '#175cd3', '#a15c00', '#7a5af8', '#c4320a', '#0891b2'],
        },
        typography: {
            bodyFamily: 'Avenir Next, Avenir, sans-serif',
            headingFamily: 'Avenir Next, Avenir, sans-serif',
            monoFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
        },
        density: 'comfortable',
        radius: 'lg',
        spacing: 4,
        motionDuration: 160,
    },
};

interface UiKitRuntime {
    manifest: UiKitManifest;
    slots: UiKitSlots;
    provided: boolean;
}

const UiKitContext = createContext<UiKitRuntime>({
    manifest: defaultUiKit,
    slots: {},
    provided: false,
});

export function useAdkUiKit(): UiKitRuntime {
    return useContext(UiKitContext);
}

export interface AdkUiKitProviderProps {
    kit: UiKitManifest;
    slots?: UiKitSlots;
    /** Explicitly permits previewing a non-approved kit. Never enable this in production hosts. */
    allowDraft?: boolean;
    children: React.ReactNode;
}

export function AdkUiKitProvider({ kit, slots = {}, allowDraft = false, children }: AdkUiKitProviderProps) {
    if (kit.status !== 'approved' && !allowDraft) {
        throw new Error(`ADK UI kit "${kit.id}" is ${kit.status}; production rendering requires an approved kit.`);
    }

    return (
        <UiKitContext.Provider value={{ manifest: kit, slots, provided: true }}>
            <style data-adk-ui-kit-styles>{ADK_UI_KIT_CSS}</style>
            {children}
        </UiKitContext.Provider>
    );
}

export function resolveApprovedUiKit(
    requestedKitId: string | undefined,
    kits: readonly UiKitManifest[],
    fallbackKit: UiKitManifest = defaultUiKit,
): UiKitManifest {
    if (fallbackKit.status !== 'approved') {
        throw new Error('The fallback ADK UI kit must be approved.');
    }
    if (!requestedKitId) {
        return fallbackKit;
    }

    const requested = kits.find((kit) => kit.id === requestedKitId);
    return requested?.status === 'approved' ? requested : fallbackKit;
}

export function resolveKitAssetSource(kit: UiKitManifest, source: string): string | null {
    if (!source.startsWith('kit://')) {
        return source;
    }

    const assetId = source.slice('kit://'.length).trim();
    if (!assetId || assetId.includes('/') || assetId.includes('\\')) {
        return null;
    }

    return kit.assets.find((asset) => asset.id === assetId)?.uri ?? null;
}

export function kitStyleVariables(kit: UiKitManifest): React.CSSProperties & Record<string, string | number> {
    const { colors, typography, spacing, motionDuration } = kit.tokens;
    return {
        '--adk-ui-primary': colors.primary,
        '--adk-ui-primary-foreground': colors.primaryForeground,
        '--adk-ui-secondary': colors.secondary,
        '--adk-ui-secondary-foreground': colors.secondaryForeground,
        '--adk-ui-background': colors.background,
        '--adk-ui-foreground': colors.foreground,
        '--adk-ui-surface': colors.surface,
        '--adk-ui-surface-foreground': colors.surfaceForeground,
        '--adk-ui-muted': colors.muted,
        '--adk-ui-muted-foreground': colors.mutedForeground,
        '--adk-ui-border': colors.border,
        '--adk-ui-input': colors.input,
        '--adk-ui-ring': colors.ring,
        '--adk-ui-destructive': colors.destructive,
        '--adk-ui-destructive-foreground': colors.destructiveForeground,
        '--adk-ui-success': colors.success,
        '--adk-ui-warning': colors.warning,
        '--adk-ui-info': colors.info,
        '--adk-ui-font-body': typography.bodyFamily,
        '--adk-ui-font-heading': typography.headingFamily,
        '--adk-ui-font-mono': typography.monoFamily,
        '--adk-ui-radius': radiusValue(kit.tokens.radius),
        '--adk-ui-space': `${Math.max(1, spacing)}px`,
        '--adk-ui-motion-duration': `${Math.max(0, motionDuration)}ms`,
        ...Object.fromEntries(colors.chart.map((color, index) => [`--adk-ui-chart-${index + 1}`, color])),
    };
}

function radiusValue(radius: UiKitRadius): string {
    return {
        none: '0px',
        sm: '4px',
        md: '8px',
        lg: '12px',
        xl: '18px',
    }[radius];
}

export const ADK_UI_KIT_CSS = `
.adk-ui-kit-surface {
  color: var(--adk-ui-foreground);
  font-family: var(--adk-ui-font-body);
}
.adk-ui-kit-surface .adk-ui-heading {
  color: var(--adk-ui-surface-foreground);
  font-family: var(--adk-ui-font-heading);
}
.adk-ui-kit-surface .adk-ui-body,
.adk-ui-kit-surface .adk-ui-label { color: var(--adk-ui-foreground); }
.adk-ui-kit-surface .adk-ui-caption { color: var(--adk-ui-muted-foreground); }
.adk-ui-kit-surface .adk-ui-card,
.adk-ui-kit-surface .adk-ui-chart,
.adk-ui-kit-surface .adk-ui-modal {
  background: var(--adk-ui-surface);
  color: var(--adk-ui-surface-foreground);
  border-color: var(--adk-ui-border);
  border-radius: var(--adk-ui-radius);
}
.adk-ui-kit-surface .adk-ui-card__header,
.adk-ui-kit-surface .adk-ui-card__footer,
.adk-ui-kit-surface .adk-ui-table__head {
  background: var(--adk-ui-muted);
  border-color: var(--adk-ui-border);
}
.adk-ui-kit-surface .adk-ui-button {
  border-radius: var(--adk-ui-radius);
  transition-duration: var(--adk-ui-motion-duration);
}
.adk-ui-kit-surface .adk-ui-button--primary {
  background: var(--adk-ui-primary);
  color: var(--adk-ui-primary-foreground);
}
.adk-ui-kit-surface .adk-ui-button--secondary {
  background: var(--adk-ui-secondary);
  color: var(--adk-ui-secondary-foreground);
}
.adk-ui-kit-surface .adk-ui-button--danger {
  background: var(--adk-ui-destructive);
  color: var(--adk-ui-destructive-foreground);
}
.adk-ui-kit-surface .adk-ui-button--ghost { color: var(--adk-ui-foreground); }
.adk-ui-kit-surface .adk-ui-button--outline {
  color: var(--adk-ui-foreground);
  border-color: var(--adk-ui-border);
}
.adk-ui-kit-surface .adk-ui-control {
  background: var(--adk-ui-surface);
  color: var(--adk-ui-surface-foreground);
  border-color: var(--adk-ui-input);
  border-radius: var(--adk-ui-radius);
  accent-color: var(--adk-ui-primary);
}
.adk-ui-kit-surface .adk-ui-control:focus {
  border-color: var(--adk-ui-ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--adk-ui-ring) 20%, transparent);
}
.adk-ui-kit-surface .adk-ui-alert--info { color: var(--adk-ui-info); }
.adk-ui-kit-surface .adk-ui-alert--success { color: var(--adk-ui-success); }
.adk-ui-kit-surface .adk-ui-alert--warning { color: var(--adk-ui-warning); }
.adk-ui-kit-surface .adk-ui-alert--error { color: var(--adk-ui-destructive); }
.adk-ui-kit-surface .adk-ui-progress__track { background: var(--adk-ui-muted); }
.adk-ui-kit-surface .adk-ui-progress__value { background: var(--adk-ui-primary); }
.adk-ui-kit-surface .adk-ui-tabs { border-color: var(--adk-ui-border); }
.adk-ui-kit-surface .adk-ui-tab--active {
  border-color: var(--adk-ui-primary);
  color: var(--adk-ui-primary);
}
.adk-ui-kit-surface .adk-ui-table { border-color: var(--adk-ui-border); }
.adk-ui-kit-surface .adk-ui-table table,
.adk-ui-kit-surface .adk-ui-table tbody { background: var(--adk-ui-surface); }
.adk-ui-kit-surface .adk-ui-table th,
.adk-ui-kit-surface .adk-ui-table td { color: var(--adk-ui-surface-foreground); }
.adk-ui-kit-surface .adk-ui-divider { border-color: var(--adk-ui-border); }
.adk-ui-kit-surface .adk-ui-code { font-family: var(--adk-ui-font-mono); }
.adk-ui-scene-3d {
  position: relative;
  overflow: hidden;
  margin: 0;
  border: 1px solid var(--adk-ui-border);
  border-radius: calc(var(--adk-ui-radius) * 1.5);
  background: var(--adk-ui-surface);
  box-shadow: 0 24px 80px color-mix(in srgb, var(--adk-ui-foreground) 12%, transparent);
}
.adk-ui-scene-3d__viewport,
.adk-ui-scene-3d__viewport canvas { display: block; width: 100%; }
.adk-ui-scene-3d__caption {
  position: absolute;
  z-index: 1;
  top: 18px;
  left: 18px;
  display: grid;
  max-width: min(420px, calc(100% - 36px));
  gap: 3px;
  padding: 10px 13px;
  border: 1px solid color-mix(in srgb, var(--adk-ui-border) 72%, transparent);
  border-radius: var(--adk-ui-radius);
  background: color-mix(in srgb, var(--adk-ui-surface) 84%, transparent);
  backdrop-filter: blur(14px);
}
.adk-ui-scene-3d__caption strong { font-family: var(--adk-ui-font-heading); }
.adk-ui-scene-3d__caption span { color: var(--adk-ui-muted-foreground); font-size: 0.78rem; }
.adk-ui-scene-3d__actions {
  position: absolute;
  z-index: 1;
  right: 16px;
  bottom: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.adk-ui-scene-3d__actions button,
.adk-ui-scene-3d__actions span {
  border: 1px solid var(--adk-ui-border);
  border-radius: 999px;
  padding: 7px 10px;
  background: color-mix(in srgb, var(--adk-ui-surface) 88%, transparent);
  color: var(--adk-ui-foreground);
  font: 600 0.72rem var(--adk-ui-font-body);
  backdrop-filter: blur(12px);
}
.adk-ui-scene-3d__actions button { cursor: pointer; }
.adk-ui-scene-3d__loading,
.adk-ui-scene-3d__fallback {
  display: grid;
  place-items: center;
  min-height: 240px;
  border-radius: calc(var(--adk-ui-radius) * 1.5);
  background: linear-gradient(120deg, var(--adk-ui-muted), var(--adk-ui-surface), var(--adk-ui-muted));
  background-size: 220% 100%;
  color: var(--adk-ui-muted-foreground);
  animation: adk-ui-scene-loading 1.8s ease infinite;
}
@keyframes adk-ui-scene-loading { 50% { background-position: 100% 0; } }
@media (prefers-reduced-motion: reduce) { .adk-ui-scene-3d__loading { animation: none; } }
.adk-ui-kit-surface .adk-ui-badge--default,
.adk-ui-kit-surface .adk-ui-badge--secondary {
  background: var(--adk-ui-muted);
  color: var(--adk-ui-muted-foreground);
}
`;