diff --git a/app/components/@settings/tabs/connections/components/RepositoryCard.tsx b/app/components/@settings/tabs/connections/components/RepositoryCard.tsx index 3d798663..2ce02d33 100644 --- a/app/components/@settings/tabs/connections/components/RepositoryCard.tsx +++ b/app/components/@settings/tabs/connections/components/RepositoryCard.tsx @@ -21,6 +21,7 @@ export function RepositoryCard({ repo, onSelect }: RepositoryCardProps) { 'from-pink-500/10 to-purple-500/5', ]; const index = name.length % colors.length; + return colors[index]; }; @@ -31,10 +32,21 @@ export function RepositoryCard({ repo, onSelect }: RepositoryCardProps) { const diffTime = Math.abs(now.getTime() - date.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - if (diffDays <= 1) return 'Today'; - if (diffDays <= 2) return 'Yesterday'; - if (diffDays <= 7) return `${diffDays} days ago`; - if (diffDays <= 30) return `${Math.floor(diffDays / 7)} weeks ago`; + if (diffDays <= 1) { + return 'Today'; + } + + if (diffDays <= 2) { + return 'Yesterday'; + } + + if (diffDays <= 7) { + return `${diffDays} days ago`; + } + + if (diffDays <= 30) { + return `${Math.floor(diffDays / 7)} weeks ago`; + } return date.toLocaleDateString(undefined, { year: 'numeric', diff --git a/app/components/@settings/tabs/connections/components/RepositoryDialogContext.tsx b/app/components/@settings/tabs/connections/components/RepositoryDialogContext.tsx index d2c8c0d5..6ca74ff7 100644 --- a/app/components/@settings/tabs/connections/components/RepositoryDialogContext.tsx +++ b/app/components/@settings/tabs/connections/components/RepositoryDialogContext.tsx @@ -5,6 +5,8 @@ export interface RepositoryDialogContextType { setShowAuthDialog: React.Dispatch>; } +// Default context value with a no-op function +// eslint-disable-next-line @typescript-eslint/no-empty-function export const RepositoryDialogContext = createContext({ setShowAuthDialog: () => {}, }); diff --git a/app/components/@settings/tabs/connections/components/RepositoryList.tsx b/app/components/@settings/tabs/connections/components/RepositoryList.tsx index 883010a7..d6f0abda 100644 --- a/app/components/@settings/tabs/connections/components/RepositoryList.tsx +++ b/app/components/@settings/tabs/connections/components/RepositoryList.tsx @@ -1,5 +1,4 @@ import React, { useContext } from 'react'; -import { motion } from 'framer-motion'; import type { GitHubRepoInfo } from '~/types/GitHub'; import { EmptyState, StatusIndicator } from '~/components/ui'; import { RepositoryCard } from './RepositoryCard'; diff --git a/app/components/@settings/tabs/connections/components/RepositorySelectionDialog.tsx b/app/components/@settings/tabs/connections/components/RepositorySelectionDialog.tsx index 5686cfc8..d15c5b30 100644 --- a/app/components/@settings/tabs/connections/components/RepositorySelectionDialog.tsx +++ b/app/components/@settings/tabs/connections/components/RepositorySelectionDialog.tsx @@ -1,5 +1,5 @@ import type { GitHubRepoInfo, GitHubContent, RepositoryStats, GitHubUserResponse } from '~/types/GitHub'; -import { useState, useEffect, useContext } from 'react'; +import { useState, useEffect } from 'react'; import { toast } from 'react-toastify'; import * as Dialog from '@radix-ui/react-dialog'; import { classNames } from '~/utils/classNames'; @@ -8,17 +8,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import Cookies from 'js-cookie'; // Import UI components -import { - Input, - SearchInput, - Badge, - FilterChip, - EmptyState, - GradientCard, - TabsWithSlider, - StatusIndicator, - RepositoryStats as RepoStats, -} from '~/components/ui'; +import { Input, SearchInput, Badge, FilterChip } from '~/components/ui'; // Import the components we've extracted import { TabButton } from './TabButton'; diff --git a/app/components/@settings/tabs/connections/components/StatsDialog.tsx b/app/components/@settings/tabs/connections/components/StatsDialog.tsx index 3ed812b4..933ae225 100644 --- a/app/components/@settings/tabs/connections/components/StatsDialog.tsx +++ b/app/components/@settings/tabs/connections/components/StatsDialog.tsx @@ -3,7 +3,7 @@ import * as Dialog from '@radix-ui/react-dialog'; import { motion } from 'framer-motion'; import type { RepositoryStats } from '~/types/GitHub'; import { formatSize } from '~/utils/formatSize'; -import { RepositoryStats as RepoStats, Badge } from '~/components/ui'; +import { RepositoryStats as RepoStats } from '~/components/ui'; interface StatsDialogProps { isOpen: boolean; diff --git a/app/components/ui/Breadcrumbs.tsx b/app/components/ui/Breadcrumbs.tsx index a7f7d2dd..1ba2b935 100644 --- a/app/components/ui/Breadcrumbs.tsx +++ b/app/components/ui/Breadcrumbs.tsx @@ -24,24 +24,27 @@ export function Breadcrumbs({ maxItems = 0, renderItem, }: BreadcrumbsProps) { - const displayItems = maxItems > 0 && items.length > maxItems - ? [ - ...items.slice(0, 1), - { label: '...', onClick: undefined, href: undefined }, - ...items.slice(-Math.max(1, maxItems - 2)), - ] - : items; + const displayItems = + maxItems > 0 && items.length > maxItems + ? [ + ...items.slice(0, 1), + { label: '...', onClick: undefined, href: undefined }, + ...items.slice(-Math.max(1, maxItems - 2)), + ] + : items; const defaultRenderItem = (item: BreadcrumbItem, index: number, isLast: boolean) => { const content = (
{item.icon && } - + {item.label}
@@ -49,12 +52,7 @@ export function Breadcrumbs({ if (item.href && !isLast) { return ( - + {content} ); @@ -82,12 +80,17 @@ export function Breadcrumbs({
    {displayItems.map((item, index) => { const isLast = index === displayItems.length - 1; - + return (
  1. {renderItem ? renderItem(item, index, isLast) : defaultRenderItem(item, index, isLast)} {!isLast && ( - + )}
  2. ); diff --git a/app/components/ui/CodeBlock.tsx b/app/components/ui/CodeBlock.tsx index 3fb9e452..71dfbc29 100644 --- a/app/components/ui/CodeBlock.tsx +++ b/app/components/ui/CodeBlock.tsx @@ -37,11 +37,13 @@ export function CodeBlock({ const lines = code.split('\n'); return ( -
    +
    {/* Header */}
    @@ -66,36 +68,26 @@ export function CodeBlock({ whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > - {copied ? ( - - ) : ( - - )} + {copied ? : }
    - + {/* Code content */} -
    +
    {lines.map((line, index) => ( - {showLineNumbers && ( )}
    - - {index + 1} - + {index + 1} diff --git a/app/components/ui/EmptyState.tsx b/app/components/ui/EmptyState.tsx index ebba42e6..22375f06 100644 --- a/app/components/ui/EmptyState.tsx +++ b/app/components/ui/EmptyState.tsx @@ -32,20 +32,28 @@ const VARIANT_STYLES = { interface EmptyStateProps { /** Icon class name */ icon?: string; + /** Title text */ title: string; + /** Optional description text */ description?: string; + /** Primary action button label */ actionLabel?: string; + /** Primary action button callback */ onAction?: () => void; + /** Secondary action button label */ secondaryActionLabel?: string; + /** Secondary action button callback */ onSecondaryAction?: () => void; + /** Additional class name */ className?: string; + /** Component size variant */ variant?: 'default' | 'compact'; } diff --git a/app/components/ui/FileIcon.tsx b/app/components/ui/FileIcon.tsx index d7999c7d..05f69796 100644 --- a/app/components/ui/FileIcon.tsx +++ b/app/components/ui/FileIcon.tsx @@ -14,110 +14,312 @@ export function FileIcon({ filename, size = 'md', className }: FileIconProps) { const getIconForExtension = (extension: string): string => { // Code files - if (['js', 'jsx', 'ts', 'tsx'].includes(extension)) return 'i-ph:file-js'; - if (['html', 'htm', 'xhtml'].includes(extension)) return 'i-ph:file-html'; - if (['css', 'scss', 'sass', 'less'].includes(extension)) return 'i-ph:file-css'; - if (['json', 'jsonc'].includes(extension)) return 'i-ph:brackets-curly'; - if (['md', 'markdown'].includes(extension)) return 'i-ph:file-text'; - if (['py', 'pyc', 'pyd', 'pyo'].includes(extension)) return 'i-ph:file-py'; - if (['java', 'class', 'jar'].includes(extension)) return 'i-ph:file-java'; - if (['php'].includes(extension)) return 'i-ph:file-php'; - if (['rb', 'ruby'].includes(extension)) return 'i-ph:file-rs'; - if (['c', 'cpp', 'h', 'hpp', 'cc'].includes(extension)) return 'i-ph:file-cpp'; - if (['go'].includes(extension)) return 'i-ph:file-rs'; - if (['rs', 'rust'].includes(extension)) return 'i-ph:file-rs'; - if (['swift'].includes(extension)) return 'i-ph:file-swift'; - if (['kt', 'kotlin'].includes(extension)) return 'i-ph:file-kotlin'; - if (['dart'].includes(extension)) return 'i-ph:file-dart'; - + if (['js', 'jsx', 'ts', 'tsx'].includes(extension)) { + return 'i-ph:file-js'; + } + + if (['html', 'htm', 'xhtml'].includes(extension)) { + return 'i-ph:file-html'; + } + + if (['css', 'scss', 'sass', 'less'].includes(extension)) { + return 'i-ph:file-css'; + } + + if (['json', 'jsonc'].includes(extension)) { + return 'i-ph:brackets-curly'; + } + + if (['md', 'markdown'].includes(extension)) { + return 'i-ph:file-text'; + } + + if (['py', 'pyc', 'pyd', 'pyo'].includes(extension)) { + return 'i-ph:file-py'; + } + + if (['java', 'class', 'jar'].includes(extension)) { + return 'i-ph:file-java'; + } + + if (['php'].includes(extension)) { + return 'i-ph:file-php'; + } + + if (['rb', 'ruby'].includes(extension)) { + return 'i-ph:file-rs'; + } + + if (['c', 'cpp', 'h', 'hpp', 'cc'].includes(extension)) { + return 'i-ph:file-cpp'; + } + + if (['go'].includes(extension)) { + return 'i-ph:file-rs'; + } + + if (['rs', 'rust'].includes(extension)) { + return 'i-ph:file-rs'; + } + + if (['swift'].includes(extension)) { + return 'i-ph:file-swift'; + } + + if (['kt', 'kotlin'].includes(extension)) { + return 'i-ph:file-kotlin'; + } + + if (['dart'].includes(extension)) { + return 'i-ph:file-dart'; + } + // Config files - if (['yml', 'yaml'].includes(extension)) return 'i-ph:file-cloud'; - if (['xml', 'svg'].includes(extension)) return 'i-ph:file-xml'; - if (['toml'].includes(extension)) return 'i-ph:file-text'; - if (['ini', 'conf', 'config'].includes(extension)) return 'i-ph:file-text'; - if (['env', 'env.local', 'env.development', 'env.production'].includes(extension)) return 'i-ph:file-lock'; - + if (['yml', 'yaml'].includes(extension)) { + return 'i-ph:file-cloud'; + } + + if (['xml', 'svg'].includes(extension)) { + return 'i-ph:file-xml'; + } + + if (['toml'].includes(extension)) { + return 'i-ph:file-text'; + } + + if (['ini', 'conf', 'config'].includes(extension)) { + return 'i-ph:file-text'; + } + + if (['env', 'env.local', 'env.development', 'env.production'].includes(extension)) { + return 'i-ph:file-lock'; + } + // Document files - if (['pdf'].includes(extension)) return 'i-ph:file-pdf'; - if (['doc', 'docx'].includes(extension)) return 'i-ph:file-doc'; - if (['xls', 'xlsx'].includes(extension)) return 'i-ph:file-xls'; - if (['ppt', 'pptx'].includes(extension)) return 'i-ph:file-ppt'; - if (['txt'].includes(extension)) return 'i-ph:file-text'; - + if (['pdf'].includes(extension)) { + return 'i-ph:file-pdf'; + } + + if (['doc', 'docx'].includes(extension)) { + return 'i-ph:file-doc'; + } + + if (['xls', 'xlsx'].includes(extension)) { + return 'i-ph:file-xls'; + } + + if (['ppt', 'pptx'].includes(extension)) { + return 'i-ph:file-ppt'; + } + + if (['txt'].includes(extension)) { + return 'i-ph:file-text'; + } + // Image files - if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'ico', 'tiff'].includes(extension)) return 'i-ph:file-image'; - + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'ico', 'tiff'].includes(extension)) { + return 'i-ph:file-image'; + } + // Audio/Video files - if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(extension)) return 'i-ph:file-audio'; - if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) return 'i-ph:file-video'; - + if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(extension)) { + return 'i-ph:file-audio'; + } + + if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) { + return 'i-ph:file-video'; + } + // Archive files - if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(extension)) return 'i-ph:file-zip'; - + if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(extension)) { + return 'i-ph:file-zip'; + } + // Special files - if (filename === 'package.json') return 'i-ph:package'; - if (filename === 'tsconfig.json') return 'i-ph:file-ts'; - if (filename === 'README.md') return 'i-ph:book-open'; - if (filename === 'LICENSE') return 'i-ph:scales'; - if (filename === '.gitignore') return 'i-ph:git-branch'; - if (filename.startsWith('Dockerfile')) return 'i-ph:docker-logo'; - + if (filename === 'package.json') { + return 'i-ph:package'; + } + + if (filename === 'tsconfig.json') { + return 'i-ph:file-ts'; + } + + if (filename === 'README.md') { + return 'i-ph:book-open'; + } + + if (filename === 'LICENSE') { + return 'i-ph:scales'; + } + + if (filename === '.gitignore') { + return 'i-ph:git-branch'; + } + + if (filename.startsWith('Dockerfile')) { + return 'i-ph:docker-logo'; + } + // Default return 'i-ph:file'; }; const getIconColorForExtension = (extension: string): string => { // Code files - if (['js', 'jsx'].includes(extension)) return 'text-yellow-500'; - if (['ts', 'tsx'].includes(extension)) return 'text-blue-500'; - if (['html', 'htm', 'xhtml'].includes(extension)) return 'text-orange-500'; - if (['css', 'scss', 'sass', 'less'].includes(extension)) return 'text-blue-400'; - if (['json', 'jsonc'].includes(extension)) return 'text-yellow-400'; - if (['md', 'markdown'].includes(extension)) return 'text-gray-500'; - if (['py', 'pyc', 'pyd', 'pyo'].includes(extension)) return 'text-green-500'; - if (['java', 'class', 'jar'].includes(extension)) return 'text-red-500'; - if (['php'].includes(extension)) return 'text-purple-500'; - if (['rb', 'ruby'].includes(extension)) return 'text-red-600'; - if (['c', 'cpp', 'h', 'hpp', 'cc'].includes(extension)) return 'text-blue-600'; - if (['go'].includes(extension)) return 'text-cyan-500'; - if (['rs', 'rust'].includes(extension)) return 'text-orange-600'; - if (['swift'].includes(extension)) return 'text-orange-500'; - if (['kt', 'kotlin'].includes(extension)) return 'text-purple-400'; - if (['dart'].includes(extension)) return 'text-cyan-400'; - + if (['js', 'jsx'].includes(extension)) { + return 'text-yellow-500'; + } + + if (['ts', 'tsx'].includes(extension)) { + return 'text-blue-500'; + } + + if (['html', 'htm', 'xhtml'].includes(extension)) { + return 'text-orange-500'; + } + + if (['css', 'scss', 'sass', 'less'].includes(extension)) { + return 'text-blue-400'; + } + + if (['json', 'jsonc'].includes(extension)) { + return 'text-yellow-400'; + } + + if (['md', 'markdown'].includes(extension)) { + return 'text-gray-500'; + } + + if (['py', 'pyc', 'pyd', 'pyo'].includes(extension)) { + return 'text-green-500'; + } + + if (['java', 'class', 'jar'].includes(extension)) { + return 'text-red-500'; + } + + if (['php'].includes(extension)) { + return 'text-purple-500'; + } + + if (['rb', 'ruby'].includes(extension)) { + return 'text-red-600'; + } + + if (['c', 'cpp', 'h', 'hpp', 'cc'].includes(extension)) { + return 'text-blue-600'; + } + + if (['go'].includes(extension)) { + return 'text-cyan-500'; + } + + if (['rs', 'rust'].includes(extension)) { + return 'text-orange-600'; + } + + if (['swift'].includes(extension)) { + return 'text-orange-500'; + } + + if (['kt', 'kotlin'].includes(extension)) { + return 'text-purple-400'; + } + + if (['dart'].includes(extension)) { + return 'text-cyan-400'; + } + // Config files - if (['yml', 'yaml'].includes(extension)) return 'text-purple-300'; - if (['xml'].includes(extension)) return 'text-orange-300'; - if (['svg'].includes(extension)) return 'text-green-400'; - if (['toml'].includes(extension)) return 'text-gray-500'; - if (['ini', 'conf', 'config'].includes(extension)) return 'text-gray-500'; - if (['env', 'env.local', 'env.development', 'env.production'].includes(extension)) return 'text-green-500'; - + if (['yml', 'yaml'].includes(extension)) { + return 'text-purple-300'; + } + + if (['xml'].includes(extension)) { + return 'text-orange-300'; + } + + if (['svg'].includes(extension)) { + return 'text-green-400'; + } + + if (['toml'].includes(extension)) { + return 'text-gray-500'; + } + + if (['ini', 'conf', 'config'].includes(extension)) { + return 'text-gray-500'; + } + + if (['env', 'env.local', 'env.development', 'env.production'].includes(extension)) { + return 'text-green-500'; + } + // Document files - if (['pdf'].includes(extension)) return 'text-red-500'; - if (['doc', 'docx'].includes(extension)) return 'text-blue-600'; - if (['xls', 'xlsx'].includes(extension)) return 'text-green-600'; - if (['ppt', 'pptx'].includes(extension)) return 'text-red-600'; - if (['txt'].includes(extension)) return 'text-gray-500'; - + if (['pdf'].includes(extension)) { + return 'text-red-500'; + } + + if (['doc', 'docx'].includes(extension)) { + return 'text-blue-600'; + } + + if (['xls', 'xlsx'].includes(extension)) { + return 'text-green-600'; + } + + if (['ppt', 'pptx'].includes(extension)) { + return 'text-red-600'; + } + + if (['txt'].includes(extension)) { + return 'text-gray-500'; + } + // Image files - if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'ico', 'tiff'].includes(extension)) return 'text-pink-500'; - + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'ico', 'tiff'].includes(extension)) { + return 'text-pink-500'; + } + // Audio/Video files - if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(extension)) return 'text-green-500'; - if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) return 'text-blue-500'; - + if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(extension)) { + return 'text-green-500'; + } + + if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) { + return 'text-blue-500'; + } + // Archive files - if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(extension)) return 'text-yellow-600'; - + if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(extension)) { + return 'text-yellow-600'; + } + // Special files - if (filename === 'package.json') return 'text-red-400'; - if (filename === 'tsconfig.json') return 'text-blue-500'; - if (filename === 'README.md') return 'text-blue-400'; - if (filename === 'LICENSE') return 'text-gray-500'; - if (filename === '.gitignore') return 'text-orange-500'; - if (filename.startsWith('Dockerfile')) return 'text-blue-500'; - + if (filename === 'package.json') { + return 'text-red-400'; + } + + if (filename === 'tsconfig.json') { + return 'text-blue-500'; + } + + if (filename === 'README.md') { + return 'text-blue-400'; + } + + if (filename === 'LICENSE') { + return 'text-gray-500'; + } + + if (filename === '.gitignore') { + return 'text-orange-500'; + } + + if (filename.startsWith('Dockerfile')) { + return 'text-blue-500'; + } + // Default return 'text-gray-400'; }; @@ -140,7 +342,5 @@ export function FileIcon({ filename, size = 'md', className }: FileIconProps) { const color = getIconColorForExtension(extension); const sizeClass = getSizeClass(size); - return ( - - ); + return ; } diff --git a/app/components/ui/FilterChip.tsx b/app/components/ui/FilterChip.tsx index d367d3a2..705cec20 100644 --- a/app/components/ui/FilterChip.tsx +++ b/app/components/ui/FilterChip.tsx @@ -5,14 +5,19 @@ import { classNames } from '~/utils/classNames'; interface FilterChipProps { /** The label text to display */ label: string; + /** Optional value to display after the label */ value?: string | number; + /** Function to call when the remove button is clicked */ onRemove?: () => void; + /** Whether the chip is active/selected */ active?: boolean; + /** Optional icon to display before the label */ icon?: string; + /** Additional class name */ className?: string; } diff --git a/app/components/ui/GradientCard.tsx b/app/components/ui/GradientCard.tsx index 21af94c0..154bf97f 100644 --- a/app/components/ui/GradientCard.tsx +++ b/app/components/ui/GradientCard.tsx @@ -14,17 +14,27 @@ const GRADIENT_COLORS = [ 'from-pink-500/10 to-purple-500/5', ]; -interface GradientCardProps extends React.HTMLAttributes { +interface GradientCardProps { /** Custom gradient class (overrides seed-based gradient) */ gradient?: string; + /** Seed string to determine gradient color */ seed?: string; + /** Whether to apply hover animation effect */ hoverEffect?: boolean; + /** Whether to apply border effect */ borderEffect?: boolean; + /** Card content */ children: React.ReactNode; + + /** Additional class name */ + className?: string; + + /** Additional props */ + [key: string]: any; } /** @@ -54,7 +64,7 @@ export function GradientCard({ }, whileTap: { scale: 0.98 }, } - : {}; + : undefined; return ( )} - +
    {totalFiles !== undefined && (
    @@ -33,7 +33,7 @@ export function RepositoryStats({ stats, className, compact = false }: Repositor Total Files: {totalFiles.toLocaleString()}
    )} - + {totalSize !== undefined && (
    @@ -53,19 +53,12 @@ export function RepositoryStats({ stats, className, compact = false }: Repositor .sort(([, a], [, b]) => b - a) .slice(0, compact ? 3 : 5) .map(([lang, size]) => ( - + {lang} ({formatSize(size)}) ))} {Object.keys(languages).length > (compact ? 3 : 5) && ( - + +{Object.keys(languages).length - (compact ? 3 : 5)} more )} @@ -77,20 +70,12 @@ export function RepositoryStats({ stats, className, compact = false }: Repositor
    {hasPackageJson && ( - + package.json )} {hasDependencies && ( - + Dependencies )} diff --git a/app/components/ui/SearchInput.tsx b/app/components/ui/SearchInput.tsx index cc4fe7d7..c3209218 100644 --- a/app/components/ui/SearchInput.tsx +++ b/app/components/ui/SearchInput.tsx @@ -6,12 +6,16 @@ import { motion, AnimatePresence } from 'framer-motion'; interface SearchInputProps extends React.InputHTMLAttributes { /** Function to call when the clear button is clicked */ onClear?: () => void; + /** Whether to show the clear button when there is input */ showClearButton?: boolean; + /** Additional class name for the search icon */ iconClassName?: string; + /** Additional class name for the container */ containerClassName?: string; + /** Whether the search is loading */ loading?: boolean; } @@ -47,7 +51,7 @@ export const SearchInput = forwardRef( {/* Input field */} diff --git a/app/components/ui/SearchResultItem.tsx b/app/components/ui/SearchResultItem.tsx index 1f32d404..b2dddccc 100644 --- a/app/components/ui/SearchResultItem.tsx +++ b/app/components/ui/SearchResultItem.tsx @@ -41,7 +41,7 @@ export function SearchResultItem({ className={classNames( 'p-5 rounded-xl border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark hover:border-purple-500/40 transition-all duration-300 shadow-sm hover:shadow-md bg-bolt-elements-background-depth-1/50 dark:bg-bolt-elements-background-depth-3/50', onClick ? 'cursor-pointer' : '', - className + className, )} whileHover={{ scale: 1.01, @@ -57,7 +57,12 @@ export function SearchResultItem({
    {icon && ( -
    +
    )} @@ -72,7 +77,7 @@ export function SearchResultItem({ )}
    - + {actionLabel && onAction && ( { diff --git a/app/components/ui/StatusIndicator.tsx b/app/components/ui/StatusIndicator.tsx index 081d46e2..6466dfbe 100644 --- a/app/components/ui/StatusIndicator.tsx +++ b/app/components/ui/StatusIndicator.tsx @@ -37,12 +37,16 @@ const TEXT_SIZE_CLASSES: Record = { interface StatusIndicatorProps { /** The status to display */ status: StatusType; + /** Size of the indicator */ size?: SizeType; + /** Whether to show a pulsing animation */ pulse?: boolean; + /** Optional label text */ label?: string; + /** Additional class name */ className?: string; } diff --git a/app/components/ui/TabsWithSlider.tsx b/app/components/ui/TabsWithSlider.tsx index dee8eb6b..8dee0f4b 100644 --- a/app/components/ui/TabsWithSlider.tsx +++ b/app/components/ui/TabsWithSlider.tsx @@ -5,8 +5,10 @@ import { classNames } from '~/utils/classNames'; interface Tab { /** Unique identifier for the tab */ id: string; + /** Content to display in the tab */ label: React.ReactNode; + /** Optional icon to display before the label */ icon?: string; } @@ -14,16 +16,22 @@ interface Tab { interface TabsWithSliderProps { /** Array of tab objects */ tabs: Tab[]; + /** ID of the currently active tab */ activeTab: string; + /** Function called when a tab is clicked */ onChange: (tabId: string) => void; + /** Additional class name for the container */ className?: string; + /** Additional class name for inactive tabs */ tabClassName?: string; + /** Additional class name for the active tab */ activeTabClassName?: string; + /** Additional class name for the slider */ sliderClassName?: string; } @@ -51,8 +59,10 @@ export function TabsWithSlider({ // Update slider position when active tab changes useEffect(() => { const activeIndex = tabs.findIndex((tab) => tab.id === activeTab); + if (activeIndex !== -1 && tabsRef.current[activeIndex]) { const activeTabElement = tabsRef.current[activeIndex]; + if (activeTabElement) { setSliderDimensions({ width: activeTabElement.offsetWidth,