This commit includes multiple new components and improvements to the UI essentials library: ## New Components Added: - **Accordion**: Expandable/collapsible content panels with full accessibility - **Alert**: Notification and messaging component with variants and dismiss functionality - **Popover**: Floating content containers with positioning and trigger options - **Timeline**: Chronological event display with customizable styling - **Tree View**: Hierarchical data display with expand/collapse functionality - **Toast**: Notification component (previously committed, includes style refinements) ## Component Features: - Full TypeScript implementation with Angular 19+ patterns - Comprehensive SCSS styling using semantic design tokens exclusively - Multiple size variants (sm, md, lg) and color variants (primary, success, warning, danger, info) - Accessibility support with ARIA attributes and keyboard navigation - Responsive design with mobile-first approach - Interactive demos showcasing all component features - Integration with existing design system and routing ## Demo Applications: - Created comprehensive demo components for each new component - Interactive examples with live code demonstrations - Integrated into main demo application routing and navigation ## Documentation: - Added COMPONENT_CREATION_TEMPLATE.md with detailed guidelines - Comprehensive component creation patterns and best practices - Design token usage guidelines and validation rules ## Code Quality: - Follows established Angular and SCSS conventions - Uses only verified semantic design tokens - Maintains consistency with existing component architecture - Comprehensive error handling and edge case management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
884 lines
30 KiB
Markdown
884 lines
30 KiB
Markdown
# Component Creation Template
|
|
|
|
## ⚠️ CRITICAL RESTRICTIONS ⚠️
|
|
|
|
### DO NOT MODIFY EXISTING COMPONENTS
|
|
- **NEVER** alter Angular or SCSS code of already built components
|
|
- **NEVER** change existing component files unless explicitly instructed
|
|
- **ONLY** create NEW components as specified in the task
|
|
- **ONLY** update integration files (routes, menu, public API) to add references to new components
|
|
- If asked to modify an existing component, REQUEST CLARIFICATION before proceeding
|
|
- Existing components are PRODUCTION CODE and must remain untouched
|
|
|
|
### Protected Files and Directories
|
|
The following should NEVER be modified without explicit permission:
|
|
- Any existing component files in `@projects/ui-essentials/src/lib/components/`
|
|
- Any existing demo files in `@projects/demo-ui-essentials/src/app/demos/`
|
|
- Only ADD new entries to:
|
|
- `demos.routes.ts` (add new cases, don't modify existing)
|
|
- `dashboard.component.ts` (add new menu items, don't modify existing)
|
|
- `public-api.ts` (add new exports, don't modify existing)
|
|
|
|
---
|
|
|
|
## Component Creation Task Template
|
|
|
|
### 1. Component Selection
|
|
|
|
- Alert/Notification
|
|
|
|
|
|
### 2. Pre-Development Checklist
|
|
- [ ] Check if similar component exists in codebase
|
|
- [ ] Review existing components in same category for patterns
|
|
- [ ] Identify required design tokens from semantic layer
|
|
- [ ] Plan component API (inputs, outputs, content projection)
|
|
- [ ] **CONFIRM: This is a NEW component, not a modification of an existing one**
|
|
|
|
### 3. Implementation Steps
|
|
|
|
#### Step 1: Create Component Structure
|
|
**Location:** `@projects/ui-essentials/src/lib/components/[category]/[component-name]/`
|
|
|
|
Create these NEW files (do not overwrite existing):
|
|
- `[component-name].component.ts`
|
|
- `[component-name].component.scss`
|
|
- `index.ts` (barrel export)
|
|
|
|
#### Step 2: SCSS Implementation
|
|
Follow the **Design Token Guidelines** below to create component styles.
|
|
|
|
#### Step 3: Angular Component Implementation
|
|
Follow the **Angular Component Guidelines** below.
|
|
|
|
#### Step 4: Create Demo Component
|
|
**Location:** `@projects/demo-ui-essentials/src/app/demos/[component-name]-demo/`
|
|
- Create NEW demo component showing all variants, sizes, and states
|
|
- Include interactive examples and code snippets
|
|
- Follow existing demo patterns
|
|
|
|
#### Step 5: Integration Updates (ADDITIVE ONLY)
|
|
|
|
##### A. Update Routes (ADD ONLY - DO NOT MODIFY EXISTING)
|
|
**File:** `@projects/demo-ui-essentials/src/app/demos/demos.routes.ts`
|
|
```typescript
|
|
// Add import - DO NOT MODIFY EXISTING IMPORTS
|
|
import { [ComponentName]DemoComponent } from './[component-name]-demo/[component-name]-demo.component';
|
|
|
|
// Add to template switch case - DO NOT MODIFY EXISTING CASES
|
|
@case ("[component-name]") {
|
|
<ui-[component-name]-demo></ui-[component-name]-demo>
|
|
}
|
|
|
|
// Add to imports array - DO NOT REMOVE OR MODIFY EXISTING ENTRIES
|
|
imports: [...existing, [ComponentName]DemoComponent]
|
|
```
|
|
|
|
##### B. Update Dashboard Menu (ADD ONLY - DO NOT MODIFY EXISTING)
|
|
**File:** `@projects/demo-ui-essentials/src/app/features/dashboard/dashboard.component.ts`
|
|
```typescript
|
|
// Import appropriate FontAwesome icon - DO NOT MODIFY EXISTING IMPORTS
|
|
import { faIconName } from '@fortawesome/free-solid-svg-icons';
|
|
|
|
// Add in ngOnInit() - DO NOT MODIFY EXISTING MENU ITEMS
|
|
this.addMenuItem("[component-id]", "[Component Label]", this.faIconName);
|
|
```
|
|
|
|
##### C. Update Public API (ADD ONLY - DO NOT MODIFY EXISTING)
|
|
**File:** `@projects/ui-essentials/src/public-api.ts`
|
|
```typescript
|
|
// Add export in appropriate section - DO NOT MODIFY EXISTING EXPORTS
|
|
export * from './lib/components/[category]/[component-name]';
|
|
```
|
|
|
|
### ⚠️ INTEGRATION WARNING ⚠️
|
|
When updating integration files:
|
|
- **ONLY ADD** new entries
|
|
- **NEVER DELETE** existing entries
|
|
- **NEVER MODIFY** existing entries
|
|
- **PRESERVE** all existing formatting and structure
|
|
- **TEST** that existing components still work after changes
|
|
|
|
---
|
|
|
|
## Design Token Guidelines ⚠️ CRITICAL: ONLY USE EXISTING TOKENS ⚠️
|
|
|
|
### MANDATORY: Import and Use Semantic Tokens
|
|
```scss
|
|
@use '@projects/shared-ui/src/styles/semantic' as *;
|
|
```
|
|
|
|
### 🚨 TOKEN VALIDATION RULES 🚨
|
|
- ❌ **NEVER** invent or guess token names
|
|
- ❌ **NEVER** use tokens not listed below
|
|
- ❌ **NEVER** hardcode values (px, rem, #hex colors, etc.)
|
|
- ✅ **ALWAYS** use semantic tokens for ALL values
|
|
- ✅ **ALWAYS** check this reference before using any token
|
|
- ✅ **ASK FOR GUIDANCE** if needed token doesn't exist
|
|
|
|
### Available Token Categories (VERIFIED ACTUAL TOKENS):
|
|
|
|
## 1. **COLORS** (Single Values - Use Directly)
|
|
```scss
|
|
// Text Colors
|
|
$semantic-color-text-primary // Primary text
|
|
$semantic-color-text-secondary // Secondary text
|
|
$semantic-color-text-tertiary // Tertiary text
|
|
$semantic-color-text-disabled // Disabled text
|
|
$semantic-color-text-inverse // Inverse text
|
|
|
|
// Surface Colors
|
|
$semantic-color-surface-primary // Primary surface
|
|
$semantic-color-surface-secondary // Secondary surface
|
|
$semantic-color-surface-elevated // Elevated surface
|
|
$semantic-color-surface // Base surface
|
|
$semantic-color-surface-container // Container surface
|
|
|
|
// Border Colors
|
|
$semantic-color-border-primary // Primary border
|
|
$semantic-color-border-secondary // Secondary border
|
|
$semantic-color-border-subtle // Subtle border
|
|
$semantic-color-border-focus // Focus border
|
|
$semantic-color-border-error // Error border
|
|
|
|
// Brand Colors
|
|
$semantic-color-primary // Primary brand
|
|
$semantic-color-secondary // Secondary brand
|
|
$semantic-color-on-primary // Text on primary
|
|
$semantic-color-on-secondary // Text on secondary
|
|
|
|
// Feedback Colors
|
|
$semantic-color-success // Success state
|
|
$semantic-color-warning // Warning state
|
|
$semantic-color-danger // Danger/error state
|
|
$semantic-color-info // Info state
|
|
$semantic-color-on-success // Text on success
|
|
$semantic-color-on-warning // Text on warning
|
|
$semantic-color-on-danger // Text on danger
|
|
$semantic-color-on-info // Text on info
|
|
|
|
// Focus and Interactive
|
|
$semantic-color-focus // Focus indicator
|
|
$semantic-color-interactive-primary // Interactive primary
|
|
$semantic-color-backdrop // Backdrop/overlay
|
|
```
|
|
|
|
## 2. **TYPOGRAPHY** (Maps and Single Values)
|
|
|
|
### Typography Maps (Use with map-get())
|
|
```scss
|
|
// Headings (Maps - Extract individual properties)
|
|
$semantic-typography-heading-h1 // h1 heading map
|
|
$semantic-typography-heading-h2 // h2 heading map
|
|
$semantic-typography-heading-h3 // h3 heading map
|
|
$semantic-typography-heading-h4 // h4 heading map
|
|
$semantic-typography-heading-h5 // h5 heading map
|
|
|
|
// Body Text (Maps)
|
|
$semantic-typography-body-large // Large body text map
|
|
$semantic-typography-body-medium // Medium body text map
|
|
$semantic-typography-body-small // Small body text map
|
|
|
|
// UI Components (Maps)
|
|
$semantic-typography-button-large // Large button text map
|
|
$semantic-typography-button-medium // Medium button text map
|
|
$semantic-typography-button-small // Small button text map
|
|
$semantic-typography-label // Form label map
|
|
$semantic-typography-input // Input text map
|
|
$semantic-typography-caption // Caption text map
|
|
|
|
// Usage Example for Maps:
|
|
font-family: map-get($semantic-typography-body-medium, font-family);
|
|
font-size: map-get($semantic-typography-body-medium, font-size);
|
|
font-weight: map-get($semantic-typography-body-medium, font-weight);
|
|
line-height: map-get($semantic-typography-body-medium, line-height);
|
|
```
|
|
|
|
### Typography Single Values (Use Directly)
|
|
```scss
|
|
// Font Weights
|
|
$semantic-typography-font-weight-normal
|
|
$semantic-typography-font-weight-medium
|
|
$semantic-typography-font-weight-semibold
|
|
$semantic-typography-font-weight-bold
|
|
|
|
// Font Sizes
|
|
$semantic-typography-font-size-xs
|
|
$semantic-typography-font-size-sm
|
|
$semantic-typography-font-size-md
|
|
$semantic-typography-font-size-lg
|
|
$semantic-typography-font-size-xl
|
|
$semantic-typography-font-size-2xl
|
|
$semantic-typography-font-size-3xl
|
|
|
|
// Line Heights
|
|
$semantic-typography-line-height-tight
|
|
$semantic-typography-line-height-normal
|
|
$semantic-typography-line-height-relaxed
|
|
|
|
// Font Families
|
|
$semantic-typography-font-family-sans
|
|
$semantic-typography-font-family-serif
|
|
$semantic-typography-font-family-mono
|
|
```
|
|
|
|
## 3. **SPACING** (Single Values - Use Directly)
|
|
```scss
|
|
// Component Spacing
|
|
$semantic-spacing-component-xs // Extra small component spacing
|
|
$semantic-spacing-component-sm // Small component spacing
|
|
$semantic-spacing-component-md // Medium component spacing
|
|
$semantic-spacing-component-lg // Large component spacing
|
|
$semantic-spacing-component-xl // Extra large component spacing
|
|
|
|
// Layout Spacing
|
|
$semantic-spacing-layout-section-xs // Extra small section spacing
|
|
$semantic-spacing-layout-section-sm // Small section spacing
|
|
$semantic-spacing-layout-section-md // Medium section spacing
|
|
$semantic-spacing-layout-section-lg // Large section spacing
|
|
$semantic-spacing-layout-section-xl // Extra large section spacing
|
|
|
|
// Content Spacing
|
|
$semantic-spacing-content-paragraph // Paragraph spacing
|
|
$semantic-spacing-content-heading // Heading spacing
|
|
$semantic-spacing-content-list-item // List item spacing
|
|
$semantic-spacing-content-line-tight // Tight line spacing
|
|
|
|
// Interactive Spacing
|
|
$semantic-spacing-interactive-button-padding-x // Button horizontal padding
|
|
$semantic-spacing-interactive-button-padding-y // Button vertical padding
|
|
$semantic-spacing-interactive-input-padding-x // Input horizontal padding
|
|
$semantic-spacing-interactive-input-padding-y // Input vertical padding
|
|
|
|
// Grid and Stack Spacing
|
|
$semantic-spacing-grid-gap-sm // Small grid gap
|
|
$semantic-spacing-grid-gap-md // Medium grid gap
|
|
$semantic-spacing-grid-gap-lg // Large grid gap
|
|
$semantic-spacing-stack-sm // Small stack spacing
|
|
$semantic-spacing-stack-md // Medium stack spacing
|
|
$semantic-spacing-stack-lg // Large stack spacing
|
|
|
|
// Form Spacing
|
|
$semantic-spacing-form-field-gap // Form field gap
|
|
$semantic-spacing-form-group-gap // Form group gap
|
|
```
|
|
|
|
## 4. **BORDERS** (Single Values - Use Directly)
|
|
```scss
|
|
// Border Widths
|
|
$semantic-border-width-1 // 1px border
|
|
$semantic-border-width-2 // 2px border
|
|
$semantic-border-card-width // Standard card border width
|
|
|
|
// Border Radius
|
|
$semantic-border-radius-sm // Small radius
|
|
$semantic-border-radius-md // Medium radius
|
|
$semantic-border-radius-lg // Large radius
|
|
$semantic-border-radius-xl // Extra large radius
|
|
$semantic-border-radius-full // Full/circle radius
|
|
|
|
// Component Borders
|
|
$semantic-border-button-radius // Button border radius
|
|
$semantic-border-input-radius // Input border radius
|
|
$semantic-border-card-radius // Card border radius
|
|
```
|
|
|
|
## 5. **SHADOWS** (Single Values - Use Directly)
|
|
```scss
|
|
// Elevation Shadows
|
|
$semantic-shadow-elevation-0 // No shadow
|
|
$semantic-shadow-elevation-1 // Level 1 elevation
|
|
$semantic-shadow-elevation-2 // Level 2 elevation
|
|
$semantic-shadow-elevation-3 // Level 3 elevation
|
|
$semantic-shadow-elevation-4 // Level 4 elevation
|
|
$semantic-shadow-elevation-5 // Level 5 elevation
|
|
|
|
// Component Shadows
|
|
$semantic-shadow-card-rest // Card default shadow
|
|
$semantic-shadow-card-hover // Card hover shadow
|
|
$semantic-shadow-button-rest // Button default shadow
|
|
$semantic-shadow-button-hover // Button hover shadow
|
|
$semantic-shadow-dropdown // Dropdown shadow
|
|
$semantic-shadow-modal // Modal shadow
|
|
```
|
|
|
|
## 6. **MOTION** (Single Values and Maps)
|
|
|
|
### Motion Single Values (Use Directly)
|
|
```scss
|
|
// Duration
|
|
$semantic-motion-duration-fast // Fast duration
|
|
$semantic-motion-duration-normal // Normal duration
|
|
$semantic-motion-duration-slow // Slow duration
|
|
|
|
// Easing
|
|
$semantic-motion-easing-ease // Standard easing
|
|
$semantic-motion-easing-ease-in-out // Ease in-out
|
|
$semantic-motion-easing-spring // Spring easing
|
|
```
|
|
|
|
## 7. **SIZING** (Single Values - Use Directly)
|
|
```scss
|
|
// Button Heights
|
|
$semantic-sizing-button-height-sm // Small button height
|
|
$semantic-sizing-button-height-md // Medium button height
|
|
$semantic-sizing-button-height-lg // Large button height
|
|
|
|
// Input Heights
|
|
$semantic-sizing-input-height-sm // Small input height
|
|
$semantic-sizing-input-height-md // Medium input height
|
|
$semantic-sizing-input-height-lg // Large input height
|
|
|
|
// Icon Sizes
|
|
$semantic-sizing-icon-inline // Inline icon size
|
|
$semantic-sizing-icon-button // Button icon size
|
|
$semantic-sizing-icon-navigation // Navigation icon size
|
|
|
|
// Touch Targets
|
|
$semantic-sizing-touch-minimum // Minimum touch target
|
|
$semantic-sizing-touch-target // Standard touch target
|
|
```
|
|
|
|
## 8. **Z-INDEX** (Single Values - Use Directly)
|
|
```scss
|
|
$semantic-z-index-dropdown // Dropdown layer
|
|
$semantic-z-index-modal // Modal layer
|
|
$semantic-z-index-tooltip // Tooltip layer
|
|
$semantic-z-index-overlay // Overlay layer
|
|
```
|
|
|
|
## 9. **OPACITY** (Single Values - Use Directly)
|
|
```scss
|
|
$semantic-opacity-disabled // Disabled opacity
|
|
$semantic-opacity-hover // Hover opacity
|
|
$semantic-opacity-backdrop // Backdrop opacity
|
|
$semantic-opacity-subtle // Subtle opacity
|
|
```
|
|
|
|
### Strict Rules:
|
|
- ❌ **NEVER** hardcode values (px, rem, #hex colors, etc.)
|
|
- ❌ **NEVER** use base tokens directly (from `/base/` directory)
|
|
- ❌ **NEVER** use magic numbers
|
|
- ✅ **ALWAYS** use semantic tokens for ALL values
|
|
- ✅ **ALWAYS** follow BEM naming convention for classes
|
|
- ✅ **ALWAYS** use SCSS nesting appropriately (max 3 levels)
|
|
- ✅ **ALWAYS** include responsive breakpoints using `$semantic-breakpoint-*` tokens
|
|
- ✅ **ALWAYS** maintain consistency with existing components
|
|
|
|
### Component SCSS Template (CORRECTED)
|
|
```scss
|
|
@use '@projects/shared-ui/src/styles/semantic' as *;
|
|
|
|
.ui-[component-name] {
|
|
// Core Structure
|
|
display: flex;
|
|
position: relative;
|
|
|
|
// Layout & Spacing - ONLY USE ACTUAL TOKENS
|
|
padding: $semantic-spacing-component-md;
|
|
margin: $semantic-spacing-layout-section-sm;
|
|
|
|
// Visual Design - ONLY USE ACTUAL TOKENS
|
|
background: $semantic-color-surface-primary;
|
|
border: $semantic-border-card-width solid $semantic-color-border-primary;
|
|
border-radius: $semantic-border-card-radius;
|
|
box-shadow: $semantic-shadow-elevation-1;
|
|
|
|
// Typography - USE MAP-GET FOR MAPS, SINGLE VALUES DIRECTLY
|
|
font-family: map-get($semantic-typography-body-medium, font-family);
|
|
font-size: map-get($semantic-typography-body-medium, font-size);
|
|
font-weight: map-get($semantic-typography-body-medium, font-weight);
|
|
line-height: map-get($semantic-typography-body-medium, line-height);
|
|
color: $semantic-color-text-primary;
|
|
|
|
// Transitions - ONLY USE ACTUAL TOKENS
|
|
transition: all $semantic-motion-duration-fast $semantic-motion-easing-ease;
|
|
|
|
// Sizing Variants - ONLY USE ACTUAL TOKENS
|
|
&--sm {
|
|
min-height: $semantic-sizing-button-height-sm;
|
|
padding: $semantic-spacing-component-xs;
|
|
font-family: map-get($semantic-typography-body-small, font-family);
|
|
font-size: map-get($semantic-typography-body-small, font-size);
|
|
font-weight: map-get($semantic-typography-body-small, font-weight);
|
|
line-height: map-get($semantic-typography-body-small, line-height);
|
|
}
|
|
|
|
&--md {
|
|
min-height: $semantic-sizing-button-height-md;
|
|
padding: $semantic-spacing-component-sm;
|
|
// Typography already set above for medium
|
|
}
|
|
|
|
&--lg {
|
|
min-height: $semantic-sizing-button-height-lg;
|
|
padding: $semantic-spacing-component-lg;
|
|
font-family: map-get($semantic-typography-body-large, font-family);
|
|
font-size: map-get($semantic-typography-body-large, font-size);
|
|
font-weight: map-get($semantic-typography-body-large, font-weight);
|
|
line-height: map-get($semantic-typography-body-large, line-height);
|
|
}
|
|
|
|
// Color Variants - ONLY USE ACTUAL TOKENS
|
|
&--primary {
|
|
background: $semantic-color-primary;
|
|
color: $semantic-color-on-primary;
|
|
border-color: $semantic-color-primary;
|
|
}
|
|
|
|
&--secondary {
|
|
background: $semantic-color-secondary;
|
|
color: $semantic-color-on-secondary;
|
|
border-color: $semantic-color-secondary;
|
|
}
|
|
|
|
&--success {
|
|
background: $semantic-color-success;
|
|
color: $semantic-color-on-success;
|
|
border-color: $semantic-color-success;
|
|
}
|
|
|
|
&--danger {
|
|
background: $semantic-color-danger;
|
|
color: $semantic-color-on-danger;
|
|
border-color: $semantic-color-danger;
|
|
}
|
|
|
|
// State Variants - ONLY USE ACTUAL TOKENS
|
|
&--disabled {
|
|
opacity: $semantic-opacity-disabled;
|
|
cursor: not-allowed;
|
|
pointer-events: none;
|
|
}
|
|
|
|
// BEM Element - ONLY USE ACTUAL TOKENS
|
|
&__element {
|
|
padding: $semantic-spacing-content-line-tight;
|
|
color: $semantic-color-text-secondary;
|
|
|
|
// Element modifier
|
|
&--active {
|
|
color: $semantic-color-text-primary;
|
|
font-weight: $semantic-typography-font-weight-semibold;
|
|
}
|
|
}
|
|
|
|
// Interactive States - ONLY USE ACTUAL TOKENS
|
|
&:not(.ui-[component-name]--disabled) {
|
|
&:hover {
|
|
box-shadow: $semantic-shadow-card-hover;
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid $semantic-color-focus;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
&:active {
|
|
box-shadow: $semantic-shadow-card-rest;
|
|
}
|
|
}
|
|
|
|
// Responsive Design - USE ACTUAL BREAKPOINT TOKENS
|
|
@media (max-width: $semantic-breakpoint-md - 1) {
|
|
padding: $semantic-spacing-component-sm;
|
|
}
|
|
|
|
@media (max-width: $semantic-breakpoint-sm - 1) {
|
|
padding: $semantic-spacing-component-xs;
|
|
font-family: map-get($semantic-typography-body-small, font-family);
|
|
font-size: map-get($semantic-typography-body-small, font-size);
|
|
font-weight: map-get($semantic-typography-body-small, font-weight);
|
|
line-height: map-get($semantic-typography-body-small, line-height);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🚨 CRITICAL TOKEN VALIDATION 🚨
|
|
|
|
#### ❌ COMMON MISTAKES TO AVOID:
|
|
```scss
|
|
// WRONG - These tokens DON'T EXIST:
|
|
font-size: $semantic-typography-heading-h3; // This is a MAP, not a single value!
|
|
padding: $semantic-spacing-layout-xs; // DOESN'T EXIST
|
|
box-shadow: $semantic-shadow-card-featured; // DOESN'T EXIST
|
|
border-radius: $semantic-border-radius-xs; // DOESN'T EXIST
|
|
transition: $semantic-motion-duration-medium; // DOESN'T EXIST
|
|
background: $semantic-color-background-primary; // DOESN'T EXIST
|
|
|
|
// WRONG - Hardcoded values:
|
|
padding: 16px; // Use $semantic-spacing-component-md
|
|
color: #333333; // Use $semantic-color-text-primary
|
|
border-radius: 8px; // Use $semantic-border-radius-md
|
|
```
|
|
|
|
#### ✅ CORRECT USAGE:
|
|
```scss
|
|
// RIGHT - Use map-get for typography maps:
|
|
font-size: map-get($semantic-typography-heading-h3, font-size);
|
|
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
|
|
|
// RIGHT - Use single value tokens directly:
|
|
padding: $semantic-spacing-component-md;
|
|
color: $semantic-color-text-primary;
|
|
border-radius: $semantic-border-card-radius;
|
|
box-shadow: $semantic-shadow-elevation-2;
|
|
```
|
|
|
|
### ⚠️ MAP USAGE WARNING ⚠️
|
|
These tokens are **MAPS** and require `map-get()`:
|
|
- `$semantic-typography-heading-*` (h1, h2, h3, h4, h5)
|
|
- `$semantic-typography-body-*` (large, medium, small)
|
|
- `$semantic-typography-button-*` (large, medium, small)
|
|
- `$semantic-typography-label`
|
|
- `$semantic-typography-input`
|
|
- `$semantic-typography-caption`
|
|
|
|
**Always extract individual properties:**
|
|
```scss
|
|
// CORRECT MAP USAGE:
|
|
font-family: map-get($semantic-typography-body-medium, font-family);
|
|
font-size: map-get($semantic-typography-body-medium, font-size);
|
|
font-weight: map-get($semantic-typography-body-medium, font-weight);
|
|
line-height: map-get($semantic-typography-body-medium, line-height);
|
|
```
|
|
|
|
### Expected Deliverables:
|
|
1. **SCSS file** properly structured with ONLY existing semantic tokens
|
|
2. **BEM methodology** consistently applied throughout
|
|
3. **Responsive design** using actual breakpoint tokens
|
|
4. **Interactive states** (hover, focus, active) defined with real tokens
|
|
5. **Zero hardcoded values** - everything uses verified design tokens
|
|
6. **Proper map usage** for typography tokens
|
|
7. **Comments** explaining major sections if complex
|
|
|
|
### Quality Checklist:
|
|
- [ ] All colors use actual `$semantic-color-*` tokens
|
|
- [ ] All spacing uses actual `$semantic-spacing-*` tokens
|
|
- [ ] All typography uses proper map-get() for maps or single tokens
|
|
- [ ] All shadows use actual `$semantic-shadow-*` tokens
|
|
- [ ] All borders use actual `$semantic-border-*` tokens
|
|
- [ ] All motion uses actual `$semantic-motion-*` tokens
|
|
- [ ] **No hardcoded values anywhere**
|
|
- [ ] **No invented token names**
|
|
- [ ] BEM naming convention followed
|
|
- [ ] Responsive breakpoints implemented with actual tokens
|
|
- [ ] File saved in correct directory
|
|
|
|
---
|
|
|
|
## Angular Component Guidelines (Enhanced)
|
|
|
|
### Angular 19+ Development Standards:
|
|
|
|
#### TEMPLATES:
|
|
- Use inline templates in @Component decorator
|
|
- Use new control flow syntax: @if, @for, @switch (NOT *ngIf, *ngFor)
|
|
- Use @defer for lazy loading where appropriate
|
|
- Put stylesheets in a separate scss file to the component
|
|
|
|
#### STYLING:
|
|
- Import design tokens: @use '@projects/shared-ui/src/styles/semantic' as *;
|
|
- **CRITICAL:** Ensure correct semantic tokens are used. Do not invent new ones.
|
|
- **If a semantic variable is required but doesn't exist, ASK FOR INSTRUCTION**
|
|
- NO hardcoded values - use only design tokens for colors, spacing, typography, breakpoints
|
|
- Follow BEM naming convention for class names
|
|
- Use SCSS nesting judiciously (max 3 levels)
|
|
- Prefer mixins and functions from the design system
|
|
|
|
#### TYPESCRIPT:
|
|
- Use standalone components (no NgModules)
|
|
- Prefer signals over observables for state management
|
|
- Use inject() function over constructor injection
|
|
- Strong typing everywhere - avoid 'any'
|
|
- Use readonly where applicable
|
|
- Implement OnPush change detection by default
|
|
|
|
#### STRUCTURE:
|
|
- Feature-based folder structure
|
|
- Barrel exports for clean imports
|
|
- Smart/presentational component pattern
|
|
- Services should be provided in root unless scoped needed
|
|
|
|
### Component Template
|
|
```typescript
|
|
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
type ComponentSize = 'sm' | 'md' | 'lg';
|
|
type ComponentVariant = 'primary' | 'secondary' | 'success' | 'danger';
|
|
|
|
@Component({
|
|
selector: 'ui-[component-name]',
|
|
standalone: true,
|
|
imports: [CommonModule],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
encapsulation: ViewEncapsulation.None,
|
|
template: `
|
|
<div
|
|
class="ui-[component-name]"
|
|
[class.ui-[component-name]--{{size}}]="size"
|
|
[class.ui-[component-name]--{{variant}}]="variant"
|
|
[class.ui-[component-name]--disabled]="disabled"
|
|
[class.ui-[component-name]--loading]="loading"
|
|
[attr.aria-disabled]="disabled"
|
|
[attr.aria-busy]="loading"
|
|
[attr.role]="role"
|
|
[tabindex]="disabled ? -1 : tabIndex"
|
|
(click)="handleClick($event)"
|
|
(keydown)="handleKeydown($event)">
|
|
|
|
@if (loading) {
|
|
<span class="ui-[component-name]__loader" aria-hidden="true"></span>
|
|
}
|
|
|
|
<ng-content></ng-content>
|
|
</div>
|
|
`,
|
|
styleUrl: './[component-name].component.scss'
|
|
})
|
|
export class [ComponentName]Component {
|
|
@Input() size: ComponentSize = 'md';
|
|
@Input() variant: ComponentVariant = 'primary';
|
|
@Input() disabled = false;
|
|
@Input() loading = false;
|
|
@Input() role = '[appropriate-aria-role]';
|
|
@Input() tabIndex = 0;
|
|
|
|
@Output() clicked = new EventEmitter<MouseEvent>();
|
|
|
|
handleClick(event: MouseEvent): void {
|
|
if (!this.disabled && !this.loading) {
|
|
this.clicked.emit(event);
|
|
}
|
|
}
|
|
|
|
handleKeydown(event: KeyboardEvent): void {
|
|
// Implement keyboard navigation
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
this.handleClick(event as any);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Demo Component Template
|
|
```typescript
|
|
import { Component } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { [ComponentName]Component } from '../[component-name].component';
|
|
|
|
@Component({
|
|
selector: 'ui-[component-name]-demo',
|
|
standalone: true,
|
|
imports: [CommonModule, [ComponentName]Component],
|
|
template: `
|
|
<div class="demo-container">
|
|
<h2>[Component Name] Demo</h2>
|
|
|
|
<!-- Size Variants -->
|
|
<section class="demo-section">
|
|
<h3>Sizes</h3>
|
|
<div class="demo-row">
|
|
@for (size of sizes; track size) {
|
|
<ui-[component-name] [size]="size">
|
|
{{ size }} size
|
|
</ui-[component-name]>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Color Variants -->
|
|
<section class="demo-section">
|
|
<h3>Variants</h3>
|
|
<div class="demo-row">
|
|
@for (variant of variants; track variant) {
|
|
<ui-[component-name] [variant]="variant">
|
|
{{ variant }}
|
|
</ui-[component-name]>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- States -->
|
|
<section class="demo-section">
|
|
<h3>States</h3>
|
|
<div class="demo-row">
|
|
<ui-[component-name] [disabled]="true">Disabled</ui-[component-name]>
|
|
<ui-[component-name] [loading]="true">Loading</ui-[component-name]>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Interactive Example -->
|
|
<section class="demo-section">
|
|
<h3>Interactive</h3>
|
|
<ui-[component-name] (clicked)="handleDemoClick($event)">
|
|
Click me
|
|
</ui-[component-name]>
|
|
<p>Clicked: {{ clickCount }} times</p>
|
|
</section>
|
|
</div>
|
|
`,
|
|
styleUrl: './[component-name]-demo.component.scss'
|
|
})
|
|
export class [ComponentName]DemoComponent {
|
|
sizes = ['sm', 'md', 'lg'] as const;
|
|
variants = ['primary', 'secondary', 'success', 'danger'] as const;
|
|
clickCount = 0;
|
|
|
|
handleDemoClick(event: MouseEvent): void {
|
|
this.clickCount++;
|
|
console.log('Component clicked', event);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Component Requirements
|
|
- **Design Tokens**: Use ONLY verified semantic tokens throughout
|
|
- **Variants**: Include common UI system variants (colors, styles)
|
|
- **Sizes**: sm, md, lg sizing options
|
|
- **States**: hover, focus, active, disabled
|
|
- **Accessibility**: ARIA support, keyboard navigation
|
|
|
|
---
|
|
|
|
## Testing Requirements
|
|
|
|
### Unit Test Template
|
|
```typescript
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
import { [ComponentName]Component } from './[component-name].component';
|
|
|
|
describe('[ComponentName]Component', () => {
|
|
let component: [ComponentName]Component;
|
|
let fixture: ComponentFixture<[ComponentName]Component>;
|
|
|
|
beforeEach(async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [[ComponentName]Component]
|
|
}).compileComponents();
|
|
|
|
fixture = TestBed.createComponent([ComponentName]Component);
|
|
component = fixture.componentInstance;
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
it('should create', () => {
|
|
expect(component).toBeTruthy();
|
|
});
|
|
|
|
it('should apply size classes correctly', () => {
|
|
component.size = 'lg';
|
|
fixture.detectChanges();
|
|
|
|
const element = fixture.nativeElement.querySelector('.ui-[component-name]');
|
|
expect(element).toHaveClass('ui-[component-name]--lg');
|
|
});
|
|
|
|
it('should handle disabled state', () => {
|
|
component.disabled = true;
|
|
fixture.detectChanges();
|
|
|
|
const element = fixture.nativeElement.querySelector('.ui-[component-name]');
|
|
expect(element).toHaveClass('ui-[component-name]--disabled');
|
|
expect(element.getAttribute('aria-disabled')).toBe('true');
|
|
});
|
|
|
|
it('should emit click events when not disabled', () => {
|
|
spyOn(component.clicked, 'emit');
|
|
const element = fixture.nativeElement.querySelector('.ui-[component-name]');
|
|
|
|
element.click();
|
|
|
|
expect(component.clicked.emit).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not emit click events when disabled', () => {
|
|
component.disabled = true;
|
|
spyOn(component.clicked, 'emit');
|
|
const element = fixture.nativeElement.querySelector('.ui-[component-name]');
|
|
|
|
element.click();
|
|
|
|
expect(component.clicked.emit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should support keyboard navigation', () => {
|
|
spyOn(component, 'handleClick');
|
|
const element = fixture.nativeElement.querySelector('.ui-[component-name]');
|
|
|
|
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
|
|
element.dispatchEvent(enterEvent);
|
|
|
|
expect(component.handleClick).toHaveBeenCalled();
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Component Quality Checklist
|
|
|
|
### ⚠️ PRE-FLIGHT CHECK ⚠️
|
|
- [ ] **CONFIRMED: Creating NEW component, not modifying existing**
|
|
- [ ] **VERIFIED: No existing component files will be altered**
|
|
- [ ] **CHECKED: Only adding to integration files, not modifying**
|
|
|
|
### Functionality
|
|
- [ ] All size variants work correctly (sm, md, lg)
|
|
- [ ] All color variants apply properly
|
|
- [ ] Disabled state prevents interaction
|
|
- [ ] Loading state shows indicator
|
|
- [ ] Events emit correctly
|
|
- [ ] Content projection works
|
|
|
|
### Styling ⚠️ CRITICAL VALIDATION ⚠️
|
|
- [ ] **Uses ONLY verified existing semantic tokens**
|
|
- [ ] **NO HARDCODED VALUES** (px, rem, hex colors)
|
|
- [ ] **NO INVENTED TOKEN NAMES**
|
|
- [ ] Responsive on all breakpoints
|
|
- [ ] Follows BEM naming convention
|
|
- [ ] Proper state transitions
|
|
- [ ] Map tokens used with map-get()
|
|
|
|
### Accessibility
|
|
- [ ] Proper ARIA attributes
|
|
- [ ] Keyboard navigable
|
|
- [ ] Focus indicators visible
|
|
- [ ] Screen reader friendly
|
|
- [ ] Meets WCAG 2.1 AA standards
|
|
- [ ] Color contrast sufficient
|
|
|
|
### Code Quality
|
|
- [ ] TypeScript strictly typed
|
|
- [ ] OnPush change detection
|
|
- [ ] Standalone component
|
|
- [ ] Proper imports/exports
|
|
- [ ] Demo shows all features
|
|
- [ ] Unit tests pass
|
|
|
|
### Integration
|
|
- [ ] Added to public API (additive only)
|
|
- [ ] Demo route configured (additive only)
|
|
- [ ] Menu item added to dashboard (additive only)
|
|
- [ ] Build passes without errors
|
|
- [ ] Follows project conventions
|
|
- [ ] **Existing components still work**
|
|
|
|
---
|
|
|
|
## 🚨 FINAL TOKEN VALIDATION 🚨
|
|
|
|
### Before Implementation:
|
|
1. **VERIFY** every token used exists in the reference above
|
|
2. **ASK FOR HELP** if you need a token that doesn't exist
|
|
3. **TEST** build process to catch any invalid tokens
|
|
4. **NEVER GUESS** or invent token names
|
|
|
|
### If you need a token that doesn't exist:
|
|
- **STOP** implementation
|
|
- **ASK** "This component needs [specific token type] but I don't see one available. Should I [alternative approach] or do you have guidance?"
|
|
- **WAIT** for direction rather than inventing tokens
|
|
|
|
This template provides a comprehensive guide for creating consistent, high-quality NEW components that integrate seamlessly with the existing design system without affecting any existing code, **and most importantly, without using non-existent tokens that would cause build failures**. |