Skip to content

06 - Components

Overview

Wavic WebApp uses a combination of RizzUI v2 (pre-built component library) and custom components. The component architecture follows atomic design principles.

Component Organization

src/components/
├── ui/                    # Base UI components (atoms/molecules)
├── cards/                 # Card components
├── headers/               # Page headers
├── modal/                 # Modal dialogs
├── skeletons/             # Loading states
├── tables/                # Data tables
├── icons/                 # Icon components
├── loader/                # Loading indicators
├── charts/                # Data visualization
├── banners/               # Promotional banners
├── button/                # Button variants
├── search/                # Search components
├── dnd-sortable/          # Drag and drop
├── drop-downs/            # Dropdown menus
└── settings/              # Settings UI

Design System

RizzUI Components

RizzUI provides the base component library:

typescript
import {
  Button,
  Text,
  Loader,
  Tooltip,
  Input,
  Select,
  Modal,
  Drawer,
  Avatar,
  Badge,
  Checkbox,
  Radio,
  Switch,
  Textarea,
  // ... more
} from 'rizzui';

Tailwind CSS

All styling uses Tailwind with custom theme:

typescript
// Utility for merging classes
import cn from '@/utils/class-names';

<div className={cn(
  'flex items-center gap-4',
  'bg-gray-100 dark:bg-gray-800',
  isActive && 'ring-2 ring-primary'
)} />

Color System

css
/* src/app/globals.css - CSS-first Tailwind v4 config */
@theme inline {
  --color-gray-0: rgb(var(--gray-0) / <alpha-value>);
  /* ... 50-1000 */
  --color-primary-lighter: rgb(var(--primary-lighter) / <alpha-value>);
  --color-primary: rgb(var(--primary-default) / <alpha-value>);
  --color-primary-dark: rgb(var(--primary-dark) / <alpha-value>);
  /* secondary, red, orange, blue, green... */
}

Core Components

1. Page Header

Standard page header with breadcrumbs:

typescript
// src/app/shared/page-header.tsx
import PageHeader from '@/app/shared/page-header';

<PageHeader
  title="Projects"
  breadcrumb={[
    { href: '/', name: 'Artist spaces' },
    { name: 'Artist space' },
  ]}
/>

2. Artist Header

Artist-specific header with actions:

typescript
// src/components/headers/artist-header.tsx
<ArtistHeader
  artist={artist}
  isEditable={isEditable}
  role={role}
  onShare={handleShareModal}
  onSettings={handleSettings}
/>

3. Project Header

typescript
// src/components/headers/new-project-header.tsx
<ProjectHeader
  project={project}
  tracks={tracks}
  onAddTrack={handleAddTrack}
  isEditable={isEditable}
/>

4. Track Player

Audio player with waveform:

typescript
// src/components/ui/Track-player.tsx
// Used by src/app/shared/track-player/

<TrackPlayer
  track={track}
  audioFile={selectedAudioTrack}
  onPlayPause={togglePlay}
  onSeek={handleSeek}
/>

Components:

  • desktop-player.tsx - Full desktop player
  • mobile-player.tsx - Compact mobile player
  • audio-controllers.tsx - Play/pause, volume, etc.
  • player-dashboard.tsx - Main player wrapper

5. Project Cards

typescript
// src/app/shared/artist/get-projects.tsx
<ProjectCards
  projects={projects}
  artistId={artistId}
  onProjectClick={handleProjectClick}
  isEditable={isEditable}
/>

6. Share Modal

typescript
// src/components/modal/share-modal.tsx
<ShareModal
  artistId={artistId}
  teamMembers={collaborators}
  type="artist"  // or "project", "track"
/>
typescript
// src/app/shared/modal-views/container.tsx
// Global modal container - included in root layout
<GlobalModal />

Using Modals

typescript
import { useModal } from '@/app/shared/modal-views/use-modal';

function MyComponent() {
  const { openModal, closeModal } = useModal();

  const handleOpen = () => {
    openModal({
      view: <ShareModal artistId={artistId} />,
      customSize: '700px',
    });
  };

  return <Button onClick={handleOpen}>Share</Button>;
}

Drawer System

Drawer Views Container

typescript
// src/app/shared/drawer-views/container.tsx
<GlobalDrawer />

Using Drawers

typescript
import { useDrawer } from '@/app/shared/drawer-views/use-drawer';

