diff --git a/.npmrc b/.npmrc index 4f51e76..b329cb9 100644 --- a/.npmrc +++ b/.npmrc @@ -1,5 +1,6 @@ -@bytelyst:registry=http://${GITEA_NPM_HOST:-localhost}:3300/api/packages/ByteLyst/npm/ -//localhost:3300/api/packages/ByteLyst/npm/:_authToken=${GITEA_NPM_TOKEN} +# Remove repo-level GITEA_NPM_TOKEN interpolation +# Gitea registry auth should live in user-level ~/.npmrc or CI secrets +# Only when BYTELYST_PACKAGE_SOURCE=gitea is explicitly used strict-ssl=false link-workspace-packages=true prefer-workspace-packages=true diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs new file mode 100644 index 0000000..0a140ad --- /dev/null +++ b/.pnpmfile.cjs @@ -0,0 +1,95 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +const PACKAGE_SCOPE = '@bytelyst/'; +const PACKAGE_SOURCE = process.env.BYTELYST_PACKAGE_SOURCE || 'common-plat'; +const DEFAULT_COMMON_PLAT_ROOT = path.resolve(__dirname, '..', 'learning_ai', 'learning_ai_common_plat'); +const LEGACY_COMMON_PLAT_ROOT = path.resolve(__dirname, '..', 'learning_ai_common_plat'); +const COMMON_PLAT_ROOT = + process.env.BYTELYST_COMMON_PLAT_ROOT || + (fs.existsSync(DEFAULT_COMMON_PLAT_ROOT) ? DEFAULT_COMMON_PLAT_ROOT : LEGACY_COMMON_PLAT_ROOT); +const COMMON_PLAT_PACKAGES_ROOT = path.join(COMMON_PLAT_ROOT, 'packages'); +const VERSION_CACHE = new Map(); +let loggedSource = false; + +function packageDirFor(name) { + return name.startsWith(PACKAGE_SCOPE) ? name.slice(PACKAGE_SCOPE.length) : null; +} + +function pathIfPackageExists(rootDir, name) { + const packageDir = packageDirFor(name); + if (!packageDir) return null; + + const candidate = path.join(rootDir, packageDir); + return fs.existsSync(path.join(candidate, 'package.json')) ? candidate : null; +} + +function readPackageVersion(packagePath) { + if (VERSION_CACHE.has(packagePath)) { + return VERSION_CACHE.get(packagePath); + } + + try { + const packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'), 'utf8')); + const version = packageJson.version || null; + VERSION_CACHE.set(packagePath, version); + return version; + } catch { + VERSION_CACHE.set(packagePath, null); + return null; + } +} + +function resolveSpecifier(name) { + if (!name.startsWith(PACKAGE_SCOPE)) { + return null; + } + + const commonPlatPath = pathIfPackageExists(COMMON_PLAT_PACKAGES_ROOT, name); + + if (PACKAGE_SOURCE === 'common-plat') { + return commonPlatPath ? 'workspace:*' : null; + } + + if (PACKAGE_SOURCE === 'gitea') { + return commonPlatPath ? readPackageVersion(commonPlatPath) : null; + } + + return commonPlatPath ? 'workspace:*' : null; +} + +function rewriteDependencySet(dependencies = {}) { + for (const dependencyName of Object.keys(dependencies)) { + const rewrittenSpecifier = resolveSpecifier(dependencyName); + if (rewrittenSpecifier) { + dependencies[dependencyName] = rewrittenSpecifier; + } + } +} + +function logSourceOnce() { + if (loggedSource) { + return; + } + + loggedSource = true; + process.stderr.write( + `[bytelyst] pnpm package source=${PACKAGE_SOURCE} commonPlatRoot=${COMMON_PLAT_ROOT}\n`, + ); +} + +module.exports = { + hooks: { + readPackage(packageJson) { + logSourceOnce(); + if (packageJson.name?.startsWith(PACKAGE_SCOPE)) { + return packageJson; + } + + rewriteDependencySet(packageJson.dependencies); + rewriteDependencySet(packageJson.devDependencies); + rewriteDependencySet(packageJson.optionalDependencies); + return packageJson; + }, + }, +}; \ No newline at end of file diff --git a/docs/UX_TESTING_SETUP_GUIDE.md b/docs/UX_TESTING_SETUP_GUIDE.md new file mode 100644 index 0000000..87a5466 --- /dev/null +++ b/docs/UX_TESTING_SETUP_GUIDE.md @@ -0,0 +1,815 @@ +# UX Implementation Guide + +> **Purpose:** Comprehensive guide for UX implementation across ByteLyst products using common platform UI packages, design tokens, and Cipher design system integration. + +> **Target Audience:** Product teams implementing UX improvements and testing infrastructure. + +> **Product:** ChronoMind - AI-powered contextual clock & timer + +--- + +## Overview + +This guide documents the complete UX implementation approach for ChronoMind, replicating the patterns established in the trading dashboard. The approach covers: + +- **Common Platform UI Integration** - Leveraging `@bytelyst/ui` shared primitives +- **Design Token Usage** - Using `@bytelyst/design-tokens` for consistent styling +- **Component Normalization** - Replacing one-off components with shared primitives +- **Accessibility Improvements** - Keyboard navigation, ARIA labels, focus management +- **Responsive Design** - Viewport matrix testing, shell breakpoints +- **Testing Infrastructure** - Playwright E2E tests, Storybook, AI-friendly reports +- **Cipher Design System** - Visual hierarchy, spacing, typography principles + +--- + +## Part 1: Common Platform UI Integration + +### Local Package Resolution + +**Critical:** Use local common platform packages by default, not Gitea registry. + +**File:** `.pnpmfile.cjs` + +```javascript +// .pnpmfile.cjs +// Default to local common platform packages +function readPackage(pkg, context) { + if (!context.workspace) return pkg; + + // Default to common-plat (local packages) + const packageSource = process.env.BYTELYST_PACKAGE_SOURCE || 'common-plat'; + + if (packageSource === 'common-plat') { + // Resolve @bytelyst/* packages from local common platform + if (pkg.name.startsWith('@bytelyst/')) { + pkg.dependencies = pkg.dependencies || {}; + pkg.dependencies[pkg.name] = 'workspace:*'; + } + } + + return pkg; +} + +module.exports = { + name: 'bytelyst-package-source', + hooks: { + readPackage, + }, +}; +``` + +**File:** `.npmrc` + +```ini +# Remove repo-level GITEA_NPM_TOKEN interpolation +# Gitea registry auth should live in user-level ~/.npmrc or CI secrets +# Only when BYTELYST_PACKAGE_SOURCE=gitea is explicitly used +``` + +### Environment Variables + +```bash +# Default: Use local common platform packages +BYTELYST_PACKAGE_SOURCE=common-plat + +# Optional: Use Gitea registry (requires auth) +BYTELYST_PACKAGE_SOURCE=gitea +# GITEA_NPM_TOKEN should be in ~/.npmrc or CI secrets +``` + +### Verification Commands + +```bash +# Verify local package resolution +pnpm install @bytelyst/ui @bytelyst/design-tokens + +# Verify packages resolve from local common platform +pnpm list @bytelyst/ui +# Should show: @bytelyst/ui -> link:../learning_ai_common_plat/packages/ui +``` + +### Product Adapter Pattern + +Create a product adapter to normalize imports and extend shared primitives with product-specific variants: + +**File:** `web/src/components/ui/Primitives.tsx` + +```typescript +import * as React from 'react'; +import { + Badge as CommonBadge, + Button as CommonButton, + Input as CommonInput, + Select as CommonSelect, + type BadgeProps as CommonBadgeProps, + type ButtonProps as CommonButtonProps, + type InputProps as CommonInputProps, + type SelectProps as CommonSelectProps, +} from '@bytelyst/ui'; + +// Re-export all shared primitives +export { + ActionMenu, + AlertBanner, + DataList, + DataTable, + Drawer, + EmptyState, + Modal, + PageHeader, + Panel, + Skeleton, + Timeline, + Toolbar, + // ... all other @bytelyst/ui components +} from '@bytelyst/ui'; + +// Define product-specific variants for ChronoMind +type ProductButtonVariant = NonNullable | 'link'; +type ProductButtonSize = NonNullable | 'icon'; +type ProductFieldVariant = 'surface' | 'muted'; +type ProductFieldSize = 'sm' | 'md'; +type ProductBadgeVariant = NonNullable | 'danger'; +type ProductStatusTone = 'success' | 'warning' | 'error' | 'info' | 'neutral'; + +// Extend interfaces with product-specific props +export interface ButtonProps extends Omit { + variant?: ProductButtonVariant; + size?: ProductButtonSize; +} + +export interface IconButtonProps extends Omit { + icon: React.ReactNode; + label: string; +} + +export interface InputProps extends CommonInputProps { + controlSize?: ProductFieldSize; + variant?: ProductFieldVariant; +} + +// ChronoMind-specific status mapping for badges +export type ChronoMindStatus = + | 'running' | 'paused' | 'stopped' | 'completed' | 'alarm' + | 'snoozed' | 'overdue' | 'upcoming' | 'recurring' | 'one-time' + | 'focus' | 'break' | 'pomodoro' | 'routine' | 'error' | 'success'; + +const chronomindStatusTone: Record = { + running: 'success', + paused: 'warning', + stopped: 'neutral', + completed: 'success', + alarm: 'error', + snoozed: 'warning', + overdue: 'error', + upcoming: 'info', + recurring: 'info', + 'one-time': 'neutral', + focus: 'success', + break: 'info', + pomodoro: 'success', + routine: 'info', + error: 'error', + success: 'success', +}; + +// Helper function to map timer status to tone +export function statusToneFor(status: ChronoMindStatus | string | null | undefined): ProductStatusTone { + if (!status) return 'neutral'; + const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as ChronoMindStatus; + return chronomindStatusTone[normalized] ?? 'neutral'; +} + +// Product-specific component implementations +export const Button = React.forwardRef( + ({ variant = 'primary', size = 'md', className, ...props }, ref) => ( + + ), +); + +export const IconButton = React.forwardRef( + ({ icon, label, variant = 'ghost', size = 'icon', className, ...props }, ref) => ( + + ), +); + +export const Input = React.forwardRef( + ({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => ( + + ), +); + +export function ChronoMindStatusBadge({ + status, + children, +}: { + status: ChronoMindStatus | string | null | undefined; + children?: React.ReactNode; +}) { + return ( + + {children ?? status ?? 'Unknown'} + + ); +} +``` + +**Benefits of Product Adapter Pattern:** +- Centralized import point for all UI components +- Product-specific variants without modifying common platform +- Consistent styling across the application +- Easy to migrate to new common platform versions +- Type-safe extensions with TypeScript + +--- + +## Part 2: Design Token Usage + +### CSS Variable Integration + +Use design tokens from `@bytelyst/design-tokens` via CSS custom properties with ChronoMind-specific prefixes: + +```css +/* Surface colors */ +background: var(--cm-surface-card); +background: var(--cm-surface-muted); +background: var(--cm-surface-overlay); + +/* Text colors */ +color: var(--cm-text-primary); +color: var(--cm-text-secondary); +color: var(--cm-text-muted); + +/* Border colors */ +border-color: var(--cm-border); +border-color: var(--cm-border-muted); + +/* Input styling */ +background: var(--cm-input); +color: var(--cm-text-primary); +border-color: var(--cm-border); + +/* Focus states */ +border-color: var(--cm-focus-ring); +box-shadow: 0 0 0 2px var(--cm-focus-ring-muted); + +/* Semantic colors */ +color: var(--cm-accent); +background: var(--cm-success); +background: var(--cm-warning); +background: var(--cm-error); + +/* Spacing */ +padding: var(--cm-spacing-sm); +padding: var(--cm-spacing-md); +padding: var(--cm-spacing-lg); +padding: var(--cm-spacing-xl); + +/* Border radius */ +border-radius: var(--cm-radius-control); +border-radius: var(--cm-radius-md); +border-radius: var(--cm-radius-lg); + +/* Typography */ +font-size: var(--cm-text-sm); +font-size: var(--cm-text-base); +font-size: var(--cm-text-lg); +font-weight: var(--cm-font-medium); +font-weight: var(--cm-font-semibold); +``` + +### Component Token Patterns + +**Example: Badge Component for ChronoMind** + +```typescript +// Using design tokens for badge styling +const badgeStyles = { + success: { + background: 'var(--cm-success-light)', + color: 'var(--cm-success-dark)', + borderColor: 'var(--cm-success)', + }, + warning: { + background: 'var(--cm-warning-light)', + color: 'var(--cm-warning-dark)', + borderColor: 'var(--cm-warning)', + }, + error: { + background: 'var(--cm-error-light)', + color: 'var(--cm-error-dark)', + borderColor: 'var(--cm-error)', + }, +}; +``` + +--- + +## Part 3: Component Normalization + +### Badge Normalization + +**Before:** One-off CSS classes for different badge styles + +```css +/* Old approach - one-off classes */ +.timer-chip { + background: #f0f0f0; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; +} + +.category-tag { + background: #e0e0e0; + padding: 6px 12px; + border-radius: 6px; +} + +.status-pill { + background: #d0d0d0; + padding: 5px 10px; + border-radius: 20px; +} +``` + +**After:** Shared Badge component from product adapter + +```typescript +import { Badge, ChronoMindStatusBadge } from '../components/ui/Primitives'; + +// Replace .timer-chip with Badge +Running + +// Replace .category-tag with Badge +Work + +// Replace .status-pill with ChronoMindStatusBadge + +``` + +### Alert Banner Unification + +**Before:** Different alert implementations across components + +```typescript +// TimerCard.tsx - custom alert +
+ ⚠️ + Warning message +
+ +// RoutineEditor.tsx - different alert +
+
!
+
Warning text
+
+``` + +**After:** Shared AlertBanner component + +```typescript +import { AlertBanner } from '../components/ui/Primitives'; + +// Both components now use shared AlertBanner + + Warning message + + + + Error message + +``` + +### Table Controls Standardization + +**Before:** Custom table controls with inconsistent styling + +```typescript +// Different button styles across tables + + + +``` + +**After:** Standardized Button component + +```typescript +import { Button, IconButton } from '../components/ui/Primitives'; + +// Consistent button styles + + +} label="Complete" /> +``` + +--- + +## Part 4: Accessibility Improvements + +### Keyboard Navigation + +**Focus Management** + +```typescript +// Ensure interactive elements are focusable + +``` + +**Keyboard Toggles** + +```typescript +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggle(); + } + }} + aria-pressed={isActive} +> + Toggle Option +
+``` + +### ARIA Labels + +**Button Labels** + +```typescript +// Icon buttons need explicit labels +} + label="Start timer" + aria-label="Start timer" +/> +``` + +**Status Indicators** + +```typescript +// Status badges need descriptive labels + + Running + +``` + +**Form Labels** + +```typescript +// All inputs need associated labels + +``` + +### Focus Indicators + +**Visible Focus States** + +```css +/* Ensure focus is visible */ +*:focus-visible { + outline: 2px solid var(--cm-focus-ring); + outline-offset: 2px; +} + +/* Or use design token */ +*:focus-visible { + outline: 2px solid var(--cm-focus-ring); + box-shadow: 0 0 0 2px var(--cm-focus-ring-muted); +} +``` + +--- + +## Part 5: Responsive Design + +### Shell Breakpoints + +**Responsive Shell Testing** + +```typescript +const breakpoints = { + mobile: 'max-width: 560px', + tablet: 'max-width: 768px', + desktop: 'min-width: 769px', +}; + +// Shell adapts at these breakpoints +@media (max-width: 560px) { + .timer-main { + margin-left: 0; + } + .timer-right-panel { + display: none; + } + .timer-sidebar { + position: fixed; + bottom: 0; + width: 100%; + height: 60px; + } +} +``` + +### Viewport Matrix Testing + +**Test all routes across viewports** + +```typescript +const routes = ['/dashboard', '/focus', '/history', '/routines', '/settings']; +const viewports = [ + { name: 'Desktop', width: 1200, height: 800 }, + { name: 'Tablet', width: 768, height: 1024 }, + { name: 'Mobile', width: 375, height: 667 }, +]; + +routes.forEach((route) => { + viewports.forEach((viewport) => { + test(`${route} - ${viewport.name} viewport`, async ({ page }) => { + await page.setViewportSize({ width: viewport.width, height: viewport.height }); + await page.goto(route); + + // Check for horizontal overflow + const bodyWidth = await page.evaluate(() => document.body.scrollWidth); + expect(bodyWidth).toBeLessThanOrEqual(viewport.width + 10); + + // Check main content visibility + const mainContent = page.locator('main'); + await expect(mainContent).toBeVisible(); + }); + }); +}); +``` + +--- + +## Part 6: Testing Infrastructure + +### Playwright Setup + +**Install Dependencies** + +```bash +cd web +pnpm add -D @playwright/test +pnpm exec playwright install chromium +``` + +**Create Playwright Config** + +Create `web/playwright.config.ts`: + +```typescript +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3030', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, + { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, + { name: 'webkit', use: { ...devices['Desktop Safari'] } }, + { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } }, + { name: 'Mobile Safari', use: { ...devices['iPhone 12'] } }, + ], +}); +``` + +### Test Suite Structure + +Create tests in `web/e2e/` directory: + +``` +web/e2e/ +├── viewport-matrix.spec.ts # Viewport compliance +├── horizontal-overflow.spec.ts # Overflow detection +├── alert-positioning.spec.ts # Critical alerts positioning +├── timer-flows.spec.ts # Timer start/pause/stop flows +├── page-states.spec.ts # Loading/empty/error/success states +├── form-validation.spec.ts # Form validation +└── keyboard-navigation.spec.ts # Keyboard navigation +``` + +--- + +## Part 7: Cipher Design System Integration + +### Design Principles + +Follow Cipher design system principles for consistent UX across ChronoMind: + +#### 1. Visual Hierarchy + +**Size and Weight** + +```typescript +// Use design tokens for consistent hierarchy +

