Skip to content

Latest commit

 

History

History
743 lines (553 loc) · 13.5 KB

Tag.md

File metadata and controls

743 lines (553 loc) · 13.5 KB

Tag Component

Version: 4.0.0 Last Updated: January 2025

Tag/badge component for categorization, labels, and metadata display.

Purpose

Display labels and categories with:

  • Multiple color variants
  • Size options (sm, md, lg)
  • Icon support
  • Removable/dismissible option
  • Interactive (clickable) variant
  • Customizable styling
  • Accessibility support

Component Architecture

Tag Interaction Flow (Mermaid)

sequenceDiagram
    participant U as User
    participant T as Tag Component
    participant B as BlogPage
    participant F as Filter System

    Note over B: Display blog post with tags
    B->>T: Render tags ['Festival', 'Tutorial', 'Glitter']
    T-->>U: Display tag pills

    alt Clickable Tag
        U->>T: Click "Festival" tag
        T->>T: Check if onClick exists
        T->>F: onClick('festival')
        F->>B: Filter posts by tag
        B->>B: Update filteredPosts
        B-->>U: Show filtered results ✅
    else Removable Tag
        U->>T: Click X on tag
        T->>T: Check if onRemove exists
        T->>F: onRemove('festival')
        F->>B: Remove from active filters
        B-->>U: Update results ✅
    else Static Tag
        U->>T: Hover tag
        T->>T: No onClick/onRemove
        T-->>U: Display only (no action)
    end

```text

### Tag Variant Color Mapping (Mermaid)

```mermaid
flowchart TD
    A[Tag Renders] --> B{Which Variant?}

    B -->|pink| C[Pink Gradient]
    B -->|purple| D[Purple Gradient]
    B -->|blue| E[Blue Gradient]
    B -->|green| F[Green Solid]
    B -->|orange| G[Orange Solid]
    B -->|gray| H[Gray Neutral]
    B -->|default| I[Default Gray]

    C --> C1["bg-gradient-to-r from-pink-500 to-rose-500<br/>text-white"]
    D --> D1["bg-gradient-to-r from-purple-500 to-indigo-500<br/>text-white"]
    E --> E1["bg-gradient-to-r from-blue-500 to-cyan-500<br/>text-white"]
    F --> F1["bg-green-100 text-green-800<br/>border-green-300"]
    G --> G1["bg-orange-100 text-orange-800<br/>border-orange-300"]
    H --> H1["bg-gray-100 text-gray-700<br/>border-gray-300"]
    I --> I1["bg-gray-200 text-gray-800"]

    C1 --> J[Render Tag]
    D1 --> J
    E1 --> J
    F1 --> J
    G1 --> J
    H1 --> J
    I1 --> J

    J --> K{Has Icon?}

    K -->|Yes| L[Render icon + text]
    K -->|No| M[Render text only]

    L --> N{Size?}
    M --> N

    N -->|sm| O[px-2 py-1 text-xs]
    N -->|md| P[px-3 py-1.5 text-sm]
    N -->|lg| Q[px-4 py-2 text-base]

    style C1 fill:#fce7f3,stroke:#ec4899,stroke-width:2px
    style D1 fill:#f3e8ff,stroke:#a855f7,stroke-width:2px
    style E1 fill:#dbeafe,stroke:#3b82f6,stroke-width:2px

```text

### Tag State Management (Mermaid)

```mermaid
stateDiagram-v2
    [*] --> Static: onClick/onRemove undefined
    [*] --> Interactive: onClick defined
    [*] --> Removable: onRemove defined

    Static --> Idle: Display only
    Idle --> Idle: No user actions

    Interactive --> Clickable: Cursor pointer
    Clickable --> Hover: Mouse enter
    Clickable --> Focused: Keyboard focus

    Hover --> Click: Mouse click
    Focused --> Click: Enter/Space key

    Click --> FilterActive: Execute onClick
    FilterActive --> UpdateUI: Apply filter
    UpdateUI --> Clickable: Return to idle

    Removable --> RemovableIdle: Show X button
    RemovableIdle --> RemoveHover: Hover X button
    RemoveHover --> Remove: Click X

    Remove --> Unmount: Execute onRemove
    Unmount --> [*]: Tag removed from DOM

    note right of Interactive
        Clickable tags:

        - Cursor pointer
        - Hover effects
        - Filter on click
    end note

    note right of Removable
        Removable tags:

        - X close button
        - Remove from list
        - Dismiss action
    end note

