github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/ui/dashboard/src/hooks/useTheme.tsx (about) 1 import React, { createContext, useContext, useState } from "react"; 2 import useLocalStorage from "./useLocalStorage"; 3 import useMediaQuery from "./useMediaQuery"; 4 import { classNames } from "../utils/styles"; 5 6 export type Theme = { 7 name: string; 8 label: string; 9 }; 10 11 type IThemes = { 12 [key: string]: Theme; 13 }; 14 15 const ThemeNames = { 16 STEAMPIPE_DEFAULT: "steampipe-default", 17 STEAMPIPE_DARK: "steampipe-dark", 18 }; 19 20 const Themes: IThemes = { 21 [ThemeNames.STEAMPIPE_DEFAULT]: { 22 label: "Light", 23 name: ThemeNames.STEAMPIPE_DEFAULT, 24 }, 25 [ThemeNames.STEAMPIPE_DARK]: { 26 label: "Dark", 27 name: ThemeNames.STEAMPIPE_DARK, 28 }, 29 }; 30 31 type IThemeContext = { 32 localStorageTheme: string | null; 33 theme: Theme; 34 withFooterPadding: boolean; 35 wrapperRef: React.Ref<null>; 36 setTheme(theme: string): void; 37 setWithFooterPadding(newValue: boolean): void; 38 setWrapperRef(element: any): void; 39 }; 40 41 const ThemeContext = createContext<IThemeContext | undefined>(undefined); 42 43 const ThemeProvider = ({ children }) => { 44 const [withFooterPadding, setWithFooterPadding] = useState(true); 45 const [localStorageTheme, setLocalStorageTheme] = 46 useLocalStorage("steampipe.ui.theme"); 47 const prefersDarkTheme = useMediaQuery("(prefers-color-scheme: dark)"); 48 const [wrapperRef, setWrapperRef] = useState(null); 49 const doSetWrapperRef = (element) => setWrapperRef(() => element); 50 51 let theme; 52 53 if ( 54 localStorageTheme && 55 (localStorageTheme === ThemeNames.STEAMPIPE_DEFAULT || 56 localStorageTheme === ThemeNames.STEAMPIPE_DARK) 57 ) { 58 theme = Themes[localStorageTheme]; 59 } else if (prefersDarkTheme) { 60 theme = Themes[ThemeNames.STEAMPIPE_DARK]; 61 } else { 62 theme = Themes[ThemeNames.STEAMPIPE_DEFAULT]; 63 } 64 65 return ( 66 <ThemeContext.Provider 67 value={{ 68 localStorageTheme, 69 theme, 70 setTheme: setLocalStorageTheme, 71 setWrapperRef: doSetWrapperRef, 72 withFooterPadding, 73 wrapperRef, 74 setWithFooterPadding, 75 }} 76 > 77 {children} 78 </ThemeContext.Provider> 79 ); 80 }; 81 82 const FullHeightThemeWrapper = ({ children }) => { 83 const { setWrapperRef, theme, withFooterPadding } = useTheme(); 84 return ( 85 <div 86 ref={setWrapperRef} 87 className={classNames( 88 `h-screen flex flex-col theme-${theme.name} bg-dashboard print:bg-white print:theme-steampipe-default text-foreground print:text-black overflow-y-hidden`, 89 withFooterPadding ? "pb-4" : "" 90 )} 91 > 92 {children} 93 </div> 94 ); 95 }; 96 97 const ThemeWrapper = ({ children }) => { 98 const { setWrapperRef, theme } = useTheme(); 99 return ( 100 <div 101 ref={setWrapperRef} 102 className={classNames( 103 `theme-${theme.name} bg-dashboard print:bg-white print:theme-steampipe-default text-foreground print:text-black` 104 )} 105 > 106 {children} 107 </div> 108 ); 109 }; 110 111 const ModalThemeWrapper = ({ children }) => { 112 const { setWrapperRef, theme } = useTheme(); 113 return ( 114 <div 115 ref={setWrapperRef} 116 className={`theme-${theme.name} print:bg-white print:theme-steampipe-default text-foreground print:text-black`} 117 > 118 {children} 119 </div> 120 ); 121 }; 122 123 const useTheme = () => { 124 const context = useContext(ThemeContext); 125 if (context === undefined) { 126 throw new Error("useTheme must be used within a ThemeContext"); 127 } 128 return context; 129 }; 130 131 export { 132 FullHeightThemeWrapper, 133 ModalThemeWrapper, 134 Themes, 135 ThemeNames, 136 ThemeProvider, 137 ThemeWrapper, 138 useTheme, 139 };