syaOS syaOS / Docs
GitHub Launch

Component Architecture

syaOS uses a layered component architecture with theme-aware rendering, organized into layout, UI, dialog, and shared component categories.

Architecture Overview

graph TD
    subgraph Layer4["Layer 4: Application"]
        D1[Dialog Components]
        D2[Shared Components]
    end
    
    subgraph Layer3["Layer 3: Layout"]
        C1[WindowFrame]
        C2[MenuBar]
        C3[Desktop]
        C4[Dock]
        C5[Taskbar]
    end
    
    subgraph Layer2["Layer 2: Custom UI"]
        B1[audio-bars]
        B2[dial]
        B3[volume-bar]
        B4[playback-bars]
        B5[ThemedIcon]
        B6[TrafficLightButton]
    end
    
    subgraph Layer1["Layer 1: Base (shadcn)"]
        A1[button]
        A2[dialog]
        A3[dropdown-menu]
        A4[select]
        A5[slider]
        A6[tabs]
    end
    
    A1 --> B1 & B2 & B3
    B1 & B2 --> C1 & C2
    C1 & C2 --> D1 & D2

Component Directory Structure

src/components/
├── layout/           # Desktop environment structure
│   ├── Desktop.tsx
│   ├── Dock.tsx
│   ├── MenuBar.tsx
│   ├── WindowFrame.tsx
│   ├── AppleMenu.tsx
│   ├── StartMenu.tsx
│   ├── ExposeView.tsx
│   └── AppManager.tsx
├── ui/               # Base UI components
│   ├── button.tsx
│   ├── dialog.tsx
│   ├── dropdown-menu.tsx
│   ├── select.tsx
│   ├── slider.tsx
│   ├── switch.tsx
│   ├── tabs.tsx
│   ├── audio-bars.tsx
│   ├── dial.tsx
│   ├── volume-bar.tsx
│   └── ...
├── dialogs/          # Modal dialogs
│   ├── AboutDialog.tsx
│   ├── ConfirmDialog.tsx
│   ├── HelpDialog.tsx
│   └── ...
└── shared/           # Cross-app utilities
    ├── ThemedIcon.tsx
    ├── TrafficLightButton.tsx
    ├── EmojiAquarium.tsx
    └── ...

Layout Components

Desktop (Desktop.tsx)

The root desktop surface handling wallpaper, icons, and drag-drop.

interface DesktopProps {
  apps: AnyApp[];
  appStates: AppManagerState;
  toggleApp: (appId: AppId, initialData?: unknown) => void;
  onClick?: () => void;
  desktopStyles?: DesktopStyles;
}
Features:
  • Theme-conditional icon layout (XP: left-aligned, macOS: right-aligned)
  • Video wallpaper support with visibility handling
  • Desktop shortcut management via useFilesStore
  • Drag-and-drop alias creation
  • Long-press context menu for mobile

WindowFrame (WindowFrame.tsx)

Window chrome with theme-specific rendering.

interface WindowFrameProps {
  children: React.ReactNode;
  title: string;
  appId: AppId;
  material?: "default" | "transparent" | "notitlebar";
  windowConstraints?: WindowConstraints;
  instanceId?: string;
  menuBar?: React.ReactNode;
  keepMountedWhenMinimized?: boolean;
}
Theme-Specific Rendering:
ThemeTitle BarControlsPosition
macOS XPinstripe gradientTraffic lightsLeft
System 7Dotted patternClose boxLeft
XPBlue gradientMin/Max/CloseRight
Win983D beveledMin/Max/CloseRight

MenuBar (MenuBar.tsx)

Top menu bar (Mac) or taskbar integration.

Mac Themes:
  • Apple menu with logo
  • Application menu
  • Status area (clock, volume, battery)
Windows Themes:
  • Renders as part of taskbar
  • Start menu integration

Dock (Dock.tsx)

macOS-style dock with magnification.

Features:
  • Icon magnification on hover
  • Running app indicators
  • Pinned items management
  • Drag-to-reorder
  • Context menus

Component Hierarchy

graph TD
    AM[AppManager] --> MB[MenuBar]
    AM --> DT[Desktop]
    AM --> WF[WindowFrame *]
    AM --> DK[Dock]
    AM --> EV[ExposeView]
    
    MB --> APM[AppleMenu]
    MB --> STM[StartMenu]
    MB --> VC[VolumeControl]
    MB --> CLK[Clock]
    
    DT --> FI[FileIcon *]
    DT --> RCM[RightClickMenu]
    DT --> VW[Video Wallpaper]
    
    WF --> TLB[TrafficLightButton]
    WF --> TI[ThemedIcon]
    WF --> APP[App Content]