```text

---

## Usage

### Basic Usage

```tsx
import { Tag } from './components/ui/Tag';

<Tag>Festival</Tag>

```text

### With Color Variant

```tsx
<Tag variant="pink">Festival</Tag>
<Tag variant="purple">Editorial</Tag>
<Tag variant="blue">Special Event</Tag>

```text

### Clickable Tag

```tsx
<Tag
  variant="pink"
  onClick={() => filterByTag('festival')}
>
  Festival
</Tag>

```text

### Removable Tag

```tsx
<Tag
  variant="purple"
  onRemove={() => removeTag('makeup-tutorial')}
>
  Makeup Tutorial
</Tag>

```text

### With Icon

```tsx
import { Sparkle } from '@phosphor-icons/react';

<Tag
  variant="pink"
  icon={<Sparkle className="w-3 h-3" />}
>
  Featured
</Tag>

```text

---

## Props

```typescript
interface TagProps {
  /**

   * Tag content
   * @required
   */
  children: React.ReactNode;

  /**

   * Color variant
   * @default "gray"
   */
  variant?: 'pink' | 'purple' | 'blue' | 'green' | 'yellow' | 'red' | 'gray';

  /**

   * Tag size
   * @default "md"
   */
  size?: 'sm' | 'md' | 'lg';

  /**

   * Optional icon
   * @optional
   */
  icon?: React.ReactNode;

  /**

   * Click handler (makes tag interactive)
   * @optional
   */
  onClick?: () => void;

  /**

   * Remove/dismiss handler
   * @optional
   */
  onRemove?: () => void;

  /**

   * Additional CSS classes
   * @default ""
   */
  className?: string;
}

```text

---

## Implementation Example

Complete tag component implementation:

```tsx
import React from 'react';
import { X } from '@phosphor-icons/react';

interface TagProps {
  children: React.ReactNode;
  variant?: 'pink' | 'purple' | 'blue' | 'green' | 'yellow' | 'red' | 'gray';
  size?: 'sm' | 'md' | 'lg';
  icon?: React.ReactNode;
  onClick?: () => void;
  onRemove?: () => void;
  className?: string;
}

export function Tag({
  children,
  variant = 'gray',
  size = 'md',
  icon,
  onClick,
  onRemove,
  className = ''
}: TagProps) {
  const variantClasses = {
    pink: 'bg-pink-100 text-pink-700 hover:bg-pink-200',
    purple: 'bg-purple-100 text-purple-700 hover:bg-purple-200',
    blue: 'bg-blue-100 text-blue-700 hover:bg-blue-200',
    green: 'bg-green-100 text-green-700 hover:bg-green-200',
    yellow: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200',
    red: 'bg-red-100 text-red-700 hover:bg-red-200',
    gray: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
  };

  const sizeClasses = {
    sm: 'px-2 py-0.5 text-fluid-xs',
    md: 'px-3 py-1 text-fluid-sm',
    lg: 'px-4 py-1.5 text-fluid-base'
  };

  const iconSizes = {
    sm: 'w-3 h-3',
    md: 'w-4 h-4',
    lg: 'w-5 h-5'
  };

  const Component = onClick ? 'button' : 'span';

  return (
    <Component
      onClick={onClick}
      className={`
        inline-flex items-center gap-1.5
        rounded-full font-body font-medium
        transition-colors duration-200
        ${variantClasses[variant]}
        ${sizeClasses[size]}
        ${onClick ? 'cursor-pointer' : ''}
        ${className}
      `}
      type={onClick ? 'button' : undefined}
    >
      {icon && <span className={iconSizes[size]}>{icon}</span>}

      <span>{children}</span>

      {onRemove && (
        <button
          onClick={(e) => {
            e.stopPropagation();
            onRemove();
          }}
          className="ml-1 hover:opacity-70 transition-opacity"
          aria-label={`Remove ${children}`}
        >
          <X className={iconSizes[size]} />
        </button>
      )}
    </Component>
  );
}

