diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/admin/admin-login/admin-login.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/admin/admin-login/admin-login.component.scss index a177acac..24e29e70 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/admin/admin-login/admin-login.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/admin/admin-login/admin-login.component.scss @@ -19,7 +19,7 @@ max-width: 300px; min-width: 300px; border-radius: 10px; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); + box-shadow: var(--ov-shadow-low); } .form-btn { diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/avatar-profile/avatar-profile.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/avatar-profile/avatar-profile.component.scss index 22c8e533..d6cc49d5 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/avatar-profile/avatar-profile.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/avatar-profile/avatar-profile.component.scss @@ -1,7 +1,7 @@ .poster { height: 100%; width: 100%; - background-color: #000000; + background-color: var(--ov-video-background); position: absolute; z-index: 888; border-radius: var(--ov-video-radius); @@ -20,7 +20,7 @@ width: 70px; border-radius: var(--ov-video-radius); border: 2px solid var(--ov-text-primary-color); - color: #000000; + color: var(--ov-video-background); } #poster-text { diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/media-element/media-element.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/media-element/media-element.component.scss index e5d47a7c..ccb06802 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/media-element/media-element.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/media-element/media-element.component.scss @@ -9,5 +9,5 @@ video { border: 0; font-size: 100%; border-radius: var(--ov-video-radius); - background-color: #000000; + background-color: var(--ov-video-background); } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/activities-panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/activities-panel.component.scss index d3440835..a4be27c3 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/activities-panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/activities-panel.component.scss @@ -1,5 +1,3 @@ -$ov-activity-status-color: #afafaf; - :host { .activities-body-container { display: block !important; @@ -14,12 +12,12 @@ $ov-activity-status-color: #afafaf; padding: 3px; font-size: 11px; border-radius: var(--ov-surface-radius); - background-color: $ov-activity-status-color; + background-color: var(--ov-primary-action-color); } .activity-icon { display: inherit; - background-color: $ov-activity-status-color;; + background-color: var(--ov-primary-action-color); border-radius: var(--ov-surface-radius); margin: 10px 0px !important; padding: 10px; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component.scss index 55ff69fb..cde20948 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component.scss @@ -1,8 +1,3 @@ -$ov-broadcasting-color: #5903ca; - -$ov-input-color: #cccccc; - - .time-container { padding: 2px; } @@ -14,7 +9,7 @@ $ov-input-color: #cccccc; } #broadcasting-icon { - color: $ov-broadcasting-color !important; + color: var(--ov-broadcasting-color) !important; } .broadcasting-duration { @@ -31,12 +26,12 @@ $ov-input-color: #cccccc; } .started { - background-color: $ov-broadcasting-color !important; + background-color: var(--ov-broadcasting-color) !important; color: var(--ov-text-primary-color); } .activity-icon.started { - background-color: $ov-broadcasting-color !important; + background-color: var(--ov-broadcasting-color) !important; color: var(--ov-text-primary-color); } @@ -119,10 +114,11 @@ mat-expansion-panel { .input-container { height: 25px; display: flex; - background-color: $ov-input-color; + background-color: var(--ov-input-background); padding: 10px; margin: 10px; border-radius: var(--ov-surface-radius); + border: 1px solid var(--ov-border-color); order: 3; justify-content: space-evenly; align-items: center; @@ -131,6 +127,7 @@ mat-expansion-panel { .input-container input { width: 100%; height: 16px; + color: var(--ov-text-surface-color); margin: auto; background-color: transparent; display: block; @@ -143,5 +140,19 @@ mat-expansion-panel { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; - font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif; + + &::placeholder { + color: var(--ov-text-surface-color); + opacity: 1; /* Firefox */ + } +} + +#broadcasting-btn { + color: var(--ov-text-secondary-color); + + &:disabled { + // background-color: var(--ov-disabled-color) !important; + color: var(--ov-text-disabled-color) !important; + cursor: not-allowed !important; + } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/recording-activity/recording-activity.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/recording-activity/recording-activity.component.scss index e5e77190..07f30254 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/recording-activity/recording-activity.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/activities-panel/recording-activity/recording-activity.component.scss @@ -1,5 +1,4 @@ :host { - $ov-activity-status-color: #afafaf; .recording-title, .recording-subtitle { color: var(--ov-text-surface-color); @@ -25,7 +24,7 @@ } .recording-duration { - background-color: $ov-activity-status-color; + background-color: var(--ov-activity-status-color); padding: 4px 8px; border-radius: var(--ov-surface-radius); font-weight: 500; @@ -120,6 +119,7 @@ font-weight: 600; font-size: 15px; margin-bottom: 2px; + color: var(--ov-text-surface-color); } .status-message { @@ -173,7 +173,7 @@ } .recording-card { - background: var(--ov-surface-background-color); + background: var(--ov-surface-container-color); border: 1px solid rgba(0, 102, 204, 0.1); border-radius: var(--ov-surface-radius); padding: 8px; @@ -261,7 +261,7 @@ font-weight: 500; &.recording-live-text { - color: var(--ov-primary-action-color); + color: var(--ov-text-surface-color); text-transform: uppercase; letter-spacing: 0.5px; } @@ -317,22 +317,24 @@ color: var(--ov-accent-action-color); &:hover { - background: rgba(0, 102, 204, 0.1); - color: var(--ov-accent-action-color); + background: transparent; } } &.action-view { color: var(--ov-accent-action-color); border-radius: var(--ov-surface-radius); + + &:hover { + background: transparent; + } } &.action-download { - color: #4caf50; + color: var(--ov-success-color); &:hover { - background: rgba(76, 175, 80, 0.1); - color: #4caf50; + background: transparent; } } @@ -340,8 +342,7 @@ color: var(--ov-error-color); &:hover { - background: rgba(244, 67, 54, 0.1); - color: var(--ov-error-color); + background: transparent; } } } @@ -538,7 +539,7 @@ #start-recording-btn { width: 100%; background-color: var(--ov-primary-action-color); - color: var(--ov-secondary-action-color); + color: var(--ov-text-surface-color); border-radius: var(--ov-surface-radius); } @@ -562,7 +563,7 @@ #stop-recording-btn { width: 100%; background-color: var(--ov-error-color); - color: var(--ov-secondary-action-color); + color: #fff; border-radius: var(--ov-surface-radius); } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.html index 49daab22..8ac505a1 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.html @@ -8,7 +8,7 @@ } @else { - } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss index 594b78ba..e7712d61 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss @@ -2,10 +2,15 @@ margin: 0 10px 0px 10px; max-height: 100%; min-height: 100%; + + .panel-close-button { + margin: 0; + } } .background-title { color: var(--ov-text-surface-color); margin: 10px 0; + font-weight: 300; } .effects-container { display: block !important; @@ -17,8 +22,8 @@ .effect-button { margin: 5px; border-radius: var(--ov-surface-radius); - background-color: var(--ov-secondary-action-color); - color: var(--ov-primary-action-color); + background-color: var(--ov-primary-action-color); + color: var(--ov-secondary-action-color); width: 60px; height: 60px; line-height: inherit; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/chat-panel/chat-panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/chat-panel/chat-panel.component.scss index 4a34afa6..05eb9656 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/chat-panel/chat-panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/chat-panel/chat-panel.component.scss @@ -1,5 +1,3 @@ -$ov-selection-color: #d4d6d7; - .text-container { color: var(--ov-text-primary-color); text-align: center; @@ -29,8 +27,8 @@ $ov-selection-color: #d4d6d7; .input-container { height: 65px; display: flex; - background-color: var(--ov-surface-color); - border: 1px solid $ov-selection-color; + background-color: var(--ov-input-background); + border: 1px solid var(--ov-border-color); padding: 10px 5px 10px 10px; margin: 10px; border-radius: var(--ov-surface-radius); @@ -87,7 +85,7 @@ $ov-selection-color: #d4d6d7; position: relative; border-radius: var(--ov-surface-radius); padding: 8px; - color: var(--ov-secondary-action-color); + color: var(--ov-text-surface-color); width: auto; max-width: 95%; font-size: 14px; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/panel.component.scss index 0f8a41c3..5279b327 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/panel.component.scss @@ -37,12 +37,12 @@ } ::-webkit-scrollbar-thumb { - background: #a7a7a7; + background: var(--ov-selection-color-btn); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: #7c7c7c; + background: var(--ov-text-disabled-color); } ::-webkit-scrollbar-track { diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss index 1586373d..5c8b464a 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss @@ -6,8 +6,8 @@ align-items: center; padding: 12px 16px; border-radius: var(--ov-surface-radius, 8px); - background-color: var(--ov-surface-background, #ffffff); - border-bottom: 1px solid var(--ov-surface-border, #e0e0e0); + background-color: var(--ov-surface-color); + border-bottom: 1px solid var(--ov-border-color); transition: all 0.2s ease-in-out; min-height: 64px; @@ -57,7 +57,7 @@ border-radius: var(--ov-surface-radius); margin-right: 12px; padding: 0; - color: #ffffff; + color: var(--ov-text-primary-color); font-weight: 500; flex-shrink: 0; position: relative; @@ -85,7 +85,7 @@ font-weight: 600 !important; font-size: 14px; line-height: 1.2; - color: var(--ov-text-primary, #212121); + color: var(--ov-text-surface-color); margin: 0 0 4px 0; display: flex; align-items: center; @@ -98,14 +98,14 @@ .local-indicator { font-size: 10px; font-weight: 600; - color: var(--ov-primary-color, #1976d2); - background-color: var(--ov-primary-light, #e3f2fd); + color: var(--ov-focus-color); + background-color: var(--ov-surface-color); padding: 4px 8px; border-radius: var(--ov-surface-radius); text-transform: uppercase; letter-spacing: 0.5px; flex-shrink: 0; - border: 1px solid var(--ov-primary-color, #1976d2); + border: 1px solid var(--ov-border-focus-color); } } @@ -115,7 +115,7 @@ font-size: 12px !important; font-weight: 400; margin: 0; - color: var(--ov-text-secondary, #757575); + color: var(--ov-text-secondary-color); line-height: 1.3; display: flex; align-items: center; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.scss index b1dc8afa..e73afc06 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.scss @@ -11,7 +11,7 @@ .item-menu { padding-right: 5px; - border-right: 1px solid var(--ov-secondary-action-color); + border-right: 1px solid var(--ov-border-color); width: 170px; } .item-menu.mobile { diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html index ee8ebab8..54930231 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html @@ -62,7 +62,7 @@ @if (backgroundEffectEnabled && hasVideoDevices) {
+ + + + +
+
+ Current theme: {{ currentTheme }} +
+ ` +}) +export class ThemeDemoComponent implements OnInit { + currentTheme: OpenViduThemeMode = 'auto'; + + constructor(private themeService: OpenViduThemeService) {} + + ngOnInit() { + this.themeService.currentTheme$.subscribe(theme => { + this.currentTheme = theme; + }); + } + + setTheme(theme: OpenViduThemeMode) { + this.themeService.setTheme(theme); + } + + toggleTheme() { + this.themeService.toggleTheme(); + } + + applyCustomBrand() { + this.themeService.updateThemeVariables({ + '--ov-primary-action-color': '#ff6b35', + '--ov-accent-action-color': '#f7931e', + '--ov-surface-radius': '12px' + }); + } +} +``` diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/config/theme.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/config/theme.scss new file mode 100644 index 00000000..dd0f7c50 --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/config/theme.scss @@ -0,0 +1,147 @@ +@use '@angular/material' as mat; + +// Mixin for applying OpenVidu CSS variables based on an Angular Material theme +@mixin apply-openvidu-theme($theme) { + :root { + // === Core Background Colors === + --ov-background-color: #{mat.get-theme-color($theme, background)}; + --ov-surface-color: #{mat.get-theme-color($theme, surface)}; + --ov-surface-container-color: #{mat.get-theme-color($theme, surface-container)}; + --ov-surface-container-high-color: #{mat.get-theme-color($theme, surface-container-high)}; + + // === Action Colors (Primary, Secondary, Accent) === + --ov-primary-action-color: #{mat.get-theme-color($theme, primary)}; + --ov-primary-action-color-lighter: #{mat.get-theme-color($theme, primary-container)}; + --ov-secondary-action-color: #{mat.get-theme-color($theme, secondary)}; + --ov-accent-action-color: #{mat.get-theme-color($theme, tertiary)}; + + // === State Colors === + --ov-error-color: #{mat.get-theme-color($theme, error)}; + --ov-warn-color: #{mat.get-theme-color($theme, error-container)}; + --ov-success-color: #{mat.get-theme-color($theme, tertiary-container)}; + + // === Text Colors === + --ov-text-primary-color: #{mat.get-theme-color($theme, on-background)}; + --ov-text-surface-color: #{mat.get-theme-color($theme, on-surface)}; + --ov-text-secondary-color: #{mat.get-theme-color($theme, on-surface-variant)}; + --ov-text-disabled-color: #{mat.get-theme-color($theme, outline)}; + + // === Interactive States === + --ov-hover-color: #{mat.get-theme-color($theme, surface-container-highest)}; + --ov-active-color: #{mat.get-theme-color($theme, primary-container)}; + --ov-focus-color: #{mat.get-theme-color($theme, primary)}; + --ov-disabled-background: #{mat.get-theme-color($theme, surface-container-low)}; + --ov-disabled-border-color: #{mat.get-theme-color($theme, outline-variant)}; + + // === Input & Form Colors === + --ov-input-background: #{mat.get-theme-color($theme, surface-container)}; + --ov-border-color: #{mat.get-theme-color($theme, outline-variant)}; + --ov-border-focus-color: #{mat.get-theme-color($theme, primary)}; + } +} + +// Mixin for applying dark theme of OpenVidu +@mixin apply-openvidu-dark-theme() { + :root { + // === Core Background Colors === + --ov-background-color: #1f2020; + --ov-surface-color: #2d2d2d; + --ov-surface-container-color: #3a3a3a; + --ov-surface-container-high-color: #474747; + + // === Action Colors === + --ov-primary-action-color: #4a5a5d; + --ov-primary-action-color-lighter: #5a6a6d; + --ov-secondary-action-color: #e1e1e1; + --ov-accent-action-color: #00b3d6; + + // === State Colors === + --ov-error-color: #ff6b6b; + --ov-warn-color: #ffd93d; + --ov-success-color: #69db7c; + + // === Text Colors === + --ov-text-primary-color: #ffffff; + --ov-text-surface-color: #ffffff; + --ov-text-secondary-color: #b3b3b3; + --ov-text-disabled-color: #666666; + + // === Interactive States === + --ov-hover-color: #4a4a4a; + --ov-active-color: rgba(66, 133, 244, 0.2); + --ov-focus-color: #5294ff; + --ov-disabled-background: #3a3a3a; + --ov-disabled-border-color: #555555; + + // === Input & Form Colors === + --ov-input-background: #3a3a3a; + --ov-border-color: #555555; + --ov-border-focus-color: #5294ff; + } +} + +// Mixin for applying light theme of OpenVidu +@mixin apply-openvidu-light-theme() { + :root { + // === Core Background Colors === + --ov-background-color: #ffffff; + --ov-surface-color: #ffffff; + --ov-surface-container-color: #f8f9fa; + --ov-surface-container-high-color: #f0f0f0; + + // === Action Colors === + --ov-primary-action-color: #273235; + --ov-primary-action-color-lighter: #394649; + --ov-secondary-action-color: #6c757d; + --ov-accent-action-color: #0089ab; + + // === State Colors === + --ov-error-color: #dc3545; + --ov-warn-color: #ffc107; + --ov-success-color: #28a745; + + // === Text Colors === + --ov-text-primary-color: #212529; + --ov-text-surface-color: #212529; + --ov-text-secondary-color: #6c757d; + --ov-text-disabled-color: #adb5bd; + + // === Interactive States === + --ov-hover-color: #f8f9fa; + --ov-active-color: rgba(66, 133, 244, 0.08); + --ov-focus-color: #4285f4; + --ov-disabled-background: #f8f9fa; + --ov-disabled-border-color: #dee2e6; + + // === Input & Form Colors === + --ov-input-background: #ffffff; + --ov-border-color: #ced4da; + --ov-border-focus-color: #4285f4; + } +} + +// Mixin for establishing responsive theme properties +@mixin openvidu-theme-responsive() { + // Media query for detecting system dark theme preference + @media (prefers-color-scheme: dark) { + :root:not([data-ov-theme]) { + @include apply-openvidu-dark-theme(); + } + } + + // Media query for detecting system light theme preference + @media (prefers-color-scheme: light) { + :root:not([data-ov-theme]) { + @include apply-openvidu-light-theme(); + } + } + + // Apply specific theme when explicitly defined + :root[data-ov-theme='dark'] { + @include apply-openvidu-dark-theme(); + } + + :root[data-ov-theme='light'] { + @include apply-openvidu-light-theme(); + } +} diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts new file mode 100644 index 00000000..8d056f4a --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts @@ -0,0 +1,139 @@ +/** + * Represents the possible theme modes for OpenVidu components + * @internal + */ +export enum OpenViduThemeMode { + Light = 'light', + Dark = 'dark', + None = 'none', + Auto = 'auto' +} + +/** + * Interface representing the complete set of theme variables for OpenVidu components + * @internal + */ +export interface OpenViduThemeVariables { + // === Core Background Colors === + '--ov-background-color'?: string; + '--ov-surface-color'?: string; + '--ov-surface-container-color'?: string; + '--ov-surface-container-high-color'?: string; + + // === Action Colors === + '--ov-primary-action-color'?: string; + '--ov-primary-action-color-lighter'?: string; + '--ov-secondary-action-color'?: string; + '--ov-accent-action-color'?: string; + + // === State Colors === + '--ov-error-color'?: string; + '--ov-warn-color'?: string; + '--ov-success-color'?: string; + + // === Text Colors === + '--ov-text-primary-color'?: string; + '--ov-text-surface-color'?: string; + '--ov-text-secondary-color'?: string; + '--ov-text-disabled-color'?: string; + + // === Interactive States === + '--ov-hover-color'?: string; + '--ov-active-color'?: string; + '--ov-focus-color'?: string; + '--ov-disabled-background'?: string; + '--ov-disabled-border-color'?: string; + + // === Input & Form Colors === + '--ov-input-background'?: string; + '--ov-border-color'?: string; + '--ov-border-focus-color'?: string; + + // === Layout & Spacing === + '--ov-toolbar-buttons-radius'?: string; + '--ov-leave-button-radius'?: string; + '--ov-video-radius'?: string; + '--ov-surface-radius'?: string; + '--ov-input-radius'?: string; + + // === Special Colors === + '--ov-recording-color'?: string; + '--ov-broadcasting-color'?: string; + '--ov-selection-color'?: string; + '--ov-selection-color-btn'?: string; + '--ov-activity-status-color'?: string; + + // === Video/Media Specific === + '--ov-video-background'?: string; + '--ov-audio-wave-color'?: string; + '--ov-captions-height'?: string; + + // Allow for custom variables + [key: string]: string | undefined; +} + +/** + * Predefined theme configurations + * @internal + */ +export const OPENVIDU_LIGHT_THEME: OpenViduThemeVariables = { + '--ov-background-color': '#f0f0f0', + '--ov-surface-color': '#ffffff', + '--ov-surface-container-color': '#f8f9fa', + '--ov-surface-container-high-color': '#f0f0f0', + '--ov-primary-action-color': '#d3d7d8ff', + '--ov-primary-action-color-lighter': '#c1cbceff', + '--ov-secondary-action-color': '#6e6d6dff', + '--ov-accent-action-color': '#bddfe7ff', + '--ov-error-color': '#dc3545', + '--ov-warn-color': '#eea300', + '--ov-success-color': '#28a745', + '--ov-text-primary-color': '#212529', + '--ov-text-surface-color': '#212529', + '--ov-text-secondary-color': '#6c757d', + '--ov-text-disabled-color': '#adb5bd', + '--ov-hover-color': '#f8f9fa', + '--ov-active-color': 'rgba(66, 133, 244, 0.08)', + '--ov-focus-color': '#4285f4', + '--ov-disabled-background': '#f8f9fa', + '--ov-disabled-border-color': '#dee2e6', + '--ov-input-background': '#ffffff', + '--ov-border-color': '#ced4da', + '--ov-border-focus-color': '#4285f4', + '--ov-activity-status-color': '#c8cdd6', + '--ov-broadcasting-color': '#8837f1', + '--ov-video-background': '#000000' +}; + +/** + * Predefined dark theme configuration + * @internal + */ +export const OPENVIDU_DARK_THEME: OpenViduThemeVariables = { + '--ov-background-color': '#1f2020', + '--ov-surface-color': '#2d2d2d', + '--ov-surface-container-color': '#3a3a3a', + '--ov-surface-container-high-color': '#474747', + '--ov-primary-action-color': '#4a4e4e', + '--ov-primary-action-color-lighter': '#93a5a8ff', + '--ov-secondary-action-color': '#e1e1e1', + '--ov-accent-action-color': '#009ab9ff', + '--ov-error-color': '#dc3545', + '--ov-warn-color': '#eea300', + '--ov-success-color': '#69db7c', + '--ov-text-primary-color': '#ffffff', + '--ov-text-surface-color': '#f0f0f0', + '--ov-text-secondary-color': '#b3b3b3', + '--ov-text-disabled-color': '#666666', + '--ov-hover-color': '#4a4a4a', + '--ov-active-color': '#4285f433', + '--ov-focus-color': '#5294ff', + '--ov-disabled-background': '#3a3a3a', + '--ov-disabled-border-color': '#555555', + '--ov-input-background': '#3a3a3a', + '--ov-border-color': '#555555', + '--ov-border-focus-color': '#5294ff', + '--ov-activity-status-color': '#c8cdd6ff', + '--ov-broadcasting-color': '#9d5af3ff', + '--ov-video-background': '#000000' +}; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts index b243a956..cd67bde3 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts @@ -19,7 +19,7 @@ import { CustomDevice } from '../../models/device.model'; }) export class StorageService implements OnDestroy { public log: ILogger; - protected readonly PREFIX_KEY = STORAGE_PREFIX; + readonly PREFIX_KEY = STORAGE_PREFIX; private readonly tabId: string; private readonly TAB_CLEANUP_INTERVAL = 30000; // 30 seconds private readonly TAB_TIMEOUT_THRESHOLD = 60000; // 60 seconds diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts new file mode 100644 index 00000000..898821ee --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts @@ -0,0 +1,238 @@ +import { Injectable, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { OPENVIDU_DARK_THEME, OPENVIDU_LIGHT_THEME, OpenViduThemeMode, OpenViduThemeVariables } from '../../models/theme.model'; + +/** + * Service for managing OpenVidu component themes dynamically + * + * This service allows you to: + * - Switch between light, dark, and auto themes + * - Apply custom theme variables + * - Listen to theme changes + * - Integrate with Angular Material themes + * + * @example + * ```typescript + * // Inject the service + * constructor(private themeService: OpenViduThemeService) {} + * + * // Switch to dark theme + * this.themeService.setTheme('dark'); + * + * // Apply custom variables + * this.themeService.updateThemeVariables({ + * '--ov-primary-action-color': '#ff5722', + * '--ov-accent-action-color': '#4caf50' + * }); + * + * // Listen to theme changes + * this.themeService.currentTheme$.subscribe(theme => { + * console.log('Current theme:', theme); + * }); + * ``` + * + * @internal + */ +@Injectable({ + providedIn: 'root' +}) +export class OpenViduThemeService { + private readonly THEME_STORAGE_KEY = 'openvidu-theme'; + private readonly THEME_ATTRIBUTE = 'data-ov-theme'; + + private currentThemeSubject = new BehaviorSubject(OpenViduThemeMode.None); + private currentVariablesSubject = new BehaviorSubject({}); + + /** + * Observable that emits the current theme mode + */ + public readonly currentTheme$: Observable = this.currentThemeSubject.asObservable(); + + /** + * Observable that emits the current theme variables + */ + public readonly currentVariables$: Observable = this.currentVariablesSubject.asObservable(); + + constructor(@Inject(DOCUMENT) private document: Document) { + this.initializeTheme(); + this.setupSystemThemeListener(); + } + + /** + * Gets the current theme mode + */ + getCurrentTheme(): OpenViduThemeMode { + return this.currentThemeSubject.value; + } + + /** + * Gets the current theme variables + */ + getCurrentVariables(): OpenViduThemeVariables { + return this.currentVariablesSubject.value; + } + + /** + * Sets the theme mode (light, dark, or auto) + * @param theme The theme mode to apply + */ + setTheme(theme: OpenViduThemeMode): void { + this.currentThemeSubject.next(theme); + this.applyTheme(theme); + this.saveThemeToStorage(theme); + } + + /** + * Updates specific theme variables + * @param variables Object containing CSS variables to update + */ + updateThemeVariables(variables: OpenViduThemeVariables): void { + const mergedVariables = { ...this.currentVariablesSubject.value, ...variables }; + this.currentVariablesSubject.next(mergedVariables); + this.applyCSSVariables(variables); + } + + /** + * Replaces all theme variables with a new set + * @param variables Complete set of theme variables + */ + setThemeVariables(variables: OpenViduThemeVariables): void { + this.currentVariablesSubject.next(variables); + this.applyCSSVariables(variables); + } + + /** + * Resets theme variables to default values based on current theme + */ + resetThemeVariables(): void { + const currentTheme = this.getCurrentTheme(); + const defaultVariables = this.getDefaultVariablesForTheme(currentTheme); + this.setThemeVariables(defaultVariables); + } + + /** + * Applies a predefined theme configuration + * @param themeVariables Predefined theme configuration (e.g., OPENVIDU_LIGHT_THEME) + */ + applyThemeConfiguration(themeVariables: OpenViduThemeVariables): void { + this.setThemeVariables(themeVariables); + } + + /** + * Toggles between light and dark themes + */ + toggleTheme(): void { + const currentTheme = this.getCurrentTheme(); + if (currentTheme === OpenViduThemeMode.Light) { + this.setTheme(OpenViduThemeMode.Dark); + } else if (currentTheme === OpenViduThemeMode.Dark) { + this.setTheme(OpenViduThemeMode.Light); + } else { + // If auto, switch to opposite of system preference + const prefersDark = this.prefersDarkMode(); + this.setTheme(prefersDark ? OpenViduThemeMode.Light : OpenViduThemeMode.Dark); + } + } + + /** + * Gets a specific CSS variable value + * @param variableName The CSS variable name (with or without --) + */ + getThemeVariable(variableName: string): string { + const varName = variableName.startsWith('--') ? variableName : `--${variableName}`; + return getComputedStyle(this.document.documentElement).getPropertyValue(varName).trim(); + } + + /** + * Checks if the system prefers dark mode + */ + prefersDarkMode(): boolean { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + private initializeTheme(): void { + const savedTheme = this.getThemeFromStorage(); + const initialTheme = savedTheme || OpenViduThemeMode.None; + this.applyTheme(initialTheme); + this.currentThemeSubject.next(initialTheme); + } + + private applyTheme(theme: OpenViduThemeMode): void { + const documentElement = this.document.documentElement; + + if (theme === OpenViduThemeMode.Auto || theme === OpenViduThemeMode.None) { + documentElement.removeAttribute(this.THEME_ATTRIBUTE); + } else { + documentElement.setAttribute(this.THEME_ATTRIBUTE, theme); + } + + // Apply default variables for the theme + const defaultVariables = this.getDefaultVariablesForTheme(theme); + this.applyCSSVariables(defaultVariables); + } + + private applyCSSVariables(variables: OpenViduThemeVariables): void { + const documentElement = this.document.documentElement; + + Object.entries(variables).forEach(([property, value]) => { + if (value !== undefined) { + documentElement.style.setProperty(property, value); + } + }); + } + + private getDefaultVariablesForTheme(theme: OpenViduThemeMode): OpenViduThemeVariables { + switch (theme) { + case OpenViduThemeMode.Light: + return OPENVIDU_LIGHT_THEME; + case OpenViduThemeMode.Dark: + return OPENVIDU_DARK_THEME; + case OpenViduThemeMode.None: + return {}; + case OpenViduThemeMode.Auto: + // Auto theme - use system preference + return this.prefersDarkMode() ? OPENVIDU_DARK_THEME : OPENVIDU_LIGHT_THEME; + default: + return {}; + } + } + + private setupSystemThemeListener(): void { + if (window.matchMedia) { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + const handleSystemThemeChange = (event: MediaQueryListEvent) => { + if (this.getCurrentTheme() === OpenViduThemeMode.Auto) { + const defaultVariables = this.getDefaultVariablesForTheme(OpenViduThemeMode.Auto); + this.applyCSSVariables(defaultVariables); + } + }; + + // Use the newer addEventListener if available, otherwise use the deprecated addListener + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', handleSystemThemeChange); + } + } + } + + private saveThemeToStorage(theme: OpenViduThemeMode): void { + try { + localStorage.setItem(this.THEME_STORAGE_KEY, theme); + } catch (error) { + console.warn('Failed to save theme to localStorage:', error); + } + } + + private getThemeFromStorage(): OpenViduThemeMode | null { + try { + const saved = localStorage.getItem(this.THEME_STORAGE_KEY) as OpenViduThemeMode; + if (saved && [OpenViduThemeMode.Light, OpenViduThemeMode.Dark, OpenViduThemeMode.Auto].includes(saved)) { + return saved; + } + } catch (error) { + console.warn('Failed to read theme from localStorage:', error); + } + return null; + } +} diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts index 8d74796e..217d849c 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts @@ -40,6 +40,7 @@ export * from './lib/models/toolbar.model'; export * from './lib/models/logger.model' export * from './lib/models/storage.model'; export * from './lib/models/lang.model'; +export * from './lib/models/theme.model'; // Pipes export * from './lib/pipes/participant.pipe'; export * from './lib/pipes/recording.pipe'; @@ -57,6 +58,7 @@ export * from './lib/services/config/global-config.service'; export * from './lib/services/logger/logger.service'; export * from './lib/services/storage/storage.service'; export * from './lib/services/translate/translate.service'; +export * from './lib/services/theme/theme.service'; //Modules export * from './lib/openvidu-components-angular.module'; export * from './lib/openvidu-components-angular-ui.module'; diff --git a/openvidu-components-angular/src/styles.scss b/openvidu-components-angular/src/styles.scss index a1b26e05..65241dd5 100644 --- a/openvidu-components-angular/src/styles.scss +++ b/openvidu-components-angular/src/styles.scss @@ -1,4 +1,5 @@ @use '@angular/material' as mat; +@use '../projects/openvidu-components-angular/src/lib/config/theme' as ovtheme; @include mat.elevation-classes(); @include mat.app-background(); @@ -15,8 +16,14 @@ html { @include mat.all-component-colors($openvidu-theme); @include mat.all-component-typographies($openvidu-theme); @include mat.all-component-densities($openvidu-theme); + + // Apply OpenVidu theme integration with Angular Material + @include ovtheme.apply-openvidu-theme($openvidu-theme); } +// Include responsive theme detection +@include ovtheme.openvidu-theme-responsive(); + html, body { height: 100%; @@ -27,23 +34,77 @@ body { font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif; } -// Custom openvidu-components styles +// Custom openvidu-components styles with Angular Material Theme support :root { - --ov-background-color: #1f2020; - --ov-surface-color: #ffffff; + // === Core Background Colors === + --ov-background-color: var(--mat-sys-background, #1f2020); + --ov-surface-color: var(--mat-sys-surface, #ffffff); + --ov-surface-container-color: var(--mat-sys-surface-container, #f3f3f3); + --ov-surface-container-high-color: var(--mat-sys-surface-container-high, #e6e6e6); - --ov-primary-action-color: #273235; - --ov-secondary-action-color: #f1f1f1; - --ov-accent-action-color: #0089ab; + // === Action Colors (Primary, Secondary, Accent) === + --ov-primary-action-color: var(--mat-sys-primary, #273235); + --ov-primary-action-color-lighter: var(--mat-sys-primary-container, #394649); + --ov-secondary-action-color: var(--mat-sys-secondary, #f1f1f1); + --ov-accent-action-color: var(--mat-sys-tertiary, #0089ab); - --ov-error-color: #eb5144; - --ov-warn-color: #ffba53; + // === State Colors === + --ov-error-color: var(--mat-sys-error, #eb5144); + --ov-warn-color: var(--mat-sys-error-container, #ffba53); + --ov-success-color: var(--mat-sys-tertiary-container, #8bffc9); - --ov-text-primary-color: #ffffff; - --ov-text-surface-color: #1d1d1d; + // === Text Colors === + --ov-text-primary-color: var(--mat-sys-on-background, #ffffff); + --ov-text-surface-color: var(--mat-sys-on-surface, #1d1d1d); + --ov-text-secondary-color: var(--mat-sys-on-surface-variant, #666666); + --ov-text-disabled-color: var(--mat-sys-outline, #999999); + // === Interactive States === + --ov-hover-color: var(--mat-sys-surface-container-highest, #f5f5f5); + --ov-active-color: var(--mat-sys-primary-container, rgba(66, 133, 244, 0.08)); + --ov-focus-color: var(--mat-sys-primary, #4285f4); + --ov-disabled-background: var(--mat-sys-surface-container-low, #f5f5f5); + --ov-disabled-border-color: var(--mat-sys-outline-variant, #ddd); + + // === Input & Form Colors === + --ov-input-background: var(--mat-sys-surface-container, #f8f9fa); + --ov-border-color: var(--mat-sys-outline-variant, #e0e0e0); + --ov-border-focus-color: var(--mat-sys-primary, #4285f4); + + // === Shadow & Elevation === + --ov-shadow-low: 0 2px 8px rgba(0, 0, 0, 0.1); + --ov-shadow-medium: 0 4px 20px rgba(0, 0, 0, 0.1); + --ov-shadow-high: 0 8px 32px rgba(0, 0, 0, 0.12); + --ov-border-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2); + + // === Layout & Spacing === --ov-toolbar-buttons-radius: 50%; --ov-leave-button-radius: 10px; --ov-video-radius: 5px; --ov-surface-radius: 5px; + --ov-input-radius: 4px; + + // === Special Colors (with fallbacks) === + --ov-recording-color: var(--ov-error-color); + --ov-broadcasting-color: #5903ca; + --ov-selection-color: #d4d6d7; + --ov-selection-color-btn: #afafaf; + --ov-activity-status-color: #afafaf; + + // === Alpha/Transparency Variants === + --ov-primary-alpha-08: rgba(66, 133, 244, 0.08); + --ov-primary-alpha-10: rgba(66, 133, 244, 0.1); + --ov-error-alpha-10: rgba(211, 47, 47, 0.1); + --ov-warning-alpha-10: rgba(255, 193, 7, 0.1); + --ov-warning-alpha-30: rgba(255, 193, 7, 0.3); + --ov-black-alpha-10: rgba(0, 0, 0, 0.1); + --ov-white-alpha-70: rgba(255, 255, 255, 0.7); + --ov-white-alpha-90: rgba(255, 255, 255, 0.9); + --ov-gray-alpha-50: rgba(150, 150, 150, 0.5); + --ov-gray-alpha-80: rgba(150, 150, 150, 0.8); + + // === Video/Media Specific === + --ov-video-background: #000000; + --ov-audio-wave-color: var(--ov-accent-action-color); + --ov-captions-height: 250px; }