Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:59 +08:00
commit 38e80921c8
89 changed files with 20480 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
/**
* Button - Simple button component with variants
*
* @example
* <Button variant="primary" onClick={() => console.log('clicked')}>
* Click me
* </Button>
*/
import React from 'react';
import styles from './Button.module.css';
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
className?: string;
}
export const Button: React.FC<ButtonProps> = ({
children,
onClick,
variant = 'primary',
disabled = false,
type = 'button',
className,
}) => {
return (
<button
type={type}
className={`${styles.button} ${styles[variant]} ${className || ''}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
};

View File

@@ -0,0 +1,52 @@
/**
* SearchBar - Search input with debounced onChange
*
* @example
* <SearchBar
* onSearch={(query) => console.log('Search:', query)}
* placeholder="Search users..."
* />
*/
import React, { useState, useEffect, useCallback } from 'react';
import styles from './SearchBar.module.css';
interface SearchBarProps {
onSearch: (query: string) => void;
placeholder?: string;
debounceMs?: number;
className?: string;
}
export const SearchBar: React.FC<SearchBarProps> = ({
onSearch,
placeholder = 'Search...',
debounceMs = 300,
className,
}) => {
const [query, setQuery] = useState('');
const handleSearch = useCallback(() => {
if (query.trim()) {
onSearch(query);
}
}, [query, onSearch]);
useEffect(() => {
const timer = setTimeout(handleSearch, debounceMs);
return () => clearTimeout(timer);
}, [query, debounceMs, handleSearch]);
return (
<div className={`${styles.container} ${className || ''}`}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
className={styles.input}
aria-label="Search"
/>
</div>
);
};