Reusable utility functions and helpers
Tags: utility helpers functions
The theme library provides utility functions and helpers for common tasks.
Class Utilities
cn()
Merge class names with Tailwind conflict resolution:
<script>
import { cn } from '@classic-homes/theme-svelte';
</script>
<div
class={cn(
'px-4 py-2 rounded-md',
variant === 'primary' && 'bg-primary text-primary-foreground',
variant === 'secondary' && 'bg-secondary text-secondary-foreground',
className
)}
>
Content
</div>The cn() function combines clsx for conditional classes with tailwind-merge for intelligent Tailwind class conflict resolution.
Examples
import { cn } from '@classic-homes/theme-svelte';
// Conditional classes
cn('base-class', isActive && 'active-class');
// → 'base-class active-class' or 'base-class'
// Override conflicting Tailwind classes
cn('px-4 py-2', 'px-6');
// → 'py-2 px-6' (px-4 is removed)
// Object syntax
cn('base', { active: isActive, disabled: isDisabled });
// Array syntax
cn(['class1', 'class2'], condition && 'class3');
// Combining all
cn(
'base-styles',
variant === 'primary' && 'bg-primary',
{ 'opacity-50': disabled },
className // user override
);Type Utilities
Component Types
Export your own types for reusable patterns:
// types.ts
import type { DataTableColumn, Tab, NavItem, NavSection } from '@classic-homes/theme-svelte';
export type { DataTableColumn, Tab, NavItem, NavSection };Common Exported Types
// Navigation
interface NavItem {
id: string;
name: string;
href?: string;
icon?: string;
active?: boolean;
children?: NavItem[];
roles?: string[];
external?: boolean;
}
interface NavSection {
id: string;
title?: string;
items: NavItem[];
collapsible?: boolean;
expanded?: boolean;
}
// Data Table
interface DataTableColumn<T> {
id: string;
header: string;
accessor: keyof T | ((row: T) => unknown);
sortable?: boolean;
width?: string;
align?: 'left' | 'center' | 'right';
format?: (value: unknown, row: T) => string;
}
// Tabs
interface Tab {
id: string;
label: string;
disabled?: boolean;
}
// Toast
interface Toast {
id: string;
message: string;
type: 'success' | 'error' | 'warning' | 'info';
title?: string;
duration?: number;
}
// User
interface User {
name: string;
email?: string;
initials?: string;
avatar?: string;
roles?: string[];
}Creating Custom Composables
Media Query Hook
// composables/useMediaQuery.svelte.ts
export function useMediaQuery(query: string) {
let matches = $state(false);
$effect(() => {
if (typeof window === 'undefined') return;
const mediaQuery = window.matchMedia(query);
matches = mediaQuery.matches;
const handler = (e: MediaQueryListEvent) => {
matches = e.matches;
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
});
return {
get matches() {
return matches;
},
};
}Usage:
<script>
import { useMediaQuery } from './composables/useMediaQuery.svelte';
const isMobile = useMediaQuery('(max-width: 768px)');
</script>
{#if isMobile.matches}
<MobileNav />
{:else}
<DesktopNav />
{/if}Click Outside
// composables/useClickOutside.svelte.ts
export function useClickOutside(getElement: () => HTMLElement | null, callback: () => void) {
$effect(() => {
const element = getElement();
if (!element) return;
const handler = (event: MouseEvent) => {
if (!element.contains(event.target as Node)) {
callback();
}
};
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
});
}Usage:
<script>
import { useClickOutside } from './composables/useClickOutside.svelte';
let menuRef = $state<HTMLElement | null>(null);
let open = $state(false);
useClickOutside(
() => menuRef,
() => (open = false)
);
</script>
<div bind:this={menuRef}>
{#if open}
<div class="menu">Menu content</div>
{/if}
</div>Debounced Value
// composables/useDebounce.svelte.ts
export function useDebounce<T>(getValue: () => T, delay: number) {
let debouncedValue = $state<T>(getValue());
$effect(() => {
const value = getValue();
const timeout = setTimeout(() => {
debouncedValue = value;
}, delay);
return () => clearTimeout(timeout);
});
return {
get value() {
return debouncedValue;
},
};
}Usage:
<script>
import { useDebounce } from './composables/useDebounce.svelte';
let searchInput = $state('');
const debouncedSearch = useDebounce(() => searchInput, 300);
$effect(() => {
if (debouncedSearch.value) {
search(debouncedSearch.value);
}
});
</script>
<input bind:value={searchInput} placeholder="Search..." />Best Practices
- Use
cn()for all className merging - Prevents Tailwind conflicts - Import types for reuse - Consistent interfaces across app
- Create composables for repeated logic - DRY principle
- Use
$effectfor side effects - Proper cleanup with return functions
Related
- Stores - State management
- Validation - Prop validation