FoundryKit

Integration

How to integrate FoundryKit components with primitives

Integration with Primitives

FoundryKit components are built on top of primitives and are designed to work seamlessly together. This guide shows you how to integrate components with primitives effectively.

Basic Integration

Using Components with Primitives

Components can be used alongside primitives in the same interface:

import { Button, Input, Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { SearchBar, Calendar } from '@foundrykit/components'
import { useState } from 'react'

function IntegratedInterface() {
  const [date, setDate] = useState<Date>()
  const [searchQuery, setSearchQuery] = useState('')

  return (
    <Card className="w-full max-w-2xl">
      <CardHeader>
        <CardTitle>Search and Filter</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="space-y-2">
          <label className="text-sm font-medium">Search</label>
          <SearchBar
            placeholder="Search items..."
            onSearch={setSearchQuery}
          />
        </div>
        
        <div className="space-y-2">
          <label className="text-sm font-medium">Select Date</label>
          <Calendar
            mode="single"
            selected={date}
            onSelect={setDate}
            className="rounded-md border"
          />
        </div>
        
        <div className="flex space-x-2">
          <Button>Apply Filters</Button>
          <Button variant="outline">Clear All</Button>
        </div>
      </CardContent>
    </Card>
  )
}

Form Integration

Create forms that combine primitives and components:

import { Button, Input, Label, Textarea, Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { DatePicker } from '@foundrykit/components'
import { useState } from 'react'

function IntegratedForm() {
  const [formData, setFormData] = useState({
    title: '',
    description: '',
    dueDate: undefined as Date | undefined,
    priority: 'medium'
  })

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    console.log('Form submitted:', formData)
  }

  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>Create Task</CardTitle>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div className="space-y-2">
            <Label htmlFor="title">Task Title</Label>
            <Input
              id="title"
              value={formData.title}
              onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
              required
            />
          </div>
          
          <div className="space-y-2">
            <Label htmlFor="description">Description</Label>
            <Textarea
              id="description"
              value={formData.description}
              onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
              rows={3}
            />
          </div>
          
          <div className="space-y-2">
            <Label>Due Date</Label>
            <DatePicker
              selected={formData.dueDate}
              onSelect={(date) => setFormData(prev => ({ ...prev, dueDate: date }))}
              placeholder="Select due date"
            />
          </div>
          
          <div className="flex space-x-2">
            <Button type="submit" className="flex-1">Create Task</Button>
            <Button type="button" variant="outline">Cancel</Button>
          </div>
        </form>
      </CardContent>
    </Card>
  )
}

Advanced Integration Patterns

Data Table with Search and Pagination

Create a complete data table interface:

import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { SearchBar, Pagination } from '@foundrykit/components'
import { useState, useMemo } from 'react'

interface User {
  id: string
  name: string
  email: string
  role: string
}