+ Timer Title +

+

+ Section Header +

+

+ Timer content +

+``` + +#### 2. Spacing System + +**Consistent Spacing** + +```typescript +// Use design token spacing +
+ Content +
+ +
+ + +
+``` + +#### 3. Typography + +**Font Scales** + +```typescript +// Use design token font sizes +const textStyles = { + xs: 'var(--cm-text-xs)', + sm: 'var(--cm-text-sm)', + base: 'var(--cm-text-base)', + lg: 'var(--cm-text-lg)', + xl: 'var(--cm-text-xl)', + '2xl': 'var(--cm-text-2xl)', +}; + +

Body text

+``` + +#### 4. Color System + +**Semantic Colors** + +```typescript +// Use semantic color tokens, not literal colors +const statusColors = { + success: 'var(--cm-success)', + warning: 'var(--cm-warning)', + error: 'var(--cm-error)', + info: 'var(--cm-info)', +}; + +
Success message
+``` + +--- + +## Part 8: Implementation Roadmap + +### Phase 1: Foundation Setup + +**Week 1-2** +- [ ] Set up local package resolution (.pnpmfile.cjs) +- [ ] Remove repo-level GITEA_NPM_TOKEN from .npmrc +- [ ] Install common platform packages from local source +- [ ] Create product adapter (`Primitives.tsx`) +- [ ] Set up design token integration +- [ ] Configure Playwright + +### Phase 2: Component Normalization + +**Week 3-4** +- [ ] Replace one-off badges with Badge component +- [ ] Replace custom buttons with Button component +- [ ] Replace custom alerts with AlertBanner component +- [ ] Replace custom inputs with Input component +- [ ] Remove old CSS classes + +### Phase 3: Design Token Migration + +**Week 5-6** +- [ ] Identify hardcoded colors +- [ ] Map to semantic tokens +- [ ] Replace with CSS variables +- [ ] Test across themes +- [ ] Audit for missed tokens + +### Phase 4: Accessibility Improvements + +**Week 7** +- [ ] Add keyboard navigation +- [ ] Add ARIA labels +- [ ] Improve focus indicators +- [ ] Test with screen readers + +### Phase 5: Responsive Design + +**Week 8** +- [ ] Test viewport matrix +- [ ] Fix horizontal overflow +- [ ] Optimize mobile layout +- [ ] Test breakpoints + +### Phase 6: Testing Infrastructure + +**Week 9-10** +- [ ] Create E2E test suite +- [ ] Set up test runner script +- [ ] Configure AI-friendly reports +- [ ] Integrate with CI + +--- + +## Part 9: Verification Commands + +### Type Checking + +```bash +# Verify TypeScript types +pnpm run typecheck +``` + +### Build Verification + +```bash +# Ensure build succeeds +pnpm run build +``` + +### Test Execution + +```bash +# Run all E2E tests +cd web +pnpm test:e2e + +# Run specific test suites +pnpm test:e2e:viewport +pnpm test:e2e:overflow +pnpm test:e2e:timer-flows +``` + +--- + +## Summary + +This guide provides a comprehensive approach to implementing UX improvements for ChronoMind using the ByteLyst common platform UI packages, design tokens, and Cipher design system. By following these patterns, ChronoMind will achieve: + +- Consistent UI with other ByteLyst products +- Improved accessibility and keyboard navigation +- Responsive design across all viewports +- Comprehensive testing infrastructure +- Maintainable and scalable codebase + +The implementation is organized in phases to allow for incremental adoption and verification at each stage. \ No newline at end of file diff --git a/web/src/components/ui/Primitives.tsx b/web/src/components/ui/Primitives.tsx new file mode 100644 index 0000000..92f53e1 --- /dev/null +++ b/web/src/components/ui/Primitives.tsx @@ -0,0 +1,369 @@ +import { + AppShell as BytelystAppShell, + AppShellMain as BytelystAppShellMain, + AppShellMobileToggle as BytelystAppShellMobileToggle, + AppShellNav as BytelystAppShellNav, + AppShellNavItem as BytelystAppShellNavItem, + AppShellOverlay as BytelystAppShellOverlay, + AppShellPageHeader as BytelystAppShellPageHeader, + AppShellSidebar as BytelystAppShellSidebar, + AppShellSkipLink as BytelystAppShellSkipLink, + Badge as BytelystBadge, + Button as BytelystButton, + Card as BytelystCard, + Checkbox, + DataList as BytelystDataList, + DataListItem as BytelystDataListItem, + DataListMeta, + DataTable, + DataTableBody, + DataTableCell, + DataTableHead, + DataTableHeader, + DataTableRow, + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuTrigger, + EmptyState as BytelystEmptyState, + IconButton as BytelystIconButton, + Input as BytelystInput, + Label as BytelystLabel, + ListItemButton as BytelystListItemButton, + LoadingSpinner as BytelystLoadingSpinner, + Panel as BytelystPanel, + PanelBody as BytelystPanelBody, + PanelDescription as BytelystPanelDescription, + PanelHeader as BytelystPanelHeader, + PanelTitle as BytelystPanelTitle, + RadioGroup, + RadioGroupItem, + SegmentedControl, + Select as BytelystSelect, + StatusBadge as BytelystStatusBadge, + Surface as BytelystSurface, + SurfaceList as BytelystSurfaceList, + SurfaceListItem as BytelystSurfaceListItem, + Switch, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Textarea as BytelystTextarea, + Timeline as BytelystTimeline, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, + type AppShellMainProps, + type AppShellMobileToggleProps, + type AppShellNavItemProps, + type AppShellNavProps, + type AppShellOverlayProps, + type AppShellPageHeaderProps, + type AppShellProps, + type AppShellSidebarProps, + type AppShellSkipLinkProps, + type BadgeProps, + type ButtonProps, + type CardProps, + type DataListItemProps, + type DataListProps, + type EmptyStateProps, + type IconButtonProps, + type InputProps, + type LabelProps, + type ListItemButtonProps, + type LoadingSpinnerProps, + type PanelBodyProps, + type PanelDescriptionProps, + type PanelHeaderProps, + type PanelProps, + type PanelTitleProps, + type SelectProps, + type StatusBadgeProps, + type StatusTone, + type SurfaceListItemProps, + type SurfaceListProps, + type SurfaceProps, + type TextareaProps, + type TimelineProps, +} from "@bytelyst/ui"; + +function mergeClassNames(...classes: Array) { + return classes.filter(Boolean).join(" "); +} + +export { + Checkbox, + DataListMeta, + DataTable, + DataTableBody, + DataTableCell, + DataTableHead, + DataTableHeader, + DataTableRow, + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuTrigger, + RadioGroup, + RadioGroupItem, + SegmentedControl, + Switch, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +}; + +export type ChronoMindStatus = + | "running" + | "paused" + | "stopped" + | "completed" + | "alarm" + | "snoozed" + | "overdue" + | "upcoming" + | "recurring" + | "one-time" + | "focus" + | "break" + | "pomodoro" + | "routine" + | "error" + | "success"; + +const statusToneMap: Record = { + running: "success", + paused: "warning", + stopped: "neutral", + completed: "success", + alarm: "danger", + snoozed: "warning", + overdue: "danger", + upcoming: "info", + recurring: "info", + "one-time": "neutral", + focus: "success", + break: "info", + pomodoro: "success", + routine: "info", + error: "danger", + success: "success", +}; + +export function AppShell({ className, ...props }: AppShellProps) { + return ; +} + +export function AppShellSkipLink({ className, ...props }: AppShellSkipLinkProps) { + return ; +} + +export function AppShellMobileToggle({ className, ...props }: AppShellMobileToggleProps) { + return ; +} + +export function AppShellOverlay({ className, ...props }: AppShellOverlayProps) { + return ; +} + +export function AppShellSidebar({ className, ...props }: AppShellSidebarProps) { + return ; +} + +export function AppShellMain({ className, ...props }: AppShellMainProps) { + return ; +} + +export function AppShellPageHeader({ className, ...props }: AppShellPageHeaderProps) { + return ; +} + +export function AppShellNav({ className, ...props }: AppShellNavProps) { + return ; +} + +export function AppShellNavItem({ className, ...props }: AppShellNavItemProps) { + return ; +} + +export function getStatusTone(status: ChronoMindStatus): StatusTone { + return statusToneMap[status]; +} + +export function Button({ className, ...props }: ButtonProps) { + return ( + + ); +} + +export function Badge({ className, ...props }: BadgeProps) { + return ; +} + +export function Card({ className, ...props }: CardProps) { + return ( + + ); +} + +export function Panel({ className, ...props }: PanelProps) { + return ( + + ); +} + +export function PanelHeader({ className, ...props }: PanelHeaderProps) { + return ; +} + +export function PanelBody({ className, ...props }: PanelBodyProps) { + return ; +} + +export function PanelTitle({ className, ...props }: PanelTitleProps) { + return ; +} + +export function PanelDescription({ className, ...props }: PanelDescriptionProps) { + return ; +} + +export function IconButton({ className, ...props }: IconButtonProps) { + return ; +} + +export function ListItemButton({ className, ...props }: ListItemButtonProps) { + return ( + + ); +} + +export interface ChronoMindStatusBadgeProps extends StatusBadgeProps { + status?: ChronoMindStatus; +} + +export function StatusBadge({ className, status, tone, ...props }: ChronoMindStatusBadgeProps) { + return ; +} + +export function Input({ className, ...props }: InputProps) { + return ( + + ); +} + +export function Textarea({ className, ...props }: TextareaProps) { + return ( + + ); +} + +export function Select({ className, ...props }: SelectProps) { + return ( + + ); +} + +export function Label({ className, ...props }: LabelProps) { + return ; +} + +export function Timeline({ className, ...props }: TimelineProps) { + return ; +} + +export function Surface({ className, padding = "md", ...props }: SurfaceProps) { + return ( + + ); +} + +export function SurfaceList({ density = "normal", className, ...props }: SurfaceListProps) { + return ; +} + +export function SurfaceListItem({ className, ...props }: SurfaceListItemProps) { + return ; +} + +export function DataList({ density = "normal", className, ...props }: DataListProps) { + return ; +} + +export function DataListItem({ className, ...props }: DataListItemProps) { + return ; +} + +export const OperationalList = DataList; +export const OperationalListItem = DataListItem; + +export function EmptyState({ className, ...props }: EmptyStateProps) { + return ; +} + +export function LoadingSpinner({ className, ...props }: LoadingSpinnerProps) { + return ; +} \ No newline at end of file