# Webflow Apps Utils

**Internal Finsweet Library**

Shared UI components and utilities for Finsweet's Webflow applications.

🔗 **[Live Storybook Documentation](https://webflow-apps-utils.pages.dev)**

## Installation

```bash
pnpm add @finsweet/webflow-apps-utils
```

## Usage

```typescript
import '@finsweet/webflow-apps-utils/index.css';

import { Button, Modal, Input, siteInfo } from '@finsweet/webflow-apps-utils';
```

## Project Structure

```
src/
├── lib/
│   ├── router/          # Client-side routing utilities
│   ├── stores/          # Stores for state management
│   ├── types/           # TypeScript type definitions
│   ├── ui/              # UI components and styling
│   │   ├── components/  # Reusable UI components
│   │   └── icons/       # SVG icon components
│   └── utils/           # Helper functions and utilities
```

## Coding Standards

### Naming Conventions

#### File and Folder Names

- **Folders**: Use `kebab-case` for all directory names

  ```
  ✅ custom-code/
  ✅ webflow-canvas/
  ✅ browser-storage/
  ❌ customCode/
  ❌ WebflowCanvas/
  ```

- **Files**: Use `camelCase` and `PascalCase` for all file names

  ```
  ✅ localStorage.ts
  ✅ handlePaste.ts
  ✅ getAllAssets.ts
  ✅ Button.svelte
  ✅ Form.svelte
  ❌ local-storage.ts
  ❌ handle_paste.ts
  ❌ form.svelte

  ```

#### Code Naming

- **Components**: Use `PascalCase` for component names
- **Functions**: Use `camelCase` for function names
- **Variables**: Use `camelCase` for variable names
- **Constants**: Use `UPPERCASE` for constants
- **Types/Interfaces**: Use `PascalCase` for type definitions

### Documentation Standards

- Keep function documentation concise

## Testing UI Components

### Testing Strategy

All UI components should include comprehensive tests covering:

- **Basic Rendering**: Default props, custom props, and various configurations
- **Component Variants**: All available variants (primary, secondary, danger, etc.)
- **Interactive States**: Disabled, loading, invalid, and other state combinations
- **User Interactions**: Click events, keyboard navigation, and form submissions
- **Accessibility**: ARIA attributes, focus management, and screen reader support
- **Visual Features**: Icons, tooltips, custom styling, and layout variations

### Test Structure

Use Vitest and Testing Library for component testing:

```typescript
import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
import { describe, expect, it, vi } from 'vitest';
import Component from './Component.svelte';

describe('Component Name', () => {
	describe('Basic Rendering', () => {
		it('renders with default props', () => {
			render(Component);
			const element = screen.getByRole('...');
			expect(element).toBeInTheDocument();
		});
	});

	describe('Variants', () => {
		// Test all component variants
	});

	describe('States', () => {
		// Test disabled, loading, invalid states
	});

	describe('Events', () => {
		// Test user interactions
	});

	describe('Accessibility', () => {
		// Test ARIA attributes and keyboard navigation
	});
});
```

### Testing Best Practices

- **Comprehensive Coverage**: Test all props, variants, and states
- **User-Centric Tests**: Focus on user interactions rather than implementation details
- **Accessibility Testing**: Verify ARIA attributes, keyboard navigation, and screen reader support
- **Async Behavior**: Use `waitFor` for testing tooltips, modals, and dynamic content
- **Event Mocking**: Mock external dependencies and event handlers properly
- **State Verification**: Check both visual state and underlying attributes

### Example: Button Component Testing

```typescript
// Testing component variants
const variants: ButtonVariant[] = ['primary', 'secondary', 'danger', 'cms'];
variants.forEach((variant) => {
	it(`renders ${variant} variant correctly`, () => {
		render(Button, { variant });
		const button = screen.getByRole('button');
		expect(button).toHaveClass(`button--${variant}`);
	});
});

// Testing interactive states
it('renders loading state', () => {
	render(Button, { loading: true });
	const button = screen.getByRole('button');
	expect(button).toBeDisabled();
	expect(button).toHaveAttribute('aria-busy', 'true');
	expect(button).toHaveClass('button--loading');
});

// Testing accessibility
it('has proper ARIA attributes', () => {
	render(Button, { ariaLabel: 'Custom label', loading: true });
	const button = screen.getByRole('button');
	expect(button).toHaveAttribute('aria-label', 'Custom label');
	expect(button).toHaveAttribute('aria-busy', 'true');
});

// Testing async interactions (tooltips)
it('shows tooltip on hover', async () => {
	render(Button, {
		tooltip: { message: 'Help text', listener: 'hover', listenerout: 'hover' }
	});

	const button = screen.getByRole('button');
	const tooltipTarget = button.closest('.target');
	await fireEvent.mouseEnter(tooltipTarget || button);

	expect(screen.getByText('Help text')).toBeInTheDocument();
});
```

## Development

```bash
# Setup
pnpm install

# Development
pnpm dev              # Start dev server
pnpm storybook        # Component development

# Building & Testing
pnpm build            # Build library
pnpm test             # Run tests
pnpm lint             # Check code quality
pnpm check            # Check code quality
```

## Publishing

Uses Changesets for automated releases. Create a changeset:

```bash
pnpm changeset
```

Push changes to trigger automatic publishing to npm via GitHub Actions.
