+
diff --git a/lib/theme.ts b/lib/theme.ts
new file mode 100644
index 000000000..1344f7e9d
--- /dev/null
+++ b/lib/theme.ts
@@ -0,0 +1,38 @@
+export type Theme = 'system' | 'dark' | 'light';
+export type ThemesProps = {
+ id: Theme;
+ name: string;
+ icon: React.ForwardRefExoticComponent<
+ Omit, 'ref'> & {
+ title?: string | undefined;
+ titleId?: string | undefined;
+ } & React.RefAttributes
+ >;
+};
+
+export const applyTheme = (theme: Theme) => {
+ switch (theme) {
+ case 'dark':
+ document.documentElement.classList.add('dark');
+ document.documentElement.setAttribute('data-theme', 'black');
+ localStorage.setItem('theme', 'dark');
+ break;
+ case 'light':
+ document.documentElement.classList.remove('dark');
+ document.documentElement.setAttribute('data-theme', 'corporate');
+ localStorage.setItem('theme', 'light');
+ break;
+ case 'system':
+ default:
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ document.documentElement.classList.add('dark');
+ document.documentElement.setAttribute('data-theme', 'black');
+ localStorage.removeItem('theme');
+ } else {
+ document.documentElement.classList.remove('dark');
+ document.documentElement.setAttribute('data-theme', 'corporate');
+ localStorage.removeItem('theme');
+ }
+ break;
+ }
+};
diff --git a/lib/ui/hooks/useTheme.ts b/lib/ui/hooks/useTheme.ts
new file mode 100644
index 000000000..934e44fe8
--- /dev/null
+++ b/lib/ui/hooks/useTheme.ts
@@ -0,0 +1,53 @@
+import { ComputerDesktopIcon, MoonIcon, SunIcon } from '@heroicons/react/24/outline';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'next-i18next';
+
+import { ThemesProps, applyTheme } from '../../../lib/theme';
+
+const useTheme = () => {
+ const [theme, setTheme] = useState(null);
+ const { t } = useTranslation('common');
+
+ useEffect(() => {
+ setTheme(localStorage.getItem('theme'));
+ }, []);
+
+ const themes: ThemesProps[] = [
+ {
+ id: 'system',
+ name: t('system'),
+ icon: ComputerDesktopIcon,
+ },
+ {
+ id: 'dark',
+ name: t('dark'),
+ icon: MoonIcon,
+ },
+ {
+ id: 'light',
+ name: t('light'),
+ icon: SunIcon,
+ },
+ ];
+
+ const selectedTheme = themes.find((t) => t.id === theme) || themes[0];
+
+ const toggleTheme = () => {
+ selectedTheme.id === 'light' ? applyTheme('dark') : applyTheme('light');
+
+ if (selectedTheme.id === 'light') {
+ applyTheme('dark');
+ setTheme('dark');
+ } else if (selectedTheme.id === 'dark') {
+ applyTheme('light');
+ setTheme('light');
+ } else if (selectedTheme.id === 'system') {
+ applyTheme('dark');
+ setTheme('dark');
+ }
+ };
+
+ return { theme, setTheme, selectedTheme, toggleTheme, themes, applyTheme };
+};
+
+export default useTheme;
diff --git a/package-lock.json b/package-lock.json
index c7efc0ff8..1c859bc1b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@boxyhq/metrics": "0.2.5",
"@boxyhq/react-ui": "3.3.13",
"@boxyhq/saml-jackson": "file:npm",
- "@heroicons/react": "2.0.18",
+ "@heroicons/react": "^2.0.18",
"@retracedhq/logs-viewer": "2.5.1",
"@retracedhq/retraced": "0.7.0",
"@tailwindcss/typography": "0.5.10",
@@ -1878,7 +1878,8 @@
},
"node_modules/@heroicons/react": {
"version": "2.0.18",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz",
+ "integrity": "sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==",
"peerDependencies": {
"react": ">= 16"
}
diff --git a/package.json b/package.json
index e850ee1f3..17ef9c17b 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"@boxyhq/metrics": "0.2.5",
"@boxyhq/react-ui": "3.3.13",
"@boxyhq/saml-jackson": "file:npm",
- "@heroicons/react": "2.0.18",
+ "@heroicons/react": "^2.0.18",
"@retracedhq/logs-viewer": "2.5.1",
"@retracedhq/retraced": "0.7.0",
"@tailwindcss/typography": "0.5.10",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 2c34594ed..9e5c29cec 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -5,13 +5,14 @@ import { SessionProvider } from 'next-auth/react';
import { useRouter } from 'next/router';
import { Toaster } from '@components/Toaster';
import { appWithTranslation } from 'next-i18next';
-import { ReactElement, ReactNode } from 'react';
+import { ReactElement, ReactNode, useEffect } from 'react';
import micromatch from 'micromatch';
import nextI18NextConfig from '../next-i18next.config.js';
import { AccountLayout, SetupLinkLayout } from '@components/layouts';
import '@boxyhq/react-ui/dist/style.css';
import '../styles/globals.css';
+import { applyTheme } from '../lib/theme';
const unauthenticatedRoutes = [
'/',
@@ -35,7 +36,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const { session, ...props } = pageProps;
const getLayout = Component.getLayout;
-
+ useEffect(() => {
+ applyTheme(localStorage.getItem('theme') as Theme);
+ }, []);
// If a page level layout exists, use it
if (getLayout) {
return (
diff --git a/pages/admin/sso-connection/index.tsx b/pages/admin/sso-connection/index.tsx
index 53a3ba5ee..564125718 100644
--- a/pages/admin/sso-connection/index.tsx
+++ b/pages/admin/sso-connection/index.tsx
@@ -1,5 +1,6 @@
import type { GetServerSidePropsContext, NextPage } from 'next';
import ConnectionList from '@components/connection/ConnectionList';
+
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
const ConnectionsIndexPage: NextPage = () => {
diff --git a/styles/globals.css b/styles/globals.css
index ce2e60259..be6e43c16 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -121,3 +121,12 @@ span.react-datepicker__navigation-icon {
.modal-content > div > textarea {
background-color: white;
}
+
+::-webkit-scrollbar {
+ width: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #a0a0a0;
+ border-radius: 4px;
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 9a16deb4b..004cf6b0a 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -23,6 +23,8 @@ module.exports = {
error: '#F87272',
},
},
+ 'black',
+ 'corporate',
],
},
plugins: [require('@tailwindcss/typography'), require('daisyui')],
{children}