function DataTable({ users }: { users: User[] }) {
  const [searchQuery, setSearchQuery] = useState('')
  const [currentPage, setCurrentPage] = useState(1)
  const itemsPerPage = 10

  const filteredUsers = useMemo(() => {
    return users.filter(user =>
      user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
      user.email.toLowerCase().includes(searchQuery.toLowerCase())
    )
  }, [users, searchQuery])

  const totalPages = Math.ceil(filteredUsers.length / itemsPerPage)
  const paginatedUsers = filteredUsers.slice(
    (currentPage - 1) * itemsPerPage,
    currentPage * itemsPerPage
  )

  return (
    <Card>
      <CardHeader>
        <CardTitle>Users</CardTitle>
        <SearchBar
          placeholder="Search users..."
          onSearch={setSearchQuery}
        />
      </CardHeader>
      <CardContent>
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Name</TableHead>
              <TableHead>Email</TableHead>
              <TableHead>Role</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {paginatedUsers.map(user => (
              <TableRow key={user.id}>
                <TableCell>{user.name}</TableCell>
                <TableCell>{user.email}</TableCell>
                <TableCell>{user.role}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
        
        <div className="mt-4">
          <Pagination
            currentPage={currentPage}
            totalPages={totalPages}
            onPageChange={setCurrentPage}
          />
        </div>
      </CardContent>
    </Card>
  )
}

Dashboard Layout

Create a dashboard that combines multiple components and primitives:

import { Card, CardContent, CardHeader, CardTitle, Button, Badge, Separator } from '@foundrykit/primitives'
import { Calendar, SearchBar, Command, CommandInput, CommandList, CommandItem } from '@foundrykit/components'
import { useState } from 'react'

function Dashboard() {
  const [selectedDate, setSelectedDate] = useState<Date>()
  const [searchQuery, setSearchQuery] = useState('')

  return (
    <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 p-6">
      {/* Main Content */}
      <div className="lg:col-span-2 space-y-6">
        <Card>
          <CardHeader>
            <CardTitle>Quick Actions</CardTitle>
          </CardHeader>
          <CardContent>
            <SearchBar
              placeholder="Search or type commands..."
              onSearch={setSearchQuery}
            />
            
            <div className="mt-4 flex space-x-2">
              <Button>New Project</Button>
              <Button variant="outline">Import Data</Button>
              <Button variant="outline">Export Report</Button>
            </div>
          </CardContent>
        </Card>
        
        <Card>
          <CardHeader>
            <CardTitle>Recent Activity</CardTitle>
          </CardHeader>
          <CardContent>
            <div className="space-y-4">
              {[1, 2, 3].map(i => (
                <div key={i} className="flex items-center space-x-4">
                  <Badge variant="secondary">New</Badge>
                  <div className="flex-1">
                    <p className="text-sm font-medium">Project {i} updated</p>
                    <p className="text-sm text-muted-foreground">2 hours ago</p>
                  </div>
                  <Button variant="ghost" size="sm">View</Button>
                </div>
              ))}
            </div>
          </CardContent>
        </Card>
      </div>
      
      {/* Sidebar */}
      <div className="space-y-6">
        <Card>
          <CardHeader>
            <CardTitle>Calendar</CardTitle>
          </CardHeader>
          <CardContent>
            <Calendar
              mode="single"
              selected={selectedDate}
              onSelect={setSelectedDate}
              className="rounded-md border"
            />
          </CardContent>
        </Card>
        
        <Card>
          <CardHeader>
            <CardTitle>Quick Commands</CardTitle>
          </CardHeader>
          <CardContent>
            <Command>
              <CommandInput placeholder="Type a command..." />
              <CommandList>
                <CommandItem>Open Settings</CommandItem>
                <CommandItem>New Task</CommandItem>
                <CommandItem>View Analytics</CommandItem>
                <CommandItem>Export Data</CommandItem>
              </CommandList>
            </Command>
          </CardContent>
        </Card>
      </div>
    </div>
  )
}

Styling Integration

Consistent Theming

Ensure components and primitives use consistent theming:

import { Button, Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { Calendar, SearchBar } from '@foundrykit/components'

function ThemedInterface() {
  return (
    <div className="space-y-6">
      {/* Use consistent border radius and spacing */}
      <Card className="rounded-lg border shadow-sm">
        <CardHeader className="pb-4">
          <CardTitle className="text-lg font-semibold">Search Interface</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <SearchBar
            placeholder="Search..."
            className="rounded-md"
          />
          
          <Calendar
            mode="single"
            className="rounded-md border"
          />
          
          <div className="flex space-x-2">
            <Button className="rounded-md">Search</Button>
            <Button variant="outline" className="rounded-md">Clear</Button>
          </div>
        </CardContent>
      </Card>
    </div>
  )
}

Custom CSS Variables

Use CSS variables for consistent theming across components and primitives:

:root {
  /* Shared color palette */
  --color-primary: oklch(0.55 0.15 250);
  --color-primary-foreground: oklch(0.98 0.005 250);
  --color-secondary: oklch(0.95 0.01 250);
  --color-secondary-foreground: oklch(0.15 0.08 250);
  
  /* Shared spacing */
  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-3: 0.75rem;
  --spacing-4: 1rem;
  
  /* Shared border radius */
  --radius: 0.5rem;
  --radius-lg: 0.75rem;
  
  /* Shared shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}

Performance Integration

Lazy Loading Components

Lazy load components that aren't immediately needed:

import { lazy, Suspense } from 'react'
import { Card, CardContent, Skeleton } from '@foundrykit/primitives'

const Calendar = lazy(() => import('@foundrykit/components').then(m => ({ default: m.Calendar })))
const Command = lazy(() => import('@foundrykit/components').then(m => ({ default: m.Command })))

function LazyLoadedInterface() {
  return (
    <div className="space-y-6">
      <Card>
        <CardContent>
          <Suspense fallback={<Skeleton className="h-32 w-full" />}>
            <Calendar mode="single" />
          </Suspense>
        </CardContent>
      </Card>
      
      <Card>
        <CardContent>
          <Suspense fallback={<Skeleton className="h-20 w-full" />}>
            <Command>
              <CommandInput placeholder="Loading commands..." />
            </Command>
          </Suspense>
        </CardContent>
      </Card>
    </div>
  )
}

Memoized Integration

Optimize performance with memoization:

import { memo, useMemo } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { SearchBar, Pagination } from '@foundrykit/components'

const MemoizedSearchBar = memo(SearchBar)
const MemoizedPagination = memo(Pagination)

function OptimizedInterface({ data, onSearch, onPageChange }) {
  const searchConfig = useMemo(() => ({
    placeholder: "Search data...",
    debounceMs: 300
  }), [])

  const paginationConfig = useMemo(() => ({
    showFirstLast: true,
    showPrevNext: true,
    maxVisible: 5
  }), [])

  return (
    <Card>
      <CardHeader>
        <CardTitle>Data Interface</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <MemoizedSearchBar
          {...searchConfig}
          onSearch={onSearch}
        />
        
        {/* Data content */}
        <div className="min-h-[200px]">
          {data.map(item => (
            <div key={item.id} className="p-2 border-b">
              {item.name}
            </div>
          ))}
        </div>
        
        <MemoizedPagination
          {...paginationConfig}
          currentPage={currentPage}
          totalPages={totalPages}
          onPageChange={onPageChange}
        />
      </CardContent>
    </Card>
  )
}

Accessibility Integration

Keyboard Navigation

Ensure proper keyboard navigation between components and primitives:

import { Button, Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
import { SearchBar, Calendar } from '@foundrykit/components'

function AccessibleInterface() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Accessible Interface</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div>
          <label htmlFor="search" className="sr-only">Search</label>
          <SearchBar
            id="search"
            placeholder="Search..."
            onSearch={handleSearch}
          />
        </div>
        
        <div>
          <label className="sr-only">Select date</label>
          <Calendar
            mode="single"
            selected={date}
            onSelect={setDate}
            className="rounded-md border"
          />
        </div>
        
        <div className="flex space-x-2">
          <Button>Apply</Button>
          <Button variant="outline">Reset</Button>
        </div>
      </CardContent>
    </Card>
  )
}

Next Steps