mirror of https://github.com/OpenVidu/openvidu.git
ov-components: Implement theming system
- Added THEME.md documentation detailing the theming system, including usage, service methods, and CSS variables reference. - Created theme.scss with SCSS mixins for applying OpenVidu themes and integrating with Angular Material. - Introduced theme.model.ts to define theme modes and variables. - Developed theme.service.ts to manage theme switching, variable updates, and system theme detection. - Updated public-api.ts to export new theme model and service. - Enhanced styles.scss to incorporate OpenVidu theme integration with Angular Material. - Added support for responsive theme detection based on system preferences.master
parent
7821f3a75d
commit
f0b3c2e2c6
|
@ -19,7 +19,7 @@
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--ov-shadow-low);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-btn {
|
.form-btn {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.poster {
|
.poster {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #000000;
|
background-color: var(--ov-video-background);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 888;
|
z-index: 888;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
width: 70px;
|
width: 70px;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
border: 2px solid var(--ov-text-primary-color);
|
border: 2px solid var(--ov-text-primary-color);
|
||||||
color: #000000;
|
color: var(--ov-video-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
#poster-text {
|
#poster-text {
|
||||||
|
|
|
@ -9,5 +9,5 @@ video {
|
||||||
border: 0;
|
border: 0;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
background-color: #000000;
|
background-color: var(--ov-video-background);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
$ov-activity-status-color: #afafaf;
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
.activities-body-container {
|
.activities-body-container {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
@ -14,12 +12,12 @@ $ov-activity-status-color: #afafaf;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
background-color: $ov-activity-status-color;
|
background-color: var(--ov-primary-action-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon {
|
.activity-icon {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
background-color: $ov-activity-status-color;;
|
background-color: var(--ov-primary-action-color);
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
margin: 10px 0px !important;
|
margin: 10px 0px !important;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
$ov-broadcasting-color: #5903ca;
|
|
||||||
|
|
||||||
$ov-input-color: #cccccc;
|
|
||||||
|
|
||||||
|
|
||||||
.time-container {
|
.time-container {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +9,7 @@ $ov-input-color: #cccccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#broadcasting-icon {
|
#broadcasting-icon {
|
||||||
color: $ov-broadcasting-color !important;
|
color: var(--ov-broadcasting-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.broadcasting-duration {
|
.broadcasting-duration {
|
||||||
|
@ -31,12 +26,12 @@ $ov-input-color: #cccccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.started {
|
.started {
|
||||||
background-color: $ov-broadcasting-color !important;
|
background-color: var(--ov-broadcasting-color) !important;
|
||||||
color: var(--ov-text-primary-color);
|
color: var(--ov-text-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.started {
|
.activity-icon.started {
|
||||||
background-color: $ov-broadcasting-color !important;
|
background-color: var(--ov-broadcasting-color) !important;
|
||||||
color: var(--ov-text-primary-color);
|
color: var(--ov-text-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +114,11 @@ mat-expansion-panel {
|
||||||
.input-container {
|
.input-container {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: $ov-input-color;
|
background-color: var(--ov-input-background);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
|
border: 1px solid var(--ov-border-color);
|
||||||
order: 3;
|
order: 3;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -131,6 +127,7 @@ mat-expansion-panel {
|
||||||
.input-container input {
|
.input-container input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -143,5 +140,19 @@ mat-expansion-panel {
|
||||||
-webkit-box-shadow: none;
|
-webkit-box-shadow: none;
|
||||||
-moz-box-shadow: none;
|
-moz-box-shadow: none;
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
:host {
|
:host {
|
||||||
$ov-activity-status-color: #afafaf;
|
|
||||||
.recording-title,
|
.recording-title,
|
||||||
.recording-subtitle {
|
.recording-subtitle {
|
||||||
color: var(--ov-text-surface-color);
|
color: var(--ov-text-surface-color);
|
||||||
|
@ -25,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-duration {
|
.recording-duration {
|
||||||
background-color: $ov-activity-status-color;
|
background-color: var(--ov-activity-status-color);
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -120,6 +119,7 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-message {
|
.status-message {
|
||||||
|
@ -173,7 +173,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-card {
|
.recording-card {
|
||||||
background: var(--ov-surface-background-color);
|
background: var(--ov-surface-container-color);
|
||||||
border: 1px solid rgba(0, 102, 204, 0.1);
|
border: 1px solid rgba(0, 102, 204, 0.1);
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
@ -261,7 +261,7 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&.recording-live-text {
|
&.recording-live-text {
|
||||||
color: var(--ov-primary-action-color);
|
color: var(--ov-text-surface-color);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
@ -317,22 +317,24 @@
|
||||||
color: var(--ov-accent-action-color);
|
color: var(--ov-accent-action-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 102, 204, 0.1);
|
background: transparent;
|
||||||
color: var(--ov-accent-action-color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.action-view {
|
&.action-view {
|
||||||
color: var(--ov-accent-action-color);
|
color: var(--ov-accent-action-color);
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.action-download {
|
&.action-download {
|
||||||
color: #4caf50;
|
color: var(--ov-success-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(76, 175, 80, 0.1);
|
background: transparent;
|
||||||
color: #4caf50;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,8 +342,7 @@
|
||||||
color: var(--ov-error-color);
|
color: var(--ov-error-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(244, 67, 54, 0.1);
|
background: transparent;
|
||||||
color: var(--ov-error-color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,7 +539,7 @@
|
||||||
#start-recording-btn {
|
#start-recording-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--ov-primary-action-color);
|
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);
|
border-radius: var(--ov-surface-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,7 +563,7 @@
|
||||||
#stop-recording-btn {
|
#stop-recording-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--ov-error-color);
|
background-color: var(--ov-error-color);
|
||||||
color: var(--ov-secondary-action-color);
|
color: #fff;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<button class="pansel-close-button" mat-icon-button matTooltip="{{ 'PANEL.CLOSE' | translate }}" (click)="close()">
|
<button class="panel-close-button" mat-icon-button matTooltip="{{ 'PANEL.CLOSE' | translate }}" (click)="close()">
|
||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,15 @@
|
||||||
margin: 0 10px 0px 10px;
|
margin: 0 10px 0px 10px;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
|
.panel-close-button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.background-title {
|
.background-title {
|
||||||
color: var(--ov-text-surface-color);
|
color: var(--ov-text-surface-color);
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.effects-container {
|
.effects-container {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
@ -17,8 +22,8 @@
|
||||||
.effect-button {
|
.effect-button {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
background-color: var(--ov-secondary-action-color);
|
background-color: var(--ov-primary-action-color);
|
||||||
color: var(--ov-primary-action-color);
|
color: var(--ov-secondary-action-color);
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
$ov-selection-color: #d4d6d7;
|
|
||||||
|
|
||||||
.text-container {
|
.text-container {
|
||||||
color: var(--ov-text-primary-color);
|
color: var(--ov-text-primary-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -29,8 +27,8 @@ $ov-selection-color: #d4d6d7;
|
||||||
.input-container {
|
.input-container {
|
||||||
height: 65px;
|
height: 65px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--ov-surface-color);
|
background-color: var(--ov-input-background);
|
||||||
border: 1px solid $ov-selection-color;
|
border: 1px solid var(--ov-border-color);
|
||||||
padding: 10px 5px 10px 10px;
|
padding: 10px 5px 10px 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
|
@ -87,7 +85,7 @@ $ov-selection-color: #d4d6d7;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
color: var(--ov-secondary-action-color);
|
color: var(--ov-text-surface-color);
|
||||||
width: auto;
|
width: auto;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -37,12 +37,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #a7a7a7;
|
background: var(--ov-selection-color-btn);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #7c7c7c;
|
background: var(--ov-text-disabled-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-radius: var(--ov-surface-radius, 8px);
|
border-radius: var(--ov-surface-radius, 8px);
|
||||||
background-color: var(--ov-surface-background, #ffffff);
|
background-color: var(--ov-surface-color);
|
||||||
border-bottom: 1px solid var(--ov-surface-border, #e0e0e0);
|
border-bottom: 1px solid var(--ov-border-color);
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #ffffff;
|
color: var(--ov-text-primary-color);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
color: var(--ov-text-primary, #212121);
|
color: var(--ov-text-surface-color);
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 4px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -98,14 +98,14 @@
|
||||||
.local-indicator {
|
.local-indicator {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ov-primary-color, #1976d2);
|
color: var(--ov-focus-color);
|
||||||
background-color: var(--ov-primary-light, #e3f2fd);
|
background-color: var(--ov-surface-color);
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
flex-shrink: 0;
|
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-size: 12px !important;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--ov-text-secondary, #757575);
|
color: var(--ov-text-secondary-color);
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
.item-menu {
|
.item-menu {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
border-right: 1px solid var(--ov-secondary-action-color);
|
border-right: 1px solid var(--ov-border-color);
|
||||||
width: 170px;
|
width: 170px;
|
||||||
}
|
}
|
||||||
.item-menu.mobile {
|
.item-menu.mobile {
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
@if (backgroundEffectEnabled && hasVideoDevices) {
|
@if (backgroundEffectEnabled && hasVideoDevices) {
|
||||||
<div class="background-control">
|
<div class="background-control">
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-flat-button
|
||||||
class="background-button"
|
class="background-button"
|
||||||
(click)="toggleBackgroundPanel()"
|
(click)="toggleBackgroundPanel()"
|
||||||
[matTooltip]="'Virtual Backgrounds'"
|
[matTooltip]="'Virtual Backgrounds'"
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
aspect-ratio: 4/3;
|
aspect-ratio: 4/3;
|
||||||
border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0;
|
border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #000;
|
background: var(--ov-video-background);
|
||||||
.video-frame {
|
.video-frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -154,23 +154,16 @@
|
||||||
.background-button {
|
.background-button {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background: rgba(255, 255, 255, 0.7);
|
min-width: 48px;
|
||||||
backdrop-filter: blur(20px);
|
min-height: 48px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
background: var(--ov-primary-action-color);
|
||||||
|
color: var(--ov-secondary-action-color);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
padding: 0;
|
||||||
color: #333333;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
||||||
transform: translateZ(0);
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mat-mdc-button-disabled {
|
&.mat-mdc-button-disabled {
|
||||||
background: rgba(255, 255, 255, 0.137);
|
background:var(--ov-disabled-background);
|
||||||
color: rgba(233, 233, 233, 0.5);
|
color: var(--ov-text-disabled-color);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -184,10 +177,7 @@
|
||||||
height: 22px;
|
height: 22px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
}
|
margin: 0;
|
||||||
|
|
||||||
&:hover mat-icon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,11 +225,11 @@
|
||||||
input {
|
input {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--ov-text-primary-color, #333);
|
color: var(--ov-text-surface-color, #666);
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: var(--ov-text-secondary-color, #666);
|
color: var(--ov-text-surface-color, #666);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +263,7 @@
|
||||||
.join-button {
|
.join-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
background: var(--ov-primary-action-color, #4285f4);
|
background: var(--ov-focus-color, #4285f4);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
background-color: black;
|
background-color: var(--ov-video-background);
|
||||||
opacity: 80%;
|
opacity: 80%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
@ -96,17 +96,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(mdc-migration): The following rule targets internal classes of button that may no longer apply for the MDC version. */
|
|
||||||
::ng-deep .mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
|
::ng-deep .mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
|
||||||
padding: 1px !important;
|
padding: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-input-element {
|
::ng-deep .mat-mdc-input-element {
|
||||||
caret-color: #000000;
|
caret-color: var(--ov-video-background);
|
||||||
}
|
}
|
||||||
/* TODO(mdc-migration): The following rule targets internal classes of option that may no longer apply for the MDC version. */
|
|
||||||
::ng-deep .mat-primary .mat-mdc-option.mat-selected:not(.mat-option-disabled) {
|
::ng-deep .mat-primary .mat-mdc-option.mat-selected:not(.mat-option-disabled) {
|
||||||
color: #000000;
|
color: var(--ov-video-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
|
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
|
||||||
|
|
|
@ -10,12 +10,12 @@
|
||||||
&.compact {
|
&.compact {
|
||||||
.unified-device-button {
|
.unified-device-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: rgba(255, 255, 255, 0.7);
|
background: var(--ov-primary-action-color);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--ov-shadow-low);
|
||||||
|
|
||||||
.toggle-section {
|
.toggle-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -31,25 +31,25 @@
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&.device-enabled {
|
&.device-enabled {
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
color: var(--ov-focus-color);
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
color: var(--ov-focus-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.device-disabled {
|
&.device-disabled {
|
||||||
background: rgba(244, 67, 54, 0.9);
|
background: var(--ov-error-color);
|
||||||
color: white;
|
color: var(--ov-surface-color);
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
color: white;
|
color: var(--ov-surface-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
background: rgba(150, 150, 150, 0.5);
|
background: var(--ov-gray-alpha-50);
|
||||||
color: rgba(150, 150, 150, 0.8);
|
color: var(--ov-gray-alpha-80);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
border-left: 1px solid var(--ov-black-alpha-10);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: var(--ov-text-secondary-color, #666);
|
color: var(--ov-text-secondary-color, #666);
|
||||||
|
@ -217,17 +217,17 @@
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--ov-hover-color, #f5f5f5);
|
background-color: var(--ov-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: rgba(66, 133, 244, 0.08);
|
background-color: var(--ov-active-color);
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
color: var(--ov-text-surface-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,12 @@
|
||||||
.expand-icon {
|
.expand-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
min-width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
min-height: 16px;
|
||||||
color: var(--ov-text-secondary-color, #666);
|
color: var(--ov-text-secondary-color, #666);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[aria-expanded='true'] .expand-icon {
|
&[aria-expanded='true'] .expand-icon {
|
||||||
|
@ -68,9 +71,9 @@
|
||||||
::ng-deep .language-menu.mat-mdc-menu-panel {
|
::ng-deep .language-menu.mat-mdc-menu-panel {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||||
border: 1px solid var(--ov-border-color, #e0e0e0);
|
border: 1px solid var(--ov-border-color);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--ov-surface-color, #ffffff);
|
background: var(--ov-surface-color);
|
||||||
|
|
||||||
.language-option {
|
.language-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -83,15 +86,14 @@
|
||||||
color: var(--ov-text-surface-color);
|
color: var(--ov-text-surface-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--ov-hover-color, #f5f5f5);
|
background-color: var(--ov-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: rgba(66, 133, 244, 0.08);
|
background-color: var(--ov-active-color);
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
|
||||||
|
|
||||||
.check-icon {
|
.check-icon {
|
||||||
color: var(--ov-primary-action-color, #4285f4);
|
color: var(--ov-text-surface-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,9 +107,5 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected .lang-option-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
background: var(--ov-surface-secondary, #f0f0f0);
|
background: var(--ov-surface-color, #f0f0f0);
|
||||||
color: var(--ov-text-secondary-color, #666);
|
color: var(--ov-text-secondary-color, #666);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
|
@ -44,6 +44,7 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--ov-text-surface-color, #666);
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: var(--ov-text-secondary-color, #666);
|
color: var(--ov-text-secondary-color, #666);
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
@use '../device-selector-shared' as shared;
|
@use '../device-selector-shared' as shared;
|
||||||
|
|
||||||
$ov-selection-color-btn: #afafaf;
|
|
||||||
$ov-selection-color: #cccccc;
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -27,7 +24,7 @@ $ov-selection-color: #cccccc;
|
||||||
.selector-button {
|
.selector-button {
|
||||||
// Video-specific hover effect with box-shadow
|
// Video-specific hover effect with box-shadow
|
||||||
&:hover:not([disabled]) {
|
&:hover:not([disabled]) {
|
||||||
background-color: white !important;
|
background-color: var(--ov-surface-color) !important;
|
||||||
border-color: var(--ov-primary-action-color);
|
border-color: var(--ov-primary-action-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,5 +44,5 @@ $ov-selection-color: #cccccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled):not(.mat-mdc-option-multiple) {
|
::ng-deep .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled):not(.mat-mdc-option-multiple) {
|
||||||
background-color: $ov-selection-color !important;
|
background-color: var(--ov-selection-color) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
$ov-video-elements-bg-color: var(--ov-primary-action-color);
|
|
||||||
:host {
|
:host {
|
||||||
/* Fixes layout bug. The OV_root is created with the entire layout width and it has a weird UX behaviour */
|
/* Fixes layout bug. The OV_root is created with the entire layout width and it has a weird UX behaviour */
|
||||||
.no-size {
|
.no-size {
|
||||||
|
@ -7,10 +6,10 @@ $ov-video-elements-bg-color: var(--ov-primary-action-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-name-container {
|
.participant-name-container {
|
||||||
background-color: $ov-video-elements-bg-color;
|
background-color: var(--ov-primary-action-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: var(--ov-secondary-action-color);
|
color: var(--ov-secondary-action-color);
|
||||||
font-weight: bold;
|
font-weight: 400;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
}
|
}
|
||||||
.participant-name {
|
.participant-name {
|
||||||
|
@ -46,7 +45,7 @@ $ov-video-elements-bg-color: var(--ov-primary-action-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stream-video-controls {
|
.stream-video-controls {
|
||||||
background-color: $ov-video-elements-bg-color;
|
background-color: var(--ov-primary-action-color);
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
*ngIf="showCameraButton"
|
*ngIf="showCameraButton"
|
||||||
(click)="toggleCamera()"
|
(click)="toggleCamera()"
|
||||||
[disabled]="isConnectionLost || !hasVideoDevices || cameraMuteChanging"
|
[disabled]="isConnectionLost || !hasVideoDevices || cameraMuteChanging"
|
||||||
[class.warn-btn]="!isCameraEnabled"
|
[class.disabled]="!isCameraEnabled"
|
||||||
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
|
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
|
||||||
[matTooltipDisabled]="!hasVideoDevices"
|
[matTooltipDisabled]="!hasVideoDevices"
|
||||||
>
|
>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
*ngIf="showMicrophoneButton"
|
*ngIf="showMicrophoneButton"
|
||||||
(click)="toggleMicrophone()"
|
(click)="toggleMicrophone()"
|
||||||
[disabled]="isConnectionLost || !hasAudioDevices || microphoneMuteChanging"
|
[disabled]="isConnectionLost || !hasAudioDevices || microphoneMuteChanging"
|
||||||
[class.warn-btn]="!isMicrophoneEnabled"
|
[class.disabled]="!isMicrophoneEnabled"
|
||||||
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
|
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
|
||||||
[matTooltipDisabled]="!hasAudioDevices"
|
[matTooltipDisabled]="!hasAudioDevices"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
$ov-broadcasting-blinking-color: #5903ca;
|
|
||||||
$ov-recording-blinking-color: #eb5144;
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
#toolbar {
|
#toolbar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -58,10 +55,13 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
#menu-buttons-container button {
|
#menu-buttons-container button {
|
||||||
border-radius: var(--ov-toolbar-buttons-radius);
|
border-radius: var(--ov-toolbar-buttons-radius);
|
||||||
color: var(--ov-secondary-action-color);
|
color: var(--ov-secondary-action-color);
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: var(--ov-error-color) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#media-buttons-container > button,
|
#media-buttons-container > button,
|
||||||
::ng-deep #media-buttons-container > button,
|
::ng-deep #media-buttons-container > button,
|
||||||
#media-buttons-container:not(#media-buttons-container > button) button,
|
#media-buttons-container:not(#media-buttons-container > button) button,
|
||||||
|
@ -74,9 +74,6 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warn-btn {
|
|
||||||
background-color: var(--ov-error-color) !important;
|
|
||||||
}
|
|
||||||
#disable-screen-button > mat-icon {
|
#disable-screen-button > mat-icon {
|
||||||
color: var(--ov-error-color) !important;
|
color: var(--ov-error-color) !important;
|
||||||
}
|
}
|
||||||
|
@ -133,7 +130,7 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
background-color: var(--ov-error-color);
|
background-color: var(--ov-error-color);
|
||||||
}
|
}
|
||||||
.broadcasting-tag {
|
.broadcasting-tag {
|
||||||
background-color: $ov-broadcasting-blinking-color;
|
background-color: var(--ov-broadcasting-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-tag mat-icon,
|
.recording-tag mat-icon,
|
||||||
|
@ -152,12 +149,13 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
background-color: var(--ov-error-color) !important;
|
background-color: var(--ov-error-color) !important;
|
||||||
border-radius: var(--ov-leave-button-radius) !important;
|
border-radius: var(--ov-leave-button-radius) !important;
|
||||||
width: 65px !important;
|
width: 65px !important;
|
||||||
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-icon-button[disabled] {
|
.mat-mdc-icon-button[disabled] {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
::ng-deep .mat-badge-content{
|
::ng-deep .mat-badge-content {
|
||||||
background-color: var(--ov-warn-color);
|
background-color: var(--ov-warn-color);
|
||||||
}
|
}
|
||||||
.divider {
|
.divider {
|
||||||
|
@ -186,14 +184,14 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
/* Animation for recording blinking */
|
/* Animation for recording blinking */
|
||||||
@keyframes blinker-recording {
|
@keyframes blinker-recording {
|
||||||
50% {
|
50% {
|
||||||
background-color: $ov-recording-blinking-color;
|
background-color: var(--ov-recording-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animation for broadcasting blinking */
|
/* Animation for broadcasting blinking */
|
||||||
@keyframes blinker-broadcasting {
|
@keyframes blinker-broadcasting {
|
||||||
50% {
|
50% {
|
||||||
background-color: $ov-broadcasting-blinking-color;
|
background-color: var(--ov-broadcasting-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,14 +244,14 @@ $ov-recording-blinking-color: #eb5144;
|
||||||
color: var(--ov-text-surface-color) !important;
|
color: var(--ov-text-surface-color) !important;
|
||||||
}
|
}
|
||||||
::ng-deep #toolbar-broadcasting-btn > .mat-icon {
|
::ng-deep #toolbar-broadcasting-btn > .mat-icon {
|
||||||
color: $ov-broadcasting-blinking-color !important;
|
color: var(--ov-broadcasting-color) !important;
|
||||||
}
|
}
|
||||||
::ng-deep #recording-btn > .mat-icon {
|
::ng-deep #recording-btn > .mat-icon {
|
||||||
color: $ov-recording-blinking-color !important;
|
color: var(--ov-recording-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-menu-panel {
|
::ng-deep .mat-mdc-menu-panel {
|
||||||
border-radius: var(--ov-surface-radius) !important;
|
border-radius: var(--ov-surface-radius) !important;
|
||||||
background-color: var(--ov-surface-color) !important;
|
background-color: var(--ov-surface-color) !important;
|
||||||
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2) !important;
|
box-shadow: var(--ov-border-shadow) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
# OpenVidu Components Angular - Theme System
|
||||||
|
|
||||||
|
The OpenVidu Components Angular library provides a comprehensive theming system that allows you to customize the appearance of all components to match your application's design. The theme system is fully compatible with Angular Material themes and supports dynamic theme switching.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Quick Start](#quick-start)
|
||||||
|
2. [Theme Service](#theme-service)
|
||||||
|
3. [CSS Variables Reference](#css-variables-reference)
|
||||||
|
4. [Angular Material Integration](#angular-material-integration)
|
||||||
|
5. [Custom Themes](#custom-themes)
|
||||||
|
6. [SCSS Mixins](#scss-mixins)
|
||||||
|
7. [Migration Guide](#migration-guide)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
To get started with theming, import and inject the `OpenViduThemeService`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { OpenViduThemeService, OpenViduThemeMode } from 'openvidu-components-angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html'
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
constructor(private themeService: OpenViduThemeService) {}
|
||||||
|
|
||||||
|
setLightTheme() {
|
||||||
|
this.themeService.setTheme('light');
|
||||||
|
}
|
||||||
|
|
||||||
|
setDarkTheme() {
|
||||||
|
this.themeService.setTheme('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTheme() {
|
||||||
|
this.themeService.toggleTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Variable Override
|
||||||
|
|
||||||
|
You can also customize themes by overriding CSS variables directly in your global styles:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
:root {
|
||||||
|
--ov-primary-action-color: #ff5722;
|
||||||
|
--ov-accent-action-color: #4caf50;
|
||||||
|
--ov-background-color: #fafafa;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Service
|
||||||
|
|
||||||
|
The `OpenViduThemeService` provides methods to manage themes dynamically:
|
||||||
|
|
||||||
|
### Available Methods
|
||||||
|
|
||||||
|
#### `setTheme(theme: OpenViduThemeMode)`
|
||||||
|
Sets the global theme mode.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set light theme
|
||||||
|
this.themeService.setTheme('light');
|
||||||
|
|
||||||
|
// Set dark theme
|
||||||
|
this.themeService.setTheme('dark');
|
||||||
|
|
||||||
|
// Set auto theme (follows system preference)
|
||||||
|
this.themeService.setTheme('auto');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `updateThemeVariables(variables: OpenViduThemeVariables)`
|
||||||
|
Updates specific theme variables without changing the overall theme.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.themeService.updateThemeVariables({
|
||||||
|
'--ov-primary-action-color': '#ff5722',
|
||||||
|
'--ov-accent-action-color': '#4caf50'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `applyThemeConfiguration(themeVariables: OpenViduThemeVariables)`
|
||||||
|
Applies a complete theme configuration.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { OPENVIDU_DARK_THEME } from 'openvidu-components-angular';
|
||||||
|
|
||||||
|
this.themeService.applyThemeConfiguration(OPENVIDU_DARK_THEME);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `getCurrentTheme(): OpenViduThemeMode`
|
||||||
|
Returns the current active theme mode.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const currentTheme = this.themeService.getCurrentTheme();
|
||||||
|
console.log('Current theme:', currentTheme); // 'light', 'dark', 'auto' or 'none'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `toggleTheme()`
|
||||||
|
Toggles between light and dark themes.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.themeService.toggleTheme();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Observables
|
||||||
|
|
||||||
|
Listen to theme changes using the provided observables:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Listen to theme mode changes
|
||||||
|
this.themeService.currentTheme$.subscribe(theme => {
|
||||||
|
console.log('Theme changed to:', theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to theme variable changes
|
||||||
|
this.themeService.currentVariables$.subscribe(variables => {
|
||||||
|
console.log('Theme variables updated:', variables);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Variables Reference
|
||||||
|
|
||||||
|
### Core Background Colors
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-background-color` | Primary background color | `#ffffff` | `#1f2020` |
|
||||||
|
| `--ov-surface-color` | Surface/card background | `#ffffff` | `#2d2d2d` |
|
||||||
|
| `--ov-surface-container-color` | Container surfaces | `#f8f9fa` | `#3a3a3a` |
|
||||||
|
| `--ov-surface-container-high-color` | Elevated surfaces | `#f0f0f0` | `#474747` |
|
||||||
|
|
||||||
|
### Action Colors
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-primary-action-color` | Primary buttons/actions | `#273235` | `#4a5a5d` |
|
||||||
|
| `--ov-primary-action-color-lighter` | Primary hover states | `#394649` | `#5a6a6d` |
|
||||||
|
| `--ov-secondary-action-color` | Secondary buttons | `#6c757d` | `#e1e1e1` |
|
||||||
|
| `--ov-accent-action-color` | Accent/highlight color | `#0089ab` | `#00b3d6` |
|
||||||
|
|
||||||
|
### State Colors
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-error-color` | Error states | `#dc3545` | `#ff6b6b` |
|
||||||
|
| `--ov-warn-color` | Warning states | `#ffc107` | `#ffd93d` |
|
||||||
|
| `--ov-success-color` | Success states | `#28a745` | `#69db7c` |
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-text-primary-color` | Primary text | `#212529` | `#ffffff` |
|
||||||
|
| `--ov-text-surface-color` | Text on surfaces | `#212529` | `#ffffff` |
|
||||||
|
| `--ov-text-secondary-color` | Secondary text | `#6c757d` | `#b3b3b3` |
|
||||||
|
| `--ov-text-disabled-color` | Disabled text | `#adb5bd` | `#666666` |
|
||||||
|
|
||||||
|
### Interactive States
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-hover-color` | Hover background | `#f8f9fa` | `#4a4a4a` |
|
||||||
|
| `--ov-active-color` | Active state | `rgba(66, 133, 244, 0.08)` | `rgba(66, 133, 244, 0.2)` |
|
||||||
|
| `--ov-focus-color` | Focus ring color | `#4285f4` | `#5294ff` |
|
||||||
|
| `--ov-disabled-background` | Disabled background | `#f8f9fa` | `#3a3a3a` |
|
||||||
|
| `--ov-disabled-border-color` | Disabled borders | `#dee2e6` | `#555555` |
|
||||||
|
|
||||||
|
### Input & Form Colors
|
||||||
|
|
||||||
|
| Variable | Description | Light Default | Dark Default |
|
||||||
|
|----------|-------------|---------------|--------------|
|
||||||
|
| `--ov-input-background` | Input backgrounds | `#ffffff` | `#3a3a3a` |
|
||||||
|
| `--ov-border-color` | Default borders | `#ced4da` | `#555555` |
|
||||||
|
| `--ov-border-focus-color` | Focused borders | `#4285f4` | `#5294ff` |
|
||||||
|
|
||||||
|
### Layout & Spacing
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `--ov-toolbar-buttons-radius` | Toolbar button radius | `50%` |
|
||||||
|
| `--ov-leave-button-radius` | Leave button radius | `10px` |
|
||||||
|
| `--ov-video-radius` | Video element radius | `5px` |
|
||||||
|
| `--ov-surface-radius` | Surface/card radius | `5px` |
|
||||||
|
| `--ov-input-radius` | Input field radius | `4px` |
|
||||||
|
|
||||||
|
### Special Colors
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `--ov-recording-color` | Recording indicator | `var(--ov-error-color)` |
|
||||||
|
| `--ov-broadcasting-color` | Broadcasting indicator | `#5903ca` |
|
||||||
|
| `--ov-selection-color` | Selection highlight | `#d4d6d7` |
|
||||||
|
| `--ov-selection-color-btn` | Button selection | `#afafaf` |
|
||||||
|
| `--ov-activity-status-color` | Activity status | `#afafaf` |
|
||||||
|
|
||||||
|
### Video/Media Specific
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `--ov-video-background` | Video element background | `#000000` |
|
||||||
|
| `--ov-audio-wave-color` | Audio wave visualization | `var(--ov-accent-action-color)` |
|
||||||
|
| `--ov-captions-height` | Captions panel height | `250px` |
|
||||||
|
|
||||||
|
### Shadow & Elevation
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `--ov-shadow-low` | Low elevation shadow | `0 2px 8px rgba(0, 0, 0, 0.1)` |
|
||||||
|
| `--ov-shadow-medium` | Medium elevation shadow | `0 4px 20px rgba(0, 0, 0, 0.1)` |
|
||||||
|
| `--ov-shadow-high` | High elevation shadow | `0 8px 32px rgba(0, 0, 0, 0.12)` |
|
||||||
|
| `--ov-border-shadow` | Border shadow | `1px 1px 5px 0px rgba(0, 0, 0, 0.2)` |
|
||||||
|
|
||||||
|
## Angular Material Integration
|
||||||
|
|
||||||
|
### Using SCSS Mixins
|
||||||
|
|
||||||
|
Import and use the provided SCSS mixins to integrate with Angular Material themes:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@use '@angular/material' as mat;
|
||||||
|
@use 'openvidu-components-angular/theme' as ovtheme;
|
||||||
|
|
||||||
|
// Define your Material theme
|
||||||
|
$my-theme: mat.define-theme();
|
||||||
|
|
||||||
|
// Apply the theme to OpenVidu components
|
||||||
|
@include ovtheme.apply-openvidu-theme($my-theme);
|
||||||
|
|
||||||
|
// Or apply responsive theme detection
|
||||||
|
@include ovtheme.openvidu-theme-responsive();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Integration
|
||||||
|
|
||||||
|
To manually integrate with Angular Material themes in your component:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { OpenViduThemeService } from 'openvidu-components-angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MaterialThemeIntegration {
|
||||||
|
constructor(private themeService: OpenViduThemeService) {}
|
||||||
|
|
||||||
|
applyMaterialTheme(materialTheme: any) {
|
||||||
|
// Extract colors from Material theme and apply to OpenVidu
|
||||||
|
this.themeService.updateThemeVariables({
|
||||||
|
'--ov-primary-action-color': materialTheme.primary,
|
||||||
|
'--ov-accent-action-color': materialTheme.accent,
|
||||||
|
'--ov-background-color': materialTheme.background,
|
||||||
|
'--ov-surface-color': materialTheme.surface
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Themes
|
||||||
|
|
||||||
|
### Creating a Custom Theme
|
||||||
|
|
||||||
|
Define a custom theme object:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { OpenViduThemeVariables } from 'openvidu-components-angular';
|
||||||
|
|
||||||
|
const myCustomTheme: OpenViduThemeVariables = {
|
||||||
|
'--ov-primary-action-color': '#ff5722',
|
||||||
|
'--ov-accent-action-color': '#4caf50',
|
||||||
|
'--ov-background-color': '#fafafa',
|
||||||
|
'--ov-surface-color': '#ffffff',
|
||||||
|
'--ov-text-primary-color': '#333333',
|
||||||
|
'--ov-text-secondary-color': '#666666',
|
||||||
|
// ... add more variables as needed
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply the custom theme
|
||||||
|
this.themeService.applyThemeConfiguration(myCustomTheme);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Brand-Specific Themes
|
||||||
|
|
||||||
|
Create brand-specific themes for multi-tenant applications:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brandThemes = {
|
||||||
|
'brand-a': {
|
||||||
|
'--ov-primary-action-color': '#ff5722',
|
||||||
|
'--ov-accent-action-color': '#4caf50'
|
||||||
|
},
|
||||||
|
'brand-b': {
|
||||||
|
'--ov-primary-action-color': '#2196f3',
|
||||||
|
'--ov-accent-action-color': '#ff9800'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply brand theme based on user/tenant
|
||||||
|
const userBrand = 'brand-a';
|
||||||
|
this.themeService.updateThemeVariables(brandThemes[userBrand]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## SCSS Mixins
|
||||||
|
|
||||||
|
### Available Mixins
|
||||||
|
|
||||||
|
#### `apply-openvidu-theme($theme)`
|
||||||
|
Applies an Angular Material theme to OpenVidu components.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@include ovtheme.apply-openvidu-theme($my-material-theme);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `apply-openvidu-dark-theme()`
|
||||||
|
Applies the predefined dark theme.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@include ovtheme.apply-openvidu-dark-theme();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `apply-openvidu-light-theme()`
|
||||||
|
Applies the predefined light theme.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@include ovtheme.apply-openvidu-light-theme();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `openvidu-theme-responsive()`
|
||||||
|
Sets up responsive theme detection based on system preferences.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@include ovtheme.openvidu-theme-responsive();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Complete Theme Integration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
OpenViduThemeService,
|
||||||
|
OpenViduThemeMode,
|
||||||
|
OPENVIDU_LIGHT_THEME,
|
||||||
|
OPENVIDU_DARK_THEME
|
||||||
|
} from 'openvidu-components-angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-theme-demo',
|
||||||
|
template: `
|
||||||
|
<div class="theme-controls">
|
||||||
|
<button (click)="setTheme('light')">Light</button>
|
||||||
|
<button (click)="setTheme('dark')">Dark</button>
|
||||||
|
<button (click)="setTheme('auto')">Auto</button>
|
||||||
|
<button (click)="toggleTheme()">Toggle</button>
|
||||||
|
<button (click)="applyCustomBrand()">Custom Brand</button>
|
||||||
|
</div>
|
||||||
|
<div class="current-theme">
|
||||||
|
Current theme: {{ currentTheme }}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
|
};
|
|
@ -19,7 +19,7 @@ import { CustomDevice } from '../../models/device.model';
|
||||||
})
|
})
|
||||||
export class StorageService implements OnDestroy {
|
export class StorageService implements OnDestroy {
|
||||||
public log: ILogger;
|
public log: ILogger;
|
||||||
protected readonly PREFIX_KEY = STORAGE_PREFIX;
|
readonly PREFIX_KEY = STORAGE_PREFIX;
|
||||||
private readonly tabId: string;
|
private readonly tabId: string;
|
||||||
private readonly TAB_CLEANUP_INTERVAL = 30000; // 30 seconds
|
private readonly TAB_CLEANUP_INTERVAL = 30000; // 30 seconds
|
||||||
private readonly TAB_TIMEOUT_THRESHOLD = 60000; // 60 seconds
|
private readonly TAB_TIMEOUT_THRESHOLD = 60000; // 60 seconds
|
||||||
|
|
|
@ -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>(OpenViduThemeMode.None);
|
||||||
|
private currentVariablesSubject = new BehaviorSubject<OpenViduThemeVariables>({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits the current theme mode
|
||||||
|
*/
|
||||||
|
public readonly currentTheme$: Observable<OpenViduThemeMode> = this.currentThemeSubject.asObservable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits the current theme variables
|
||||||
|
*/
|
||||||
|
public readonly currentVariables$: Observable<OpenViduThemeVariables> = 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ export * from './lib/models/toolbar.model';
|
||||||
export * from './lib/models/logger.model'
|
export * from './lib/models/logger.model'
|
||||||
export * from './lib/models/storage.model';
|
export * from './lib/models/storage.model';
|
||||||
export * from './lib/models/lang.model';
|
export * from './lib/models/lang.model';
|
||||||
|
export * from './lib/models/theme.model';
|
||||||
// Pipes
|
// Pipes
|
||||||
export * from './lib/pipes/participant.pipe';
|
export * from './lib/pipes/participant.pipe';
|
||||||
export * from './lib/pipes/recording.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/logger/logger.service';
|
||||||
export * from './lib/services/storage/storage.service';
|
export * from './lib/services/storage/storage.service';
|
||||||
export * from './lib/services/translate/translate.service';
|
export * from './lib/services/translate/translate.service';
|
||||||
|
export * from './lib/services/theme/theme.service';
|
||||||
//Modules
|
//Modules
|
||||||
export * from './lib/openvidu-components-angular.module';
|
export * from './lib/openvidu-components-angular.module';
|
||||||
export * from './lib/openvidu-components-angular-ui.module';
|
export * from './lib/openvidu-components-angular-ui.module';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@use '@angular/material' as mat;
|
@use '@angular/material' as mat;
|
||||||
|
@use '../projects/openvidu-components-angular/src/lib/config/theme' as ovtheme;
|
||||||
|
|
||||||
@include mat.elevation-classes();
|
@include mat.elevation-classes();
|
||||||
@include mat.app-background();
|
@include mat.app-background();
|
||||||
|
@ -15,8 +16,14 @@ html {
|
||||||
@include mat.all-component-colors($openvidu-theme);
|
@include mat.all-component-colors($openvidu-theme);
|
||||||
@include mat.all-component-typographies($openvidu-theme);
|
@include mat.all-component-typographies($openvidu-theme);
|
||||||
@include mat.all-component-densities($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,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -27,23 +34,77 @@ body {
|
||||||
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
|
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom openvidu-components styles
|
// Custom openvidu-components styles with Angular Material Theme support
|
||||||
:root {
|
:root {
|
||||||
--ov-background-color: #1f2020;
|
// === Core Background Colors ===
|
||||||
--ov-surface-color: #ffffff;
|
--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;
|
// === Action Colors (Primary, Secondary, Accent) ===
|
||||||
--ov-secondary-action-color: #f1f1f1;
|
--ov-primary-action-color: var(--mat-sys-primary, #273235);
|
||||||
--ov-accent-action-color: #0089ab;
|
--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;
|
// === State Colors ===
|
||||||
--ov-warn-color: #ffba53;
|
--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;
|
// === Text Colors ===
|
||||||
--ov-text-surface-color: #1d1d1d;
|
--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-toolbar-buttons-radius: 50%;
|
||||||
--ov-leave-button-radius: 10px;
|
--ov-leave-button-radius: 10px;
|
||||||
--ov-video-radius: 5px;
|
--ov-video-radius: 5px;
|
||||||
--ov-surface-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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue