From 91a09df7ab38a1a203c33cf81fd07b80383ba33c Mon Sep 17 00:00:00 2001 From: Cesar Mendivil Date: Tue, 11 Nov 2025 18:14:02 -0700 Subject: [PATCH] Unify ui-components into avanza-ui; remove ui-components package; fix type issues and studio-panel wrappers --- .playwright-mcp/streamyard-initial.png | Bin 0 -> 61092 bytes docs/STREAMYARD_STUDIO_UI_ANALYSIS.md | 558 ++++++++++++++++++ docs/STREAMYARD_UI_ANALYSIS.md | 373 ++++++++++++ package-lock.json | 312 +--------- packages/avanza-ui/README.md | 227 +++++++ .../{ui-components => avanza-ui}/package.json | 20 +- .../src/components/Accordion.module.css | 0 .../src/components/Accordion.tsx | 0 .../src/components/Alert.module.css | 0 .../src/components/Alert.tsx | 0 .../src/components/Avatar.module.css | 0 .../src/components/Avatar.tsx | 0 .../src/components/Badge.module.css | 0 .../src/components/Badge.tsx | 0 .../src/components/Breadcrumb.module.css | 0 .../src/components/Breadcrumb.tsx | 0 .../src/components/Button.module.css | 0 .../src/components/Button.tsx | 0 .../src/components/Button/Button.css | 220 +++++++ .../src/components/Button/Button.tsx | 98 +++ .../avanza-ui/src/components/Button/index.ts | 4 + .../src/components/Card.module.css | 0 .../src/components/Card.tsx | 0 .../src/components/Checkbox.module.css | 0 .../src/components/Checkbox.tsx | 0 .../src/components/ControlButton.module.css | 80 +++ .../src/components/ControlButton.tsx | 55 ++ .../src/components/Dropdown.module.css | 0 .../src/components/Dropdown.tsx | 0 .../src/components/Input.module.css | 0 .../src/components/Input.tsx | 11 +- .../src/components/Modal.module.css | 0 .../src/components/Modal.tsx | 0 .../src/components/Pagination.module.css | 0 .../src/components/Pagination.tsx | 0 .../src/components/Progress.module.css | 0 .../src/components/Progress.tsx | 0 .../src/components/Radio.module.css | 0 .../src/components/Radio.tsx | 0 .../src/components/SceneCard.module.css | 74 +++ .../avanza-ui/src/components/SceneCard.tsx | 102 ++++ .../src/components/Select.module.css | 0 .../src/components/Select.tsx | 0 .../src/components/Spinner.module.css | 0 .../src/components/Spinner.tsx | 0 .../src/components/StudioHeader.module.css | 57 ++ .../avanza-ui/src/components/StudioHeader.tsx | 50 ++ .../src/components/Switch.module.css | 0 .../src/components/Switch.tsx | 0 .../src/components/Tabs.module.css | 0 .../src/components/Tabs.tsx | 0 .../src/components/Textarea.module.css | 0 .../src/components/Textarea.tsx | 3 +- .../src/components/Tooltip.module.css | 0 .../src/components/Tooltip.tsx | 0 .../src/components/VideoTile.module.css | 129 ++++ .../avanza-ui/src/components/VideoTile.tsx | 160 +++++ packages/avanza-ui/src/components/index.ts | 76 +++ packages/avanza-ui/src/global.d.ts | 22 + .../{ui-components => avanza-ui}/src/index.ts | 13 + .../src/styles/globals.css | 0 .../avanza-ui/src/styles/studio-theme.css | 259 ++++++++ .../src/types/css-modules.d.ts | 0 .../src/types/index.ts | 0 .../src/utils/helpers.ts | 0 .../tsconfig.json | 17 +- packages/studio-panel/.storybook/main.ts | 21 + packages/studio-panel/.storybook/preview.tsx | 36 ++ packages/studio-panel/MIGRATION_REPORT.md | 275 +++++++++ packages/studio-panel/README.md | 326 +++++++++- packages/studio-panel/STREAMYARD_REDESIGN.md | 280 +++++++++ packages/studio-panel/index.html | 12 +- packages/studio-panel/package.json | 29 +- packages/studio-panel/src/App.css | 46 ++ packages/studio-panel/src/App.tsx | 190 +++++- .../studio-panel/src/components/Avatar.tsx | 42 +- .../studio-panel/src/components/Button.tsx | 25 +- .../studio-panel/src/components/ChatPanel.tsx | 134 ++++- .../src/components/ControlBar.tsx | 47 +- .../studio-panel/src/components/Header.tsx | 46 +- .../src/components/LivekitConnector.tsx | 92 ++- .../src/components/LowerThird.tsx | 48 +- .../studio-panel/src/components/Roster.tsx | 97 ++- .../studio-panel/src/components/Sidebar.tsx | 72 ++- .../src/components/StudioLayout.tsx | 23 +- .../src/components/StudioRoom/StudioRoom.css | 216 +++++++ .../src/components/StudioRoom/StudioRoom.tsx | 117 ++++ .../src/components/ThemeToggle.tsx | 19 +- .../studio-panel/src/components/VideoGrid.tsx | 2 +- .../VideoParticipant/VideoParticipant.css | 70 +++ .../VideoParticipant/VideoParticipant.tsx | 22 + .../studio-panel/src/components/VideoTile.tsx | 153 +---- packages/studio-panel/src/global.d.ts | 32 + packages/studio-panel/src/main.tsx | 15 +- .../src/stories/Button.stories.tsx | 316 ++++++++++ packages/studio-panel/src/styles/studio.css | 244 ++++++++ packages/studio-panel/tsconfig.json | 10 +- packages/studio-panel/vite.config.ts | 17 +- packages/ui-components/COMPLETION_REPORT.md | 279 --------- packages/ui-components/COMPONENTS_UPDATE.md | 400 ------------- packages/ui-components/GUIDE.md | 300 ---------- packages/ui-components/INDEX.md | 162 ----- packages/ui-components/QUICKSTART.md | 173 ------ packages/ui-components/QUICK_REFERENCE.md | 413 ------------- packages/ui-components/README.md | 264 --------- .../ui-components/STUDIO_IMPLEMENTATION.md | 335 ----------- packages/ui-components/SUMMARY.md | 291 --------- packages/ui-components/UPDATE_V2.md | 433 -------------- .../ui-components/VERIFICATION_CHECKLIST.md | 325 ---------- packages/ui-components/rollup.config.js | 38 -- 110 files changed, 5308 insertions(+), 4099 deletions(-) create mode 100644 .playwright-mcp/streamyard-initial.png create mode 100644 docs/STREAMYARD_STUDIO_UI_ANALYSIS.md create mode 100644 docs/STREAMYARD_UI_ANALYSIS.md create mode 100644 packages/avanza-ui/README.md rename packages/{ui-components => avanza-ui}/package.json (77%) rename packages/{ui-components => avanza-ui}/src/components/Accordion.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Accordion.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Alert.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Alert.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Avatar.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Avatar.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Badge.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Badge.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Breadcrumb.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Breadcrumb.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Button.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Button.tsx (100%) create mode 100644 packages/avanza-ui/src/components/Button/Button.css create mode 100644 packages/avanza-ui/src/components/Button/Button.tsx create mode 100644 packages/avanza-ui/src/components/Button/index.ts rename packages/{ui-components => avanza-ui}/src/components/Card.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Card.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Checkbox.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Checkbox.tsx (100%) create mode 100644 packages/avanza-ui/src/components/ControlButton.module.css create mode 100644 packages/avanza-ui/src/components/ControlButton.tsx rename packages/{ui-components => avanza-ui}/src/components/Dropdown.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Dropdown.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Input.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Input.tsx (93%) rename packages/{ui-components => avanza-ui}/src/components/Modal.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Modal.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Pagination.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Pagination.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Progress.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Progress.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Radio.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Radio.tsx (100%) create mode 100644 packages/avanza-ui/src/components/SceneCard.module.css create mode 100644 packages/avanza-ui/src/components/SceneCard.tsx rename packages/{ui-components => avanza-ui}/src/components/Select.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Select.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Spinner.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Spinner.tsx (100%) create mode 100644 packages/avanza-ui/src/components/StudioHeader.module.css create mode 100644 packages/avanza-ui/src/components/StudioHeader.tsx rename packages/{ui-components => avanza-ui}/src/components/Switch.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Switch.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Tabs.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Tabs.tsx (100%) rename packages/{ui-components => avanza-ui}/src/components/Textarea.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Textarea.tsx (97%) rename packages/{ui-components => avanza-ui}/src/components/Tooltip.module.css (100%) rename packages/{ui-components => avanza-ui}/src/components/Tooltip.tsx (100%) create mode 100644 packages/avanza-ui/src/components/VideoTile.module.css create mode 100644 packages/avanza-ui/src/components/VideoTile.tsx create mode 100644 packages/avanza-ui/src/components/index.ts create mode 100644 packages/avanza-ui/src/global.d.ts rename packages/{ui-components => avanza-ui}/src/index.ts (83%) rename packages/{ui-components => avanza-ui}/src/styles/globals.css (100%) create mode 100644 packages/avanza-ui/src/styles/studio-theme.css rename packages/{ui-components => avanza-ui}/src/types/css-modules.d.ts (100%) rename packages/{ui-components => avanza-ui}/src/types/index.ts (100%) rename packages/{ui-components => avanza-ui}/src/utils/helpers.ts (100%) rename packages/{ui-components => avanza-ui}/tsconfig.json (65%) create mode 100644 packages/studio-panel/.storybook/main.ts create mode 100644 packages/studio-panel/.storybook/preview.tsx create mode 100644 packages/studio-panel/MIGRATION_REPORT.md create mode 100644 packages/studio-panel/STREAMYARD_REDESIGN.md create mode 100644 packages/studio-panel/src/App.css create mode 100644 packages/studio-panel/src/components/StudioRoom/StudioRoom.css create mode 100644 packages/studio-panel/src/components/StudioRoom/StudioRoom.tsx create mode 100644 packages/studio-panel/src/components/VideoParticipant/VideoParticipant.css create mode 100644 packages/studio-panel/src/components/VideoParticipant/VideoParticipant.tsx create mode 100644 packages/studio-panel/src/global.d.ts create mode 100644 packages/studio-panel/src/stories/Button.stories.tsx create mode 100644 packages/studio-panel/src/styles/studio.css delete mode 100644 packages/ui-components/COMPLETION_REPORT.md delete mode 100644 packages/ui-components/COMPONENTS_UPDATE.md delete mode 100644 packages/ui-components/GUIDE.md delete mode 100644 packages/ui-components/INDEX.md delete mode 100644 packages/ui-components/QUICKSTART.md delete mode 100644 packages/ui-components/QUICK_REFERENCE.md delete mode 100644 packages/ui-components/README.md delete mode 100644 packages/ui-components/STUDIO_IMPLEMENTATION.md delete mode 100644 packages/ui-components/SUMMARY.md delete mode 100644 packages/ui-components/UPDATE_V2.md delete mode 100644 packages/ui-components/VERIFICATION_CHECKLIST.md delete mode 100644 packages/ui-components/rollup.config.js diff --git a/.playwright-mcp/streamyard-initial.png b/.playwright-mcp/streamyard-initial.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5a22f156d4e965852c90ffe9bda1fc42af3a14 GIT binary patch literal 61092 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