```text

---

## Usage Patterns

### Blog Post Tags

```tsx
function BlogPost({ post }: Props) {
  return (
    <article>
      <h1>{post.title}</h1>

      <div className="flex flex-wrap gap-2 mb-fluid-md">
        {post.tags.map(tag => (
          <Tag
            key={tag}
            variant="pink"
            size="sm"
            onClick={() => filterByTag(tag)}
          >
            #{tag}
          </Tag>
        ))}
      </div>

      {/* Post content */}
    </article>
  );
}

```text

### Portfolio Categories

```tsx
function PortfolioCard({ entry }: Props) {
  const categoryColors = {
    festival: 'purple',
    editorial: 'pink',
    'special-event': 'blue',
    nails: 'green'
  };

  return (
    <article className="bg-white rounded-2xl p-fluid-md">
      <Tag variant={categoryColors[entry.category]}>
        {entry.category}
      </Tag>

      <h3>{entry.title}</h3>
      {/* Card content */}
    </article>
  );
}

```text

### Filter Tags (Removable)

```tsx
function ActiveFilters({ filters, onRemove }: Props) {
  if (filters.length === 0) return null;

  return (
    <div className="flex flex-wrap gap-2 mb-fluid-md">
      <span className="text-fluid-sm font-body text-gray-600">
        Active filters:
      </span>

      {filters.map(filter => (
        <Tag
          key={filter.id}
          variant="pink"
          size="sm"
          onRemove={() => onRemove(filter.id)}
        >
          {filter.label}
        </Tag>
      ))}

      <button
        onClick={() => onRemove('all')}
        className="text-fluid-sm font-body text-pink-600 hover:text-pink-700"
      >
        Clear all
      </button>
    </div>
  );
}

```text

### Project Status

```tsx
function ProjectStatus({ status }: Props) {
  const statusConfig = {
    pending: { variant: 'yellow', label: 'Pending' },
    confirmed: { variant: 'green', label: 'Confirmed' },
    completed: { variant: 'blue', label: 'Completed' },
    cancelled: { variant: 'red', label: 'Cancelled' }
  };

  const config = statusConfig[status];

  return (
    <Tag variant={config.variant}>
      {config.label}
    </Tag>
  );
}

```text

### Tag Cloud

```tsx
function TagCloud({ tags }: Props) {
  return (
    <div className="bg-gray-50 rounded-2xl p-fluid-lg">
      <h3 className="text-fluid-lg font-heading font-semibold mb-fluid-md">
        Popular Tags
      </h3>

      <div className="flex flex-wrap gap-2">
        {tags.map(tag => (
          <Tag
            key={tag.name}
            variant="purple"
            size={tag.count > 10 ? 'lg' : tag.count > 5 ? 'md' : 'sm'}
            onClick={() => navigate(`/blog/tag/${tag.slug}`)}
          >
            {tag.name} ({tag.count})
          </Tag>
        ))}
      </div>
    </div>
  );
}

```text

---

## Advanced Features

### With Badge Count

```tsx
<Tag variant="pink">
  Notifications
<span className="ml-1 px-1.5 py-0.5 bg-pink-600 text-white rounded-full
text-fluid-xs">
    3
  </span>
</Tag>

```text

### Gradient Tags

```tsx
<span className="inline-flex items-center px-3 py-1 rounded-full
bg-gradient-pink-purple-blue text-white font-body font-medium text-fluid-sm">
  Featured
</span>

