From 29665c13f5ead1d5871497a6505a43398a9bc069 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:49:21 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 12 + README.md | 3 + plugin.lock.json | 72 ++ skills/frontend-designer/SKILL.md | 862 ++++++++++++++++ .../references/accessibility_checklist.md | 622 ++++++++++++ .../references/component_library.md | 937 ++++++++++++++++++ .../references/design_tokens.md | 608 ++++++++++++ .../references/responsive_patterns.md | 839 ++++++++++++++++ .../scripts/audit_accessibility.sh | 467 +++++++++ .../scripts/generate_component.sh | 619 ++++++++++++ .../scripts/setup_design_system.sh | 570 +++++++++++ 11 files changed, 5611 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 plugin.lock.json create mode 100644 skills/frontend-designer/SKILL.md create mode 100644 skills/frontend-designer/references/accessibility_checklist.md create mode 100644 skills/frontend-designer/references/component_library.md create mode 100644 skills/frontend-designer/references/design_tokens.md create mode 100644 skills/frontend-designer/references/responsive_patterns.md create mode 100755 skills/frontend-designer/scripts/audit_accessibility.sh create mode 100755 skills/frontend-designer/scripts/generate_component.sh create mode 100755 skills/frontend-designer/scripts/setup_design_system.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..3c94721 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "frontend-designer", + "description": "Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns.", + "version": "0.0.0-2025.11.28", + "author": { + "name": "James Rochabrun", + "email": "jamesrochabrun@gmail.com" + }, + "skills": [ + "./skills/frontend-designer" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ada10fb --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# frontend-designer + +Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..fb73592 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,72 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jamesrochabrun/skills:frontend-designer", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "25006303dc46aeb276e3867b2a6969938f5f9360", + "treeHash": "3ddcd83c606e56801426300ea6cd6b05a7be6b81613e9ef392a5e02500315daf", + "generatedAt": "2025-11-28T10:17:48.628984Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "frontend-designer", + "description": "Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns." + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "6843e1e7e42d80b31a1a6486aa749240fea67755a98772e2715769b945f02b62" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "97ca00c41e61c927b515383b129c3ece2b0e33c1629d2252cfec6e6fcbc8f25d" + }, + { + "path": "skills/frontend-designer/SKILL.md", + "sha256": "f144c92b819f8cca125c710b3085cee3cf8d3093512aada40ab2e0f7e2125705" + }, + { + "path": "skills/frontend-designer/references/accessibility_checklist.md", + "sha256": "16eecde5bc6e0ad4045f1be67eedaa9587ba56360036250a87853681c596d4fb" + }, + { + "path": "skills/frontend-designer/references/responsive_patterns.md", + "sha256": "06250863cb7fef0e25d3272dc17a84a7e777a82878138778fd9feddce17b8f60" + }, + { + "path": "skills/frontend-designer/references/component_library.md", + "sha256": "709b28d81b6c0505f96d08b6ad168aeadbc660c01c703639e018b7095994d93d" + }, + { + "path": "skills/frontend-designer/references/design_tokens.md", + "sha256": "fbba3f6d2d0b6fa0af11bec39532db48489646d16b11fcc6b7ec2314bb993c77" + }, + { + "path": "skills/frontend-designer/scripts/setup_design_system.sh", + "sha256": "50ed5f7a8e7cdb823aa42a6f549ba988333450e2cfdab3bafb0cca237d67506f" + }, + { + "path": "skills/frontend-designer/scripts/audit_accessibility.sh", + "sha256": "3c86c0d8e2fab2c9f9a49931b7952361413ff8e8f432da903e5f7cf578a9053d" + }, + { + "path": "skills/frontend-designer/scripts/generate_component.sh", + "sha256": "f7c2fb178721672f7e8dfee6a57456877c791dd59fbfb2ce34fce491b5a76e14" + } + ], + "dirSha256": "3ddcd83c606e56801426300ea6cd6b05a7be6b81613e9ef392a5e02500315daf" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/frontend-designer/SKILL.md b/skills/frontend-designer/SKILL.md new file mode 100644 index 0000000..440108e --- /dev/null +++ b/skills/frontend-designer/SKILL.md @@ -0,0 +1,862 @@ +--- +name: frontend-designer +description: Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns. +--- + +# Frontend Designer + +A comprehensive skill for frontend designers and developers to build beautiful, accessible, and performant user interfaces with modern best practices. + +## What This Skill Does + +Helps frontend designers/developers with: +- **Component Design & Development** - Build reusable, accessible components +- **Design Systems** - Implement tokens, patterns, and documentation +- **Responsive Design** - Mobile-first, fluid layouts +- **Accessibility (WCAG 2.1)** - Inclusive design patterns +- **Modern CSS** - Flexbox, Grid, custom properties +- **Performance Optimization** - Fast, efficient frontends +- **Framework Patterns** - React, Vue, Svelte best practices +- **Design-to-Code** - Figma to production workflows + +## Why This Skill Matters + +**Without systematic approach:** +- Inconsistent component implementations +- Accessibility issues +- Poor responsive behavior +- Duplicate code and styles +- Hard to maintain +- Performance problems + +**With this skill:** +- Consistent, reusable components +- WCAG AA compliant by default +- Mobile-first responsive design +- Design system aligned +- Maintainable codebase +- Fast, optimized delivery + +## Core Principles + +### 1. Accessibility First +- WCAG 2.1 AA minimum +- Semantic HTML +- Keyboard navigation +- Screen reader support +- Focus management +- Color contrast + +### 2. Mobile-First Responsive +- Start with mobile (320px) +- Progressive enhancement +- Fluid typography +- Flexible layouts +- Touch-friendly targets + +### 3. Performance by Default +- Minimal CSS/JS +- Lazy loading +- Optimized images +- Critical CSS +- Tree shaking + +### 4. Component-Driven +- Atomic design methodology +- Reusable components +- Props-based customization +- Composition over inheritance + +### 5. Design System Aligned +- Design tokens +- Consistent spacing +- Typography scale +- Color palette +- Component library + +## Component Patterns + +### Button Component + +**Accessible, flexible button pattern:** + +```tsx +// React example +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'ghost'; + size?: 'sm' | 'md' | 'lg'; + disabled?: boolean; + loading?: boolean; + children: React.ReactNode; + onClick?: () => void; +} + +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + disabled = false, + loading = false, + children, + onClick, + ...props +}) => { + return ( + + ); +}; +``` + +**CSS (with design tokens):** + +```css +.btn { + /* Base styles */ + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + + font-family: var(--font-sans); + font-weight: 600; + text-decoration: none; + + border: none; + border-radius: var(--radius-md); + cursor: pointer; + + transition: all 0.2s ease; + + /* Accessibility */ + min-height: 44px; /* WCAG touch target */ + + &:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +/* Variants */ +.btn--primary { + background: var(--color-primary); + color: var(--color-on-primary); + + &:hover:not(:disabled) { + background: var(--color-primary-hover); + } +} + +.btn--secondary { + background: var(--color-secondary); + color: var(--color-on-secondary); +} + +.btn--ghost { + background: transparent; + color: var(--color-primary); + border: 1px solid currentColor; +} + +/* Sizes */ +.btn--sm { + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); +} + +.btn--md { + padding: var(--space-3) var(--space-4); + font-size: var(--text-base); +} + +.btn--lg { + padding: var(--space-4) var(--space-6); + font-size: var(--text-lg); +} +``` + +### Card Component + +**Flexible, accessible card:** + +```tsx +interface CardProps { + variant?: 'elevated' | 'outlined' | 'filled'; + padding?: 'none' | 'sm' | 'md' | 'lg'; + interactive?: boolean; + children: React.ReactNode; +} + +export const Card: React.FC = ({ + variant = 'elevated', + padding = 'md', + interactive = false, + children, +}) => { + const Component = interactive ? 'button' : 'div'; + + return ( + + {children} + + ); +}; +``` + +**CSS:** + +```css +.card { + border-radius: var(--radius-lg); + background: var(--color-surface); +} + +.card--elevated { + box-shadow: var(--shadow-md); +} + +.card--outlined { + border: 1px solid var(--color-border); +} + +.card--filled { + background: var(--color-surface-variant); +} + +.card--interactive { + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + + &:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + } + + &:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; + } +} + +.card--padding-sm { padding: var(--space-3); } +.card--padding-md { padding: var(--space-4); } +.card--padding-lg { padding: var(--space-6); } +``` + +### Form Input Component + +**Accessible form input:** + +```tsx +interface InputProps { + label: string; + error?: string; + hint?: string; + required?: boolean; + type?: 'text' | 'email' | 'password' | 'number'; +} + +export const Input: React.FC = ({ + label, + error, + hint, + required = false, + type = 'text', + ...props +}) => { + const id = useId(); + const hintId = `${id}-hint`; + const errorId = `${id}-error`; + + return ( +
+ + + {hint && ( +

+ {hint} +

+ )} + + + + {error && ( + + )} +
+ ); +}; +``` + +## Design Tokens + +**CSS Custom Properties for design system:** + +```css +:root { + /* Colors - Primary */ + --color-primary: #0066FF; + --color-primary-hover: #0052CC; + --color-on-primary: #FFFFFF; + + /* Colors - Surface */ + --color-surface: #FFFFFF; + --color-surface-variant: #F5F5F5; + --color-on-surface: #1A1A1A; + + /* Colors - Borders */ + --color-border: #E0E0E0; + --color-border-hover: #BDBDBD; + + /* Colors - Semantic */ + --color-error: #D32F2F; + --color-success: #388E3C; + --color-warning: #F57C00; + --color-info: #1976D2; + + /* Spacing Scale (8px base) */ + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.5rem; /* 24px */ + --space-6: 2rem; /* 32px */ + --space-8: 3rem; /* 48px */ + --space-10: 4rem; /* 64px */ + + /* Typography Scale */ + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.875rem; /* 14px */ + --text-base: 1rem; /* 16px */ + --text-lg: 1.125rem; /* 18px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + --text-4xl: 2.25rem; /* 36px */ + + /* Font Families */ + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: "SF Mono", Monaco, "Cascadia Code", monospace; + + /* Line Heights */ + --leading-tight: 1.25; + --leading-normal: 1.5; + --leading-relaxed: 1.75; + + /* Border Radius */ + --radius-sm: 0.25rem; /* 4px */ + --radius-md: 0.5rem; /* 8px */ + --radius-lg: 1rem; /* 16px */ + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15); + + /* Focus Ring */ + --color-focus: #0066FF; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-base: 200ms ease; + --transition-slow: 300ms ease; +} + +/* Dark mode */ +@media (prefers-color-scheme: dark) { + :root { + --color-surface: #1A1A1A; + --color-surface-variant: #2A2A2A; + --color-on-surface: #FFFFFF; + --color-border: #3A3A3A; + } +} +``` + +## Responsive Design Patterns + +### Mobile-First Breakpoints + +```css +/* Mobile-first approach */ +.container { + padding: var(--space-4); + + /* Tablet: 768px and up */ + @media (min-width: 48rem) { + padding: var(--space-6); + } + + /* Desktop: 1024px and up */ + @media (min-width: 64rem) { + padding: var(--space-8); + max-width: 1200px; + margin: 0 auto; + } +} +``` + +### Fluid Typography + +```css +/* Responsive typography */ +h1 { + font-size: clamp(2rem, 5vw, 3.5rem); + line-height: var(--leading-tight); +} + +h2 { + font-size: clamp(1.5rem, 4vw, 2.5rem); +} + +p { + font-size: clamp(1rem, 2vw, 1.125rem); + line-height: var(--leading-normal); +} +``` + +### Grid Layouts + +```css +/* Responsive grid */ +.grid { + display: grid; + gap: var(--space-4); + + /* Auto-fit columns (min 280px) */ + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +/* 12-column grid system */ +.grid-12 { + display: grid; + grid-template-columns: repeat(12, 1fr); + gap: var(--space-4); +} + +.col-span-4 { + grid-column: span 4; +} + +/* Stack on mobile */ +@media (max-width: 48rem) { + .col-span-4 { + grid-column: span 12; + } +} +``` + +## Accessibility Patterns + +### Skip Links + +```tsx +export const SkipLink = () => ( + + Skip to main content + +); +``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--color-primary); + color: var(--color-on-primary); + padding: var(--space-2) var(--space-4); + text-decoration: none; + z-index: 100; + + &:focus { + top: 0; + } +} +``` + +### Focus Management + +```tsx +// Modal with focus trap +export const Modal = ({ isOpen, onClose, children }) => { + const modalRef = useRef(null); + + useEffect(() => { + if (isOpen) { + // Save currently focused element + const previouslyFocused = document.activeElement; + + // Focus first focusable element in modal + const firstFocusable = modalRef.current?.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + firstFocusable?.focus(); + + // Restore focus on close + return () => previouslyFocused?.focus(); + } + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+
+ {children} +
+
+ ); +}; +``` + +### ARIA Labels + +```tsx +// Icon button with accessible label +export const IconButton = ({ icon, label, ...props }) => ( + +); + +// Loading state +export const LoadingButton = ({ loading, children, ...props }) => ( + +); +``` + +## Performance Optimization + +### Critical CSS + +```html + + + + + +``` + +### Lazy Loading Images + +```tsx +export const LazyImage = ({ src, alt, ...props }) => ( + {alt} +); +``` + +### Code Splitting + +```tsx +// React lazy loading +const Dashboard = lazy(() => import('./Dashboard')); + +function App() { + return ( + }> + + + ); +} +``` + +## Modern CSS Techniques + +### Container Queries + +```css +.card { + container-type: inline-size; +} + +.card-content { + display: flex; + flex-direction: column; + + /* Switch to row layout when container > 400px */ + @container (min-width: 400px) { + flex-direction: row; + } +} +``` + +### CSS Grid Auto-Fit + +```css +.gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-4); +} +``` + +### Custom Properties for Theming + +```css +/* Light theme (default) */ +:root { + --bg: #ffffff; + --text: #000000; +} + +/* Dark theme */ +[data-theme="dark"] { + --bg: #000000; + --text: #ffffff; +} + +body { + background: var(--bg); + color: var(--text); +} +``` + +## Component Library Structure + +``` +src/ +├── components/ +│ ├── Button/ +│ │ ├── Button.tsx +│ │ ├── Button.css +│ │ ├── Button.test.tsx +│ │ └── Button.stories.tsx +│ ├── Card/ +│ ├── Input/ +│ └── index.ts +├── tokens/ +│ ├── colors.css +│ ├── spacing.css +│ ├── typography.css +│ └── index.css +├── utils/ +│ ├── a11y.ts +│ └── responsive.ts +└── index.ts +``` + +## Using This Skill + +### Generate Component + +```bash +./scripts/generate_component.sh Button +``` + +Creates component with: +- TypeScript/JSX file +- CSS module +- Test file +- Storybook story +- Accessibility checks + +### Design System Setup + +```bash +./scripts/setup_design_system.sh +``` + +Creates: +- Design tokens (CSS custom properties) +- Base styles +- Component templates +- Documentation structure + +### Accessibility Audit + +```bash +./scripts/audit_accessibility.sh +``` + +Checks: +- Color contrast ratios +- Keyboard navigation +- ARIA attributes +- Semantic HTML +- Focus management + +## Best Practices + +### Component Design + +✅ **DO:** +- Use semantic HTML +- Make components keyboard accessible +- Provide ARIA labels +- Support both light and dark modes +- Make touch targets 44x44px minimum +- Use proper heading hierarchy +- Handle loading and error states + +❌ **DON'T:** +- Use divs for buttons +- Forget focus styles +- Hard-code colors +- Ignore mobile viewports +- Skip alt text on images +- Create inaccessible modals + +### CSS Best Practices + +✅ **DO:** +- Use design tokens +- Mobile-first responsive +- BEM or CSS modules for naming +- Logical properties (inline-start vs left) +- Modern layout (flexbox, grid) + +❌ **DON'T:** +- Use !important +- Deep nesting (> 3 levels) +- Fixed pixel values everywhere +- Browser-specific hacks +- Inline styles + +### Performance + +✅ **DO:** +- Lazy load images +- Code split routes +- Minimize CSS +- Use system fonts +- Optimize images (WebP) +- Critical CSS inline + +❌ **DON'T:** +- Load unused CSS +- Large JavaScript bundles +- Unoptimized images +- Blocking resources +- Too many web fonts + +## Framework-Specific Patterns + +### React + +```tsx +// Composition pattern +export const Card = ({ children }) => ( +
{children}
+); + +export const CardHeader = ({ children }) => ( +
{children}
+); + +// Usage + + Title + Content + +``` + +### Vue + +```vue + + + + +``` + +## Resources + +All reference materials included: +- Design token systems +- Accessibility checklist (WCAG 2.1) +- Responsive design patterns +- Component library templates +- Performance optimization guide + +## Summary + +This skill provides: +- **Accessible components** - WCAG AA by default +- **Responsive design** - Mobile-first approach +- **Design systems** - Token-based consistency +- **Modern CSS** - Flexbox, Grid, custom properties +- **Performance** - Optimized delivery +- **Best practices** - Production-ready patterns + +**Use this skill to build beautiful, accessible, performant frontends.** + +--- + +**"Good design is accessible design."** diff --git a/skills/frontend-designer/references/accessibility_checklist.md b/skills/frontend-designer/references/accessibility_checklist.md new file mode 100644 index 0000000..b84e3ea --- /dev/null +++ b/skills/frontend-designer/references/accessibility_checklist.md @@ -0,0 +1,622 @@ +# WCAG 2.1 AA Accessibility Checklist + +Comprehensive checklist for ensuring your frontend meets WCAG 2.1 Level AA compliance. + +## Perceivable + +Information and user interface components must be presentable to users in ways they can perceive. + +### 1.1 Text Alternatives + +**1.1.1 Non-text Content (Level A)** +- [ ] All images have appropriate alt text +- [ ] Decorative images use empty alt (`alt=""`) +- [ ] Complex images have detailed descriptions +- [ ] Icons have text alternatives or aria-label +- [ ] Charts/graphs have text descriptions +- [ ] CAPTCHAs have alternative forms + +```html + +Company Name + + +``` + +### 1.2 Time-based Media + +**1.2.1 Audio-only and Video-only (Level A)** +- [ ] Audio-only content has transcripts +- [ ] Video-only content has transcripts or audio description + +**1.2.2 Captions (Level A)** +- [ ] All pre-recorded videos have captions +- [ ] Captions are synchronized and accurate + +**1.2.3 Audio Description or Media Alternative (Level A)** +- [ ] Videos have audio descriptions or text alternative + +**1.2.4 Captions (Live) (Level AA)** +- [ ] Live videos have captions + +**1.2.5 Audio Description (Level AA)** +- [ ] All pre-recorded videos have audio descriptions + +```html + +``` + +### 1.3 Adaptable + +**1.3.1 Info and Relationships (Level A)** +- [ ] Semantic HTML used correctly (headings, lists, tables) +- [ ] Form labels properly associated with inputs +- [ ] Related form controls are grouped +- [ ] Visual presentation matches code structure +- [ ] ARIA roles used when needed + +```html + +
+