NXpDM_0; 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 1024px + +--- + +## 10. COMPONENTES ESPECÍFICOS DE VIDEO STUDIO + +### Avatar/Profile Display: +```yaml +Características: + - Forma: Circular + - Tamaño: Variable según contexto + - Border: Sutil + - Placeholder: Ícono de usuario cuando no hay imagen +``` + +### Device Selectors (Mic/Camera): +```yaml +Características: + - Dropdown style + - Iconos descriptivos + - Estados visuales claros (muted/active) + - Feedback inmediato de cambios +``` + +### Settings Button: +```yaml +Características: + - Ícono de engranaje/configuración + - Posición: Accesible pero no intrusiva + - Hover: Efecto de rotación sutil (opcional) +``` + +--- + +## 11. RECOMENDACIONES PARA AVANZA-UI + +### Para Implementar en Avanza-UI: + +1. **Sistema de Design Tokens**: +```typescript +// colors.ts +export const colors = { + background: { + primary: '#1a1a1a', + secondary: '#2a2a2a', + elevated: '#353535', + }, + text: { + primary: '#ffffff', + secondary: '#b0b0b0', + muted: '#808080', + }, + accent: { + primary: '#4a90e2', + hover: '#5a9ff2', + }, + border: { + default: '#404040', + focus: '#4a90e2', + } +}; + +// spacing.ts +export const spacing = { + xs: '4px', + sm: '8px', + md: '16px', + lg: '24px', + xl: '32px', + '2xl': '48px', +}; + +// typography.ts +export const typography = { + fontFamily: 'system-ui, -apple-system, sans-serif', + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + }, + fontWeight: { + regular: 400, + medium: 500, + semibold: 600, + bold: 700, + }, +}; + +// borderRadius.ts +export const borderRadius = { + sm: '4px', + md: '8px', + lg: '12px', + full: '9999px', +}; +``` + +2. **Componentes Base Mejorados**: + - ` + + ); +} +``` + +**Importante:** Asegúrate de envolver tu aplicación con la clase `studio-theme` para aplicar los estilos correctamente. + +## Componentes Disponibles + +### Button + +Botón personalizable con múltiples variantes y tamaños. + +```tsx +import { Button } from 'avanza-ui'; + +// Variantes + + + + + + +// Tamaños + + + + +// Con íconos + + + + +// Estados + + + +// Full width + +``` + +#### Props del Button + +| Prop | Tipo | Default | Descripción | +|------|------|---------|-------------| +| `variant` | `'primary' \| 'secondary' \| 'danger' \| 'success' \| 'ghost'` | `'secondary'` | Variante visual del botón | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Tamaño del botón | +| `loading` | `boolean` | `false` | Muestra spinner de carga | +| `disabled` | `boolean` | `false` | Deshabilita el botón | +| `fullWidth` | `boolean` | `false` | Ancho completo | +| `iconOnly` | `boolean` | `false` | Solo muestra ícono (sin texto) | +| `leftIcon` | `ReactNode` | - | Ícono a la izquierda | +| `rightIcon` | `ReactNode` | - | Ícono a la derecha | + +## Variables CSS (Studio Theme) + +Todas las variables CSS están definidas en `studio-theme.css` y pueden ser personalizadas: + +### Colores + +```css +--studio-bg-primary: #0f0f0f; +--studio-bg-secondary: #1a1a1a; +--studio-bg-tertiary: #242424; +--studio-accent: #3b82f6; +--studio-success: #10b981; +--studio-warning: #f59e0b; +--studio-danger: #ef4444; +``` + +### Espaciado + +```css +--studio-space-xs: 4px; +--studio-space-sm: 8px; +--studio-space-md: 12px; +--studio-space-lg: 16px; +--studio-space-xl: 24px; +``` + +### Tipografía + +```css +--studio-text-xs: 11px; +--studio-text-sm: 12px; +--studio-text-base: 14px; +--studio-text-md: 16px; +--studio-text-lg: 18px; +``` + +### Border Radius + +```css +--studio-radius-sm: 4px; +--studio-radius-md: 6px; +--studio-radius-lg: 8px; +--studio-radius-xl: 12px; +``` + +## Personalización + +Puedes sobrescribir las variables CSS en tu aplicación: + +```css +:root { + --studio-accent: #your-color; + --studio-bg-primary: #your-bg; +} +``` + +## Próximos Componentes + +- [ ] Input +- [ ] Select +- [ ] Textarea +- [ ] Checkbox +- [ ] Radio +- [ ] Switch +- [ ] Modal +- [ ] Dropdown +- [ ] Tooltip +- [ ] Card +- [ ] Badge +- [ ] Avatar +- [ ] IconButton +- [ ] Tabs +- [ ] Panel +- [ ] Layout components (StudioLayout, TopBar, BottomBar, etc.) + +## Desarrollo + +### Estructura del Proyecto + +``` +avanza-ui/ +├── src/ +│ ├── components/ +│ │ ├── Button/ +│ │ │ ├── Button.tsx +│ │ │ ├── Button.css +│ │ │ └── index.ts +│ │ └── ... (más componentes) +│ ├── styles/ +│ │ └── studio-theme.css +│ └── index.ts +├── package.json +└── README.md +``` + +### Agregar un Nuevo Componente + +1. Crea una carpeta en `src/components/` +2. Crea `ComponentName.tsx` con el componente React +3. Crea `ComponentName.css` con los estilos +4. Crea `index.ts` para exportar el componente +5. Actualiza `src/index.ts` para exportar desde la raíz + +### Convenciones de Nomenclatura + +- **Componentes**: PascalCase (ej: `Button`, `IconButton`) +- **Archivos**: PascalCase para componentes, kebab-case para estilos +- **CSS Classes**: kebab-case con prefijo `avanza-` (ej: `avanza-button`) +- **CSS Variables**: kebab-case con prefijo `--studio-` (ej: `--studio-accent`) + +## Guía de Estilo + +### CSS + +- Usa variables CSS de `studio-theme.css` en lugar de valores hardcoded +- Sigue el patrón BEM para nombres de clases +- Agrupa propiedades relacionadas +- Usa transiciones para interacciones suaves + +### TypeScript + +- Exporta interfaces de props +- Usa `React.forwardRef` para componentes que necesiten refs +- Documenta props con JSDoc +- Usa tipos estrictos (evita `any`) + +## Licencia + +Uso interno - AvanzaCast + +## Contribuidores + +- Equipo AvanzaCast + +--- + +**Versión:** 1.0.0 +**Última actualización:** 2025-11-11 + diff --git a/packages/ui-components/package.json b/packages/avanza-ui/package.json similarity index 77% rename from packages/ui-components/package.json rename to packages/avanza-ui/package.json index c8a61fb..a991ce9 100644 --- a/packages/ui-components/package.json +++ b/packages/avanza-ui/package.json @@ -1,30 +1,31 @@ { "name": "avanza-ui", "version": "1.0.0", - "type": "module", - "description": "Sistema de componentes UI independiente - Inspirado en Tailwind CSS y Vristo", + "description": "Biblioteca de componentes React para AvanzaCast basada en StreamYard y unificada con ui-components", "main": "dist/index.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", "files": [ - "dist" + "dist", + "src" ], "scripts": { "build": "rollup -c", "dev": "rollup -c -w", + "typecheck": "tsc --noEmit", "test": "vitest", - "test:ui": "vitest --ui", - "type-check": "tsc --noEmit" + "prepublishOnly": "npm run build" }, "keywords": [ - "ui", - "components", "react", + "components", + "ui", "avanzacast", - "design-system" + "studio" ], "author": "AvanzaCast Team", "license": "MIT", + "private": true, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" @@ -35,8 +36,6 @@ "@rollup/plugin-typescript": "^11.1.6", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", "rollup": "^4.18.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", @@ -48,4 +47,3 @@ "clsx": "^2.1.1" } } - diff --git a/packages/ui-components/src/components/Accordion.module.css b/packages/avanza-ui/src/components/Accordion.module.css similarity index 100% rename from packages/ui-components/src/components/Accordion.module.css rename to packages/avanza-ui/src/components/Accordion.module.css diff --git a/packages/ui-components/src/components/Accordion.tsx b/packages/avanza-ui/src/components/Accordion.tsx similarity index 100% rename from packages/ui-components/src/components/Accordion.tsx rename to packages/avanza-ui/src/components/Accordion.tsx diff --git a/packages/ui-components/src/components/Alert.module.css b/packages/avanza-ui/src/components/Alert.module.css similarity index 100% rename from packages/ui-components/src/components/Alert.module.css rename to packages/avanza-ui/src/components/Alert.module.css diff --git a/packages/ui-components/src/components/Alert.tsx b/packages/avanza-ui/src/components/Alert.tsx similarity index 100% rename from packages/ui-components/src/components/Alert.tsx rename to packages/avanza-ui/src/components/Alert.tsx diff --git a/packages/ui-components/src/components/Avatar.module.css b/packages/avanza-ui/src/components/Avatar.module.css similarity index 100% rename from packages/ui-components/src/components/Avatar.module.css rename to packages/avanza-ui/src/components/Avatar.module.css diff --git a/packages/ui-components/src/components/Avatar.tsx b/packages/avanza-ui/src/components/Avatar.tsx similarity index 100% rename from packages/ui-components/src/components/Avatar.tsx rename to packages/avanza-ui/src/components/Avatar.tsx diff --git a/packages/ui-components/src/components/Badge.module.css b/packages/avanza-ui/src/components/Badge.module.css similarity index 100% rename from packages/ui-components/src/components/Badge.module.css rename to packages/avanza-ui/src/components/Badge.module.css diff --git a/packages/ui-components/src/components/Badge.tsx b/packages/avanza-ui/src/components/Badge.tsx similarity index 100% rename from packages/ui-components/src/components/Badge.tsx rename to packages/avanza-ui/src/components/Badge.tsx diff --git a/packages/ui-components/src/components/Breadcrumb.module.css b/packages/avanza-ui/src/components/Breadcrumb.module.css similarity index 100% rename from packages/ui-components/src/components/Breadcrumb.module.css rename to packages/avanza-ui/src/components/Breadcrumb.module.css diff --git a/packages/ui-components/src/components/Breadcrumb.tsx b/packages/avanza-ui/src/components/Breadcrumb.tsx similarity index 100% rename from packages/ui-components/src/components/Breadcrumb.tsx rename to packages/avanza-ui/src/components/Breadcrumb.tsx diff --git a/packages/ui-components/src/components/Button.module.css b/packages/avanza-ui/src/components/Button.module.css similarity index 100% rename from packages/ui-components/src/components/Button.module.css rename to packages/avanza-ui/src/components/Button.module.css diff --git a/packages/ui-components/src/components/Button.tsx b/packages/avanza-ui/src/components/Button.tsx similarity index 100% rename from packages/ui-components/src/components/Button.tsx rename to packages/avanza-ui/src/components/Button.tsx diff --git a/packages/avanza-ui/src/components/Button/Button.css b/packages/avanza-ui/src/components/Button/Button.css new file mode 100644 index 0000000..3f9e278 --- /dev/null +++ b/packages/avanza-ui/src/components/Button/Button.css @@ -0,0 +1,220 @@ +/* Button Component - Studio Theme */ + +.avanza-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--studio-space-sm); + font-size: var(--studio-text-base); + font-weight: var(--studio-font-medium); + font-family: var(--studio-font-family); + line-height: var(--studio-leading-tight); + border-radius: var(--studio-radius-md); + border: 1px solid transparent; + cursor: pointer; + transition: var(--studio-transition); + text-decoration: none; + white-space: nowrap; + user-select: none; + position: relative; + overflow: hidden; +} + +/* ===== SIZES ===== */ +.avanza-button--sm { + height: var(--studio-btn-sm-height); + padding: 0 var(--studio-space-md); + font-size: var(--studio-text-sm); +} + +.avanza-button--md { + height: var(--studio-btn-md-height); + padding: 0 var(--studio-space-lg); +} + +.avanza-button--lg { + height: var(--studio-btn-lg-height); + padding: 0 var(--studio-space-xl); + font-size: var(--studio-text-md); +} + +/* Icon Only */ +.avanza-button--icon-only.avanza-button--sm { + width: var(--studio-btn-sm-height); + padding: 0; +} + +.avanza-button--icon-only.avanza-button--md { + width: var(--studio-btn-md-height); + padding: 0; +} + +.avanza-button--icon-only.avanza-button--lg { + width: var(--studio-btn-lg-height); + padding: 0; +} + +/* ===== VARIANTS ===== */ + +/* Primary (Accent) */ +.avanza-button--primary { + background-color: var(--studio-accent); + color: var(--studio-text-primary); +} + +.avanza-button--primary:hover:not(:disabled) { + background-color: var(--studio-accent-hover); + transform: translateY(-1px); +} + +.avanza-button--primary:active:not(:disabled) { + transform: translateY(0); +} + +/* Secondary (Default) */ +.avanza-button--secondary { + background-color: var(--studio-bg-elevated); + color: var(--studio-text-secondary); + border-color: var(--studio-border-light); +} + +.avanza-button--secondary:hover:not(:disabled) { + background-color: var(--studio-bg-hover); + border-color: var(--studio-border-light); +} + +.avanza-button--secondary:active:not(:disabled) { + background-color: var(--studio-bg-tertiary); +} + +/* Danger */ +.avanza-button--danger { + background-color: var(--studio-danger); + color: var(--studio-text-primary); +} + +.avanza-button--danger:hover:not(:disabled) { + background-color: var(--studio-danger-hover); + transform: translateY(-1px); +} + +.avanza-button--danger:active:not(:disabled) { + transform: translateY(0); +} + +/* Success */ +.avanza-button--success { + background-color: var(--studio-success); + color: var(--studio-text-primary); +} + +.avanza-button--success:hover:not(:disabled) { + background-color: var(--studio-success-hover); + transform: translateY(-1px); +} + +.avanza-button--success:active:not(:disabled) { + transform: translateY(0); +} + +/* Ghost */ +.avanza-button--ghost { + background-color: transparent; + color: var(--studio-text-secondary); + border-color: transparent; +} + +.avanza-button--ghost:hover:not(:disabled) { + background-color: var(--studio-bg-elevated); +} + +.avanza-button--ghost:active:not(:disabled) { + background-color: var(--studio-bg-tertiary); +} + +/* ===== STATES ===== */ + +/* Disabled */ +.avanza-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Loading */ +.avanza-button--loading { + pointer-events: none; +} + +/* Full Width */ +.avanza-button--full-width { + width: 100%; +} + +/* ===== ICONS ===== */ +.avanza-button__icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.avanza-button__icon svg { + width: var(--studio-icon-md); + height: var(--studio-icon-md); +} + +.avanza-button--sm .avanza-button__icon svg { + width: var(--studio-icon-sm); + height: var(--studio-icon-sm); +} + +.avanza-button--lg .avanza-button__icon svg { + width: var(--studio-icon-lg); + height: var(--studio-icon-lg); +} + +/* ===== SPINNER ===== */ +.avanza-button__spinner { + position: absolute; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.avanza-button__spinner-icon { + width: var(--studio-icon-md); + height: var(--studio-icon-md); + animation: spin 1s linear infinite; +} + +.avanza-button--sm .avanza-button__spinner-icon { + width: var(--studio-icon-sm); + height: var(--studio-icon-sm); +} + +.avanza-button--lg .avanza-button__spinner-icon { + width: var(--studio-icon-lg); + height: var(--studio-icon-lg); +} + +.avanza-button__spinner-circle { + stroke: currentColor; + stroke-dasharray: 50; + stroke-dashoffset: 25; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Content */ +.avanza-button__content { + display: inline-flex; + align-items: center; +} + +.avanza-button--loading .avanza-button__content { + visibility: hidden; +} + diff --git a/packages/avanza-ui/src/components/Button/Button.tsx b/packages/avanza-ui/src/components/Button/Button.tsx new file mode 100644 index 0000000..a9d83f9 --- /dev/null +++ b/packages/avanza-ui/src/components/Button/Button.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import './Button.css'; + +export interface ButtonProps extends React.ButtonHTMLAttributes { + /** Variante del botón */ + variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'ghost'; + /** Tamaño del botón */ + size?: 'sm' | 'md' | 'lg'; + /** Mostrar estado de carga */ + loading?: boolean; + /** Deshabilitar botón */ + disabled?: boolean; + /** Ancho completo */ + fullWidth?: boolean; + /** Solo ícono */ + iconOnly?: boolean; + /** Ícono a la izquierda */ + leftIcon?: React.ReactNode; + /** Ícono a la derecha */ + rightIcon?: React.ReactNode; + /** Clase CSS adicional */ + className?: string; + /** Hijos del componente */ + children?: React.ReactNode; +} + +export const Button = React.forwardRef( + ( + { + variant = 'secondary', + size = 'md', + loading = false, + disabled = false, + fullWidth = false, + iconOnly = false, + leftIcon, + rightIcon, + className = '', + children, + ...props + }, + ref + ) => { + const classNames = [ + 'avanza-button', + `avanza-button--${variant}`, + `avanza-button--${size}`, + fullWidth && 'avanza-button--full-width', + iconOnly && 'avanza-button--icon-only', + loading && 'avanza-button--loading', + className, + ] + .filter(Boolean) + .join(' '); + + const isDisabled = disabled || loading; + + return ( + + ); + } +); + +Button.displayName = 'Button'; + +export default Button; + diff --git a/packages/avanza-ui/src/components/Button/index.ts b/packages/avanza-ui/src/components/Button/index.ts new file mode 100644 index 0000000..749d693 --- /dev/null +++ b/packages/avanza-ui/src/components/Button/index.ts @@ -0,0 +1,4 @@ +export { Button } from './Button'; +export type { ButtonProps } from './Button'; +export { Button as default } from './Button'; + diff --git a/packages/ui-components/src/components/Card.module.css b/packages/avanza-ui/src/components/Card.module.css similarity index 100% rename from packages/ui-components/src/components/Card.module.css rename to packages/avanza-ui/src/components/Card.module.css diff --git a/packages/ui-components/src/components/Card.tsx b/packages/avanza-ui/src/components/Card.tsx similarity index 100% rename from packages/ui-components/src/components/Card.tsx rename to packages/avanza-ui/src/components/Card.tsx diff --git a/packages/ui-components/src/components/Checkbox.module.css b/packages/avanza-ui/src/components/Checkbox.module.css similarity index 100% rename from packages/ui-components/src/components/Checkbox.module.css rename to packages/avanza-ui/src/components/Checkbox.module.css diff --git a/packages/ui-components/src/components/Checkbox.tsx b/packages/avanza-ui/src/components/Checkbox.tsx similarity index 100% rename from packages/ui-components/src/components/Checkbox.tsx rename to packages/avanza-ui/src/components/Checkbox.tsx diff --git a/packages/avanza-ui/src/components/ControlButton.module.css b/packages/avanza-ui/src/components/ControlButton.module.css new file mode 100644 index 0000000..8326b28 --- /dev/null +++ b/packages/avanza-ui/src/components/ControlButton.module.css @@ -0,0 +1,80 @@ +.controlButton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + width: 64px; + height: 64px; + background: linear-gradient(135deg, var(--au-gray-700) 0%, var(--au-gray-800) 100%); + border: 2px solid var(--au-border-dark); + color: var(--au-text-primary); + cursor: pointer; + border-radius: var(--au-radius-full); + transition: all var(--au-transition-fast); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + position: relative; + backdrop-filter: blur(10px); + font-size: 24px; + outline: none; +} + +.controlButton:hover:not(:disabled) { + background: linear-gradient(135deg, var(--au-gray-600) 0%, var(--au-gray-700) 100%); + border-color: var(--au-primary); + transform: scale(1.05); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5); +} + +.controlButton:active:not(:disabled) { + transform: scale(0.98); +} + +.controlButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.controlButton.active { + background: linear-gradient(135deg, var(--au-primary) 0%, var(--au-primary-hover) 100%); + border-color: var(--au-primary); + box-shadow: 0 6px 20px rgba(79, 70, 229, 0.5); +} + +.controlButton.danger { + background: linear-gradient(135deg, var(--au-danger-600) 0%, var(--au-danger-700) 100%); + border-color: var(--au-danger-600); +} + +.controlButton.danger:hover:not(:disabled) { + background: linear-gradient(135deg, var(--au-danger-500) 0%, var(--au-danger-600) 100%); + box-shadow: 0 6px 20px rgba(220, 38, 38, 0.5); +} + +/* Sizes */ +.sm { + width: 48px; + height: 48px; + font-size: 20px; +} + +.md { + width: 64px; + height: 64px; + font-size: 24px; +} + +.lg { + width: 80px; + height: 80px; + font-size: 32px; +} + +.controlButtonLabel { + font-size: 10px; + font-weight: var(--au-font-medium); + margin-top: 2px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + diff --git a/packages/avanza-ui/src/components/ControlButton.tsx b/packages/avanza-ui/src/components/ControlButton.tsx new file mode 100644 index 0000000..d1e3059 --- /dev/null +++ b/packages/avanza-ui/src/components/ControlButton.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { cn } from '../utils/helpers'; +import type { ComponentBaseProps } from '../types'; +import styles from './ControlButton.module.css'; + +export interface ControlButtonProps extends ComponentBaseProps { + icon?: React.ReactNode; + label?: string; + active?: boolean; + danger?: boolean; + size?: 'sm' | 'md' | 'lg'; + onClick?: () => void; + disabled?: boolean; + title?: string; +} + +export const ControlButton: React.FC = (props) => { + const { + icon, + label, + active = false, + danger = false, + size = 'md', + onClick, + disabled = false, + title, + className, + style, + id, + } = props; + + return ( + + ); +}; + +ControlButton.displayName = 'ControlButton'; + diff --git a/packages/ui-components/src/components/Dropdown.module.css b/packages/avanza-ui/src/components/Dropdown.module.css similarity index 100% rename from packages/ui-components/src/components/Dropdown.module.css rename to packages/avanza-ui/src/components/Dropdown.module.css diff --git a/packages/ui-components/src/components/Dropdown.tsx b/packages/avanza-ui/src/components/Dropdown.tsx similarity index 100% rename from packages/ui-components/src/components/Dropdown.tsx rename to packages/avanza-ui/src/components/Dropdown.tsx diff --git a/packages/ui-components/src/components/Input.module.css b/packages/avanza-ui/src/components/Input.module.css similarity index 100% rename from packages/ui-components/src/components/Input.module.css rename to packages/avanza-ui/src/components/Input.module.css diff --git a/packages/ui-components/src/components/Input.tsx b/packages/avanza-ui/src/components/Input.tsx similarity index 93% rename from packages/ui-components/src/components/Input.tsx rename to packages/avanza-ui/src/components/Input.tsx index b6fd4a1..6db122d 100644 --- a/packages/ui-components/src/components/Input.tsx +++ b/packages/avanza-ui/src/components/Input.tsx @@ -72,11 +72,11 @@ export const Input = React.forwardRef( const wrapperClasses = cn( styles.inputWrapper, styles[size], - error && styles.error, - success && styles.success, - leftIcon && styles.withLeftIcon, - rightIcon && styles.withRightIcon, - fullWidth && styles.fullWidth, + error ? styles.error : undefined, + success ? styles.success : undefined, + leftIcon ? styles.withLeftIcon : undefined, + rightIcon ? styles.withRightIcon : undefined, + fullWidth ? styles.fullWidth : undefined, className ); @@ -136,4 +136,3 @@ export const Input = React.forwardRef( ); Input.displayName = 'Input'; - diff --git a/packages/ui-components/src/components/Modal.module.css b/packages/avanza-ui/src/components/Modal.module.css similarity index 100% rename from packages/ui-components/src/components/Modal.module.css rename to packages/avanza-ui/src/components/Modal.module.css diff --git a/packages/ui-components/src/components/Modal.tsx b/packages/avanza-ui/src/components/Modal.tsx similarity index 100% rename from packages/ui-components/src/components/Modal.tsx rename to packages/avanza-ui/src/components/Modal.tsx diff --git a/packages/ui-components/src/components/Pagination.module.css b/packages/avanza-ui/src/components/Pagination.module.css similarity index 100% rename from packages/ui-components/src/components/Pagination.module.css rename to packages/avanza-ui/src/components/Pagination.module.css diff --git a/packages/ui-components/src/components/Pagination.tsx b/packages/avanza-ui/src/components/Pagination.tsx similarity index 100% rename from packages/ui-components/src/components/Pagination.tsx rename to packages/avanza-ui/src/components/Pagination.tsx diff --git a/packages/ui-components/src/components/Progress.module.css b/packages/avanza-ui/src/components/Progress.module.css similarity index 100% rename from packages/ui-components/src/components/Progress.module.css rename to packages/avanza-ui/src/components/Progress.module.css diff --git a/packages/ui-components/src/components/Progress.tsx b/packages/avanza-ui/src/components/Progress.tsx similarity index 100% rename from packages/ui-components/src/components/Progress.tsx rename to packages/avanza-ui/src/components/Progress.tsx diff --git a/packages/ui-components/src/components/Radio.module.css b/packages/avanza-ui/src/components/Radio.module.css similarity index 100% rename from packages/ui-components/src/components/Radio.module.css rename to packages/avanza-ui/src/components/Radio.module.css diff --git a/packages/ui-components/src/components/Radio.tsx b/packages/avanza-ui/src/components/Radio.tsx similarity index 100% rename from packages/ui-components/src/components/Radio.tsx rename to packages/avanza-ui/src/components/Radio.tsx diff --git a/packages/avanza-ui/src/components/SceneCard.module.css b/packages/avanza-ui/src/components/SceneCard.module.css new file mode 100644 index 0000000..c05eb7f --- /dev/null +++ b/packages/avanza-ui/src/components/SceneCard.module.css @@ -0,0 +1,74 @@ +.sceneCard { + cursor: pointer; + border-radius: var(--au-radius-md); + overflow: hidden; + border: 2px solid transparent; + background: var(--au-gray-800); + transition: all var(--au-transition-fast); +} + +.sceneCard:hover { + border-color: var(--au-border-medium); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.sceneCard.active { + border-color: var(--au-primary); + background: var(--au-gray-700); + box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3); +} + +.sceneCardThumbnail { + width: 100%; + aspect-ratio: 16/9; + background: linear-gradient(135deg, var(--au-gray-700) 0%, var(--au-gray-800) 100%); + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.sceneCardThumbnailContent { + width: 60%; + height: 60%; + border: 2px dashed var(--au-border-dark); + border-radius: var(--au-radius-sm); + display: flex; + align-items: center; + justify-content: center; + color: var(--au-text-secondary); + font-size: 24px; +} + +.sceneCardIndicator { + position: absolute; + top: 8px; + right: 8px; +} + +.sceneCardFooter { + padding: 8px 10px; + border-top: 1px solid var(--au-border-dark); +} + +.sceneCardTitle { + font-size: 13px; + font-weight: var(--au-font-semibold); + color: var(--au-text-primary); + transition: color var(--au-transition-fast); +} + +.sceneCard.active .sceneCardTitle { + color: var(--au-primary); +} + +/* Drag and drop */ +.sceneCard.dragging { + opacity: 0.5; +} + +.sceneCard.dragOver { + border-color: var(--au-success-500); +} + diff --git a/packages/avanza-ui/src/components/SceneCard.tsx b/packages/avanza-ui/src/components/SceneCard.tsx new file mode 100644 index 0000000..2cd0aad --- /dev/null +++ b/packages/avanza-ui/src/components/SceneCard.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { cn } from '../utils/helpers'; +import type { ComponentBaseProps } from '../types'; +import { Badge } from './Badge'; +import styles from './SceneCard.module.css'; + +export interface SceneCardProps extends ComponentBaseProps { + title: string; + preview?: React.ReactNode; + active?: boolean; + onClick?: () => void; + draggable?: boolean; + onDragStart?: (e: React.DragEvent) => void; + onDragEnd?: (e: React.DragEvent) => void; + onDrop?: (e: React.DragEvent) => void; +} + +export const SceneCard: React.FC = (props) => { + const { + title, + preview, + active = false, + onClick, + draggable = false, + onDragStart, + onDragEnd, + onDrop, + className, + style, + id, + } = props; + + const [isDragging, setIsDragging] = React.useState(false); + const [isDragOver, setIsDragOver] = React.useState(false); + + const handleDragStart = (e: React.DragEvent) => { + setIsDragging(true); + onDragStart?.(e); + }; + + const handleDragEnd = (e: React.DragEvent) => { + setIsDragging(false); + onDragEnd?.(e); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }; + + const handleDragLeave = () => { + setIsDragOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + onDrop?.(e); + }; + + return ( +

