Exemples Solid.js

Exemples de composants Solid.js incluant les hooks, la gestion d'état, le routing et les patterns modernes Solid

Key Facts

Category
Web Frameworks
Items
5
Format Families
sample

Sample Overview

Exemples de composants Solid.js incluant les hooks, la gestion d'état, le routing et les patterns modernes Solid This sample set belongs to Web Frameworks and can be used to test related workflows inside Elysia Tools.

💻 Bonjour Monde Solid.js tsx

🟢 simple

Exemples de base de composants Solid.js et applications Bonjour Monde avec JSX

// Solid.js Hello World Examples

// 1. Basic functional component
import { createSignal } from 'solid-js';

function HelloWorld() {
    return <h1>Hello, World!</h1>;
}

// 2. Component with props
interface GreetingProps {
    name: string;
}

function Greeting(props: GreetingProps) {
    return <h1>Hello, {props.name}!</h1>;
}

// 3. Component with signal (reactive state)
function Counter() {
    const [count, setCount] = createSignal(0);

    return (
        <div>
            <h1>Count: {count()}</h1>
            <button onClick={() => setCount(count() + 1)}>
                Increment
            </button>
            <button onClick={() => setCount(count() - 1)}>
                Decrement
            </button>
        </div>
    );
}

// 4. Component with multiple signals
function UserProfile() {
    const [name, setName] = createSignal('Guest');
    const [age, setAge] = createSignal(25);
    const [isLoggedIn, setIsLoggedIn] = createSignal(false);

    return (
        <div>
            <h2>User Profile</h2>
            {isLoggedIn() ? (
                <div>
                    <p>Name: {name()}</p>
                    <p>Age: {age()}</p>
                    <input
                        type="text"
                        value={name()}
                        onInput={(e) => setName(e.currentTarget.value)}
                        placeholder="Enter name"
                    />
                    <input
                        type="number"
                        value={age()}
                        onInput={(e) => setAge(Number(e.currentTarget.value))}
                        placeholder="Enter age"
                    />
                    <button onClick={() => setIsLoggedIn(false)}>Logout</button>
                </div>
            ) : (
                <button onClick={() => setIsLoggedIn(true)}>Login</button>
            )}
        </div>
    );
}

