>(
+ record: T,
+ targetElement: HTMLElement = document.body
+): T {
+ const perfStart = performance.now();
+
+ // Add a temporary div to attach temp css variables to
+ const tmpPropEl = document.createElement('div');
+ targetElement.appendChild(tmpPropEl);
+
+ const varExpressions = [...extractDistinctCssVariableExpressions(record)];
+
+ // Set temporary css variables for resolving var expressions
+ varExpressions.forEach((varExpression, i) => {
+ const tmpPropKey = `--${TMP_CSS_PROP_PREFIX}-${i}`;
+ tmpPropEl.style.setProperty(tmpPropKey, varExpression);
+ });
+
+ const result = {} as T;
+
+ const computedStyle = window.getComputedStyle(tmpPropEl);
+
+ const resolver = (varExpression: string): string => {
+ const tmpPropKey = `--${TMP_CSS_PROP_PREFIX}-${varExpressions.indexOf(
+ varExpression
+ )}`;
+
+ const resolved = computedStyle.getPropertyValue(tmpPropKey);
+
+ return ColorUtils.normalizeCssColor(resolved);
+ };
+
+ // Resolve the temporary css variables
+ Object.entries(record).forEach(([key, value]) => {
+ result[key as keyof T] = resolveCssVariablesInString(
+ resolver,
+ value
+ ) as T[keyof T];
+ });
+
+ // Remove the temporary css variables
+ tmpPropEl.remove();
+
+ log.debug('Resolved css variables', performance.now() - perfStart, 'ms');
+
+ return result;
+}
+
+/**
+ * Resolve css variable expressions in the given string using the
+ * given resolver and replace the original expressions with the resolved values.
+ *
+ * @param resolver Function that can resolve a css variable expression
+ * @param value Value that may contain css variable expressions
+ */
+export function resolveCssVariablesInString(
+ resolver: VarExpressionResolver,
+ value: string
+): string {
+ const result: string[] = [];
+ let i = 0;
+ getCssVariableRanges(value).forEach(([start, end]) => {
+ if (i < start) {
+ result.push(value.substring(i, start));
+ i += start - i;
+ }
+
+ result.push(resolver(value.substring(start, end + 1)));
+
+ i += end - start + 1;
+ });
+
+ if (result.length === 0) {
+ return value;
+ }
+
+ return result.join('');
+}
+
/**
* Store theme preload data in local storage.
* @param preloadData The preload data to set
diff --git a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
index 245e385a12..33d1ae08e2 100644
--- a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
+++ b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
@@ -22,6 +22,9 @@ exports[`ThemeProvider setSelectedThemeKey: [ [Object] ] should change selected
data-theme-key="default-dark"
>
test-file-stub
+test-file-stub
+test-file-stub
+test-file-stub
Child
diff --git a/packages/components/src/theme/theme-dark/index.ts b/packages/components/src/theme/theme-dark/index.ts
new file mode 100644
index 0000000000..04f5f09c2b
--- /dev/null
+++ b/packages/components/src/theme/theme-dark/index.ts
@@ -0,0 +1,13 @@
+import themeDarkPalette from './theme-dark-palette.css?inline';
+import themeDarkSemantic from './theme-dark-semantic.css?inline';
+import themeDarkSemanticEditor from './theme-dark-semantic-editor.css?inline';
+import themeDarkSemanticGrid from './theme-dark-semantic-grid.css?inline';
+
+export const themeDark = [
+ themeDarkPalette,
+ themeDarkSemantic,
+ themeDarkSemanticEditor,
+ themeDarkSemanticGrid,
+].join('\n');
+
+export default themeDark;
diff --git a/packages/components/src/theme/theme-dark/theme-dark-palette.css b/packages/components/src/theme/theme-dark/theme-dark-palette.css
new file mode 100644
index 0000000000..e5c60aa08e
--- /dev/null
+++ b/packages/components/src/theme/theme-dark/theme-dark-palette.css
@@ -0,0 +1,302 @@
+:root {
+ /* Gray */
+ --dh-color-gray-hue: 0deg;
+ --dh-color-gray-50: hsl(var(--dh-color-gray-hue) 6% 10%);
+ --dh-color-gray-75: hsl(var(--dh-color-gray-hue) 5% 13%);
+ --dh-color-gray-100: hsl(var(--dh-color-gray-hue), 5%, 17%);
+ --dh-color-gray-200: hsl(var(--dh-color-gray-hue) 4% 19%);
+ --dh-color-gray-300: hsl(var(--dh-color-gray-hue) 4% 21%);
+ --dh-color-gray-400: hsl(var(--dh-color-gray-hue) 2% 25%);
+ --dh-color-gray-500: hsl(var(--dh-color-gray-hue) 1% 36%);
+ --dh-color-gray-600: hsl(var(--dh-color-gray-hue) 0% 57%);
+ --dh-color-gray-700: hsl(var(--dh-color-gray-hue) 1% 75%);
+ --dh-color-gray-800: hsl(var(--dh-color-gray-hue) 6% 94%);
+ --dh-color-gray-900: hsl(var(--dh-color-gray-hue) 25% 98%);
+
+ /** Black & White */
+ --dh-color-black: var(--dh-color-gray-50);
+ --dh-color-white: var(--dh-color-gray-800);
+
+ /* Blue */
+ --dh-color-blue-hue: 222deg;
+ --dh-color-blue-100: hsl(var(--dh-color-blue-hue) 65% 19%);
+ --dh-color-blue-200: hsl(var(--dh-color-blue-hue) 66% 25%);
+ --dh-color-blue-300: hsl(var(--dh-color-blue-hue) 65% 32%);
+ --dh-color-blue-400: hsl(var(--dh-color-blue-hue) 63% 39%);
+ --dh-color-blue-500: hsl(var(--dh-color-blue-hue) 61% 47%);
+ --dh-color-blue-600: hsl(var(--dh-color-blue-hue) 68% 54%);
+ --dh-color-blue-700: hsl(var(--dh-color-blue-hue) 83% 62%);
+ --dh-color-blue-800: hsl(var(--dh-color-blue-hue) 94% 68%);
+ --dh-color-blue-900: hsl(var(--dh-color-blue-hue) 100% 74%);
+ --dh-color-blue-1000: hsl(var(--dh-color-blue-hue) 100% 80%);
+ --dh-color-blue-1100: hsl(calc(var(--dh-color-blue-hue) - 1deg) 100% 84%);
+ --dh-color-blue-1200: hsl(calc(var(--dh-color-blue-hue) - 1deg) 100% 89%);
+ --dh-color-blue-1300: hsl(var(--dh-color-blue-hue) 100% 93%);
+ --dh-color-blue-1400: hsl(calc(var(--dh-color-blue-hue) + 2deg) 100% 96%);
+
+ /* Red */
+ --dh-color-red-hue: 345deg;
+ --dh-color-red-100: hsl(calc(var(--dh-color-red-hue) + 1deg) 54% 18%);
+ --dh-color-red-200: hsl(var(--dh-color-red-hue) 55% 24%);
+ --dh-color-red-300: hsl(calc(var(--dh-color-red-hue) + 1deg) 54% 30%);
+ --dh-color-red-400: hsl(var(--dh-color-red-hue) 54% 37%);
+ --dh-color-red-500: hsl(var(--dh-color-red-hue) 53% 44%);
+ --dh-color-red-600: hsl(var(--dh-color-red-hue) 55% 51%);
+ --dh-color-red-700: hsl(var(--dh-color-red-hue) 71% 59%);
+ --dh-color-red-800: hsl(var(--dh-color-red-hue) 92% 67%);
+ --dh-color-red-900: hsl(calc(var(--dh-color-red-hue) - 1deg) 100% 74%);
+ --dh-color-red-1000: hsl(calc(var(--dh-color-red-hue) - 2deg) 100% 80%);
+ --dh-color-red-1100: hsl(calc(var(--dh-color-red-hue) - 2deg) 100% 85%);
+ --dh-color-red-1200: hsl(calc(var(--dh-color-red-hue) - 1deg) 100% 90%);
+ --dh-color-red-1300: hsl(calc(var(--dh-color-red-hue) - 1deg) 100% 94%);
+ --dh-color-red-1400: hsl(calc(var(--dh-color-red-hue) - 1deg) 100% 96%);
+
+ /* Orange */
+ --dh-color-orange-hue: 22deg;
+ --dh-color-orange-100: hsl(calc(var(--dh-color-orange-hue) - 2deg) 100% 14%);
+ --dh-color-orange-200: hsl(var(--dh-color-orange-hue) 96% 18%);
+ --dh-color-orange-300: hsl(calc(var(--dh-color-orange-hue) + 1deg) 90% 23%);
+ --dh-color-orange-400: hsl(var(--dh-color-orange-hue) 84% 29%);
+ --dh-color-orange-500: hsl(var(--dh-color-orange-hue) 80% 35%);
+ --dh-color-orange-600: hsl(var(--dh-color-orange-hue) 74% 41%);
+ --dh-color-orange-700: hsl(var(--dh-color-orange-hue) 70% 48%);
+ --dh-color-orange-800: hsl(var(--dh-color-orange-hue) 78% 55%);
+ --dh-color-orange-900: hsl(calc(var(--dh-color-orange-hue) - 1deg) 95% 63%);
+ --dh-color-orange-1000: hsl(var(--dh-color-orange-hue) 100% 71%);
+ --dh-color-orange-1100: hsl(calc(var(--dh-color-orange-hue) + 1deg) 100% 78%);
+ --dh-color-orange-1200: hsl(calc(var(--dh-color-orange-hue) + 2deg) 100% 84%);
+ --dh-color-orange-1300: hsl(calc(var(--dh-color-orange-hue) + 2deg) 100% 90%);
+ --dh-color-orange-1400: hsl(calc(var(--dh-color-orange-hue) + 3deg) 100% 94%);
+
+ /* Yellow */
+ --dh-color-yellow-hue: 49deg;
+ --dh-color-yellow-100: hsl(calc(var(--dh-color-yellow-hue) + 2deg) 100% 9%);
+ --dh-color-yellow-200: hsl(calc(var(--dh-color-yellow-hue) + 1deg) 100% 12%);
+ --dh-color-yellow-300: hsl(calc(var(--dh-color-yellow-hue) + 2deg) 100% 15%);
+ --dh-color-yellow-400: hsl(calc(var(--dh-color-yellow-hue) + 1deg) 90% 20%);
+ --dh-color-yellow-500: hsl(var(--dh-color-yellow-hue) 82% 25%);
+ --dh-color-yellow-600: hsl(calc(var(--dh-color-yellow-hue) - 1deg) 75% 30%);
+ --dh-color-yellow-700: hsl(calc(var(--dh-color-yellow-hue) - 1deg) 70% 35%);
+ --dh-color-yellow-800: hsl(calc(var(--dh-color-yellow-hue) - 2deg) 66% 41%);
+ --dh-color-yellow-900: hsl(calc(var(--dh-color-yellow-hue) - 2deg) 61% 47%);
+ --dh-color-yellow-1000: hsl(calc(var(--dh-color-yellow-hue) - 2deg) 66% 54%);
+ --dh-color-yellow-1100: hsl(calc(var(--dh-color-yellow-hue) - 3deg) 79% 60%);
+ --dh-color-yellow-1200: hsl(calc(var(--dh-color-yellow-hue) - 3deg) 94% 66%);
+ --dh-color-yellow-1300: hsl(var(--dh-color-yellow-hue) 100% 74%);
+ --dh-color-yellow-1400: hsl(calc(var(--dh-color-yellow-hue) + 2deg) 100% 84%);
+
+ /* Chartreuse */
+ --dh-color-chartreuse-hue: 70deg;
+ --dh-color-chartreuse-100: hsl(
+ calc(var(--dh-color-chartreuse-hue) + 7deg) 100% 8%
+ );
+ --dh-color-chartreuse-200: hsl(
+ calc(var(--dh-color-chartreuse-hue) + 5deg) 84% 12%
+ );
+ --dh-color-chartreuse-300: hsl(
+ calc(var(--dh-color-chartreuse-hue) + 4deg) 73% 16%
+ );
+ --dh-color-chartreuse-400: hsl(
+ calc(var(--dh-color-chartreuse-hue) + 1deg) 67% 20%
+ );
+ --dh-color-chartreuse-500: hsl(
+ calc(var(--dh-color-chartreuse-hue) + 1deg) 62% 25%
+ );
+ --dh-color-chartreuse-600: hsl(var(--dh-color-chartreuse-hue) 59% 30%);
+ --dh-color-chartreuse-700: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 1deg) 56% 35%
+ );
+ --dh-color-chartreuse-800: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 2deg) 53% 40%
+ );
+ --dh-color-chartreuse-900: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 3deg) 50% 45%
+ );
+ --dh-color-chartreuse-1000: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 3deg) 49% 51%
+ );
+ --dh-color-chartreuse-1100: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 3deg) 58% 57%
+ );
+ --dh-color-chartreuse-1200: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 4deg) 67% 64%
+ );
+ --dh-color-chartreuse-1300: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 4deg) 76% 73%
+ );
+ --dh-color-chartreuse-1400: hsl(
+ calc(var(--dh-color-chartreuse-hue) - 4deg) 82% 83%
+ );
+
+ /* Celery */
+ --dh-color-celery-hue: 126deg;
+ --dh-color-celery-100: hsl(calc(var(--dh-color-celery-hue) + 1deg) 43% 12%);
+ --dh-color-celery-200: hsl(calc(var(--dh-color-celery-hue) - 1deg) 43% 16%);
+ --dh-color-celery-300: hsl(calc(var(--dh-color-celery-hue) - 1deg) 43% 21%);
+ --dh-color-celery-400: hsl(calc(var(--dh-color-celery-hue) - 1deg) 43% 25%);
+ --dh-color-celery-500: hsl(calc(var(--dh-color-celery-hue) - 1deg) 42% 30%);
+ --dh-color-celery-600: hsl(var(--dh-color-celery-hue) 41% 36%);
+ --dh-color-celery-700: hsl(calc(var(--dh-color-celery-hue) - 1deg) 39% 41%);
+ --dh-color-celery-800: hsl(calc(var(--dh-color-celery-hue) - 1deg) 37% 48%);
+ --dh-color-celery-900: hsl(calc(var(--dh-color-celery-hue) - 1deg) 39% 55%);
+ --dh-color-celery-1000: hsl(calc(var(--dh-color-celery-hue) - 1deg) 44% 63%);
+ --dh-color-celery-1100: hsl(calc(var(--dh-color-celery-hue) - 1deg) 48% 71%);
+ --dh-color-celery-1200: hsl(var(--dh-color-celery-hue) 50% 80%);
+ --dh-color-celery-1300: hsl(var(--dh-color-celery-hue) 48% 87%);
+ --dh-color-celery-1400: hsl(calc(var(--dh-color-celery-hue) + 1deg) 50% 93%);
+
+ /* Green */
+ --dh-color-green-hue: 94deg;
+ --dh-color-green-100: hsl(calc(var(--dh-color-green-hue) + 1deg) 17% 14%);
+ --dh-color-green-200: hsl(calc(var(--dh-color-green-hue) - 2deg) 19% 18%);
+ --dh-color-green-300: hsl(var(--dh-color-green-hue) 22% 23%);
+ --dh-color-green-400: hsl(var(--dh-color-green-hue) 23% 27%);
+ --dh-color-green-500: hsl(calc(var(--dh-color-green-hue) - 1deg) 26% 32%);
+ --dh-color-green-600: hsl(calc(var(--dh-color-green-hue) + 1deg) 28% 37%);
+ --dh-color-green-700: hsl(calc(var(--dh-color-green-hue) + 1deg) 30% 42%);
+ --dh-color-green-800: hsl(var(--dh-color-green-hue) 32% 48%);
+ --dh-color-green-900: hsl(var(--dh-color-green-hue) 38% 53%);
+ --dh-color-green-1000: hsl(var(--dh-color-green-hue) 47% 58%);
+ --dh-color-green-1100: hsl(var(--dh-color-green-hue) 59% 64%);
+ --dh-color-green-1200: hsl(var(--dh-color-green-hue) 71% 70%);
+ --dh-color-green-1300: hsl(var(--dh-color-green-hue) 81% 79%);
+ --dh-color-green-1400: hsl(calc(var(--dh-color-green-hue) - 1deg) 88% 87%);
+
+ /* Seafoam */
+ --dh-color-seafoam-hue: 159deg;
+ --dh-color-seafoam-100: hsl(calc(var(--dh-color-seafoam-hue) - 3deg) 14% 14%);
+ --dh-color-seafoam-200: hsl(calc(var(--dh-color-seafoam-hue) - 1deg) 17% 18%);
+ --dh-color-seafoam-300: hsl(var(--dh-color-seafoam-hue) 20% 23%);
+ --dh-color-seafoam-400: hsl(calc(var(--dh-color-seafoam-hue) - 1deg) 23% 28%);
+ --dh-color-seafoam-500: hsl(calc(var(--dh-color-seafoam-hue) - 1deg) 26% 33%);
+ --dh-color-seafoam-600: hsl(var(--dh-color-seafoam-hue) 29% 37%);
+ --dh-color-seafoam-700: hsl(calc(var(--dh-color-seafoam-hue) + 2deg) 33% 42%);
+ --dh-color-seafoam-800: hsl(calc(var(--dh-color-seafoam-hue) + 2deg) 36% 47%);
+ --dh-color-seafoam-900: hsl(calc(var(--dh-color-seafoam-hue) + 3deg) 42% 51%);
+ --dh-color-seafoam-1000: hsl(
+ calc(var(--dh-color-seafoam-hue) + 2deg) 53% 56%
+ );
+ --dh-color-seafoam-1100: hsl(
+ calc(var(--dh-color-seafoam-hue) + 3deg) 66% 62%
+ );
+ --dh-color-seafoam-1200: hsl(
+ calc(var(--dh-color-seafoam-hue) + 2deg) 76% 70%
+ );
+ --dh-color-seafoam-1300: hsl(
+ calc(var(--dh-color-seafoam-hue) - 1deg) 87% 80%
+ );
+ --dh-color-seafoam-1400: hsl(
+ calc(var(--dh-color-seafoam-hue) - 3deg) 89% 89%
+ );
+
+ /* Cyan */
+ --dh-color-cyan-hue: 186deg;
+ --dh-color-cyan-100: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 38% 13%);
+ --dh-color-cyan-200: hsl(var(--dh-color-cyan-hue) 38% 16%);
+ --dh-color-cyan-300: hsl(var(--dh-color-cyan-hue) 39% 21%);
+ --dh-color-cyan-400: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 38% 26%);
+ --dh-color-cyan-500: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 38% 31%);
+ --dh-color-cyan-600: hsl(var(--dh-color-cyan-hue) 38% 37%);
+ --dh-color-cyan-700: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 38% 42%);
+ --dh-color-cyan-800: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 37% 48%);
+ --dh-color-cyan-900: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 42% 54%);
+ --dh-color-cyan-1000: hsl(calc(var(--dh-color-cyan-hue) - 1deg) 52% 60%);
+ --dh-color-cyan-1100: hsl(var(--dh-color-cyan-hue) 64% 67%);
+ --dh-color-cyan-1200: hsl(var(--dh-color-cyan-hue) 79% 74%);
+ --dh-color-cyan-1300: hsl(var(--dh-color-cyan-hue) 92% 81%);
+ --dh-color-cyan-1400: hsl(var(--dh-color-cyan-hue) 100% 90%);
+
+ /* Indigo */
+ --dh-color-indigo-hue: 235deg;
+ --dh-color-indigo-100: hsl(calc(var(--dh-color-indigo-hue) + 2deg) 58% 24%);
+ --dh-color-indigo-200: hsl(calc(var(--dh-color-indigo-hue) + 1deg) 52% 30%);
+ --dh-color-indigo-300: hsl(calc(var(--dh-color-indigo-hue) + 1deg) 47% 37%);
+ --dh-color-indigo-400: hsl(var(--dh-color-indigo-hue) 43% 44%);
+ --dh-color-indigo-500: hsl(var(--dh-color-indigo-hue) 40% 51%);
+ --dh-color-indigo-600: hsl(var(--dh-color-indigo-hue) 49% 58%);
+ --dh-color-indigo-700: hsl(calc(var(--dh-color-indigo-hue) - 1deg) 60% 65%);
+ --dh-color-indigo-800: hsl(calc(var(--dh-color-indigo-hue) - 1deg) 75% 72%);
+ --dh-color-indigo-900: hsl(calc(var(--dh-color-indigo-hue) - 1deg) 91% 78%);
+ --dh-color-indigo-1000: hsl(calc(var(--dh-color-indigo-hue) - 1deg) 100% 83%);
+ --dh-color-indigo-1100: hsl(calc(var(--dh-color-indigo-hue) - 1deg) 100% 87%);
+ --dh-color-indigo-1200: hsl(var(--dh-color-indigo-hue) 100% 91%);
+ --dh-color-indigo-1300: hsl(calc(var(--dh-color-indigo-hue) - 3deg) 100% 94%);
+ --dh-color-indigo-1400: hsl(calc(var(--dh-color-indigo-hue) + 1deg) 100% 97%);
+
+ /* Purple */
+ --dh-color-purple-hue: 254deg;
+ --dh-color-purple-100: hsl(calc(var(--dh-color-purple-hue) + 9deg) 73% 23%);
+ --dh-color-purple-200: hsl(calc(var(--dh-color-purple-hue) + 6deg) 59% 30%);
+ --dh-color-purple-300: hsl(calc(var(--dh-color-purple-hue) + 4deg) 50% 37%);
+ --dh-color-purple-400: hsl(calc(var(--dh-color-purple-hue) + 3deg) 43% 44%);
+ --dh-color-purple-500: hsl(calc(var(--dh-color-purple-hue) + 1deg) 38% 50%);
+ --dh-color-purple-600: hsl(var(--dh-color-purple-hue) 45% 57%);
+ --dh-color-purple-700: hsl(calc(var(--dh-color-purple-hue) - 1deg) 54% 64%);
+ --dh-color-purple-800: hsl(calc(var(--dh-color-purple-hue) - 3deg) 64% 71%);
+ --dh-color-purple-900: hsl(calc(var(--dh-color-purple-hue) - 4deg) 79% 78%);
+ --dh-color-purple-1000: hsl(calc(var(--dh-color-purple-hue) - 5deg) 91% 83%);
+ --dh-color-purple-1100: hsl(calc(var(--dh-color-purple-hue) - 5deg) 97% 88%);
+ --dh-color-purple-1200: hsl(calc(var(--dh-color-purple-hue) - 4deg) 100% 91%);
+ --dh-color-purple-1300: hsl(calc(var(--dh-color-purple-hue) - 3deg) 100% 95%);
+ --dh-color-purple-1400: hsl(calc(var(--dh-color-purple-hue) + 6deg) 88% 97%);
+
+ /* Fuchsia */
+ --dh-color-fuchsia-hue: 286deg;
+ --dh-color-fuchsia-100: hsl(
+ calc(var(--dh-color-fuchsia-hue) + 15deg) 64% 16%
+ );
+ --dh-color-fuchsia-200: hsl(
+ calc(var(--dh-color-fuchsia-hue) + 14deg) 53% 22%
+ );
+ --dh-color-fuchsia-300: hsl(
+ calc(var(--dh-color-fuchsia-hue) + 11deg) 44% 29%
+ );
+ --dh-color-fuchsia-400: hsl(calc(var(--dh-color-fuchsia-hue) + 8deg) 39% 35%);
+ --dh-color-fuchsia-500: hsl(calc(var(--dh-color-fuchsia-hue) + 5deg) 35% 43%);
+ --dh-color-fuchsia-600: hsl(calc(var(--dh-color-fuchsia-hue) + 1deg) 33% 51%);
+ --dh-color-fuchsia-700: hsl(calc(var(--dh-color-fuchsia-hue) - 3deg) 40% 58%);
+ --dh-color-fuchsia-800: hsl(calc(var(--dh-color-fuchsia-hue) - 7deg) 50% 66%);
+ --dh-color-fuchsia-900: hsl(calc(var(--dh-color-fuchsia-hue) - 9deg) 61% 74%);
+ --dh-color-fuchsia-1000: hsl(
+ calc(var(--dh-color-fuchsia-hue) - 11deg) 71% 80%
+ );
+ --dh-color-fuchsia-1100: hsl(
+ calc(var(--dh-color-fuchsia-hue) - 11deg) 76% 85%
+ );
+ --dh-color-fuchsia-1200: hsl(
+ calc(var(--dh-color-fuchsia-hue) - 9deg) 78% 89%
+ );
+ --dh-color-fuchsia-1300: hsl(
+ calc(var(--dh-color-fuchsia-hue) - 6deg) 77% 93%
+ );
+ --dh-color-fuchsia-1400: hsl(
+ calc(var(--dh-color-fuchsia-hue) + 2deg) 71% 96%
+ );
+
+ /* Magenta */
+ --dh-color-magenta-hue: 330deg;
+ --dh-color-magenta-100: hsl(calc(var(--dh-color-magenta-hue) + 3deg) 91% 17%);
+ --dh-color-magenta-200: hsl(calc(var(--dh-color-magenta-hue) + 4deg) 72% 23%);
+ --dh-color-magenta-300: hsl(calc(var(--dh-color-magenta-hue) + 4deg) 60% 30%);
+ --dh-color-magenta-400: hsl(calc(var(--dh-color-magenta-hue) + 4deg) 52% 36%);
+ --dh-color-magenta-500: hsl(calc(var(--dh-color-magenta-hue) + 3deg) 46% 43%);
+ --dh-color-magenta-600: hsl(calc(var(--dh-color-magenta-hue) + 2deg) 42% 50%);
+ --dh-color-magenta-700: hsl(var(--dh-color-magenta-hue) 51% 58%);
+ --dh-color-magenta-800: hsl(calc(var(--dh-color-magenta-hue) - 1deg) 61% 65%);
+ --dh-color-magenta-900: hsl(calc(var(--dh-color-magenta-hue) - 1deg) 76% 72%);
+ --dh-color-magenta-1000: hsl(
+ calc(var(--dh-color-magenta-hue) - 2deg) 92% 79%
+ );
+ --dh-color-magenta-1100: hsl(
+ calc(var(--dh-color-magenta-hue) - 3deg) 100% 85%
+ );
+ --dh-color-magenta-1200: hsl(
+ calc(var(--dh-color-magenta-hue) - 3deg) 100% 89%
+ );
+ --dh-color-magenta-1300: hsl(
+ calc(var(--dh-color-magenta-hue) - 4deg) 100% 93%
+ );
+ --dh-color-magenta-1400: hsl(
+ calc(var(--dh-color-magenta-hue) - 2deg) 100% 96%
+ );
+}
diff --git a/packages/components/src/theme/theme-dark/theme-dark-semantic-editor.css b/packages/components/src/theme/theme-dark/theme-dark-semantic-editor.css
new file mode 100644
index 0000000000..41ee75dcc5
--- /dev/null
+++ b/packages/components/src/theme/theme-dark/theme-dark-semantic-editor.css
@@ -0,0 +1,67 @@
+:root {
+ /* Editor */
+ --dh-color-editor-background: var(--dh-color-content-background);
+ --dh-color-editor-foreground: var(--dh-color-gray-900);
+ --dh-color-editor-error-foreground: var(--dh-color-visual-red);
+ --dh-color-editor-line-number-foreground: var(--dh-color-gray-700);
+ --dh-color-editor-line-highlight-bg: var(--dh-color-gray-200);
+ --dh-color-editor-selection-background: var(--dh-color-text-highlight);
+
+ /* Code rules */
+ --dh-color-editor-string: var(--dh-color-visual-yellow);
+ --dh-color-editor-string-delim: var(--dh-color-gray-700);
+ --dh-color-editor-delimiter: var(--dh-color-gray-700);
+ --dh-color-editor-predefined: var(--dh-color-visual-green);
+ --dh-color-editor-keyword: var(--dh-color-visual-cyan);
+ --dh-color-editor-storage: var(--dh-color-visual-red);
+ --dh-color-editor-number: var(--dh-color-visual-purple);
+ --dh-color-editor-operator: var(--dh-color-visual-red);
+ --dh-color-editor-identifier: var(--dh-color-gray-900);
+ --dh-color-editor-identifier-namespace: var(--dh-color-visual-red);
+ --dh-color-editor-identifier-js: var(--dh-color-visual-yellow);
+ --dh-color-editor-comment: var(--dh-color-gray-700);
+
+ /* Input */
+ --dh-color-editor-focus-border: var(--dh-color-focus-border);
+ --dh-color-editor-input-option-active-border: var(--dh-color-focus-ring);
+ --dh-color-editor-input-background: var(--dh-color-background);
+ --dh-color-editor-input-foreground: var(--dh-color-text);
+ --dh-color-editor-input-border: var(--dh-color-border);
+
+ /* Menus */
+ --dh-color-editor-context-menu-background: var(--dh-color-gray-300);
+ --dh-color-editor-context-menu-foreground: var(--dh-color-gray-900);
+ --dh-color-editor-menu-selection-background: var(--dh-color-highlight-hover);
+
+ /* Logging */
+ --dh-color-editor-log-date: var(--dh-color-gray-700);
+ --dh-color-editor-log-error: var(--dh-color-visual-red);
+ --dh-color-editor-log-info: var(--dh-color-visual-cyan);
+ --dh-color-editor-log-stdout: var(--dh-color-gray-900);
+ --dh-color-editor-log-warn: var(--dh-color-visual-yellow);
+ --dh-color-editor-log-debug: var(--dh-color-visual-purple);
+ --dh-color-editor-log-trace: var(--dh-color-visual-green);
+
+ /* Find */
+ --dh-color-editor-find-background: var(--dh-color-gray-200);
+ --dh-color-editor-find-match-background: var(--dh-color-highlight-selected);
+ --dh-color-editor-find-match-highlight-background: var(
+ --dh-color-highlight-selected-hover
+ );
+ --dh-color-editor-find-option-active-background: var(--dh-color-accent-700);
+ --dh-color-editor-find-option-active-foreground: var(--dh-color-gray-900);
+
+ /* Suggest */
+ --dh-color-editor-suggest-background: var(--dh-color-gray-200);
+ --dh-color-editor-suggest-border: var(--dh-color-gray-400);
+ --dh-color-editor-suggest-foreground: var(--dh-color-gray-100);
+ --dh-color-editor-suggest-selected-background: var(
+ --dh-color-highlight-selected
+ );
+ --dh-color-editor-suggest-highlight-foreground: var(--dh-color-accent-700);
+ --dh-color-editor-suggest-hover-background: var(--dh-color-highlight-hover);
+
+ /* Links */
+ --dh-color-editor-link-foreground: var(--dh-color-accent-1000);
+ --dh-color-editor-link-active-foreground: var(--dh-color-accent-1100);
+}
diff --git a/packages/components/src/theme/theme-dark/theme-dark-semantic-grid.css b/packages/components/src/theme/theme-dark/theme-dark-semantic-grid.css
new file mode 100644
index 0000000000..20cdf034de
--- /dev/null
+++ b/packages/components/src/theme/theme-dark/theme-dark-semantic-grid.css
@@ -0,0 +1,3 @@
+:root {
+ --dh-color-grid-background: var(--dh-color-background);
+}
diff --git a/packages/components/src/theme/theme-dark/theme-dark-semantic.css b/packages/components/src/theme/theme-dark/theme-dark-semantic.css
new file mode 100644
index 0000000000..34bea973d8
--- /dev/null
+++ b/packages/components/src/theme/theme-dark/theme-dark-semantic.css
@@ -0,0 +1,74 @@
+/* stylelint-disable alpha-value-notation */
+:root {
+ /* General */
+ --dh-color-accent: var(--dh-color-blue-700);
+ --dh-color-border: var(--dh-color-gray-500);
+ --dh-color-background: var(--dh-color-black);
+ --dh-color-foreground: var(--dh-color-white);
+ --dh-color-content-background: var(--dh-color-gray-100);
+
+ /* Text */
+ --dh-color-text: var(--dh-color-gray-800);
+ --dh-color-text-highlight: hsla(var(--dh-color-blue-hue), 83%, 62%, 0.3);
+
+ /* Focus */
+ --dh-color-focus: var(--dh-color-blue-800);
+ --dh-color-focus-border: var(--dh-color-blue-800);
+ --dh-color-focus-ring: var(--dh-color-focus);
+
+ /* Highlight */
+ --dh-color-highlight-active: hsla(var(--dh-color-gray-hue), 0%, 94%, 0.15);
+ --dh-color-highlight-hover: hsla(var(--dh-color-gray-hue), 0%, 100%, 0.08);
+ --dh-color-highlight-invalid: hsla(var(--dh-color-red-hue), 80%, 48%, 0.15);
+ --dh-color-highlight-selected: hsla(var(--dh-color-blue-hue), 83%, 62%, 0.13);
+ --dh-color-highlight-selected-hover: hsla(
+ var(--dh-color-blue-hue),
+ 83%,
+ 62%,
+ 0.2
+ );
+
+ /* Visual Colors */
+ --dh-color-visual-blue: var(--dh-color-blue-700);
+ --dh-color-visual-celery: var(--dh-color-celery-1000);
+ --dh-color-visual-chartreuse: var(--dh-color-chartreuse-1100);
+ --dh-color-visual-cyan: var(--dh-color-cyan-1100);
+ --dh-color-visual-fuchsia: var(--dh-color-fuchsia-900);
+ --dh-color-visual-gray: var(--dh-color-gray-600);
+ --dh-color-visual-green: var(--dh-color-green-1100);
+ --dh-color-visual-indigo: var(--dh-color-indigo-900);
+ --dh-color-visual-magenta: var(--dh-color-magenta-900);
+ --dh-color-visual-orange: var(--dh-color-orange-900);
+ --dh-color-visual-purple: var(--dh-color-purple-900);
+ --dh-color-visual-red: var(--dh-color-red-800);
+ --dh-color-visual-seafoam: var(--dh-color-seafoam-1100);
+ --dh-color-visual-yellow: var(--dh-color-yellow-1200);
+
+ /** Accent Colors */
+ --dh-color-accent-100: var(--dh-color-blue-100);
+ --dh-color-accent-200: var(--dh-color-blue-200);
+ --dh-color-accent-300: var(--dh-color-blue-300);
+ --dh-color-accent-400: var(--dh-color-blue-400);
+ --dh-color-accent-500: var(--dh-color-blue-500);
+ --dh-color-accent-600: var(--dh-color-blue-600);
+ --dh-color-accent-700: var(--dh-color-blue-700);
+ --dh-color-accent-800: var(--dh-color-blue-800);
+ --dh-color-accent-900: var(--dh-color-blue-900);
+ --dh-color-accent-1000: var(--dh-color-blue-1000);
+ --dh-color-accent-1100: var(--dh-color-blue-1100);
+ --dh-color-accent-1200: var(--dh-color-blue-1200);
+ --dh-color-accent-1300: var(--dh-color-blue-1300);
+ --dh-color-accent-1400: var(--dh-color-blue-1400);
+
+ /* Accent Background */
+ --dh-color-accent-background-default: var(--dh-color-accent-600);
+ --dh-color-accent-background-hover: var(--dh-color-accent-500);
+ --dh-color-accent-background-down: var(--dh-color-accent-400);
+ --dh-color-accent-background-key-focus: var(--dh-color-accent-500);
+
+ /* Negative Background */
+ --dh-color-negative-background-default: var(--dh-color-red-600);
+ --dh-color-negative-background-hover: var(--dh-color-red-500);
+ --dh-color-negative-background-down: var(--dh-color-red-400);
+ --dh-color-negative-background-key-focus: var(--dh-color-red-500);
+}
diff --git a/packages/components/src/theme/theme-light/index.ts b/packages/components/src/theme/theme-light/index.ts
new file mode 100644
index 0000000000..b6c7d42258
--- /dev/null
+++ b/packages/components/src/theme/theme-light/index.ts
@@ -0,0 +1,5 @@
+import themeLightPalette from './theme-light-palette.css?inline';
+
+export const themeLight = themeLightPalette;
+
+export default themeLight;
diff --git a/packages/components/src/theme/theme_default_light.css b/packages/components/src/theme/theme-light/theme-light-palette.css
similarity index 87%
rename from packages/components/src/theme/theme_default_light.css
rename to packages/components/src/theme/theme-light/theme-light-palette.css
index 20fb5c30f2..ce7c31acc7 100644
--- a/packages/components/src/theme/theme_default_light.css
+++ b/packages/components/src/theme/theme-light/theme-light-palette.css
@@ -47,8 +47,8 @@
/* Semantic */
--dh-color-black: var(--dh-color-gray-50);
--dh-color-white: var(--dh-color-gray-75);
- --dh-accent-color: var(--dh-color-blue-700);
- --dh-background-color: var(--dh-color-white);
- --dh-foreground-color: var(--dh-color-black);
- --dh-grid-background-color: var(--dh-background-color);
+ --dh-color-accent: var(--dh-color-blue-700);
+ --dh-color-background: var(--dh-color-white);
+ --dh-color-foreground: var(--dh-color-black);
+ --dh-color-grid-background: var(--dh-color-background);
}
diff --git a/packages/components/src/theme/theme_default_dark.css b/packages/components/src/theme/theme_default_dark.css
deleted file mode 100644
index 7e2c6d7cfc..0000000000
--- a/packages/components/src/theme/theme_default_dark.css
+++ /dev/null
@@ -1,54 +0,0 @@
-:root {
- /* Grays */
- --dh-color-gray-900: #fcfcfa;
- --dh-color-gray-800: #f0f0ee;
- --dh-color-gray-700: #c0bfbf;
- --dh-color-gray-600: #929192;
- --dh-color-gray-500: #5b5a5c;
- --dh-color-gray-400: #403e41;
- --dh-color-gray-300: #373438;
- --dh-color-gray-200: #322f33;
- --dh-color-gray-100: #2d2a2e;
- --dh-color-gray-75: #211f22;
- --dh-color-gray-50: #1a171a;
-
- /* Blues */
- --dh-color-blue-100: #112451;
- --dh-color-blue-200: #16306c;
- --dh-color-blue-300: #1d3d88;
- --dh-color-blue-400: #254ba4;
- --dh-color-blue-500: #2f5bc0;
- --dh-color-blue-600: #3b6bda;
- --dh-color-blue-700: #4c7dee;
- --dh-color-blue-800: #6390fa;
- --dh-color-blue-900: #7ca4ff;
- --dh-color-blue-1000: #97b7ff;
- --dh-color-blue-1100: #afc9ff;
- --dh-color-blue-1200: #c7d9ff;
- --dh-color-blue-1300: #dbe6ff;
- --dh-color-blue-1400: #ecf1ff;
-
- /* Seafoam */
- --dh-color-seafoam-100: #1f2925;
- --dh-color-seafoam-200: #263630;
- --dh-color-seafoam-300: #2f463e;
- --dh-color-seafoam-400: #37574b;
- --dh-color-seafoam-500: #3e6959;
- --dh-color-seafoam-600: #447b68;
- --dh-color-seafoam-700: #488f78;
- --dh-color-seafoam-800: #4ca387;
- --dh-color-seafoam-900: #4fb797;
- --dh-color-seafoam-1000: #54cba6;
- --dh-color-seafoam-1100: #5edeb7;
- --dh-color-seafoam-1200: #78edc7;
- --dh-color-seafoam-1300: #9ef8d7;
- --dh-color-seafoam-1400: #cbfce8;
-
- /* Semantic */
- --dh-color-black: var(--dh-color-gray-50);
- --dh-color-white: var(--dh-color-gray-800);
- --dh-accent-color: var(--dh-color-blue-700);
- --dh-background-color: var(--dh-color-black);
- --dh-foreground-color: var(--dh-color-white);
- --dh-grid-background-color: var(--dh-background-color);
-}
diff --git a/packages/console/src/monaco/MonacoTheme.module.scss b/packages/console/src/monaco/MonacoTheme.module.scss
index 24d242ebf0..7185cb8386 100644
--- a/packages/console/src/monaco/MonacoTheme.module.scss
+++ b/packages/console/src/monaco/MonacoTheme.module.scss
@@ -3,71 +3,80 @@
:export {
// iris dark theme
- error-foreground: $red;
- background: $content-bg;
- foreground: $gray-100;
+ error-foreground: var(--dh-color-editor-error-foreground);
+ background: var(--dh-color-editor-background);
+ foreground: var(--dh-color-editor-foreground);
line-height: 19px; // 19 is the line height in the default monaco theme
//code rules
- //more nuanced grays required in a few spots
- string: $yellow;
- string-delim: $gray-400;
- delimiter: mix($gray-400, $gray-300, 30%);
- predefined: $green;
- keyword: $blue;
- storage: $red;
- number: $purple;
- operator: $red;
- identifier: $gray-100;
- namespace-identifier: $red;
- identifier-js: $yellow;
- comment: mix($gray-500, $gray-400, 40%);
+ string: var(--dh-color-editor-string);
+ string-delim: var(--dh-color-editor-string-delim);
+ delimiter: var(--dh-color-editor-delimiter);
+ predefined: var(--dh-color-editor-predefined);
+ keyword: var(--dh-color-editor-keyword);
+ storage: var(--dh-color-editor-storage);
+ number: var(--dh-color-editor-number);
+ operator: var(--dh-color-editor-operator);
+ identifier: var(--dh-color-editor-identifier);
+ namespace-identifier: var(--dh-color-editor-identifier-namespace);
+ identifier-js: var(--dh-color-editor-identifier-js);
+ comment: var(--dh-color-editor-comment);
//input
- input-option-active-border: $input-border-color;
- focus-border: $input-focus-border-color;
- input-background: $input-bg;
- input-foreground: $input-color;
- input-border: $input-border-color;
+ input-option-active-border: var(--dh-color-editor-input-option-active-border);
+ focus-border: var(--dh-color-editor-focus-border);
+ input-background: var(--dh-color-editor-input-background);
+ input-foreground: var(--dh-color-editor-input-foreground);
+ input-border: var(--dh-color-editor-input-border);
//editor
- editor-line-number-foreground: $gray-400;
- editor-selection-background: $text-select-color-editor;
- editor-line-highlight-bg: $gray-800;
+ editor-line-number-foreground: var(--dh-color-editor-line-number-foreground);
+ editor-selection-background: var(--dh-color-editor-selection-background);
+ editor-line-highlight-bg: var(--dh-color-editor-line-highlight-bg);
//context menu
- context-menu-background: $contextmenu-bg;
- context-menu-foreground: $contextmenu-color;
- menu-selection-background: $contextmenu-selected-bg;
+ context-menu-background: var(--dh-color-editor-context-menu-background);
+ context-menu-foreground: var(--dh-color-editor-context-menu-foreground);
+ menu-selection-background: var(--dh-color-editor-menu-selection-background);
//log items
- log-date: $gray-400;
- log-error: $danger;
- log-info: $blue;
- log-stdout: $foreground;
- log-warn: $yellow;
- log-debug: $purple;
- log-trace: $green;
+ log-date: var(--dh-color-editor-log-date);
+ log-error: var(--dh-color-editor-log-error);
+ log-info: var(--dh-color-editor-log-info);
+ log-stdout: var(--dh-color-editor-log-stdout);
+ log-warn: var(--dh-color-editor-log-warn);
+ log-debug: var(--dh-color-editor-log-debug);
+ log-trace: var(--dh-color-editor-log-trace);
// find matches
- editor-find-match-background: mix($primary, $content-bg, 55%);
- editor-find-match-highlight-background: mix($primary, $content-bg, 25%);
+ editor-find-match-background: var(--dh-color-editor-find-match-background);
+ editor-find-match-highlight-background: var(
+ --dh-color-editor-find-match-highlight-background
+ );
// find widget
- editor-widget-background: $gray-700;
- input-option-active-background: $primary;
- input-option-active-foreground: $foreground;
+ editor-widget-background: var(--dh-color-editor-find-background);
+ input-option-active-background: var(
+ --dh-color-editor-find-option-active-background
+ );
+ input-option-active-foreground: var(
+ --dh-color-editor-find-option-active-foreground
+ );
// suggest widget
- editor-suggest-widget-background: $gray-700;
- editor-suggest-widget-border: $gray-500;
- editor-suggest-widget-foreground: $white;
- editor-suggest-widget-selected-background: mix($primary, $gray-700, 15%);
- editor-suggest-widget-highlightForeground: $primary;
- list-hover-background: $gray-600;
+ editor-suggest-widget-background: var(--dh-color-editor-suggest-background);
+ editor-suggest-widget-border: var(--dh-color-editor-suggest-border);
+ editor-suggest-widget-foreground: var(--dh-color-editor-suggest-foreground);
+ editor-suggest-widget-selected-background: var(
+ --dh-color-editor-suggest-selected-background
+ );
+ editor-suggest-widget-highlightForeground: var(
+ --dh-color-editor-suggest-highlight-foreground
+ );
+ list-hover-background: var(--dh-color-editor-suggest-hover-background);
// links
- text-link-foreground: $link-color;
- text-link-active-foreground: $link-hover-color;
- editor-link-active-foreground: $link-hover-color;
+ text-link-foreground: var(--dh-color-editor-link-foreground);
+ text-link-active-foreground: var(--dh-color-editor-link-active-foreground);
+ editor-link-active-foreground: var(--dh-color-editor-link-active-foreground);
}
diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts
index 26a9fb54ee..49f39e2127 100644
--- a/packages/console/src/monaco/MonacoUtils.ts
+++ b/packages/console/src/monaco/MonacoUtils.ts
@@ -3,7 +3,7 @@ import shortid from 'shortid';
/**
* Exports a function for initializing monaco with the deephaven theme/config
*/
-import { Shortcut } from '@deephaven/components';
+import { resolveCssVariablesInRecord, Shortcut } from '@deephaven/components';
import type { IdeSession } from '@deephaven/jsapi-types';
import { assertNotNull } from '@deephaven/utils';
import { find as linkifyFind } from 'linkifyjs';
@@ -12,7 +12,7 @@ import type { Environment } from 'monaco-editor';
// @ts-ignore
import { KeyCodeUtils } from 'monaco-editor/esm/vs/base/common/keyCodes.js';
import Log from '@deephaven/log';
-import MonacoTheme from './MonacoTheme.module.scss';
+import MonacoThemeRaw from './MonacoTheme.module.scss';
import PyLang from './lang/python';
import GroovyLang from './lang/groovy';
import ScalaLang from './lang/scala';
@@ -45,6 +45,10 @@ class MonacoUtils {
const { registerLanguages, removeHashtag } = MonacoUtils;
+ const MonacoTheme = resolveCssVariablesInRecord(MonacoThemeRaw);
+ log.debug2('Monaco theme:', MonacoThemeRaw);
+ log.debug2('Monaco theme derived:', MonacoTheme);
+
const dhDarkRules = [
{ token: '', foreground: removeHashtag(MonacoTheme.foreground) },
{ token: 'string', foreground: removeHashtag(MonacoTheme.string) },
@@ -154,7 +158,7 @@ class MonacoUtils {
rules: dhDarkRules,
colors: dhDarkColors,
});
- log.debug2('monaco theme: ', MonacoTheme);
+
monaco.editor.setTheme('dh-dark');
registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]);
diff --git a/packages/golden-layout/scss/goldenlayout-dark-theme.scss b/packages/golden-layout/scss/goldenlayout-dark-theme.scss
index d80d933f19..8bf2c972a7 100644
--- a/packages/golden-layout/scss/goldenlayout-dark-theme.scss
+++ b/packages/golden-layout/scss/goldenlayout-dark-theme.scss
@@ -63,7 +63,7 @@ body:not(.lm_dragging) .lm_header .lm_tab .lm_close_tab:hover {
// Entire GoldenLayout Container, if a background is set, it is visible as color of "pane header" and "splitters" (if these latest has opacity very low)
.lm_goldenlayout {
- background: var(--dh-background-color, $background);
+ background: var(--dh-color-background, $background);
position: absolute;
}
diff --git a/packages/utils/src/ColorUtils.test.ts b/packages/utils/src/ColorUtils.test.ts
index 3616722903..5cc3166e82 100644
--- a/packages/utils/src/ColorUtils.test.ts
+++ b/packages/utils/src/ColorUtils.test.ts
@@ -1,4 +1,104 @@
import ColorUtils from './ColorUtils';
+import TestUtils from './TestUtils';
+
+const { createMockProxy } = TestUtils;
+
+const getBackgroundColor = jest.fn();
+const setBackgroundColor = jest.fn();
+
+const mockDivEl = createMockProxy({
+ style: {
+ get backgroundColor(): string {
+ return getBackgroundColor();
+ },
+ set backgroundColor(value: string) {
+ setBackgroundColor(value);
+ },
+ } as HTMLDivElement['style'],
+});
+
+const colorMap = [
+ {
+ rgb: { r: 255, g: 0, b: 0 },
+ hex: '#ff0000ff',
+ },
+ {
+ rgb: { r: 255, g: 128, b: 0 },
+ hex: '#ff8000ff',
+ },
+ {
+ rgb: { r: 255, g: 255, b: 0 },
+ hex: '#ffff00ff',
+ },
+ {
+ rgb: { r: 128, g: 255, b: 0 },
+ hex: '#80ff00ff',
+ },
+ {
+ rgb: { r: 0, g: 255, b: 0 },
+ hex: '#00ff00ff',
+ },
+ {
+ rgb: { r: 0, g: 255, b: 128 },
+ hex: '#00ff80ff',
+ },
+ {
+ rgb: { r: 0, g: 255, b: 255 },
+ hex: '#00ffffff',
+ },
+ {
+ rgb: { r: 0, g: 128, b: 255 },
+ hex: '#0080ffff',
+ },
+ {
+ rgb: { r: 0, g: 0, b: 255 },
+ hex: '#0000ffff',
+ },
+ {
+ rgb: { r: 128, g: 0, b: 255 },
+ hex: '#8000ffff',
+ },
+ {
+ rgb: { r: 255, g: 0, b: 255 },
+ hex: '#ff00ffff',
+ },
+ {
+ rgb: { r: 255, g: 0, b: 128 },
+ hex: '#ff0080ff',
+ },
+];
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ expect.hasAssertions();
+
+ getBackgroundColor.mockName('getBackgroundColor');
+ setBackgroundColor.mockName('setBackgroundColor');
+});
+
+describe('asRgbOrRgbaString', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(document, 'createElement')
+ .mockName('createElement')
+ .mockReturnValue(mockDivEl);
+ });
+
+ it('should return resolved backgroundColor value', () => {
+ getBackgroundColor.mockReturnValue('get backgroundColor');
+
+ const actual = ColorUtils.asRgbOrRgbaString('red');
+ expect(actual).toEqual('get backgroundColor');
+ });
+
+ it('should return null if backgroundColor resolves to empty string', () => {
+ getBackgroundColor.mockReturnValue('');
+
+ const actual = ColorUtils.asRgbOrRgbaString('red');
+ expect(actual).toBeNull();
+ });
+});
describe('isDark', () => {
it('returns true if the background is dark', () => {
@@ -21,3 +121,83 @@ describe('isDark', () => {
expect(() => ColorUtils.isDark('')).toThrowError(/Invalid color received/);
});
});
+
+describe('normalizeCssColor', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(document, 'createElement')
+ .mockName('createElement')
+ .mockReturnValue(mockDivEl);
+ });
+
+ it.each([
+ 'rgb(0, 128, 255)',
+ 'rgba(0, 128, 255, 64)',
+ 'rgb(0 128 255)',
+ 'rgba(0 128 255 64)',
+ ])(
+ 'should normalize a resolved rgb/a color to 8 character hex value',
+ rgbOrRgbaColor => {
+ getBackgroundColor.mockReturnValue(rgbOrRgbaColor);
+
+ const actual = ColorUtils.normalizeCssColor('some.color');
+ expect(actual).toEqual(
+ ColorUtils.rgbaToHex8(ColorUtils.parseRgba(rgbOrRgbaColor)!)
+ );
+ }
+ );
+
+ it('should return original color if backgroundColor resolves to empty string', () => {
+ getBackgroundColor.mockReturnValue('');
+
+ const actual = ColorUtils.normalizeCssColor('red');
+ expect(actual).toEqual('red');
+ });
+
+ it('should return original color if backgroundColor resolves to non rgb/a', () => {
+ getBackgroundColor.mockReturnValue('xxx');
+
+ const actual = ColorUtils.normalizeCssColor('red');
+ expect(actual).toEqual('red');
+ });
+});
+
+describe('parseRgba', () => {
+ it.each([
+ ['rgb(255, 255, 255)', { r: 255, g: 255, b: 255, a: 1 }],
+ ['rgb(0,0,0)', { r: 0, g: 0, b: 0, a: 1 }],
+ ['rgb(255 255 255)', { r: 255, g: 255, b: 255, a: 1 }],
+ ['rgb(0 0 0)', { r: 0, g: 0, b: 0, a: 1 }],
+ ['rgb(0 128 255)', { r: 0, g: 128, b: 255, a: 1 }],
+ ['rgb(0 128 255 / .5)', { r: 0, g: 128, b: 255, a: 0.5 }],
+ ])('should parse rgb: %s, %s', (rgb, hex) => {
+ expect(ColorUtils.parseRgba(rgb)).toEqual(hex);
+ });
+
+ it.each([
+ ['rgba(255, 255, 255, 1)', { r: 255, g: 255, b: 255, a: 1 }],
+ ['rgba(0,0,0,0)', { r: 0, g: 0, b: 0, a: 0 }],
+ ['rgba(255 255 255 1)', { r: 255, g: 255, b: 255, a: 1 }],
+ ['rgba(0 0 0 0)', { r: 0, g: 0, b: 0, a: 0 }],
+ ['rgba(0 128 255 .5)', { r: 0, g: 128, b: 255, a: 0.5 }],
+ ])('should parse rgba: %s, %s', (rgba, hex) => {
+ expect(ColorUtils.parseRgba(rgba)).toEqual(hex);
+ });
+
+ it('should return null if not rgb or rgba', () => {
+ expect(ColorUtils.parseRgba('xxx')).toBeNull();
+ });
+
+ it.each(['rgb(0 128)', 'rgba(0 128)', 'rgb(0, 128)', 'rgba(0, 128)'])(
+ 'should return null if given < 3 args',
+ value => {
+ expect(ColorUtils.parseRgba(value)).toBeNull();
+ }
+ );
+});
+
+describe('rgbaToHex8', () => {
+ it.each(colorMap)('should convert rgb to hex: %s, %s', ({ rgb, hex }) => {
+ expect(ColorUtils.rgbaToHex8(rgb)).toEqual(hex);
+ });
+});
diff --git a/packages/utils/src/ColorUtils.ts b/packages/utils/src/ColorUtils.ts
index 6531fedec0..560b9fd6aa 100644
--- a/packages/utils/src/ColorUtils.ts
+++ b/packages/utils/src/ColorUtils.ts
@@ -1,4 +1,20 @@
class ColorUtils {
+ /**
+ * Attempt to get the rgb or rgba string for a color string. If the color string
+ * can't be resolved to a valid color, null is returned.
+ * @param colorString The color string to resolve
+ */
+ static asRgbOrRgbaString(colorString: string): string | null {
+ const divEl = document.createElement('div');
+ divEl.style.backgroundColor = colorString;
+
+ if (divEl.style.backgroundColor === '') {
+ return null;
+ }
+
+ return divEl.style.backgroundColor;
+ }
+
/**
* THIS HAS POOR PERFORMANCE DUE TO DOM MANIPULATION
* DO NOT USE HEAVILY
@@ -32,5 +48,93 @@ class ColorUtils {
(color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000
);
}
+
+ /**
+ * Normalize a css color to 8 character hex value. If the color can't be resolved,
+ * return the original color string.
+ * @param colorString The color string to normalize
+ */
+ static normalizeCssColor(colorString: string): string {
+ const maybeRgbOrRgba = ColorUtils.asRgbOrRgbaString(colorString);
+ if (maybeRgbOrRgba == null) {
+ return colorString;
+ }
+
+ const rgba = ColorUtils.parseRgba(maybeRgbOrRgba);
+ if (rgba === null) {
+ return colorString;
+ }
+
+ return ColorUtils.rgbaToHex8(rgba);
+ }
+
+ /**
+ * Parse a given `rgb` or `rgba` css expression into its constituent r, g, b, a
+ * values. If the expression cannot be parsed, it will return null.
+ * Note that this parser is more permissive than the CSS spec and shouldn't be
+ * relied on as a full validation mechanism. For the most part, it assumes that
+ * the input is already a valid rgb or rgba expression.
+ *
+ * e.g. `rgb(255, 255, 255)` -> `{ r: 255, g: 255, b: 255, a: 1 }`
+ * e.g. `rgba(255, 255, 255, 0.5)` -> `{ r: 255, g: 255, b: 255, a: 0.5 }`
+ * @param rgbOrRgbaString The rgb or rgba string to parse
+ */
+ static parseRgba(
+ rgbOrRgbaString: string
+ ): { r: number; g: number; b: number; a: number } | null {
+ const [, name, args] = /^(rgba?)\((.*?)\)$/.exec(rgbOrRgbaString) ?? [];
+ if (name == null) {
+ return null;
+ }
+
+ // Split on spaces, commas, and slashes. Note that this more permissive than
+ // the CSS spec in that slashes should only be used to delimit the alpha value
+ // (e.g. r g b / a), but this would match r/g/b/a. It also would match a mixed
+ // delimiter case (e.g. r,g b,a). This seems like a reasonable tradeoff for the
+ // complexity that would be added to enforce the full spec.
+ const tokens = args.split(/[ ,/]/).filter(Boolean);
+
+ if (tokens.length < 3) {
+ return null;
+ }
+
+ const [r, g, b, a = 1] = tokens.map(Number);
+
+ return {
+ r,
+ g,
+ b,
+ a,
+ };
+ }
+
+ /**
+ * Convert an rgba object to an 8 character hex color string.
+ * @param r The red value
+ * @param g The green value
+ * @param b The blue value
+ * @param a The alpha value (defaults to 1)
+ * @returns The a character hex string with # prefix
+ */
+ static rgbaToHex8({
+ r,
+ g,
+ b,
+ a = 1,
+ }: {
+ r: number;
+ g: number;
+ b: number;
+ a?: number;
+ }): string {
+ // eslint-disable-next-line no-param-reassign
+ a = Math.round(a * 255);
+
+ const [rh, gh, bh, ah] = [r, g, b, a].map(v =>
+ v.toString(16).padStart(2, '0')
+ );
+
+ return `#${rh}${gh}${bh}${ah}`;
+ }
}
export default ColorUtils;