| 1 | import type { GlobalThemeOverrides, ThemeCommonVars } from "naive-ui" |
| 2 | import { useOsTheme } from "naive-ui" |
| 3 | import tokens from "@/design-tokens.json" |
| 4 | import { Layout, RouterTransition, ThemeNameEnum } from "@/types/theme" |
| 5 | import { colorToArray, expandPattern, getThemeColors, getTypeValue } from "@/utils/theme" |
| 6 | |
| 7 | type ThemeState = ReturnType<typeof getDefaultState> |
| 8 | |
| 9 | interface ThemeGetters { |
| 10 | naiveCommon: ThemeCommonVars |
| 11 | } |
| 12 | |
| 13 | const osTheme = useOsTheme() |
| 14 | |
| 15 | export function getDefaultState() { |
| 16 | return { |
| 17 | layout: Layout.HorizontalNav, // Type of layout, with vertical or horizontal navigation |
| 18 | themeName: osTheme.value === "dark" ? ThemeNameEnum.Dark : ThemeNameEnum.Light, // Theme name (Dark, Light), with fallback to the light theme |
| 19 | routerTransition: RouterTransition.FadeUp, // Type of transition for the router |
| 20 | routerTransitionDuration: 0.3, // Duration of the router transition in seconds |
| 21 | rtl: false, // RTL (right to left) mode toggle |
| 22 | boxed: { |
| 23 | enabled: true, // Choose whether to apply a maximum width to the page |
| 24 | toolbar: true, // Choose whether to apply the maximum width to the toolbar as well |
| 25 | width: 1600 // Maximum width size in pixels |
| 26 | }, |
| 27 | sidebar: { |
| 28 | autoClose: true, // Choose whether to automatically close the sidebar when the view goes below the "autoCloseBreakpoint" value |
| 29 | collapsed: false, // Indicates if the sidebar is collapsed |
| 30 | autoCloseBreakpoint: 1000, // Breakpoint for the automatic closing of the sidebar (in pixels) |
| 31 | animEase: "ease-in-out", // Type of easing for animations |
| 32 | animDuration: 0.3, // Duration of sidebar animations (in seconds) |
| 33 | openWidth: 300, // Width of the open sidebar (in pixels) |
| 34 | closeWidth: 64 // Width of the closed sidebar (in pixels) |
| 35 | }, |
| 36 | footer: { |
| 37 | show: true // Show or hide the footer |
| 38 | }, |
| 39 | responsive: { |
| 40 | breakpoint: 700, // Breakpoint in pixels (Desk -> Mobile) |
| 41 | // Parameters to be adjusted based on the breakpoint |
| 42 | override: { |
| 43 | viewPadding: { |
| 44 | desk: 40, // View padding for desktop |
| 45 | mobile: 20 // View padding for mobile devices |
| 46 | }, |
| 47 | toolbarHeight: { |
| 48 | desk: 80, // Height of the toolbar for desktop |
| 49 | mobile: 70 // Height of the toolbar for mobile devices |
| 50 | } |
| 51 | } |
| 52 | }, |
| 53 | toolbarHeight: 80, // Default toolbar height (in pixels) |
| 54 | viewPadding: 40, // Default view padding (in pixels) |
| 55 | headerBarHeight: 60, // Height of the header bar (in pixels) |
| 56 | colors: tokens.colors, // Color definitions from the token |
| 57 | borderRadius: tokens.borderRadius, // Border radius from the token |
| 58 | lineHeight: tokens.lineHeight, // Line height from the token |
| 59 | fontSize: tokens.fontSize, // Font size from the token |
| 60 | fontWeight: tokens.fontWeight, // Font weight from the token |
| 61 | fontFamily: tokens.fontFamily, // Font family from the token |
| 62 | typography: tokens.typography // Typography configurations from the token |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | export function getThemeOverrides(state: ThemeState): GlobalThemeOverrides { |
| 67 | const { |
| 68 | primary, |
| 69 | success, |
| 70 | warning, |
| 71 | error, |
| 72 | info, |
| 73 | background, |
| 74 | backgroundSecondary, |
| 75 | bodyBackground, |
| 76 | text, |
| 77 | textTertiary, |
| 78 | border, |
| 79 | hover |
| 80 | } = state.colors[state.themeName] |
| 81 | |
| 82 | const themeColors = getThemeColors({ primary, success, warning, error, info }) |
| 83 | |
| 84 | const lineHeight = state.lineHeight.default |
| 85 | const borderRadius = state.borderRadius.default |
| 86 | const borderRadiusSmall = state.borderRadius.small |
| 87 | const borderColor = border |
| 88 | const hoverColor = hover |
| 89 | |
| 90 | return { |
| 91 | common: { |
| 92 | ...themeColors, |
| 93 | textColorBase: text, |
| 94 | textColor1: text, |
| 95 | textColor2: text, |
| 96 | textColor3: textTertiary, |
| 97 | bodyColor: bodyBackground, |
| 98 | baseColor: background, |
| 99 | popoverColor: backgroundSecondary, |
| 100 | cardColor: background, |
| 101 | modalColor: background, |
| 102 | lineHeight, |
| 103 | borderRadius, |
| 104 | borderRadiusSmall, |
| 105 | fontSize: state.fontSize.default, |
| 106 | fontWeight: state.fontWeight.default, |
| 107 | fontWeightStrong: state.fontWeight.strong, |
| 108 | fontFamily: state.fontFamily.default, |
| 109 | fontFamilyMono: state.fontFamily.mono, |
| 110 | borderColor, |
| 111 | hoverColor, |
| 112 | dividerColor: borderColor |
| 113 | }, |
| 114 | Card: { |
| 115 | color: background, |
| 116 | colorEmbedded: backgroundSecondary, |
| 117 | colorEmbeddedModal: backgroundSecondary, |
| 118 | colorEmbeddedPopover: backgroundSecondary, |
| 119 | titleFontSizeSmall: state.fontSize.cardTitle, |
| 120 | titleFontSizeMedium: state.fontSize.cardTitle, |
| 121 | titleFontSizeLarge: state.fontSize.cardTitle, |
| 122 | titleFontSizeHuge: state.fontSize.cardTitle, |
| 123 | titleFontWeight: state.fontWeight.cardTitle |
| 124 | }, |
| 125 | Drawer: { |
| 126 | borderRadius: 0 |
| 127 | }, |
| 128 | LoadingBar: { |
| 129 | colorLoading: primary |
| 130 | }, |
| 131 | Tag: { |
| 132 | colorBordered: "rgba(0, 0, 0, 0.1)" |
| 133 | }, |
| 134 | Typography: { |
| 135 | headerFontSize1: getTypeValue(state, state.typography.h1.fontSize), |
| 136 | headerFontSize2: getTypeValue(state, state.typography.h2.fontSize), |
| 137 | headerFontSize3: getTypeValue(state, state.typography.h3.fontSize), |
| 138 | headerFontSize4: getTypeValue(state, state.typography.h4.fontSize), |
| 139 | headerFontSize5: getTypeValue(state, state.typography.h5.fontSize), |
| 140 | headerFontSize6: getTypeValue(state, state.typography.h6.fontSize) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | export function getCssVars(state: ThemeState, getters: ThemeGetters): { [key: string]: string } { |
| 146 | const naive = getters.naiveCommon |
| 147 | |
| 148 | const bgColor = naive.baseColor |
| 149 | const bgSecondaryColor = state.colors[state.themeName].backgroundSecondary |
| 150 | const fgColor = state.colors[state.themeName].text |
| 151 | const fgSecondaryColor = state.colors[state.themeName].textSecondary |
| 152 | const fgTertiaryColor = state.colors[state.themeName].textTertiary |
| 153 | |
| 154 | const bgSidebar = state.colors[state.themeName].sidebarBackground |
| 155 | const bgBody = state.colors[state.themeName].bodyBackground |
| 156 | |
| 157 | const boxedWidth = state.boxed.width |
| 158 | const routerTransitionDuration = state.routerTransitionDuration |
| 159 | const sidebarAnimEase = state.sidebar.animEase |
| 160 | const sidebarAnimDuration = state.sidebar.animDuration |
| 161 | const sidebarOpenWidth = state.sidebar.openWidth |
| 162 | const sidebarCloseWidth = state.sidebar.closeWidth |
| 163 | const toolbarHeight = state.toolbarHeight |
| 164 | const viewPadding = state.viewPadding |
| 165 | const headerBarHeight = state.headerBarHeight |
| 166 | const fontFamily = state.fontFamily.default |
| 167 | const fontFamilyDisplay = state.fontFamily.display |
| 168 | const fontFamilyMono = state.fontFamily.mono |
| 169 | const fontSize = state.fontSize.default |
| 170 | const lineHeight = state.lineHeight.default |
| 171 | |
| 172 | const borderRadius = state.borderRadius.default |
| 173 | const borderRadiusSmall = state.borderRadius.small |
| 174 | |
| 175 | const bezierEase = naive.cubicBezierEaseInOut |
| 176 | |
| 177 | // This style object, imported via the themeStore, will be available application-wide and is exposed in the HTML tag as a list of CSS variables, which you can use in your CSS/SCSS code like: var(--bg-default-color) |
| 178 | const styleObject: Record<string, string> = { |
| 179 | "bg-body-color": `${bgBody}`, |
| 180 | "bg-sidebar-color": `${bgSidebar}`, |
| 181 | |
| 182 | "fg-default-color": `${fgColor}`, |
| 183 | "fg-secondary-color": `${fgSecondaryColor}`, |
| 184 | "fg-tertiary-color": `${fgTertiaryColor}`, |
| 185 | "bg-default-color": `${bgColor}`, |
| 186 | "bg-secondary-color": `${bgSecondaryColor}`, |
| 187 | |
| 188 | "bezier-ease": `${bezierEase}`, |
| 189 | "router-transition-duration": `${routerTransitionDuration}s`, |
| 190 | "sidebar-anim-ease": `${sidebarAnimEase}`, |
| 191 | "sidebar-anim-duration": `${sidebarAnimDuration}s`, |
| 192 | "sidebar-open-width": `${sidebarOpenWidth}px`, |
| 193 | "sidebar-close-width": `${sidebarCloseWidth}px`, |
| 194 | "boxed-width": `${boxedWidth}px`, |
| 195 | "toolbar-height": `${toolbarHeight}px`, |
| 196 | "header-bar-height": `${headerBarHeight}px`, |
| 197 | "view-padding": `${viewPadding}px`, |
| 198 | "border-radius": `${borderRadius}`, |
| 199 | "border-radius-small": `${borderRadiusSmall}`, |
| 200 | |
| 201 | "font-family": `${fontFamily}`, |
| 202 | "font-family-display": `${fontFamilyDisplay}`, |
| 203 | "font-family-mono": `${fontFamilyMono}`, |
| 204 | "font-size": `${fontSize}`, |
| 205 | "line-height": `${lineHeight}` |
| 206 | } |
| 207 | |
| 208 | // import colors by patterns |
| 209 | for (const pattern of ["extra(1|2|3|4)", "primary", "success", "warning", "error", "info", "border", "hover"]) { |
| 210 | const keys = expandPattern(pattern) |
| 211 | |
| 212 | for (const key of keys) { |
| 213 | styleObject[`${key}-color`] = (state.colors[state.themeName] as Record<string, string>)[key] ?? "" |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // add RGB values variant |
| 218 | for (const [key, value] of Object.entries(styleObject)) { |
| 219 | if (key.endsWith("-color")) { |
| 220 | styleObject[`${key}-rgb`] = colorToArray(value, "rgb").join(" ") |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | return styleObject |
| 225 | } |