diff --git a/package-lock.json b/package-lock.json
index 578a0f7a..4733353b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
"eslint-plugin-standard": "^5.0.0",
"jest": "^30.3.0",
"jest-environment-jsdom": "^30.3.0",
+ "jest-preset-angular": "^16.1.2",
"ng-packagr": "^21.2.2",
"pa11y-ci": "^4.0.1",
"prettier": "^3.4.2",
@@ -5346,9 +5347,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5365,9 +5363,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5384,9 +5379,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5403,9 +5395,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5422,9 +5411,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5441,9 +5427,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5460,9 +5443,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -5574,8 +5554,10 @@
"node_modules/@noble/hashes": {
"version": "2.0.1",
"integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
- "extraneous": true,
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"engines": {
"node": ">= 20.19.0"
},
@@ -6030,9 +6012,6 @@
"arm"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6053,9 +6032,6 @@
"arm"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6076,9 +6052,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6099,9 +6072,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6122,9 +6092,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6145,9 +6112,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6664,9 +6628,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6683,9 +6644,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6702,9 +6660,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6721,9 +6676,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6930,9 +6882,6 @@
"arm"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6946,9 +6895,6 @@
"arm"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6962,9 +6908,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6978,9 +6921,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -6994,9 +6934,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7010,9 +6947,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7026,9 +6960,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7042,9 +6973,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7058,9 +6986,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7074,9 +6999,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7090,9 +7012,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7106,9 +7025,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7122,9 +7038,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8188,9 +8101,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8204,9 +8114,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8220,9 +8127,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8236,9 +8140,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8252,9 +8153,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8268,9 +8166,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8284,9 +8179,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -8300,9 +8192,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
diff --git a/package.json b/package.json
index dfb0be32..11920e87 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
"eslint-plugin-standard": "^5.0.0",
"jest": "^30.3.0",
"jest-environment-jsdom": "^30.3.0",
+ "jest-preset-angular": "^16.1.2",
"ng-packagr": "^21.2.2",
"pa11y-ci": "^4.0.1",
"prettier": "^3.4.2",
diff --git a/projects/composition/src/app/api-data/cps-loader.json b/projects/composition/src/app/api-data/cps-loader.json
index 688310df..e6ddf4de 100644
--- a/projects/composition/src/app/api-data/cps-loader.json
+++ b/projects/composition/src/app/api-data/cps-loader.json
@@ -26,7 +26,7 @@
"optional": false,
"readonly": false,
"type": "string",
- "default": "depth",
+ "default": "var(--cps-text-primary)",
"description": "Color of the label."
},
{
diff --git a/projects/composition/src/app/api-data/cps-notification.json b/projects/composition/src/app/api-data/cps-notification.json
index 93baf712..52ffc175 100644
--- a/projects/composition/src/app/api-data/cps-notification.json
+++ b/projects/composition/src/app/api-data/cps-notification.json
@@ -127,7 +127,7 @@
"optional": true,
"readonly": false,
"type": "number",
- "description": "The duration (in milliseconds) that the notification will be displayed before automatically closing.\nValue 0 means that the notification is persistent and will not be automatically closed."
+ "description": "The duration (in milliseconds) that the notification will be displayed before automatically closing.\r\nValue 0 means that the notification is persistent and will not be automatically closed."
},
{
"name": "allowDuplicates",
diff --git a/projects/composition/src/app/api-data/cps-progress-linear.json b/projects/composition/src/app/api-data/cps-progress-linear.json
index 7ca8b869..0d2c554c 100644
--- a/projects/composition/src/app/api-data/cps-progress-linear.json
+++ b/projects/composition/src/app/api-data/cps-progress-linear.json
@@ -26,7 +26,7 @@
"optional": false,
"readonly": false,
"type": "string",
- "default": "calm",
+ "default": "var(--cps-accent-primary)",
"description": "Color of the progress bar."
},
{
diff --git a/projects/composition/src/app/api-data/cps-table.json b/projects/composition/src/app/api-data/cps-table.json
index 2f546faf..3b7375d8 100644
--- a/projects/composition/src/app/api-data/cps-table.json
+++ b/projects/composition/src/app/api-data/cps-table.json
@@ -107,7 +107,7 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "Determines whether the 'Remove' button should be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
+ "description": "Determines whether the 'Remove' button should be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
},
{
"name": "showRowEditButton",
@@ -115,14 +115,14 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "Determines whether the 'Edit' button should be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
+ "description": "Determines whether the 'Edit' button should be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
},
{
"name": "rowMenuItems",
"optional": true,
"readonly": false,
"type": "CpsMenuItem[]",
- "description": "Custom items to be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true."
+ "description": "Custom items to be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true."
},
{
"name": "reorderableRows",
@@ -178,7 +178,7 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.\nNote: This setting only takes effect if 'filterableByColumns' is true."
+ "description": "If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.\r\nNote: This setting only takes effect if 'filterableByColumns' is true."
},
{
"name": "sortMode",
@@ -570,7 +570,7 @@
"readonly": false,
"type": "boolean",
"default": "false",
- "description": "Determines whether columns are resizable.\nIn case of using a custom template for columns, it is also needed to add cpsTColResizable directive to th elements."
+ "description": "Determines whether columns are resizable.\r\nIn case of using a custom template for columns, it is also needed to add cpsTColResizable directive to th elements."
},
{
"name": "columnResizeMode",
@@ -578,7 +578,7 @@
"readonly": false,
"type": "\"expand\" | \"fit\"",
"default": "fit",
- "description": "Determines how the columns are resized. It can be 'fit' (total width of the table stays the same) or 'expand' (total width of the table changes when resizing columns).\nNote: This setting only takes effect if 'resizableColumns' is true."
+ "description": "Determines how the columns are resized. It can be 'fit' (total width of the table stays the same) or 'expand' (total width of the table changes when resizing columns).\r\nNote: This setting only takes effect if 'resizableColumns' is true."
}
]
},
diff --git a/projects/composition/src/app/api-data/cps-theme.json b/projects/composition/src/app/api-data/cps-theme.json
new file mode 100644
index 00000000..031d1f98
--- /dev/null
+++ b/projects/composition/src/app/api-data/cps-theme.json
@@ -0,0 +1,30 @@
+{
+ "components": {},
+ "name": "CpsThemeService",
+ "description": "CpsThemeService manages application theming including dark mode support.\r\n\r\nThis service provides:\r\n- Light and dark theme switching with smooth transitions\r\n- Automatic persistence of theme preference in localStorage\r\n- Reactive state management using Angular signals",
+ "types": {
+ "description": "Defines the custom types used by the module.",
+ "values": [
+ {
+ "name": "CpsTheme",
+ "value": "\"light\" | \"dark\"",
+ "description": "Available theme options"
+ },
+ {
+ "name": "CpsColorTheme",
+ "value": "\"neutral\" | \"calm\" | \"energy\" | \"passion\"",
+ "description": "Available color theme options"
+ },
+ {
+ "name": "CpsBaseTheme",
+ "value": "\"default\" | \"graphite\" | \"midnight\" | \"aubergine\"",
+ "description": "Available dark-mode base theme options"
+ },
+ {
+ "name": "CpsRadiusTheme",
+ "value": "\"none\" | \"compact\" | \"rounded\" | \"pill\"",
+ "description": "Available radius theme options"
+ }
+ ]
+ }
+}
diff --git a/projects/composition/src/app/api-data/cps-tree-table.json b/projects/composition/src/app/api-data/cps-tree-table.json
index 04fbe608..145823c9 100644
--- a/projects/composition/src/app/api-data/cps-tree-table.json
+++ b/projects/composition/src/app/api-data/cps-tree-table.json
@@ -123,7 +123,7 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "Determines whether the 'Remove' button should be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
+ "description": "Determines whether the 'Remove' button should be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
},
{
"name": "showRowEditButton",
@@ -131,14 +131,14 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "Determines whether the 'Edit' button should be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
+ "description": "Determines whether the 'Edit' button should be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true and 'rowMenuItems' is not set."
},
{
"name": "rowMenuItems",
"optional": true,
"readonly": false,
"type": "CpsMenuItem[]",
- "description": "Custom items to be displayed in the row menu.\nNote: This setting only takes effect if 'showRowMenu' is true."
+ "description": "Custom items to be displayed in the row menu.\r\nNote: This setting only takes effect if 'showRowMenu' is true."
},
{
"name": "loading",
@@ -530,7 +530,7 @@
"readonly": false,
"type": "boolean",
"default": "true",
- "description": "If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.\nNote: This setting only takes effect if 'filterableByColumns' is true."
+ "description": "If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.\r\nNote: This setting only takes effect if 'filterableByColumns' is true."
},
{
"name": "showExportBtn",
@@ -586,7 +586,7 @@
"readonly": false,
"type": "boolean",
"default": "false",
- "description": "Determines whether columns are resizable.\nIn case of using a custom template for columns, it is also needed to add cpsTTColResizable directive to th elements."
+ "description": "Determines whether columns are resizable.\r\nIn case of using a custom template for columns, it is also needed to add cpsTTColResizable directive to th elements."
},
{
"name": "columnResizeMode",
@@ -594,7 +594,7 @@
"readonly": false,
"type": "\"expand\" | \"fit\"",
"default": "fit",
- "description": "Determines how the columns are resized. It can be 'fit' (total width of the table stays the same) or 'expand' (total width of the table changes when resizing columns).\nNote: This setting only takes effect if 'resizableColumns' is true."
+ "description": "Determines how the columns are resized. It can be 'fit' (total width of the table stays the same) or 'expand' (total width of the table changes when resizing columns).\r\nNote: This setting only takes effect if 'resizableColumns' is true."
}
]
},
diff --git a/projects/composition/src/app/api-data/cron-validation.service.json b/projects/composition/src/app/api-data/cron-validation.service.json
index 28b4e9af..77d68f60 100644
--- a/projects/composition/src/app/api-data/cron-validation.service.json
+++ b/projects/composition/src/app/api-data/cron-validation.service.json
@@ -1,7 +1,7 @@
{
"components": {},
"name": "CronValidationService",
- "description": "Service for validating 6-field cron expressions with extended features.\n\nThis service handles cron validation logic for extended cron expression formats\nthat support additional features beyond standard Unix cron for more flexible\nscheduling capabilities.\n\nFormat: minutes hours day-of-month month day-of-week year\n\nKey Features:\n- Wildcards: asterisk (any value), question mark (any value for day fields)\n- Ranges: 1-5, MON-FRI, JAN-MAR\n- Steps: asterisk/15, 5/10, 1-5/2\n- Lists: 1,3,5, MON,WED,FRI\n- Special chars: L (last), W (weekday), hash (nth occurrence)",
+ "description": "Service for validating 6-field cron expressions with extended features.\r\n\r\nThis service handles cron validation logic for extended cron expression formats\r\nthat support additional features beyond standard Unix cron for more flexible\r\nscheduling capabilities.\r\n\r\nFormat: minutes hours day-of-month month day-of-week year\r\n\r\nKey Features:\r\n- Wildcards: asterisk (any value), question mark (any value for day fields)\r\n- Ranges: 1-5, MON-FRI, JAN-MAR\r\n- Steps: asterisk/15, 5/10, 1-5/2\r\n- Lists: 1,3,5, MON,WED,FRI\r\n- Special chars: L (last), W (weekday), hash (nth occurrence)",
"methods": {
"description": "Methods used in service.",
"values": [
diff --git a/projects/composition/src/app/api-data/types_map.json b/projects/composition/src/app/api-data/types_map.json
index f247bb05..3abaf6e8 100644
--- a/projects/composition/src/app/api-data/types_map.json
+++ b/projects/composition/src/app/api-data/types_map.json
@@ -32,5 +32,9 @@
"CpsTooltipOpenOn": "tooltip",
"CpsNotificationConfig": "notification",
"CpsNotificationAppearance": "notification",
- "CpsNotificationPosition": "notification"
+ "CpsNotificationPosition": "notification",
+ "CpsTheme": "theme",
+ "CpsColorTheme": "theme",
+ "CpsBaseTheme": "theme",
+ "CpsRadiusTheme": "theme"
}
\ No newline at end of file
diff --git a/projects/composition/src/app/app.component.html b/projects/composition/src/app/app.component.html
index 806ac9db..c85ffa7b 100644
--- a/projects/composition/src/app/app.component.html
+++ b/projects/composition/src/app/app.component.html
@@ -1,11 +1,12 @@
{{ componentTitle }}
diff --git a/projects/composition/src/app/app.component.scss b/projects/composition/src/app/app.component.scss
index c3da3b32..2ba8863e 100644
--- a/projects/composition/src/app/app.component.scss
+++ b/projects/composition/src/app/app.component.scss
@@ -2,19 +2,38 @@
.top-toolbar {
height: vars.$top-tbar-height;
- background-color: white;
+ background-color: var(--cps-surface-body);
display: flex;
- padding-left: 20px;
+ padding: 0 14px;
align-items: center;
- border-bottom: 1px solid lightgrey;
+ border-bottom: 1px solid var(--cps-border-color);
+ gap: 8px;
img {
width: 42px;
height: 42px;
}
+
span {
- margin-left: 16px;
- font-size: 18px;
- color: vars.$color-calm;
+ font-size: 13px;
+ color: var(--cps-text-secondary);
+ line-height: 1;
+
+ &:first-of-type {
+ flex: 1;
+ color: var(--cps-text-primary);
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ }
+
+ b {
+ color: var(--cps-accent-primary);
+ font-weight: 700;
+ font-size: 15px;
+ }
+ }
+
+ app-theme-toggle {
+ margin-left: auto;
}
}
.composition-container {
@@ -29,11 +48,12 @@
background-color: vars.$composition-background;
.composition-body-toolbar {
height: vars.$inner-tbar-height;
- background: vars.$color-calm;
+ background: var(--cps-surface-elevated);
+ border-bottom: 1px solid var(--cps-border-color);
display: flex;
align-items: center;
justify-content: space-between;
- color: white;
+ color: var(--cps-text-primary);
font-size: 20px;
.composition-body-toolbar-title {
margin: 0 auto;
diff --git a/projects/composition/src/app/app.component.spec.ts b/projects/composition/src/app/app.component.spec.ts
index 06e2ae99..198b2c59 100644
--- a/projects/composition/src/app/app.component.spec.ts
+++ b/projects/composition/src/app/app.component.spec.ts
@@ -1,8 +1,9 @@
import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
import { CpsIconComponent } from 'cps-ui-kit';
+import { AppComponent } from './app.component';
import { NavigationSidebarComponent } from './components/navigation-sidebar/navigation-sidebar.component';
+import { ThemeToggleComponent } from './components/theme-toggle/theme-toggle.component';
jest.mock('projects/cps-ui-kit/package.json', () => ({ version: '1.0.0' }), {
virtual: true
@@ -12,7 +13,12 @@ describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
- imports: [NavigationSidebarComponent, CpsIconComponent, RouterOutlet],
+ imports: [
+ NavigationSidebarComponent,
+ CpsIconComponent,
+ ThemeToggleComponent,
+ RouterOutlet
+ ],
providers: [{ provide: ActivatedRoute, useValue: {} }]
}).compileComponents();
});
diff --git a/projects/composition/src/app/app.module.ts b/projects/composition/src/app/app.module.ts
index 2821ea7b..b96364c7 100644
--- a/projects/composition/src/app/app.module.ts
+++ b/projects/composition/src/app/app.module.ts
@@ -4,12 +4,13 @@ import {
// provideClientHydration
} from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { TitleStrategy } from '@angular/router';
+import { CpsIconComponent } from 'cps-ui-kit';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
-import { NavigationSidebarComponent } from './components/navigation-sidebar/navigation-sidebar.component';
-import { TitleStrategy } from '@angular/router';
import { AppPrefixTitleStrategy } from './app.prefix-title-strategy';
-import { CpsIconComponent } from 'cps-ui-kit';
+import { NavigationSidebarComponent } from './components/navigation-sidebar/navigation-sidebar.component';
+import { ThemeToggleComponent } from './components/theme-toggle/theme-toggle.component';
@NgModule({
declarations: [AppComponent],
@@ -18,7 +19,8 @@ import { CpsIconComponent } from 'cps-ui-kit';
BrowserAnimationsModule,
AppRoutingModule,
NavigationSidebarComponent,
- CpsIconComponent
+ CpsIconComponent,
+ ThemeToggleComponent
],
providers: [
{ provide: TitleStrategy, useClass: AppPrefixTitleStrategy }
diff --git a/projects/composition/src/app/components/component-docs-viewer/component-docs-viewer.component.html b/projects/composition/src/app/components/component-docs-viewer/component-docs-viewer.component.html
index 826fe31a..9cee4d60 100644
--- a/projects/composition/src/app/components/component-docs-viewer/component-docs-viewer.component.html
+++ b/projects/composition/src/app/components/component-docs-viewer/component-docs-viewer.component.html
@@ -1,5 +1,5 @@
diff --git a/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.scss b/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.scss
index 712d5b3e..403d634f 100644
--- a/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.scss
+++ b/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.scss
@@ -1,15 +1,15 @@
@use '../../../variables.scss' as vars;
:host {
- $item-hover-background: var(--cps-color-highlight-hover);
- $item-active-background: var(--cps-color-highlight-active);
- $item-border-color: var(--cps-color-line-light);
+ $item-hover-background: var(--cps-highlight-hover);
+ $item-active-background: var(--cps-highlight-active);
+ $item-border-color: var(--cps-color-line);
border-right: 1px solid $item-border-color;
- background-color: var(--cps-color-bg-light);
+ background-color: var(--cps-surface-body);
.sidebar {
transition: width 0.2s;
- background-color: white;
+ background-color: var(--cps-surface-body);
width: vars.$sidebar-width;
height: calc(100vh - vars.$top-tbar-height - 20px);
overflow: auto;
@@ -17,7 +17,7 @@
&-title {
margin: 0;
padding: 12px 0 12px 12px;
- color: vars.$color-text;
+ color: var(--cps-text-primary);
}
&-item {
border-bottom: 1px solid $item-border-color;
@@ -26,7 +26,7 @@
align-items: center;
padding-left: 30px;
text-decoration: none;
- color: vars.$color-text;
+ color: var(--cps-text-primary);
&:hover {
background: $item-hover-background;
}
@@ -35,7 +35,7 @@
}
}
._active {
- color: white;
+ color: var(--cps-text-on-accent);
font-weight: bold;
background: vars.$color-calm;
}
diff --git a/projects/composition/src/app/components/theme-toggle/theme-toggle.component.html b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.html
new file mode 100644
index 00000000..e0b483d6
--- /dev/null
+++ b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.html
@@ -0,0 +1,134 @@
+
+ @if (showCustomize) {
+
+ }
+ @if (menuOpen) {
+
+
+ }
+
+
diff --git a/projects/composition/src/app/components/theme-toggle/theme-toggle.component.scss b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.scss
new file mode 100644
index 00000000..ba0f26dc
--- /dev/null
+++ b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.scss
@@ -0,0 +1,187 @@
+.theme-controls {
+ --appearance-radius-sm: 8px;
+ --appearance-radius-md: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ position: relative;
+}
+.theme-toggle-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 32px;
+ padding: 0 11px;
+ background: var(--cps-surface-elevated);
+ border: 1px solid var(--cps-border-color);
+ border-radius: var(--appearance-radius-sm);
+ color: var(--cps-text-primary);
+ cursor: pointer;
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 12px;
+ font-weight: 600;
+ transition: all 0.2s;
+ &:hover {
+ background: var(--cps-highlight-hover);
+ border-color: var(--cps-border-focus);
+ }
+ &:active {
+ background: var(--cps-highlight-active);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--cps-ring-color);
+ outline-offset: 2px;
+ }
+}
+.theme-toggle-caret {
+ color: var(--cps-text-muted);
+ font-size: 11px;
+ line-height: 1;
+}
+.menu-backdrop {
+ position: fixed;
+ inset: 0;
+ margin: 0;
+ padding: 0;
+ background: var(--cps-surface-overlay);
+ opacity: 0.22;
+ border: 0;
+ border-radius: 0;
+ appearance: none;
+ -webkit-appearance: none;
+ z-index: 10;
+}
+.theme-menu {
+ position: absolute;
+ top: calc(100% + 10px);
+ right: 0;
+ width: min(320px, calc(100vw - 16px));
+ max-height: min(70vh, 520px);
+ overflow: auto;
+ background: var(--cps-surface-control);
+ border: 1px solid var(--cps-border-color);
+ border-radius: var(--appearance-radius-md);
+ box-shadow: var(--cps-shadow-md);
+ z-index: 20;
+ padding: 6px;
+ transform-origin: top right;
+ animation: menu-enter var(--cps-motion-fast) var(--cps-motion-easing);
+}
+@keyframes menu-enter {
+ from {
+ opacity: 0;
+ transform: translateY(-4px) scale(0.985);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ .theme-menu {
+ animation: none;
+ }
+}
+.theme-menu-header {
+ padding: 10px 10px 9px;
+ border-bottom: 1px solid var(--cps-border-color);
+}
+.theme-menu-title {
+ color: var(--cps-text-primary);
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 13px;
+ font-weight: 700;
+ line-height: 1.2;
+}
+.theme-menu-subtitle {
+ margin-top: 3px;
+ color: var(--cps-text-muted);
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 11px;
+ line-height: 1.35;
+}
+.theme-section {
+ padding: 10px;
+}
+.theme-section + .theme-section {
+ border-top: 1px solid var(--cps-border-color);
+}
+.theme-section-title {
+ margin: 0 0 6px;
+ color: var(--cps-text-muted);
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+}
+.theme-section-hint {
+ margin: -1px 0 8px;
+ color: var(--cps-text-muted);
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 11px;
+ line-height: 1.35;
+}
+.theme-options {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+.theme-option {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 8px;
+ min-height: 32px;
+ padding: 7px 8px;
+ border: 1px solid transparent;
+ border-radius: var(--appearance-radius-sm);
+ background: transparent;
+ color: var(--cps-text-primary);
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 12px;
+ line-height: 1;
+ text-align: left;
+ cursor: pointer;
+ &:hover {
+ background: var(--cps-highlight-hover);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--cps-ring-color);
+ outline-offset: 2px;
+ }
+}
+.theme-option.selected {
+ background: var(--cps-highlight-active);
+ border-color: var(--cps-border-focus);
+}
+.theme-option.selected::after {
+ content: '✓';
+ margin-left: auto;
+ color: var(--cps-accent-primary);
+ font-weight: 700;
+ font-size: 12px;
+}
+.option-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 9999px;
+ background: transparent;
+ border: 1px solid var(--cps-border-color);
+ flex: 0 0 auto;
+}
+.theme-section-theme .theme-option:nth-child(1) .option-dot {
+ background: var(--cps-text-muted);
+}
+.theme-section-theme .theme-option:nth-child(2) .option-dot {
+ background: var(--cps-color-calm);
+}
+.theme-section-theme .theme-option:nth-child(3) .option-dot {
+ background: var(--cps-color-energy);
+}
+.theme-section-theme .theme-option:nth-child(4) .option-dot {
+ background: var(--cps-color-passion);
+}
+.theme-option.selected .option-dot {
+ border-color: transparent;
+}
diff --git a/projects/composition/src/app/components/theme-toggle/theme-toggle.component.ts b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.ts
new file mode 100644
index 00000000..492c5130
--- /dev/null
+++ b/projects/composition/src/app/components/theme-toggle/theme-toggle.component.ts
@@ -0,0 +1,61 @@
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import {
+ CpsBaseTheme,
+ CpsColorTheme,
+ CpsIconComponent,
+ CpsRadiusTheme,
+ CpsThemeService
+} from 'cps-ui-kit';
+
+@Component({
+ selector: 'app-theme-toggle',
+ imports: [CpsIconComponent],
+ templateUrl: './theme-toggle.component.html',
+ styleUrl: './theme-toggle.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ '(document:keydown.escape)': 'onEscapeKey()'
+ }
+})
+export class ThemeToggleComponent {
+ private themeService = inject(CpsThemeService);
+
+ showCustomize =
+ new URLSearchParams(window.location.search).get('experimental') === 'true';
+
+ isDark = this.themeService.isDark;
+ colorTheme = this.themeService.colorTheme;
+ radiusTheme = this.themeService.radiusTheme;
+ baseTheme = this.themeService.baseTheme;
+ menuOpen = false;
+
+ toggleTheme(): void {
+ this.themeService.toggleTheme();
+ }
+
+ toggleMenu(): void {
+ this.menuOpen = !this.menuOpen;
+ }
+
+ closeMenu(): void {
+ this.menuOpen = false;
+ }
+
+ onEscapeKey(): void {
+ if (this.menuOpen) {
+ this.closeMenu();
+ }
+ }
+
+ setColorTheme(value: CpsColorTheme): void {
+ this.themeService.setColorTheme(value);
+ }
+
+ setRadiusTheme(value: CpsRadiusTheme): void {
+ this.themeService.setRadiusTheme(value);
+ }
+
+ setBaseTheme(value: CpsBaseTheme): void {
+ this.themeService.setBaseTheme(value);
+ }
+}
diff --git a/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.html b/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.html
index ab3bde80..85fa867e 100644
--- a/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.html
+++ b/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.html
@@ -12,7 +12,10 @@
@for (name of filteredIconsList; track name) {
-
+
{{ name }}
}
diff --git a/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.scss b/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.scss
index 4645cb80..6cbd8dca 100644
--- a/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.scss
+++ b/projects/composition/src/app/pages/icons-page/icons-page/icons-page.component.scss
@@ -17,6 +17,6 @@
cursor: pointer;
span {
margin-left: 16px;
- color: vars.$color-calm;
+ color: var(--cps-text-primary);
}
}
diff --git a/projects/composition/src/styles.scss b/projects/composition/src/styles.scss
index 210f2849..97428f8e 100644
--- a/projects/composition/src/styles.scss
+++ b/projects/composition/src/styles.scss
@@ -41,16 +41,20 @@ body {
font-weight: bold;
text-align: left;
padding: 0.75rem 1rem;
- border-bottom: 1px solid var(--cps-color-line-light);
+ background-color: var(--cps-surface-elevated);
+ border-bottom: 1px solid var(--cps-color-line);
}
tr {
transition: background-color 0.5s ease-in;
+ &:hover {
+ background-color: var(--cps-highlight-hover);
+ }
}
td {
padding: 0.75rem 1rem;
- border-bottom: 1px solid var(--cps-color-line-light);
+ border-bottom: 1px solid var(--cps-color-line);
white-space: pre-line;
span {
@@ -61,18 +65,18 @@ body {
&.highlighted-bg {
span {
- color: var(--cps-color-depth-darken4);
- background-color: var(--cps-color-human-lighten5);
+ color: var(--cps-text-on-accent);
+ background-color: var(--cps-accent-primary);
border-radius: 6px;
padding: 0.2rem 0.5rem;
}
}
&.highlighted-text {
- color: var(--cps-color-calm);
+ color: var(--cps-text-primary);
a {
- color: var(--cps-color-calm);
+ color: var(--cps-text-primary);
&:hover {
text-decoration: none;
@@ -92,8 +96,8 @@ body {
Consolas,
Liberation Mono,
monospace;
- background-color: var(--cps-color-bg-mid);
- border: 1px solid var(--cps-color-line-light);
+ background-color: var(--cps-surface-elevated);
+ border: 1px solid var(--cps-color-line);
}
}
}
diff --git a/projects/composition/src/variables.scss b/projects/composition/src/variables.scss
index ca663339..f26fb833 100644
--- a/projects/composition/src/variables.scss
+++ b/projects/composition/src/variables.scss
@@ -1,7 +1,7 @@
$sidebar-width: 300px;
-$composition-background: var(--cps-color-bg-light);
+$composition-background: var(--cps-background-color);
$top-tbar-height: 64px;
$inner-tbar-height: 45px;
-$color-calm: var(--cps-color-calm);
-$color-text: var(--cps-color-text-dark);
+$color-calm: var(--cps-accent-primary);
+$color-text: var(--cps-text-primary);
diff --git a/projects/cps-ui-kit/assets/icons.svg b/projects/cps-ui-kit/assets/icons.svg
index 7ac1852f..df8e0425 100644
--- a/projects/cps-ui-kit/assets/icons.svg
+++ b/projects/cps-ui-kit/assets/icons.svg
@@ -513,4 +513,11 @@
+
+
+
+
+
+
+
diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html
index 1ca9034e..d10d5411 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html
+++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html
@@ -1,7 +1,7 @@
@@ -129,6 +129,7 @@
#autocompleteInput
class="cps-autocomplete-box-input"
spellcheck="false"
+ [attr.aria-label]="label || placeholder || 'Autocomplete input'"
[placeholder]="
(!multiple && isEmptyValue()) || (value?.length < 1 && multiple)
? placeholder
@@ -283,6 +284,7 @@
spellcheck="false"
[class]="inputClass"
[style]="inputStyle"
+ [attr.aria-label]="label || placeholder || 'Autocomplete input'"
[placeholder]="
(!multiple && isEmptyValue()) || (value?.length < 1 && multiple)
? placeholder
diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.scss b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.scss
index 1199eea0..ce8172b7 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.scss
+++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.scss
@@ -1,21 +1,21 @@
-$color-calm: var(--cps-color-calm);
-$color-error: var(--cps-color-error);
-$error-background: #fef3f2;
-$autocomplete-placeholder-color: var(--cps-color-text-lightest);
-$autocomplete-label-color: var(--cps-color-text-dark);
-$autocomplete-label-disabled-color: var(--cps-color-text-mild);
-$autocomplete-items-disabled-color: var(--cps-color-text-light);
-$autocomplete-hint-color: var(--cps-color-text-mild);
-$option-hover-background: var(--cps-color-highlight-hover);
-$selected-option-background: var(--cps-color-highlight-selected);
-$option-highlight-background: var(--cps-color-highlight-active);
-$option-highlight-selected-background: var(--cps-color-highlight-selected-dark);
-$autocomplete-option-info-color: var(--cps-color-text-light);
-$autocomplete-option-value-color: var(--cps-color-text-dark);
-$autocomplete-about-remove-color: var(--cps-color-text-light);
-$autocomplete-about-remove-background: var(--cps-color-bg-mid);
-$autocomplete-prefix-icon-color: var(--cps-color-text-dark);
-$autocomplete-border-color: var(--cps-color-line-light);
+$color-calm: var(--cps-accent-primary);
+$color-error: var(--cps-state-error);
+$error-background: var(--cps-error-background);
+$autocomplete-placeholder-color: var(--cps-input-placeholder);
+$autocomplete-label-color: var(--cps-text-primary);
+$autocomplete-label-disabled-color: var(--cps-text-secondary);
+$autocomplete-items-disabled-color: var(--cps-text-disabled);
+$autocomplete-hint-color: var(--cps-text-muted);
+$option-hover-background: var(--cps-highlight-hover);
+$selected-option-background: var(--cps-highlight-selected);
+$option-highlight-background: var(--cps-highlight-active);
+$option-highlight-selected-background: var(--cps-highlight-selected);
+$autocomplete-option-info-color: var(--cps-text-secondary);
+$autocomplete-option-value-color: var(--cps-text-primary);
+$autocomplete-about-remove-color: var(--cps-text-muted);
+$autocomplete-about-remove-background: var(--cps-surface-muted);
+$autocomplete-prefix-icon-color: var(--cps-text-secondary);
+$autocomplete-border-color: var(--cps-border-color);
$hover-transition-duration: 0.2s;
@@ -26,7 +26,7 @@ $hover-transition-duration: 0.2s;
position: relative;
width: 100%;
outline: none;
- font-family: 'Source Sans Pro', sans-serif;
+ font-family: inherit;
font-weight: normal;
display: grid;
@@ -40,7 +40,7 @@ $hover-transition-duration: 0.2s;
&.focused {
.cps-autocomplete-box {
- background: white !important;
+ background: var(--cps-input-background) !important;
}
}
@@ -62,6 +62,7 @@ $hover-transition-duration: 0.2s;
&.active {
.cps-autocomplete-box {
border: 1px solid $color-calm;
+ box-shadow: 0 0 0 3px var(--cps-highlight-selected);
.cps-autocomplete-box-area {
.prefix-icon {
color: $color-calm;
@@ -79,6 +80,7 @@ $hover-transition-duration: 0.2s;
display: inline-flex;
margin-bottom: 0.2rem;
color: $autocomplete-label-color;
+ background-color: var(--cps-surface-body);
font-size: 0.875rem;
font-weight: 600;
.cps-autocomplete-label-info-circle {
@@ -106,13 +108,16 @@ $hover-transition-duration: 0.2s;
min-height: 38px;
width: 100%;
cursor: text;
- background: white;
+ background: var(--cps-input-background);
font-size: 1rem;
outline: none;
padding: 0 12px 0 12px;
- border-radius: 4px;
+ border-radius: var(--cps-border-radius-medium);
border: 1px solid $autocomplete-border-color;
- transition-duration: $hover-transition-duration;
+ transition:
+ border-color $hover-transition-duration var(--cps-motion-easing),
+ box-shadow $hover-transition-duration var(--cps-motion-easing),
+ background-color $hover-transition-duration var(--cps-motion-easing);
&-area {
display: flex;
@@ -135,7 +140,7 @@ $hover-transition-duration: 0.2s;
color: $autocomplete-option-value-color;
border-style: none;
outline: none;
- font-family: 'Source Sans Pro', sans-serif;
+ font-family: inherit;
&::placeholder {
color: $autocomplete-placeholder-color;
font-style: italic;
@@ -206,7 +211,7 @@ $hover-transition-duration: 0.2s;
}
&:hover {
- border: 1px solid $color-calm;
+ border: 1px solid var(--cps-border-strong);
.cps-autocomplete-box-area {
.prefix-icon {
color: $color-calm;
@@ -248,6 +253,8 @@ $hover-transition-duration: 0.2s;
.cps-autocomplete-hint {
color: $autocomplete-hint-color;
+ background-color: var(--cps-surface-body);
+ display: inline-block;
font-size: 0.75rem;
min-height: 1.125rem;
line-height: 1.125rem;
@@ -267,7 +274,7 @@ $hover-transition-duration: 0.2s;
&.disabled {
pointer-events: none;
.cps-autocomplete-box {
- background: #f7f7f7;
+ background: var(--cps-background-disabled);
&-items {
color: $autocomplete-items-disabled-color;
.text-group,
@@ -297,8 +304,12 @@ $hover-transition-duration: 0.2s;
}
.cps-autocomplete-options {
- font-family: 'Source Sans Pro', sans-serif;
- background: white;
+ font-family: inherit;
+ background: var(--cps-popover-background);
+ color: var(--cps-popover-foreground);
+ border: 1px solid var(--cps-border-color);
+ border-radius: var(--cps-border-radius-medium);
+ box-shadow: var(--cps-shadow-md);
overflow-x: hidden;
max-height: 242px;
overflow-y: auto;
@@ -390,7 +401,7 @@ $hover-transition-duration: 0.2s;
}
.select-all-option {
- border-bottom: 1px solid lightgrey;
+ border-bottom: 1px solid var(--cps-border-color);
font-weight: 600;
}
diff --git a/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.html b/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.html
index 70e42d96..1fdaf5e5 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.html
+++ b/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.html
@@ -11,7 +11,7 @@
class="cps-chip-close-icon"
icon="close-x"
size="xsmall"
- color="text-darkest"
+ [color]="'var(--cps-text-primary)'"
(click)="onCloseClick($event)">
}
diff --git a/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.scss b/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.scss
index 7f3e2bc9..2c91bc6a 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.scss
+++ b/projects/cps-ui-kit/src/lib/components/cps-chip/cps-chip.component.scss
@@ -7,8 +7,9 @@
.cps-chip {
align-items: center;
display: inline-flex;
- background-color: var(--cps-color-bg-dark);
- border-radius: 14px;
+ background-color: var(--cps-surface-elevated);
+ border: 1px solid var(--cps-border-color);
+ border-radius: var(--cps-radius-full);
line-height: 16px;
padding: 4px 12px;
cursor: default;
@@ -17,28 +18,28 @@
cursor: pointer;
&:hover {
::ng-deep .cps-icon {
- color: var(--cps-color-calm) !important;
+ color: var(--cps-accent-primary) !important;
}
}
}
&-label {
font-size: 14px;
- color: var(--cps-color-text-darkest);
- font-family: 'Source Sans Pro', sans-serif;
+ color: var(--cps-text-primary);
+ font-family: inherit;
font-style: normal;
font-weight: 400;
}
&.cps-chip-disabled {
pointer-events: none;
- background-color: var(--cps-color-bg-mid);
+ background-color: var(--cps-background-disabled);
.cps-chip-label {
- color: var(--cps-color-text-light);
+ color: var(--cps-text-muted);
}
.cps-chip-icon,
.cps-chip-close-icon {
::ng-deep .cps-icon {
- color: var(--cps-color-text-light) !important;
+ color: var(--cps-text-muted) !important;
}
}
}
diff --git a/projects/cps-ui-kit/src/lib/components/cps-icon/cps-icon.component.ts b/projects/cps-ui-kit/src/lib/components/cps-icon/cps-icon.component.ts
index 37308567..af01f768 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-icon/cps-icon.component.ts
+++ b/projects/cps-ui-kit/src/lib/components/cps-icon/cps-icon.component.ts
@@ -7,8 +7,8 @@ import {
Input,
OnChanges
} from '@angular/core';
-import { convertSize } from '../../utils/internal/size-utils';
import { getCSSColor } from '../../utils/colors-utils';
+import { convertSize } from '../../utils/internal/size-utils';
/**
* Injection token that is used to provide the path to the icons.
@@ -81,6 +81,7 @@ export const iconNames = [
'issues',
'jpeg',
'json',
+ 'kafka',
'kris',
'last-seen-product',
'left',
@@ -94,6 +95,7 @@ export const iconNames = [
'menu-shrink',
'minimize',
'minus',
+ 'moon',
'move-grabber',
'open',
'ownership',
@@ -120,6 +122,7 @@ export const iconNames = [
'stepper-completed',
'success',
'suggestion',
+ 'sun',
'survivorship',
'table-row-error',
'table-row-success',
diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html
index c078ce2a..492aeb2d 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html
+++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html
@@ -2,7 +2,8 @@
@if (label) {
+ [class.cps-input-label-disabled]="disabled && !readonly"
+ [class.cps-input-label-error]="error">
@if (infoTooltip) {
+ [class.password]="type === 'password'"
+ [class.cps-input-wrap-error]="error"
+ [class.clearable]="clearable"
+ [class.persistent-clear]="persistentClear"
+ [class.borderless]="appearance === 'borderless'"
+ [class.underlined]="appearance === 'underlined'">
@if (!valueToDisplay) {
+ [class.password-show-btn-active]="currentType === 'text'">
diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss
index 75c985f1..4d4f0a94 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss
+++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss
@@ -1,17 +1,17 @@
-$color-calm: var(--cps-color-calm);
-$input-hint-color: var(--cps-color-text-mild);
-$input-label-disabled-color: var(--cps-color-text-mild);
-$input-pass-show-btn-color: var(--cps-color-text-mild);
-$input-placeholder-color: var(--cps-color-text-lightest);
-$input-border-color: var(--cps-color-line-light);
-$input-label-color: var(--cps-color-text-dark);
-$input-text-color: var(--cps-color-text-dark);
-$input-text-disabled-color: var(--cps-color-text-light);
-$input-prefix-text-color: var(--cps-color-text-mild);
-$input-prefix-icon-color: var(--cps-color-text-dark);
-
-$color-error: var(--cps-color-error);
-$error-background: #fef3f2;
+$color-calm: var(--cps-accent-primary);
+$input-hint-color: var(--cps-text-muted);
+$input-label-disabled-color: var(--cps-text-muted);
+$input-pass-show-btn-color: var(--cps-text-muted);
+$input-placeholder-color: var(--cps-input-placeholder);
+$input-border-color: var(--cps-border-color);
+$input-label-color: var(--cps-text-primary); // --cps-color-text-dark
+$input-text-color: var(--cps-text-primary); // --cps-color-text-dark
+$input-text-disabled-color: var(--cps-text-disabled);
+$input-prefix-text-color: var(--cps-text-muted);
+$input-prefix-icon-color: var(--cps-text-primary); // --cps-color-text-dark
+
+$color-error: var(--cps-state-error);
+$error-background: var(--cps-error-background);
$hover-transition-duration: 0.2s;
@@ -22,14 +22,14 @@ $hover-transition-duration: 0.2s;
gap: 0.2rem !important;
display: flex !important;
flex-direction: column !important;
- font-family: 'Source Sans Pro', sans-serif;
+ font-family: inherit;
.cps-input-wrap {
position: relative;
overflow: hidden;
&:hover {
input:enabled:not(:read-only) {
- border: 1px solid $color-calm;
+ border-color: var(--cps-border-strong);
}
}
&-error {
@@ -46,39 +46,37 @@ $hover-transition-duration: 0.2s;
input {
min-height: 38px;
- font-family: 'Source Sans Pro', sans-serif;
+ font-family: inherit;
font-size: 1rem;
color: $input-text-color;
- background: #ffffff;
+ background: var(--cps-input-background);
padding: 0.375rem 0.75rem;
line-height: 1.5;
border: 1px solid $input-border-color;
transition-duration: $hover-transition-duration;
appearance: none;
- border-radius: 4px;
+ border-radius: var(--cps-border-radius-medium);
width: 100%;
&:focus {
outline: 0;
}
&:focus:not(:read-only) {
- border: 1px solid $color-calm;
+ border-color: $color-calm;
+ box-shadow: 0 0 0 3px var(--cps-highlight-selected);
}
&:read-only {
cursor: default;
}
&:disabled {
- opacity: 1;
+ opacity: 0.7;
}
&:disabled:not([readonly]) {
- color: $input-text-disabled-color;
- background-color: #f7f7f7;
+ color: $input-text-disabled-color !important;
+ -webkit-text-fill-color: $input-text-disabled-color !important;
+ background-color: var(--cps-background-disabled);
pointer-events: none;
}
-
- &[type='password'] {
- font-family: Verdana;
- }
}
input:focus:not(:read-only) + .cps-input-prefix > .cps-input-prefix-icon,
@@ -116,7 +114,7 @@ $hover-transition-duration: 0.2s;
.clear-btn {
display: flex;
cursor: pointer;
- color: $color-calm;
+ color: var(--cps-state-error);
cps-icon {
opacity: 0;
transition-duration: $hover-transition-duration;
@@ -188,6 +186,26 @@ $hover-transition-duration: 0.2s;
&.underlined {
input {
border-bottom: 1px solid $input-border-color !important;
+ background: transparent;
+ box-shadow: none !important;
+ }
+ }
+
+ &.borderless {
+ input {
+ background: var(--cps-surface-muted);
+ box-shadow: none !important;
+ }
+
+ &:hover {
+ input:enabled:not(:read-only) {
+ background: var(--cps-highlight-hover);
+ }
+ }
+
+ input:focus:not(:read-only) {
+ background: var(--cps-highlight-selected);
+ border-color: transparent !important;
}
}
}
@@ -213,6 +231,7 @@ $hover-transition-duration: 0.2s;
.cps-input-hint {
color: $input-hint-color;
+ font-family: inherit;
font-size: 0.75rem;
min-height: 1.125rem;
line-height: 1.125rem;
@@ -220,6 +239,7 @@ $hover-transition-duration: 0.2s;
}
.cps-input-error {
color: $color-error;
+ font-family: inherit;
font-weight: bold;
font-size: 0.75rem;
min-height: 1.125rem;
@@ -240,9 +260,13 @@ $hover-transition-duration: 0.2s;
&-disabled {
color: $input-label-disabled-color;
}
+
+ &-error {
+ color: $color-error;
+ }
}
::placeholder {
- font-family: 'Source Sans Pro', sans-serif;
+ font-family: inherit;
color: $input-placeholder-color;
font-style: italic;
opacity: 1; /* Firefox */
diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts
index 5995a28f..237d7f44 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts
+++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts
@@ -14,16 +14,16 @@ import {
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
+import { Subscription } from 'rxjs';
+import { CpsTooltipPosition } from '../../directives/cps-tooltip/cps-tooltip.directive';
+import { convertSize } from '../../utils/internal/size-utils';
import {
CpsIconComponent,
IconType,
iconSizeType
} from '../cps-icon/cps-icon.component';
-import { Subscription } from 'rxjs';
-import { convertSize } from '../../utils/internal/size-utils';
-import { CpsProgressLinearComponent } from '../cps-progress-linear/cps-progress-linear.component';
import { CpsInfoCircleComponent } from '../cps-info-circle/cps-info-circle.component';
-import { CpsTooltipPosition } from '../../directives/cps-tooltip/cps-tooltip.directive';
+import { CpsProgressLinearComponent } from '../cps-progress-linear/cps-progress-linear.component';
/**
* CpsInputAppearanceType is used to define the border of the input field.
diff --git a/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.scss b/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.scss
index 98d14147..5905270b 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.scss
+++ b/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.scss
@@ -1,5 +1,5 @@
-$color-outer: var(--cps-color-calm);
-$color-middle: var(--cps-color-warmth);
+$color-outer: var(--cps-accent-primary);
+$color-middle: var(--cps-accent-secondary);
$color-inner: var(--cps-color-energy);
:host {
diff --git a/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.ts b/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.ts
index 6a37f298..87128d90 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.ts
+++ b/projects/cps-ui-kit/src/lib/components/cps-loader/cps-loader.component.ts
@@ -29,7 +29,7 @@ export class CpsLoaderComponent implements OnInit {
* Color of the label.
* @group Props
*/
- @Input() labelColor = 'depth';
+ @Input() labelColor = 'var(--cps-text-primary)';
/**
* Determines whether to show 'Loading...' label.
@@ -37,7 +37,7 @@ export class CpsLoaderComponent implements OnInit {
*/
@Input() showLabel = true;
- backgroundColor = 'rgba(0, 0, 0, 0.1)';
+ backgroundColor = 'var(--cps-surface-overlay)';
// eslint-disable-next-line no-useless-constructor
constructor(@Inject(DOCUMENT) private document: Document) {}
diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts
index 7d853bec..af4fe6d4 100644
--- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts
+++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts
@@ -1,7 +1,7 @@
import { CommonModule, DOCUMENT } from '@angular/common';
import { Component, Inject, Input, OnInit } from '@angular/core';
-import { convertSize } from '../../utils/internal/size-utils';
import { getCSSColor } from '../../utils/colors-utils';
+import { convertSize } from '../../utils/internal/size-utils';
/**
* CpsProgressLinearComponent is a process status indicator of a rectangular form.
@@ -30,7 +30,7 @@ export class CpsProgressLinearComponent implements OnInit {
* Color of the progress bar.
* @group Props
*/
- @Input() color = 'calm';
+ @Input() color = 'var(--cps-accent-primary)';
/**
* Background color of the progress bar.
diff --git a/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.spec.ts b/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.spec.ts
new file mode 100644
index 00000000..76d0d036
--- /dev/null
+++ b/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.spec.ts
@@ -0,0 +1,62 @@
+import { TestBed } from '@angular/core/testing';
+import { CpsThemeService } from './cps-theme.service';
+
+describe('CpsThemeService', () => {
+ let service: CpsThemeService;
+
+ beforeEach(() => {
+ localStorage.clear();
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(CpsThemeService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should initialize with light theme by default', () => {
+ expect(service.theme()).toBe('light');
+ });
+
+ it('should toggle theme', () => {
+ const initialTheme = service.theme();
+ service.toggleTheme();
+ const newTheme = service.theme();
+ expect(newTheme).not.toBe(initialTheme);
+ });
+
+ it('should save theme preference to localStorage', () => {
+ service.setTheme('dark', false);
+ expect(localStorage.getItem('cps-theme-preference')).toBe('dark');
+ });
+
+ it('should compute isDark correctly', () => {
+ service.setTheme('dark', false);
+ expect(service.isDark()).toBe(true);
+ service.setTheme('light', false);
+ expect(service.isDark()).toBe(false);
+ });
+
+ it('should initialize with calm color theme by default', () => {
+ expect(service.colorTheme()).toBe('calm');
+ });
+
+ it('should save color theme preference to localStorage', () => {
+ service.setColorTheme('energy', false);
+ expect(localStorage.getItem('cps-color-theme-preference')).toBe('energy');
+ });
+
+ it('should save base theme preference to localStorage', () => {
+ service.setBaseTheme('midnight', false);
+ expect(localStorage.getItem('cps-base-theme-preference')).toBe('midnight');
+ });
+
+ it('should save radius theme preference to localStorage', () => {
+ service.setRadiusTheme('rounded', false);
+ expect(localStorage.getItem('cps-radius-theme-preference')).toBe('rounded');
+ });
+
+ it('should initialize with compact radius theme by default', () => {
+ expect(service.radiusTheme()).toBe('compact');
+ });
+});
diff --git a/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.ts b/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.ts
new file mode 100644
index 00000000..d160cc6a
--- /dev/null
+++ b/projects/cps-ui-kit/src/lib/services/cps-theme/cps-theme.service.ts
@@ -0,0 +1,322 @@
+import { DOCUMENT } from '@angular/common';
+import { computed, inject, Injectable, signal } from '@angular/core';
+
+/**
+ * Available theme options
+ * @group Types
+ */
+export type CpsTheme = 'light' | 'dark';
+
+/**
+ * Available color theme options
+ * @group Types
+ */
+export type CpsColorTheme = 'neutral' | 'calm' | 'energy' | 'passion';
+
+/**
+ * Available dark-mode base theme options
+ * @group Types
+ */
+export type CpsBaseTheme = 'default' | 'graphite' | 'midnight' | 'aubergine';
+
+/**
+ * Available radius theme options
+ * @group Types
+ */
+export type CpsRadiusTheme = 'none' | 'compact' | 'rounded' | 'pill';
+
+/**
+ * CpsThemeService manages application theming including dark mode support.
+ *
+ * This service provides:
+ * - Light and dark theme switching with smooth transitions
+ * - Automatic persistence of theme preference in localStorage
+ * - Reactive state management using Angular signals
+ *
+ * @example
+ * ```typescript
+ * class MyComponent {
+ * private themeService = inject(CpsThemeService);
+ *
+ * isDark = this.themeService.isDark;
+ *
+ * toggleTheme() {
+ * this.themeService.toggleTheme();
+ * }
+ * }
+ * ```
+ *
+ * @group Services
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class CpsThemeService {
+ private document = inject(DOCUMENT);
+ private readonly THEME_STORAGE_KEY = 'cps-theme-preference';
+ private readonly COLOR_THEME_STORAGE_KEY = 'cps-color-theme-preference';
+ private readonly BASE_THEME_STORAGE_KEY = 'cps-base-theme-preference';
+ private readonly RADIUS_THEME_STORAGE_KEY = 'cps-radius-theme-preference';
+ private readonly TRANSITION_CLASS = 'cps-theme-transition';
+ private readonly TRANSITION_DURATION = 500;
+ private transitionTimeout: ReturnType | null = null;
+
+ private _theme = signal(this.getInitialTheme());
+ private _colorTheme = signal(this.getInitialColorTheme());
+ private _baseTheme = signal(this.getInitialBaseTheme());
+ private _radiusTheme = signal(this.getInitialRadiusTheme());
+
+ /**
+ * Current active theme (readonly)
+ */
+ readonly theme = this._theme.asReadonly();
+
+ /**
+ * Current active color theme (readonly)
+ */
+ readonly colorTheme = this._colorTheme.asReadonly();
+
+ /**
+ * Current active base theme (readonly)
+ */
+ readonly baseTheme = this._baseTheme.asReadonly();
+
+ /**
+ * Current active radius theme (readonly)
+ */
+ readonly radiusTheme = this._radiusTheme.asReadonly();
+
+ /**
+ * Whether dark mode is currently active
+ */
+ readonly isDark = computed(() => this._theme() === 'dark');
+
+ constructor() {
+ // Apply initial theme to DOM synchronously
+ this.applyCurrentTheme();
+
+ // Listen for system theme changes
+ this.watchSystemTheme();
+ }
+
+ /**
+ * Toggle between light and dark themes with smooth transition
+ */
+ toggleTheme(): void {
+ const newTheme: CpsTheme = this._theme() === 'light' ? 'dark' : 'light';
+ this.setTheme(newTheme);
+ }
+
+ /**
+ * Set specific theme
+ * @param theme - Theme to apply ('light' or 'dark')
+ * @param animated - Whether to animate the transition (default: true)
+ */
+ setTheme(theme: CpsTheme, animated = true): void {
+ if (this._theme() === theme) return;
+
+ if (animated) {
+ this.enableTransition();
+ }
+
+ this._theme.set(theme);
+ this.saveThemePreference(theme);
+ this.applyCurrentTheme();
+
+ if (animated) {
+ this.scheduleDisableTransition();
+ }
+ }
+
+ /**
+ * Set specific color theme independently from mode
+ * @param colorTheme - Color theme to apply
+ * @param animated - Whether to animate the transition (default: true)
+ */
+ setColorTheme(colorTheme: CpsColorTheme, animated = true): void {
+ if (this._colorTheme() === colorTheme) return;
+
+ if (animated) {
+ this.enableTransition();
+ }
+
+ this._colorTheme.set(colorTheme);
+ this.saveColorThemePreference(colorTheme);
+ this.applyCurrentTheme();
+
+ if (animated) {
+ this.scheduleDisableTransition();
+ }
+ }
+
+ /**
+ * Set base background theme (primarily affects dark mode)
+ */
+ setBaseTheme(baseTheme: CpsBaseTheme, animated = true): void {
+ if (this._baseTheme() === baseTheme) return;
+
+ if (animated) {
+ this.enableTransition();
+ }
+
+ this._baseTheme.set(baseTheme);
+ this.saveBaseThemePreference(baseTheme);
+ this.applyCurrentTheme();
+
+ if (animated) {
+ this.scheduleDisableTransition();
+ }
+ }
+
+ /**
+ * Set radius profile
+ */
+ setRadiusTheme(radiusTheme: CpsRadiusTheme, animated = true): void {
+ if (this._radiusTheme() === radiusTheme) return;
+
+ if (animated) {
+ this.enableTransition();
+ }
+
+ this._radiusTheme.set(radiusTheme);
+ this.saveRadiusThemePreference(radiusTheme);
+ this.applyCurrentTheme();
+
+ if (animated) {
+ this.scheduleDisableTransition();
+ }
+ }
+
+ private applyCurrentTheme(): void {
+ this.document.documentElement.setAttribute('data-theme', this._theme());
+ this.document.documentElement.setAttribute(
+ 'data-color-theme',
+ this._colorTheme()
+ );
+ this.document.documentElement.setAttribute(
+ 'data-base-theme',
+ this._baseTheme()
+ );
+ this.document.documentElement.setAttribute(
+ 'data-radius-theme',
+ this._radiusTheme()
+ );
+ }
+
+ private enableTransition(): void {
+ if (this.transitionTimeout) {
+ clearTimeout(this.transitionTimeout);
+ this.transitionTimeout = null;
+ }
+ this.document.documentElement.classList.add(this.TRANSITION_CLASS);
+ }
+
+ private scheduleDisableTransition(): void {
+ this.transitionTimeout = setTimeout(() => {
+ this.document.documentElement.classList.remove(this.TRANSITION_CLASS);
+ this.transitionTimeout = null;
+ }, this.TRANSITION_DURATION);
+ }
+
+ private getInitialTheme(): CpsTheme {
+ // Check saved preference first
+ const stored = localStorage.getItem(
+ this.THEME_STORAGE_KEY
+ ) as CpsTheme | null;
+ if (stored === 'light' || stored === 'dark') {
+ return stored;
+ }
+
+ // Fall back to light mode
+ return 'light';
+ }
+
+ private getInitialColorTheme(): CpsColorTheme {
+ const stored = localStorage.getItem(
+ this.COLOR_THEME_STORAGE_KEY
+ ) as CpsColorTheme | null;
+
+ if (
+ stored === 'neutral' ||
+ stored === 'calm' ||
+ stored === 'energy' ||
+ stored === 'passion'
+ ) {
+ return stored;
+ }
+
+ return 'calm';
+ }
+
+ private getInitialBaseTheme(): CpsBaseTheme {
+ const stored = localStorage.getItem(
+ this.BASE_THEME_STORAGE_KEY
+ ) as CpsBaseTheme | null;
+
+ if (
+ stored === 'default' ||
+ stored === 'graphite' ||
+ stored === 'midnight' ||
+ stored === 'aubergine'
+ ) {
+ return stored;
+ }
+
+ return 'default';
+ }
+
+ private getInitialRadiusTheme(): CpsRadiusTheme {
+ const stored = localStorage.getItem(
+ this.RADIUS_THEME_STORAGE_KEY
+ ) as CpsRadiusTheme | null;
+
+ if (
+ stored === 'none' ||
+ stored === 'compact' ||
+ stored === 'rounded' ||
+ stored === 'pill'
+ ) {
+ return stored;
+ }
+
+ return 'compact';
+ }
+
+ // TODO: Use as fallback in getInitialTheme() once dark mode is fully supported across all components.
+ private getSystemTheme(): CpsTheme {
+ const win = this.document.defaultView;
+ if (!win?.matchMedia) return 'light';
+ return win.matchMedia('(prefers-color-scheme: dark)').matches
+ ? 'dark'
+ : 'light';
+ }
+
+ // TODO: Enable system preference fallback once dark mode is fully supported across all components.
+ private watchSystemTheme(): void {
+ const win = this.document.defaultView;
+ if (!win?.matchMedia) return;
+ const mediaQuery = win.matchMedia('(prefers-color-scheme: dark)');
+ mediaQuery.addEventListener('change', (e) => {
+ // Only auto-switch if user hasn't set a preference
+ if (!localStorage.getItem(this.THEME_STORAGE_KEY)) {
+ this.setTheme(e.matches ? 'dark' : 'light');
+ }
+ });
+ }
+
+ private saveThemePreference(theme: CpsTheme): void {
+ localStorage.setItem(this.THEME_STORAGE_KEY, theme);
+ }
+
+ private saveColorThemePreference(colorTheme: CpsColorTheme): void {
+ localStorage.setItem(this.COLOR_THEME_STORAGE_KEY, colorTheme);
+ }
+
+ private saveBaseThemePreference(baseTheme: CpsBaseTheme): void {
+ localStorage.setItem(this.BASE_THEME_STORAGE_KEY, baseTheme);
+ }
+
+ private saveRadiusThemePreference(radiusTheme: CpsRadiusTheme): void {
+ localStorage.setItem(this.RADIUS_THEME_STORAGE_KEY, radiusTheme);
+ }
+}
diff --git a/projects/cps-ui-kit/src/lib/utils/colors-utils.ts b/projects/cps-ui-kit/src/lib/utils/colors-utils.ts
index 64d9701f..410e3167 100644
--- a/projects/cps-ui-kit/src/lib/utils/colors-utils.ts
+++ b/projects/cps-ui-kit/src/lib/utils/colors-utils.ts
@@ -20,12 +20,12 @@ const isDark = (color: string): boolean => {
let g = 0;
let b = 0;
if (color.match(/^rgb/)) {
- const colorMatched = color.match(
- /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/
- ) as any;
- r = colorMatched[1];
- g = colorMatched[2];
- b = colorMatched[3];
+ // Match both legacy rgba(r, g, b, a) and modern rgb(r g b / a) syntax
+ const colorMatched = color.match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/);
+ if (!colorMatched) return false;
+ r = +colorMatched[1];
+ g = +colorMatched[2];
+ b = +colorMatched[3];
} else {
const colorNum = +(
'0x' + color.slice(1).replace(color.length < 5 && (/./g as any), '$&$&')
@@ -42,6 +42,11 @@ const isDark = (color: string): boolean => {
return hsp <= 127.5;
};
+/**
+ * Collects all --cps-color-* CSS custom properties from :root rules only.
+ * Theme overrides (e.g. [data-theme='dark']) are excluded to avoid duplicates,
+ * since the Colors page serves as a base palette reference.
+ */
export const getCpsColors = (_document: Document): [string, string][] =>
[...(_document.styleSheets as any)]
.filter((sheet: any) =>
@@ -50,16 +55,20 @@ export const getCpsColors = (_document: Document): [string, string][] =>
.reduce(
(finalArr, sheet) =>
finalArr.concat(
- [...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule) => {
- const props = [...rule.style]
- .map((propName) => [
- propName.trim(),
- rule.style.getPropertyValue(propName).trim()
- ])
- .filter(([propName]) => propName.indexOf('--cps-color') === 0);
+ [...sheet.cssRules]
+ .filter(
+ (rule: any) => isStyleRule(rule) && rule.selectorText === ':root'
+ )
+ .reduce((propValArr, rule) => {
+ const props = [...rule.style]
+ .map((propName) => [
+ propName.trim(),
+ rule.style.getPropertyValue(propName).trim()
+ ])
+ .filter(([propName]) => propName.indexOf('--cps-color') === 0);
- return [...propValArr, ...props];
- }, [])
+ return [...propValArr, ...props];
+ }, [])
),
[]
);
diff --git a/projects/cps-ui-kit/src/public-api.ts b/projects/cps-ui-kit/src/public-api.ts
index 0141399e..97b6370c 100644
--- a/projects/cps-ui-kit/src/public-api.ts
+++ b/projects/cps-ui-kit/src/public-api.ts
@@ -2,53 +2,53 @@
* Public API Surface of cps-ui-kit
*/
-export * from './lib/components/cps-icon/cps-icon.component';
-export * from './lib/components/cps-input/cps-input.component';
-export * from './lib/components/cps-select/cps-select.component';
-export * from './lib/components/cps-tree-select/cps-tree-select.component';
export * from './lib/components/cps-autocomplete/cps-autocomplete.component';
-export * from './lib/components/cps-tree-autocomplete/cps-tree-autocomplete.component';
-export * from './lib/components/cps-info-circle/cps-info-circle.component';
+export * from './lib/components/cps-button-toggle/cps-button-toggle.component';
export * from './lib/components/cps-button/cps-button.component';
export * from './lib/components/cps-checkbox/cps-checkbox.component';
-export * from './lib/components/cps-radio-group/cps-radio/cps-radio.component';
+export * from './lib/components/cps-chip/cps-chip.component';
+export * from './lib/components/cps-datepicker/cps-datepicker.component';
+export * from './lib/components/cps-divider/cps-divider.component';
+export * from './lib/components/cps-expansion-panel/cps-expansion-panel.component';
+export * from './lib/components/cps-file-upload/cps-file-upload.component';
+export * from './lib/components/cps-icon/cps-icon.component';
+export * from './lib/components/cps-info-circle/cps-info-circle.component';
+export * from './lib/components/cps-input/cps-input.component';
+export * from './lib/components/cps-loader/cps-loader.component';
+export * from './lib/components/cps-menu/cps-menu.component';
+export * from './lib/components/cps-paginator/cps-paginator.component';
+export * from './lib/components/cps-paginator/pipes/cps-paginate.pipe';
+export * from './lib/components/cps-progress-circular/cps-progress-circular.component';
+export * from './lib/components/cps-progress-linear/cps-progress-linear.component';
export * from './lib/components/cps-radio-group/cps-radio-group.component';
+export * from './lib/components/cps-radio-group/cps-radio/cps-radio.component';
+export * from './lib/components/cps-scheduler/cps-scheduler.component';
+export * from './lib/components/cps-select/cps-select.component';
+export * from './lib/components/cps-sidebar-menu/cps-sidebar-menu.component';
+export * from './lib/components/cps-switch/cps-switch.component';
+export * from './lib/components/cps-tab-group/cps-tab-group.component';
+export * from './lib/components/cps-tab-group/cps-tab/cps-tab.component';
+export * from './lib/components/cps-table/cps-column-filter-types';
export * from './lib/components/cps-table/cps-table.component';
-export * from './lib/components/cps-table/directives/cps-table-column-sortable.directive';
export * from './lib/components/cps-table/directives/cps-table-column-filter.directive';
export * from './lib/components/cps-table/directives/cps-table-column-resizable.directive';
+export * from './lib/components/cps-table/directives/cps-table-column-sortable.directive';
export * from './lib/components/cps-table/directives/cps-table-header-selectable.directive';
export * from './lib/components/cps-table/directives/cps-table-row-selectable.directive';
-export * from './lib/components/cps-table/cps-column-filter-types';
export * from './lib/components/cps-table/pipes/cps-table-detect-filter-type.pipe';
+export * from './lib/components/cps-tag/cps-tag.component';
+export * from './lib/components/cps-textarea/cps-textarea.component';
+export * from './lib/components/cps-timepicker/cps-timepicker.component';
+export * from './lib/components/cps-tree-autocomplete/cps-tree-autocomplete.component';
+export * from './lib/components/cps-tree-select/cps-tree-select.component';
export * from './lib/components/cps-tree-table/cps-tree-table.component';
-export * from './lib/components/cps-tree-table/directives/cps-tree-table-column-sortable.directive';
export * from './lib/components/cps-tree-table/directives/cps-tree-table-column-filter.directive';
export * from './lib/components/cps-tree-table/directives/cps-tree-table-column-resizable.directive';
-export * from './lib/components/cps-tree-table/directives/cps-tree-table-row-toggler.directive';
+export * from './lib/components/cps-tree-table/directives/cps-tree-table-column-sortable.directive';
export * from './lib/components/cps-tree-table/directives/cps-tree-table-header-selectable.directive';
export * from './lib/components/cps-tree-table/directives/cps-tree-table-row-selectable.directive';
+export * from './lib/components/cps-tree-table/directives/cps-tree-table-row-toggler.directive';
export * from './lib/components/cps-tree-table/pipes/cps-tree-table-detect-filter-type.pipe';
-export * from './lib/components/cps-tag/cps-tag.component';
-export * from './lib/components/cps-chip/cps-chip.component';
-export * from './lib/components/cps-menu/cps-menu.component';
-export * from './lib/components/cps-paginator/cps-paginator.component';
-export * from './lib/components/cps-paginator/pipes/cps-paginate.pipe';
-export * from './lib/components/cps-loader/cps-loader.component';
-export * from './lib/components/cps-expansion-panel/cps-expansion-panel.component';
-export * from './lib/components/cps-progress-circular/cps-progress-circular.component';
-export * from './lib/components/cps-progress-linear/cps-progress-linear.component';
-export * from './lib/components/cps-datepicker/cps-datepicker.component';
-export * from './lib/components/cps-sidebar-menu/cps-sidebar-menu.component';
-export * from './lib/components/cps-textarea/cps-textarea.component';
-export * from './lib/components/cps-button-toggle/cps-button-toggle.component';
-export * from './lib/components/cps-tab-group/cps-tab-group.component';
-export * from './lib/components/cps-tab-group/cps-tab/cps-tab.component';
-export * from './lib/components/cps-timepicker/cps-timepicker.component';
-export * from './lib/components/cps-file-upload/cps-file-upload.component';
-export * from './lib/components/cps-scheduler/cps-scheduler.component';
-export * from './lib/components/cps-switch/cps-switch.component';
-export * from './lib/components/cps-divider/cps-divider.component';
export * from './lib/directives/cps-tooltip/cps-tooltip.directive';
@@ -59,4 +59,5 @@ export * from './lib/services/cps-dialog/utils/cps-dialog-ref';
export * from './lib/services/cps-notification/cps-notification.service';
export * from './lib/services/cps-notification/utils/cps-notification-config';
+export * from './lib/services/cps-theme/cps-theme.service';
export * from './lib/utils/colors-utils';
diff --git a/projects/cps-ui-kit/styles/_colors-dark.scss b/projects/cps-ui-kit/styles/_colors-dark.scss
new file mode 100644
index 00000000..2abc1d6f
--- /dev/null
+++ b/projects/cps-ui-kit/styles/_colors-dark.scss
@@ -0,0 +1,184 @@
+// Dark theme color palette
+// Only overrides variables that differ from the light theme (:root).
+// All brand palette, darks, and state variant colors are inherited from :root.
+
+[data-theme='dark'] {
+ // Brand color overrides (base values only — variants inherited from :root)
+ --cps-color-energy: #f47721;
+ --cps-color-care: #f05a78;
+
+ // States backgrounds
+ --cps-color-info-bg: rgba(73, 185, 255, 0.16);
+ --cps-color-success-bg: rgba(93, 211, 58, 0.16);
+ --cps-color-warn-bg: rgba(255, 186, 48, 0.16);
+ --cps-color-error-bg: rgba(255, 90, 103, 0.16);
+
+ // Highlights
+ --cps-color-highlight-hover: rgba(251, 251, 251, 0.06);
+ --cps-color-highlight-active: rgba(251, 251, 251, 0.1);
+ --cps-color-highlight-selected: rgba(244, 119, 33, 0.16);
+ --cps-color-highlight-selected-dark: rgba(244, 119, 33, 0.24);
+
+ // Backgrounds
+ --cps-color-bg-lightest: #2a2a31;
+ --cps-color-bg-light: #1f1f24;
+ --cps-color-bg-mid: #1b1b1e;
+ --cps-color-bg-dark: #151419;
+
+ // Lines
+ --cps-color-line-light: #3f3f46;
+ --cps-color-line-mid: #50505a;
+ --cps-color-line-dark: #666670;
+ --cps-color-line-darkest: #7a7a86;
+
+ // Text
+ --cps-color-text-lightest: #fbfbfb;
+ --cps-color-text-light: #d0d0d2;
+ --cps-color-text-mild: #a8a8ad;
+ --cps-color-text-dark: #878787;
+ --cps-color-text-darkest: #151419;
+
+ // Semantic design tokens
+ --cps-accent-primary: var(--cps-color-energy);
+ --cps-accent-primary-contrast: #151419;
+ --cps-accent-secondary: #fbfbfb;
+ --cps-accent-secondary-contrast: #151419;
+ --cps-error-background: rgba(255, 90, 103, 0.16);
+
+ // Lines and borders
+ --cps-color-line: rgba(251, 251, 251, 0.12);
+ --cps-surface-body: #151419;
+ --cps-surface-highlight: #1b1b1e;
+ --cps-surface-muted: #202028;
+ --cps-surface-elevated: #262626;
+ --cps-surface-control: #262626;
+ --cps-surface-overlay: rgba(21, 20, 25, 0.85);
+ --cps-background-color: #151419;
+ --cps-background-disabled: rgba(255, 255, 255, 0.05);
+
+ // Tab component
+ --cps-tab-subtabs-background: var(--cps-surface-highlight);
+ --cps-tabs-subtabs-active-background: var(--cps-color-highlight-selected);
+ --cps-tabs-subtabs-background-hover: var(--cps-highlight-hover);
+ --cps-tabs-subtabs-text-hover: var(--cps-text-primary);
+
+ // Text
+ --cps-text-primary: #fbfbfb;
+ --cps-text-secondary: #d0d0d2;
+ --cps-text-muted: #878787;
+ --cps-text-inverse: var(--cps-color-depth-darken4);
+ --cps-text-on-accent: #151419;
+ --cps-text-disabled: #6f6f74;
+
+ --cps-border-color: #3a3a3a;
+ --cps-border-strong: #505050;
+ --cps-border-focus: var(--cps-accent-primary);
+
+ --cps-highlight-hover: rgba(251, 251, 251, 0.1);
+ --cps-highlight-active: rgba(251, 251, 251, 0.16);
+ --cps-highlight-selected: var(--cps-color-highlight-selected-dark);
+
+ --cps-state-info: #49b9ff;
+ --cps-state-info-contrast: #151419;
+ --cps-state-info-surface: rgba(73, 185, 255, 0.16);
+ --cps-state-success: #5dd33a;
+ --cps-state-success-contrast: #151419;
+ --cps-state-success-surface: rgba(93, 211, 58, 0.16);
+ --cps-state-warn: #ffba30;
+ --cps-state-warn-contrast: #151419;
+ --cps-state-warn-surface: rgba(255, 186, 48, 0.16);
+ --cps-state-error: var(--cps-color-error);
+ --cps-state-error-contrast: #ffffff;
+ --cps-state-error-surface: rgba(185, 28, 28, 0.28);
+
+ // Modern semantic surfaces
+ --cps-card-background: var(--cps-surface-highlight);
+ --cps-card-foreground: var(--cps-text-primary);
+ --cps-popover-background: var(--cps-surface-elevated);
+ --cps-popover-foreground: var(--cps-text-primary);
+ --cps-input-background: var(--cps-surface-highlight);
+ --cps-input-foreground: var(--cps-text-primary);
+ --cps-input-placeholder: var(--cps-text-muted);
+
+ // Focus ring and outlines
+ --cps-ring-color: var(--cps-border-focus);
+ --cps-ring-offset-color: var(--cps-surface-body);
+
+ // Elevation shadows (darker than light theme)
+ --cps-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --cps-shadow-sm: 0 4px 10px rgba(0, 0, 0, 0.34);
+ --cps-shadow-md: 0 12px 24px rgba(0, 0, 0, 0.4);
+ --cps-shadow-lg: 0 24px 48px rgba(0, 0, 0, 0.5);
+}
+
+[data-theme='dark'][data-color-theme='neutral'] {
+ --cps-accent-primary: #e4e4e7;
+ --cps-accent-primary-contrast: #151419;
+ --cps-accent-secondary: #f4f4f5;
+ --cps-accent-secondary-contrast: #151419;
+ --cps-border-focus: #e4e4e7;
+ --cps-highlight-selected: rgba(228, 228, 231, 0.22);
+}
+
+[data-theme='dark'][data-color-theme='calm'] {
+ --cps-accent-primary: var(--cps-color-calm-lighten4);
+ --cps-accent-primary-contrast: #151419;
+ --cps-accent-secondary: var(--cps-color-care-lighten3);
+ --cps-accent-secondary-contrast: #151419;
+ --cps-border-focus: var(--cps-color-calm-lighten4);
+ --cps-highlight-selected: rgba(253, 128, 158, 0.28);
+}
+
+[data-theme='dark'][data-color-theme='energy'] {
+ --cps-accent-primary: var(--cps-color-energy);
+ --cps-accent-primary-contrast: #151419;
+ --cps-accent-secondary: var(--cps-color-prepared-lighten4);
+ --cps-accent-secondary-contrast: #151419;
+ --cps-border-focus: var(--cps-color-energy);
+ --cps-highlight-selected: var(--cps-color-highlight-selected-dark);
+}
+
+[data-theme='dark'][data-color-theme='passion'] {
+ --cps-accent-primary: var(--cps-color-passion-lighten3);
+ --cps-accent-primary-contrast: #151419;
+ --cps-accent-secondary: var(--cps-color-care-lighten4);
+ --cps-accent-secondary-contrast: #151419;
+ --cps-border-focus: var(--cps-color-passion-lighten3);
+ --cps-highlight-selected: rgba(255, 120, 121, 0.3);
+}
+
+[data-theme='dark'][data-base-theme='graphite'] {
+ --cps-surface-body: #121216;
+ --cps-surface-highlight: #191a20;
+ --cps-surface-muted: #1d1f26;
+ --cps-surface-elevated: #23252f;
+ --cps-surface-control: #23252f;
+ --cps-background-color: #121216;
+ --cps-border-color: #3f424d;
+ --cps-border-strong: #555968;
+ --cps-ring-offset-color: #121216;
+}
+
+[data-theme='dark'][data-base-theme='midnight'] {
+ --cps-surface-body: #0f1624;
+ --cps-surface-highlight: #172033;
+ --cps-surface-muted: #1b263c;
+ --cps-surface-elevated: #21304a;
+ --cps-surface-control: #21304a;
+ --cps-background-color: #0f1624;
+ --cps-border-color: #32445f;
+ --cps-border-strong: #4a6388;
+ --cps-ring-offset-color: #0f1624;
+}
+
+[data-theme='dark'][data-base-theme='aubergine'] {
+ --cps-surface-body: #1a1320;
+ --cps-surface-highlight: #231a2c;
+ --cps-surface-muted: #2b2136;
+ --cps-surface-elevated: #332943;
+ --cps-surface-control: #332943;
+ --cps-background-color: #1a1320;
+ --cps-border-color: #534363;
+ --cps-border-strong: #6d5683;
+ --cps-ring-offset-color: #1a1320;
+}
diff --git a/projects/cps-ui-kit/styles/_colors.scss b/projects/cps-ui-kit/styles/_colors.scss
index c63e637a..5a540f05 100644
--- a/projects/cps-ui-kit/styles/_colors.scss
+++ b/projects/cps-ui-kit/styles/_colors.scss
@@ -230,7 +230,7 @@
--cps-color-warn-darken3: #a15300;
--cps-color-warn-darken4: #843b00;
- --cps-color-error: #cc3333;
+ --cps-color-error: #b91c1c;
--cps-color-error-highlighten: #f9e4e5;
--cps-color-error-lighten5: #f5d9d9;
--cps-color-error-lighten4: #f2cece;
@@ -272,4 +272,162 @@
--cps-color-text-mild: #787272;
--cps-color-text-dark: #524a4a;
--cps-color-text-darkest: #2d2323;
+
+ // Semantic design tokens (use these inside components)
+ --cps-accent-primary: var(--cps-color-passion);
+ --cps-accent-primary-contrast: #ffffff;
+ --cps-accent-secondary: var(--cps-color-energy);
+ --cps-accent-secondary-contrast: #2d2323;
+ --cps-error-background: #fef3f2;
+
+ // Lines and borders
+ --cps-color-line: #e3e2e2;
+
+ --cps-surface-body: var(--cps-color-bg-light);
+ --cps-surface-highlight: #ffffff;
+ --cps-surface-muted: var(--cps-color-bg-lightest);
+ --cps-surface-elevated: var(--cps-color-bg-mid);
+ --cps-surface-control: #ffffff;
+ --cps-surface-overlay: rgba(0, 0, 0, 0.45);
+ --cps-background-color: var(--cps-color-bg-lightest);
+ --cps-background-disabled: #f7f7f7;
+
+ // Tab component
+ --cps-tab-subtabs-background: #d7d7d759;
+ --cps-tabs-subtabs-active-background: #fff;
+ --cps-tabs-subtabs-background-hover: var(--cps-highlight-active);
+ --cps-tabs-subtabs-text-hover: var(--cps-accent-primary);
+
+ --cps-text-primary: var(--cps-color-text-darkest);
+ --cps-text-secondary: var(--cps-color-text-dark);
+ --cps-text-muted: var(--cps-color-text-mild);
+ --cps-text-inverse: var(--cps-color-depth-darken4);
+ --cps-text-on-accent: #ffffff;
+ --cps-text-disabled: var(--cps-color-text-light);
+
+ --cps-border-color: var(--cps-color-line-mid);
+ --cps-border-strong: var(--cps-color-line-dark);
+ --cps-border-focus: var(--cps-color-passion);
+
+ --cps-highlight-hover: var(--cps-color-highlight-hover);
+ --cps-highlight-active: var(--cps-color-highlight-active);
+ --cps-highlight-selected: var(--cps-color-highlight-selected);
+
+ --cps-state-info: var(--cps-color-info);
+ --cps-state-info-contrast: #2d2323;
+ --cps-state-info-surface: var(--cps-color-info-bg);
+ --cps-state-success: var(--cps-color-success);
+ --cps-state-success-contrast: #2d2323;
+ --cps-state-success-surface: var(--cps-color-success-bg);
+ --cps-state-warn: var(--cps-color-warn);
+ --cps-state-warn-contrast: #3b2500;
+ --cps-state-warn-surface: var(--cps-color-warn-bg);
+ --cps-state-error: var(--cps-color-error);
+ --cps-state-error-contrast: #ffffff;
+ --cps-state-error-surface: rgba(185, 28, 28, 0.14);
+
+ // Modern semantic surfaces
+ --cps-card-background: var(--cps-surface-highlight);
+ --cps-card-foreground: var(--cps-text-primary);
+ --cps-popover-background: var(--cps-surface-control);
+ --cps-popover-foreground: var(--cps-text-primary);
+ --cps-input-background: var(--cps-surface-control);
+ --cps-input-foreground: var(--cps-text-primary);
+ --cps-input-placeholder: var(--cps-text-muted);
+
+ // Focus ring and outlines
+ --cps-ring-color: var(--cps-border-focus);
+ --cps-ring-offset-color: var(--cps-surface-body);
+
+ // Radius scale
+ --cps-radius-xs: 2px;
+ --cps-radius-sm: 4px;
+ --cps-radius-md: 8px;
+ --cps-radius-lg: 12px;
+ --cps-radius-xl: 16px;
+ --cps-radius-full: 9999px;
+
+ // Backward compatible radius aliases
+ --cps-border-radius-small: var(--cps-radius-sm);
+ --cps-border-radius-medium: var(--cps-radius-md);
+ --cps-border-radius-large: var(--cps-radius-lg);
+ --cps-border-radius-round: var(--cps-radius-full);
+
+ // Elevation shadows
+ --cps-shadow-xs: 0 1px 2px rgba(45, 35, 35, 0.08);
+ --cps-shadow-sm: 0 2px 6px rgba(45, 35, 35, 0.12);
+ --cps-shadow-md: 0 8px 20px rgba(45, 35, 35, 0.14);
+ --cps-shadow-lg: 0 16px 40px rgba(45, 35, 35, 0.2);
+
+ // Motion tokens
+ --cps-motion-fast: 120ms;
+ --cps-motion-base: 180ms;
+ --cps-motion-slow: 260ms;
+ --cps-motion-easing: cubic-bezier(0.2, 0, 0, 1);
+}
+
+:root[data-color-theme='neutral'] {
+ --cps-accent-primary: #27272a;
+ --cps-accent-primary-contrast: #ffffff;
+ --cps-accent-secondary: #3f3f46;
+ --cps-accent-secondary-contrast: #ffffff;
+ --cps-border-focus: #27272a;
+ --cps-highlight-selected: rgba(39, 39, 42, 0.14);
+}
+
+:root[data-color-theme='calm'] {
+ --cps-accent-primary: var(--cps-color-calm);
+ --cps-accent-primary-contrast: #ffffff;
+ --cps-accent-secondary: var(--cps-color-care);
+ --cps-accent-secondary-contrast: #2d2323;
+ --cps-border-focus: var(--cps-color-calm);
+ --cps-highlight-selected: rgba(135, 10, 60, 0.16);
+}
+
+:root[data-color-theme='energy'] {
+ --cps-accent-primary: var(--cps-color-energy);
+ --cps-accent-primary-contrast: #2d2323;
+ --cps-text-on-accent: #2d2323;
+ --cps-accent-secondary: var(--cps-color-prepared);
+ --cps-accent-secondary-contrast: #2d2323;
+ --cps-border-focus: var(--cps-color-energy);
+ --cps-highlight-selected: var(--cps-color-highlight-selected);
+}
+
+:root[data-color-theme='passion'] {
+ --cps-accent-primary: var(--cps-color-passion);
+ --cps-accent-primary-contrast: #ffffff;
+ --cps-accent-secondary: var(--cps-color-warmth);
+ --cps-accent-secondary-contrast: #ffffff;
+ --cps-border-focus: var(--cps-color-passion);
+ --cps-highlight-selected: rgba(220, 0, 50, 0.16);
+}
+
+:root[data-radius-theme='none'] {
+ --cps-radius-xs: 0px;
+ --cps-radius-sm: 0px;
+ --cps-radius-md: 0px;
+ --cps-radius-lg: 0px;
+ --cps-radius-xl: 0px;
+}
+
+:root[data-radius-theme='compact'] {
+ --cps-radius-sm: 3px;
+ --cps-radius-md: 6px;
+ --cps-radius-lg: 10px;
+ --cps-radius-xl: 14px;
+}
+
+:root[data-radius-theme='rounded'] {
+ --cps-radius-sm: 6px;
+ --cps-radius-md: 10px;
+ --cps-radius-lg: 14px;
+ --cps-radius-xl: 20px;
+}
+
+:root[data-radius-theme='pill'] {
+ --cps-radius-sm: 9999px;
+ --cps-radius-md: 9999px;
+ --cps-radius-lg: 9999px;
+ --cps-radius-xl: 9999px;
}
diff --git a/projects/cps-ui-kit/styles/styles.scss b/projects/cps-ui-kit/styles/styles.scss
index 79e63a09..84dfb7d1 100644
--- a/projects/cps-ui-kit/styles/styles.scss
+++ b/projects/cps-ui-kit/styles/styles.scss
@@ -1,5 +1,6 @@
@use './_bootstrap-grid';
@use './_colors.scss';
+@use './_colors-dark.scss';
@use './_fonts.scss';
@use './_cps-tooltip-style.scss';
@use 'primeicons/primeicons.css';
@@ -18,3 +19,38 @@
position: absolute;
top: -9999px;
}
+
+/* Custom scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+::-webkit-scrollbar-track {
+ background: var(--cps-background-color);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--cps-border-color);
+ border-radius: var(--cps-radius-sm);
+}
+::-webkit-scrollbar-thumb:hover {
+ background: var(--cps-accent-primary);
+}
+
+html,
+body {
+ font-family: 'Source Sans Pro', sans-serif;
+ background: var(--cps-background-color);
+ color: var(--cps-text-primary);
+}
+
+// Theme transition hook used by CpsThemeService
+.cps-theme-transition,
+.cps-theme-transition *,
+.cps-theme-transition *::before,
+.cps-theme-transition *::after {
+ transition:
+ background-color var(--cps-motion-base) var(--cps-motion-easing),
+ border-color var(--cps-motion-base) var(--cps-motion-easing),
+ color var(--cps-motion-base) var(--cps-motion-easing),
+ box-shadow var(--cps-motion-base) var(--cps-motion-easing) !important;
+}