Formatting

This commit is contained in:
Tickbase
2025-05-18 18:23:06 +02:00
parent bbbd7482c1
commit 81519e89b7
61 changed files with 714 additions and 775 deletions

View File

@@ -39,14 +39,14 @@ const ActionButton: FC<ActionButtonProps> = ({
const getButtonVariant = (): ButtonVariant => {
// For uninstall actions, use danger variant
if (isInstalled) return 'danger'
// For install actions, use success variant
// For install actions, use success variant
return 'success'
}
// Select appropriate icon based on action type and state
const getIconInfo = () => {
const isCream = action.includes('cream')
if (isInstalled) {
// Uninstall actions
return { name: layers, variant: 'bold' }
@@ -66,17 +66,13 @@ const ActionButton: FC<ActionButtonProps> = ({
disabled={disabled || isWorking}
fullWidth
className={`action-button ${className}`}
leftIcon={isWorking ? undefined : (
<Icon
name={iconInfo.name}
variant={iconInfo.variant}
size="md"
/>
)}
leftIcon={
isWorking ? undefined : <Icon name={iconInfo.name} variant={iconInfo.variant} size="md" />
}
>
{getButtonText()}
</Button>
)
}
export default ActionButton
export default ActionButton

View File

@@ -1,11 +1,11 @@
import { Icon, check } from '@/components/icons'
interface AnimatedCheckboxProps {
checked: boolean;
onChange: () => void;
label?: string;
sublabel?: string;
className?: string;
checked: boolean
onChange: () => void
label?: string
sublabel?: string
className?: string
}
/**
@@ -20,24 +20,12 @@ const AnimatedCheckbox = ({
}: AnimatedCheckboxProps) => {
return (
<label className={`animated-checkbox ${className}`}>
<input
type="checkbox"
checked={checked}
onChange={onChange}
className="checkbox-original"
/>
<input type="checkbox" checked={checked} onChange={onChange} className="checkbox-original" />
<span className={`checkbox-custom ${checked ? 'checked' : ''}`}>
{checked && (
<Icon
name={check}
variant="bold"
size="sm"
className="checkbox-icon"
/>
)}
{checked && <Icon name={check} variant="bold" size="sm" className="checkbox-icon" />}
</span>
{(label || sublabel) && (
<div className="checkbox-content">
{label && <span className="checkbox-label">{label}</span>}
@@ -48,4 +36,4 @@ const AnimatedCheckbox = ({
)
}
export default AnimatedCheckbox
export default AnimatedCheckbox

View File

@@ -1,15 +1,15 @@
import { FC, ButtonHTMLAttributes } from 'react';
import { FC, ButtonHTMLAttributes } from 'react'
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
export type ButtonSize = 'small' | 'medium' | 'large';
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'success' | 'warning'
export type ButtonSize = 'small' | 'medium' | 'large'
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
isLoading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
fullWidth?: boolean;
variant?: ButtonVariant
size?: ButtonSize
isLoading?: boolean
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
fullWidth?: boolean
}
/**
@@ -32,7 +32,7 @@ const Button: FC<ButtonProps> = ({
small: 'btn-sm',
medium: 'btn-md',
large: 'btn-lg',
}[size];
}[size]
// Variant class mapping
const variantClass = {
@@ -41,7 +41,7 @@ const Button: FC<ButtonProps> = ({
danger: 'btn-danger',
success: 'btn-success',
warning: 'btn-warning',
}[variant];
}[variant]
return (
<button
@@ -56,12 +56,12 @@ const Button: FC<ButtonProps> = ({
<span className="spinner"></span>
</span>
)}
{leftIcon && !isLoading && <span className="btn-icon btn-icon-left">{leftIcon}</span>}
<span className="btn-text">{children}</span>
{rightIcon && !isLoading && <span className="btn-icon btn-icon-right">{rightIcon}</span>}
</button>
);
};
)
}
export default Button;
export default Button

View File

@@ -1,8 +1,8 @@
// Export all button components
export { default as Button } from './Button';
export { default as ActionButton } from './ActionButton';
export { default as AnimatedCheckbox } from './AnimatedCheckbox';
export { default as Button } from './Button'
export { default as ActionButton } from './ActionButton'
export { default as AnimatedCheckbox } from './AnimatedCheckbox'
// Export types
export type { ButtonVariant, ButtonSize } from './Button';
export type { ActionType } from './ActionButton';
export type { ButtonVariant, ButtonSize } from './Button'
export type { ActionType } from './ActionButton'

View File

@@ -4,11 +4,11 @@ export type LoadingType = 'spinner' | 'dots' | 'progress'
export type LoadingSize = 'small' | 'medium' | 'large'
interface LoadingIndicatorProps {
size?: LoadingSize;
type?: LoadingType;
message?: string;
progress?: number;
className?: string;
size?: LoadingSize
type?: LoadingType
message?: string
progress?: number
className?: string
}
/**
@@ -34,7 +34,7 @@ const LoadingIndicator = ({
switch (type) {
case 'spinner':
return <div className="loading-spinner"></div>
case 'dots':
return (
<div className="loading-dots">
@@ -43,7 +43,7 @@ const LoadingIndicator = ({
<div className="dot dot-3"></div>
</div>
)
case 'progress':
return (
<div className="loading-progress">
@@ -53,12 +53,10 @@ const LoadingIndicator = ({
style={{ width: `${Math.min(Math.max(progress, 0), 100)}%` }}
></div>
</div>
{progress > 0 && (
<div className="progress-percentage">{Math.round(progress)}%</div>
)}
{progress > 0 && <div className="progress-percentage">{Math.round(progress)}%</div>}
</div>
)
default:
return <div className="loading-spinner"></div>
}
@@ -72,4 +70,4 @@ const LoadingIndicator = ({
)
}
export default LoadingIndicator
export default LoadingIndicator

View File

@@ -1,3 +1,3 @@
export { default as LoadingIndicator } from './LoadingIndicator';
export { default as LoadingIndicator } from './LoadingIndicator'
export type { LoadingSize, LoadingType } from './LoadingIndicator';
export type { LoadingSize, LoadingType } from './LoadingIndicator'

View File

@@ -1,13 +1,13 @@
import { ReactNode, useEffect, useState } from 'react'
export interface DialogProps {
visible: boolean;
onClose?: () => void;
className?: string;
preventBackdropClose?: boolean;
children: ReactNode;
size?: 'small' | 'medium' | 'large';
showAnimationOnUnmount?: boolean;
visible: boolean
onClose?: () => void
className?: string
preventBackdropClose?: boolean
children: ReactNode
size?: 'small' | 'medium' | 'large'
showAnimationOnUnmount?: boolean
}
/**
@@ -66,17 +66,12 @@ const Dialog = ({
}[size]
return (
<div
className={`dialog-overlay ${showContent ? 'visible' : ''}`}
onClick={handleBackdropClick}
>
<div
className={`dialog ${sizeClass} ${className} ${showContent ? 'dialog-visible' : ''}`}
>
<div className={`dialog-overlay ${showContent ? 'visible' : ''}`} onClick={handleBackdropClick}>
<div className={`dialog ${sizeClass} ${className} ${showContent ? 'dialog-visible' : ''}`}>
{children}
</div>
</div>
)
}
export default Dialog
export default Dialog

View File

@@ -1,31 +1,23 @@
import { ReactNode } from 'react'
export interface DialogActionsProps {
children: ReactNode;
className?: string;
align?: 'start' | 'center' | 'end';
children: ReactNode
className?: string
align?: 'start' | 'center' | 'end'
}
/**
* Actions container for dialog footers
* Provides consistent spacing and alignment for action buttons
*/
const DialogActions = ({
children,
className = '',
align = 'end'
}: DialogActionsProps) => {
const DialogActions = ({ children, className = '', align = 'end' }: DialogActionsProps) => {
const alignClass = {
start: 'justify-start',
center: 'justify-center',
end: 'justify-end'
}[align];
end: 'justify-end',
}[align]
return (
<div className={`dialog-actions ${alignClass} ${className}`}>
{children}
</div>
)
return <div className={`dialog-actions ${alignClass} ${className}`}>{children}</div>
}
export default DialogActions
export default DialogActions

View File

@@ -1,8 +1,8 @@
import { ReactNode } from 'react'
export interface DialogBodyProps {
children: ReactNode;
className?: string;
children: ReactNode
className?: string
}
/**
@@ -10,11 +10,7 @@ export interface DialogBodyProps {
* Contains the main content with scrolling capability
*/
const DialogBody = ({ children, className = '' }: DialogBodyProps) => {
return (
<div className={`dialog-body ${className}`}>
{children}
</div>
)
return <div className={`dialog-body ${className}`}>{children}</div>
}
export default DialogBody
export default DialogBody

View File

@@ -1,8 +1,8 @@
import { ReactNode } from 'react'
export interface DialogFooterProps {
children: ReactNode;
className?: string;
children: ReactNode
className?: string
}
/**
@@ -10,11 +10,7 @@ export interface DialogFooterProps {
* Contains action buttons and optional status information
*/
const DialogFooter = ({ children, className = '' }: DialogFooterProps) => {
return (
<div className={`dialog-footer ${className}`}>
{children}
</div>
)
return <div className={`dialog-footer ${className}`}>{children}</div>
}
export default DialogFooter
export default DialogFooter

View File

@@ -1,9 +1,9 @@
import { ReactNode } from 'react'
export interface DialogHeaderProps {
children: ReactNode;
className?: string;
onClose?: () => void;
children: ReactNode
className?: string
onClose?: () => void
}
/**
@@ -15,11 +15,7 @@ const DialogHeader = ({ children, className = '', onClose }: DialogHeaderProps)
<div className={`dialog-header ${className}`}>
{children}
{onClose && (
<button
className="dialog-close-button"
onClick={onClose}
aria-label="Close dialog"
>
<button className="dialog-close-button" onClick={onClose} aria-label="Close dialog">
×
</button>
)}
@@ -27,4 +23,4 @@ const DialogHeader = ({ children, className = '', onClose }: DialogHeaderProps)
)
}
export default DialogHeader
export default DialogHeader

View File

@@ -8,15 +8,15 @@ import { Button, AnimatedCheckbox } from '@/components/buttons'
import { DlcInfo } from '@/types'
export interface DlcSelectionDialogProps {
visible: boolean;
gameTitle: string;
dlcs: DlcInfo[];
onClose: () => void;
onConfirm: (selectedDlcs: DlcInfo[]) => void;
isLoading: boolean;
isEditMode?: boolean;
loadingProgress?: number;
estimatedTimeLeft?: string;
visible: boolean
gameTitle: string
dlcs: DlcInfo[]
onClose: () => void
onConfirm: (selectedDlcs: DlcInfo[]) => void
isLoading: boolean
isEditMode?: boolean
loadingProgress?: number
estimatedTimeLeft?: string
}
/**
@@ -56,18 +56,18 @@ const DlcSelectionDialog = ({
if (!initialized) {
// Create a new array to ensure we don't share references
setSelectedDlcs([...dlcs])
// Determine initial selectAll state based on if all DLCs are enabled
const allSelected = dlcs.every((dlc) => dlc.enabled)
setSelectAll(allSelected)
// Mark as initialized to avoid resetting selections on subsequent updates
setInitialized(true)
} else {
// Find new DLCs that aren't in our current selection
// Find new DLCs that aren't in our current selection
const currentAppIds = new Set(selectedDlcs.map((dlc) => dlc.appid))
const newDlcs = dlcs.filter((dlc) => !currentAppIds.has(dlc.appid))
// If we found new DLCs, add them to our selection
if (newDlcs.length > 0) {
setSelectedDlcs((prev) => [...prev, ...newDlcs])
@@ -118,9 +118,9 @@ const DlcSelectionDialog = ({
// Submit selected DLCs to parent component
const handleConfirm = useCallback(() => {
// Create a deep copy to prevent reference issues
const dlcsCopy = JSON.parse(JSON.stringify(selectedDlcs));
onConfirm(dlcsCopy);
}, [onConfirm, selectedDlcs]);
const dlcsCopy = JSON.parse(JSON.stringify(selectedDlcs))
onConfirm(dlcsCopy)
}, [onConfirm, selectedDlcs])
// Count selected DLCs
const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length
@@ -128,7 +128,7 @@ const DlcSelectionDialog = ({
// Format dialog title and messages based on mode
const dialogTitle = isEditMode ? 'Edit DLCs' : 'Select DLCs to Enable'
const actionButtonText = isEditMode ? 'Save Changes' : 'Install with Selected DLCs'
// Format loading message to show total number of DLCs found
const getLoadingInfoText = () => {
if (isLoading && loadingProgress < 100) {
@@ -140,12 +140,7 @@ const DlcSelectionDialog = ({
}
return (
<Dialog
visible={visible}
onClose={onClose}
size="large"
preventBackdropClose={isLoading}
>
<Dialog visible={visible} onClose={onClose} size="large" preventBackdropClose={isLoading}>
<DialogHeader onClose={onClose}>
<h3>{dialogTitle}</h3>
<div className="dlc-game-info">
@@ -224,11 +219,7 @@ const DlcSelectionDialog = ({
>
Cancel
</Button>
<Button
variant="primary"
onClick={handleConfirm}
disabled={isLoading}
>
<Button variant="primary" onClick={handleConfirm} disabled={isLoading}>
{actionButtonText}
</Button>
</DialogActions>
@@ -237,4 +228,4 @@ const DlcSelectionDialog = ({
)
}
export default DlcSelectionDialog
export default DlcSelectionDialog

View File

@@ -7,20 +7,20 @@ import DialogActions from './DialogActions'
import { Button } from '@/components/buttons'
export interface InstallationInstructions {
type: string;
command: string;
game_title: string;
dlc_count?: number;
type: string
command: string
game_title: string
dlc_count?: number
}
export interface ProgressDialogProps {
visible: boolean;
title: string;
message: string;
progress: number;
showInstructions?: boolean;
instructions?: InstallationInstructions;
onClose?: () => void;
visible: boolean
title: string
message: string
progress: number
showInstructions?: boolean
instructions?: InstallationInstructions
onClose?: () => void
}
/**
@@ -126,7 +126,7 @@ const ProgressDialog = ({
<DialogHeader>
<h3>{title}</h3>
</DialogHeader>
<DialogBody>
<p>{message}</p>
@@ -150,24 +150,17 @@ const ProgressDialog = ({
</div>
)}
</DialogBody>
<DialogFooter>
<DialogActions>
{showInstructions && showCopyButton && (
<Button
variant="primary"
onClick={handleCopyCommand}
>
<Button variant="primary" onClick={handleCopyCommand}>
{copySuccess ? 'Copied!' : 'Copy to Clipboard'}
</Button>
)}
{isCloseButtonEnabled && (
<Button
variant="secondary"
onClick={onClose}
disabled={!isCloseButtonEnabled}
>
<Button variant="secondary" onClick={onClose} disabled={!isCloseButtonEnabled}>
Close
</Button>
)}
@@ -177,4 +170,4 @@ const ProgressDialog = ({
)
}
export default ProgressDialog
export default ProgressDialog

View File

@@ -1,17 +1,17 @@
// Export all dialog components
export { default as Dialog } from './Dialog';
export { default as DialogHeader } from './DialogHeader';
export { default as DialogBody } from './DialogBody';
export { default as DialogFooter } from './DialogFooter';
export { default as DialogActions } from './DialogActions';
export { default as ProgressDialog } from './ProgressDialog';
export { default as DlcSelectionDialog } from './DlcSelectionDialog';
// Export all dialog components
export { default as Dialog } from './Dialog'
export { default as DialogHeader } from './DialogHeader'
export { default as DialogBody } from './DialogBody'
export { default as DialogFooter } from './DialogFooter'
export { default as DialogActions } from './DialogActions'
export { default as ProgressDialog } from './ProgressDialog'
export { default as DlcSelectionDialog } from './DlcSelectionDialog'
// Export types
export type { DialogProps } from './Dialog';
export type { DialogHeaderProps } from './DialogHeader';
export type { DialogBodyProps } from './DialogBody';
export type { DialogFooterProps } from './DialogFooter';
export type { DialogActionsProps } from './DialogActions';
export type { ProgressDialogProps, InstallationInstructions } from './ProgressDialog';
export type { DlcSelectionDialogProps } from './DlcSelectionDialog';
export type { DialogProps } from './Dialog'
export type { DialogHeaderProps } from './DialogHeader'
export type { DialogBodyProps } from './DialogBody'
export type { DialogFooterProps } from './DialogFooter'
export type { DialogActionsProps } from './DialogActions'
export type { ProgressDialogProps, InstallationInstructions } from './ProgressDialog'
export type { DlcSelectionDialogProps } from './DlcSelectionDialog'

View File

@@ -161,4 +161,4 @@ const GameItem = ({ game, onAction, onEdit }: GameItemProps) => {
)
}
export default GameItem
export default GameItem

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo } from 'react'
import {GameItem, ImagePreloader} from '@/components/games'
import { GameItem, ImagePreloader } from '@/components/games'
import { ActionType } from '@/components/buttons'
import { Game } from '@/types'
import LoadingIndicator from '../common/LoadingIndicator'
@@ -18,7 +18,7 @@ interface GameListProps {
const GameList = ({ games, isLoading, onAction, onEdit }: GameListProps) => {
const [imagesPreloaded, setImagesPreloaded] = useState(false)
// Sort games alphabetically by title
// Sort games alphabetically by title
const sortedGames = useMemo(() => {
return [...games].sort((a, b) => a.title.localeCompare(b.title))
}, [games])
@@ -35,11 +35,7 @@ const GameList = ({ games, isLoading, onAction, onEdit }: GameListProps) => {
if (isLoading) {
return (
<div className="game-list">
<LoadingIndicator
type="spinner"
size="large"
message="Scanning for games..."
/>
<LoadingIndicator type="spinner" size="large" message="Scanning for games..." />
</div>
)
}
@@ -68,4 +64,4 @@ const GameList = ({ games, isLoading, onAction, onEdit }: GameListProps) => {
)
}
export default GameList
export default GameList

View File

@@ -16,24 +16,24 @@ const ImagePreloader = ({ gameIds, onComplete }: ImagePreloaderProps) => {
try {
// Only preload the first batch for performance (10 images max)
const batchToPreload = gameIds.slice(0, 10)
// Track loading progress
let loadedCount = 0
const totalImages = batchToPreload.length
// Load images in parallel
await Promise.allSettled(
batchToPreload.map(async (id) => {
await findBestGameImage(id)
loadedCount++
// If all images are loaded, call onComplete
if (loadedCount === totalImages && onComplete) {
onComplete()
}
})
)
// Fallback if Promise.allSettled doesn't trigger onComplete
if (onComplete) {
onComplete()
@@ -58,4 +58,4 @@ const ImagePreloader = ({ gameIds, onComplete }: ImagePreloaderProps) => {
return null
}
export default ImagePreloader
export default ImagePreloader

View File

@@ -1,4 +1,4 @@
// Export all game components
export { default as GameList } from './GameList';
export { default as GameItem } from './GameItem';
export { default as ImagePreloader } from './ImagePreloader';
export { default as GameList } from './GameList'
export { default as GameItem } from './GameItem'
export { default as ImagePreloader } from './ImagePreloader'

View File

@@ -45,7 +45,7 @@ const getSizeValue = (size: IconSize): string => {
sm: '16px',
md: '24px',
lg: '32px',
xl: '48px'
xl: '48px',
}
return sizeMap[size] || sizeMap.md
@@ -54,11 +54,15 @@ const getSizeValue = (size: IconSize): string => {
/**
* Gets the icon component based on name and variant
*/
const getIconComponent = (name: string, variant: IconVariant | string): React.ComponentType<React.SVGProps<SVGSVGElement>> | null => {
const getIconComponent = (
name: string,
variant: IconVariant | string
): React.ComponentType<React.SVGProps<SVGSVGElement>> | null => {
// Normalize variant to ensure it's a valid IconVariant
const normalizedVariant = (variant === 'bold' || variant === 'outline' || variant === 'brand')
? variant as IconVariant
: undefined;
const normalizedVariant =
variant === 'bold' || variant === 'outline' || variant === 'brand'
? (variant as IconVariant)
: undefined
// Try to get the icon from the specified variant
switch (normalizedVariant) {
@@ -97,7 +101,7 @@ const Icon: React.FC<IconProps> = ({
}) => {
// Determine default variant based on icon type if no variant provided
let defaultVariant: IconVariant | string = variant
if (defaultVariant === undefined) {
if (BRAND_ICON_NAMES.has(name)) {
defaultVariant = 'brand'
@@ -105,17 +109,17 @@ const Icon: React.FC<IconProps> = ({
defaultVariant = 'bold' // Default to bold for non-brand icons
}
}
// Get the icon component based on name and variant
let finalIconComponent = getIconComponent(name, defaultVariant)
let finalVariant = defaultVariant
// Try fallbacks if the icon doesn't exist in the requested variant
if (!finalIconComponent && defaultVariant !== 'outline') {
finalIconComponent = getIconComponent(name, 'outline')
finalVariant = 'outline'
}
if (!finalIconComponent && defaultVariant !== 'bold') {
finalIconComponent = getIconComponent(name, 'bold')
finalVariant = 'bold'
@@ -125,7 +129,7 @@ const Icon: React.FC<IconProps> = ({
finalIconComponent = getIconComponent(name, 'brand')
finalVariant = 'brand'
}
// If still no icon found, return null
if (!finalIconComponent) {
console.warn(`Icon not found: ${name} (${defaultVariant})`)
@@ -134,7 +138,7 @@ const Icon: React.FC<IconProps> = ({
const sizeValue = getSizeValue(size)
const combinedClassName = `icon icon-${size} icon-${finalVariant} ${className}`.trim()
const IconComponentToRender = finalIconComponent
return (
@@ -142,7 +146,7 @@ const Icon: React.FC<IconProps> = ({
className={combinedClassName}
width={sizeValue}
height={sizeValue}
fill={fillColor || 'currentColor'}
fill={fillColor || 'currentColor'}
stroke={strokeColor || 'currentColor'}
role="img"
aria-hidden={!title}
@@ -152,4 +156,4 @@ const Icon: React.FC<IconProps> = ({
)
}
export default Icon
export default Icon

View File

@@ -21,4 +21,4 @@
// IconComponent.displayName = `${name}Icon`
// return IconComponent
//}
//
//

View File

@@ -4,4 +4,4 @@ export { ReactComponent as Steam } from './steam.svg'
export { ReactComponent as Windows } from './windows.svg'
export { ReactComponent as Github } from './github.svg'
export { ReactComponent as Discord } from './discord.svg'
export { ReactComponent as Proton } from './proton.svg'
export { ReactComponent as Proton } from './proton.svg'

View File

@@ -57,7 +57,7 @@ export const IconNames = {
Warning: warning,
Wine: wine,
Diamond: diamond,
// Brand icons
Discord: discord,
GitHub: github,
@@ -97,4 +97,4 @@ export const IconNames = {
//export const CheckBoldIcon = createIconComponent(check, 'bold')
//export const InfoBoldIcon = createIconComponent(info, 'bold')
//export const WarningBoldIcon = createIconComponent(warning, 'bold')
//export const ErrorBoldIcon = createIconComponent(error, 'bold')
//export const ErrorBoldIcon = createIconComponent(error, 'bold')

View File

@@ -15,4 +15,4 @@ export { ReactComponent as Search } from './search.svg'
export { ReactComponent as Trash } from './trash.svg'
export { ReactComponent as Warning } from './warning.svg'
export { ReactComponent as Wine } from './wine.svg'
export { ReactComponent as Diamond } from './diamond.svg'
export { ReactComponent as Diamond } from './diamond.svg'

View File

@@ -15,4 +15,4 @@ export { ReactComponent as Search } from './search.svg'
export { ReactComponent as Trash } from './trash.svg'
export { ReactComponent as Warning } from './warning.svg'
export { ReactComponent as Wine } from './wine.svg'
export { ReactComponent as Diamond } from './diamond.svg'
export { ReactComponent as Diamond } from './diamond.svg'

View File

@@ -109,13 +109,7 @@ const AnimatedBackground = () => {
}
}, [])
return (
<canvas
ref={canvasRef}
className="animated-background"
aria-hidden="true"
/>
)
return <canvas ref={canvasRef} className="animated-background" aria-hidden="true" />
}
export default AnimatedBackground
export default AnimatedBackground

View File

@@ -2,14 +2,14 @@ import { Component, ErrorInfo, ReactNode } from 'react'
import { Button } from '@/components/buttons'
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, errorInfo: ErrorInfo) => void
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
hasError: boolean
error: Error | null
}
/**
@@ -35,7 +35,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
// Log the error
console.error('ErrorBoundary caught an error:', error, errorInfo)
// Call the onError callback if provided
if (this.props.onError) {
this.props.onError(error, errorInfo)
@@ -52,22 +52,18 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
if (this.props.fallback) {
return this.props.fallback
}
// Default error UI
return (
<div className="error-container">
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<p>{this.state.error?.toString()}</p>
</details>
<Button
variant="primary"
onClick={this.handleReset}
className="error-retry-button"
>
<Button variant="primary" onClick={this.handleReset} className="error-retry-button">
Try again
</Button>
</div>
@@ -78,4 +74,4 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
}
export default ErrorBoundary
export default ErrorBoundary

View File

@@ -12,12 +12,7 @@ interface HeaderProps {
* Application header component
* Contains the app title, search input, and refresh button
*/
const Header = ({
onRefresh,
refreshDisabled = false,
onSearch,
searchQuery,
}: HeaderProps) => {
const Header = ({ onRefresh, refreshDisabled = false, onSearch, searchQuery }: HeaderProps) => {
return (
<header className="app-header">
<div className="app-title">
@@ -25,9 +20,9 @@ const Header = ({
<h1>CreamLinux</h1>
</div>
<div className="header-controls">
<Button
<Button
variant="primary"
onClick={onRefresh}
onClick={onRefresh}
disabled={refreshDisabled}
className="refresh-button"
leftIcon={<Icon name={refresh} variant="bold" size="md" />}
@@ -49,4 +44,4 @@ const Header = ({
)
}
export default Header
export default Header

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react'
interface InitialLoadingScreenProps {
message: string;
progress: number;
onComplete?: () => void;
message: string
progress: number
onComplete?: () => void
}
/**
@@ -11,36 +11,36 @@ interface InitialLoadingScreenProps {
*/
const InitialLoadingScreen = ({ message, progress }: InitialLoadingScreenProps) => {
const [detailedStatus, setDetailedStatus] = useState<string[]>([
"Initializing application...",
"Setting up Steam integration...",
"Preparing DLC management..."
]);
'Initializing application...',
'Setting up Steam integration...',
'Preparing DLC management...',
])
// Use a sequence of messages based on progress
useEffect(() => {
const messages = [
{ threshold: 10, message: "Checking system requirements..." },
{ threshold: 30, message: "Scanning Steam libraries..." },
{ threshold: 50, message: "Discovering games..." },
{ threshold: 70, message: "Analyzing game configurations..." },
{ threshold: 90, message: "Preparing user interface..." },
{ threshold: 100, message: "Ready to launch!" }
];
{ threshold: 10, message: 'Checking system requirements...' },
{ threshold: 30, message: 'Scanning Steam libraries...' },
{ threshold: 50, message: 'Discovering games...' },
{ threshold: 70, message: 'Analyzing game configurations...' },
{ threshold: 90, message: 'Preparing user interface...' },
{ threshold: 100, message: 'Ready to launch!' },
]
// Find current status message based on progress
const currentMessage = messages.find(m => progress <= m.threshold)?.message || "Loading...";
const currentMessage = messages.find((m) => progress <= m.threshold)?.message || 'Loading...'
// Add new messages to the log as progress increases
if (currentMessage && !detailedStatus.includes(currentMessage)) {
setDetailedStatus(prev => [...prev, currentMessage]);
setDetailedStatus((prev) => [...prev, currentMessage])
}
}, [progress, detailedStatus]);
}, [progress, detailedStatus])
return (
<div className="initial-loading-screen">
<div className="loading-content">
<h1>CreamLinux</h1>
<div className="loading-animation">
{/* Enhanced animation with SVG or more elaborate CSS animation */}
<div className="loading-circles">
@@ -49,9 +49,9 @@ const InitialLoadingScreen = ({ message, progress }: InitialLoadingScreenProps)
<div className="circle circle-3"></div>
</div>
</div>
<p className="loading-message">{message}</p>
{/* Add a detailed status log that shows progress steps */}
<div className="loading-status-log">
{detailedStatus.slice(-4).map((status, index) => (
@@ -61,15 +61,15 @@ const InitialLoadingScreen = ({ message, progress }: InitialLoadingScreenProps)
</div>
))}
</div>
<div className="progress-bar-container">
<div className="progress-bar" style={{ width: `${progress}%` }} />
</div>
<div className="progress-percentage">{Math.round(progress)}%</div>
</div>
</div>
);
};
)
}
export default InitialLoadingScreen
export default InitialLoadingScreen

View File

@@ -22,29 +22,24 @@ const Sidebar = ({ setFilter, currentFilter }: SidebarProps) => {
const filters: FilterItem[] = [
{ id: 'all', label: 'All Games', icon: layers, variant: 'bold' },
{ id: 'native', label: 'Native', icon: linux, variant: 'brand' },
{ id: 'proton', label: 'Proton Required', icon: proton, variant: 'brand' }
{ id: 'proton', label: 'Proton Required', icon: proton, variant: 'brand' },
]
return (
<div className="sidebar">
<div className="sidebar-header">
<h2>Library</h2>
</div>
<ul className="filter-list">
{filters.map(filter => (
<li
{filters.map((filter) => (
<li
key={filter.id}
className={currentFilter === filter.id ? 'active' : ''}
onClick={() => setFilter(filter.id)}
>
<div className="filter-item">
<Icon
name={filter.icon}
variant={filter.variant}
size="md"
className="filter-icon"
/>
<Icon name={filter.icon} variant={filter.variant} size="md" className="filter-icon" />
<span>{filter.label}</span>
</div>
</li>
@@ -54,4 +49,4 @@ const Sidebar = ({ setFilter, currentFilter }: SidebarProps) => {
)
}
export default Sidebar
export default Sidebar

View File

@@ -1,6 +1,6 @@
// Export all layout components
export { default as Header } from './Header';
export { default as Sidebar } from './Sidebar';
export { default as AnimatedBackground } from './AnimatedBackground';
export { default as InitialLoadingScreen } from './InitialLoadingScreen';
export { default as ErrorBoundary } from './ErrorBoundary';
export { default as Header } from './Header'
export { default as Sidebar } from './Sidebar'
export { default as AnimatedBackground } from './AnimatedBackground'
export { default as InitialLoadingScreen } from './InitialLoadingScreen'
export { default as ErrorBoundary } from './ErrorBoundary'

View File

@@ -2,38 +2,38 @@ import { ReactNode, useState, useEffect, useCallback } from 'react'
import { Icon, check, info, warning, error } from '@/components/icons'
export interface ToastProps {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title?: string;
message: string;
duration?: number;
onDismiss: (id: string) => void;
id: string
type: 'success' | 'error' | 'warning' | 'info'
title?: string
message: string
duration?: number
onDismiss: (id: string) => void
}
/**
* Individual Toast component
* Displays a notification message with automatic dismissal
*/
const Toast = ({
id,
type,
title,
message,
duration = 5000, // default 5 seconds
onDismiss
const Toast = ({
id,
type,
title,
message,
duration = 5000, // default 5 seconds
onDismiss,
}: ToastProps) => {
const [visible, setVisible] = useState(false)
const [isClosing, setIsClosing] = useState(false);
const [isClosing, setIsClosing] = useState(false)
// Use useCallback to memoize the handleDismiss function
const handleDismiss = useCallback(() => {
setIsClosing(true);
setIsClosing(true)
// Give time for exit animation
setTimeout(() => {
setVisible(false);
setTimeout(() => onDismiss(id), 50);
}, 300);
}, [id, onDismiss]);
setVisible(false)
setTimeout(() => onDismiss(id), 50)
}, 300)
}, [id, onDismiss])
// Handle animation on mount/unmount
useEffect(() => {
@@ -60,20 +60,22 @@ const Toast = ({
const getIcon = (): ReactNode => {
switch (type) {
case 'success':
return <Icon name={check} size="md" variant='bold' />
return <Icon name={check} size="md" variant="bold" />
case 'error':
return <Icon name={error} size="md" variant='bold' />
return <Icon name={error} size="md" variant="bold" />
case 'warning':
return <Icon name={warning} size="md" variant='bold' />
return <Icon name={warning} size="md" variant="bold" />
case 'info':
return <Icon name={info} size="md" variant='bold' />
return <Icon name={info} size="md" variant="bold" />
default:
return <Icon name={info} size="md" variant='bold' />
return <Icon name={info} size="md" variant="bold" />
}
}
return (
<div className={`toast toast-${type} ${visible ? 'visible' : ''} ${isClosing ? 'closing' : ''}`}>
<div
className={`toast toast-${type} ${visible ? 'visible' : ''} ${isClosing ? 'closing' : ''}`}
>
<div className="toast-icon">{getIcon()}</div>
<div className="toast-content">
{title && <h4 className="toast-title">{title}</h4>}
@@ -86,4 +88,4 @@ const Toast = ({
)
}
export default Toast
export default Toast

View File

@@ -1,32 +1,28 @@
import Toast, { ToastProps } from './Toast'
export type ToastPosition =
| 'top-right'
| 'top-left'
| 'bottom-right'
| 'bottom-left'
| 'top-center'
export type ToastPosition =
| 'top-right'
| 'top-left'
| 'bottom-right'
| 'bottom-left'
| 'top-center'
| 'bottom-center'
interface ToastContainerProps {
toasts: Omit<ToastProps, 'onDismiss'>[];
onDismiss: (id: string) => void;
position?: ToastPosition;
toasts: Omit<ToastProps, 'onDismiss'>[]
onDismiss: (id: string) => void
position?: ToastPosition
}
/**
* Container for toast notifications
* Manages positioning and rendering of all toast notifications
*/
const ToastContainer = ({
toasts,
onDismiss,
position = 'bottom-right',
}: ToastContainerProps) => {
const ToastContainer = ({ toasts, onDismiss, position = 'bottom-right' }: ToastContainerProps) => {
if (toasts.length === 0) {
return null
}
return (
<div className={`toast-container ${position}`}>
{toasts.map((toast) => (
@@ -44,4 +40,4 @@ const ToastContainer = ({
)
}
export default ToastContainer
export default ToastContainer

View File

@@ -1,5 +1,5 @@
export { default as Toast } from './Toast';
export { default as ToastContainer } from './ToastContainer';
export { default as Toast } from './Toast'
export { default as ToastContainer } from './ToastContainer'
export type { ToastProps } from './Toast';
export type { ToastPosition } from './ToastContainer';
export type { ToastProps } from './Toast'
export type { ToastPosition } from './ToastContainer'