blob: cd825b3d538c5ec431d0691e832ee86aee65538d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { ReactNode, useEffect, useMemo, useState } from 'react';
import { ConfigProvider } from 'antd';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
import { useAppSelector } from '@/hooks';
import { selectThemeMode, resolveThemeMode } from '@/features/theme/slice';
import { getTheme, ResolvedTheme } from './tokens';
interface Props {
children: ReactNode;
}
const CSS_VAR_MAP: Record<string, keyof ReturnType<typeof getTheme>['colors']> = {
'--devlake-color-text': 'text',
'--devlake-color-text-muted': 'textTertiary',
'--devlake-color-text-body': 'textBody',
'--devlake-color-text-subdued': 'textMuted',
'--devlake-color-bg': 'bgContainer',
'--devlake-color-bg-elevated': 'bgElevated',
'--devlake-color-border': 'border',
'--devlake-color-success': 'success',
'--devlake-color-error': 'error',
'--devlake-color-error-alt': 'errorAlt',
'--devlake-color-warning': 'warning',
'--devlake-color-warning-alt': 'warningAlt',
'--devlake-color-warning-soft': 'warningSoft',
};
export const ThemeProvider = ({ children }: Props) => {
const mode = useAppSelector(selectThemeMode);
const [resolved, setResolved] = useState<ResolvedTheme>(() => resolveThemeMode(mode));
useEffect(() => {
setResolved(resolveThemeMode(mode));
if (mode !== 'system' || typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return;
}
const media = window.matchMedia('(prefers-color-scheme: dark)');
const onChange = () => setResolved(media.matches ? 'dark' : 'light');
media.addEventListener('change', onChange);
return () => media.removeEventListener('change', onChange);
}, [mode]);
const customPrimary = import.meta.env.DEVLAKE_COLOR_CUSTOM;
const theme = useMemo(() => getTheme(resolved, customPrimary), [resolved, customPrimary]);
useEffect(() => {
if (typeof document === 'undefined') return;
const root = document.documentElement;
root.dataset.theme = resolved;
root.style.colorScheme = resolved;
for (const [cssVar, key] of Object.entries(CSS_VAR_MAP)) {
root.style.setProperty(cssVar, theme.colors[key]);
}
}, [resolved, theme]);
return (
<ConfigProvider theme={theme.antd}>
<StyledThemeProvider theme={theme}>{children}</StyledThemeProvider>
</ConfigProvider>
);
};