UI Components & Design System
PianoRhythm uses a custom fork of Hope UI, a comprehensive component library for SolidJS that provides a consistent design system, theming capabilities, and accessible UI components optimized for real-time musical applications.
Architecture Overview
Hope UI Integration
1. Custom Fork Configuration
{
"dependencies": {
"@hope-ui/solid": "github:PianoRhythm/hope-ui#main&path:packages/solid"
}
}
Key Features:
- Custom fork with PianoRhythm-specific modifications
- Enhanced theming capabilities for dark mode
- Optimized for real-time audio applications
- Extended component variants and styling options
2. Provider Setup
import { HopeProvider } from "@hope-ui/solid";
import ThemeConfig from "./util/theme-config";
export default function App() {
return (
<HopeProvider config={ThemeConfig}>
<ServiceRegistry>
<I18nProvider>
{/* Application content */}
</I18nProvider>
</ServiceRegistry>
</HopeProvider>
);
}
Theme System
1. Theme Configuration
const ThemeConfig: HopeThemeConfig = {
initialColorMode: "dark",
darkTheme: {
colors: {
// Primary color palette
primary1: "#363942",
primary2: "#434650",
primaryDark1: "#1f2126",
primaryDark2: "#2b2d34",
// Accent colors
accent1: "#00d1b2",
accent2: "#15d6b6",
// Semantic colors
tooltipContent: "#2b2d34",
tooltipArrow: "#2b2d34",
}
},
components: {
Button: {
baseStyle: {
root: {
backgroundColor: "$primary1",
color: "$neutral12",
transition: "color 0.1s ease, background-color 0.1s ease",
_hover: {
color: "$neutral1",
backgroundColor: "$accent2",
}
}
}
}
}
};
2. Dynamic Theme Switching
export default function AppThemesService() {
const [themeColors, setThemeColors] = createStore<AppThemeColors>({
primary: "#363942",
accent: "#00d1b2",
tertiary: "#ef9e08"
});
const setTheme = (selectedTheme: AppThemes) => {
let root = document.querySelector(".hope-ui-dark") as HTMLElement;
switch (selectedTheme) {
case AppThemes.THEME_1:
setThemeColors(setUIThemeColors("#1B2430"));
break;
case AppThemes.THEME_2:
setThemeColors(setUIThemeColors("#16213E"));
break;
// ... additional themes
}
};
}
Core Components
1. Layout Components
Box & Container
import { Box, Container, Center, VStack, HStack } from "@hope-ui/solid";
// Flexible layout building blocks
<Box p="$4" bg="$primary1" borderRadius="$md">
<VStack spacing="$3">
<HStack spacing="$2">
<Box flex="1">Content</Box>
</HStack>
</VStack>
</Box>
Responsive Grid
import { SimpleGrid, Grid, GridItem } from "@hope-ui/solid";
<SimpleGrid columns={{ "@initial": 1, "@md": 2, "@lg": 3 }} spacing="$4">
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</SimpleGrid>
2. Form Components
Input Components
Input: {
baseStyle: {
input: {
backgroundColor: "$primary1",
border: "1px solid var(--hope-colors-neutral9)",
_hover: {
border: "1px solid var(--hope-colors-neutral11)",
},
_focus: {
boxShadow: "none",
},
}
}
}
Select Components
import { Select, SelectTrigger, SelectContent, SelectOption } from "@hope-ui/solid";
<Select>
<SelectTrigger>
<SelectPlaceholder>Choose option</SelectPlaceholder>
<SelectValue />
<SelectIcon />
</SelectTrigger>
<SelectContent>
<SelectListbox>
<SelectOption value="option1">
<SelectOptionText>Option 1</SelectOptionText>
<SelectOptionIndicator />
</SelectOption>
</SelectListbox>
</SelectContent>
</Select>
3. Interactive Components
Button System
Button: {
baseStyle: {
root: {
backgroundColor: "$primary1",
color: "$neutral12",
transition: "color 0.1s ease, background-color 0.1s ease",
_focus: {
boxShadow: "none",
},
_active: {
transform: "scale(1.025)",
},
_hover: {
color: "$neutral1",
backgroundColor: "$accent2",
},
_disabled: {
opacity: 0.4,
pointerEvents: "none"
}
}
}
}
Modal System
Modal: {
baseStyle: {
header: {
background: "$neutral12",
color: "$primary1",
borderTopRadius: 5,
},
body: {
backgroundColor: "$primary1",
},
footer: {
background: "$primary1",
borderTop: "solid 1px gray",
borderBottomRadius: 5,
}
}
}
Custom Components
1. Specialized UI Components
Tooltip Help Component
interface ToolTipHelpProps extends IconProps {
tooltipLabel: JSXElement;
tooltipSize?: string | number;
tooltipPlacement?: string;
}
const ToolTipHelp_: Component<ToolTipHelpProps> = (props) => {
return (
<Box
__tooltip_title={props.tooltipLabel}
__tooltip_placement={props.tooltipPlacement ?? "right"}
__tooltip_open_delay={250}
>
<Icon
font-size="10px"
size={props.tooltipSize || "10px"}
cursor="help"
as={BsQuestionCircleFill}
/>
</Box>
);
};
Button with Tooltip
const ButtonToolTip_: Component<{
label: string;
tooltip?: string;
loading?: boolean;
disabled?: boolean;
onClick?: (evt: MouseEvent) => void;
}> = (props) => {
return (
<Tooltip
closeOnClick
openDelay={500}
placement="top"
disabled={!props.tooltip}
label={props.tooltip}
withArrow
background="$neutral12"
color="$primary1"
>
<Button
loading={props.loading}
disabled={props.disabled}
onClick={props.onClick}
>
{props.label}
</Button>
</Tooltip>
);
};
2. Pagination Component
export const Pagination: Component<{
currentPage: Accessor<number>;
totalPages: Accessor<number>;
handlePage: (page: number) => void;
}> = (props) => {
return (
<HStack spacing="$2" class="pagination-container">
<FaSolidChevronLeft
__tooltip_title="Go to Previous Page"
class={props.currentPage() == 1 ? "disabled" : "page-btn"}
onMouseDown={() => props.handlePage(props.currentPage() - 1)}
/>
<For each={allPages()}>
{v => (
<Box
__tooltip_title={`Go to: Page ${v + 1}`}
class={clsx([
"page-item unselectable",
props.currentPage() === v + 1 && "--active",
])}
onMouseDown={() => props.handlePage(v + 1)}
>
{v + 1}
</Box>
)}
</For>
<FaSolidChevronRight
__tooltip_title="Go to Next Page"
class={props.currentPage() == props.totalPages() ? "disabled" : "page-btn"}
onMouseDown={() => props.handlePage(props.currentPage() + 1)}
/>
</HStack>
);
};
Design Tokens
1. Color System
// Primary color palette
primary1: "#363942" // Base primary
primary2: "#434650" // Lighter primary
primaryDark1: "#1f2126" // Darker primary
primaryDark2: "#2b2d34" // Darkest primary
// Accent colors
accent1: "#00d1b2" // Primary accent (teal)
accent2: "#15d6b6" // Secondary accent
// Semantic colors
success: "#48bb78" // Success states
warning: "#ed8936" // Warning states
danger: "#f56565" // Error states
info: "#4299e1" // Information states
2. Spacing System
// Spacing scale (based on 4px base unit)
$1: "0.25rem" // 4px
$2: "0.5rem" // 8px
$3: "0.75rem" // 12px
$4: "1rem" // 16px
$5: "1.25rem" // 20px
$6: "1.5rem" // 24px
$8: "2rem" // 32px
$10: "2.5rem" // 40px
$12: "3rem" // 48px
3. Typography Scale
// Font sizes
xs: "0.75rem" // 12px
sm: "0.875rem" // 14px
base: "1rem" // 16px
lg: "1.125rem" // 18px
xl: "1.25rem" // 20px
"2xl": "1.5rem" // 24px
"3xl": "1.875rem" // 30px
"4xl": "2.25rem" // 36px
Responsive Design
1. Breakpoint System
const breakpoints = {
"@initial": "0px",
"@sm": "640px",
"@md": "768px",
"@lg": "1024px",
"@xl": "1280px",
"@2xl": "1536px"
};
2. Responsive Props
// Responsive spacing and sizing
<Box
p={{ "@initial": "$2", "@md": "$4", "@lg": "$6" }}
fontSize={{ "@initial": "sm", "@md": "base", "@lg": "lg" }}
>
Responsive content
</Box>
Accessibility Features
1. Focus Management
// Automatic focus trap in modals
import { focus-trap } from "@hope-ui/solid";
// Keyboard navigation support
<Button onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClick();
}
}}>
Accessible Button
</Button>
2. ARIA Support
// Built-in ARIA attributes
<Button
aria-label="Close dialog"
aria-describedby="dialog-description"
role="button"
tabIndex={0}
>
Close
</Button>
Performance Optimizations
1. CSS-in-JS Optimization
// Stitches provides automatic CSS optimization
// - Dead code elimination
// - Atomic CSS generation
// - Runtime style injection
// - Critical CSS extraction
2. Component Lazy Loading
// Lazy load heavy components
const HeavyModal = lazy(() => import('./HeavyModal'));
<Suspense fallback={<Spinner />}>
<Show when={showModal()}>
<HeavyModal />
</Show>
</Suspense>
Testing Components
1. Component Testing
import { render, screen } from "@solidjs/testing-library";
import { Button } from "@hope-ui/solid";
test("button renders correctly", () => {
render(() => <Button>Click me</Button>);
expect(screen.getByRole("button")).toHaveTextContent("Click me");
});
2. Theme Testing
test("theme colors are applied", () => {
const { container } = render(() =>
<HopeProvider config={ThemeConfig}>
<Box bg="$primary1">Test</Box>
</HopeProvider>
);
expect(container.firstChild).toHaveStyle({
backgroundColor: "rgb(54, 57, 66)"
});
});
Next Steps
- Frontend Architecture - Overall frontend structure
- Audio System - Audio-reactive UI components
- Testing Guide - Component testing strategies