+ ); +}; + +SceneCard.displayName = 'SceneCard'; + diff --git a/packages/ui-components/src/components/Select.module.css b/packages/avanza-ui/src/components/Select.module.css similarity index 100% rename from packages/ui-components/src/components/Select.module.css rename to packages/avanza-ui/src/components/Select.module.css diff --git a/packages/ui-components/src/components/Select.tsx b/packages/avanza-ui/src/components/Select.tsx similarity index 100% rename from packages/ui-components/src/components/Select.tsx rename to packages/avanza-ui/src/components/Select.tsx diff --git a/packages/ui-components/src/components/Spinner.module.css b/packages/avanza-ui/src/components/Spinner.module.css similarity index 100% rename from packages/ui-components/src/components/Spinner.module.css rename to packages/avanza-ui/src/components/Spinner.module.css diff --git a/packages/ui-components/src/components/Spinner.tsx b/packages/avanza-ui/src/components/Spinner.tsx similarity index 100% rename from packages/ui-components/src/components/Spinner.tsx rename to packages/avanza-ui/src/components/Spinner.tsx diff --git a/packages/avanza-ui/src/components/StudioHeader.module.css b/packages/avanza-ui/src/components/StudioHeader.module.css new file mode 100644 index 0000000..747dea2 --- /dev/null +++ b/packages/avanza-ui/src/components/StudioHeader.module.css @@ -0,0 +1,57 @@ +.studioHeader { + display: flex; + align-items: center; + justify-content: space-between; + height: 60px; + padding: 0 20px; + background: linear-gradient(180deg, var(--au-gray-800) 0%, var(--au-gray-900) 100%); + border-bottom: 1px solid var(--au-border-dark); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.headerLeft { + display: flex; + align-items: center; + gap: 16px; +} + +.headerLogo { + width: 40px; + height: 40px; + border-radius: var(--au-radius-md); + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--au-font-bold); + font-size: 18px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.headerLogoGradient { + background: linear-gradient(135deg, var(--au-primary) 0%, var(--au-primary-hover) 100%); + color: white; +} + +.headerTitle { + display: flex; + flex-direction: column; + gap: 2px; +} + +.headerTitleMain { + font-weight: var(--au-font-bold); + font-size: 16px; + color: var(--au-text-primary); +} + +.headerTitleSub { + font-size: 12px; + color: var(--au-text-secondary); +} + +.headerRight { + display: flex; + align-items: center; + gap: 12px; +} + diff --git a/packages/avanza-ui/src/components/StudioHeader.tsx b/packages/avanza-ui/src/components/StudioHeader.tsx new file mode 100644 index 0000000..d7d66aa --- /dev/null +++ b/packages/avanza-ui/src/components/StudioHeader.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { cn } from '../utils/helpers'; +import type { ComponentBaseProps } from '../types'; +import styles from './StudioHeader.module.css'; + +export interface StudioHeaderProps extends ComponentBaseProps { + logo?: React.ReactNode; + logoText?: string; + title?: string; + subtitle?: string; + actions?: React.ReactNode; +} + +export const StudioHeader: React.FC = (props) => { + const { + logo, + logoText = 'AC', + title = 'AvanzaCast Studio', + subtitle = 'Estudio de Transmisión', + actions, + className, + style, + id, + } = props; + + return ( +
+
+ {logo || ( +
+ {logoText} +
+ )} +
+
{title}
+
{subtitle}
+
+
+ + {actions && ( +
+ {actions} +
+ )} +
+ ); +}; + +StudioHeader.displayName = 'StudioHeader'; + diff --git a/packages/ui-components/src/components/Switch.module.css b/packages/avanza-ui/src/components/Switch.module.css similarity index 100% rename from packages/ui-components/src/components/Switch.module.css rename to packages/avanza-ui/src/components/Switch.module.css diff --git a/packages/ui-components/src/components/Switch.tsx b/packages/avanza-ui/src/components/Switch.tsx similarity index 100% rename from packages/ui-components/src/components/Switch.tsx rename to packages/avanza-ui/src/components/Switch.tsx diff --git a/packages/ui-components/src/components/Tabs.module.css b/packages/avanza-ui/src/components/Tabs.module.css similarity index 100% rename from packages/ui-components/src/components/Tabs.module.css rename to packages/avanza-ui/src/components/Tabs.module.css diff --git a/packages/ui-components/src/components/Tabs.tsx b/packages/avanza-ui/src/components/Tabs.tsx similarity index 100% rename from packages/ui-components/src/components/Tabs.tsx rename to packages/avanza-ui/src/components/Tabs.tsx diff --git a/packages/ui-components/src/components/Textarea.module.css b/packages/avanza-ui/src/components/Textarea.module.css similarity index 100% rename from packages/ui-components/src/components/Textarea.module.css rename to packages/avanza-ui/src/components/Textarea.module.css diff --git a/packages/ui-components/src/components/Textarea.tsx b/packages/avanza-ui/src/components/Textarea.tsx similarity index 97% rename from packages/ui-components/src/components/Textarea.tsx rename to packages/avanza-ui/src/components/Textarea.tsx index 4637110..fe2c6e0 100644 --- a/packages/ui-components/src/components/Textarea.tsx +++ b/packages/avanza-ui/src/components/Textarea.tsx @@ -110,7 +110,7 @@ export const Textarea = React.forwardRef( {...rest} /> {showCharacterCount && maxLength && ( -
+
{charCount}/{maxLength}
)} @@ -130,4 +130,3 @@ export const Textarea = React.forwardRef( ); Textarea.displayName = 'Textarea'; - diff --git a/packages/ui-components/src/components/Tooltip.module.css b/packages/avanza-ui/src/components/Tooltip.module.css similarity index 100% rename from packages/ui-components/src/components/Tooltip.module.css rename to packages/avanza-ui/src/components/Tooltip.module.css diff --git a/packages/ui-components/src/components/Tooltip.tsx b/packages/avanza-ui/src/components/Tooltip.tsx similarity index 100% rename from packages/ui-components/src/components/Tooltip.tsx rename to packages/avanza-ui/src/components/Tooltip.tsx diff --git a/packages/avanza-ui/src/components/VideoTile.module.css b/packages/avanza-ui/src/components/VideoTile.module.css new file mode 100644 index 0000000..e185de1 --- /dev/null +++ b/packages/avanza-ui/src/components/VideoTile.module.css @@ -0,0 +1,129 @@ +.videoTile { + position: relative; + aspect-ratio: 16 / 9; + background: linear-gradient(135deg, var(--au-gray-700) 0%, var(--au-gray-800) 100%); + border-radius: var(--au-radius-lg); + overflow: hidden; + border: 2px solid transparent; + transition: all var(--au-transition-fast); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.videoTile:hover { + border-color: var(--au-primary); + box-shadow: 0 6px 20px rgba(79, 70, 229, 0.3); + transform: translateY(-2px); +} + +.videoTile.speaking { + border-color: var(--au-warning-500); + box-shadow: 0 6px 20px rgba(234, 179, 8, 0.4); +} + +.videoElement { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + background-color: var(--au-gray-800); +} + +.videoOverlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 50%); + pointer-events: none; +} + +.videoTopLeft { + position: absolute; + top: 12px; + left: 12px; + display: flex; + align-items: center; + gap: 8px; + z-index: 20; +} + +.videoTopRight { + position: absolute; + top: 12px; + right: 12px; + display: flex; + align-items: center; + gap: 8px; + z-index: 20; +} + +.videoBottomRight { + position: absolute; + bottom: 12px; + right: 12px; + display: flex; + gap: 8px; + z-index: 20; +} + +.videoName { + color: white; + font-size: 14px; + font-weight: var(--au-font-medium); + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); +} + +.videoStatus { + color: rgba(255, 255, 255, 0.9); + font-size: 12px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); +} + +.videoControl { + width: 36px; + height: 36px; + border-radius: var(--au-radius-full); + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + color: white; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: all var(--au-transition-fast); +} + +.videoControl:hover { + background: rgba(0, 0, 0, 0.8); + transform: scale(1.1); +} + +.videoControl.muted { + background: rgba(220, 38, 38, 0.9); +} + +.qualityIndicator { + display: flex; + align-items: flex-end; + gap: 2px; + padding: 4px 8px; + background: rgba(0, 0, 0, 0.6); + border-radius: var(--au-radius-md); + backdrop-filter: blur(4px); +} + +.qualityBar { + width: 2px; + background: var(--au-success-500); + border-radius: var(--au-radius-sm); +} + +.qualityBar.inactive { + background: var(--au-gray-600); +} + diff --git a/packages/avanza-ui/src/components/VideoTile.tsx b/packages/avanza-ui/src/components/VideoTile.tsx new file mode 100644 index 0000000..1db996a --- /dev/null +++ b/packages/avanza-ui/src/components/VideoTile.tsx @@ -0,0 +1,160 @@ +import React, { useEffect, useRef } from 'react'; +import { cn } from '../utils/helpers'; +import type { ComponentBaseProps } from '../types'; +import { Avatar } from './Avatar'; +import { Dropdown, DropdownItem } from './Dropdown'; +import styles from './VideoTile.module.css'; + +export type ConnectionQuality = 'excellent' | 'good' | 'poor' | 'lost'; + +export interface VideoTileProps extends ComponentBaseProps { + name: string; + stream?: MediaStream | null; + muted?: boolean; + isLocal?: boolean; + isSpeaking?: boolean; + connectionQuality?: ConnectionQuality; + onToggleMute?: () => void; + onToggleCamera?: () => void; + onRemove?: () => void; +} + +export const VideoTile: React.FC = (props) => { + const { + name, + stream = null, + muted = false, + isLocal = false, + isSpeaking = false, + connectionQuality = 'good', + onToggleMute, + onToggleCamera, + onRemove, + className, + style, + id, + } = props; + + const videoRef = useRef(null); + + useEffect(() => { + const vid = videoRef.current; + if (!vid) return; + if (stream) { + try { + vid.srcObject = stream; + const p = vid.play(); + if (p && typeof p.then === 'function') p.catch(() => {}); + } catch (e) { + // ignore + } + } else { + vid.srcObject = null; + } + }, [stream]); + + const QualityIndicator = ({ quality }: { quality: ConnectionQuality }) => { + const levels = { excellent: 4, good: 3, poor: 2, lost: 0 }; + const filled = levels[quality]; + return ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
= filled && styles.inactive)} + style={{ height: `${6 + i * 4}px` }} + /> + ))} +
+ ); + }; + + return ( +
+