# 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 = { AzureAD: { name: 'Microsoft', icon: , bgColor: '#00A4EF', // Microsoft blue textColor: '#FFFFFF', hoverBgColor: '#0078D4', }, Google: { name: 'Google', icon: , bgColor: '#FFFFFF', textColor: '#3C4043', hoverBgColor: '#F8F9FA', }, Okta: { name: 'Okta', icon: , bgColor: '#007DC1', textColor: '#FFFFFF', hoverBgColor: '#005F96', }, GenericSaml: { name: 'SSO', icon: , 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
{text || 'OR'}
``` --- ## 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: , docsLink: '/docs/sso/azure-ad', }, { value: 'Google', label: 'Google Workspace', description: 'For Google Workspace (formerly G Suite)', icon: , docsLink: '/docs/sso/google', }, { value: 'Okta', label: 'Okta', description: 'For Okta Identity Cloud', icon: , docsLink: '/docs/sso/okta', }, { value: 'GenericSaml', label: 'Generic SAML 2.0', description: 'For any SAML 2.0 compliant IdP', icon: , 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; 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
{checks.map((check, idx) => (
{check.status === 'pending' && } {check.status === 'success' && } {check.status === 'error' && } {check.label} {check.duration && ( ({check.duration}ms) )}
))}
``` --- ## 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 = { 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
{token}
``` **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
{Array.from({ length: steps }, (_, i) => { const stepNumber = i + 1; const isCompleted = stepNumber < currentStep; const isCurrent = stepNumber === currentStep; return (
{isCompleted ? : stepNumber}
{i < steps - 1 && (
)}
); })}
``` --- ### 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; 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