From 348186a395209b71e59123fae4cae50b7ebb1812 Mon Sep 17 00:00:00 2001 From: Giuliano Silvestro Date: Sat, 7 Feb 2026 21:02:21 +1000 Subject: [PATCH] feat: create ai-elements-ui demo application Demo app showcasing ai-elements-ui components: - AI Canvas with resizable split-panel layout - Conversation history sidebar - Avatar with animated states (idle, thinking, speaking) - Typing indicators (dots, pulse, wave variants) - Confidence meters (bar, gauge, dots, ring variants) - Code blocks with syntax highlighting - Skeleton loaders for various content types Uses base-ui design tokens and Apple theme for styling. Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 71 ++++- package.json | 3 + src/app/app.component.html | 535 ++++++++++++++----------------------- src/app/app.component.scss | 7 + src/app/app.component.ts | 62 ++++- src/styles.scss | 149 ++++++++++- tsconfig.json | 9 +- 7 files changed, 499 insertions(+), 337 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b6fc7e..5cf08c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "ai-elements-demo", "version": "0.0.0", "dependencies": { + "@angular/animations": "^19.2.0", "@angular/common": "^19.2.0", "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", @@ -15,6 +16,8 @@ "@angular/platform-browser": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0", "@angular/router": "^19.2.0", + "ai-elements-ui": "file:../ai-elements-ui/dist", + "base-ui": "file:../sda-frontend/libs/base-ui/dist", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -33,6 +36,46 @@ "typescript": "~5.7.2" } }, + "../ai-elements-ui/dist": { + "name": "ai-elements-ui", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^19.0.0", + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@angular/forms": "^19.0.0", + "@angular/platform-browser": "^19.0.0", + "@angular/router": "^19.0.0", + "base-ui": "^0.1.0", + "rxjs": "^7.8.0" + }, + "peerDependenciesMeta": { + "base-ui": { + "optional": true + } + } + }, + "../sda-frontend/libs/base-ui/dist": { + "name": "base-ui", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^19.0.0", + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@angular/forms": "^19.0.0", + "@angular/platform-browser": "^19.0.0", + "@angular/router": "^19.0.0", + "rxjs": "^7.8.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -306,6 +349,23 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular/animations": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.18.tgz", + "integrity": "sha512-c76x1t+OiSstPsvJdHmV8Q4taF+8SxWKqiY750fOjpd01it4jJbU6YQqIroC6Xie7154zZIxOTHH2uTj+nm5qA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "19.2.18", + "@angular/core": "19.2.18" + } + }, "node_modules/@angular/build": { "version": "19.2.19", "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.19.tgz", @@ -6003,6 +6063,10 @@ "node": ">= 14" } }, + "node_modules/ai-elements-ui": { + "resolved": "../ai-elements-ui/dist", + "link": true + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -6276,6 +6340,10 @@ "dev": true, "license": "MIT" }, + "node_modules/base-ui": { + "resolved": "../sda-frontend/libs/base-ui/dist", + "link": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9544,6 +9612,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -12537,7 +12606,6 @@ "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -14598,6 +14666,7 @@ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", diff --git a/package.json b/package.json index 33169f1..dd3cbae 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "private": true, "dependencies": { + "@angular/animations": "^19.2.0", "@angular/common": "^19.2.0", "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", @@ -17,6 +18,8 @@ "@angular/platform-browser": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0", "@angular/router": "^19.2.0", + "ai-elements-ui": "file:../ai-elements-ui/dist", + "base-ui": "file:../sda-frontend/libs/base-ui/dist", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..7aeee52 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1,211 @@ - - - - - - - - - - - -
-
-
-
+ +
+ - -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - + + + +
+ + +
+ + + + + - + + +
+

Skeleton Loaders

+

+ Animated placeholders for loading states. +

+
+
+

Text Skeleton

+
+ +
+
+
+

Message Skeleton

+
+ +
+
+
+

Card Skeleton

+
+ +
+
+
+

Avatar Skeleton

+
+ +
+
+
+
+ } + }
-
-
- - - - - - - - - - - + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..d53e798 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,7 @@ +// App component styles are in global styles.scss +// Keep component-specific overrides here if needed + +:host { + display: block; + height: 100%; +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 274ab84..3d6981c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,66 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { Component, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + AiCanvasComponent, + AiTypingIndicatorComponent, + AiAvatarComponent, + AiConfidenceMeterComponent, + AiSkeletonLoaderComponent, + AiCodeBlockComponent, + AiConversationHistoryComponent +} from 'ai-elements-ui'; +import type { Conversation } from 'ai-elements-ui'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + standalone: true, + imports: [ + CommonModule, + AiCanvasComponent, + AiTypingIndicatorComponent, + AiAvatarComponent, + AiConfidenceMeterComponent, + AiSkeletonLoaderComponent, + AiCodeBlockComponent, + AiConversationHistoryComponent + ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { - title = 'ai-elements-demo'; + // Demo state + currentView = signal<'canvas' | 'components'>('components'); + avatarState = signal<'idle' | 'thinking' | 'speaking'>('idle'); + + // Conversations + conversations: Conversation[] = [ + { id: '1', title: 'Project Planning', preview: 'Let\'s discuss the roadmap...', timestamp: new Date(), messageCount: 12, pinned: true }, + { id: '2', title: 'Code Review', preview: 'Can you review this function?', timestamp: new Date(Date.now() - 86400000), messageCount: 8 }, + { id: '3', title: 'Bug Investigation', preview: 'There\'s an issue with...', timestamp: new Date(Date.now() - 172800000), messageCount: 5 } + ]; + + // Sample code + sampleCode = `function fibonacci(n: number): number { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +// Calculate first 10 fibonacci numbers +const results = Array.from({ length: 10 }, (_, i) => fibonacci(i)); +console.log(results);`; + + setView(view: 'canvas' | 'components') { + this.currentView.set(view); + } + + onConversationSelect(conversation: Conversation) { + console.log('Selected conversation:', conversation); + } + + cycleAvatarState() { + const states: ('idle' | 'thinking' | 'speaking')[] = ['idle', 'thinking', 'speaking']; + const currentIndex = states.indexOf(this.avatarState()); + const nextIndex = (currentIndex + 1) % states.length; + this.avatarState.set(states[nextIndex]); + } } diff --git a/src/styles.scss b/src/styles.scss index 90d4ee0..ecab38f 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,148 @@ -/* You can add global styles to this file, and also import other style files */ +// Import base-ui design tokens +@use '../../sda-frontend/libs/base-ui/src/styles/tokens/primitive'; +@use '../../sda-frontend/libs/base-ui/src/styles/tokens/semantic'; +@use '../../sda-frontend/libs/base-ui/src/styles/tokens/component'; + +// Import Apple theme (applies via class) +@use '../../sda-frontend/libs/base-ui/src/styles/themes/apple'; + +// Base resets +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body { + height: 100%; + font-family: var(--font-family-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); + font-size: var(--font-size-base, 16px); + line-height: var(--line-height-base, 1.5); + color: var(--color-text-primary); + background-color: var(--color-bg-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// Apply theme class to body +body { + @extend .theme-apple; +} + +// App layout +.app { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.app__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md, 16px) var(--spacing-lg, 24px); + background: var(--color-bg-secondary, #f5f5f5); + border-bottom: 1px solid var(--color-border-primary, #e0e0e0); +} + +.app__logo { + display: flex; + align-items: center; + gap: var(--spacing-sm, 8px); + font-size: var(--font-size-lg, 18px); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary); +} + +.app__nav { + display: flex; + gap: var(--spacing-md, 16px); +} + +.app__nav-link { + padding: var(--spacing-sm, 8px) var(--spacing-md, 16px); + color: var(--color-text-secondary); + text-decoration: none; + border: none; + background: transparent; + border-radius: var(--radius-md, 8px); + cursor: pointer; + font-size: inherit; + transition: all 0.2s ease; + + &:hover { + color: var(--color-text-primary); + background: var(--color-bg-tertiary, #ebebeb); + } + + &.active { + color: var(--color-primary-500, #007aff); + background: rgba(0, 122, 255, 0.1); + } +} + +.app__main { + flex: 1; + display: flex; + overflow: hidden; +} + +.app__sidebar { + width: 280px; + background: var(--color-bg-secondary, #f5f5f5); + border-right: 1px solid var(--color-border-primary, #e0e0e0); + overflow-y: auto; +} + +.app__content { + flex: 1; + overflow-y: auto; + padding: var(--spacing-lg, 24px); +} + +// Demo sections +.demo-section { + margin-bottom: var(--spacing-xl, 32px); + padding: var(--spacing-lg, 24px); + background: var(--color-bg-secondary, #f5f5f5); + border: 1px solid var(--color-border-primary, #e0e0e0); + border-radius: var(--radius-lg, 12px); +} + +.demo-section__title { + margin-bottom: var(--spacing-md, 16px); + font-size: var(--font-size-xl, 24px); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary); +} + +.demo-section__description { + margin-bottom: var(--spacing-lg, 24px); + color: var(--color-text-secondary); +} + +.demo-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--spacing-md, 16px); +} + +.demo-card { + background: var(--color-bg-primary, #fff); + border: 1px solid var(--color-border-primary, #e0e0e0); + border-radius: var(--radius-lg, 12px); + overflow: hidden; + + h3 { + padding: var(--spacing-sm, 8px) var(--spacing-md, 16px); + margin: 0; + font-size: var(--font-size-sm, 14px); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-secondary); + background: var(--color-bg-secondary, #f5f5f5); + border-bottom: 1px solid var(--color-border-primary, #e0e0e0); + } +} diff --git a/tsconfig.json b/tsconfig.json index 5525117..a498b31 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,14 @@ "moduleResolution": "bundler", "importHelpers": true, "target": "ES2022", - "module": "ES2022" + "module": "ES2022", + "baseUrl": ".", + "paths": { + "base-ui": ["../sda-frontend/libs/base-ui/dist"], + "base-ui/*": ["../sda-frontend/libs/base-ui/dist/*"], + "ai-elements-ui": ["../ai-elements-ui/dist"], + "ai-elements-ui/*": ["../ai-elements-ui/dist/*"] + } }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false,