```text

### Outlined Variant

```tsx
<span className="inline-flex items-center px-3 py-1 rounded-full border-2
border-pink-500 text-pink-700 font-body font-medium text-fluid-sm
hover:bg-pink-50">
  Festival
</span>

```text

### With Dot Indicator

```tsx
<Tag variant="green">
  <span className="w-2 h-2 bg-green-500 rounded-full" />
  Active
</Tag>

```text

---

## Color Variants

### Pink

```tsx
<Tag variant="pink">Festival</Tag>

```text
Use for: Festival makeup, featured items, primary categories

### Purple

```tsx
<Tag variant="purple">Editorial</Tag>

```text
Use for: Editorial work, creative categories

### Blue

```tsx
<Tag variant="blue">Special Event</Tag>

```text
Use for: Events, informational tags

### Green

```tsx
<Tag variant="green">Verified</Tag>

```text
Use for: Success states, verified items, active status

### Yellow

```tsx
<Tag variant="yellow">Pending</Tag>

```text
Use for: Warnings, pending states, attention needed

### Red

```tsx
<Tag variant="red">Cancelled</Tag>

```text
Use for: Errors, cancelled items, important alerts

### Gray

```tsx
<Tag variant="gray">Draft</Tag>

```text
Use for: Neutral tags, inactive states, default

---

## Accessibility

### Interactive Tags

```tsx
<button
  onClick={onClick}
  className="tag"
  type="button"
  aria-label="Filter by festival makeup"
>
  Festival
</button>

```text

### Removable Tags

```tsx
<span className="tag">
  <span>Tutorial</span>
  <button
    onClick={onRemove}
    aria-label="Remove tutorial tag"
  >
    <X className="w-3 h-3" />
  </button>
</span>

```text

### Semantic HTML

```tsx
// For clickable tags
<button type="button">Tag</button>

// For display-only tags
<span>Tag</span>

// In a list of tags
<ul className="flex gap-2">
  <li><Tag>Tag 1</Tag></li>
  <li><Tag>Tag 2</Tag></li>
</ul>

```text

---

## Common Mistakes

### ❌ Mistake 1: All Tags Same Color

```tsx
// ❌ WRONG - No visual distinction
<Tag>Festival</Tag>
<Tag>Editorial</Tag>
<Tag>Nails</Tag>

```text

**Solution:**

```tsx
// ✅ CORRECT - Color-coded categories
<Tag variant="purple">Festival</Tag>
<Tag variant="pink">Editorial</Tag>
<Tag variant="green">Nails</Tag>

```text

### ❌ Mistake 2: Missing Click Feedback

```tsx
// ❌ WRONG - No visual indication it's clickable
<div onClick={filter}>Festival</div>

```text

**Solution:**

```tsx
// ✅ CORRECT - Button with hover state
<Tag variant="pink" onClick={filter}>
  Festival
</Tag>

```text

### ❌ Mistake 3: No Remove Confirmation

```tsx
// ❌ WRONG - Accidental removal
<Tag onRemove={() => removeTag(id)}>Tag</Tag>

```text

**Solution:**

```tsx
// ✅ CORRECT - Confirm before removing important tags
<Tag onRemove={() => {
  if (confirm('Remove this tag?')) removeTag(id);
}}>
  Important Tag
</Tag>

```text

---

## Related Components

- **[CategoryFilter](./CategoryFilter.md)** - Category filtering
- **[BlogCard](./BlogCard.md)** - Blog posts with tags
- **[PortfolioCard](./PortfolioCard.md)** - Portfolio categories

---

## Related Documentation

- **[Guidelines.md](../Guidelines.md)** - Main guidelines
- **[overview-components.md](../overview-components.md)** - Component system
- **[FILE*STRUCTURE.md](../FILE*STRUCTURE.md)** - File organization
- **[design-tokens-reference.md](../design-tokens-reference.md)** - Color system
- **[design-tokens-reference.md](../design-tokens-reference.md)** - Typography scale

---

**Last Updated:** January 2025
**Version:** 4.0.0
Loading