@udixio/theme 1.0.0-beta.10
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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/app.container.d.ts +5 -0
- package/dist/app.module.d.ts +2 -0
- package/dist/app.service.d.ts +13 -0
- package/dist/color/color.interface.d.ts +8 -0
- package/dist/color/color.module.d.ts +2 -0
- package/dist/color/entities/color.entity.d.ts +42 -0
- package/dist/color/entities/index.d.ts +1 -0
- package/dist/color/index.d.ts +5 -0
- package/dist/color/models/default-color.model.d.ts +3 -0
- package/dist/color/models/index.d.ts +1 -0
- package/dist/color/services/color-manager.service.d.ts +18 -0
- package/dist/color/services/color.service.d.ts +21 -0
- package/dist/color/services/index.d.ts +2 -0
- package/dist/config/config.interface.d.ts +13 -0
- package/dist/config/config.module.d.ts +2 -0
- package/dist/config/config.service.d.ts +12 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/main.d.ts +3 -0
- package/dist/material-color-utilities/contrastCurve.d.ts +46 -0
- package/dist/material-color-utilities/dynamic_color.d.ts +171 -0
- package/dist/material-color-utilities/index.d.ts +3 -0
- package/dist/material-color-utilities/toneDeltaPair.d.ts +60 -0
- package/dist/plugin/index.d.ts +3 -0
- package/dist/plugin/plugin.abstract.d.ts +6 -0
- package/dist/plugin/plugin.module.d.ts +2 -0
- package/dist/plugin/plugin.service.d.ts +10 -0
- package/dist/plugins/tailwind/Tailwind.plugin.d.ts +14 -0
- package/dist/plugins/tailwind/index.d.ts +3 -0
- package/dist/plugins/tailwind/main.d.ts +10 -0
- package/dist/plugins/tailwind/plugins-tailwind/index.d.ts +2 -0
- package/dist/plugins/tailwind/plugins-tailwind/state.d.ts +4 -0
- package/dist/plugins/tailwind/plugins-tailwind/themer.d.ts +4 -0
- package/dist/theme/entities/index.d.ts +2 -0
- package/dist/theme/entities/scheme.entity.d.ts +15 -0
- package/dist/theme/entities/variant.entity.d.ts +7 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/models/index.d.ts +1 -0
- package/dist/theme/models/variant.model.d.ts +8 -0
- package/dist/theme/services/index.d.ts +3 -0
- package/dist/theme/services/scheme.service.d.ts +17 -0
- package/dist/theme/services/theme.service.d.ts +22 -0
- package/dist/theme/services/variant.service.d.ts +13 -0
- package/dist/theme/theme.module.d.ts +2 -0
- package/dist/theme.cjs.development.js +2193 -0
- package/dist/theme.cjs.development.js.map +1 -0
- package/dist/theme.cjs.production.min.js +2 -0
- package/dist/theme.cjs.production.min.js.map +1 -0
- package/dist/theme.esm.js +2157 -0
- package/dist/theme.esm.js.map +1 -0
- package/package.json +95 -0
- package/src/app.container.ts +46 -0
- package/src/app.module.ts +7 -0
- package/src/app.service.spec.ts +15 -0
- package/src/app.service.ts +23 -0
- package/src/color/color.interface.ts +13 -0
- package/src/color/color.module.ts +9 -0
- package/src/color/entities/color.entity.ts +71 -0
- package/src/color/entities/index.ts +1 -0
- package/src/color/index.ts +5 -0
- package/src/color/models/default-color.model.ts +300 -0
- package/src/color/models/index.ts +1 -0
- package/src/color/services/color-manager.service.ts +191 -0
- package/src/color/services/color.service.spec.ts +28 -0
- package/src/color/services/color.service.ts +75 -0
- package/src/color/services/index.ts +2 -0
- package/src/config/config.interface.ts +14 -0
- package/src/config/config.module.ts +7 -0
- package/src/config/config.service.ts +74 -0
- package/src/config/index.ts +3 -0
- package/src/index.ts +11 -0
- package/src/main.ts +14 -0
- package/src/material-color-utilities/contrastCurve.ts +63 -0
- package/src/material-color-utilities/dynamic_color.ts +450 -0
- package/src/material-color-utilities/index.ts +3 -0
- package/src/material-color-utilities/toneDeltaPair.ts +64 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin/plugin.abstract.ts +8 -0
- package/src/plugin/plugin.module.ts +7 -0
- package/src/plugin/plugin.service.ts +30 -0
- package/src/plugins/tailwind/Tailwind.plugin.ts +53 -0
- package/src/plugins/tailwind/index.ts +3 -0
- package/src/plugins/tailwind/main.ts +18 -0
- package/src/plugins/tailwind/plugins-tailwind/index.ts +2 -0
- package/src/plugins/tailwind/plugins-tailwind/state.ts +88 -0
- package/src/plugins/tailwind/plugins-tailwind/themer.ts +53 -0
- package/src/theme/entities/index.ts +2 -0
- package/src/theme/entities/scheme.entity.ts +44 -0
- package/src/theme/entities/variant.entity.ts +39 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/models/index.ts +1 -0
- package/src/theme/models/variant.model.ts +63 -0
- package/src/theme/services/index.ts +3 -0
- package/src/theme/services/scheme.service.ts +80 -0
- package/src/theme/services/theme.service.ts +74 -0
- package/src/theme/services/variant.service.ts +52 -0
- package/src/theme/theme.module.ts +9 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PluginAbstract } from './plugin.abstract';
|
|
2
|
+
import { AppService } from '../app.service';
|
|
3
|
+
|
|
4
|
+
export type PluginConstructor = new (
|
|
5
|
+
appService: AppService,
|
|
6
|
+
options: any
|
|
7
|
+
) => PluginAbstract;
|
|
8
|
+
|
|
9
|
+
export class PluginService {
|
|
10
|
+
private pluginInstances = new Map<string, PluginAbstract>();
|
|
11
|
+
private pluginConstructors = new Map<string, [PluginConstructor, object]>();
|
|
12
|
+
|
|
13
|
+
public addPlugin(plugin: PluginConstructor, config: object) {
|
|
14
|
+
this.pluginConstructors.set(plugin.name, [plugin, config]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public loadPlugins(appService: AppService) {
|
|
18
|
+
this.pluginConstructors.forEach(([plugin, option]) => {
|
|
19
|
+
this.pluginInstances.set(plugin.name, new plugin(appService, option));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getPlugin<T extends PluginAbstract>(
|
|
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;
|
|
29
|
+
}
|
|
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
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Hct, TonalPalette } from '@material/material-color-utilities';
|
|
2
|
+
|
|
3
|
+
export interface SchemeOptions {
|
|
4
|
+
sourceColorArgb: number;
|
|
5
|
+
contrastLevel: number;
|
|
6
|
+
isDark: boolean;
|
|
7
|
+
palettes: Map<string, TonalPalette>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SchemeEntity {
|
|
11
|
+
constructor(private options: SchemeOptions) {}
|
|
12
|
+
|
|
13
|
+
get contrastLevel() {
|
|
14
|
+
if (!this.options) {
|
|
15
|
+
throw new Error('Scheme options is not set');
|
|
16
|
+
}
|
|
17
|
+
return this.options.contrastLevel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get isDark() {
|
|
21
|
+
if (!this.options) {
|
|
22
|
+
throw new Error('Scheme options is not set');
|
|
23
|
+
}
|
|
24
|
+
return this.options.isDark;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get sourceColorHct() {
|
|
28
|
+
if (!this.options) {
|
|
29
|
+
throw new Error('Scheme options is not set');
|
|
30
|
+
}
|
|
31
|
+
return Hct.fromInt(this.options.sourceColorArgb);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getPalette(key: string): TonalPalette {
|
|
35
|
+
if (!this.options) {
|
|
36
|
+
throw new Error('Scheme options is not set');
|
|
37
|
+
}
|
|
38
|
+
const palette = this.options.palettes.get(key);
|
|
39
|
+
if (!palette) {
|
|
40
|
+
throw new Error(`Palette ${key} not found`);
|
|
41
|
+
}
|
|
42
|
+
return palette;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Hct,
|
|
3
|
+
sanitizeDegreesDouble,
|
|
4
|
+
TonalPalette,
|
|
5
|
+
} from '@material/material-color-utilities';
|
|
6
|
+
|
|
7
|
+
export const getRotatedHue = (
|
|
8
|
+
sourceColor: Hct,
|
|
9
|
+
hues: number[],
|
|
10
|
+
rotations: number[]
|
|
11
|
+
): number => {
|
|
12
|
+
const sourceHue = sourceColor.hue;
|
|
13
|
+
if (hues.length !== rotations.length) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`mismatch between hue length ${hues.length} & rotations ${rotations.length}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (rotations.length === 1) {
|
|
19
|
+
return sanitizeDegreesDouble(sourceColor.hue + rotations[0]);
|
|
20
|
+
}
|
|
21
|
+
const size = hues.length;
|
|
22
|
+
for (let i = 0; i <= size - 2; i++) {
|
|
23
|
+
const thisHue = hues[i];
|
|
24
|
+
const nextHue = hues[i + 1];
|
|
25
|
+
if (thisHue < sourceHue && sourceHue < nextHue) {
|
|
26
|
+
return sanitizeDegreesDouble(sourceHue + rotations[i]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// If this statement executes, something is wrong, there should have been a
|
|
30
|
+
// rotation found using the arrays.
|
|
31
|
+
return sourceHue;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class VariantEntity {
|
|
35
|
+
constructor(
|
|
36
|
+
public palettes: Record<string, (sourceColorHct: Hct) => TonalPalette> = {},
|
|
37
|
+
public customPalettes?: (colorHct: Hct) => TonalPalette
|
|
38
|
+
) {}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './variant.model';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeDegreesDouble,
|
|
3
|
+
TonalPalette,
|
|
4
|
+
} from '@material/material-color-utilities';
|
|
5
|
+
import { getRotatedHue, VariantEntity } from '../entities/variant.entity';
|
|
6
|
+
|
|
7
|
+
export class VariantModel {
|
|
8
|
+
static tonalSpot: VariantEntity = {
|
|
9
|
+
palettes: {
|
|
10
|
+
primary: (sourceColorHct) =>
|
|
11
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 36.0),
|
|
12
|
+
secondary: (sourceColorHct) =>
|
|
13
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 16.0),
|
|
14
|
+
tertiary: (sourceColorHct) =>
|
|
15
|
+
TonalPalette.fromHueAndChroma(
|
|
16
|
+
sanitizeDegreesDouble(sourceColorHct.hue + 60.0),
|
|
17
|
+
24.0
|
|
18
|
+
),
|
|
19
|
+
neutral: (sourceColorHct) =>
|
|
20
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 6.0),
|
|
21
|
+
neutralVariant: (sourceColorHct) =>
|
|
22
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 8.0),
|
|
23
|
+
},
|
|
24
|
+
customPalettes: (colorHct) =>
|
|
25
|
+
TonalPalette.fromHueAndChroma(colorHct.hue, 16),
|
|
26
|
+
};
|
|
27
|
+
static vibrant: VariantEntity = {
|
|
28
|
+
palettes: {
|
|
29
|
+
primary: (sourceColorHct) =>
|
|
30
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 200.0),
|
|
31
|
+
secondary: (sourceColorHct) =>
|
|
32
|
+
TonalPalette.fromHueAndChroma(
|
|
33
|
+
getRotatedHue(sourceColorHct, this.hues, this.secondaryRotations),
|
|
34
|
+
24.0
|
|
35
|
+
),
|
|
36
|
+
tertiary: (sourceColorHct) =>
|
|
37
|
+
TonalPalette.fromHueAndChroma(
|
|
38
|
+
getRotatedHue(sourceColorHct, this.hues, this.tertiaryRotations),
|
|
39
|
+
32.0
|
|
40
|
+
),
|
|
41
|
+
neutral: (sourceColorHct) =>
|
|
42
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 6.0),
|
|
43
|
+
neutralVariant: (sourceColorHct) =>
|
|
44
|
+
TonalPalette.fromHueAndChroma(sourceColorHct.hue, 8.0),
|
|
45
|
+
},
|
|
46
|
+
customPalettes: (colorHct) =>
|
|
47
|
+
TonalPalette.fromHueAndChroma(
|
|
48
|
+
getRotatedHue(colorHct, this.hues, this.secondaryRotations),
|
|
49
|
+
24.0
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
private static readonly hues = [
|
|
54
|
+
0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
private static readonly secondaryRotations = [
|
|
58
|
+
18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0,
|
|
59
|
+
];
|
|
60
|
+
private static readonly tertiaryRotations = [
|
|
61
|
+
35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SchemeEntity, SchemeOptions } from '../entities/scheme.entity';
|
|
2
|
+
import {
|
|
3
|
+
argbFromHex,
|
|
4
|
+
Hct,
|
|
5
|
+
TonalPalette,
|
|
6
|
+
} from '@material/material-color-utilities';
|
|
7
|
+
|
|
8
|
+
export type SchemeServiceOptions = Omit<
|
|
9
|
+
SchemeOptions,
|
|
10
|
+
'palettes' | 'sourceColorArgb'
|
|
11
|
+
> & {
|
|
12
|
+
sourcesColorHex: Record<string, string> & { primary?: string };
|
|
13
|
+
palettes: Record<
|
|
14
|
+
string,
|
|
15
|
+
{
|
|
16
|
+
sourceColorkey?: string;
|
|
17
|
+
tonalPalette: (sourceColorHct: Hct) => TonalPalette;
|
|
18
|
+
}
|
|
19
|
+
>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class SchemeService {
|
|
23
|
+
private schemeEntity?: SchemeEntity;
|
|
24
|
+
private options?: SchemeServiceOptions;
|
|
25
|
+
|
|
26
|
+
createOrUpdate(options: Partial<SchemeServiceOptions>) {
|
|
27
|
+
this.options = {
|
|
28
|
+
...this.options,
|
|
29
|
+
...options,
|
|
30
|
+
sourcesColorHex: {
|
|
31
|
+
...this.options?.sourcesColorHex,
|
|
32
|
+
...options.sourcesColorHex,
|
|
33
|
+
},
|
|
34
|
+
palettes: {
|
|
35
|
+
...this.options?.palettes,
|
|
36
|
+
...options.palettes,
|
|
37
|
+
},
|
|
38
|
+
} as SchemeServiceOptions;
|
|
39
|
+
const palettes = new Map<string, TonalPalette>();
|
|
40
|
+
|
|
41
|
+
if (!this.options.sourcesColorHex.primary) {
|
|
42
|
+
throw new Error('Primary source color is not set');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sourceColorArgb = argbFromHex(this.options.sourcesColorHex.primary);
|
|
46
|
+
const sourceColorHct: Hct = Hct.fromInt(sourceColorArgb);
|
|
47
|
+
|
|
48
|
+
if (!this.options.palettes) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const [
|
|
52
|
+
key,
|
|
53
|
+
{ sourceColorkey, tonalPalette: paletteFunction },
|
|
54
|
+
] of Object.entries(this.options.palettes)) {
|
|
55
|
+
let palette: TonalPalette;
|
|
56
|
+
if (!sourceColorkey) {
|
|
57
|
+
palette = paletteFunction(sourceColorHct);
|
|
58
|
+
} else {
|
|
59
|
+
const sourceColorArgb = argbFromHex(
|
|
60
|
+
this.options.sourcesColorHex[sourceColorkey]
|
|
61
|
+
);
|
|
62
|
+
const sourceColorHct: Hct = Hct.fromInt(sourceColorArgb);
|
|
63
|
+
palette = paletteFunction(sourceColorHct);
|
|
64
|
+
}
|
|
65
|
+
palettes.set(key, palette);
|
|
66
|
+
}
|
|
67
|
+
this.schemeEntity = new SchemeEntity({
|
|
68
|
+
...this.options,
|
|
69
|
+
palettes: palettes,
|
|
70
|
+
sourceColorArgb: sourceColorArgb,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get(): SchemeEntity {
|
|
75
|
+
if (!this.schemeEntity) {
|
|
76
|
+
throw new Error('Scheme is not created');
|
|
77
|
+
}
|
|
78
|
+
return this.schemeEntity;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { DynamicColor } from '@material/material-color-utilities';
|
|
2
|
+
import { SchemeService, SchemeServiceOptions } from './scheme.service';
|
|
3
|
+
import { VariantService } from './variant.service';
|
|
4
|
+
import { VariantEntity } from '../entities/variant.entity';
|
|
5
|
+
|
|
6
|
+
type ThemeOptions = Omit<
|
|
7
|
+
SchemeServiceOptions,
|
|
8
|
+
'palettes' | 'sourcesColorHex'
|
|
9
|
+
> & { sourceColorHex: string };
|
|
10
|
+
|
|
11
|
+
const colorPaletteKeyColor = DynamicColor.fromPalette({
|
|
12
|
+
name: 'primary_palette_key_color',
|
|
13
|
+
palette: (s) => s.primaryPalette,
|
|
14
|
+
tone: (s) => s.primaryPalette.keyColor.tone,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export class ThemeService {
|
|
18
|
+
private readonly schemeService: SchemeService;
|
|
19
|
+
private readonly variantService: VariantService;
|
|
20
|
+
constructor({
|
|
21
|
+
schemeService,
|
|
22
|
+
variantService,
|
|
23
|
+
}: {
|
|
24
|
+
schemeService: SchemeService;
|
|
25
|
+
variantService: VariantService;
|
|
26
|
+
}) {
|
|
27
|
+
this.schemeService = schemeService;
|
|
28
|
+
this.variantService = variantService;
|
|
29
|
+
|
|
30
|
+
// this.addPalette({key: "primary", addDefaultColors: true})
|
|
31
|
+
// this.addPalette({key: "secondary", addDefaultColors: true})
|
|
32
|
+
// this.addPalette({key: "tertiary", addDefaultColors: true})
|
|
33
|
+
// this.addPalette({key: "error", palette: TonalPalette.fromHueAndChroma(25.0, 84.0)})
|
|
34
|
+
// this.addPalette({key: "neutral"})
|
|
35
|
+
// this.addPalette({key: "neutralVariant"})
|
|
36
|
+
}
|
|
37
|
+
// addPalette({key, palette, addDefaultColors}: {key: string; palette: TonalPalette; addDefaultColors: boolean}) {
|
|
38
|
+
// this.themeOptions.palettes.set(key, palette);
|
|
39
|
+
// if (addDefaultColors){
|
|
40
|
+
// this.colorService.addPalette(key)
|
|
41
|
+
// }
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
// create(args: ThemeOptions): SchemeService {
|
|
45
|
+
// return new SchemeService(args, this.colorService)
|
|
46
|
+
// }
|
|
47
|
+
//
|
|
48
|
+
// update(options: Partial<ThemeOptions>): SchemeService {
|
|
49
|
+
// Object.assign(this.themeOptions, options);
|
|
50
|
+
// return this.theme();
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
create(options: ThemeOptions & { variant: VariantEntity }) {
|
|
54
|
+
this.schemeService.createOrUpdate({
|
|
55
|
+
...options,
|
|
56
|
+
sourcesColorHex: { primary: options.sourceColorHex },
|
|
57
|
+
});
|
|
58
|
+
this.variantService.set(options.variant);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
update(options: Partial<ThemeOptions> & { variant?: VariantEntity }) {
|
|
62
|
+
const themeOptions: Partial<SchemeServiceOptions> = { ...options };
|
|
63
|
+
if (options.sourceColorHex)
|
|
64
|
+
themeOptions.sourcesColorHex = { primary: options.sourceColorHex };
|
|
65
|
+
this.schemeService.createOrUpdate(themeOptions);
|
|
66
|
+
if (options.variant) this.variantService.set(options.variant);
|
|
67
|
+
}
|
|
68
|
+
addCustomPalette(key: string, colorHex: string) {
|
|
69
|
+
this.variantService.addCustomPalette(key, colorHex);
|
|
70
|
+
}
|
|
71
|
+
// theme(): SchemeService {
|
|
72
|
+
// return new SchemeService(this.themeOptions, this.colorService)
|
|
73
|
+
// }
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { SchemeService } from './scheme.service';
|
|
2
|
+
import { VariantEntity } from '../entities/variant.entity';
|
|
3
|
+
import { Hct, TonalPalette } from '@material/material-color-utilities';
|
|
4
|
+
|
|
5
|
+
export class VariantService {
|
|
6
|
+
public customPalettes: Record<string, string> = {};
|
|
7
|
+
private variantEntity?: VariantEntity;
|
|
8
|
+
|
|
9
|
+
private readonly schemeService: SchemeService;
|
|
10
|
+
constructor({ schemeService }: { schemeService: SchemeService }) {
|
|
11
|
+
this.schemeService = schemeService;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
addCustomPalette(key: string, colorHex: string) {
|
|
15
|
+
this.customPalettes[key] = colorHex;
|
|
16
|
+
this.update();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set(variantEntity: VariantEntity) {
|
|
20
|
+
this.variantEntity = variantEntity;
|
|
21
|
+
if (!variantEntity.palettes.error) {
|
|
22
|
+
variantEntity.palettes.error = () =>
|
|
23
|
+
TonalPalette.fromHueAndChroma(25.0, 84.0);
|
|
24
|
+
}
|
|
25
|
+
this.update();
|
|
26
|
+
}
|
|
27
|
+
private update() {
|
|
28
|
+
if (!this.variantEntity) return;
|
|
29
|
+
const palettes: Record<
|
|
30
|
+
string,
|
|
31
|
+
{
|
|
32
|
+
sourceColorkey?: string;
|
|
33
|
+
tonalPalette: (sourceColorHct: Hct) => TonalPalette;
|
|
34
|
+
}
|
|
35
|
+
> = {};
|
|
36
|
+
Object.keys(this.variantEntity.palettes).forEach((key) => {
|
|
37
|
+
palettes[key] = { tonalPalette: this.variantEntity!.palettes[key] };
|
|
38
|
+
});
|
|
39
|
+
if (this.variantEntity.customPalettes) {
|
|
40
|
+
Object.keys(this.customPalettes).forEach((key) => {
|
|
41
|
+
palettes[key] = {
|
|
42
|
+
sourceColorkey: key,
|
|
43
|
+
tonalPalette: this.variantEntity!.customPalettes!,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
this.schemeService.createOrUpdate({
|
|
48
|
+
sourcesColorHex: this.customPalettes,
|
|
49
|
+
palettes: palettes,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SchemeService, ThemeService, VariantService } from './services';
|
|
2
|
+
import { asClass } from 'awilix';
|
|
3
|
+
import { Module } from '../app.container';
|
|
4
|
+
|
|
5
|
+
export const ThemeModule: Module = {
|
|
6
|
+
schemeService: asClass(SchemeService).singleton(),
|
|
7
|
+
variantService: asClass(VariantService).singleton(),
|
|
8
|
+
themeService: asClass(ThemeService).singleton(),
|
|
9
|
+
};
|