Main Heading

+
+

Section Heading

+

Content

+
+
+ + + + + + +
+ Contact preferences + + +
+``` + +**1.3.2 Meaningful Sequence (Level A)** +- [ ] Reading order is logical +- [ ] Tab order follows visual flow +- [ ] CSS positioning doesn't disrupt reading order + +**1.3.3 Sensory Characteristics (Level A)** +- [ ] Instructions don't rely solely on shape +- [ ] Instructions don't rely solely on size +- [ ] Instructions don't rely solely on location +- [ ] Instructions don't rely solely on sound + +```html + +

Click the blue button on the right

+ + +

Click the "Submit" button to continue

+``` + +**1.3.4 Orientation (Level AA)** +- [ ] Content works in portrait and landscape +- [ ] No orientation restrictions unless essential + +**1.3.5 Identify Input Purpose (Level AA)** +- [ ] Input fields use autocomplete attribute when appropriate + +```html + + + +``` + +### 1.4 Distinguishable + +**1.4.1 Use of Color (Level A)** +- [ ] Color not used as only visual means of conveying information +- [ ] Color not used as only way to distinguish interactive elements +- [ ] Links are distinguishable without color alone + +```css +/* ✅ Good - underline + color */ +a { + color: blue; + text-decoration: underline; +} + +/* Or use icons, borders, etc. */ +.error { + color: red; + border-left: 4px solid red; + padding-left: 12px; +} +.error::before { + content: "⚠ "; +} +``` + +**1.4.2 Audio Control (Level A)** +- [ ] Auto-playing audio can be paused +- [ ] Auto-playing audio stops after 3 seconds +- [ ] Volume controls available + +**1.4.3 Contrast (Minimum) (Level AA)** +- [ ] Normal text: 4.5:1 contrast ratio +- [ ] Large text (18pt+): 3:1 contrast ratio +- [ ] UI components: 3:1 contrast ratio +- [ ] Graphical objects: 3:1 contrast ratio + +```css +/* Check with contrast checkers */ +.text { + color: #595959; /* 7:1 on white ✅ */ + background: #FFFFFF; +} + +.button { + color: #FFFFFF; + background: #0066FF; /* 4.6:1 ✅ */ + border: 2px solid #0052CC; /* 3:1 ✅ */ +} +``` + +**1.4.4 Resize Text (Level AA)** +- [ ] Text can be resized to 200% without loss of content +- [ ] No horizontal scrolling at 200% zoom +- [ ] Use relative units (rem, em) + +```css +/* ✅ Good */ +body { + font-size: 1rem; /* Respects user preferences */ +} + +h1 { + font-size: 2.5rem; /* Scales with body */ +} + +/* ❌ Avoid */ +.text { + font-size: 14px; /* Fixed size */ +} +``` + +**1.4.5 Images of Text (Level AA)** +- [ ] Text is text, not images +- [ ] Exception: logos, essential presentations + +**1.4.10 Reflow (Level AA)** +- [ ] Content reflows at 320px viewport width +- [ ] No horizontal scrolling (except tables, diagrams) +- [ ] Responsive design implemented + +```css +/* Mobile-first responsive */ +.container { + width: 100%; + max-width: 1200px; + padding: 1rem; +} + +@media (min-width: 768px) { + .container { + padding: 2rem; + } +} +``` + +**1.4.11 Non-text Contrast (Level AA)** +- [ ] UI components: 3:1 contrast against background +- [ ] Graphical objects: 3:1 contrast +- [ ] Focus indicators: 3:1 contrast + +**1.4.12 Text Spacing (Level AA)** +- [ ] Content adapts to increased text spacing +- [ ] Line height: at least 1.5x font size +- [ ] Paragraph spacing: at least 2x font size +- [ ] Letter spacing: at least 0.12x font size +- [ ] Word spacing: at least 0.16x font size + +```css +/* Ensure content doesn't break */ +body { + line-height: 1.5; +} + +p { + margin-bottom: 2em; +} +``` + +**1.4.13 Content on Hover or Focus (Level AA)** +- [ ] Additional content (tooltips, dropdowns) is dismissible +- [ ] Hoverable content stays visible when hovering over it +- [ ] Content remains visible until dismissed or no longer relevant + +```css +/* Tooltip stays visible when hovering over it */ +.tooltip:hover .tooltip-content, +.tooltip .tooltip-content:hover { + display: block; +} +``` + +## Operable + +User interface components and navigation must be operable. + +### 2.1 Keyboard Accessible + +**2.1.1 Keyboard (Level A)** +- [ ] All functionality available via keyboard +- [ ] No keyboard traps +- [ ] Logical tab order +- [ ] Custom controls are keyboard accessible + +```html + +
+ Click me +
+``` + +**2.1.2 No Keyboard Trap (Level A)** +- [ ] Focus can move away from all components +- [ ] Instructions provided if non-standard exit method + +**2.1.4 Character Key Shortcuts (Level A)** +- [ ] Single-key shortcuts can be turned off +- [ ] Or remapped by user +- [ ] Or only active when component has focus + +### 2.2 Enough Time + +**2.2.1 Timing Adjustable (Level A)** +- [ ] Time limits can be turned off, adjusted, or extended +- [ ] User warned before time expires +- [ ] At least 20 seconds to extend + +**2.2.2 Pause, Stop, Hide (Level A)** +- [ ] Moving content can be paused +- [ ] Auto-updating content can be paused/stopped +- [ ] Blinking content can be stopped + +```html + + +``` + +### 2.3 Seizures and Physical Reactions + +**2.3.1 Three Flashes or Below Threshold (Level A)** +- [ ] No content flashes more than 3 times per second +- [ ] Or flashes are below general flash/red flash thresholds + +### 2.4 Navigable + +**2.4.1 Bypass Blocks (Level A)** +- [ ] Skip navigation link provided +- [ ] Landmark regions defined +- [ ] Headings structure content + +```html + + + +
+ +
+ +
+

Page Title

+
+``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: #000; + color: #fff; + padding: 8px; + z-index: 100; +} + +.skip-link:focus { + top: 0; +} +``` + +**2.4.2 Page Titled (Level A)** +- [ ] Page has descriptive title +- [ ] Title identifies page content + +```html +Contact Us - Company Name +``` + +**2.4.3 Focus Order (Level A)** +- [ ] Focus order is logical and intuitive +- [ ] Matches visual order +- [ ] No positive tabindex values + +```css +/* ❌ Avoid */ +.element { tabindex: 5; } + +/* ✅ Use */ +.element { tabindex: 0; } /* In natural order */ +.element { tabindex: -1; } /* Programmatic focus only */ +``` + +**2.4.4 Link Purpose (Level A)** +- [ ] Link text describes destination +- [ ] Context is clear +- [ ] Avoid "click here" or "read more" + +```html + +Click here + + +Download 2024 Annual Report (PDF, 2MB) +``` + +**2.4.5 Multiple Ways (Level AA)** +- [ ] Multiple ways to find pages (menu, search, sitemap) +- [ ] Exception: pages that are steps in a process + +**2.4.6 Headings and Labels (Level AA)** +- [ ] Headings describe content +- [ ] Labels describe purpose +- [ ] Headings and labels are clear + +**2.4.7 Focus Visible (Level AA)** +- [ ] Keyboard focus indicator is visible +- [ ] Sufficient contrast (3:1) +- [ ] Clearly indicates focused element + +```css +/* ✅ Strong focus indicator */ +*:focus-visible { + outline: 2px solid #0066FF; + outline-offset: 2px; +} + +/* Or custom focus styles */ +button:focus-visible { + box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.5); +} +``` + +### 2.5 Input Modalities + +**2.5.1 Pointer Gestures (Level A)** +- [ ] Complex gestures have single-pointer alternative +- [ ] Path-based gestures have simple alternative + +**2.5.2 Pointer Cancellation (Level A)** +- [ ] Actions triggered on up-event (not down) +- [ ] Or can be aborted/undone + +**2.5.3 Label in Name (Level A)** +- [ ] Visible label matches accessible name +- [ ] Accessible name starts with visible text + +```html + + + + + +``` + +**2.5.4 Motion Actuation (Level A)** +- [ ] Device motion triggers have UI alternative +- [ ] Can disable motion actuation + +## Understandable + +Information and user interface operation must be understandable. + +### 3.1 Readable + +**3.1.1 Language of Page (Level A)** +- [ ] Page language is identified + +```html + +``` + +**3.1.2 Language of Parts (Level AA)** +- [ ] Language changes are marked + +```html +

The French phrase c'est la vie means "that's life".

+``` + +### 3.2 Predictable + +**3.2.1 On Focus (Level A)** +- [ ] Focusing an element doesn't trigger context change +- [ ] No automatic form submission on focus + +**3.2.2 On Input (Level A)** +- [ ] Changing settings doesn't automatically cause context change +- [ ] User is warned of automatic changes + +**3.2.3 Consistent Navigation (Level AA)** +- [ ] Navigation order is consistent across pages +- [ ] Repeated navigation in same order + +**3.2.4 Consistent Identification (Level AA)** +- [ ] Components with same functionality are identified consistently +- [ ] Icons mean the same thing throughout + +### 3.3 Input Assistance + +**3.3.1 Error Identification (Level A)** +- [ ] Errors are identified in text +- [ ] Error is described to user + +```html + +Please enter a valid email address +``` + +**3.3.2 Labels or Instructions (Level A)** +- [ ] Labels provided for input +- [ ] Instructions provided when needed + +```html + + +``` + +**3.3.3 Error Suggestion (Level AA)** +- [ ] Errors suggest how to fix +- [ ] Specific, actionable feedback + +```html + + Password must contain at least one uppercase letter, + one number, and be at least 8 characters long. + +``` + +**3.3.4 Error Prevention (Level AA)** +- [ ] Legal/financial transactions are reversible +- [ ] Data is checked and confirmed before submission +- [ ] User can review and correct before submitting + +```html + +
+

Review Your Order

+ + + +
+``` + +## Robust + +Content must be robust enough to be interpreted by a wide variety of user agents, including assistive technologies. + +### 4.1 Compatible + +**4.1.1 Parsing (Level A)** *[Obsolete in WCAG 2.2]* +- [ ] Valid HTML (no duplicate IDs, proper nesting) + +**4.1.2 Name, Role, Value (Level A)** +- [ ] All UI components have accessible name +- [ ] Roles are appropriate +- [ ] States communicated to assistive tech + +```html + +
+
+Accept terms + + + + +``` + +**4.1.3 Status Messages (Level AA)** +- [ ] Status messages can be perceived by assistive tech +- [ ] Use aria-live, role="status", role="alert" + +```html + +
+ Form submitted successfully! +
+ + +
+ Error: Connection lost. Please try again. +
+ + +
+ Loading content... +
+``` + +## Testing Checklist + +### Automated Testing +- [ ] Run axe DevTools +- [ ] Run Lighthouse accessibility audit +- [ ] Run WAVE browser extension +- [ ] HTML validator (W3C) +- [ ] Color contrast checker + +### Manual Testing +- [ ] Keyboard-only navigation +- [ ] Screen reader testing (NVDA, JAWS, VoiceOver) +- [ ] Zoom to 200% (text resize) +- [ ] Test with browser zoom (page zoom) +- [ ] Test in high contrast mode +- [ ] Test with dark mode +- [ ] Test responsive breakpoints + +### Screen Reader Testing +- [ ] NVDA (Windows, free) +- [ ] JAWS (Windows, paid) +- [ ] VoiceOver (Mac/iOS, built-in) +- [ ] TalkBack (Android, built-in) +- [ ] ORCA (Linux, free) + +### Browser Testing +- [ ] Chrome + screen reader +- [ ] Firefox + screen reader +- [ ] Safari + VoiceOver +- [ ] Edge + screen reader + +## Quick Reference + +### Critical Items (Must Fix) +1. ✅ Images have alt text +2. ✅ Form inputs have labels +3. ✅ Sufficient color contrast (4.5:1 text, 3:1 UI) +4. ✅ Keyboard accessible (all functionality) +5. ✅ Focus indicators visible +6. ✅ No keyboard traps +7. ✅ Semantic HTML (headings, landmarks) +8. ✅ ARIA used correctly +9. ✅ Page has title +10. ✅ Language identified + +### Tools +- [axe DevTools](https://www.deque.com/axe/devtools/) +- [Lighthouse](https://developers.google.com/web/tools/lighthouse) +- [WAVE](https://wave.webaim.org/) +- [Color Contrast Analyzer](https://www.tpgi.com/color-contrast-checker/) +- [NVDA Screen Reader](https://www.nvaccess.org/) + +### Resources +- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/) +- [WebAIM](https://webaim.org/) +- [The A11Y Project](https://www.a11yproject.com/) +- [MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility) + +--- + +**"Accessibility is not a feature, it's a fundamental right."** diff --git a/skills/frontend-designer/references/component_library.md b/skills/frontend-designer/references/component_library.md new file mode 100644 index 0000000..0497ac8 --- /dev/null +++ b/skills/frontend-designer/references/component_library.md @@ -0,0 +1,937 @@ +# Component Library Architecture + +Complete guide to building, organizing, and maintaining a scalable component library. + +## Component Architecture Principles + +### 1. Atomic Design + +Organize components hierarchically from smallest to largest. + +**Hierarchy:** +``` +Atoms → Molecules → Organisms → Templates → Pages +``` + +**Example Structure:** +``` +components/ +├── atoms/ +│ ├── Button/ +│ ├── Input/ +│ ├── Icon/ +│ └── Label/ +├── molecules/ +│ ├── FormField/ +│ ├── SearchBar/ +│ └── Card/ +├── organisms/ +│ ├── Header/ +│ ├── LoginForm/ +│ └── ProductCard/ +├── templates/ +│ ├── DashboardLayout/ +│ └── AuthLayout/ +└── pages/ + ├── HomePage/ + └── ProfilePage/ +``` + +### 2. Component Types + +**Presentational (Dumb) Components:** +- Focus on how things look +- No state management +- Receive data via props +- Highly reusable + +```tsx +// Presentational Button +interface ButtonProps { + variant?: 'primary' | 'secondary'; + size?: 'sm' | 'md' | 'lg'; + children: React.ReactNode; + onClick?: () => void; +} + +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + children, + onClick, +}) => { + return ( + + ); +}; +``` + +**Container (Smart) Components:** +- Focus on how things work +- Manage state +- Connect to data sources +- Use presentational components + +```tsx +// Container Component +export const UserProfileContainer: React.FC = () => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchUser().then(data => { + setUser(data); + setLoading(false); + }); + }, []); + + if (loading) return ; + if (!user) return ; + + return ; +}; +``` + +### 3. Component Composition + +**Build complex components from simple ones.** + +```tsx +// Atoms +const Avatar: React.FC = ({ src, alt }) => ( + {alt} +); + +const Badge: React.FC = ({ children }) => ( + {children} +); + +// Molecule (composed of atoms) +const UserBadge: React.FC = ({ user }) => ( +
+ + {user.name} + {user.role} +
+); + +// Organism (composed of molecules) +const UserList: React.FC = ({ users }) => ( +
    + {users.map(user => ( +
  • + +
  • + ))} +
