typescript-utility-types
Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.
atomic-design-integration
Use when integrating Atomic Design methodology with React, Vue, Angular, or other frameworks. Framework-specific implementation patterns.
Full skill instructions
Master the integration of Atomic Design methodology with modern frontend frameworks. This skill covers React, Vue, Angular, and general patterns for implementing atomic component hierarchies.
src/
components/
atoms/
Button/
Button.tsx
Button.module.css
Button.test.tsx
Button.stories.tsx
index.ts
index.ts # Barrel export
molecules/
FormField/
FormField.tsx
FormField.module.css
FormField.test.tsx
index.ts
index.ts
organisms/
Header/
Header.tsx
Header.module.css
useHeader.ts # Custom hook
index.ts
index.ts
templates/
MainLayout/
MainLayout.tsx
MainLayout.module.css
index.ts
index.ts
index.ts # Main barrel export
pages/ # Next.js or page components
HomePage/
HomePage.tsx
index.ts
// components/atoms/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
export { Input } from './Input';
export type { InputProps } from './Input';
export { Label } from './Label';
export type { LabelProps } from './Label';
export { Icon } from './Icon';
export type { IconProps } from './Icon';
// components/index.ts
export * from './atoms';
export * from './molecules';
export * from './organisms';
export * from './templates';
// components/atoms/Button/Button.tsx
import React, { forwardRef } from 'react';
import type { ButtonHTMLAttributes } from 'react';
import styles from './Button.module.css';
import { clsx } from 'clsx';
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
export type ButtonSize = 'sm' | 'md' | 'lg';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
fullWidth?: boolean;
isLoading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant = 'primary',
size = 'md',
fullWidth = false,
isLoading = false,
leftIcon,
rightIcon,
disabled,
children,
className,
...props
},
ref
) => {
return (
<button
ref={ref}
className={clsx(
styles.button,
styles[variant],
styles[size],
fullWidth && styles.fullWidth,
isLoading && styles.loading,
className
)}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading ? (
<span className={styles.spinner} aria-hidden="true" />
) : (
<>
{leftIcon && <span className={styles.leftIcon}>{leftIcon}</span>}
{children}
{rightIcon && <span className={styles.rightIcon}>{rightIcon}</span>}
</>
)}
</button>
);
}
);
Button.displayName = 'Button';
// organisms/Header/useHeader.ts
import { useState, useCallback } from 'react';
interface UseHeaderOptions {
user?: { id: string; name: string };
onLogout?: () => void;
}
export function useHeader({ user, onLogout }: UseHeaderOptions) {
const [menuOpen, setMenuOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const toggleMenu = useCallback(() => {
setMenuOpen((prev) => !prev);
}, []);
const handleSearch = useCallback((query: string) => {
setSearchQuery(query);
// Could trigger search action here
}, []);
const handleLogout = useCallback(() => {
setMenuOpen(false);
onLogout?.();
}, [onLogout]);
return {
menuOpen,
toggleMenu,
searchQuery,
handleSearch,
handleLogout,
isAuthenticated: !!user,
};
}
// contexts/ThemeContext.tsx
import React, { createContext, useContext, useState } from 'react';
interface Theme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
spacing: {
xs: string;
sm: string;
md: string;
lg: string;
};
}
const defaultTheme: Theme = {
colors: {
primary: '#2196f3',
secondary: '#f50057',
background: '#ffffff',
text: '#212121',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
},
};
const ThemeContext = createContext<{
theme: Theme;
setTheme: (theme: Theme) => void;
}>({
theme: defaultTheme,
setTheme: () => {},
});
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [theme, setTheme] = useState(defaultTheme);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
src/
components/
atoms/
VButton/
VButton.vue
VButton.spec.ts
index.ts
VInput/
index.ts
molecules/
VFormField/
VFormField.vue
useFormField.ts
index.ts
index.ts
organisms/
VHeader/
VHeader.vue
index.ts
index.ts
templates/
MainLayout/
MainLayout.vue
index.ts
<!-- components/atoms/VButton/VButton.vue -->
<template>
<button
:class="buttonClasses"
:disabled="disabled || loading"
:aria-busy="loading"
v-bind="$attrs"
>
<span v-if="loading" class="spinner" aria-hidden="true" />
<template v-else>
<span v-if="$slots.leftIcon" class="left-icon">
<slot name="leftIcon" />
</span>
<slot />
<span v-if="$slots.rightIcon" class="right-icon">
<slot name="rightIcon" />
</span>
</template>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary';
size?: 'sm' | 'md' | 'lg';
fullWidth?: boolean;
loading?: boolean;
disabled?: boolean;
}
const props = withDefaults(defineProps<ButtonProps>(), {
variant: 'primary',
size: 'md',
fullWidth: false,
loading: false,
disabled: false,
});
const buttonClasses = computed(() => [
'btn',
`btn-${props.variant}`,
`btn-${props.size}`,
{ 'btn-full': props.fullWidth, 'btn-loading': props.loading },
]);
</script>
<style scoped>
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 150ms ease;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
.btn-sm {
padding: 6px 12px;
font-size: 14px;
}
.btn-md {
padding: 8px 16px;
font-size: 16px;
}
.btn-lg {
padding: 12px 24px;
font-size: 18px;
}
.btn-full {
width: 100%;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
// components/organisms/VHeader/useHeader.ts
import { ref, computed } from 'vue';
import type { Ref } from 'vue';
interface User {
id: string;
name: string;
}
export function useHeader(user: Ref<User | null>) {
const menuOpen = ref(false);
const searchQuery = ref('');
const isAuthenticated = computed(() => !!user.value);
const toggleMenu = () => {
menuOpen.value = !menuOpen.value;
};
const handleSearch = (query: string) => {
searchQuery.value = query;
};
return {
menuOpen,
searchQuery,
isAuthenticated,
toggleMenu,
handleSearch,
};
}
src/
app/
components/
atoms/
button/
button.component.ts
button.component.html
button.component.scss
button.component.spec.ts
input/
atoms.module.ts
molecules/
form-field/
form-field.component.ts
form-field.component.html
molecules.module.ts
organisms/
header/
header.component.ts
header.service.ts
organisms.module.ts
templates/
main-layout/
main-layout.component.ts
templates.module.ts
pages/
home/
home.component.ts
// components/atoms/button/button.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
export type ButtonSize = 'sm' | 'md' | 'lg';
@Component({
selector: 'app-button',
standalone: true,
imports: [CommonModule],
template: `
<button
[class]="buttonClasses"
[disabled]="disabled || loading"
[attr.aria-busy]="loading"
(click)="onClick.emit($event)"
>
<span *ngIf="loading" class="spinner" aria-hidden="true"></span>
<ng-container *ngIf="!loading">
<ng-content select="[leftIcon]"></ng-content>
<ng-content></ng-content>
<ng-content select="[rightIcon]"></ng-content>
</ng-container>
</button>
`,
styleUrls: ['./button.component.scss'],
})
export class ButtonComponent {
@Input() variant: ButtonVariant = 'primary';
@Input() size: ButtonSize = 'md';
@Input() fullWidth = false;
@Input() loading = false;
@Input() disabled = false;
@Output() onClick = new EventEmitter<MouseEvent>();
get buttonClasses(): string {
return [
'btn',
`btn-${this.variant}`,
`btn-${this.size}`,
this.fullWidth ? 'btn-full' : '',
this.loading ? 'btn-loading' : '',
]
.filter(Boolean)
.join(' ');
}
}
// components/atoms/atoms.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { InputComponent } from './input/input.component';
import { LabelComponent } from './label/label.component';
import { IconComponent } from './icon/icon.component';
@NgModule({
imports: [CommonModule],
declarations: [
ButtonComponent,
InputComponent,
LabelComponent,
IconComponent,
],
exports: [ButtonComponent, InputComponent, LabelComponent, IconComponent],
})
export class AtomsModule {}
// components/molecules/molecules.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AtomsModule } from '../atoms/atoms.module';
import { FormFieldComponent } from './form-field/form-field.component';
import { SearchFormComponent } from './search-form/search-form.component';
@NgModule({
imports: [CommonModule, AtomsModule],
declarations: [FormFieldComponent, SearchFormComponent],
exports: [FormFieldComponent, SearchFormComponent],
})
export class MoleculesModule {}
// components/atoms/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
import { Icon } from '../Icon';
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
description: 'Visual style variant',
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Size of the button',
},
fullWidth: {
control: 'boolean',
description: 'Whether button takes full width',
},
isLoading: {
control: 'boolean',
description: 'Loading state',
},
disabled: {
control: 'boolean',
description: 'Disabled state',
},
},
args: {
children: 'Button',
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
variant: 'primary',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
export const WithIcons: Story = {
args: {
leftIcon: <Icon name="plus" size="sm" />,
children: 'Add Item',
},
};
export const Loading: Story = {
args: {
isLoading: true,
children: 'Submitting...',
},
};
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
</div>
),
};
export const AllSizes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
),
};
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/components/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
// Organize stories by atomic level
// Title format: "Level/ComponentName"
// Atoms
title: 'Atoms/Button'
title: 'Atoms/Input'
title: 'Atoms/Icon'
// Molecules
title: 'Molecules/FormField'
title: 'Molecules/SearchForm'
// Organisms
title: 'Organisms/Header'
title: 'Organisms/Footer'
// Templates
title: 'Templates/MainLayout'
title: 'Templates/DashboardLayout'
// Pages (example compositions)
title: 'Pages/HomePage'
// components/atoms/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Button } from './Button';
describe('Button', () => {
describe('rendering', () => {
it('renders children correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('applies variant class correctly', () => {
render(<Button variant="secondary">Button</Button>);
expect(screen.getByRole('button')).toHaveClass('secondary');
});
it('applies size class correctly', () => {
render(<Button size="lg">Button</Button>);
expect(screen.getByRole('button')).toHaveClass('lg');
});
});
describe('states', () => {
it('disables button when disabled prop is true', () => {
render(<Button disabled>Button</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('disables button when loading', () => {
render(<Button isLoading>Button</Button>);
expect(screen.getByRole('button')).toBeDisabled();
expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
});
it('shows spinner when loading', () => {
render(<Button isLoading>Button</Button>);
expect(screen.getByRole('button').querySelector('.spinner')).toBeInTheDocument();
});
});
describe('interactions', () => {
it('calls onClick handler when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Button</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('does not call onClick when disabled', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick} disabled>Button</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).not.toHaveBeenCalled();
});
});
describe('accessibility', () => {
it('has no accessibility violations', async () => {
const { container } = render(<Button>Accessible Button</Button>);
// If using axe-core
// const results = await axe(container);
// expect(results).toHaveNoViolations();
});
it('forwards ref correctly', () => {
const ref = { current: null };
render(<Button ref={ref}>Button</Button>);
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
});
});
});
// components/organisms/Header/Header.integration.test.tsx
import { render, screen, fireEvent, within } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Header } from './Header';
const mockNavigation = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'products', label: 'Products', href: '/products' },
{ id: 'about', label: 'About', href: '/about' },
];
const mockUser = {
id: '1',
name: 'John Doe',
email: '[email protected]',
};
describe('Header Integration', () => {
it('renders navigation items correctly', () => {
render(
<Header
logo={<span>Logo</span>}
navigation={mockNavigation}
/>
);
const nav = screen.getByRole('navigation');
expect(within(nav).getByText('Home')).toBeInTheDocument();
expect(within(nav).getByText('Products')).toBeInTheDocument();
expect(within(nav).getByText('About')).toBeInTheDocument();
});
it('shows login button when no user', () => {
render(
<Header
logo={<span>Logo</span>}
navigation={mockNavigation}
onLogin={vi.fn()}
/>
);
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
});
it('shows user menu when authenticated', () => {
render(
<Header
logo={<span>Logo</span>}
navigation={mockNavigation}
user={mockUser}
/>
);
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
it('calls onLogout when logout clicked', async () => {
const handleLogout = vi.fn();
render(
<Header
logo={<span>Logo</span>}
navigation={mockNavigation}
user={mockUser}
onLogout={handleLogout}
/>
);
// Open user menu
fireEvent.click(screen.getByText('John Doe'));
// Click logout
fireEvent.click(screen.getByText('Logout'));
expect(handleLogout).toHaveBeenCalledTimes(1);
});
});
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["src/components/*"],
"@/components": ["src/components/index.ts"],
"@/atoms/*": ["src/components/atoms/*"],
"@/molecules/*": ["src/components/molecules/*"],
"@/organisms/*": ["src/components/organisms/*"],
"@/templates/*": ["src/components/templates/*"],
"@/hooks/*": ["src/hooks/*"],
"@/utils/*": ["src/utils/*"],
"@/design-tokens/*": ["src/design-tokens/*"]
}
}
}
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@/components': path.resolve(__dirname, './src/components'),
'@/atoms': path.resolve(__dirname, './src/components/atoms'),
'@/molecules': path.resolve(__dirname, './src/components/molecules'),
'@/organisms': path.resolve(__dirname, './src/components/organisms'),
'@/templates': path.resolve(__dirname, './src/components/templates'),
},
},
});
// Every component folder should have index.ts
// atoms/Button/index.ts
export { Button } from './Button';
export type { ButtonProps, ButtonVariant, ButtonSize } from './Button';
// Always export prop interfaces for composition
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
}
// Extract logic that can be shared across frameworks
// utils/formatPrice.ts
export function formatPrice(amount: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
}
atomic-design-fundamentals - Core methodology overviewatomic-design-atoms - Creating atomic componentsatomic-design-molecules - Composing atoms into moleculesatomic-design-organisms - Building complex organismsatomic-design-templates - Page layouts without contentUse when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.
Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.
Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.
Generate production-ready code from a Figma component or frame using the Figma MCP server
Use when integrating Dialyzer into development workflows and CI/CD pipelines for Erlang/Elixir projects.
Use when ruboCop configuration including YAML config, departments, inheritance, and customization.
Use when Elixir OTP patterns including GenServer, Supervisor, Agent, and Task. Use when building concurrent, fault-tolerant Elixir applications.
Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
Use when swift's optional handling patterns including optional binding, chaining, nil coalescing, and modern approaches to safely working with optional values while avoiding common pitfalls and force unwrapping.
Use when annotating code with structured metadata, tags, and markers for AI-assisted development workflows. Covers annotation formats, semantic tags, and integration with development tools.
Use when scala collections including immutable/mutable variants, List, Vector, Set, Map operations, collection transformations, lazy evaluation with views, parallel collections, and custom collection builders for efficient data processing.
Use when creating custom React Flow nodes, edges, and handles. Covers custom node components, resizable nodes, toolbars, and advanced customization.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience