@udixio/theme 1.0.0-beta.8 → 1.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udixio/theme",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.9",
4
4
  "license": "MIT",
5
5
  "author": "vigreux-joël",
6
6
  "main": "dist/index.js",
@@ -85,6 +85,7 @@
85
85
  "source-map-support": "^0.5.20",
86
86
  "supertest": "^6.1.3",
87
87
  "tailwindcss": "^3.4.7",
88
+ "tailwindcss-themer": "^4.0.0",
88
89
  "ts-jest": "28.0.8",
89
90
  "ts-loader": "^9.2.3",
90
91
  "ts-node": "^10.0.0",
@@ -1,7 +1,6 @@
1
1
  import { VariantEntity } from '../theme';
2
2
  import { AddColorsOptions } from '../color';
3
- import { PluginAbstract } from '../plugin/plugin.abstract';
4
- import { AppService } from '../app.service';
3
+ import { PluginConstructor } from '../plugin/plugin.service';
5
4
 
6
5
  export interface ConfigInterface {
7
6
  sourceColor: string;
@@ -11,5 +10,5 @@ export interface ConfigInterface {
11
10
  colors?: AddColorsOptions | AddColorsOptions[];
12
11
  useDefaultColors?: boolean;
13
12
  palettes?: Record<string, string>;
14
- plugins?: (new (appService: AppService) => PluginAbstract)[];
13
+ plugins?: (PluginConstructor | [PluginConstructor, object])[];
15
14
  }
@@ -54,7 +54,13 @@ export class ConfigService {
54
54
  colorService.addColors(colors);
55
55
  }
56
56
  if (plugins) {
57
- plugins.forEach((plugin) => pluginService.addPlugin(plugin));
57
+ plugins.forEach((plugin) => {
58
+ if (Array.isArray(plugin)) {
59
+ pluginService.addPlugin(plugin[0], plugin[1]);
60
+ } else {
61
+ pluginService.addPlugin(plugin, {});
62
+ }
63
+ });
58
64
  pluginService.loadPlugins(this.appService);
59
65
  }
60
66
  }
@@ -3,5 +3,6 @@ import { AppService } from '../app.service';
3
3
  export abstract class PluginAbstract {
4
4
  static dependencies: (new () => PluginAbstract)[];
5
5
 
6
- protected constructor(protected appService: AppService) {}
6
+ protected abstract appService: AppService;
7
+ protected abstract options: object;
7
8
  }
@@ -1,26 +1,30 @@
1
1
  import { PluginAbstract } from './plugin.abstract';
2
2
  import { AppService } from '../app.service';
3
3
 
4
+ export type PluginConstructor = new (
5
+ appService: AppService,
6
+ options: any
7
+ ) => PluginAbstract;
8
+
4
9
  export class PluginService {
5
10
  private pluginInstances = new Map<string, PluginAbstract>();
6
- private pluginConstructors = new Map<
7
- string,
8
- new (appService: AppService) => PluginAbstract
9
- >();
11
+ private pluginConstructors = new Map<string, [PluginConstructor, object]>();
10
12
 
11
- public addPlugin(plugin: new (appService: AppService) => PluginAbstract) {
12
- this.pluginConstructors.set(plugin.name, plugin);
13
+ public addPlugin(plugin: PluginConstructor, config: object) {
14
+ this.pluginConstructors.set(plugin.name, [plugin, config]);
13
15
  }
14
16
 
15
17
  public loadPlugins(appService: AppService) {
16
- this.pluginConstructors.forEach((plugin) => {
17
- this.pluginInstances.set(plugin.name, new plugin(appService));
18
+ this.pluginConstructors.forEach(([plugin, option]) => {
19
+ this.pluginInstances.set(plugin.name, new plugin(appService, option));
18
20
  });
19
21
  }
20
22
 
21
23
  public getPlugin<T extends PluginAbstract>(
22
- plugin: new (appService: AppService) => T
23
- ): T | undefined {
24
- return this.pluginInstances.get(plugin.name) as T;
24
+ plugin: new (appService: AppService, options: any) => T
25
+ ): T {
26
+ const pluginInstance = this.pluginInstances.get(plugin.name);
27
+ if (!pluginInstance) throw new Error(`Plugin ${plugin.name} not found`);
28
+ return pluginInstance as T;
25
29
  }
26
30
  }
@@ -0,0 +1,53 @@
1
+ import { PluginAbstract } from '../../plugin/plugin.abstract';
2
+ import { AppService } from '../../app.service';
3
+ import { Theme } from './main';
4
+ import { state } from './plugins-tailwind/state';
5
+ import { themer } from './plugins-tailwind/themer';
6
+
7
+ interface TailwindPluginOptions {
8
+ darkMode: 'class' | 'media';
9
+ }
10
+
11
+ export class TailwindPlugin extends PluginAbstract {
12
+ constructor(
13
+ protected appService: AppService,
14
+ protected options: TailwindPluginOptions
15
+ ) {
16
+ super();
17
+ }
18
+ static config(options: TailwindPluginOptions): TailwindPluginOptions {
19
+ return options;
20
+ }
21
+ getTheme(): Theme {
22
+ const colors: Record<
23
+ string,
24
+ {
25
+ light: string;
26
+ dark: string;
27
+ }
28
+ > = {};
29
+
30
+ for (const isDark of [false, true]) {
31
+ this.appService.themeService.update({ isDark: isDark });
32
+ for (const [key, value] of this.appService.colorService
33
+ .getColors()
34
+ .entries()) {
35
+ const newKey = key
36
+ .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
37
+ .toLowerCase();
38
+ colors[newKey] ??= { light: '', dark: '' };
39
+ colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
40
+ }
41
+ }
42
+ console.log(colors);
43
+
44
+ return {
45
+ colors: {},
46
+ fontFamily: { expressive: [], neutral: [] },
47
+ plugins: [
48
+ state(Object.keys(colors)),
49
+ themer(colors, this.options.darkMode),
50
+ ],
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,18 @@
1
+ import { PluginsConfig } from 'tailwindcss/types/config';
2
+ import { bootstrapFromConfig } from '../../main';
3
+ import { TailwindPlugin } from './Tailwind.plugin';
4
+
5
+ export type Theme = {
6
+ colors: Record<string, string>;
7
+ fontFamily: { expressive: string[]; neutral: string[] };
8
+ plugins: Partial<PluginsConfig>;
9
+ };
10
+
11
+ export const createTheme = async (): Promise<Theme> => {
12
+ const app = await bootstrapFromConfig();
13
+ const plugin = app.pluginService.getPlugin(TailwindPlugin);
14
+ if (!plugin) {
15
+ throw new Error('Tailwind plugin not found');
16
+ }
17
+ return plugin.getTheme();
18
+ };
@@ -0,0 +1,88 @@
1
+ // from tailwindcss src/util/flattenColors
2
+ import plugin from 'tailwindcss/plugin';
3
+ import { PluginAPI } from 'tailwindcss/types/config';
4
+
5
+ type State = {
6
+ statePrefix: string;
7
+ disabledStyles: {
8
+ textOpacity: number;
9
+ backgroundOpacity: number;
10
+ };
11
+ transition: {
12
+ duration: number;
13
+ };
14
+ };
15
+
16
+ type Components = Record<string, Record<string, {}>>;
17
+
18
+ export const state = (colorkeys: string[]) =>
19
+ plugin((pluginArgs: PluginAPI) => {
20
+ addAllNewComponents(
21
+ pluginArgs,
22
+ {
23
+ statePrefix: 'state',
24
+ disabledStyles: {
25
+ textOpacity: 0.38,
26
+ backgroundOpacity: 0.12,
27
+ },
28
+ transition: {
29
+ duration: 150,
30
+ },
31
+ },
32
+ colorkeys
33
+ );
34
+ }, {});
35
+
36
+ const addAllNewComponents = (
37
+ { addComponents }: PluginAPI,
38
+ { statePrefix, disabledStyles, transition }: State,
39
+ colorKeys: string[]
40
+ ) => {
41
+ const newComponents: Components = {};
42
+
43
+ for (const isGroup of [false, true]) {
44
+ const group = isGroup ? 'group-' : '';
45
+ for (const colorName of colorKeys) {
46
+ const className = `.${group}${statePrefix}-${colorName}`;
47
+ newComponents[className] = {
48
+ [`@apply ${group}hover:bg-${colorName}/[0.08]`]: {},
49
+ [`@apply ${group}active:bg-${colorName}/[0.12]`]: {},
50
+ [`@apply ${group}focus-visible:bg-${colorName}/[0.12]`]: {},
51
+ };
52
+ if (transition) {
53
+ newComponents[className][`@apply transition-colors`] = {};
54
+ newComponents[className][`@apply duration-${transition.duration}`] = {};
55
+ }
56
+ if (disabledStyles) {
57
+ newComponents[className][
58
+ `@apply ${group}disabled:text-on-surface/[${disabledStyles.textOpacity}]`
59
+ ] = {};
60
+ newComponents[className][
61
+ `@apply ${group}disabled:bg-on-surface/[${disabledStyles.backgroundOpacity}]`
62
+ ] = {};
63
+ }
64
+ }
65
+ }
66
+ for (const colorName of colorKeys) {
67
+ for (const stateName of ['hover', 'active', 'focus', 'disabled']) {
68
+ const className = `.${stateName}-${statePrefix}-${colorName}`;
69
+ if (stateName === 'active' || stateName === 'focus') {
70
+ newComponents[className] = {
71
+ [`@apply bg-${colorName}/[0.12]`]: {},
72
+ };
73
+ } else if (stateName === 'hover') {
74
+ newComponents[className] = {
75
+ [`@apply bg-${colorName}/[0.08]`]: {},
76
+ };
77
+ } else if (stateName === 'disabled') {
78
+ newComponents[className] = {
79
+ [`@apply text-on-surface/[${disabledStyles.textOpacity}]`]: {},
80
+ };
81
+ newComponents[className] = {
82
+ [`@apply bg-on-surface/[${disabledStyles.backgroundOpacity}]`]: {},
83
+ };
84
+ }
85
+ }
86
+ }
87
+ addComponents(newComponents);
88
+ };
@@ -0,0 +1,53 @@
1
+ export const themer = (
2
+ colors: Record<
3
+ string,
4
+ {
5
+ light: string;
6
+ dark: string;
7
+ }
8
+ >,
9
+ darkMode: 'class' | 'media'
10
+ ) => {
11
+ const options: {
12
+ defaultTheme: {
13
+ extend: {
14
+ colors: Record<string, string>;
15
+ };
16
+ };
17
+ themes: [
18
+ {
19
+ name: 'darkTheme';
20
+ selectors?: ['.dark-mode', '[data-theme="dark"]'];
21
+ mediaQuery?: '@media (prefers-color-scheme: dark)';
22
+ extend: {
23
+ colors: Record<string, string>;
24
+ };
25
+ }
26
+ ];
27
+ } = {
28
+ defaultTheme: {
29
+ extend: {
30
+ colors: {},
31
+ },
32
+ },
33
+ themes: [
34
+ {
35
+ name: 'darkTheme',
36
+ extend: {
37
+ colors: {},
38
+ },
39
+ },
40
+ ],
41
+ };
42
+
43
+ Object.entries(colors).forEach(([key, value]) => {
44
+ options.defaultTheme.extend.colors[key] = value.light;
45
+ options.themes[0].extend.colors[key] = value.dark;
46
+ });
47
+ options.themes[0].selectors =
48
+ darkMode === 'class' ? ['.dark-mode', '[data-theme="dark"]'] : undefined;
49
+ options.themes[0].mediaQuery =
50
+ darkMode === 'media' ? '@media (prefers-color-scheme: dark)' : undefined;
51
+
52
+ return require('tailwindcss-themer')(options);
53
+ };