function MyComponent() {
  const { openDrawer, closeDrawer } = useDrawer();

  const handleOpen = () => {
    openDrawer({
      view: <SettingsPanel />,
      placement: 'right',
    });
  };

  return <Button onClick={handleOpen}>Settings</Button>;
}

Loading States

Skeleton Components

typescript
// src/components/skeletons/
import HeaderSkeleton from '@/components/skeletons/header-skeleton';
import { ProjectSkeleton } from '@/components/skeletons/project-skeleton';

if (loading) {
  return (
    <>
      <HeaderSkeleton />
      <ProjectSkeleton count={6} />
    </>
  );
}

Loader Component

typescript
import { Loader } from 'rizzui';

<Loader size="lg" variant="spinner" />

Progress Bar

typescript
// src/components/progress-bar/
<ProgressBar value={uploadProgress} max={100} />

Form Components

Upload Component

typescript
// src/components/ui/upload.tsx
import Upload from '@/components/ui/upload';

<Upload
  accept="audio/*"
  multiple
  onDrop={handleFiles}
  maxSize={50 * 1024 * 1024}  // 50MB
/>

Dropzone

typescript
// src/components/globalDropZone.tsx
// Global file drop handling for the entire app

Select with Status

typescript
// src/components/ui/status-select.tsx
import StatusSelect from '@/components/ui/status-select';

<StatusSelect
  value={status}
  onChange={setStatus}
  options={TRACK_STATUS_OPTIONS}
/>

Date Picker

typescript
// src/components/ui/datepicker.tsx
import DatePicker from '@/components/ui/datepicker';

<DatePicker
  selected={releaseDate}
  onChange={setReleaseDate}
  placeholderText="Release date"
/>

Table Components

Controlled Table

typescript
// src/components/controlled-table/
import ControlledTable from '@/components/controlled-table';

<ControlledTable
  data={tracks}
  columns={columns}
  isLoading={loading}
  showLoadingText
  scroll={{ x: 800 }}
/>

Table with Sorting/Filtering

Using @tanstack/react-table:

typescript
import { useTable } from '@/hooks/use-table';

const {
  table,
  columnFilters,
  setColumnFilters,
  sortingState,
  setSortingState,
} = useTable({
  data: tracks,
  columns,
});

Icon System

React Icons

typescript
import { HiOutlineUpload } from 'react-icons/hi';
import { IoMdAdd, IoIosLink } from 'react-icons/io';
import { FiPlay, FiPause } from 'react-icons/fi';

<HiOutlineUpload className="h-5 w-5" />

Custom Icons

typescript
// src/components/icons/
import { PlayIcon, PauseIcon, TrashIcon } from '@/components/icons';

Animation

Framer Motion

typescript
import { motion, AnimatePresence } from 'framer-motion';

<AnimatePresence>
  {isVisible && (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.2 }}
    >
      Content
    </motion.div>
  )}
</AnimatePresence>

Auto-Animate

typescript
import { useAutoAnimate } from '@formkit/auto-animate/react';

function List() {
  const [parent] = useAutoAnimate();

  return (
    <ul ref={parent}>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

Drag and Drop

DnD Kit

typescript
// src/components/dnd-sortable/
import {
  DndContext,
  closestCenter,
  useSensor,
  useSensors,
  PointerSensor,
} from '@dnd-kit/core';
import {
  SortableContext,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';

<DndContext
  sensors={sensors}
  collisionDetection={closestCenter}
  onDragEnd={handleDragEnd}
>
  <SortableContext items={items} strategy={verticalListSortingStrategy}>
    {items.map((item) => (
      <SortableItem key={item.id} id={item.id} />
    ))}
  </SortableContext>
</DndContext>

Toast Notifications

typescript
import { toast } from 'react-hot-toast';

// Success
toast.success('Track uploaded successfully');

// Error
toast.error('Failed to upload track');

// Custom
toast('Custom message', {
  icon: '🎵',
  duration: 3000,
});

Tooltips

typescript
import { Tooltip } from 'rizzui';

<Tooltip content="Share with team" placement="top">
  <Button>Share</Button>
</Tooltip>

Component Best Practices

  1. Use RizzUI components as base building blocks
  2. Compose custom components from RizzUI atoms
  3. Use cn() utility for conditional classes
  4. Extract loading states into skeleton components
  5. Use TypeScript for all component props
  6. Keep components focused - single responsibility
  7. Use modals/drawers for forms and detailed views

Ctrl-Audio Platform Documentation