TypeScriptUI WidgetsWidgets

Widget Examples

Complete widgets you can adapt: lists, dashboards, and forms

These widgets show common layouts end to end.

Product Grid

A responsive grid of product cards that follows the host's theme.

import { useWidgetSDK, useTheme } from '@nitrostack/widgets';

interface Product {
  id: string;
  name: string;
  price: number;
  image_url: string;
}

interface ProductGridData {
  products: Product[];
  pagination: {
    page: number;
    totalPages: number;
  };
}

export default function ProductsGrid() {
  const { isReady, getToolOutput } = useWidgetSDK();
  const theme = useTheme();
  
  if (!isReady) return <div>Loading...</div>;
  
  const data = getToolOutput<ProductGridData>();
  
  const containerStyle = {
    background: theme === 'dark' ? '#000' : '#fff',
    color: theme === 'dark' ? '#fff' : '#000',
    padding: '24px',
    borderRadius: '12px'
  };
  
  const gridStyle = {
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
    gap: '16px',
    marginTop: '16px'
  };
  
  const cardStyle = {
    background: theme === 'dark' ? '#1a1a1a' : '#f5f5f5',
    borderRadius: '8px',
    padding: '16px',
    border: `1px solid ${theme === 'dark' ? '#333' : '#ddd'}`
  };
  
  return (
    <div style={containerStyle}>
      <h2>Products (Page {data.pagination.page} of {data.pagination.totalPages})</h2>
      
      <div style={gridStyle}>
        {data.products.map((product) => (
          <div key={product.id} style={cardStyle}>
            <img
              src={product.image_url}
              alt={product.name}
              style={{
                width: '100%',
                height: '150px',
                objectFit: 'cover',
                borderRadius: '4px',
                marginBottom: '12px'
              }}
            />
            <h3 style={{ fontSize: '16px', marginBottom: '8px' }}>
              {product.name}
            </h3>
            <p style={{ fontSize: '20px', fontWeight: 'bold' }}>
              ${product.price.toFixed(2)}
            </p>
          </div>
        ))}
      </div>
    </div>
  );
}

Dashboard Widget

A metrics dashboard that adapts its layout to the display mode.

import { useWidgetSDK, useTheme, useDisplayMode } from '@nitrostack/widgets';

interface DashboardData {
  user: {
    name: string;
    email: string;
    avatar?: string;
  };
  stats: {
    orders: number;
    spent: number;
    points: number;
  };
  recentOrders: Array<{
    id: string;
    total: number;
    date: string;
  }>;
}

export default function UserDashboard() {
  const { isReady, getToolOutput } = useWidgetSDK();
  const theme = useTheme();
  const displayMode = useDisplayMode();
  
  if (!isReady) return <div>Loading...</div>;
  
  const data = getToolOutput<DashboardData>();
  
  const isFullscreen = displayMode === 'fullscreen';
  
  const containerStyle = {
    background: theme === 'dark' ? '#000' : '#fff',
    color: theme === 'dark' ? '#fff' : '#000',
    padding: isFullscreen ? '48px' : '24px',
    borderRadius: '12px',
    maxWidth: isFullscreen ? '1200px' : '800px'
  };
  
  return (
    <div style={containerStyle}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'center', marginBottom: '24px' }}>
        {data.user.avatar && (
          <img
            src={data.user.avatar}
            alt={data.user.name}
            style={{
              width: '60px',
              height: '60px',
              borderRadius: '50%',
              marginRight: '16px'
            }}
          />
        )}
        <div>
          <h2 style={{ marginBottom: '4px' }}>{data.user.name}</h2>
          <p style={{ color: theme === 'dark' ? '#999' : '#666' }}>
            {data.user.email}
          </p>
        </div>
      </div>
      
      {/* Stats */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(3, 1fr)',
        gap: '16px',
        marginBottom: '24px'
      }}>
        <StatCard label="Orders" value={data.stats.orders} theme={theme} />
        <StatCard label="Total Spent" value={`$${data.stats.spent}`} theme={theme} />
        <StatCard label="Points" value={data.stats.points} theme={theme} />
      </div>
      
      {/* Recent Orders */}
      <h3 style={{ marginBottom: '16px' }}>Recent Orders</h3>
      {data.recentOrders.map((order) => (
        <div
          key={order.id}
          style={{
            background: theme === 'dark' ? '#1a1a1a' : '#f5f5f5',
            padding: '16px',
            borderRadius: '8px',
            marginBottom: '12px',
            display: 'flex',
            justifyContent: 'space-between'
          }}
        >
          <span>Order #{order.id}</span>
          <span>${order.total.toFixed(2)}</span>
          <span style={{ color: theme === 'dark' ? '#999' : '#666' }}>
            {order.date}
          </span>
        </div>
      ))}
    </div>
  );
}

function StatCard({ label, value, theme }: { 
  label: string; 
  value: string | number;
  theme: 'light' | 'dark' | null;
}) {
  return (
    <div style={{
      background: theme === 'dark' ? '#1a1a1a' : '#f5f5f5',
      padding: '20px',
      borderRadius: '8px',
      textAlign: 'center'
    }}>
      <div style={{ fontSize: '28px', fontWeight: 'bold' }}>
        {value}
      </div>
      <div style={{ 
        color: theme === 'dark' ? '#999' : '#666', 
        marginTop: '8px' 
      }}>
        {label}
      </div>
    </div>
  );
}

Next steps

On this page