// 5. Component with derived values
function ShoppingCart() {
    const [items, setItems] = createSignal([
        { name: 'Apple', price: 1.5, quantity: 2 },
        { name: 'Banana', price: 0.8, quantity: 5 }
    ]);

    const total = () => items().reduce((sum, item) => sum + (item.price * item.quantity), 0);
    const itemCount = () => items().reduce((sum, item) => sum + item.quantity, 0);

    return (
        <div>
            <h2>Shopping Cart</h2>
            <p>Items: {itemCount()}</p>
            <p>Total: ${total().toFixed(2)}</p>
            <ul>
                {items().map((item, index) => (
                    <li>
                        {item.name} - ${item.price} x {item.quantity}
                        <button
                            onClick={() => {
                                const newItems = [...items()];
                                newItems.splice(index, 1);
                                setItems(newItems);
                            }}
                        >
                            Remove
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

// Usage examples
function App() {
    return (
        <div>
            <HelloWorld />
            <Greeting name="Solid.js" />
            <Counter />
            <UserProfile />
            <ShoppingCart />
        </div>
    );
}

export { HelloWorld, Greeting, Counter, UserProfile, ShoppingCart, App };

💻 Signaux et Effets Solid.js tsx

🟡 intermediate

Travailler avec des signaux réactifs, des effets et des valeurs dérivées dans Solid.js

// Solid.js Signals and Effects

import {
    createSignal,
    createEffect,
    createMemo,
    createComputed,
    onMount,
    onCleanup,
    batch
} from 'solid-js';

// 1. Basic signals and effects
function SignalExample() {
    const [count, setCount] = createSignal(0);
    const [doubled, setDoubled] = createSignal(0);

    // Effect that runs when count changes
    createEffect(() => {
        console.log('Count changed to:', count());
        setDoubled(count() * 2);
    });

    // Effect with cleanup
    createEffect((prevCount) => {
        console.log('Previous count:', prevCount);
        return count();
    });

    return (
        <div>
            <p>Count: {count()}</p>
            <p>Doubled: {doubled()}</p>
            <button onClick={() => setCount(count() + 1)}>Increment</button>
        </div>
    );
}

// 2. Memo (derived values)
function MemoExample() {
    const [firstName, setFirstName] = createSignal('John');
    const [lastName, setLastName] = createSignal('Doe');

    // Memoized computed value
    const fullName = createMemo(() => `${firstName()} ${lastName()}`);
    const greeting = createMemo(() => `Hello, ${fullName()}!`);

    return (
        <div>
            <input
                type="text"
                value={firstName()}
                onInput={(e) => setFirstName(e.currentTarget.value)}
                placeholder="First name"
            />
            <input
                type="text"
                value={lastName()}
                onInput={(e) => setLastName(e.currentTarget.value)}
                placeholder="Last name"
            />
            <p>{greeting()}</p>
        </div>
    );
}

// 3. Lifecycle effects
function LifecycleExample() {
    const [data, setData] = createSignal(null);
    const [loading, setLoading] = createSignal(true);

    onMount(() => {
        console.log('Component mounted');

        // Simulate API call
        setTimeout(() => {
            setData({ message: 'Data loaded successfully!' });
            setLoading(false);
        }, 2000);
    });

    onCleanup(() => {
        console.log('Component will unmount');
    });

    // Effect for data changes
    createEffect(() => {
        if (data()) {
            console.log('Data updated:', data());
        }
    });

    return (
        <div>
            <h3>Lifecycle Example</h3>
            {loading() ? (
                <p>Loading...</p>
            ) : (
                <p>{data()?.message}</p>
            )}
        </div>
    );
}

// 4. Batch updates for performance
function BatchExample() {
    const [count, setCount] = createSignal(0);
    const [name, setName] = createSignal('');
    const [isValid, setIsValid] = createSignal(false);

    // Effect that runs only once when batched
    createEffect(() => {
        console.log('Count:', count(), 'Name:', name(), 'Valid:', isValid());
    });

    const updateMultiple = () => {
        batch(() => {
            setCount(count() + 1);
            setName('Updated');
            setIsValid(true);
        });
    };

    return (
        <div>
            <p>Count: {count()}</p>
            <p>Name: {name()}</p>
            <p>Valid: {isValid() ? 'Yes' : 'No'}</p>
            <button onClick={updateMultiple}>Update Multiple (Batched)</button>
        </div>
    );
}

// 5. Computed signals
function ComputedExample() {
    const [price, setPrice] = createSignal(100);
    const [quantity, setQuantity] = createSignal(2);
    const [taxRate] = createSignal(0.08);

    // Computed signal
    const subtotal = createComputed(() => price() * quantity());
    const tax = createComputed(() => subtotal() * taxRate());
    const total = createComputed(() => subtotal() + tax());

    return (
        <div>
            <h3>Price Calculator</h3>
            <label>
                Price: $
                <input
                    type="number"
                    value={price()}
                    onInput={(e) => setPrice(Number(e.currentTarget.value))}
                />
            </label>
            <label>
                Quantity:
                <input
                    type="number"
                    value={quantity()}
                    onInput={(e) => setQuantity(Number(e.currentTarget.value))}
                />
            </label>
            <p>Subtotal: ${subtotal().toFixed(2)}</p>
            <p>Tax ({taxRate() * 100}%): ${tax().toFixed(2)}</p>
            <p><strong>Total: ${total().toFixed(2)}</strong></p>
        </div>
    );
}

export { SignalExample, MemoExample, LifecycleExample, BatchExample, ComputedExample };

💻 Stores Solid.js tsx

🟡 intermediate

Gestion d'état global avec les stores Solid.js

// Solid.js Stores for Global State Management

import { createStore, SetStoreFunction } from 'solid-js/store';

// 1. Simple store
interface User {
    id: number;
    name: string;
    email: string;
    isLoggedIn: boolean;
}

const [user, setUser] = createStore<User>({
    id: 0,
    name: '',
    email: '',
    isLoggedIn: false
});

// Store actions
const userActions = {
    login: (userData: Omit<User, 'isLoggedIn'>) => {
        setUser({
            ...userData,
            isLoggedIn: true
        });
    },

    logout: () => {
        setUser({
            id: 0,
            name: '',
            email: '',
            isLoggedIn: false
        });
    },

    updateProfile: (updates: Partial<Pick<User, 'name' | 'email'>>) => {
        setUser(updates);
    }
};

// 2. Todo store
interface Todo {
    id: number;
    text: string;
    completed: boolean;
    createdAt: Date;
}

interface TodoStore {
    todos: Todo[];
    filter: 'all' | 'active' | 'completed';
}

const [todoStore, setTodoStore] = createStore<TodoStore>({
    todos: [],
    filter: 'all'
});

const todoActions = {
    addTodo: (text: string) => {
        const newTodo: Todo = {
            id: Date.now(),
            text,
            completed: false,
            createdAt: new Date()
        };

        setTodoStore('todos', todos => [...todos, newTodo]);
    },

    toggleTodo: (id: number) => {
        setTodoStore('todos',
            todo => todo.id === id,
            'completed',
            completed => !completed
        );
    },

    deleteTodo: (id: number) => {
        setTodoStore('todos', todos => todos.filter(todo => todo.id !== id));
    },

    clearCompleted: () => {
        setTodoStore('todos', todos => todos.filter(todo => !todo.completed));
    },

    setFilter: (filter: 'all' | 'active' | 'completed') => {
        setTodoStore('filter', filter);
    },

    // Getters
    get activeTodos() {
        return todoStore.todos.filter(todo => !todo.completed);
    },

    get completedTodos() {
        return todoStore.todos.filter(todo => todo.completed);
    },

    get filteredTodos() {
        switch (todoStore.filter) {
            case 'active':
                return todoStore.todos.filter(todo => !todo.completed);
            case 'completed':
                return todoStore.todos.filter(todo => todo.completed);
            default:
                return todoStore.todos;
        }
    },

    get stats() {
        const total = todoStore.todos.length;
        const completed = todoStore.todos.filter(todo => todo.completed).length;
        const active = total - completed;

        return { total, completed, active };
    }
};

// 3. Shopping cart store
interface CartItem {
    id: number;
    name: string;
    price: number;
    quantity: number;
}

interface CartStore {
    items: CartItem[];
    isOpen: boolean;
    couponCode?: string;
    discount: number;
}

const [cartStore, setCartStore] = createStore<CartStore>({
    items: [],
    isOpen: false,
    discount: 0
});

const cartActions = {
    addItem: (item: Omit<CartItem, 'quantity'>) => {
        setCartStore('items', items => {
            const existingItem = items.find(i => i.id === item.id);
            if (existingItem) {
                return items.map(i =>
                    i.id === item.id
                        ? { ...i, quantity: i.quantity + 1 }
                        : i
                );
            }
            return [...items, { ...item, quantity: 1 }];
        });
    },

    removeItem: (id: number) => {
        setCartStore('items', items => items.filter(item => item.id !== id));
    },

    updateQuantity: (id: number, quantity: number) => {
        if (quantity <= 0) {
            cartActions.removeItem(id);
        } else {
            setCartStore('items',
                item => item.id === id,
                'quantity',
                quantity
            );
        }
    },

    clearCart: () => {
        setCartStore('items', []);
    },

    toggleCart: () => {
        setCartStore('isOpen', isOpen => !isOpen);
    },

    applyCoupon: (code: string, discount: number) => {
        setCartStore('couponCode', code);
        setCartStore('discount', discount);
    }
};

// Cart computed values
const cartHelpers = {
    get totalItems() {
        return cartStore.items.reduce((sum, item) => sum + item.quantity, 0);
    },

    get subtotal() {
        return cartStore.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    },

    get total() {
        const subtotal = this.subtotal;
        return subtotal - (subtotal * cartStore.discount);
    }
};

// 4. Store with nested state
interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
    inStock: boolean;
}

interface ProductsStore {
    products: Product[];
    categories: string[];
    selectedCategory: string;
    searchTerm: string;
    sortBy: 'name' | 'price';
    sortOrder: 'asc' | 'desc';
}

const [productsStore, setProductsStore] = createStore<ProductsStore>({
    products: [],
    categories: [],
    selectedCategory: '',
    searchTerm: '',
    sortBy: 'name',
    sortOrder: 'asc'
});

const productActions = {
    setProducts: (products: Product[]) => {
        setProductsStore('products', products);

        // Extract categories
        const categories = [...new Set(products.map(p => p.category))];
        setProductsStore('categories', categories);
    },

    setCategory: (category: string) => {
        setProductsStore('selectedCategory', category);
    },

    setSearchTerm: (term: string) => {
        setProductsStore('searchTerm', term);
    },

    setSorting: (sortBy: 'name' | 'price', sortOrder: 'asc' | 'desc') => {
        setProductsStore('sortBy', sortBy);
        setProductsStore('sortOrder', sortOrder);
    }
};

export {
    user, userActions,
    todoStore, todoActions,
    cartStore, cartActions, cartHelpers,
    productsStore, productActions
};

💻 Routing Solid.js tsx

🟡 intermediate

Routing côté client avec Solid Router

// Solid.js Routing Examples

import {
    createRouter,
    createRoute,
    createMemoryHistory,
    Router,
    Route,
    Link,
    useParams,
    useSearchParams,
    useNavigate,
    A
} from '@solidjs/router';

// 1. Basic router setup
const router = createRouter({
    history: createMemoryHistory(),
    routes: [
        createRoute({
            path: '/',
            component: () => import('./pages/Home')
        }),
        createRoute({
            path: '/about',
            component: () => import('./pages/About')
        }),
        createRoute({
            path: '/users/:id',
            component: () => import('./pages/UserDetail')
        }),
        createRoute({
            path: '/search',
            component: () => import('./pages/Search')
        })
    ]
});

// 2. Navigation components
function Navigation() {
    const navigate = useNavigate();
    const [params] = useSearchParams();

    const handleGoHome = () => navigate('/');
    const handleGoBack = () => navigate(-1);

    return (
        <nav>
            <ul>
                <li><A href="/">Home</A></li>
                <li><A href="/about">About</A></li>
                <li><A href="/users/1">User 1</A></li>
                <li><A href="/search?q=test">Search</A></li>
            </ul>

            <div>
                <button onClick={handleGoHome}>Go Home</button>
                <button onClick={handleGoBack}>Go Back</button>
                <p>Current query: {params.q}</p>
            </div>
        </nav>
    );
}

// 3. Dynamic routes
function UserDetail() {
    const params = useParams();
    const [searchParams] = useSearchParams();

    return (
        <div>
            <h2>User Detail</h2>
            <p>User ID: {params.id}</p>
            <p>Tab: {searchParams.tab || 'profile'}</p>

            <div class="user-tabs">
                <A href={`/users/${params.id}?tab=profile`}>Profile</A>
                <A href={`/users/${params.id}?tab=settings`}>Settings</A>
                <A href={`/users/${params.id}?tab=posts`}>Posts</A>
            </div>

            <div class="tab-content">
                <Show when={searchParams.tab === 'profile'}>
                    <UserProfile id={params.id} />
                </Show>
                <Show when={searchParams.tab === 'settings'}>
                    <UserSettings id={params.id} />
                </Show>
                <Show when={searchParams.tab === 'posts'}>
                    <UserPosts id={params.id} />
                </Show>
            </div>
        </div>
    );
}

// 4. Search functionality
function SearchPage() {
    const [params, setParams] = useSearchParams();
    const navigate = useNavigate();

    const handleSearch = (query: string) => {
        setParams({ q: query });
    };

    const handleAdvancedSearch = (query: string, filters: any) => {
        const searchParams = new URLSearchParams();
        searchParams.set('q', query);
        Object.entries(filters).forEach(([key, value]) => {
            if (value) searchParams.set(key, String(value));
        });
        navigate(`/search?${searchParams.toString()}`);
    };

    return (
        <div>
            <h2>Search</h2>
            <div class="search-form">
                <input
                    type="text"
                    placeholder="Search..."
                    value={params.q || ''}
                    onInput={(e) => handleSearch(e.currentTarget.value)}
                />
            </div>

            <Show when={params.q}>
                <div class="search-results">
                    <p>Searching for: {params.q}</p>
                    <SearchResults query={params.q} />
                </div>
            </Show>
        </div>
    );
}

// 5. Protected routes
interface ProtectedRouteProps {
    children: any;
    redirectTo?: string;
}

function ProtectedRoute(props: ProtectedRouteProps) {
    const isAuthenticated = true; // Replace with actual auth logic
    const navigate = useNavigate();

    createEffect(() => {
        if (!isAuthenticated) {
            navigate(props.redirectTo || '/login');
        }
    });

    return <Show when={isAuthenticated}>{props.children}</Show>;
}

// 6. Route-based code splitting
function LazyPage() {
    const LazyComponent = lazy(() => import('./components/HeavyComponent'));

    return (
        <Suspense fallback={<div>Loading page...</div>}>
            <LazyComponent />
        </Suspense>
    );
}

// 7. Nested routes
function App() {
    return (
        <Router>
            <div class="app">
                <Navigation />
                <main>
                    <Route path="/" component={HomePage} />
                    <Route path="/about" component={AboutPage} />

                    <ProtectedRoute>
                        <Route path="/dashboard/*" component={DashboardLayout}>
                            <Route path="/dashboard" component={DashboardHome} />
                            <Route path="/dashboard/users" component={UserManagement} />
                            <Route path="/dashboard/settings" component={Settings} />
                        </Route>
                    </ProtectedRoute>

                    <Route path="/users/:id" component={UserDetail} />
                    <Route path="/search" component={SearchPage} />

                    <Route path="*" component={NotFoundPage} />
                </main>
            </div>
        </Router>
    );
}

// 8. Page components
function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
            <p>Welcome to the Solid.js app!</p>
        </div>
    );
}

function AboutPage() {
    return (
        <div>
            <h1>About Page</h1>
            <p>This is an about page with routing.</p>
        </div>
    );
}

function DashboardLayout(props: any) {
    return (
        <div class="dashboard">
            <aside>
                <h3>Dashboard</h3>
                <nav>
                    <A href="/dashboard">Overview</A>
                    <A href="/dashboard/users">Users</A>
                    <A href="/dashboard/settings">Settings</A>
                </nav>
            </aside>
            <div class="dashboard-content">
                {props.children}
            </div>
        </div>
    );
}

function DashboardHome() {
    return <div>Dashboard Overview</div>;
}

function UserManagement() {
    return <div>User Management</div>;
}

function Settings() {
    return <div>Settings</div>;
}

function NotFoundPage() {
    return (
        <div>
            <h1>404 - Page Not Found</h1>
            <p>The page you're looking for doesn't exist.</p>
            <A href="/">Go Home</A>
        </div>
    );
}

// Helper components (simplified implementations)
function UserProfile(props: { id: string }) {
    return <div>User Profile for {props.id}</div>;
}

function UserSettings(props: { id: string }) {
    return <div>User Settings for {props.id}</div>;
}

function UserPosts(props: { id: string }) {
    return <div>User Posts for {props.id}</div>;
}

function SearchResults(props: { query: string }) {
    return <div>Search results for "{props.query}"</div>;
}

export {
    App,
    router,
    Navigation,
    ProtectedRoute,
    HomePage,
    AboutPage,
    DashboardLayout,
    UserDetail,
    SearchPage
};

💻 Patterns de Composants Solid.js tsx

🔴 complex

Patterns de composants communs et meilleures pratiques dans Solid.js

// Solid.js Component Patterns

import {
    createSignal,
    For,
    Show,
    Switch,
    Match,
    Index,
    ErrorBoundary,
    Suspense,
    createResource,
    createContext,
    useContext,
    ParentComponent,
    createMemo
} from 'solid-js';

// 1. List rendering patterns
interface User {
    id: number;
    name: string;
    email: string;
    role: 'admin' | 'user' | 'guest';
}

function UserList(props: { users: User[] }) {
    const [selectedUserId, setSelectedUserId] = createSignal<number | null>(null);

    return (
        <div>
            <h3>User List</h3>

            {/* Using For (efficient for dynamic lists) */}
            <For each={props.users}>
                {(user) => (
                    <div
                        class={`user-item ${selectedUserId() === user.id ? 'selected' : ''}`}
                        onClick={() => setSelectedUserId(user.id)}
                    >
                        <h4>{user.name}</h4>
                        <p>{user.email}</p>
                        <span class="role">{user.role}</span>
                    </div>
                )}
            </For>

            {/* Using Index (when index matters) */}
            <h4>Indexed List:</h4>
            <Index each={props.users}>
                {(user, index) => (
                    <div>
                        {index + 1}. {user().name} - {user().role}
                    </div>
                )}
            </Index>
        </div>
    );
}

// 2. Conditional rendering patterns
function ConditionalRendering() {
    const [status, setStatus] = createSignal<'loading' | 'success' | 'error'>('loading');
    const [user, setUser] = createSignal<User | null>(null);

    return (
        <div>
            <h3>Conditional Rendering</h3>

            {/* Basic Show/Else */}
            <Show when={user()} fallback={<p>Please log in</p>}>
                {(user) => (
                    <p>Welcome, {user().name}!</p>
                )}
            </Show>

            {/* Switch/Match for multiple conditions */}
            <Switch fallback={<p>Unknown status</p>}>
                <Match when={status() === 'loading'}>
                    <div class="spinner">Loading...</div>
                </Match>
                <Match when={status() === 'success'}>
                    <div class="success">✓ Success!</div>
                </Match>
                <Match when={status() === 'error'}>
                    <div class="error">✗ Error occurred</div>
                </Match>
            </Switch>

            <button onClick={() => setStatus('loading')}>Set Loading</button>
            <button onClick={() => setStatus('success')}>Set Success</button>
            <button onClick={() => setStatus('error')}>Set Error</button>
        </div>
    );
}

// 3. Context pattern
interface ThemeContextType {
    theme: 'light' | 'dark';
    toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType>();

const ThemeProvider: ParentComponent = (props) => {
    const [theme, setTheme] = createSignal<'light' | 'dark'>('light');

    const toggleTheme = () => {
        setTheme(current => current === 'light' ? 'dark' : 'light');
    };

    const context: ThemeContextType = {
        theme: theme(),
        toggleTheme
    };

    return (
        <ThemeContext.Provider value={context}>
            <div class={`app ${theme()}-theme`}>
                {props.children}
            </div>
        </ThemeContext.Provider>
    );
};

const useTheme = () => useContext(ThemeContext);

function ThemedComponent() {
    const { theme, toggleTheme } = useTheme();

    return (
        <div class={`themed-component ${theme}`}>
            <p>Current theme: {theme}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
}

// 4. Error boundaries
function ErrorBoundaryExample() {
    const [shouldError, setShouldError] = createSignal(false);

    const ThrowError = () => {
        if (shouldError()) {
            throw new Error('Something went wrong!');
        }
        return <div>No error</div>;
    };

    return (
        <div>
            <h3>Error Boundary</h3>
            <button onClick={() => setShouldError(true)}>
                Trigger Error
            </button>
            <button onClick={() => setShouldError(false)}>
                Reset
            </button>

            <ErrorBoundary fallback={(err, reset) => (
                <div class="error-boundary">
                    <h4>Error: {err.message}</h4>
                    <button onClick={reset}>Try Again</button>
                </div>
            )}>
                <ThrowError />
            </ErrorBoundary>
        </div>
    );
}

// 5. Data fetching with Suspense
interface Post {
    id: number;
    title: string;
    body: string;
    userId: number;
}

async function fetchPosts(): Promise<Post[]> {
    // Simulate API delay
    await new Promise(resolve => setTimeout(resolve, 2000));
    return [
        { id: 1, title: 'Post 1', body: 'This is post 1', userId: 1 },
        { id: 2, title: 'Post 2', body: 'This is post 2', userId: 2 }
    ];
}

function DataFetching() {
    const [posts] = createResource(fetchPosts);

    return (
        <div>
            <h3>Data Fetching with Suspense</h3>
            <Suspense fallback={<div class="spinner">Loading posts...</div>}>
                <Show when={!posts.error} when={!posts.loading}>
                    <div class="posts">
                        <For each={posts()}>
                            {(post) => (
                                <article class="post">
                                    <h3>{post.title}</h3>
                                    <p>{post.body}</p>
                                </article>
                            )}
                        </For>
                    </div>
                </Show>
            </Suspense>
        </div>
    );
}

// 6. Higher-order component pattern
const withLoading = <P extends object,>(Component: (props: P) => any) => {
    return (props: P & { loading?: boolean }) => {
        return (
            <Show when={!props.loading} fallback={<div class="spinner">Loading...</div>}>
                <Component {...props} />
            </Show>
        );
    };
};

const UserProfile = (props: { name: string; email: string }) => (
    <div class="user-profile">
        <h4>{props.name}</h4>
        <p>{props.email}</p>
    </div>
);

const UserProfileWithLoading = withLoading(UserProfile);

// 7. Render props pattern
interface ListProps<T> {
    items: T[];
    renderItem: (item: T, index: () => number) => any;
    emptyMessage?: string;
}

function List<T>(props: ListProps<T>) {
    return (
        <div class="list">
            <Show when={props.items.length > 0} fallback={
                <div class="empty">{props.emptyMessage || 'No items'}</div>
            }>
                <For each={props.items}>
                    {(item, index) => props.renderItem(item, index)}
                </For>
            </Show>
        </div>
    );
}

// 8. Compound component pattern
interface TabsContextType {
    activeTab: string;
    setActiveTab: (tab: string) => void;
}

const TabsContext = createContext<TabsContextType>();

const Tabs: ParentComponent<{ initialTab?: string }> = (props) => {
    const [activeTab, setActiveTab] = createSignal(props.initialTab || '');

    return (
        <TabsContext.Provider value={{ activeTab: activeTab(), setActiveTab }}>
            <div class="tabs">{props.children}</div>
        </TabsContext.Provider>
    );
};

const TabList: ParentComponent = (props) => {
    return <div class="tab-list">{props.children}</div>;
};

const Tab: ParentComponent<{ value: string }> = (props) => {
    const { activeTab, setActiveTab } = useContext(TabsContext)!;
    const isActive = activeTab === props.value;

    return (
        <button
            class={`tab ${isActive ? 'active' : ''}`}
            onClick={() => setActiveTab(props.value)}
        >
            {props.children}
        </button>
    );
};

const TabPanels: ParentComponent = (props) => {
    return <div class="tab-panels">{props.children}</div>;
};

const TabPanel: ParentComponent<{ value: string }> = (props) => {
    const { activeTab } = useContext(TabsContext)!;

    return (
        <Show when={activeTab === props.value}>
            <div class="tab-panel">{props.children}</div>
        </Show>
    );
};

export {
    UserList,
    ConditionalRendering,
    ThemeProvider,
    ThemedComponent,
    ErrorBoundaryExample,
    DataFetching,
    UserProfileWithLoading,
    List,
    Tabs,
    TabList,
    Tab,
    TabPanels,
    TabPanel
};