diff --git a/packages/ui/PROJEKT.md b/packages/ui/PROJEKT.md index 98dcb2c31..c16280e9b 100644 --- a/packages/ui/PROJEKT.md +++ b/packages/ui/PROJEKT.md @@ -63,6 +63,7 @@ Migration vorbereiten - schrittweise neue Komponenten in Vue 3 entwickeln, die d | Variant Props | **Semantisch (vollständig)** | primary, secondary, danger, warning, success, info | | Dark Mode | **Tailwind CSS-Klassen** | Via `dark:` Prefix, kein "inverse" Prop | | Prop-Vollständigkeit | **Alle oder keine** | Wenn Komponente einen Prop unterstützt, dann die gesamte Skala | +| CSS Variables | **Keine Defaults in Library** | Webapp definiert Default-Branding, spezialisierte Brandings überschreiben | | TypeScript | **strict: true** | Strikte Typisierung | --- @@ -139,6 +140,39 @@ git log --oneline $(git describe --tags --match "ui-v*" --abbrev=0)..HEAD -- pac ``` → Wenn Output vorhanden: UI muss zuerst released werden. +### Webapp-Aufgaben (TODO für Ocelot-Webapp) + +Die folgenden Aufgaben müssen in der Webapp umgesetzt werden, nicht in der UI-Library: + +| Aufgabe | Beschreibung | Status | +|---------|--------------|--------| +| **Branding-Validierung** | Test/Workflow der `validateCssVariables()` aufruft und sicherstellt, dass das Default-Branding alle von der Library geforderten CSS-Variablen definiert | ⏳ Offen | + +**Beispiel-Implementierung (Webapp):** +```ts +// webapp/tests/branding.spec.ts +import { validateCssVariables, requiredCssVariables } from '@ocelot-social/ui/tailwind.preset' + +describe('Default Branding', () => { + it('defines all required CSS variables', () => { + // Load default branding CSS + // ... + + const styles = getComputedStyle(document.documentElement) + const missing: string[] = [] + + for (const variable of requiredCssVariables) { + const value = styles.getPropertyValue(variable).trim() + if (!value) { + missing.push(variable) + } + } + + expect(missing).toEqual([]) + }) +}) +``` + --- ## 4. CI/CD & Release-Automatisierung @@ -425,10 +459,10 @@ Bei der Migration werden: ### Phase 2: Projekt-Setup - [x] Vite + Vue 3 Projekt initialisieren - [x] vue-demi einrichten für Vue 2 Kompatibilität -- [ ] Tailwind CSS einrichten -- [ ] Dual-Build konfigurieren (Tailwind Preset + vorkompilierte CSS) -- [ ] CSS Custom Properties Token-System aufsetzen -- [ ] Dark Mode Grundstruktur +- [x] Tailwind CSS einrichten (v4 mit @tailwindcss/vite) +- [x] Dual-Build konfigurieren (Tailwind Preset + vorkompilierte CSS) +- [ ] CSS Custom Properties Token-System aufsetzen (Infrastruktur bereit, Tokens folgen mit Komponenten) +- [x] Dark Mode Grundstruktur (via Tailwind `dark:` Prefix, dokumentiert) - [ ] Histoire für Dokumentation einrichten - [x] Vitest konfigurieren - [ ] Vitest Vue 2/3 Matrix einrichten @@ -459,12 +493,34 @@ Bei der Migration werden: - [ ] Histoire Theme-Farben anpassen (ocelot.social Branding) - [ ] Token-Dokumentation in Histoire -### Phase 4: Komponenten-Migration -- [ ] _Komponenten werden nach Analyse-Ergebnis priorisiert_ -- [ ] _Für jede Komponente: Spec → Develop → Test → Integrate_ -- [ ] _Liste wird in Phase 0 erstellt_ -- [ ] System-Icons einrichten (bei Bedarf pro Komponente) -- [ ] CI docs-check Workflow (Prüft JSDoc-Coverage, README-Aktualität) +### Phase 4: Komponenten-Migration (15 Komponenten + 2 Infrastruktur) + +**Tier 1: Kern-Komponenten** +- [ ] OsIcon (vereint DsIcon + BaseIcon) +- [ ] OsSpinner (vereint DsSpinner + LoadingSpinner) +- [ ] OsButton (vereint DsButton + BaseButton) +- [ ] OsCard (vereint DsCard + BaseCard) + +**Tier 2: Layout & Feedback** +- [ ] OsModal (Basis: DsModal) +- [ ] OsDropdown (Basis: Webapp Dropdown) +- [ ] OsAvatar (vereint DsAvatar + ProfileAvatar) +- [ ] OsInput (Basis: DsInput) + +**Tier 3: Navigation & Typography** +- [ ] OsMenu (Basis: DsMenu) +- [ ] OsMenuItem (Basis: DsMenuItem) +- [ ] OsHeading (Basis: DsHeading) +- [ ] OsText (Basis: DsText) + +**Tier 4: Spezial-Komponenten** +- [ ] OsSelect +- [ ] OsTable +- [ ] OsTag + +**Infrastruktur** +- [ ] System-Icons einrichten +- [ ] CI docs-check Workflow (JSDoc-Coverage, README-Aktualität) ### Phase 5: Finalisierung - [ ] Alle Komponenten migriert und getestet @@ -483,12 +539,13 @@ Bei der Migration werden: ``` Phase 0: ██████████ 100% (6/6 Aufgaben) ✅ Phase 1: ██████████ 100% (6/6 Aufgaben) ✅ -Phase 2: ██░░░░░░░░ 22% (6/27 Aufgaben) +Phase 2: ███░░░░░░░ 33% (9/27 Aufgaben) Phase 3: ░░░░░░░░░░ 0% (0/7 Aufgaben) -Phase 4: ░░░░░░░░░░ 0% (0/18 Aufgaben) +Phase 4: ░░░░░░░░░░ 0% (0/17 Aufgaben) Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben) +Webapp: ░░░░░░░░░░ 0% (0/1 Aufgaben) ─────────────────────────────────────── -Gesamt: ██░░░░░░░░ ~18% +Gesamt: ███░░░░░░░ ~30% (21/71 Aufgaben) ``` ### Katalogisierung (Details in KATALOG.md) @@ -513,7 +570,7 @@ Integriert: 0 **Letzte Aktualisierung:** 2026-02-04 -**Aktuelle Phase:** Phase 2 (Projekt-Setup) - In Arbeit (19%) +**Aktuelle Phase:** Phase 2 (Projekt-Setup) - In Arbeit (33%) **Zuletzt abgeschlossen:** - [x] Projektordner erstellt @@ -542,10 +599,16 @@ Integriert: 0 - Vitest konfiguriert (integriert in vite.config.ts) - npm Package-Struktur mit korrekten exports - README.md Grundgerüst + - LICENSE (Apache 2.0) - Plugin-Tests geschrieben + - Tailwind CSS v4 mit @tailwindcss/vite + - Dual-Build (style.css + tailwind.preset) + - Dark Mode Grundstruktur (via Tailwind dark: Prefix) + - Prop-Types definiert (Size, Rounded, Shadow, Variant) + - Branding-Architektur (keine Defaults, validateCssVariables) **Aktuell in Arbeit:** -- Phase 2: Projekt-Setup (5/27 Aufgaben erledigt) +- Phase 2: Projekt-Setup (9/27 Aufgaben erledigt) **Nächste Schritte:** 1. ~~Phase 0: Komponenten-Analyse~~ ✅ @@ -626,6 +689,10 @@ Integriert: 0 | 62 | Variant Props | Semantisch (primary, secondary, danger, warning, success, info) | Übliche UI-Farbvarianten | | 63 | Dark Mode Handling | CSS-Klassen (`dark:` Prefix) | Standard-Tailwind-Pattern, keine "inverse" Props | | 64 | Prop-Vollständigkeit | Alle Werte einer Skala | Konsistente API, keine Teilmengen pro Komponente | +| 65 | CSS Variable Defaults | Keine Defaults in Library | Webapp definiert Branding, Library ist design-agnostisch | +| 66 | Branding-Hierarchie | Webapp → Spezialisiertes Branding | Default-Branding in Webapp, Overrides pro Instanz | +| 67 | Variable-Validierung | Runtime-Check in Development | `validateCssVariables()` warnt bei fehlenden Variablen | +| 68 | Branding-Test (Webapp) | CI-Test in Webapp | Webapp testet, dass Default-Branding alle Library-Variablen definiert | --- @@ -665,6 +732,9 @@ Integriert: 0 | 2026-02-04 | **Testing** | Vitest in vite.config.ts integriert, Plugin-Tests geschrieben | | 2026-02-04 | **Dokumentation** | README.md mit Installation und Usage (Tree-Shaking vs Plugin) | | 2026-02-04 | **Tailwind-Konventionen** | Size, Rounded, Shadow, Variant - vollständige Skalen, Dark Mode via CSS | +| 2026-02-04 | **Tailwind v4 Setup** | @tailwindcss/vite Plugin, Dual-Build (style.css + tailwind.preset) | +| 2026-02-04 | **Prop-Types** | src/types.d.ts mit Size, Rounded, Shadow, Variant | +| 2026-02-04 | **Branding-Architektur** | Keine Defaults in Library, Webapp definiert Branding, validateCssVariables() | --- diff --git a/packages/ui/package-lock.json b/packages/ui/package-lock.json index 6b9162270..4c7afaa32 100644 --- a/packages/ui/package-lock.json +++ b/packages/ui/package-lock.json @@ -12,11 +12,13 @@ "vue-demi": "^0.14.10" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.18", "@types/node": "^25.2.0", "@vitejs/plugin-vue": "^6.0.4", "@vitest/coverage-v8": "^4.0.18", "@vue/test-utils": "^2.4.6", "jsdom": "^28.0.0", + "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", "vite-plugin-dts": "^4.5.4", @@ -29,7 +31,13 @@ "node": ">=18.0.0" }, "peerDependencies": { + "tailwindcss": "^4.0.0", "vue": "^2.7.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } } }, "node_modules/@acemir/cssom": { @@ -769,6 +777,28 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1406,6 +1436,278 @@ "dev": true, "license": "MIT" }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@types/argparse": { "version": "1.0.38", "dev": true, @@ -2094,6 +2396,16 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "8.0.3", "dev": true, @@ -2129,6 +2441,20 @@ "dev": true, "license": "MIT" }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -2505,6 +2831,16 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jju": { "version": "1.4.0", "dev": true, @@ -2606,6 +2942,267 @@ "dev": true, "license": "MIT" }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/local-pkg": { "version": "1.1.2", "dev": true, @@ -2856,6 +3453,8 @@ }, "node_modules/postcss": { "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -3202,6 +3801,27 @@ "dev": true, "license": "MIT" }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tinybench": { "version": "2.9.0", "dev": true, diff --git a/packages/ui/package.json b/packages/ui/package.json index 248c3315c..8434007bc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -9,6 +9,12 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" + }, + "./style.css": "./dist/style.css", + "./tailwind.preset": { + "types": "./dist/tailwind.preset.d.ts", + "import": "./dist/tailwind.preset.mjs", + "require": "./dist/tailwind.preset.cjs" } }, "main": "./dist/index.cjs", @@ -28,17 +34,25 @@ "prepublishOnly": "npm run build" }, "peerDependencies": { - "vue": "^2.7.0 || ^3.0.0" + "vue": "^2.7.0 || ^3.0.0", + "tailwindcss": "^4.0.0" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } }, "dependencies": { "vue-demi": "^0.14.10" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.18", "@types/node": "^25.2.0", "@vitejs/plugin-vue": "^6.0.4", "@vitest/coverage-v8": "^4.0.18", "@vue/test-utils": "^2.4.6", "jsdom": "^28.0.0", + "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", "vite-plugin-dts": "^4.5.4", diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index c54056299..15443da01 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -5,8 +5,14 @@ * Works with Vue 2.7+ and Vue 3 */ +// Import styles (extracted to style.css during build) +import './styles/index.css' + // Re-export all components export * from './components' // Export Vue plugin for global registration export { default as OcelotUI } from './plugin' + +// Export prop types +export type { Size, Rounded, Shadow, Variant } from './types' diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css new file mode 100644 index 000000000..f1d8c73cd --- /dev/null +++ b/packages/ui/src/styles/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/packages/ui/src/tailwind.preset.spec.ts b/packages/ui/src/tailwind.preset.spec.ts new file mode 100644 index 000000000..ac5588572 --- /dev/null +++ b/packages/ui/src/tailwind.preset.spec.ts @@ -0,0 +1,96 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { ocelotPreset, requiredCssVariables, validateCssVariables } from './tailwind.preset' + +describe('tailwind.preset', () => { + describe('ocelotPreset', () => { + it('exports a valid Tailwind preset with theme.extend structure', () => { + expect(ocelotPreset).toBeDefined() + expect(ocelotPreset).toHaveProperty('theme') + expect(ocelotPreset.theme).toHaveProperty('extend') + }) + }) + + describe('requiredCssVariables', () => { + it('exports an array', () => { + expect(Array.isArray(requiredCssVariables)).toBe(true) + }) + + it('contains only strings', () => { + for (const variable of requiredCssVariables) { + expect(typeof variable).toBe('string') + } + }) + + it('all variables start with --', () => { + // This test validates the format constraint. + for (const variable of requiredCssVariables) { + expect(variable.startsWith('--')).toBe(true) + } + // Ensure test runs even with empty array + expect(requiredCssVariables.every((v) => v.startsWith('--'))).toBe(true) + }) + }) + + describe('validateCssVariables', () => { + const originalWindow = global.window + + afterEach(() => { + global.window = originalWindow + vi.restoreAllMocks() + }) + + it('does nothing when window is undefined (SSR)', () => { + // @ts-expect-error - simulating SSR environment + global.window = undefined + + expect(() => validateCssVariables()).not.toThrow() + }) + + it('does not warn when all variables are defined', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const mockGetPropertyValue = vi.fn().mockReturnValue('some-value') + + vi.stubGlobal('window', {}) + vi.stubGlobal('document', { + documentElement: {}, + }) + vi.stubGlobal('getComputedStyle', () => ({ + getPropertyValue: mockGetPropertyValue, + })) + + validateCssVariables() + + expect(consoleWarnSpy).not.toHaveBeenCalled() + }) + + it('warns when variables are missing', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const mockGetPropertyValue = vi.fn().mockReturnValue('') + + vi.stubGlobal('window', {}) + vi.stubGlobal('document', { + documentElement: {}, + }) + vi.stubGlobal('getComputedStyle', () => ({ + getPropertyValue: mockGetPropertyValue, + })) + + // Temporarily add a required variable for testing + const originalVariables = [...requiredCssVariables] + requiredCssVariables.push('--test-variable') + + validateCssVariables() + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('Missing required CSS variables') + ) + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('--test-variable') + ) + + // Restore original state + requiredCssVariables.length = 0 + requiredCssVariables.push(...originalVariables) + }) + }) +}) diff --git a/packages/ui/src/tailwind.preset.ts b/packages/ui/src/tailwind.preset.ts new file mode 100644 index 000000000..8ba532b84 --- /dev/null +++ b/packages/ui/src/tailwind.preset.ts @@ -0,0 +1,72 @@ +/** + * Tailwind CSS Preset for @ocelot-social/ui + * + * This preset defines CSS Custom Properties used by components. + * The library does NOT provide default values - the consuming app must define all variables. + * + * Branding hierarchy: + * 1. Webapp defines default branding (base colors) + * 2. Specialized brandings override the defaults + * + * Usage in your tailwind.config.js: + * ```js + * import { ocelotPreset } from '@ocelot-social/ui/tailwind.preset' + * + * export default { + * presets: [ocelotPreset], + * // your config... + * } + * ``` + * + * Required CSS Variables (defined by webapp): + * - See `requiredCssVariables` export for the full list + */ + +import type { Config } from 'tailwindcss' + +/** + * List of CSS Custom Properties that must be defined by the consuming app. + * This list grows as components are added to the library. + */ +export const requiredCssVariables: string[] = [ + // Currently empty - will be populated as components are added + // Example: '--color-primary', '--color-primary-hover', '--color-primary-contrast' +] + +/** + * Validates that all required CSS variables are defined. + * Call this in development to catch missing variables early. + * + * @example + * ```ts + * import { validateCssVariables } from '@ocelot-social/ui/tailwind.preset' + * + * if (process.env.NODE_ENV === 'development') { + * validateCssVariables() + * } + * ``` + */ +export function validateCssVariables(): void { + if (typeof window === 'undefined') return + + const styles = getComputedStyle(document.documentElement) + const missing = requiredCssVariables.filter( + (variable) => !styles.getPropertyValue(variable).trim() + ) + + if (missing.length > 0) { + console.warn( + `[@ocelot-social/ui] Missing required CSS variables:\n${missing.map((v) => ` - ${v}`).join('\n')}\n\nDefine these in your app's CSS.` + ) + } +} + +export const ocelotPreset: Partial = { + theme: { + extend: { + // Colors and other theme extensions will be added here as components are developed. + // All values use CSS Custom Properties WITHOUT defaults. + // Example: primary: { DEFAULT: 'var(--color-primary)' } + }, + }, +} diff --git a/packages/ui/src/types.d.ts b/packages/ui/src/types.d.ts new file mode 100644 index 000000000..dbd3bf433 --- /dev/null +++ b/packages/ui/src/types.d.ts @@ -0,0 +1,29 @@ +/** + * Component prop types based on Tailwind CSS scales + * + * These types ensure consistency across all components. + * When a component supports a prop, it must support all values of that scale. + */ + +/** + * Size scale for components (buttons, inputs, avatars, etc.) + * Maps to Tailwind's text/spacing scale + */ +export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' + +/** + * Border radius scale + * Maps to Tailwind's rounded-* utilities + */ +export type Rounded = 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full' + +/** + * Box shadow scale + * Maps to Tailwind's shadow-* utilities + */ +export type Shadow = 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' + +/** + * Semantic color variants for interactive components + */ +export type Variant = 'primary' | 'secondary' | 'danger' | 'warning' | 'success' | 'info' diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index 59811fd66..37bee89da 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' import dts from 'vite-plugin-dts' import tsconfigPaths from 'vite-tsconfig-paths' import { resolve } from 'path' @@ -7,27 +8,32 @@ import { resolve } from 'path' export default defineConfig({ plugins: [ vue(), + tailwindcss(), tsconfigPaths(), dts({ - include: ['src/**/*.ts', 'src/**/*.vue'], + include: ['src/**/*.ts', 'src/**/*.vue', 'src/**/*.d.ts'], outDir: 'dist', rollupTypes: true, + copyDtsFiles: true, }), ], build: { lib: { - entry: resolve(__dirname, 'src/index.ts'), - name: 'OcelotSocialUI', + entry: { + index: resolve(__dirname, 'src/index.ts'), + 'tailwind.preset': resolve(__dirname, 'src/tailwind.preset.ts'), + }, formats: ['es', 'cjs'], - fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`, + fileName: (format, entryName) => `${entryName}.${format === 'es' ? 'mjs' : 'cjs'}`, }, rollupOptions: { - external: ['vue', 'vue-demi'], + external: ['vue', 'vue-demi', 'tailwindcss'], output: { globals: { vue: 'Vue', 'vue-demi': 'VueDemi', }, + assetFileNames: 'style.[ext]', }, }, cssCodeSplit: false,