Files
ColaFlow/docs/design/ui-component-specs.md
Yaojia Wang fe8ad1c1f9
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
In progress
2025-11-03 11:51:02 +01:00

1655 lines
39 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UI Component Specifications
## Document Overview
This document provides detailed specifications for all UI components required for multi-tenant, SSO, and MCP Token management features in ColaFlow.
**Version:** 1.0
**Last Updated:** 2025-11-03
**Tech Stack:** Next.js 16 + React 19 + shadcn/ui + Tailwind CSS 4
**Owner:** UX/UI Team
---
## Table of Contents
1. [Design System Foundation](#design-system-foundation)
2. [Registration Components](#registration-components)
3. [Login Components](#login-components)
4. [SSO Configuration Components](#sso-configuration-components)
5. [MCP Token Components](#mcp-token-components)
6. [Shared Components](#shared-components)
7. [Component Props Reference](#component-props-reference)
---
## Design System Foundation
### Color System
Based on ColaFlow's existing design system:
```typescript
// tailwind.config.ts
const colors = {
primary: {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196F3', // Main brand color
600: '#1e88e5',
700: '#1976D2', // Darker shade
800: '#1565c0',
900: '#0d47a1',
},
secondary: {
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336',
info: '#2196F3',
},
priority: {
urgent: '#F44336',
high: '#FF9800',
medium: '#2196F3',
low: '#9E9E9E',
},
status: {
active: '#4CAF50',
suspended: '#FF9800',
cancelled: '#F44336',
pending: '#FFC107',
},
gray: {
50: '#fafafa',
100: '#f5f5f5',
200: '#eeeeee',
300: '#e0e0e0',
400: '#bdbdbd',
500: '#9e9e9e',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121',
},
}
```
### Typography Scale
```typescript
// Font families
const fontFamily = {
sans: ['Inter', 'Roboto', 'PingFang SC', 'Microsoft YaHei', 'sans-serif'],
mono: ['JetBrains Mono', 'Consolas', 'monospace'],
}
// Font sizes
const fontSize = {
'xs': ['12px', { lineHeight: '16px' }],
'sm': ['14px', { lineHeight: '20px' }],
'base': ['16px', { lineHeight: '24px' }],
'lg': ['18px', { lineHeight: '28px' }],
'xl': ['20px', { lineHeight: '28px' }],
'2xl': ['24px', { lineHeight: '32px' }],
'3xl': ['30px', { lineHeight: '36px' }],
'4xl': ['36px', { lineHeight: '40px' }],
}
// Font weights
const fontWeight = {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
}
```
### Spacing System (8px base)
```typescript
const spacing = {
0: '0px',
1: '4px', // 0.5 * 8
2: '8px', // 1 * 8
3: '12px', // 1.5 * 8
4: '16px', // 2 * 8
5: '20px', // 2.5 * 8
6: '24px', // 3 * 8
8: '32px', // 4 * 8
10: '40px', // 5 * 8
12: '48px', // 6 * 8
16: '64px', // 8 * 8
20: '80px', // 10 * 8
24: '96px', // 12 * 8
}
```
### Shadows
```typescript
const boxShadow = {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
}
```
### Border Radius
```typescript
const borderRadius = {
none: '0',
sm: '4px',
DEFAULT: '6px',
md: '8px',
lg: '12px',
xl: '16px',
'2xl': '24px',
full: '9999px',
}
```
---
## Registration Components
### 1. TenantSlugInput
**Purpose:** Input field with real-time availability check and subdomain preview.
**Visual:**
```
Company Slug * []
[acme ] [⟳]
✓ acme.colaflow.com is available
```
**Component Structure:**
```typescript
interface TenantSlugInputProps {
value: string;
onChange: (value: string) => void;
onValidationChange: (isValid: boolean) => void;
disabled?: boolean;
}
interface ValidationState {
status: 'idle' | 'checking' | 'valid' | 'invalid' | 'error';
message?: string;
suggestions?: string[];
}
```
**States:**
1. **Idle:** No input yet
- Border: gray-300
- No icon
2. **Checking:** Validating in real-time
- Border: primary-500
- Icon: Spinner (animated)
- Message: "Checking availability..."
3. **Valid:** Slug is available
- Border: green-500
- Icon: Checkmark (green)
- Message: "✓ acme.colaflow.com is available"
4. **Invalid:** Slug is taken or has errors
- Border: red-500
- Icon: X (red)
- Message: "This URL is already taken. Try: acme-corp, acme-team"
- Show suggestion chips (clickable)
5. **Error:** Network or validation error
- Border: red-500
- Icon: Alert (red)
- Message: "Unable to check availability. Please try again."
**Behavior:**
- Debounce input: 500ms
- Auto-lowercase
- Strip invalid characters
- Show preview domain below input
- Validate format: ^[a-z0-9]+(?:-[a-z0-9]+)*$
- API call: `GET /api/tenants/check-slug?slug=acme`
**Accessibility:**
- aria-label: "Company URL slug"
- aria-describedby: Points to preview text
- aria-invalid: true when status is invalid
- aria-live: "polite" for validation messages
---
### 2. PasswordStrengthInput
**Purpose:** Password input with strength indicator and requirements checklist.
**Visual:**
```
Password *
[******************] [👁️]
Strength: [███████░░░] Medium
Requirements:
☑ At least 8 characters
☑ One uppercase letter (A-Z)
☑ One lowercase letter (a-z)
☐ One number (0-9)
☐ One special character (!@#$%^&*)
```
**Component Structure:**
```typescript
interface PasswordStrengthInputProps {
value: string;
onChange: (value: string) => void;
showRequirements?: boolean;
minStrength?: 'weak' | 'medium' | 'strong';
}
interface PasswordStrength {
level: 'weak' | 'medium' | 'strong';
score: number; // 0-100
requirements: PasswordRequirement[];
}
interface PasswordRequirement {
label: string;
met: boolean;
regex: RegExp;
}
```
**Strength Calculation:**
```typescript
const calculateStrength = (password: string): PasswordStrength => {
let score = 0;
const requirements: PasswordRequirement[] = [
{ label: 'At least 8 characters', met: password.length >= 8, regex: /.{8,}/ },
{ label: 'One uppercase letter (A-Z)', met: /[A-Z]/.test(password), regex: /[A-Z]/ },
{ label: 'One lowercase letter (a-z)', met: /[a-z]/.test(password), regex: /[a-z]/ },
{ label: 'One number (0-9)', met: /\d/.test(password), regex: /\d/ },
{ label: 'One special character (!@#$%^&*)', met: /[!@#$%^&*]/.test(password), regex: /[!@#$%^&*]/ },
];
requirements.forEach(req => {
if (req.met) score += 20;
});
let level: 'weak' | 'medium' | 'strong' = 'weak';
if (score >= 80) level = 'strong';
else if (score >= 60) level = 'medium';
return { level, score, requirements };
};
```
**Strength Bar Colors:**
- **Weak (0-40%):** bg-red-500
- **Medium (40-80%):** bg-yellow-500
- **Strong (80-100%):** bg-green-500
**Behavior:**
- Show/hide toggle button
- Update strength on every keystroke
- Prevent paste (optional, for security)
- Animated strength bar transition
**Accessibility:**
- aria-label: "Password, show requirements below"
- aria-describedby: Points to requirements list
- Toggle button: aria-label "Show password" / "Hide password"
- Requirements list: role="list"
---
### 3. SubscriptionPlanCard
**Purpose:** Selectable subscription plan card with feature comparison.
**Visual:**
```
┌─────────────────────────────────┐
│ MOST POPULAR │ <- Badge (optional)
├─────────────────────────────────┤
│ Professional │
│ $49 /month │
│ │
│ Everything in Starter, plus: │
│ ✓ 50 users │
│ ✓ 100 projects │
│ ✓ 100 GB storage │
│ ✓ SSO integration │
│ ✓ Priority support │
│ ✓ Advanced analytics │
│ │
│ [Start with Professional] │
│ │
│ 14-day free trial │
└─────────────────────────────────┘
```
**Component Structure:**
```typescript
interface SubscriptionPlanCardProps {
plan: SubscriptionPlan;
selected: boolean;
onSelect: (plan: SubscriptionPlan) => void;
showBadge?: boolean;
badgeText?: string;
}
interface SubscriptionPlan {
id: string;
name: string;
price: number;
billingPeriod: 'month' | 'year';
features: PlanFeature[];
limits: PlanLimits;
}
interface PlanFeature {
label: string;
included: boolean;
tooltip?: string;
}
interface PlanLimits {
users: number | 'unlimited';
projects: number | 'unlimited';
storageGB: number;
mcpTokens: number | 'unlimited';
}
```
**States:**
1. **Unselected:**
- Border: gray-300
- Background: white
- Shadow: sm
2. **Hover:**
- Border: primary-500
- Shadow: md
- Transform: scale(1.02)
- Transition: 200ms ease-out
3. **Selected:**
- Border: primary-500 (2px)
- Background: primary-50
- Shadow: lg
- Checkmark icon in top-right corner
**Badge (optional):**
- Position: absolute top-0 right-0
- Background: primary-500
- Text: white, text-xs, uppercase
- Padding: 4px 12px
- Border-radius: bottom-left only
**Button:**
- Full width
- Size: large
- Variant: selected ? primary : secondary
**Accessibility:**
- role: "radio"
- aria-checked: selected
- Keyboard: Space/Enter to select
- Focus ring: 2px offset
---
## Login Components
### 4. SsoButton
**Purpose:** Branded SSO login button for different identity providers.
**Visual Examples:**
```
Azure AD:
[🟦 Continue with Microsoft]
Google:
[🔴 Continue with Google]
Okta:
[🔵 Continue with Okta]
Generic SAML:
[🔐 Continue with SSO]
```
**Component Structure:**
```typescript
interface SsoButtonProps {
provider: 'AzureAD' | 'Google' | 'Okta' | 'GenericSaml';
onClick: () => void;
loading?: boolean;
disabled?: boolean;
fullWidth?: boolean;
}
interface ProviderConfig {
name: string;
icon: React.ReactNode;
bgColor: string;
textColor: string;
hoverBgColor: string;
}
```
**Provider Configurations:**
```typescript
const providerConfigs: Record<string, ProviderConfig> = {
AzureAD: {
name: 'Microsoft',
icon: <MicrosoftIcon />,
bgColor: '#00A4EF', // Microsoft blue
textColor: '#FFFFFF',
hoverBgColor: '#0078D4',
},
Google: {
name: 'Google',
icon: <GoogleIcon />,
bgColor: '#FFFFFF',
textColor: '#3C4043',
hoverBgColor: '#F8F9FA',
},
Okta: {
name: 'Okta',
icon: <OktaIcon />,
bgColor: '#007DC1',
textColor: '#FFFFFF',
hoverBgColor: '#005F96',
},
GenericSaml: {
name: 'SSO',
icon: <LockIcon />,
bgColor: '#6B7280',
textColor: '#FFFFFF',
hoverBgColor: '#4B5563',
},
};
```
**States:**
1. **Default:**
- Provider-specific colors
- Border: none
- Height: 48px (large)
- Padding: 12px 24px
- Font: medium weight
2. **Hover:**
- Background: hoverBgColor
- Shadow: md
- Cursor: pointer
3. **Loading:**
- Spinner animation (white for dark bg, gray for light bg)
- Text: "Signing in..."
- Disabled cursor
4. **Disabled:**
- Opacity: 50%
- Cursor: not-allowed
**Layout:**
```
[Icon] Continue with {Provider}
```
- Icon: 20x20px, margin-right 12px
- Text: align center, font-size 16px
**Accessibility:**
- aria-label: "Sign in with {provider}"
- role: "button"
- Keyboard: Enter/Space to activate
---
### 5. LoginDivider
**Purpose:** Visual separator between SSO and local login options.
**Visual:**
```
──────────── OR ────────────
```
**Component Structure:**
```typescript
interface LoginDividerProps {
text?: string; // Default: "OR"
className?: string;
}
```
**Implementation:**
```tsx
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-white text-gray-500 uppercase tracking-wider">
{text || 'OR'}
</span>
</div>
</div>
```
---
## SSO Configuration Components
### 6. SsoProviderSelect
**Purpose:** Dropdown to select SSO provider type.
**Visual:**
```
SSO Provider *
[Azure AD / Microsoft Entra ▼]
Options:
- Azure AD / Microsoft Entra
- Google Workspace
- Okta
- Generic SAML 2.0
```
**Component Structure:**
```typescript
interface SsoProviderSelectProps {
value: SsoProvider;
onChange: (provider: SsoProvider) => void;
disabled?: boolean;
}
type SsoProvider = 'AzureAD' | 'Google' | 'Okta' | 'GenericSaml';
interface ProviderOption {
value: SsoProvider;
label: string;
description: string;
icon: React.ReactNode;
docsLink: string;
}
```
**Provider Options:**
```typescript
const providerOptions: ProviderOption[] = [
{
value: 'AzureAD',
label: 'Azure AD / Microsoft Entra',
description: 'For Microsoft 365 and Azure Active Directory',
icon: <MicrosoftIcon />,
docsLink: '/docs/sso/azure-ad',
},
{
value: 'Google',
label: 'Google Workspace',
description: 'For Google Workspace (formerly G Suite)',
icon: <GoogleIcon />,
docsLink: '/docs/sso/google',
},
{
value: 'Okta',
label: 'Okta',
description: 'For Okta Identity Cloud',
icon: <OktaIcon />,
docsLink: '/docs/sso/okta',
},
{
value: 'GenericSaml',
label: 'Generic SAML 2.0',
description: 'For any SAML 2.0 compliant IdP',
icon: <ShieldIcon />,
docsLink: '/docs/sso/saml',
},
];
```
**Dropdown Item Layout:**
```
[Icon] Provider Name
Description
```
- Icon: 24x24px
- Name: font-medium, text-gray-900
- Description: text-sm, text-gray-500
**Behavior:**
- Show help icon with link to docs
- Highlight selected option
- Keyboard navigation (arrow keys)
---
### 7. DomainListInput
**Purpose:** Add/remove allowed email domains for SSO.
**Visual:**
```
Allowed Email Domains (Optional)
[@acme.com ] [+ Add Domain]
Added Domains:
┌──────────────────────────────────┐
│ @acme.com [🗑️] │
│ @acme.co.uk [🗑️] │
│ @acme-corp.com [🗑️] │
└──────────────────────────────────┘
Only users with these email domains can log in via SSO.
Leave empty to allow all domains.
```
**Component Structure:**
```typescript
interface DomainListInputProps {
domains: string[];
onChange: (domains: string[]) => void;
placeholder?: string;
helperText?: string;
}
interface ValidationResult {
isValid: boolean;
error?: string;
}
```
**Validation:**
```typescript
const validateDomain = (domain: string): ValidationResult => {
// Remove @ if present
const cleanDomain = domain.replace('@', '').trim().toLowerCase();
// Check format: valid domain
const domainRegex = /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/;
if (!cleanDomain) {
return { isValid: false, error: 'Domain cannot be empty' };
}
if (!domainRegex.test(cleanDomain)) {
return { isValid: false, error: 'Invalid domain format' };
}
return { isValid: true };
};
```
**Behavior:**
- Input always shows "@" prefix
- Add domain on Enter or button click
- Prevent duplicate domains
- Show validation error below input
- Animate domain chips on add/remove
**Domain Chip:**
- Background: gray-100
- Border: gray-300
- Padding: 6px 12px
- Border-radius: full
- Delete icon: hover shows red
**Accessibility:**
- Input: aria-label "Add email domain"
- Chip list: role="list"
- Delete buttons: aria-label "Remove {domain}"
---
### 8. SsoTestButton
**Purpose:** Test SSO configuration before saving.
**Visual:**
```
[Test Connection]
Test Results:
┌─────────────────────────────────────────┐
│ ✓ Metadata endpoint reachable │
│ ✓ OIDC discovery document valid │
│ ⏳ Testing authentication... │
└─────────────────────────────────────────┘
```
**Component Structure:**
```typescript
interface SsoTestButtonProps {
onTest: () => Promise<SsoTestResult>;
disabled?: boolean;
}
interface SsoTestResult {
success: boolean;
checks: TestCheck[];
error?: string;
}
interface TestCheck {
label: string;
status: 'pending' | 'success' | 'error';
message?: string;
duration?: number; // ms
}
```
**Test Checks:**
1. **Metadata Endpoint Reachable**
- Request: GET metadata URL
- Success: 200 status
- Time: <500ms
2. **OIDC Discovery Valid** (OIDC only)
- Parse JSON
- Validate required fields
- Time: <100ms
3. **Certificate Valid** (SAML only)
- Parse X.509 certificate
- Check expiration
- Time: <100ms
4. **Callback URL Whitelisted**
- Attempt authorization request
- Check for redirect_uri_mismatch
- Time: <1000ms
**States:**
1. **Idle:**
- Button: secondary variant
- Text: "Test Connection"
- Icon: none
2. **Testing:**
- Button: loading spinner
- Text: "Testing..."
- Show progress: checks updating in real-time
3. **Success:**
- Button: disabled
- Results: all checks green
- Final message: "All checks passed ✓"
4. **Failure:**
- Button: enabled (can retry)
- Results: failed checks red
- Show error details
- Link to troubleshooting docs
**Results Display:**
```tsx
<div className="mt-4 space-y-2">
{checks.map((check, idx) => (
<div key={idx} className="flex items-center gap-2">
{check.status === 'pending' && <Spinner className="h-4 w-4" />}
{check.status === 'success' && <CheckIcon className="h-4 w-4 text-green-500" />}
{check.status === 'error' && <XIcon className="h-4 w-4 text-red-500" />}
<span className={check.status === 'error' ? 'text-red-700' : 'text-gray-700'}>
{check.label}
</span>
{check.duration && (
<span className="text-xs text-gray-500">({check.duration}ms)</span>
)}
</div>
))}
</div>
```
---
## MCP Token Components
### 9. McpTokenCard
**Purpose:** Display token information in list view.
**Visual:**
```
┌──────────────────────────────────────────────────┐
│ Claude Desktop [Active] │
│ Permissions: Projects (R), Issues (R/W) │
│ Created: 2025-11-01 • Last Used: 2 hours ago │
│ Expires: In 30 days │
│ │
│ [View Details] [Revoke] │
└──────────────────────────────────────────────────┘
```
**Component Structure:**
```typescript
interface McpTokenCardProps {
token: McpToken;
onViewDetails: (tokenId: string) => void;
onRevoke: (tokenId: string) => void;
}
interface McpToken {
id: string;
name: string;
permissions: TokenPermissions;
createdAt: string;
lastUsedAt?: string;
expiresAt?: string;
status: TokenStatus;
usageCount: number;
}
type TokenStatus = 'Active' | 'Expired' | 'Revoked';
interface TokenPermissions {
[resource: string]: string[]; // e.g., { projects: ['read'], issues: ['read', 'create'] }
}
```
**Status Badge:**
```typescript
const statusConfig: Record<TokenStatus, { color: string; text: string }> = {
Active: { color: 'green', text: 'Active' },
Expired: { color: 'gray', text: 'Expired' },
Revoked: { color: 'red', text: 'Revoked' },
};
```
**Permissions Display:**
Format: `Resource (Operations)`
- R = Read
- C = Create
- U = Update
- D = Delete
- S = Search
Example: `Projects (R), Issues (R/C/U), Documents (R/C)`
Truncate if more than 3 resources, show "+X more"
**Time Display:**
```typescript
const formatRelativeTime = (date: string): string => {
const now = new Date();
const then = new Date(date);
const diffMs = now.getTime() - then.getTime();
const seconds = Math.floor(diffMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (minutes < 1) return 'Just now';
if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
if (days < 30) return `${days} day${days > 1 ? 's' : ''} ago`;
return then.toLocaleDateString();
};
```
**Hover State:**
- Border: primary-500
- Shadow: md
- Background: gray-50
**Actions:**
- "View Details" button: secondary variant
- "Revoke" button: danger text button
---
### 10. PermissionMatrix
**Purpose:** Interactive matrix for selecting token permissions.
**Visual:**
```
┌─────────────────────────────────────────────────┐
│ Resource │ Read │Create│Update│Delete│Search │
│────────────┼──────┼──────┼──────┼──────┼────── │
│ Projects │ ✓ │ │ │ │ ✓ │
│ Issues │ ✓ │ ✓ │ ✓ │ │ ✓ │
│ Documents │ ✓ │ ✓ │ │ │ ✓ │
│ Reports │ ✓ │ │ │ │ │
│ Sprints │ ✓ │ │ │ │ ✓ │
│ Comments │ ✓ │ ✓ │ │ │ │
└─────────────────────────────────────────────────┘
[Select All] [Clear All] [Templates ▼]
```
**Component Structure:**
```typescript
interface PermissionMatrixProps {
value: TokenPermissions;
onChange: (permissions: TokenPermissions) => void;
restrictions?: PermissionRestrictions;
}
interface PermissionRestrictions {
[resource: string]: {
allowedOperations: string[];
disabledMessage?: string;
};
}
interface PermissionCell {
resource: string;
operation: string;
enabled: boolean;
disabled?: boolean;
disabledReason?: string;
}
```
**Resources and Operations:**
```typescript
const resources = ['projects', 'issues', 'documents', 'reports', 'sprints', 'comments'];
const operations = ['read', 'create', 'update', 'delete', 'search'];
```
**Restrictions (Business Rules):**
```typescript
const defaultRestrictions: PermissionRestrictions = {
issues: {
allowedOperations: ['read', 'create', 'update', 'search'], // No delete
disabledMessage: 'Deleting issues is not allowed for safety',
},
users: {
allowedOperations: [], // No user management via MCP
disabledMessage: 'User management is not available via MCP tokens',
},
};
```
**Behavior:**
1. **Click checkbox:** Toggle permission
2. **Click row header:** Select all operations for that resource
3. **Click column header:** Select that operation for all resources
4. **Disabled cells:** Show tooltip with reason
5. **Templates:** Quick presets (Read Only, Read+Write, Custom)
**Templates:**
```typescript
const templates = {
'Read Only': {
projects: ['read', 'search'],
issues: ['read', 'search'],
documents: ['read', 'search'],
reports: ['read'],
},
'Read + Write': {
projects: ['read', 'search'],
issues: ['read', 'create', 'update', 'search'],
documents: ['read', 'create', 'search'],
reports: ['read'],
},
};
```
**Visual States:**
- **Enabled checkbox:**
- Unchecked: border-gray-300
- Checked: bg-primary-500, checkmark visible
- **Disabled checkbox:**
- Unchecked: border-gray-200, bg-gray-100
- Tooltip on hover
**Accessibility:**
- Table: role="table"
- Row headers: scope="row"
- Column headers: scope="col"
- Checkboxes: aria-label "{operation} permission for {resource}"
- Disabled cells: aria-disabled="true"
---
### 11. TokenDisplayModal
**Purpose:** One-time display of generated MCP token with copy functionality.
**Visual:**
```
┌────────────────────────────────────────────────┐
│ ✓ Token Created Successfully! [× ] │
├────────────────────────────────────────────────┤
│ │
│ ⚠️ IMPORTANT: Save this token now! │
│ │
│ This is the ONLY time you'll see this token. │
│ If you lose it, you'll need to generate a new │
│ one. │
│ │
│ Your Token: │
│ ┌──────────────────────────────────────────┐ │
│ │ mcp_acme_7f3d8a9c4e1b2f5a6d8c9e0f1a2b3 │ │
│ └──────────────────────────────────────────┘ │
│ [📋 Copy to Clipboard] │
│ │
│ [Download as .env file] │
│ │
│ [☐] I have saved this token securely │
│ │
│ [Close] │
└────────────────────────────────────────────────┘
```
**Component Structure:**
```typescript
interface TokenDisplayModalProps {
token: string;
tokenName: string;
onClose: () => void;
open: boolean;
}
interface TokenDisplayState {
copied: boolean;
downloaded: boolean;
acknowledged: boolean;
}
```
**Token Display Box:**
```tsx
<div className="bg-gray-50 rounded-lg border border-gray-300 p-4 my-4">
<code className="text-sm font-mono break-all select-all">
{token}
</code>
</div>
```
**Copy Button Behavior:**
```typescript
const handleCopy = async () => {
await navigator.clipboard.writeText(token);
setCopied(true);
toast.success('Token copied to clipboard!');
// Reset after 3 seconds
setTimeout(() => setCopied(false), 3000);
};
```
**Download .env File:**
```typescript
const handleDownload = () => {
const content = `# ColaFlow MCP Token
# Token Name: ${tokenName}
# Created: ${new Date().toISOString()}
# Expires: ${expiresAt || 'Never'}
COLAFLOW_MCP_TOKEN=${token}
`;
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'colaflow-mcp-token.env';
link.click();
URL.revokeObjectURL(url);
setDownloaded(true);
};
```
**Close Button State:**
- Disabled until checkbox is checked
- Shows tooltip: "Please confirm you've saved the token"
**Warning on Close (if not acknowledged):**
```
Are you sure you want to close?
You won't be able to see this token again.
[Go Back] [Close Anyway]
```
---
### 12. AuditLogTable
**Purpose:** Display MCP token usage audit log.
**Visual:**
```
┌──────────────────────────────────────────────────────────┐
│ Timestamp Action Resource Result IP │
│──────────────────────────────────────────────────────────│
│ Nov 3, 08:15 AM Read Issue #123 Success 192.168… │
│ Nov 3, 08:10 AM Create Issue #456 Success 192.168… │
│ Nov 3, 08:05 AM Update Issue #456 Success 192.168… │
│ Nov 3, 08:00 AM Read Project #1 Success 192.168… │
│ Nov 3, 07:55 AM Delete Issue #789 Failed 192.168… │
└──────────────────────────────────────────────────────────┘
Showing 1-50 of 1,247 operations
[Load More]
```
**Component Structure:**
```typescript
interface AuditLogTableProps {
logs: AuditLog[];
loading?: boolean;
onLoadMore?: () => void;
hasMore?: boolean;
}
interface AuditLog {
id: string;
timestamp: string;
action: string; // read, create, update, delete, search
resource: string; // Issue #123, Project #1
result: 'Success' | 'Failed';
ipAddress: string;
userAgent?: string;
errorMessage?: string;
durationMs: number;
}
```
**Result Badge:**
```typescript
const resultConfig = {
Success: { color: 'green', icon: CheckCircleIcon },
Failed: { color: 'red', icon: XCircleIcon },
};
```
**Row Hover:**
- Background: gray-50
- Cursor: pointer
- Show details on click (expandable row)
**Expandable Row Details:**
```
┌──────────────────────────────────────────────────────┐
│ Request Details │
│ Method: POST /api/mcp/issues │
│ Duration: 234ms │
│ User Agent: Claude Desktop 1.2.3 │
│ IP Address: 192.168.1.100 │
│ │
│ Error: Permission denied │
│ Message: Token does not have delete permission for │
│ issues. │
└──────────────────────────────────────────────────────┘
```
**Pagination:**
- Load more button (not traditional pagination)
- Infinite scroll (optional)
- Show "X of Y operations" count
**Filters (future):**
- Action type
- Result (success/failed)
- Date range
- Resource type
---
## Shared Components
### 13. ProgressIndicator
**Purpose:** Show progress through multi-step flows.
**Visual:**
```
● ○ ○ Step 1 of 3
✓ ● ○ Step 2 of 3
✓ ✓ ● Step 3 of 3
```
**Component Structure:**
```typescript
interface ProgressIndicatorProps {
steps: number;
currentStep: number;
labels?: string[];
variant?: 'dots' | 'line' | 'numbers';
}
```
**Variants:**
1. **Dots (default):**
- Completed: filled circle with checkmark
- Current: filled circle (primary color)
- Upcoming: outlined circle (gray)
- Size: 32px
- Gap: 16px
2. **Line:**
- Horizontal line connecting steps
- Progress fills from left to right
3. **Numbers:**
- Numbered steps instead of dots
- Better for >5 steps
**Implementation:**
```tsx
<div className="flex items-center justify-center gap-4">
{Array.from({ length: steps }, (_, i) => {
const stepNumber = i + 1;
const isCompleted = stepNumber < currentStep;
const isCurrent = stepNumber === currentStep;
return (
<div key={i} className="flex items-center gap-2">
<div className={cn(
"w-8 h-8 rounded-full flex items-center justify-center",
isCompleted && "bg-green-500 text-white",
isCurrent && "bg-primary-500 text-white",
!isCompleted && !isCurrent && "border-2 border-gray-300 text-gray-500"
)}>
{isCompleted ? <CheckIcon /> : stepNumber}
</div>
{i < steps - 1 && (
<div className="w-12 h-0.5 bg-gray-300" />
)}
</div>
);
})}
</div>
```
---
### 14. InfoTooltip
**Purpose:** Contextual help icon with tooltip.
**Visual:**
```
Company Slug * []
Hover shows tooltip
```
**Component Structure:**
```typescript
interface InfoTooltipProps {
content: React.ReactNode | string;
placement?: 'top' | 'right' | 'bottom' | 'left';
maxWidth?: number;
}
```
**Icon:**
- Component: Information Circle Icon
- Size: 16px
- Color: gray-400
- Hover: gray-600
**Tooltip:**
- Background: gray-900
- Text: white, text-sm
- Padding: 8px 12px
- Border-radius: 6px
- Max-width: 200px (default)
- Arrow: pointing to icon
- Animation: fade-in 200ms
- Delay: 300ms (on hover)
**Accessibility:**
- Icon button: role="button", aria-label="Show help"
- Tooltip: role="tooltip"
- Keyboard: Focus with Tab, show tooltip on Enter
---
### 15. EmptyState
**Purpose:** Placeholder when no data exists.
**Visual:**
```
┌────────────────────────────────────┐
│ [Icon/Illustration] │
│ │
│ No tokens yet │
│ │
│ Generate your first MCP token to │
│ connect AI agents to ColaFlow. │
│ │
│ [Generate Token] │
└────────────────────────────────────┘
```
**Component Structure:**
```typescript
interface EmptyStateProps {
icon?: React.ReactNode;
title: string;
description: string;
action?: {
label: string;
onClick: () => void;
};
secondaryAction?: {
label: string;
href: string;
};
}
```
**Layout:**
- Center-aligned
- Icon: 64x64px, gray-400
- Title: text-xl, font-semibold, gray-900
- Description: text-sm, gray-600, max-w-md
- Spacing: 16px between elements
- Action button: primary, large
**Common Variations:**
1. **No MCP Tokens:**
- Icon: Robot icon
- Action: "Generate Token"
2. **No SSO Config:**
- Icon: Shield icon
- Action: "Configure SSO"
3. **No Users:**
- Icon: Users icon
- Action: "Invite Team"
---
### 16. ConfirmDialog
**Purpose:** Confirmation modal for destructive actions.
**Visual:**
```
┌────────────────────────────────────┐
│ Revoke Token? │
├────────────────────────────────────┤
│ │
│ Are you sure you want to revoke │
│ this token? │
│ │
│ Token: Claude Desktop │
│ Last used: 2 hours ago │
│ │
│ This will: │
│ • Immediately stop all API access │
│ • Cannot be undone │
│ • Require generating a new token │
│ │
│ [Cancel] [Revoke Token] │
└────────────────────────────────────┘
```
**Component Structure:**
```typescript
interface ConfirmDialogProps {
open: boolean;
title: string;
description: string;
details?: React.ReactNode;
consequences?: string[];
confirmLabel?: string;
cancelLabel?: string;
variant?: 'danger' | 'warning' | 'info';
onConfirm: () => void | Promise<void>;
onCancel: () => void;
loading?: boolean;
}
```
**Variants:**
1. **Danger (red):**
- Confirm button: bg-red-500
- Icon: Alert triangle (red)
- Used for: delete, revoke, suspend
2. **Warning (yellow):**
- Confirm button: bg-yellow-500
- Icon: Warning icon (yellow)
- Used for: archival, downgrade
3. **Info (blue):**
- Confirm button: bg-primary-500
- Icon: Info icon (blue)
- Used for: non-destructive confirmations
**Behavior:**
- Backdrop: semi-transparent black
- Click outside: calls onCancel
- Escape key: calls onCancel
- Enter key: calls onConfirm (if confirm button focused)
- Loading state: disable buttons, show spinner
**Animation:**
- Enter: fade-in 200ms + scale from 0.95
- Exit: fade-out 200ms + scale to 0.95
---
## Component Props Reference
### Common Props
All components accept these base props:
```typescript
interface BaseComponentProps {
className?: string;
style?: React.CSSProperties;
testId?: string; // for e2e testing
'aria-label'?: string;
'aria-describedby'?: string;
}
```
### Form Field Props
All form fields accept:
```typescript
interface FormFieldProps extends BaseComponentProps {
label?: string;
helperText?: string;
error?: string;
required?: boolean;
disabled?: boolean;
autoFocus?: boolean;
}
```
### Button Props
```typescript
interface ButtonProps extends BaseComponentProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
fullWidth?: boolean;
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
onClick?: () => void;
type?: 'button' | 'submit' | 'reset';
}
```
### Modal Props
```typescript
interface ModalProps extends BaseComponentProps {
open: boolean;
onClose: () => void;
title?: string;
description?: string;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
closeOnBackdrop?: boolean;
closeOnEscape?: boolean;
showCloseButton?: boolean;
footer?: React.ReactNode;
}
```
---
## Implementation Guidelines
### Component File Structure
```
src/components/
├── registration/
│ ├── TenantSlugInput.tsx
│ ├── PasswordStrengthInput.tsx
│ └── SubscriptionPlanCard.tsx
├── auth/
│ ├── SsoButton.tsx
│ ├── LoginDivider.tsx
│ └── LoginForm.tsx
├── sso/
│ ├── SsoProviderSelect.tsx
│ ├── DomainListInput.tsx
│ └── SsoTestButton.tsx
├── mcp-tokens/
│ ├── McpTokenCard.tsx
│ ├── PermissionMatrix.tsx
│ ├── TokenDisplayModal.tsx
│ └── AuditLogTable.tsx
└── shared/
├── ProgressIndicator.tsx
├── InfoTooltip.tsx
├── EmptyState.tsx
└── ConfirmDialog.tsx
```
### Naming Conventions
- **Components:** PascalCase (e.g., `McpTokenCard`)
- **Files:** Match component name (e.g., `McpTokenCard.tsx`)
- **Props interfaces:** `{ComponentName}Props`
- **CSS classes:** kebab-case with BEM (if needed)
- **Test files:** `{ComponentName}.test.tsx`
- **Story files:** `{ComponentName}.stories.tsx` (Storybook)
### State Management
- **Local state:** Use `useState` for component-specific state
- **Form state:** Use `react-hook-form` for complex forms
- **Server state:** Use `TanStack Query` for API data
- **Global state:** Use Zustand for app-wide state
### Testing Requirements
Each component must have:
1. **Unit tests:** Component rendering and interaction
2. **Accessibility tests:** ARIA, keyboard navigation
3. **Visual tests:** Storybook stories for all variants
4. **Integration tests:** E2E flows with Playwright
---
## Conclusion
This component specification provides a complete blueprint for implementing the UI for multi-tenant, SSO, and MCP Token features. All components follow ColaFlow's design system and adhere to accessibility best practices.
**Next Steps:**
1. Review with development team
2. Create Storybook stories for each component
3. Begin implementation in priority order:
- Registration flow (M1)
- Login + SSO (M1)
- MCP Tokens (M2)
4. Conduct accessibility audit
5. User testing with prototypes
**Questions:** Contact UX/UI team at ux@colaflow.com