diff --git a/apps/site/app/global.css b/apps/site/app/global.css index 7408c0a4e..f0866d052 100644 --- a/apps/site/app/global.css +++ b/apps/site/app/global.css @@ -2,4 +2,80 @@ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; +/* Scan sources for Tailwind classes */ @source '../node_modules/fumadocs-ui/dist/**/*.js'; +@source '../../packages/components/src/**/*.{ts,tsx}'; +@source '../../packages/react/src/**/*.{ts,tsx}'; +@source '../../packages/plugin-*/src/**/*.{ts,tsx}'; + +/* ObjectUI component CSS custom properties */ +:root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; +} + +.dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; +} + +* { + border-color: hsl(var(--border)); +} + +body { + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); +} diff --git a/apps/site/tailwind.config.ts b/apps/site/tailwind.config.ts new file mode 100644 index 000000000..932911ba4 --- /dev/null +++ b/apps/site/tailwind.config.ts @@ -0,0 +1,57 @@ +import type { Config } from 'tailwindcss' + +export default { + darkMode: "class", + content: [ + './app/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './content/**/*.{md,mdx}', + '../../packages/components/src/**/*.{ts,tsx}', + '../../packages/react/src/**/*.{ts,tsx}', + '../../packages/plugin-*/src/**/*.{ts,tsx}', + ], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, + }, +} satisfies Config diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..b4fb7b7bf --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,170 @@ +# Component Style Testing + +This directory contains automated testing scripts for verifying that ObjectUI components render with proper styles in the fumadocs documentation site. + +## Scripts + +### 1. `test-component-styles.js` (Node.js) + +A comprehensive automated test that: +- Launches a headless browser +- Navigates to all component documentation pages +- Checks for proper CSS styling on components +- Takes screenshots for visual verification +- Reports errors and warnings + +**Usage:** +```bash +# Requires dev server to be running on http://localhost:3000 +node scripts/test-component-styles.js +``` + +**Features:** +- ✅ Tests 15+ critical components +- ✅ Validates background colors, borders, padding, etc. +- ✅ Captures screenshots for manual review +- ✅ Detects missing or invalid styles +- ✅ Distinguishes between critical and non-critical failures + +### 2. `test-component-styles.sh` (Bash) + +A wrapper script that: +- Automatically starts the dev/production server +- Runs the Node.js test script +- Cleans up the server after testing + +**Usage:** +```bash +# Test with dev server (default) +./scripts/test-component-styles.sh dev + +# Test with production build +./scripts/test-component-styles.sh build +``` + +## Quick Start + +1. **Install dependencies:** + ```bash + pnpm install + ``` + +2. **Run the tests:** + ```bash + # Option 1: Manual (requires server running) + cd apps/site && pnpm dev & + node scripts/test-component-styles.js + + # Option 2: Automatic + ./scripts/test-component-styles.sh + ``` + +3. **Check results:** + - Console output shows pass/fail for each component + - Screenshots saved to `/tmp/*.png` + - Exit code 0 = all tests passed, 1 = critical failures + +## Adding New Components + +To test a new component, edit `test-component-styles.js`: + +```javascript +const COMPONENT_PAGES = [ + // Add your component + { + path: '/docs/components/category/your-component', + name: 'Your Component', + critical: true // Set to true if it's a critical component + }, + // ... +]; +``` + +## Common Issues + +### Calendar/Date Picker Styling + +If calendar components show styling issues: + +1. **Check CSS custom properties**: Ensure `global.css` includes all design tokens +2. **Check Tailwind config**: Verify `tailwind.config.ts` maps CSS variables correctly +3. **Check content paths**: Ensure Tailwind scans all component source files +4. **Check for dynamic classes**: Some components use runtime-generated class names + +### Fixing Style Issues + +1. **Missing colors**: Add CSS custom property to `apps/site/app/global.css` +2. **Missing mapping**: Add color mapping to `apps/site/tailwind.config.ts` +3. **Classes not found**: Add path to `content` array in Tailwind config +4. **Component-specific**: Check component UI source in `packages/components/src/ui/` + +## CI Integration + +Add to your CI pipeline: + +```yaml +- name: Test Component Styles + run: | + pnpm build + pnpm start & + sleep 10 + node scripts/test-component-styles.js +``` + +## Output Format + +``` +🚀 Starting Component Style Tests... + +Testing: Button (/docs/components/form/button) +✅ PASSED - Button + +Testing: Calendar (/docs/components/data-display/calendar) +❌ FAILED - Calendar (3 errors, 1 warnings) + ⚠️ CRITICAL COMPONENT + +📊 TEST SUMMARY +================================================================================ + +Total Tests: 15 +Passed: 14 ✅ +Failed: 1 ❌ +Critical Failures: 1 ⚠️ + +Failed Components: + + ⚠️ Calendar: + Path: /docs/components/data-display/calendar + Errors: 3, Warnings: 1 + Screenshot: /tmp/calendar.png + - [ERROR] Missing or invalid background-color on button[class*="selected"] + - [ERROR] Missing or invalid color on button[class*="selected"] + - [ERROR] Missing or invalid border-color on [class*="border"] +``` + +## Troubleshooting + +**Server not starting:** +- Check if port 3000 is available +- Ensure all dependencies are installed (`pnpm install`) +- Try building first (`pnpm build`) + +**Tests timing out:** +- Increase timeout in script (default: 30s) +- Check for console errors in browser +- Verify components are actually rendering + +**False positives:** +- Some components may have intentionally transparent backgrounds +- Adjust `STYLE_CHECKS` in the script to match expected behavior +- Add component-specific validation rules + +## Maintenance + +Update the test script when: +- Adding new components to the documentation +- Changing component styling approach +- Modifying Tailwind configuration +- Updating design tokens + +Last updated: 2026-01-23 diff --git a/scripts/test-component-styles.js b/scripts/test-component-styles.js new file mode 100644 index 000000000..3e7030e73 --- /dev/null +++ b/scripts/test-component-styles.js @@ -0,0 +1,279 @@ +#!/usr/bin/env node + +/** + * Automated Component Style Testing Script + * + * This script tests all ObjectUI component styles in the fumadocs site + * by navigating to component pages and checking for proper styling. + */ + +const { chromium } = require('playwright'); +const path = require('path'); + +// Component pages to test +const COMPONENT_PAGES = [ + // Form components + { path: '/docs/components/form/button', name: 'Button', critical: true }, + { path: '/docs/components/form/input', name: 'Input', critical: true }, + { path: '/docs/components/form/date-picker', name: 'Date Picker', critical: true }, + { path: '/docs/components/form/select', name: 'Select', critical: true }, + { path: '/docs/components/form/checkbox', name: 'Checkbox', critical: true }, + { path: '/docs/components/form/switch', name: 'Switch', critical: true }, + { path: '/docs/components/form/slider', name: 'Slider', critical: true }, + + // Layout components + { path: '/docs/components/layout/card', name: 'Card', critical: true }, + { path: '/docs/components/layout/tabs', name: 'Tabs', critical: true }, + { path: '/docs/components/layout/grid', name: 'Grid', critical: false }, + + // Data Display + { path: '/docs/components/data-display/badge', name: 'Badge', critical: true }, + { path: '/docs/components/data-display/alert', name: 'Alert', critical: true }, + { path: '/docs/components/data-display/avatar', name: 'Avatar', critical: true }, + { path: '/docs/components/data-display/calendar', name: 'Calendar', critical: true }, + + // Overlay components + { path: '/docs/components/overlay/dialog', name: 'Dialog', critical: true }, + { path: '/docs/components/overlay/tooltip', name: 'Tooltip', critical: true }, + { path: '/docs/components/overlay/popover', name: 'Popover', critical: true }, +]; + +// CSS properties to check for proper styling +const STYLE_CHECKS = { + button: [ + { selector: 'button[class*="bg-primary"]', styles: ['background-color', 'color', 'padding'] }, + { selector: 'button[class*="bg-destructive"]', styles: ['background-color', 'color'] }, + ], + calendar: [ + { selector: '[class*="calendar"]', styles: ['display', 'border'] }, + { selector: 'button[class*="selected"]', styles: ['background-color', 'color'] }, + { selector: '[class*="day"]', styles: ['width', 'height'] }, + ], + general: [ + { selector: '[class*="border"]', styles: ['border-color', 'border-width'] }, + { selector: '[class*="bg-"]', styles: ['background-color'] }, + { selector: '[class*="text-"]', styles: ['color'] }, + ] +}; + +class ComponentStyleTester { + constructor(baseUrl = 'http://localhost:3000') { + this.baseUrl = baseUrl; + this.browser = null; + this.page = null; + this.results = []; + } + + async init() { + this.browser = await chromium.launch({ headless: true }); + this.page = await this.browser.newPage(); + } + + async close() { + await this.browser?.close(); + } + + async waitForComponents(timeout = 10000) { + // Wait for plugins to load + await this.page.waitForTimeout(2000); + + // Check if components are loaded (not showing "Loading plugins...") + const loadingText = await this.page.locator('text=Loading plugins...').count(); + if (loadingText > 0) { + await this.page.waitForTimeout(3000); + } + } + + async checkComponentStyles(componentName, checks) { + const issues = []; + + for (const check of checks) { + const elements = await this.page.locator(check.selector).all(); + + if (elements.length === 0) { + issues.push({ + severity: 'warning', + message: `No elements found for selector: ${check.selector}` + }); + continue; + } + + for (let i = 0; i < Math.min(elements.length, 3); i++) { + const element = elements[i]; + + for (const styleProp of check.styles) { + const value = await element.evaluate((el, prop) => { + return window.getComputedStyle(el).getPropertyValue(prop); + }, styleProp); + + // Check if style has a meaningful value + if (!value || value === 'auto' || value === 'none' || value === 'rgba(0, 0, 0, 0)') { + issues.push({ + severity: 'error', + message: `Missing or invalid ${styleProp} on ${check.selector}`, + value: value + }); + } + } + } + } + + return issues; + } + + async testComponentPage(componentConfig) { + const url = `${this.baseUrl}${componentConfig.path}`; + console.log(`\nTesting: ${componentConfig.name} (${url})`); + + try { + await this.page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); + await this.waitForComponents(); + + // Take screenshot + const screenshotPath = path.join('/tmp', `${componentConfig.name.toLowerCase().replace(/\s+/g, '-')}.png`); + await this.page.screenshot({ + path: screenshotPath, + fullPage: false + }); + + // Check for general styles + const generalIssues = await this.checkComponentStyles( + componentConfig.name, + STYLE_CHECKS.general + ); + + // Check for component-specific styles + const componentType = componentConfig.name.toLowerCase().replace(/\s+/g, '-'); + const specificChecks = STYLE_CHECKS[componentType] || []; + const specificIssues = await this.checkComponentStyles( + componentConfig.name, + specificChecks + ); + + const allIssues = [...generalIssues, ...specificIssues]; + const errorCount = allIssues.filter(i => i.severity === 'error').length; + const warningCount = allIssues.filter(i => i.severity === 'warning').length; + + const result = { + name: componentConfig.name, + path: componentConfig.path, + url: url, + screenshot: screenshotPath, + passed: errorCount === 0, + critical: componentConfig.critical, + errors: errorCount, + warnings: warningCount, + issues: allIssues + }; + + this.results.push(result); + + if (result.passed) { + console.log(`✅ PASSED - ${componentConfig.name}`); + } else { + console.log(`❌ FAILED - ${componentConfig.name} (${errorCount} errors, ${warningCount} warnings)`); + if (componentConfig.critical) { + console.log(' ⚠️ CRITICAL COMPONENT'); + } + } + + return result; + + } catch (error) { + console.error(`❌ ERROR - ${componentConfig.name}: ${error.message}`); + this.results.push({ + name: componentConfig.name, + path: componentConfig.path, + url: url, + passed: false, + critical: componentConfig.critical, + errors: 1, + warnings: 0, + issues: [{ severity: 'error', message: error.message }] + }); + } + } + + async runAllTests() { + console.log('🚀 Starting Component Style Tests...\n'); + console.log(`Testing ${COMPONENT_PAGES.length} component pages\n`); + + for (const component of COMPONENT_PAGES) { + await this.testComponentPage(component); + } + } + + printSummary() { + console.log('\n' + '='.repeat(80)); + console.log('📊 TEST SUMMARY'); + console.log('='.repeat(80) + '\n'); + + const totalTests = this.results.length; + const passed = this.results.filter(r => r.passed).length; + const failed = this.results.filter(r => !r.passed).length; + const criticalFailed = this.results.filter(r => !r.passed && r.critical).length; + + console.log(`Total Tests: ${totalTests}`); + console.log(`Passed: ${passed} ✅`); + console.log(`Failed: ${failed} ❌`); + console.log(`Critical Failures: ${criticalFailed} ⚠️\n`); + + if (failed > 0) { + console.log('Failed Components:'); + this.results + .filter(r => !r.passed) + .forEach(r => { + console.log(`\n ${r.critical ? '⚠️ ' : ''}${r.name}:`); + console.log(` Path: ${r.path}`); + console.log(` Errors: ${r.errors}, Warnings: ${r.warnings}`); + console.log(` Screenshot: ${r.screenshot}`); + + // Show first 3 issues + r.issues.slice(0, 3).forEach(issue => { + console.log(` - [${issue.severity.toUpperCase()}] ${issue.message}`); + }); + + if (r.issues.length > 3) { + console.log(` ... and ${r.issues.length - 3} more issues`); + } + }); + } + + console.log('\n' + '='.repeat(80)); + + // Exit with error code if critical tests failed + if (criticalFailed > 0) { + console.log('\n❌ Critical components failed! Please fix before deploying.'); + return 1; + } else if (failed > 0) { + console.log('\n⚠️ Some tests failed, but no critical components affected.'); + return 0; + } else { + console.log('\n✅ All component style tests passed!'); + return 0; + } + } +} + +async function main() { + const tester = new ComponentStyleTester(); + + try { + await tester.init(); + await tester.runAllTests(); + const exitCode = tester.printSummary(); + await tester.close(); + process.exit(exitCode); + } catch (error) { + console.error('Fatal error:', error); + await tester.close(); + process.exit(1); + } +} + +// Run if called directly +if (require.main === module) { + main().catch(console.error); +} + +module.exports = { ComponentStyleTester }; diff --git a/scripts/test-component-styles.sh b/scripts/test-component-styles.sh new file mode 100755 index 000000000..57aceb3e9 --- /dev/null +++ b/scripts/test-component-styles.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Automated Component Style Testing Script (Bash Version) +# Usage: ./scripts/test-component-styles.sh [dev|build] + +set -e + +MODE="${1:-dev}" +SITE_DIR="apps/site" +TEST_URL="http://localhost:3000" + +echo "🚀 Component Style Testing Script" +echo "==================================" +echo "" + +# Check if we need to start the dev server +if [ "$MODE" = "dev" ]; then + echo "Starting dev server..." + cd "$SITE_DIR" + pnpm dev & + SERVER_PID=$! + + # Wait for server to be ready + echo "Waiting for server to start..." + sleep 10 + + # Check if server is running + if ! curl -s "$TEST_URL" > /dev/null; then + echo "❌ Server failed to start" + kill $SERVER_PID 2>/dev/null || true + exit 1 + fi + + echo "✅ Server started successfully" + cd ../.. +elif [ "$MODE" = "build" ]; then + echo "Building site..." + cd "$SITE_DIR" + pnpm build + + echo "Starting production server..." + pnpm start & + SERVER_PID=$! + + sleep 10 + cd ../.. +fi + +# Run the Node.js test script +echo "" +echo "Running component style tests..." +node scripts/test-component-styles.js + +TEST_EXIT_CODE=$? + +# Cleanup: stop the server if we started it +if [ ! -z "$SERVER_PID" ]; then + echo "" + echo "Stopping server..." + kill $SERVER_PID 2>/dev/null || true +fi + +exit $TEST_EXIT_CODE