vite-plugin-theme.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { Plugin } from 'vite';
  2. type ThemeColors = {
  3. background?: string;
  4. foreground?: string;
  5. card?: string;
  6. 'card-foreground'?: string;
  7. popover?: string;
  8. 'popover-foreground'?: string;
  9. primary?: string;
  10. 'primary-foreground'?: string;
  11. secondary?: string;
  12. 'secondary-foreground'?: string;
  13. muted?: string;
  14. 'muted-foreground'?: string;
  15. accent?: string;
  16. 'accent-foreground'?: string;
  17. destructive?: string;
  18. 'destructive-foreground'?: string;
  19. success?: string;
  20. 'success-foreground'?: string;
  21. 'dev-mode'?: string;
  22. 'dev-mode-foreground'?: string;
  23. border?: string;
  24. input?: string;
  25. ring?: string;
  26. 'chart-1'?: string;
  27. 'chart-2'?: string;
  28. 'chart-3'?: string;
  29. 'chart-4'?: string;
  30. 'chart-5'?: string;
  31. radius?: string;
  32. sidebar?: string;
  33. 'sidebar-foreground'?: string;
  34. 'sidebar-primary'?: string;
  35. 'sidebar-primary-foreground'?: string;
  36. 'sidebar-accent'?: string;
  37. 'sidebar-accent-foreground'?: string;
  38. 'sidebar-border'?: string;
  39. 'sidebar-ring'?: string;
  40. brand?: string;
  41. 'brand-lighter'?: string;
  42. 'brand-darker'?: string;
  43. 'font-sans'?: string;
  44. 'font-mono'?: string;
  45. [key: string]: string | undefined;
  46. };
  47. export interface ThemeVariables {
  48. light?: ThemeColors;
  49. dark?: ThemeColors;
  50. }
  51. const defaultVariables: ThemeVariables = {
  52. light: {
  53. background: 'hsl(0 0% 100%)',
  54. foreground: 'hsl(0 0% 3.9%)',
  55. card: 'hsl(0 0% 100%)',
  56. 'card-foreground': 'hsl(0 0% 3.9%)',
  57. popover: 'hsl(0 0% 100%)',
  58. 'popover-foreground': 'hsl(0 0% 3.9%)',
  59. primary: 'hsl(0 0% 9%)',
  60. 'primary-foreground': 'hsl(0 0% 98%)',
  61. secondary: 'hsl(0 0% 96.1%)',
  62. 'secondary-foreground': 'hsl(0 0% 9%)',
  63. muted: 'hsl(0 0% 96.1%)',
  64. 'muted-foreground': 'hsl(0 0% 45.1%)',
  65. accent: 'hsl(0 0% 96.1%)',
  66. 'accent-foreground': 'hsl(0 0% 9%)',
  67. destructive: 'hsl(0 84.2% 60.2%)',
  68. 'destructive-foreground': 'hsl(0 0% 98%)',
  69. success: 'hsl(100, 81%, 35%)',
  70. 'success-foreground': 'hsl(0 0% 98%)',
  71. 'dev-mode': 'hsl(204, 76%, 62%)',
  72. 'dev-mode-foreground': 'hsl(0 0% 98%)',
  73. border: 'hsl(0 0% 89.8%)',
  74. input: 'hsl(0 0% 89.8%)',
  75. ring: 'hsl(0 0% 3.9%)',
  76. 'chart-1': 'hsl(12 76% 61%)',
  77. 'chart-2': 'hsl(173 58% 39%)',
  78. 'chart-3': 'hsl(197 37% 24%)',
  79. 'chart-4': 'hsl(43 74% 66%)',
  80. 'chart-5': 'hsl(27 87% 67%)',
  81. radius: '0.6rem',
  82. sidebar: 'hsl(0 0% 98%)',
  83. 'sidebar-foreground': 'hsl(240 5.3% 26.1%)',
  84. 'sidebar-primary': 'hsl(240 5.9% 10%)',
  85. 'sidebar-primary-foreground': 'hsl(0 0% 98%)',
  86. 'sidebar-accent': 'hsl(0, 0%, 92%)',
  87. 'sidebar-accent-foreground': 'hsl(240 5.9% 10%)',
  88. 'sidebar-border': 'hsl(220 13% 91%)',
  89. 'sidebar-ring': 'hsl(217.2 91.2% 59.8%)',
  90. brand: '#17c1ff',
  91. 'brand-lighter': '#e6f9ff',
  92. 'brand-darker': '#0099ff',
  93. 'font-sans': "'Geist', sans-serif",
  94. 'font-mono': "'Geist Mono', monospace",
  95. },
  96. dark: {
  97. background: 'hsl(0 0% 3.9%)',
  98. foreground: 'hsl(0 0% 98%)',
  99. card: 'hsl(0 0% 3.9%)',
  100. 'card-foreground': 'hsl(0 0% 98%)',
  101. popover: 'hsl(0 0% 3.9%)',
  102. 'popover-foreground': 'hsl(0 0% 98%)',
  103. primary: 'hsl(0 0% 98%)',
  104. 'primary-foreground': 'hsl(0 0% 9%)',
  105. secondary: 'hsl(0 0% 14.9%)',
  106. 'secondary-foreground': 'hsl(0 0% 98%)',
  107. muted: 'hsl(0 0% 14.9%)',
  108. 'muted-foreground': 'hsl(0 0% 63.9%)',
  109. accent: 'hsl(0 0% 14.9%)',
  110. 'accent-foreground': 'hsl(0 0% 98%)',
  111. destructive: 'hsl(0 62.8% 30.6%)',
  112. 'destructive-foreground': 'hsl(0 0% 98%)',
  113. success: 'hsl(100, 100%, 35%)',
  114. 'success-foreground': 'hsl(0 0% 98%)',
  115. 'dev-mode': 'hsl(204, 86%, 53%)',
  116. 'dev-mode-foreground': 'hsl(0 0% 98%)',
  117. border: 'hsl(0 0% 14.9%)',
  118. input: 'hsl(0 0% 14.9%)',
  119. ring: 'hsl(0 0% 83.1%)',
  120. 'chart-1': 'hsl(220 70% 50%)',
  121. 'chart-2': 'hsl(160 60% 45%)',
  122. 'chart-3': 'hsl(30 80% 55%)',
  123. 'chart-4': 'hsl(280 65% 60%)',
  124. 'chart-5': 'hsl(340 75% 55%)',
  125. sidebar: 'hsl(240 5.9% 10%)',
  126. 'sidebar-foreground': 'hsl(240 4.8% 95.9%)',
  127. 'sidebar-primary': 'hsl(224.3 76.3% 48%)',
  128. 'sidebar-primary-foreground': 'hsl(0 0% 100%)',
  129. 'sidebar-accent': 'hsl(240 3.7% 15.9%)',
  130. 'sidebar-accent-foreground': 'hsl(240 4.8% 95.9%)',
  131. 'sidebar-border': 'hsl(240 3.7% 15.9%)',
  132. 'sidebar-ring': 'hsl(217.2 91.2% 59.8%)',
  133. brand: '#17c1ff',
  134. 'brand-lighter': '#e6f9ff',
  135. 'brand-darker': '#0099ff',
  136. 'font-sans': "'Geist', sans-serif",
  137. 'font-mono': "'Geist Mono', monospace",
  138. },
  139. };
  140. export type ThemeVariablesPluginOptions = {
  141. theme?: ThemeVariables;
  142. };
  143. export function themeVariablesPlugin(options: ThemeVariablesPluginOptions): Plugin {
  144. const virtualModuleId = 'virtual:admin-theme';
  145. const resolvedVirtualModuleId = `\0${virtualModuleId}`;
  146. return {
  147. name: 'vendure:admin-theme',
  148. enforce: 'pre', // This ensures our plugin runs before other CSS processors
  149. transform(code, id) {
  150. // Only transform CSS files
  151. if (!id.endsWith('styles.css')) {
  152. return null;
  153. }
  154. // Replace the @import 'virtual:admin-theme'; with our theme variables
  155. if (
  156. code.includes('@import "virtual:admin-theme";') ||
  157. code.includes("@import 'virtual:admin-theme';")
  158. ) {
  159. const lightTheme = options.theme?.light || {};
  160. const darkTheme = options.theme?.dark || {};
  161. // Merge default themes with custom themes
  162. const mergedLightTheme = { ...defaultVariables.light, ...lightTheme };
  163. const mergedDarkTheme = { ...defaultVariables.dark, ...darkTheme };
  164. const themeCSS = `
  165. :root {
  166. ${Object.entries(mergedLightTheme)
  167. .filter(([key, value]) => value !== undefined)
  168. .map(([key, value]) => `--${key}: ${value as string};`)
  169. .join('\n')}
  170. }
  171. .dark {
  172. ${Object.entries(mergedDarkTheme)
  173. .filter(([key, value]) => value !== undefined)
  174. .map(([key, value]) => `--${key}: ${value as string};`)
  175. .join('\n')}
  176. }
  177. `;
  178. return code.replace(/@import ['"]virtual:admin-theme['"];?/, themeCSS);
  179. }
  180. return null;
  181. },
  182. };
  183. }