Wave 9.D additions to the shared UI primitive package.
──────────────────────────────────────────────────────────────────
Skeleton — `card` shape added + new <SkeletonGroup> orchestrator
──────────────────────────────────────────────────────────────────
packages/ui/src/components/Skeleton.tsx
- 4 shape variants: text / block / circle / **card**
The `card` variant uses min-h-32 + rounded-2xl + a subtle
border so a real <Card> can swap in without CLS.
- New <SkeletonGroup loading fallback>{children}</SkeletonGroup>
component handles the fade-out → content swap centrally. Use
one per loadable region rather than sprinkling <Skeleton/>
everywhere. Supports `keepContent` for re-fetch flows.
- In-source TODO #2 marker for the pending vitest setup.
──────────────────────────────────────────────────────────────────
LoadingDots — three-dot inline pulse
──────────────────────────────────────────────────────────────────
packages/ui/src/components/LoadingDots.tsx (new)
- sm / md / lg sizes
- `color` override, defaults to var(--bl-accent)
- motion-safe:animate-bounce respects prefers-reduced-motion
- role="status" + sr-only label for screen readers
- inline-flex layout — composes inside chat bubbles + buttons
──────────────────────────────────────────────────────────────────
SearchInput — themed search field with suggestions slot
──────────────────────────────────────────────────────────────────
packages/ui/src/components/SearchInput.tsx (new)
- Leading Search icon + clear-x button (visible while value !== '')
- role="searchbox", proper aria-label fallback to placeholder
- 3 size scales matching ButtonSize convention
- `suggestions` slot for typeahead lists below
- Forwards ref to <input> for imperative focus
- Consolidates the bespoke search-field pattern from notes,
fastgap, voice, jarvisjr (roadmap §2.2).
──────────────────────────────────────────────────────────────────
Package hygiene
──────────────────────────────────────────────────────────────────
- package.json: 0.1.11 → 0.2.0
- tsconfig.json: added DOM + DOM.Iterable libs to match
motion/data-viz packages (required for e.target.value typing)
- src/index.ts: exported SkeletonGroup, LoadingDots, SearchInput
──────────────────────────────────────────────────────────────────
Quality gates
──────────────────────────────────────────────────────────────────
✓ tsc --noEmit clean
✓ tsc build clean (no errors)
✓ No regression to existing ui exports
──────────────────────────────────────────────────────────────────
Roadmap tracker — 5 boxes flipped (§11)
──────────────────────────────────────────────────────────────────
9.D.1 Skeleton extended with card shape
9.D.2 SkeletonGroup orchestrator
9.D.3 EmptyState verified (already shipped in 0.1.x)
9.D.4 SearchInput added
9.D.6 LoadingDots added + LoadingSpinner verified
§11.2 counter rewrote by scripts/count-roadmap-progress.ts:
Wave 9 Data: 0/42 → 5/42 (12%)
TOTAL: 5/202 → 10/202 (5%)
Open TODOs (§11.2.A):
#2 Add vitest + happy-dom + @testing-library/react to
@bytelyst/ui devDeps; write unit tests for the new
Skeleton/SkeletonGroup/LoadingDots/SearchInput surfaces.
#4 Republish @bytelyst/ui@0.2.0 to Gitea registry once #1
(publish workflow) closes.
Showcase demos for these primitives land in the next showcase
commit (9.D.7–9.D.9).
|
||
|---|---|---|
| .. | ||
| .storybook | ||
| src | ||
| eslint.config.js | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
@bytelyst/ui
Shared component library for the ByteLyst ecosystem. Built with Radix UI primitives, Lucide icons, and CSS custom properties from @bytelyst/design-tokens.
Install
pnpm add @bytelyst/ui
Peer dependencies: react, react-dom.
Components (15)
Button
5 variants (primary, secondary, ghost, destructive, outline), 3 sizes, loading state with spinner.
import { Button } from '@bytelyst/ui';
<Button variant="primary" size="md" loading={saving}>Save</Button>
<Button variant="destructive" onClick={onDelete}>Delete</Button>
<Button variant="ghost" size="sm">Cancel</Button>
Input
Text input with error/success states. Supports aria-invalid automatically when error is truthy.
import { Input } from '@bytelyst/ui';
<Input placeholder="Email" type="email" />
<Input error="Required field" />
Textarea
Auto-resize textarea with error states.
import { Textarea } from '@bytelyst/ui';
<Textarea placeholder="Description" rows={4} />;
Select
Styled <select> with keyboard navigation.
import { Select } from '@bytelyst/ui';
<Select
options={[
{ value: 'a', label: 'Option A' },
{ value: 'b', label: 'Option B' },
]}
value={selected}
onChange={setSelected}
/>;
Modal / Dialog
Radix @radix-ui/react-dialog with focus trap, Esc close, and aria-modal.
import { Modal } from '@bytelyst/ui';
<Modal open={isOpen} onOpenChange={setOpen} title="Edit item">
<p>Modal content here</p>
</Modal>;
ConfirmDialog
Radix AlertDialog for destructive/warning confirmations. Supports async onConfirm.
import { ConfirmDialog } from '@bytelyst/ui';
<ConfirmDialog
open={showConfirm}
onOpenChange={setShowConfirm}
title="Delete item?"
description="This action cannot be undone."
onConfirm={handleDelete}
variant="destructive"
/>;
Toast
4 types (success, error, warning, info), auto-dismiss, role="alert". Use ToastProvider in your root layout and toast() anywhere.
import { ToastProvider, toast } from '@bytelyst/ui';
// In providers.tsx
<ToastProvider>{children}</ToastProvider>;
// Anywhere in app
toast('Saved successfully', 'success');
toast('Something went wrong', 'error');
Card
Container with default, elevated, and interactive variants. Includes CardHeader, CardTitle, CardDescription sub-components.
import { Card, CardHeader, CardTitle, CardDescription } from '@bytelyst/ui';
<Card>
<CardHeader>
<CardTitle>Dashboard</CardTitle>
<CardDescription>Overview of your data</CardDescription>
</CardHeader>
{children}
</Card>;
Badge
Status/count/risk-level badges, color-coded by semantic tokens.
import { Badge } from '@bytelyst/ui';
<Badge variant="success">Active</Badge>
<Badge variant="danger">Critical</Badge>
<Badge variant="warning" count={5} />
EmptyState
Placeholder for empty lists with icon, description, and optional CTA.
import { EmptyState } from '@bytelyst/ui';
<EmptyState
icon={<FileText size={48} />}
title="No items yet"
description="Create your first item to get started."
action={<Button onClick={onCreate}>Create</Button>}
/>;
Label
Styled form label.
import { Label } from '@bytelyst/ui';
<Label htmlFor="email">Email address</Label>;
Separator
Horizontal/vertical divider.
import { Separator } from '@bytelyst/ui';
<Separator />
<Separator orientation="vertical" />
Sidebar
Responsive collapsible sidebar with mobile toggle, overlay, and navigation items.
import { Sidebar, SidebarItem } from '@bytelyst/ui';
<Sidebar
collapsed={collapsed}
onToggle={() => setCollapsed(!collapsed)}
header={<span>MyApp</span>}
footer="v1.0.0"
>
<SidebarItem href="/dashboard" label="Dashboard" icon={<Home size={18} />} active />
<SidebarItem href="/settings" label="Settings" icon={<Settings size={18} />} />
</Sidebar>;
StatCard
Dashboard metric card with trend indicator.
import { StatCard } from '@bytelyst/ui';
<StatCard label="Total Users" value="1,234" trend="up" trendValue="+12%" />
<StatCard label="Errors" value={42} trend="down" trendValue="-8%" icon={<AlertCircle />} />
LoadingSpinner
Accessible loading indicator with aria-busy and prefers-reduced-motion support.
import { LoadingSpinner } from '@bytelyst/ui';
<LoadingSpinner size="md" label="Loading data…" />
<LoadingSpinner size="sm" label="" /> {/* spinner only, no text */}
Design Token Integration
All components use CSS custom properties with --bl- prefix and sensible fallbacks. Override them per-product:
:root {
--bl-accent: var(--jj-accent); /* JarvisJr */
--bl-surface-card: var(--fm-surface); /* FlowMonk */
}
Accessibility
Every component includes:
focus-visiblering styles- ARIA attributes (
aria-label,aria-modal,aria-busy,aria-invalid,aria-current) - Keyboard navigation (Esc to close, Tab focus trap in modals)
prefers-reduced-motionrespected via Tailwind'sanimate-spin
Storybook
pnpm --filter @bytelyst/ui storybook
Storybook is configured with @storybook/addon-a11y for automated accessibility checks.