+); +``` + +## File Structure + +### Standard Component Structure + +``` +Button/ +├── Button.tsx # Main component +├── Button.test.tsx # Tests +├── Button.stories.tsx # Storybook stories +├── Button.module.css # Styles (CSS Modules) +├── Button.types.ts # TypeScript types +└── index.ts # Barrel export +``` + +### Example Files + +**Button/Button.tsx** +```tsx +import React from 'react'; +import styles from './Button.module.css'; +import { ButtonProps } from './Button.types'; + +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + disabled = false, + loading = false, + children, + onClick, + ...props +}) => { + const className = [ + styles.button, + styles[`button--${variant}`], + styles[`button--${size}`], + disabled && styles['button--disabled'], + loading && styles['button--loading'], + ].filter(Boolean).join(' '); + + return ( + + ); +}; +``` + +**Button/Button.types.ts** +```tsx +export interface ButtonProps + extends React.ButtonHTMLAttributes { + /** Visual style variant */ + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + + /** Size variant */ + size?: 'sm' | 'md' | 'lg'; + + /** Disabled state */ + disabled?: boolean; + + /** Loading state */ + loading?: boolean; + + /** Button content */ + children: React.ReactNode; + + /** Click handler */ + onClick?: (event: React.MouseEvent) => void; +} +``` + +**Button/Button.test.tsx** +```tsx +import { render, screen, fireEvent } from '@testing-library/react'; +import { Button } from './Button'; + +describe('Button', () => { + it('renders children correctly', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('handles click events', () => { + const handleClick = jest.fn(); + render(); + + fireEvent.click(screen.getByText('Click me')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('applies variant classes', () => { + render(); + const button = screen.getByText('Click me'); + expect(button).toHaveClass('button--secondary'); + }); + + it('disables interaction when loading', () => { + const handleClick = jest.fn(); + render(); + + const button = screen.getByText('Click me'); + expect(button).toBeDisabled(); + expect(button).toHaveAttribute('aria-busy', 'true'); + }); +}); +``` + +**Button/Button.stories.tsx** +```tsx +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'Components/Button', + component: Button, + argTypes: { + variant: { + control: 'select', + options: ['primary', 'secondary', 'ghost', 'danger'], + }, + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + variant: 'primary', + children: 'Primary Button', + }, +}; + +export const Secondary: Story = { + args: { + variant: 'secondary', + children: 'Secondary Button', + }, +}; + +export const Loading: Story = { + args: { + loading: true, + children: 'Loading...', + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + children: 'Disabled Button', + }, +}; +``` + +**Button/index.ts** +```tsx +export { Button } from './Button'; +export type { ButtonProps } from './Button.types'; +``` + +## Common Components + +### 1. Button Component + +```tsx +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + disabled?: boolean; + loading?: boolean; + icon?: React.ReactNode; + iconPosition?: 'left' | 'right'; + children: React.ReactNode; + onClick?: () => void; +} +``` + +### 2. Input Component + +```tsx +interface InputProps { + type?: 'text' | 'email' | 'password' | 'number' | 'tel'; + label?: string; + placeholder?: string; + value?: string; + error?: string; + helpText?: string; + required?: boolean; + disabled?: boolean; + onChange?: (value: string) => void; +} + +export const Input: React.FC = ({ + type = 'text', + label, + placeholder, + value, + error, + helpText, + required, + disabled, + onChange, +}) => { + const id = useId(); + const errorId = `${id}-error`; + const helpId = `${id}-help`; + + return ( +
+ {label && ( + + )} + + onChange?.(e.target.value)} + /> + + {error && ( + + {error} + + )} + + {helpText && !error && ( + + {helpText} + + )} +
+ ); +}; +``` + +### 3. Card Component + +```tsx +interface CardProps { + variant?: 'default' | 'outlined' | 'elevated'; + padding?: 'none' | 'sm' | 'md' | 'lg'; + clickable?: boolean; + children: React.ReactNode; + onClick?: () => void; +} + +export const Card: React.FC = ({ + variant = 'default', + padding = 'md', + clickable = false, + children, + onClick, +}) => { + const Component = clickable ? 'button' : 'div'; + + return ( + + {children} + + ); +}; +``` + +### 4. Modal Component + +```tsx +interface ModalProps { + open: boolean; + title?: string; + size?: 'sm' | 'md' | 'lg' | 'full'; + children: React.ReactNode; + onClose: () => void; +} + +export const Modal: React.FC = ({ + open, + title, + size = 'md', + children, + onClose, +}) => { + useEffect(() => { + if (open) { + // Trap focus in modal + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = ''; + }; + } + }, [open]); + + if (!open) return null; + + return createPortal( +
+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby={title ? 'modal-title' : undefined} + > + {title && ( +
+ + +
+ )} + +
{children}
+
+
, + document.body + ); +}; +``` + +### 5. Dropdown Component + +```tsx +interface DropdownProps { + trigger: React.ReactNode; + children: React.ReactNode; + align?: 'left' | 'right'; +} + +export const Dropdown: React.FC = ({ + trigger, + children, + align = 'left', +}) => { + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + return ( +
+ + + {open && ( +
+ {children} +
+ )} +
+ ); +}; +``` + +## Component Patterns + +### 1. Compound Components + +**Allow components to work together while sharing implicit state.** + +```tsx +// Context for shared state +const AccordionContext = createContext<{ + activeIndex: number | null; + setActiveIndex: (index: number | null) => void; +} | null>(null); + +// Parent component +export const Accordion: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [activeIndex, setActiveIndex] = useState(null); + + return ( + +
{children}
+
+ ); +}; + +// Child component +export const AccordionItem: React.FC<{ + index: number; + title: string; + children: React.ReactNode; +}> = ({ index, title, children }) => { + const context = useContext(AccordionContext); + if (!context) throw new Error('AccordionItem must be used within Accordion'); + + const { activeIndex, setActiveIndex } = context; + const isOpen = activeIndex === index; + + return ( +
+ + + {isOpen &&
{children}
} +
+ ); +}; + +// Usage + + Content 1 + Content 2 + +``` + +### 2. Render Props + +**Pass rendering logic as a prop.** + +```tsx +interface DataFetcherProps { + url: string; + children: (data: { + data: T | null; + loading: boolean; + error: Error | null; + }) => React.ReactNode; +} + +export function DataFetcher({ url, children }: DataFetcherProps) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetch(url) + .then(res => res.json()) + .then(setData) + .catch(setError) + .finally(() => setLoading(false)); + }, [url]); + + return <>{children({ data, loading, error })}; +} + +// Usage + url="/api/user"> + {({ data, loading, error }) => { + if (loading) return ; + if (error) return ; + if (!data) return null; + return ; + }} + +``` + +### 3. Custom Hooks Pattern + +**Extract reusable logic into custom hooks.** + +```tsx +// useToggle hook +export function useToggle(initialValue = false) { + const [value, setValue] = useState(initialValue); + + const toggle = useCallback(() => setValue(v => !v), []); + const setTrue = useCallback(() => setValue(true), []); + const setFalse = useCallback(() => setValue(false), []); + + return { value, toggle, setTrue, setFalse }; +} + +// Usage in component +export const Modal: React.FC = ({ children }) => { + const { value: isOpen, setTrue: open, setFalse: close } = useToggle(); + + return ( + <> + + {isOpen && ( +
+ {children} + +
+ )} + + ); +}; +``` + +## Styling Strategies + +### 1. CSS Modules + +```css +/* Button.module.css */ +.button { + display: inline-flex; + align-items: center; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); +} + +.button--primary { + background: var(--color-primary); + color: var(--color-white); +} + +.button--secondary { + background: transparent; + color: var(--color-primary); + border: 1px solid var(--color-primary); +} +``` + +```tsx +import styles from './Button.module.css'; + +export const Button = ({ variant }) => ( + +); +``` + +### 2. CSS-in-JS (Styled Components) + +```tsx +import styled from 'styled-components'; + +const StyledButton = styled.button<{ variant: string }>` + display: inline-flex; + align-items: center; + padding: ${({ theme }) => theme.space[3]} ${({ theme }) => theme.space[4]}; + background: ${({ theme, variant }) => + variant === 'primary' ? theme.colors.primary : 'transparent'}; + color: ${({ theme, variant }) => + variant === 'primary' ? theme.colors.white : theme.colors.primary}; +`; + +export const Button = ({ variant, children }) => ( + {children} +); +``` + +### 3. Tailwind CSS + +```tsx +import clsx from 'clsx'; + +export const Button: React.FC = ({ variant, size, children }) => { + return ( + + ); +}; +``` + +## Documentation + +### Component Documentation Template + +```tsx +/** + * Button component for triggering actions and navigation. + * + * @example + * ```tsx + * + * ``` + * + * @see {@link https://design-system.example.com/button | Design System Docs} + */ +export const Button: React.FC = ({ ... }) => { + // Implementation +}; +``` + +### Storybook Documentation + +```tsx +import type { Meta } from '@storybook/react'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'Components/Button', + component: Button, + parameters: { + docs: { + description: { + component: ` + Button component for triggering actions. + + ## Usage + + \`\`\`tsx + import { Button } from '@/components/Button'; + + + \`\`\` + + ## Accessibility + + - Keyboard accessible + - Screen reader friendly + - WCAG 2.1 AA compliant + `, + }, + }, + }, +}; +``` + +## Testing Strategies + +### Unit Tests + +```tsx +describe('Button', () => { + it('renders with correct variant', () => { + render(); + expect(screen.getByRole('button')).toHaveClass('button--primary'); + }); + + it('calls onClick when clicked', () => { + const handleClick = jest.fn(); + render(); + + fireEvent.click(screen.getByRole('button')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); +}); +``` + +### Accessibility Tests + +```tsx +import { axe, toHaveNoViolations } from 'jest-axe'; + +expect.extend(toHaveNoViolations); + +it('has no accessibility violations', async () => { + const { container } = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); +``` + +### Visual Regression Tests + +```tsx +// Using Chromatic or Percy +it('matches snapshot', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); +}); +``` + +## Performance Optimization + +### Code Splitting + +```tsx +// Lazy load heavy components +const HeavyComponent = lazy(() => import('./HeavyComponent')); + +export const App = () => ( + }> + + +); +``` + +### Memoization + +```tsx +// Memoize expensive components +export const ExpensiveComponent = memo(({ data }) => { + // Expensive rendering logic + return
{processData(data)}
; +}); + +// Memoize callbacks +const handleClick = useCallback(() => { + // Handle click +}, [dependencies]); + +// Memoize values +const expensiveValue = useMemo(() => { + return computeExpensiveValue(data); +}, [data]); +``` + +## Version Control + +### Semantic Versioning + +``` +MAJOR.MINOR.PATCH + +1.0.0 → Initial release +1.1.0 → New feature (backwards compatible) +1.1.1 → Bug fix (backwards compatible) +2.0.0 → Breaking change +``` + +### Changelog + +```markdown +# Changelog + +## [2.0.0] - 2024-01-15 + +### Breaking Changes +- Removed `type` prop from Button (use `variant` instead) + +### Added +- New `loading` state for Button +- Icon support in Button component + +### Fixed +- Button focus indicator contrast ratio + +## [1.1.0] - 2024-01-01 + +### Added +- New Input component +- Card component variants +``` + +## Resources + +- [React Component Patterns](https://kentcdodds.com/blog/compound-components-with-react-hooks) +- [Design Systems Handbook](https://www.designbetter.co/design-systems-handbook) +- [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) +- [Storybook Best Practices](https://storybook.js.org/docs/react/writing-docs/introduction) + +--- + +**"Good components are reusable, accessible, and well-documented."** diff --git a/skills/frontend-designer/references/design_tokens.md b/skills/frontend-designer/references/design_tokens.md new file mode 100644 index 0000000..f6c27f5 --- /dev/null +++ b/skills/frontend-designer/references/design_tokens.md @@ -0,0 +1,608 @@ +# Design Tokens Reference + +Complete guide to implementing and using design tokens in your design system. + +## What Are Design Tokens? + +Design tokens are the visual design atoms of your design system — specifically, they are named entities that store visual design attributes. They're used in place of hard-coded values to maintain a scalable and consistent visual system. + +## Benefits + +**Consistency** +- Single source of truth for design values +- Ensures brand consistency across platforms +- Easy to maintain and update + +**Scalability** +- Change values in one place, update everywhere +- Support multiple themes (light/dark) +- Platform-agnostic (web, iOS, Android) + +**Developer-Designer Communication** +- Shared vocabulary between teams +- Design decisions are codified +- Reduces ambiguity + +## Token Categories + +### 1. Color Tokens + +```css +:root { + /* Brand Colors */ + --color-primary: #0066FF; + --color-primary-hover: #0052CC; + --color-primary-active: #003D99; + --color-primary-subtle: #E6F0FF; + + --color-secondary: #6B7280; + --color-secondary-hover: #4B5563; + + /* Semantic Colors */ + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --color-info: #3B82F6; + + /* Neutral Palette */ + --color-white: #FFFFFF; + --color-black: #000000; + --color-gray-50: #F9FAFB; + --color-gray-100: #F3F4F6; + /* ... through gray-900 */ + + /* Surface Colors */ + --color-surface: var(--color-white); + --color-background: var(--color-white); + + /* Text Colors */ + --color-text: var(--color-gray-900); + --color-text-secondary: var(--color-gray-600); + --color-text-tertiary: var(--color-gray-400); + + /* Border Colors */ + --color-border: var(--color-gray-200); + --color-border-hover: var(--color-gray-300); + + /* Focus */ + --color-focus: var(--color-primary); +} +``` + +**Naming Convention:** +``` +--color-{category}-{variant}-{state} + +Examples: +--color-primary (base brand color) +--color-primary-hover (interactive state) +--color-success-subtle (background usage) +--color-text-secondary (content hierarchy) +``` + +### 2. Typography Tokens + +```css +:root { + /* Font Families */ + --font-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-heading: var(--font-base); + --font-mono: "SF Mono", Monaco, "Cascadia Code", monospace; + + /* Font Sizes - Fluid Typography */ + --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); + --text-sm: clamp(0.875rem, 0.825rem + 0.25vw, 1rem); + --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); + --text-lg: clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem); + --text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem); + --text-2xl: clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem); + --text-3xl: clamp(1.875rem, 1.65rem + 1.125vw, 2.25rem); + --text-4xl: clamp(2.25rem, 1.95rem + 1.5vw, 3rem); + --text-5xl: clamp(3rem, 2.55rem + 2.25vw, 3.75rem); + + /* Font Weights */ + --font-light: 300; + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* Line Heights */ + --leading-tight: 1.25; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + + /* Letter Spacing */ + --tracking-tight: -0.025em; + --tracking-normal: 0; + --tracking-wide: 0.025em; +} +``` + +**Usage:** +```css +.heading-1 { + font-family: var(--font-heading); + font-size: var(--text-4xl); + font-weight: var(--font-bold); + line-height: var(--leading-tight); + letter-spacing: var(--tracking-tight); +} + +.body-text { + font-family: var(--font-base); + font-size: var(--text-base); + font-weight: var(--font-normal); + line-height: var(--leading-normal); +} +``` + +### 3. Spacing Tokens + +```css +:root { + /* Spacing Scale (4px base) */ + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ +} +``` + +**Usage:** +```css +.card { + padding: var(--space-4); + margin-bottom: var(--space-6); + gap: var(--space-3); +} + +.section { + padding-block: var(--space-12); +} +``` + +### 4. Shadow Tokens + +```css +:root { + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* Special Shadows */ + --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + --shadow-focus: 0 0 0 3px rgba(0, 102, 255, 0.5); +} +``` + +**Usage:** +```css +.card { + box-shadow: var(--shadow-md); +} + +.card:hover { + box-shadow: var(--shadow-lg); +} + +button:focus-visible { + box-shadow: var(--shadow-focus); +} +``` + +### 5. Border Tokens + +```css +:root { + /* Border Radius */ + --radius-sm: 0.125rem; /* 2px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-full: 9999px; /* Pills/circles */ + + /* Border Widths */ + --border-1: 1px; + --border-2: 2px; + --border-4: 4px; +} +``` + +**Usage:** +```css +.button { + border-radius: var(--radius-md); + border: var(--border-1) solid var(--color-border); +} + +.avatar { + border-radius: var(--radius-full); +} +``` + +### 6. Z-Index Tokens + +```css +:root { + /* Layer Management */ + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-popover: 1060; + --z-tooltip: 1070; +} +``` + +**Usage:** +```css +.modal-backdrop { + z-index: var(--z-modal-backdrop); +} + +.modal { + z-index: var(--z-modal); +} +``` + +### 7. Animation/Transition Tokens + +```css +:root { + /* Duration */ + --duration-instant: 0ms; + --duration-fast: 150ms; + --duration-base: 250ms; + --duration-slow: 350ms; + --duration-slower: 500ms; + + /* Easing */ + --ease-linear: linear; + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); +} +``` + +**Usage:** +```css +.button { + transition: all var(--duration-base) var(--ease-out); +} + +.modal { + animation: slideIn var(--duration-slow) var(--ease-out); +} +``` + +## Semantic vs Reference Tokens + +### Reference Tokens (Raw Values) +```css +:root { + --blue-500: #0066FF; + --gray-100: #F3F4F6; + --spacing-4: 1rem; +} +``` + +### Semantic Tokens (Meaningful Names) +```css +:root { + --color-primary: var(--blue-500); + --color-surface: var(--gray-100); + --button-padding: var(--spacing-4); +} +``` + +**Always use semantic tokens in components:** +```css +/* ❌ Don't */ +.button { + background: var(--blue-500); + padding: var(--spacing-4); +} + +/* ✅ Do */ +.button { + background: var(--color-primary); + padding: var(--button-padding); +} +``` + +## Theming with Tokens + +### Light/Dark Mode + +```css +:root { + /* Light theme (default) */ + --color-surface: #FFFFFF; + --color-text: #111827; + --color-border: #E5E7EB; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-surface: #1F2937; + --color-text: #F9FAFB; + --color-border: #4B5563; + } +} + +/* Or class-based theming */ +[data-theme="dark"] { + --color-surface: #1F2937; + --color-text: #F9FAFB; + --color-border: #4B5563; +} +``` + +### Brand Theming + +```css +:root { + --color-primary: var(--brand-blue); +} + +[data-brand="tech"] { + --brand-blue: #0066FF; +} + +[data-brand="health"] { + --brand-blue: #10B981; +} + +[data-brand="finance"] { + --brand-blue: #6366F1; +} +``` + +## Component-Specific Tokens + +```css +:root { + /* Button */ + --button-padding-sm: var(--space-2) var(--space-3); + --button-padding-md: var(--space-3) var(--space-4); + --button-padding-lg: var(--space-4) var(--space-6); + --button-radius: var(--radius-md); + --button-font-weight: var(--font-medium); + + /* Input */ + --input-height-sm: 36px; + --input-height-md: 44px; + --input-height-lg: 52px; + --input-border-color: var(--color-border); + --input-focus-color: var(--color-primary); + + /* Card */ + --card-padding: var(--space-6); + --card-radius: var(--radius-lg); + --card-shadow: var(--shadow-md); +} +``` + +## Platform-Specific Tokens + +### CSS Custom Properties (Web) +```css +:root { + --color-primary: #0066FF; +} + +.button { + background: var(--color-primary); +} +``` + +### JSON (React Native, iOS, Android) +```json +{ + "color": { + "primary": "#0066FF", + "surface": "#FFFFFF", + "text": "#111827" + }, + "spacing": { + "4": 16, + "6": 24, + "8": 32 + } +} +``` + +### JavaScript/TypeScript +```typescript +export const tokens = { + color: { + primary: '#0066FF', + surface: '#FFFFFF', + text: '#111827', + }, + spacing: { + 4: '1rem', + 6: '1.5rem', + 8: '2rem', + }, +} as const; + +// Usage +const Button = styled.button` + background: ${tokens.color.primary}; + padding: ${tokens.spacing.4}; +`; +``` + +## Best Practices + +### Naming Conventions + +**Do:** +- Use descriptive, semantic names +- Follow consistent patterns +- Group related tokens + +```css +/* ✅ Good */ +--color-primary +--color-primary-hover +--color-primary-subtle +--button-padding-md +--shadow-card +``` + +**Don't:** +- Use arbitrary or cryptic names +- Mix naming conventions +- Use values in names + +```css +/* ❌ Bad */ +--blue +--primary-color-hover-state +--padding16px +--btn-pad +``` + +### Organization + +Group tokens by category: +``` +tokens/ +├── colors.css +├── typography.css +├── spacing.css +├── shadows.css +├── borders.css +├── zindex.css +└── index.css (imports all) +``` + +### Documentation + +Document token usage: +```css +/** + * Primary brand color + * Used for: primary buttons, links, focus states + * Contrast ratio: 4.6:1 (WCAG AA compliant) + */ +--color-primary: #0066FF; +``` + +### Accessibility + +Ensure proper contrast: +```css +:root { + --color-primary: #0066FF; + --color-text-on-primary: #FFFFFF; + /* Contrast ratio: 4.5:1 ✅ */ +} +``` + +### Performance + +Use CSS variables efficiently: +```css +/* ✅ Define once, use everywhere */ +:root { + --color-primary: #0066FF; +} + +/* ❌ Don't redefine in every selector */ +.button { --color-primary: #0066FF; } +.link { --color-primary: #0066FF; } +``` + +## Migration Strategy + +### Step 1: Audit Current Values +```bash +# Find all color values +grep -r "#[0-9A-Fa-f]\{6\}" src/ + +# Find all spacing values +grep -r "padding:\|margin:" src/ +``` + +### Step 2: Create Token Definitions +```css +:root { + /* Extract unique values */ + --color-primary: #0066FF; + --space-4: 1rem; +} +``` + +### Step 3: Replace Hard-Coded Values +```css +/* Before */ +.button { + background: #0066FF; + padding: 1rem; +} + +/* After */ +.button { + background: var(--color-primary); + padding: var(--space-4); +} +``` + +### Step 4: Test & Validate +- Visual regression testing +- Accessibility testing +- Cross-browser testing +- Dark mode verification + +## Tools + +**Design Token Tools:** +- Style Dictionary (transforms tokens to multiple formats) +- Theo (Salesforce's token transformer) +- Design Tokens CLI + +**Browser DevTools:** +- Chrome: Inspect computed custom properties +- Firefox: CSS Variables viewer + +**Example Script:** +```javascript +// Extract all CSS custom properties +const properties = Array.from(document.styleSheets) + .flatMap(sheet => Array.from(sheet.cssRules)) + .filter(rule => rule.style) + .flatMap(rule => Array.from(rule.style)) + .filter(prop => prop.startsWith('--')); + +console.log([...new Set(properties)]); +``` + +## Resources + +- [Design Tokens W3C Spec](https://design-tokens.github.io/community-group/format/) +- [Style Dictionary](https://amzn.github.io/style-dictionary/) +- [Design Tokens in Figma](https://www.figma.com/community/plugin/888356646278934516/Design-Tokens) + +--- + +**"Design tokens are the translation layer between design decisions and code."** diff --git a/skills/frontend-designer/references/responsive_patterns.md b/skills/frontend-designer/references/responsive_patterns.md new file mode 100644 index 0000000..a7d8f09 --- /dev/null +++ b/skills/frontend-designer/references/responsive_patterns.md @@ -0,0 +1,839 @@ +# Responsive Design Patterns + +Modern responsive design patterns and techniques for creating flexible, accessible layouts that work across all devices. + +## Mobile-First Approach + +Start with mobile design and enhance for larger screens. + +### Why Mobile-First? + +**Benefits:** +- Forces focus on essential content +- Better performance (smaller base CSS) +- Progressive enhancement mindset +- Easier to add features than remove them + +**Basic Pattern:** +```css +/* Base (mobile) styles - no media query */ +.container { + width: 100%; + padding: 1rem; +} + +/* Tablet and up */ +@media (min-width: 768px) { + .container { + padding: 2rem; + } +} + +/* Desktop and up */ +@media (min-width: 1024px) { + .container { + max-width: 1200px; + margin: 0 auto; + padding: 3rem; + } +} +``` + +## Breakpoints + +### Standard Breakpoints + +```css +:root { + /* Mobile first - these are MIN widths */ + --breakpoint-sm: 640px; /* Small tablets, large phones */ + --breakpoint-md: 768px; /* Tablets */ + --breakpoint-lg: 1024px; /* Laptops, desktops */ + --breakpoint-xl: 1280px; /* Large desktops */ + --breakpoint-2xl: 1536px; /* Extra large screens */ +} + +/* Usage */ +@media (min-width: 768px) { + /* Tablet and up */ +} + +@media (min-width: 1024px) { + /* Desktop and up */ +} +``` + +### Custom Breakpoints + +```css +/* Content-based breakpoints */ +@media (min-width: 400px) { + /* When content needs it, not arbitrary device size */ +} + +/* Prefer rem-based breakpoints for accessibility */ +@media (min-width: 48rem) { /* 768px at 16px base */ + /* Scales with user's font size preferences */ +} +``` + +### Container Queries (Modern) + +```css +/* Respond to container size, not viewport */ +.card-container { + container-type: inline-size; + container-name: card; +} + +@container card (min-width: 400px) { + .card { + display: grid; + grid-template-columns: 200px 1fr; + } +} +``` + +## Responsive Typography + +### Fluid Typography + +```css +/* Scales smoothly between min and max */ +h1 { + font-size: clamp(2rem, 5vw + 1rem, 4rem); + /* Min: 2rem (32px) */ + /* Preferred: 5vw + 1rem */ + /* Max: 4rem (64px) */ +} + +/* More examples */ +.text-sm { + font-size: clamp(0.875rem, 0.85rem + 0.125vw, 1rem); +} + +.text-base { + font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); +} + +.text-lg { + font-size: clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem); +} +``` + +### Responsive Line Height + +```css +body { + /* Tighter on mobile, looser on desktop */ + line-height: 1.5; +} + +@media (min-width: 768px) { + body { + line-height: 1.6; + } +} +``` + +## Layout Patterns + +### 1. Stack Layout + +**Everything stacks vertically on mobile, side-by-side on larger screens.** + +```css +.stack { + display: flex; + flex-direction: column; + gap: 1rem; +} + +@media (min-width: 768px) { + .stack { + flex-direction: row; + } +} +``` + +### 2. Sidebar Layout + +**Sidebar stacks on mobile, side-by-side on desktop.** + +```css +.sidebar-layout { + display: grid; + gap: 2rem; +} + +@media (min-width: 768px) { + .sidebar-layout { + grid-template-columns: 250px 1fr; + } +} + +/* Flexible sidebar */ +@media (min-width: 768px) { + .sidebar-layout--flexible { + grid-template-columns: minmax(200px, 300px) 1fr; + } +} +``` + +### 3. Grid Layout + +**Responsive column count.** + +```css +.grid { + display: grid; + gap: 1rem; + + /* Mobile: 1 column */ + grid-template-columns: 1fr; +} + +@media (min-width: 640px) { + .grid { + /* Tablet: 2 columns */ + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .grid { + /* Desktop: 3-4 columns */ + grid-template-columns: repeat(4, 1fr); + } +} + +/* Auto-responsive grid (no media queries!) */ +.grid-auto { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + /* Creates as many columns as fit, minimum 250px each */ +} +``` + +### 4. Holy Grail Layout + +**Classic three-column layout that adapts to mobile.** + +```css +.holy-grail { + display: grid; + gap: 1rem; + min-height: 100vh; + + /* Mobile: stack everything */ + grid-template: + "header" auto + "main" 1fr + "sidebar1" auto + "sidebar2" auto + "footer" auto + / 1fr; +} + +@media (min-width: 768px) { + .holy-grail { + /* Desktop: traditional layout */ + grid-template: + "header header header" auto + "sidebar1 main sidebar2" 1fr + "footer footer footer" auto + / 200px 1fr 200px; + } +} + +.header { grid-area: header; } +.sidebar-1 { grid-area: sidebar1; } +.main { grid-area: main; } +.sidebar-2 { grid-area: sidebar2; } +.footer { grid-area: footer; } +``` + +### 5. Card Layout + +**Responsive cards that adapt their internal layout.** + +```css +.card { + display: grid; + gap: 1rem; + + /* Mobile: stack image and content */ + grid-template: + "image" auto + "content" 1fr + / 1fr; +} + +@media (min-width: 640px) { + .card { + /* Tablet+: side-by-side */ + grid-template: + "image content" 1fr + / 200px 1fr; + } +} + +.card__image { grid-area: image; } +.card__content { grid-area: content; } +``` + +### 6. Switcher Pattern + +**Switch between horizontal and vertical based on available space.** + +```css +.switcher { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.switcher > * { + /* Grow to fill, but switch to vertical when < 400px */ + flex-grow: 1; + flex-basis: calc((400px - 100%) * 999); + /* Clever calc that breaks at threshold */ +} +``` + +### 7. Pancake Stack + +**Header, main, footer layout that adapts height.** + +```css +.pancake { + display: grid; + grid-template-rows: auto 1fr auto; + min-height: 100vh; +} + +.header { /* auto height */ } +.main { /* fills available space */ } +.footer { /* auto height */ } +``` + +## Responsive Images + +### 1. Flexible Images + +```css +img { + max-width: 100%; + height: auto; + display: block; +} +``` + +### 2. Art Direction (Different Images per Breakpoint) + +```html + + + + Description + +``` + +### 3. Resolution Switching (Same Image, Different Sizes) + +```html +Description +``` + +### 4. Background Images + +```css +.hero { + background-image: url('small.jpg'); + background-size: cover; + background-position: center; +} + +@media (min-width: 768px) { + .hero { + background-image: url('medium.jpg'); + } +} + +@media (min-width: 1024px) { + .hero { + background-image: url('large.jpg'); + } +} + +@media (min-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) { + .hero { + background-image: url('large@2x.jpg'); + } +} +``` + +### 5. Aspect Ratio + +```css +/* Modern aspect ratio */ +.image-container { + aspect-ratio: 16 / 9; + overflow: hidden; +} + +.image-container img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Fallback for older browsers */ +.image-container-fallback { + position: relative; + padding-bottom: 56.25%; /* 16:9 aspect ratio */ +} + +.image-container-fallback img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} +``` + +## Responsive Navigation + +### 1. Mobile Menu (Hamburger) + +```html + +``` + +```css +.nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; +} + +/* Mobile: hidden menu */ +.nav__menu { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background: white; + flex-direction: column; + padding: 1rem; +} + +.nav__menu[aria-expanded="true"] { + display: flex; +} + +.nav__toggle { + display: block; +} + +/* Desktop: visible menu */ +@media (min-width: 768px) { + .nav__menu { + display: flex; + position: static; + flex-direction: row; + gap: 2rem; + } + + .nav__toggle { + display: none; + } +} +``` + +### 2. Priority+ Navigation + +**Show important items, collapse others into "More" menu.** + +```css +.priority-nav { + display: flex; + gap: 1rem; +} + +.priority-nav__item { + flex-shrink: 0; /* Don't shrink */ +} + +.priority-nav__more { + margin-left: auto; /* Push to end */ +} + +/* Hide items that don't fit */ +@media (max-width: 768px) { + .priority-nav__item:nth-child(n+4) { + display: none; /* Hide items 4+ */ + } +} +``` + +## Responsive Tables + +### 1. Horizontal Scroll + +```css +.table-container { + overflow-x: auto; + -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */ +} + +table { + min-width: 600px; + width: 100%; +} +``` + +### 2. Stacked Table (Card View) + +```html + + + + + + + + + + + + + + + +
NameEmailRole
John Doejohn@example.comDeveloper
+``` + +```css +/* Desktop: normal table */ +@media (min-width: 768px) { + .responsive-table { + display: table; + } +} + +/* Mobile: stacked cards */ +@media (max-width: 767px) { + .responsive-table, + .responsive-table thead, + .responsive-table tbody, + .responsive-table tr, + .responsive-table th, + .responsive-table td { + display: block; + } + + .responsive-table thead { + display: none; /* Hide table header */ + } + + .responsive-table tr { + margin-bottom: 1rem; + border: 1px solid #ddd; + border-radius: 0.5rem; + padding: 1rem; + } + + .responsive-table td { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + border-bottom: 1px solid #eee; + } + + .responsive-table td:last-child { + border-bottom: none; + } + + .responsive-table td::before { + content: attr(data-label); + font-weight: bold; + margin-right: 1rem; + } +} +``` + +## Responsive Forms + +### 1. Single Column to Multi-Column + +```css +.form { + display: grid; + gap: 1rem; + + /* Mobile: single column */ + grid-template-columns: 1fr; +} + +@media (min-width: 768px) { + .form { + /* Desktop: two columns */ + grid-template-columns: repeat(2, 1fr); + } + + .form__field--full { + /* Some fields span both columns */ + grid-column: 1 / -1; + } +} +``` + +### 2. Touch-Friendly Inputs + +```css +input, +button, +select, +textarea { + /* Minimum 44x44px touch target */ + min-height: 44px; + padding: 0.75rem 1rem; + font-size: 1rem; /* Prevents zoom on iOS */ +} + +@media (min-width: 768px) { + input, + button, + select, + textarea { + /* Can be smaller on desktop */ + min-height: 40px; + font-size: 0.875rem; + } +} +``` + +## Responsive Utilities + +### Show/Hide at Breakpoints + +```css +/* Hide on mobile */ +.hidden-mobile { + display: none; +} + +@media (min-width: 768px) { + .hidden-mobile { + display: block; + } +} + +/* Show only on mobile */ +.visible-mobile { + display: block; +} + +@media (min-width: 768px) { + .visible-mobile { + display: none; + } +} + +/* Hide on desktop */ +@media (min-width: 1024px) { + .hidden-desktop { + display: none; + } +} +``` + +### Responsive Spacing + +```css +.section { + /* Mobile: smaller padding */ + padding: 2rem 1rem; +} + +@media (min-width: 768px) { + .section { + /* Tablet: medium padding */ + padding: 4rem 2rem; + } +} + +@media (min-width: 1024px) { + .section { + /* Desktop: larger padding */ + padding: 6rem 3rem; + } +} + +/* Or use fluid spacing */ +.section-fluid { + padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 3rem); +} +``` + +## Testing Responsive Design + +### Browser DevTools + +```javascript +// Common viewport sizes to test +const viewports = [ + { width: 375, height: 667, name: 'iPhone SE' }, + { width: 390, height: 844, name: 'iPhone 12 Pro' }, + { width: 428, height: 926, name: 'iPhone 14 Pro Max' }, + { width: 768, height: 1024, name: 'iPad' }, + { width: 1024, height: 768, name: 'iPad Landscape' }, + { width: 1280, height: 720, name: 'Desktop' }, + { width: 1920, height: 1080, name: 'Full HD' }, +]; +``` + +### Responsive Testing Checklist + +- [ ] Test all breakpoints +- [ ] Test between breakpoints (awkward sizes) +- [ ] Test portrait and landscape +- [ ] Test zoom levels (100%, 200%, 400%) +- [ ] Test with real devices when possible +- [ ] Test touch interactions on mobile +- [ ] Test with different font sizes +- [ ] Test with slow network (images, fonts) + +## Performance Considerations + +### Lazy Loading + +```html + +Description + + +Hero image +``` + +### Conditional Loading + +```javascript +// Load component only on larger screens +if (window.matchMedia('(min-width: 768px)').matches) { + import('./DesktopComponent.js').then(module => { + // Initialize desktop component + }); +} +``` + +### Font Loading + +```css +@font-face { + font-family: 'CustomFont'; + src: url('font.woff2') format('woff2'); + font-display: swap; /* Show fallback while loading */ +} +``` + +## Modern CSS Features + +### 1. CSS Grid Auto-Fill + +```css +.grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + /* Automatically creates columns, minimum 250px */ +} +``` + +### 2. Flexbox Gap + +```css +.flex-container { + display: flex; + flex-wrap: wrap; + gap: 1rem; /* No more margin hacks! */ +} +``` + +### 3. Container Queries + +```css +.card { + container-type: inline-size; +} + +@container (min-width: 400px) { + .card__title { + font-size: 1.5rem; + } +} +``` + +### 4. Aspect Ratio + +```css +.video-container { + aspect-ratio: 16 / 9; +} +``` + +### 5. Logical Properties + +```css +/* Better for RTL/LTR support */ +.element { + margin-block-start: 1rem; /* margin-top */ + margin-block-end: 1rem; /* margin-bottom */ + margin-inline-start: 1rem; /* margin-left in LTR, margin-right in RTL */ + margin-inline-end: 1rem; /* margin-right in LTR, margin-left in RTL */ +} +``` + +## Resources + +- [MDN: Responsive Design](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design) +- [This is Responsive](https://bradfrost.github.io/this-is-responsive/) +- [Responsive Design Patterns](https://responsivedesign.is/patterns/) +- [CSS-Tricks: Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) +- [CSS-Tricks: Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) + +--- + +**"The best design is the one that works everywhere, for everyone."** diff --git a/skills/frontend-designer/scripts/audit_accessibility.sh b/skills/frontend-designer/scripts/audit_accessibility.sh new file mode 100755 index 0000000..652b354 --- /dev/null +++ b/skills/frontend-designer/scripts/audit_accessibility.sh @@ -0,0 +1,467 @@ +#!/bin/bash + +# Frontend Designer - Accessibility Audit +# Comprehensive WCAG 2.1 AA compliance checker + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +# Counters +PASS_COUNT=0 +FAIL_COUNT=0 +WARNING_COUNT=0 + +# Helper functions +print_success() { + echo -e "${GREEN}✓ PASS${NC} $1" + ((PASS_COUNT++)) +} + +print_error() { + echo -e "${RED}✗ FAIL${NC} $1" + ((FAIL_COUNT++)) +} + +print_warning() { + echo -e "${YELLOW}⚠ WARN${NC} $1" + ((WARNING_COUNT++)) +} + +print_info() { + echo -e "${BLUE}ℹ INFO${NC} $1" +} + +print_section() { + echo "" + echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${MAGENTA}$1${NC}" + echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +# Banner +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ Frontend Designer - Accessibility Audit ║" +echo "║ WCAG 2.1 AA Compliance ║" +echo "║ ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" + +# Get target +if [ -z "$1" ]; then + print_info "Usage: $0 " + print_info "Example: $0 index.html" + print_info "Example: $0 src/components/" + exit 1 +fi + +TARGET="$1" + +# Check if target exists +if [ ! -e "$TARGET" ]; then + print_error "Target not found: $TARGET" + exit 1 +fi + +# Section 1: HTML Structure +print_section "1. HTML STRUCTURE & SEMANTICS" + +check_html_lang() { + if grep -q ']*\slang=' "$1"; then + print_success "HTML lang attribute present" + else + print_error "Missing lang attribute on " + echo " Fix: " + fi +} + +check_page_title() { + if grep -q '' "$1"; then + print_success "Page title present" + else + print_error "Missing <title> element" + echo " Fix: Add <title>Page Title" + fi +} + +check_main_landmark() { + if grep -q ' landmark found" + echo " Tip: Use
for primary content" + fi +} + +check_heading_structure() { + if grep -q ' for main page heading" + fi +} + +check_semantic_html() { + local semantic_tags=("nav" "header" "footer" "article" "section" "aside") + local found=false + + for tag in "${semantic_tags[@]}"; do + if grep -q "<$tag" "$1"; then + found=true + break + fi + done + + if [ "$found" = true ]; then + print_success "Semantic HTML elements used" + else + print_warning "Consider using semantic HTML (nav, header, footer, etc.)" + fi +} + +# Section 2: Images & Media +print_section "2. IMAGES & MEDIA" + +check_img_alt() { + local img_count=$(grep -o '" + fi + fi +} + +# Section 3: Forms +print_section "3. FORMS & INPUTS" + +check_form_labels() { + local input_count=$(grep -o ' or aria-label" + fi + + # Check for required fields + if grep -q 'required' "$1"; then + if grep -q 'aria-required="true"' "$1"; then + print_success "Required fields marked with aria-required" + else + print_warning "Consider adding aria-required=\"true\" to required fields" + fi + fi + fi +} + +check_error_messages() { + if grep -q 'aria-describedby' "$1"; then + print_success "Error messages linked with aria-describedby" + elif grep -q 'error' "$1"; then + print_warning "Error handling present, verify aria-describedby usage" + fi +} + +# Section 4: Interactive Elements +print_section "4. INTERACTIVE ELEMENTS" + +check_button_text() { + # Check for empty buttons + if grep -q ']*>' "$1"; then + print_error "Empty button found" + echo " Fix: Add text or aria-label to button" + else + print_success "No empty buttons found" + fi +} + +check_link_text() { + # Check for generic link text + if grep -qi 'click here\|read more\|more' "$1"; then + print_warning "Generic link text found (click here, read more)" + echo " Tip: Use descriptive link text" + else + print_success "No generic link text detected" + fi +} + +check_skip_links() { + if grep -q 'skip.*content\|skip.*navigation' "$1"; then + print_success "Skip navigation link present" + else + print_warning "No skip navigation link found" + echo " Tip: Add skip link for keyboard users" + echo " Skip to content" + fi +} + +# Section 5: ARIA +print_section "5. ARIA ATTRIBUTES" + +check_aria_roles() { + if grep -q 'role=' "$1"; then + print_success "ARIA roles found" + + # Check for button roles on non-button elements + if grep -q ']*role="button"' "$1" || grep -q ']*role="button"' "$1"; then + if grep -q 'tabindex=' "$1"; then + print_success "Custom buttons have tabindex" + else + print_error "role=\"button\" without tabindex" + echo " Fix: Add tabindex=\"0\" to custom buttons" + fi + fi + fi +} + +check_aria_labels() { + if grep -q 'aria-label=' "$1"; then + print_success "ARIA labels used for context" + fi + + # Check for redundant aria-label + if grep -q ']*aria-label.*>[^<]*' "$1"; then + print_warning "Possible redundant aria-label on button with text" + echo " Tip: Use aria-label when button has no visible text" + fi +} + +check_aria_live() { + if grep -q 'aria-live' "$1"; then + print_success "Live regions defined" + fi +} + +# Section 6: Keyboard Navigation +print_section "6. KEYBOARD NAVIGATION" + +check_tabindex() { + # Check for positive tabindex + if grep -q 'tabindex="[1-9]' "$1"; then + print_error "Positive tabindex values found" + echo " Fix: Use tabindex=\"0\" or \"-1\" only" + echo " Positive values disrupt natural tab order" + else + print_success "No positive tabindex values (good)" + fi +} + +check_focus_indicators() { + # This would need CSS analysis + print_info "Manual check: Verify focus indicators are visible" + echo " Test: Tab through page, ensure focus is visible" + echo " CSS: :focus-visible { outline: 2px solid; }" +} + +# Section 7: Color & Contrast +print_section "7. COLOR & CONTRAST" + +print_info "Manual checks required for color/contrast:" +echo "" +echo " Required contrast ratios (WCAG AA):" +echo " ✓ Normal text: 4.5:1" +echo " ✓ Large text (18pt+): 3:1" +echo " ✓ UI components: 3:1" +echo "" +echo " Tools for testing:" +echo " - Chrome DevTools (Lighthouse)" +echo " - WebAIM Contrast Checker" +echo " - axe DevTools" +echo "" + +check_color_only() { + if grep -qi 'color:.*red\|color:.*green' "$1"; then + print_warning "Color usage detected - ensure not used as only indicator" + echo " Tip: Don't rely on color alone (add icons, text, patterns)" + fi +} + +# Section 8: Responsive & Mobile +print_section "8. RESPONSIVE & MOBILE" + +check_viewport() { + if grep -q 'viewport' "$1"; then + print_success "Viewport meta tag present" + else + print_error "Missing viewport meta tag" + echo " Fix: " + fi +} + +check_touch_targets() { + print_info "Manual check: Touch targets minimum 44x44px" + echo " Test: Verify buttons/links meet minimum size" + echo " CSS: min-height: 44px; min-width: 44px;" +} + +# Section 9: Content +print_section "9. CONTENT & READABILITY" + +check_lang_changes() { + if grep -q '\slang=' "$1"; then + local lang_count=$(grep -o '\slang=' "$1" | wc -l) + if [ "$lang_count" -gt 1 ]; then + print_success "Language changes marked ($lang_count instances)" + fi + fi +} + +check_abbreviations() { + if grep -q ' element" + fi +} + +# Section 10: Motion & Animation +print_section "10. MOTION & ANIMATIONS" + +print_info "Manual check: Respect prefers-reduced-motion" +echo "" +echo " CSS:" +echo " @media (prefers-reduced-motion: reduce) {" +echo " * { animation: none !important; }" +echo " }" +echo "" + +# Run checks on files +if [ -f "$TARGET" ]; then + # Single file + check_html_lang "$TARGET" + check_page_title "$TARGET" + check_main_landmark "$TARGET" + check_heading_structure "$TARGET" + check_semantic_html "$TARGET" + check_img_alt "$TARGET" + check_video_captions "$TARGET" + check_form_labels "$TARGET" + check_error_messages "$TARGET" + check_button_text "$TARGET" + check_link_text "$TARGET" + check_skip_links "$TARGET" + check_aria_roles "$TARGET" + check_aria_labels "$TARGET" + check_aria_live "$TARGET" + check_tabindex "$TARGET" + check_focus_indicators "$TARGET" + check_color_only "$TARGET" + check_viewport "$TARGET" + check_touch_targets "$TARGET" + check_lang_changes "$TARGET" + check_abbreviations "$TARGET" +elif [ -d "$TARGET" ]; then + # Directory - find HTML files + html_files=$(find "$TARGET" -name "*.html" -o -name "*.htm") + + if [ -z "$html_files" ]; then + print_error "No HTML files found in $TARGET" + exit 1 + fi + + for file in $html_files; do + print_info "Checking: $file" + check_html_lang "$file" + check_page_title "$file" + check_main_landmark "$file" + check_heading_structure "$file" + check_img_alt "$file" + check_form_labels "$file" + echo "" + done +fi + +# Summary +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ Audit Summary ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" +echo -e "${GREEN}✓ Passed: $PASS_COUNT${NC}" +echo -e "${RED}✗ Failed: $FAIL_COUNT${NC}" +echo -e "${YELLOW}⚠ Warnings: $WARNING_COUNT${NC}" +echo "" + +# Calculate score +TOTAL=$((PASS_COUNT + FAIL_COUNT)) +if [ $TOTAL -gt 0 ]; then + SCORE=$(( (PASS_COUNT * 100) / TOTAL )) + echo "Score: $SCORE%" + echo "" + + if [ $SCORE -ge 90 ]; then + echo -e "${GREEN}Excellent! Your site is highly accessible.${NC}" + elif [ $SCORE -ge 70 ]; then + echo -e "${YELLOW}Good, but needs improvements.${NC}" + else + echo -e "${RED}Needs significant accessibility improvements.${NC}" + fi +fi + +echo "" +print_info "Additional Testing Recommended:" +echo " 1. Screen reader testing (NVDA, JAWS, VoiceOver)" +echo " 2. Keyboard-only navigation" +echo " 3. Automated tools (axe, Lighthouse, WAVE)" +echo " 4. Color contrast analyzer" +echo " 5. Real user testing with assistive technologies" +echo "" +print_info "Resources:" +echo " - WCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/" +echo " - WebAIM: https://webaim.org/" +echo " - a11y Project: https://www.a11yproject.com/" +echo "" + +# Exit code based on failures +if [ $FAIL_COUNT -gt 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/skills/frontend-designer/scripts/generate_component.sh b/skills/frontend-designer/scripts/generate_component.sh new file mode 100755 index 0000000..caacf2a --- /dev/null +++ b/skills/frontend-designer/scripts/generate_component.sh @@ -0,0 +1,619 @@ +#!/bin/bash + +# Frontend Designer - Component Generator +# Generates accessible, responsive components with design tokens + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +prompt_input() { + local prompt="$1" + local var_name="$2" + local required="${3:-false}" + + while true; do + echo -e "${BLUE}${prompt}${NC}" + read -r input + + if [ -z "$input" ] && [ "$required" = true ]; then + print_error "This field is required." + continue + fi + + eval "$var_name='$input'" + break + done +} + +prompt_select() { + local prompt="$1" + local var_name="$2" + shift 2 + local options=("$@") + + echo -e "${BLUE}${prompt}${NC}" + PS3="Select (1-${#options[@]}): " + select opt in "${options[@]}"; do + if [ -n "$opt" ]; then + eval "$var_name='$opt'" + break + else + print_error "Invalid selection. Try again." + fi + done +} + +# Banner +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ Frontend Designer - Component Generator ║" +echo "║ ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" + +# Step 1: Component Type +print_info "Step 1/6: Component Type" +prompt_select "What type of component?" COMPONENT_TYPE \ + "Button" \ + "Input" \ + "Card" \ + "Modal" \ + "Dropdown" \ + "Navigation" \ + "Form" \ + "List" \ + "Custom" + +# Step 2: Component Name +print_info "Step 2/6: Component Name" +prompt_input "Component name (PascalCase, e.g., UserProfile):" COMPONENT_NAME true + +# Step 3: Framework +print_info "Step 3/6: Framework" +prompt_select "Which framework?" FRAMEWORK \ + "React" \ + "Vue" \ + "Vanilla JS" \ + "Web Components" + +# Step 4: Features +print_info "Step 4/6: Features (comma-separated)" +echo -e "${BLUE}Select features to include (e.g., variants,loading,disabled):${NC}" +echo " - variants (different visual styles)" +echo " - sizes (sm, md, lg)" +echo " - loading (loading state)" +echo " - disabled (disabled state)" +echo " - icons (icon support)" +echo " - responsive (responsive behavior)" +read -r FEATURES + +# Step 5: Accessibility +print_info "Step 5/6: Accessibility Requirements" +prompt_select "WCAG compliance level?" A11Y_LEVEL \ + "AA (recommended)" \ + "AAA (strict)" \ + "Basic" + +# Step 6: Output Directory +print_info "Step 6/6: Output Location" +prompt_input "Output directory (default: ./components):" OUTPUT_DIR +OUTPUT_DIR=${OUTPUT_DIR:-"./components"} + +# Create output directory +mkdir -p "$OUTPUT_DIR" + +# Generate based on framework +case $FRAMEWORK in + "React") + generate_react_component + ;; + "Vue") + generate_vue_component + ;; + "Vanilla JS") + generate_vanilla_component + ;; + "Web Components") + generate_web_component + ;; +esac + +# Generate component based on selected framework +generate_react_component() { + local file_path="$OUTPUT_DIR/$COMPONENT_NAME.tsx" + + cat > "$file_path" << 'EOF' +import React from 'react'; +import './COMPONENT_NAME.css'; + +interface COMPONENT_NAMEProps { + children?: React.ReactNode; + className?: string; + VARIANT_PROP + SIZE_PROP + DISABLED_PROP + LOADING_PROP + onClick?: () => void; +} + +export const COMPONENT_NAME: React.FC = ({ + children, + className = '', + VARIANT_DEFAULT + SIZE_DEFAULT + DISABLED_DEFAULT + LOADING_DEFAULT + onClick, +}) => { + const baseClass = 'COMPONENT_CLASS'; + const variantClass = `${baseClass}--${variant}`; + const sizeClass = `${baseClass}--${size}`; + const classes = `${baseClass} ${variantClass} ${sizeClass} ${className}`; + + return ( + + LOADING_SPINNER + {children} + + ); +}; +EOF + + # Replace placeholders based on features + sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$file_path" + sed -i "s/COMPONENT_CLASS/$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\L\1/g' | sed 's/^-//')/g" "$file_path" + + if [[ $FEATURES == *"variants"* ]]; then + sed -i "s/VARIANT_PROP/variant?: 'primary' | 'secondary' | 'ghost';/" "$file_path" + sed -i "s/VARIANT_DEFAULT/variant = 'primary',/" "$file_path" + else + sed -i "/VARIANT_PROP/d" "$file_path" + sed -i "/VARIANT_DEFAULT/d" "$file_path" + fi + + if [[ $FEATURES == *"sizes"* ]]; then + sed -i "s/SIZE_PROP/size?: 'sm' | 'md' | 'lg';/" "$file_path" + sed -i "s/SIZE_DEFAULT/size = 'md',/" "$file_path" + else + sed -i "/SIZE_PROP/d" "$file_path" + sed -i "/SIZE_DEFAULT/d" "$file_path" + fi + + if [[ $FEATURES == *"disabled"* ]]; then + sed -i "s/DISABLED_PROP/disabled?: boolean;/" "$file_path" + sed -i "s/DISABLED_DEFAULT/disabled = false,/" "$file_path" + else + sed -i "/DISABLED_PROP/d" "$file_path" + sed -i "/DISABLED_DEFAULT/d" "$file_path" + fi + + if [[ $FEATURES == *"loading"* ]]; then + sed -i "s/LOADING_PROP/loading?: boolean;/" "$file_path" + sed -i "s/LOADING_DEFAULT/loading = false,/" "$file_path" + sed -i "s|LOADING_SPINNER|{loading \&\& }|" "$file_path" + else + sed -i "/LOADING_PROP/d" "$file_path" + sed -i "/LOADING_DEFAULT/d" "$file_path" + sed -i "/LOADING_SPINNER/d" "$file_path" + fi + + # Determine element type + case $COMPONENT_TYPE in + "Button") + sed -i "s/COMPONENT_ELEMENT/button/" "$file_path" + sed -i "s/ARIA_ATTRIBUTES//" "$file_path" + ;; + "Input") + sed -i "s/COMPONENT_ELEMENT/input/" "$file_path" + sed -i "s/ARIA_ATTRIBUTES/aria-label=\"\" aria-describedby=\"\"/" "$file_path" + ;; + "Card") + sed -i "s/COMPONENT_ELEMENT/div/" "$file_path" + sed -i "s/ARIA_ATTRIBUTES/role=\"article\"/" "$file_path" + ;; + *) + sed -i "s/COMPONENT_ELEMENT/div/" "$file_path" + sed -i "s/ARIA_ATTRIBUTES//" "$file_path" + ;; + esac + + print_success "Created React component: $file_path" + generate_css + generate_test_file +} + +generate_vue_component() { + local file_path="$OUTPUT_DIR/$COMPONENT_NAME.vue" + + cat > "$file_path" << 'EOF' + + + + + +EOF + + sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$file_path" + sed -i "s/COMPONENT_CLASS/$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\L\1/g' | sed 's/^-//')/g" "$file_path" + + case $COMPONENT_TYPE in + "Button") + sed -i "s/COMPONENT_ELEMENT/button/" "$file_path" + ;; + "Input") + sed -i "s/COMPONENT_ELEMENT/input/" "$file_path" + ;; + *) + sed -i "s/COMPONENT_ELEMENT/div/" "$file_path" + ;; + esac + + print_success "Created Vue component: $file_path" + generate_css +} + +generate_css() { + local css_file="$OUTPUT_DIR/$COMPONENT_NAME.css" + local class_name=$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\L\1/g' | sed 's/^-//') + + cat > "$css_file" << EOF +/* $COMPONENT_NAME Component Styles */ + +.$class_name { + /* Design Tokens */ + --component-bg: var(--color-surface); + --component-text: var(--color-text); + --component-border: var(--color-border); + --component-radius: var(--radius-md); + --component-shadow: var(--shadow-sm); + + /* Base Styles */ + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + + padding: var(--space-3) var(--space-4); + + background-color: var(--component-bg); + color: var(--component-text); + border: 1px solid var(--component-border); + border-radius: var(--component-radius); + + font-family: var(--font-base); + font-size: var(--text-base); + font-weight: 500; + line-height: 1.5; + + cursor: pointer; + user-select: none; + + transition: all 0.2s ease; + + /* Accessibility */ + min-height: 44px; /* WCAG touch target */ + min-width: 44px; +} + +/* Variants */ +.$class_name--primary { + --component-bg: var(--color-primary); + --component-text: var(--color-white); + --component-border: var(--color-primary); +} + +.$class_name--primary:hover:not(:disabled) { + --component-bg: var(--color-primary-hover); + --component-border: var(--color-primary-hover); + box-shadow: var(--shadow-md); +} + +.$class_name--secondary { + --component-bg: transparent; + --component-text: var(--color-primary); + --component-border: var(--color-primary); +} + +.$class_name--secondary:hover:not(:disabled) { + --component-bg: var(--color-primary-subtle); +} + +.$class_name--ghost { + --component-bg: transparent; + --component-text: var(--color-text); + --component-border: transparent; +} + +.$class_name--ghost:hover:not(:disabled) { + --component-bg: var(--color-surface-hover); +} + +/* Sizes */ +.$class_name--sm { + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + min-height: 36px; +} + +.$class_name--md { + padding: var(--space-3) var(--space-4); + font-size: var(--text-base); + min-height: 44px; +} + +.$class_name--lg { + padding: var(--space-4) var(--space-6); + font-size: var(--text-lg); + min-height: 52px; +} + +/* States */ +.$class_name:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.$class_name:active:not(:disabled) { + transform: scale(0.98); +} + +.$class_name:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.$class_name[aria-busy="true"] { + cursor: wait; +} + +/* Loading Spinner */ +.spinner { + display: inline-block; + width: 1em; + height: 1em; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Responsive */ +@media (max-width: 768px) { + .$class_name { + width: 100%; + } +} + +/* Dark Mode */ +@media (prefers-color-scheme: dark) { + .$class_name { + --component-bg: var(--color-surface-dark); + --component-text: var(--color-text-dark); + --component-border: var(--color-border-dark); + } +} + +/* High Contrast Mode */ +@media (prefers-contrast: high) { + .$class_name { + border-width: 2px; + } + + .$class_name:focus-visible { + outline-width: 3px; + } +} + +/* Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + .$class_name, + .spinner { + animation: none; + transition: none; + } +} +EOF + + print_success "Created CSS file: $css_file" +} + +generate_test_file() { + if [ "$FRAMEWORK" != "React" ]; then + return + fi + + local test_file="$OUTPUT_DIR/$COMPONENT_NAME.test.tsx" + + cat > "$test_file" << 'EOF' +import { render, screen, fireEvent } from '@testing-library/react'; +import { COMPONENT_NAME } from './COMPONENT_NAME'; + +describe('COMPONENT_NAME', () => { + it('renders children correctly', () => { + render(Click me); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('handles click events', () => { + const handleClick = jest.fn(); + render(Click me); + + fireEvent.click(screen.getByText('Click me')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('renders with different variants', () => { + const { rerender } = render(Primary); + expect(screen.getByText('Primary')).toHaveClass('COMPONENT_CLASS--primary'); + + rerender(Secondary); + expect(screen.getByText('Secondary')).toHaveClass('COMPONENT_CLASS--secondary'); + }); + + it('renders with different sizes', () => { + const { rerender } = render(Small); + expect(screen.getByText('Small')).toHaveClass('COMPONENT_CLASS--sm'); + + rerender(Large); + expect(screen.getByText('Large')).toHaveClass('COMPONENT_CLASS--lg'); + }); + + it('disables interaction when disabled', () => { + const handleClick = jest.fn(); + render(Disabled); + + const element = screen.getByText('Disabled'); + expect(element).toBeDisabled(); + + fireEvent.click(element); + expect(handleClick).not.toHaveBeenCalled(); + }); + + it('shows loading state', () => { + render(Loading); + + const element = screen.getByText('Loading'); + expect(element).toHaveAttribute('aria-busy', 'true'); + expect(element).toBeDisabled(); + }); + + it('is keyboard accessible', () => { + const handleClick = jest.fn(); + render(Accessible); + + const element = screen.getByText('Accessible'); + element.focus(); + expect(element).toHaveFocus(); + }); + + it('has proper ARIA attributes', () => { + render(ARIA Test); + const element = screen.getByText('ARIA Test'); + expect(element).toHaveAttribute('aria-busy', 'true'); + }); +}); +EOF + + sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$test_file" + sed -i "s/COMPONENT_CLASS/$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\L\1/g' | sed 's/^-//')/g" "$test_file" + + print_success "Created test file: $test_file" +} + +# Summary +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ Generation Complete ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" +print_success "Component: $COMPONENT_NAME" +print_success "Type: $COMPONENT_TYPE" +print_success "Framework: $FRAMEWORK" +print_success "Location: $OUTPUT_DIR" +echo "" +print_info "Files created:" +case $FRAMEWORK in + "React") + echo " - $COMPONENT_NAME.tsx (component)" + echo " - $COMPONENT_NAME.css (styles)" + echo " - $COMPONENT_NAME.test.tsx (tests)" + ;; + "Vue") + echo " - $COMPONENT_NAME.vue (component)" + echo " - $COMPONENT_NAME.css (styles)" + ;; + *) + echo " - $COMPONENT_NAME.js (component)" + echo " - $COMPONENT_NAME.css (styles)" + ;; +esac +echo "" +print_info "Next steps:" +echo " 1. Review generated files" +echo " 2. Customize component logic" +echo " 3. Add to your component library" +echo " 4. Run tests (npm test)" +echo " 5. Test accessibility (npm run a11y)" +echo "" diff --git a/skills/frontend-designer/scripts/setup_design_system.sh b/skills/frontend-designer/scripts/setup_design_system.sh new file mode 100755 index 0000000..689327a --- /dev/null +++ b/skills/frontend-designer/scripts/setup_design_system.sh @@ -0,0 +1,570 @@ +#!/bin/bash + +# Frontend Designer - Design System Setup +# Initialize a complete design system with tokens, components, and documentation + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +prompt_input() { + local prompt="$1" + local var_name="$2" + local default="${3:-}" + + echo -e "${BLUE}${prompt}${NC}" + if [ -n "$default" ]; then + echo -e "${YELLOW}(default: $default)${NC}" + fi + read -r input + + if [ -z "$input" ]; then + input="$default" + fi + + eval "$var_name='$input'" +} + +prompt_confirm() { + local prompt="$1" + echo -e "${BLUE}${prompt} (y/n):${NC}" + read -r response + [[ "$response" =~ ^[Yy]$ ]] +} + +# Banner +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ Frontend Designer - Design System Setup ║" +echo "║ ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" + +# Configuration +print_info "Step 1/5: Project Configuration" +prompt_input "Design system name:" DS_NAME "design-system" +prompt_input "Primary brand color (hex):" PRIMARY_COLOR "#0066FF" +prompt_input "Base font size (px):" BASE_FONT_SIZE "16" +prompt_input "Output directory:" OUTPUT_DIR "./design-system" + +# Create directory structure +print_info "Step 2/5: Creating Directory Structure" +mkdir -p "$OUTPUT_DIR"/{tokens,components,utilities,docs,examples} +print_success "Created directory structure" + +# Generate design tokens +print_info "Step 3/5: Generating Design Tokens" +generate_design_tokens + +# Generate base components +print_info "Step 4/5: Generating Base Components" +if prompt_confirm "Generate base component library?"; then + generate_base_components +fi + +# Generate documentation +print_info "Step 5/5: Generating Documentation" +generate_documentation + +# Summary +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ Setup Complete! ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" +print_success "Design system created: $OUTPUT_DIR" +echo "" +print_info "Next steps:" +echo " 1. Review tokens in $OUTPUT_DIR/tokens/" +echo " 2. Customize brand colors and typography" +echo " 3. Import tokens in your app" +echo " 4. Use components from $OUTPUT_DIR/components/" +echo " 5. Read documentation in $OUTPUT_DIR/docs/" +echo "" + +generate_design_tokens() { + # Color tokens + cat > "$OUTPUT_DIR/tokens/colors.css" << EOF +/** + * Color Tokens + * Generated by Frontend Designer + */ + +:root { + /* Brand Colors */ + --color-primary: $PRIMARY_COLOR; + --color-primary-hover: color-mix(in srgb, var(--color-primary) 85%, black); + --color-primary-active: color-mix(in srgb, var(--color-primary) 75%, black); + --color-primary-subtle: color-mix(in srgb, var(--color-primary) 10%, white); + + --color-secondary: #6B7280; + --color-secondary-hover: #4B5563; + --color-secondary-subtle: #F3F4F6; + + /* Semantic Colors */ + --color-success: #10B981; + --color-success-hover: #059669; + --color-success-subtle: #D1FAE5; + + --color-warning: #F59E0B; + --color-warning-hover: #D97706; + --color-warning-subtle: #FEF3C7; + + --color-error: #EF4444; + --color-error-hover: #DC2626; + --color-error-subtle: #FEE2E2; + + --color-info: #3B82F6; + --color-info-hover: #2563EB; + --color-info-subtle: #DBEAFE; + + /* Neutral Colors */ + --color-white: #FFFFFF; + --color-black: #000000; + + --color-gray-50: #F9FAFB; + --color-gray-100: #F3F4F6; + --color-gray-200: #E5E7EB; + --color-gray-300: #D1D5DB; + --color-gray-400: #9CA3AF; + --color-gray-500: #6B7280; + --color-gray-600: #4B5563; + --color-gray-700: #374151; + --color-gray-800: #1F2937; + --color-gray-900: #111827; + + /* Surface Colors */ + --color-surface: var(--color-white); + --color-surface-hover: var(--color-gray-50); + --color-surface-subtle: var(--color-gray-100); + + --color-background: var(--color-white); + --color-background-alt: var(--color-gray-50); + + /* Text Colors */ + --color-text: var(--color-gray-900); + --color-text-secondary: var(--color-gray-600); + --color-text-tertiary: var(--color-gray-400); + --color-text-inverse: var(--color-white); + + /* Border Colors */ + --color-border: var(--color-gray-200); + --color-border-hover: var(--color-gray-300); + --color-border-focus: var(--color-primary); + + /* Focus Ring */ + --color-focus: var(--color-primary); + --color-focus-ring: color-mix(in srgb, var(--color-primary) 50%, transparent); +} + +/* Dark Mode */ +@media (prefers-color-scheme: dark) { + :root { + --color-surface: var(--color-gray-800); + --color-surface-hover: var(--color-gray-700); + --color-surface-subtle: var(--color-gray-900); + + --color-background: var(--color-gray-900); + --color-background-alt: var(--color-gray-800); + + --color-text: var(--color-gray-50); + --color-text-secondary: var(--color-gray-300); + --color-text-tertiary: var(--color-gray-500); + + --color-border: var(--color-gray-700); + --color-border-hover: var(--color-gray-600); + } +} +EOF + + # Typography tokens + cat > "$OUTPUT_DIR/tokens/typography.css" << EOF +/** + * Typography Tokens + * Generated by Frontend Designer + */ + +:root { + /* Font Families */ + --font-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-heading: var(--font-base); + --font-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace; + + /* Font Sizes - Fluid Typography */ + --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); + --text-sm: clamp(0.875rem, 0.825rem + 0.25vw, 1rem); + --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); + --text-lg: clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem); + --text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem); + --text-2xl: clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem); + --text-3xl: clamp(1.875rem, 1.65rem + 1.125vw, 2.25rem); + --text-4xl: clamp(2.25rem, 1.95rem + 1.5vw, 3rem); + --text-5xl: clamp(3rem, 2.55rem + 2.25vw, 3.75rem); + + /* Font Weights */ + --font-light: 300; + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + --font-extrabold: 800; + + /* Line Heights */ + --leading-none: 1; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + + /* Letter Spacing */ + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + --tracking-widest: 0.1em; +} +EOF + + # Spacing tokens + cat > "$OUTPUT_DIR/tokens/spacing.css" << EOF +/** + * Spacing Tokens + * Generated by Frontend Designer + */ + +:root { + /* Base spacing scale */ + --space-0: 0; + --space-px: 1px; + --space-0-5: 0.125rem; /* 2px */ + --space-1: 0.25rem; /* 4px */ + --space-1-5: 0.375rem; /* 6px */ + --space-2: 0.5rem; /* 8px */ + --space-2-5: 0.625rem; /* 10px */ + --space-3: 0.75rem; /* 12px */ + --space-3-5: 0.875rem; /* 14px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-7: 1.75rem; /* 28px */ + --space-8: 2rem; /* 32px */ + --space-9: 2.25rem; /* 36px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-14: 3.5rem; /* 56px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + --space-32: 8rem; /* 128px */ + + /* Container widths */ + --container-xs: 20rem; /* 320px */ + --container-sm: 24rem; /* 384px */ + --container-md: 28rem; /* 448px */ + --container-lg: 32rem; /* 512px */ + --container-xl: 36rem; /* 576px */ + --container-2xl: 42rem; /* 672px */ + --container-3xl: 48rem; /* 768px */ + --container-4xl: 56rem; /* 896px */ + --container-5xl: 64rem; /* 1024px */ + --container-6xl: 72rem; /* 1152px */ + --container-7xl: 80rem; /* 1280px */ +} +EOF + + # Shadow tokens + cat > "$OUTPUT_DIR/tokens/shadows.css" << EOF +/** + * Shadow Tokens + * Generated by Frontend Designer + */ + +:root { + /* Box Shadows */ + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* Inner Shadow */ + --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + + /* Focus Shadow */ + --shadow-focus: 0 0 0 3px var(--color-focus-ring); +} + +/* Dark Mode Shadows */ +@media (prefers-color-scheme: dark) { + :root { + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.4); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.5), 0 1px 2px 0 rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.6); + } +} +EOF + + # Border radius tokens + cat > "$OUTPUT_DIR/tokens/borders.css" << EOF +/** + * Border Tokens + * Generated by Frontend Designer + */ + +:root { + /* Border Radius */ + --radius-none: 0; + --radius-sm: 0.125rem; /* 2px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-3xl: 1.5rem; /* 24px */ + --radius-full: 9999px; + + /* Border Widths */ + --border-0: 0; + --border-1: 1px; + --border-2: 2px; + --border-4: 4px; + --border-8: 8px; +} +EOF + + # Z-index tokens + cat > "$OUTPUT_DIR/tokens/zindex.css" << EOF +/** + * Z-Index Tokens + * Generated by Frontend Designer + */ + +:root { + --z-0: 0; + --z-10: 10; + --z-20: 20; + --z-30: 30; + --z-40: 40; + --z-50: 50; + + /* Semantic z-index */ + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-popover: 1060; + --z-tooltip: 1070; +} +EOF + + # Main tokens file + cat > "$OUTPUT_DIR/tokens/index.css" << EOF +/** + * Design Tokens - $DS_NAME + * Generated by Frontend Designer + * + * Import this file in your app to use all design tokens + */ + +@import './colors.css'; +@import './typography.css'; +@import './spacing.css'; +@import './shadows.css'; +@import './borders.css'; +@import './zindex.css'; + +/* Base Reset */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +html { + font-size: ${BASE_FONT_SIZE}px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: var(--font-base); + font-size: var(--text-base); + line-height: var(--leading-normal); + color: var(--color-text); + background-color: var(--color-background); +} + +/* Accessibility */ +:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +/* Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +EOF + + print_success "Generated design tokens" +} + +generate_base_components() { + # This would generate base components + # For now, just create placeholder + cat > "$OUTPUT_DIR/components/README.md" << EOF +# Components + +Base component library for $DS_NAME. + +## Usage + +Import components from this directory: + +\`\`\`tsx +import { Button } from './components/Button'; +import { Card } from './components/Card'; +\`\`\` + +## Available Components + +- Button (multiple variants and sizes) +- Card (container component) +- Input (form input with validation) +- Modal (accessible dialog) +- Dropdown (accessible select) + +Generate components using: + +\`\`\`bash +./scripts/generate_component.sh +\`\`\` +EOF + + print_success "Created components directory" +} + +generate_documentation() { + cat > "$OUTPUT_DIR/docs/README.md" << EOF +# $DS_NAME Documentation + +Complete design system documentation and guidelines. + +## Getting Started + +1. **Install design tokens** + +\`\`\`css +@import 'design-system/tokens/index.css'; +\`\`\` + +2. **Use design tokens** + +\`\`\`css +.my-component { + color: var(--color-primary); + padding: var(--space-4); + border-radius: var(--radius-md); +} +\`\`\` + +3. **Import components** + +\`\`\`tsx +import { Button } from 'design-system/components'; +\`\`\` + +## Design Tokens + +### Colors + +- **Brand**: \`--color-primary\`, \`--color-secondary\` +- **Semantic**: \`--color-success\`, \`--color-warning\`, \`--color-error\` +- **Neutral**: \`--color-gray-*\` (50-900) + +### Typography + +- **Font families**: \`--font-base\`, \`--font-heading\`, \`--font-mono\` +- **Sizes**: \`--text-xs\` through \`--text-5xl\` +- **Weights**: \`--font-light\` through \`--font-extrabold\` + +### Spacing + +- **Scale**: \`--space-0\` through \`--space-32\` +- **Containers**: \`--container-xs\` through \`--container-7xl\` + +### Shadows + +- **Elevation**: \`--shadow-xs\` through \`--shadow-2xl\` +- **Focus**: \`--shadow-focus\` + +### Border Radius + +- **Sizes**: \`--radius-sm\` through \`--radius-3xl\` +- **Full**: \`--radius-full\` (pills/circles) + +## Accessibility + +This design system follows WCAG 2.1 AA guidelines: + +- ✅ Color contrast ratios: 4.5:1 for text, 3:1 for UI +- ✅ Touch targets: 44x44px minimum +- ✅ Keyboard navigation support +- ✅ Focus indicators +- ✅ Reduced motion support +- ✅ Dark mode support +- ✅ High contrast mode support + +## Browser Support + +- Chrome (last 2 versions) +- Firefox (last 2 versions) +- Safari (last 2 versions) +- Edge (last 2 versions) + +## Contributing + +See CONTRIBUTING.md for contribution guidelines. +EOF + + print_success "Created documentation" +}