UI Components

shadcn-based Components

Standard UI primitives from shadcn/ui:

ComponentBasePurpose
buttonCVA variantsButtons with theme variants
dialogRadix DialogModal dialogs
dropdown-menuRadix DropdownMenuContext/dropdown menus
selectRadix SelectSelection dropdowns
sliderRadix SliderValue sliders
switchRadix SwitchToggle switches
tabsRadix TabsTabbed interfaces
checkboxRadix CheckboxCheckboxes
tooltipRadix TooltipHover tooltips
scroll-areaRadix ScrollAreaCustom scrollbars

Custom Components

syaOS-specific UI components:

audio-bars

Real-time audio visualization bars.

interface AudioBarsProps {
  isPlaying: boolean;
  barCount?: number;
  color?: string;
  height?: number;
}

dial

Rotary knob control for audio parameters.

interface DialProps {
  value: number;
  min: number;
  max: number;
  step?: number;
  onChange: (value: number) => void;
  size?: "sm" | "md" | "lg";
  color?: string;
  label?: string;
  showValue?: boolean;
}
Features:
  • Horizontal drag-to-adjust
  • Touch and mouse support
  • Conic gradient fill
  • Size variants

volume-bar

Volume level indicator with animation.

interface VolumeBarProps {
  level: number;
  maxLevel?: number;
  barCount?: number;
  activeColor?: string;
  inactiveColor?: string;
}

playback-bars

Animated equalizer-style bars.

interface PlaybackBarsProps {
  isPlaying: boolean;
  variant?: "small" | "medium" | "large";
}

Theme-Aware Button Pattern

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, ...props }, ref) => {
    const currentTheme = useThemeStore((state) => state.current);
    const isXpTheme = currentTheme === "xp" || currentTheme === "win98";
    const isMacTheme = currentTheme === "macosx";

    // macOS Aqua styling
    if (isMacTheme && variant === "default") {
      return <Comp className={cn("aqua-button primary", className)} {...props} />;
    }
    
    // Windows styling
    if (isXpTheme && variant === "default") {
      return <Comp className={cn("button", className)} {...props} />;
    }
    
    // Fallback
    return <Comp className={cn(buttonVariants({ variant }), className)} {...props} />;
  }
);

Dialog Components

Dialog Inventory

DialogPurposeHas Sound
AboutDialogApp informationNo
AboutFinderDialogSystem infoYes
BootScreenBoot animationYes
ConfirmDialogConfirmationsYes (Alert)
EmojiDialogEmoji pickerNo
HelpDialogApp help contentNo
InputDialogText inputNo
LoginDialogAuthenticationNo
ShareItemDialogSharing optionsNo
SongSearchDialogSong searchNo

Dialog Pattern

graph TD
    A[Dialog Component] --> B[Dialog Root]
    B --> C[DialogContent]
    C --> D[DialogHeader]
    C --> E[Content Area]
    C --> F[DialogFooter]
    
    D --> D1[Theme Title Bar]
    D1 --> D1a[macOS Traffic Lights]
    D1 --> D1b[Windows Controls]
    
    F --> F1[Cancel Button]
    F --> F2[Confirm Button]

ConfirmDialog Example

