main
ts 197 lines 5.4 KB
Raw
1 import type { GlobalThemeOverrides, ThemeCommonVars } from "naive-ui"
2 import type { BuiltInGlobalTheme } from "naive-ui/es/themes/interface"
3 import type { Layout, RouterTransition } from "@/types/theme"
4 import { useWindowSize } from "@vueuse/core"
5 import _get from "lodash/get"
6 import _set from "lodash/set"
7 import { darkTheme, lightTheme } from "naive-ui"
8 import { acceptHMRUpdate, defineStore } from "pinia"
9 import { watch } from "vue"
10 import { getCssVars, getDefaultState, getThemeOverrides } from "@/theme"
11 import { ThemeNameEnum } from "@/types/theme"
12
13 export const useThemeStore = defineStore("theme", {
14 state: () => getDefaultState(),
15 actions: {
16 setLayout(layout: Layout): void {
17 this.layout = layout
18 },
19 setRTL(rtl: boolean): void {
20 this.rtl = rtl
21 },
22 setBoxed(boxed: boolean): void {
23 this.boxed.enabled = boxed
24 },
25 setFooterShow(show: boolean): void {
26 this.footer.show = show
27 },
28 setToolbarBoxed(boxed: boolean): void {
29 this.boxed.toolbar = boxed
30 },
31 setRouterTransition(routerTransition: RouterTransition): void {
32 this.routerTransition = routerTransition
33 },
34 setTheme(themeName: ThemeNameEnum): void {
35 this.themeName = themeName
36 },
37 setThemeLight(): void {
38 this.themeName = ThemeNameEnum.Light
39 },
40 setThemeDark(): void {
41 this.themeName = ThemeNameEnum.Dark
42 },
43 setColor(theme: ThemeNameEnum, colorName: string, color: string): void {
44 ;(this.colors[theme] as Record<string, string>)[colorName] = color
45 },
46 toggleTheme(): void {
47 if (this.isThemeDark) {
48 this.setThemeLight()
49 } else {
50 this.setThemeDark()
51 }
52 },
53 toggleSidebar(): void {
54 this.sidebar.collapsed = !this.sidebar.collapsed
55 },
56 refreshSidebar(): void {
57 // this is useful in context like NUXT
58 this.sidebar.collapsed = !this.sidebar.collapsed
59 setTimeout(() => {
60 this.sidebar.collapsed = !this.sidebar.collapsed
61 }, 10)
62 },
63 openSidebar(): void {
64 this.sidebar.collapsed = false
65 },
66 closeSidebar(): void {
67 this.sidebar.collapsed = true
68 },
69 updateResponsiveVars() {
70 for (const key in this.responsive.override) {
71 if (_get(this, key) && key in this.responsive.override) {
72 _set(
73 this,
74 key,
75 window.innerWidth <= this.responsive.breakpoint
76 ? this.responsive.override[key as keyof typeof this.responsive.override].mobile
77 : this.responsive.override[key as keyof typeof this.responsive.override].desk
78 )
79 }
80 }
81 },
82 ensureSidebarState() {
83 // auto close sidebar on resize
84 if (this.sidebar.autoClose) {
85 if (!this.sidebar.collapsed && window.innerWidth <= this.sidebar.autoCloseBreakpoint) {
86 this.sidebar.collapsed = true
87 }
88 }
89 },
90 setDocumentThemeName(val: ThemeNameEnum, old?: ThemeNameEnum) {
91 if (document) {
92 const html = document.children[0] as HTMLElement
93 if (old) {
94 html.classList.remove(`theme-${old}`)
95 }
96 html.classList.add(`theme-${val}`)
97 }
98 },
99 // This function allows you to utilize the values in the style object as variables within your CSS/SCSS code like: var(--bg-default-color)
100 setCssGlobalVars() {
101 if (document) {
102 const html = document.children[0] as HTMLElement
103 const body = document.getElementsByTagName("body")?.[0]
104 if (body) {
105 if (this.isRTL) {
106 body.classList.add("direction-rtl")
107 body.classList.remove("direction-ltr")
108 } else {
109 body.classList.remove("direction-rtl")
110 body.classList.add("direction-ltr")
111 }
112 }
113 // html.dir = this.isRTL ? "rtl" : "ltr"
114 const { style: htmlStyle } = html
115 for (const key in this.style) {
116 const value = this.style[key]
117
118 if (!value) continue
119
120 htmlStyle.setProperty(`--${key}`, value)
121 }
122 }
123 },
124 startWatchers() {
125 const { width } = useWindowSize()
126
127 watch([() => this.isRTL, () => this.style], () => {
128 this.setCssGlobalVars()
129 })
130
131 watch(
132 () => this.themeName,
133 (val, old) => {
134 this.setDocumentThemeName(val, old)
135 }
136 )
137
138 watch(width, () => {
139 this.updateResponsiveVars()
140 this.ensureSidebarState()
141 })
142 },
143 initTheme() {
144 this.updateResponsiveVars()
145 this.ensureSidebarState()
146 this.setCssGlobalVars()
147 this.setDocumentThemeName(this.themeName)
148 this.startWatchers()
149 }
150 },
151 getters: {
152 naiveTheme(state): BuiltInGlobalTheme {
153 return state.themeName === ThemeNameEnum.Dark ? darkTheme : lightTheme
154 },
155 themeOverrides(state): GlobalThemeOverrides {
156 return getThemeOverrides(state)
157 },
158 darkPrimaryColor(state): string {
159 return state.colors.dark.primary
160 },
161 lightPrimaryColor(state): string {
162 return state.colors.light.primary
163 },
164 naiveCommon(): ThemeCommonVars {
165 return { ...this.naiveTheme.common, ...this.themeOverrides.common }
166 },
167 style(state): { [key: string]: string } {
168 return getCssVars(state, this)
169 },
170 isThemeDark(state): boolean {
171 return state.themeName === ThemeNameEnum.Dark
172 },
173 isThemeLight(state): boolean {
174 return state.themeName === ThemeNameEnum.Light
175 },
176 isBoxed(state): boolean {
177 return state.boxed.enabled
178 },
179 isRTL(state): boolean {
180 return state.rtl
181 },
182 isFooterShown(state): boolean {
183 return state.footer.show
184 },
185 isToolbarBoxed(state): boolean {
186 return state.boxed.toolbar && state.boxed.enabled
187 }
188 },
189 persist: {
190 // use this param to save specific state chunk on localStorage
191 pick: ["layout", "themeName", "routerTransition", "boxed", "sidebar.collapsed"]
192 }
193 })
194
195 if (import.meta.hot) {
196 import.meta.hot.accept(acceptHMRUpdate(useThemeStore, import.meta.hot))
197 }