39 KiB
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
- Design System Foundation
- Registration Components
- Login Components
- SSO Configuration Components
- MCP Token Components
- Shared Components
- Component Props Reference
Design System Foundation
Color System
Based on ColaFlow's existing design system:
// 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
// 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)
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
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
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:
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:
-
Idle: No input yet
- Border: gray-300
- No icon
-
Checking: Validating in real-time
- Border: primary-500
- Icon: Spinner (animated)
- Message: "Checking availability..."
-
Valid: Slug is available
- Border: green-500
- Icon: Checkmark (green)
- Message: "✓ acme.colaflow.com is available"
-
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)
-
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:
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:
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:
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:
-
Unselected:
- Border: gray-300
- Background: white
- Shadow: sm
-
Hover:
- Border: primary-500
- Shadow: md
- Transform: scale(1.02)
- Transition: 200ms ease-out
-
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:
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:
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:
-
Default:
- Provider-specific colors
- Border: none
- Height: 48px (large)
- Padding: 12px 24px
- Font: medium weight
-
Hover:
- Background: hoverBgColor
- Shadow: md
- Cursor: pointer
-
Loading:
- Spinner animation (white for dark bg, gray for light bg)
- Text: "Signing in..."
- Disabled cursor
-
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:
interface LoginDividerProps {
text?: string; // Default: "OR"
className?: string;
}
Implementation:
<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:
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:
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:
interface DomainListInputProps {
domains: string[];
onChange: (domains: string[]) => void;
placeholder?: string;
helperText?: string;
}
interface ValidationResult {
isValid: boolean;
error?: string;
}
Validation:
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:
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:
-
Metadata Endpoint Reachable
- Request: GET metadata URL
- Success: 200 status
- Time: <500ms
-
OIDC Discovery Valid (OIDC only)
- Parse JSON
- Validate required fields
- Time: <100ms
-
Certificate Valid (SAML only)
- Parse X.509 certificate
- Check expiration
- Time: <100ms
-
Callback URL Whitelisted
- Attempt authorization request
- Check for redirect_uri_mismatch
- Time: <1000ms
States:
-
Idle:
- Button: secondary variant
- Text: "Test Connection"
- Icon: none
-
Testing:
- Button: loading spinner
- Text: "Testing..."
- Show progress: checks updating in real-time
-
Success:
- Button: disabled
- Results: all checks green
- Final message: "All checks passed ✓"
-
Failure:
- Button: enabled (can retry)
- Results: failed checks red
- Show error details
- Link to troubleshooting docs
Results Display:
<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:
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:
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:
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:
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:
const resources = ['projects', 'issues', 'documents', 'reports', 'sprints', 'comments'];
const operations = ['read', 'create', 'update', 'delete', 'search'];
Restrictions (Business Rules):
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:
- Click checkbox: Toggle permission
- Click row header: Select all operations for that resource
- Click column header: Select that operation for all resources
- Disabled cells: Show tooltip with reason
- Templates: Quick presets (Read Only, Read+Write, Custom)
Templates:
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:
interface TokenDisplayModalProps {
token: string;
tokenName: string;
onClose: () => void;
open: boolean;
}
interface TokenDisplayState {
copied: boolean;
downloaded: boolean;
acknowledged: boolean;
}
Token Display Box:
<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:
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:
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:
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:
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:
interface ProgressIndicatorProps {
steps: number;
currentStep: number;
labels?: string[];
variant?: 'dots' | 'line' | 'numbers';
}
Variants:
-
Dots (default):
- Completed: filled circle with checkmark
- Current: filled circle (primary color)
- Upcoming: outlined circle (gray)
- Size: 32px
- Gap: 16px
-
Line:
- Horizontal line connecting steps
- Progress fills from left to right
-
Numbers:
- Numbered steps instead of dots
- Better for >5 steps
Implementation:
<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:
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:
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:
-
No MCP Tokens:
- Icon: Robot icon
- Action: "Generate Token"
-
No SSO Config:
- Icon: Shield icon
- Action: "Configure SSO"
-
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:
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:
-
Danger (red):
- Confirm button: bg-red-500
- Icon: Alert triangle (red)
- Used for: delete, revoke, suspend
-
Warning (yellow):
- Confirm button: bg-yellow-500
- Icon: Warning icon (yellow)
- Used for: archival, downgrade
-
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:
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:
interface FormFieldProps extends BaseComponentProps {
label?: string;
helperText?: string;
error?: string;
required?: boolean;
disabled?: boolean;
autoFocus?: boolean;
}
Button Props
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
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
useStatefor component-specific state - Form state: Use
react-hook-formfor complex forms - Server state: Use
TanStack Queryfor API data - Global state: Use Zustand for app-wide state
Testing Requirements
Each component must have:
- Unit tests: Component rendering and interaction
- Accessibility tests: ARIA, keyboard navigation
- Visual tests: Storybook stories for all variants
- 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:
- Review with development team
- Create Storybook stories for each component
- Begin implementation in priority order:
- Registration flow (M1)
- Login + SSO (M1)
- MCP Tokens (M2)
- Conduct accessibility audit
- User testing with prototypes
Questions: Contact UX/UI team at ux@colaflow.com