export function ConfirmDialog({
  isOpen,
  onOpenChange,
  onConfirm,
  title,
  description,
}: ConfirmDialogProps) {
  const { play: playAlertSound } = useSound(Sounds.ALERT_SOSUMI);
  const currentTheme = useThemeStore((state) => state.current);
  
  // Play alert sound on open
  useEffect(() => {
    if (isOpen) {
      playAlertSound();
    }
  }, [isOpen, playAlertSound]);

  return (
    <Dialog open={isOpen} onOpenChange={onOpenChange}>
      <DialogContent>
        <DialogHeader>{title}</DialogHeader>
        <div className="p-4">{description}</div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button onClick={onConfirm}>Confirm</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Shared Components

ThemedIcon

Theme-aware icon rendering with fallback support.

interface ThemedIconProps extends ImgHTMLAttributes<HTMLImageElement> {
  name: string;
  alt?: string;
  themeOverride?: OsThemeId;
}

// Usage
<ThemedIcon name="folder.png" alt="Folder" />
Features:
  • Automatic theme path resolution
  • Fallback to default icons
  • Safari image stabilizer for themed variants
  • Async theme path loading
Resolution Logic:
1. Check /icons/{theme}/{name}
2. If not in manifest, use /icons/default/{name}

TrafficLightButton

macOS window control buttons.

interface TrafficLightButtonProps {
  color: "red" | "yellow" | "green";
  onClick: () => void;
  isForeground?: boolean;
  ariaLabel?: string;
}
Features:
  • Gradient fills matching Aqua design
  • Inactive state styling
  • Enlarged clickable area
  • Proper accessibility labels

EmojiAquarium

Decorative animated emoji display.

interface EmojiAquariumProps {
  emojis: string[];
  containerRef: RefObject<HTMLDivElement>;
  speed?: number;
}

LinkPreview

URL preview card with OpenGraph data.

interface LinkPreviewProps {
  url: string;
  onLoad?: (data: OGData) => void;
  compact?: boolean;
}

Theme Integration Pattern

Standard Pattern

function MyComponent() {
  const currentTheme = useThemeStore((state) => state.current);
  const isXpTheme = currentTheme === "xp" || currentTheme === "win98";
  const isMacTheme = currentTheme === "macosx";

  return (
    <div className={cn(
      "base-styles",
      isMacTheme && "mac-specific-styles",
      isXpTheme && "windows-specific-styles",
    )}>
      {/* content */}
    </div>
  );
}

Theme Metadata Usage

import { getThemeMetadata } from "@/themes";

function ResponsiveLayout() {
  const theme = useThemeStore((s) => s.current);
  const metadata = getThemeMetadata(theme);
  
  return (
    <div style={{
      paddingTop: metadata.menuBarHeight,
      paddingBottom: metadata.hasDock ? metadata.baseDockHeight : metadata.taskbarHeight,
    }}>
      {/* content */}
    </div>
  );
}

Sound Integration Pattern

Most interactive components integrate with useSound:

import { useSound, Sounds } from "@/hooks/useSound";

function InteractiveButton({ onClick, children }) {
  const { play: playClick } = useSound(Sounds.BUTTON_CLICK);
  
  const handleClick = (e) => {
    playClick();
    onClick?.(e);
  };
  
  return <button onClick={handleClick}>{children}</button>;
}

Composition Patterns

WindowFrame + App Content

<WindowFrame
  appId="ipod"
  title="iPod"
  material="transparent"
  menuBar={<IpodMenuBar />}
  keepMountedWhenMinimized={true}
  windowConstraints={{
    minWidth: 280,
    minHeight: 400,
  }}
>
  <IpodAppContent />
</WindowFrame>

Dialog with Theme-Aware Content

<Dialog open={isOpen} onOpenChange={onOpenChange}>
  <DialogContent className="max-w-[400px]">
    <DialogHeader>
      {title}
    </DialogHeader>
    <div className={isXpTheme ? "window-body" : "p-6"}>
      {content}
    </div>
    <DialogFooter>
      <Button variant={isMacTheme ? "default" : "retro"}>
        Confirm
      </Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Component Relationships

graph TB
    subgraph "Application Layer"
        AM[AppManager]
    end
    
    subgraph "Layout Layer"
        MB[MenuBar]
        DT[Desktop]
        WF[WindowFrame]
        DK[Dock]
    end
    
    subgraph "UI Layer"
        BTN[Button]
        DLG[Dialog]
        RCM[RightClickMenu]
    end
    
    subgraph "Shared Layer"
        TI[ThemedIcon]
        TLB[TrafficLightButton]
    end
    
    subgraph "State Layer"
        TS[useThemeStore]
        AS[useAppStore]
    end
    
    AM --> MB
    AM --> DT
    AM --> WF
    AM --> DK
    
    MB --> TI
    DK --> TI
    WF --> TLB
    DLG --> TLB
    
    MB --> TS
    DT --> TS
    WF --> TS
    DK --> AS

Best Practices

  1. Always use useThemeStore for theme-conditional rendering
  1. Use ThemedIcon for all icon rendering (handles theme paths)
  2. Integrate useSound for interactive feedback
  3. Use cn() utility for conditional classNames
  4. Use shadcn components from @/components/ui/ as base primitives
  5. Support touch and mouse for custom interactive components
  6. Provide accessibility labels for interactive elements

Related Documentation