anubis-ui 1.2.15 → 1.2.18

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.
@@ -1,201 +1,340 @@
1
- import { config } from "../config.tool"
2
- import { IPreset } from "../../interfaces/preset.interface"
3
- import { log } from "../logger"
1
+ import { IRuleInfo } from '@/interfaces/preset.interface';
2
+ import { config } from '@tools/config.tool';
3
+ import { log } from '@tools/logger';
4
4
 
5
5
  const mapClassesIntoRules = (classes: string[]) => {
6
- const mappedRules = classes
7
- ?.map(cssClass => mapClassIntoRule(cssClass))
8
- ?.filter(rule => rule)
9
-
10
- log(`${mappedRules?.length} rules generated`)
11
-
12
- const rules = mappedRules?.join('\n')
13
- return rules
14
- }
15
-
16
- const mapClassIntoRule = (stringClass: string) => {
17
- const params = getClassInfos(stringClass)
18
-
19
- /**
20
- * _ If no variations are found, maybe it's just a color like bg-primary
21
- * _ So we need to check if the color exists to avoid useless computing
22
- * */
23
- if (!params.preset) {
24
- const { colorExists } = checkOpacity(params.color)
25
-
26
- if (!colorExists) {
27
- return
28
- }
29
- }
30
-
31
- /**
32
- * _ If the current QoL isn't standalone and doesn't have a variation (can be called without variation)
33
- * _ then no
34
- */
35
- if (!params.color && !params.preset?.standalone && !params.variationName) {
36
- return
37
- }
38
-
39
- const rule = mapIntoRule(params)
40
- return rule
41
- }
6
+ const usedVariations = new Map<string, string>();
7
+ const ruleInfos = classes
8
+ .map(cssClass => mapClassIntoRule(cssClass))
9
+ .filter(ruleInfo => ruleInfo !== null);
10
+
11
+ // Collecter les variations utilisés dans les règles générées
12
+ ruleInfos.forEach(ruleInfo => {
13
+ if (ruleInfo.variant && ruleInfo.variant.shouldExport) {
14
+ const variableName = `${ruleInfo.variant.prefix}-${ruleInfo.variant.variantName}`;
15
+ usedVariations.set(variableName, ruleInfo.variant.variantValue);
16
+ }
17
+ });
18
+
19
+ // Générer les règles CSS
20
+ const rules = generateCssRules(ruleInfos);
21
+
22
+ log(`${ruleInfos.length} rules generated`);
23
+
24
+ return {
25
+ rules,
26
+ variationsFromRules: Object.fromEntries(usedVariations),
27
+ };
28
+ };
29
+
30
+ const generateCssRules = (ruleInfos: IRuleInfo[]): string => {
31
+ return ruleInfos
32
+ .map(ruleInfo => `.${ruleInfo.selector} { ${ruleInfo.declaration} }`)
33
+ .join('\n');
34
+ };
35
+
36
+ const mapClassIntoRule = (stringClass: string): IRuleInfo | null => {
37
+ const params = getClassInfos(stringClass);
38
+
39
+ /**
40
+ * _ If no variations are found, maybe it's just a color like bg-primary
41
+ * _ So we need to check if the color exists to avoid useless computing
42
+ * */
43
+ if (!params.utility) {
44
+ const { colorExists } = getColorInfos(params.color);
45
+
46
+ if (!colorExists) {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * _ If the current QoL isn't standalone and doesn't have a variation (can be called without variation)
53
+ * _ then no
54
+ */
55
+ if (!params.color && !Object.keys(params.utility?.variations || []).includes('default') && !params.variationName) {
56
+ return null;
57
+ }
58
+
59
+ const ruleInfo = buildRuleInfo(params);
60
+ return ruleInfo;
61
+ };
42
62
 
43
63
  const getClassInfos = (stringClass: string) => {
44
- const { cleanedClass, state } = getStateInfos(stringClass)
45
- const { cleanedColor, prefix } = getPrefixInfos(cleanedClass)
46
- const { baseColor, preset, variation, variationName } = getPresetInfos({ cleanedColor, prefix })
47
-
48
- return {
49
- state,
50
-
51
- color: baseColor,
52
- prefix,
53
-
54
- preset,
55
- variation,
56
- variationName
57
- }
58
- }
64
+ const { cleanedClass, state } = getStateInfos(stringClass);
65
+ const { cleanedColor, prefix } = getPrefixInfos(cleanedClass);
66
+ const { color, baseColor, utility, variation, variationName } = getUtilityInfos({
67
+ cleanedColor,
68
+ prefix,
69
+ });
70
+
71
+ return {
72
+ state,
73
+
74
+ color,
75
+ baseColor,
76
+ prefix,
77
+
78
+ utility,
79
+ variation,
80
+ variationName,
81
+ };
82
+ };
59
83
 
60
84
  const getStateInfos = (stringClass: string) => {
61
- let state = undefined
62
-
63
- for (const configState of config.states) {
64
- if (!stringClass.startsWith(configState)) { continue }
65
-
66
- state = configState
67
- }
68
-
69
- const cleanedClass = !!state ? stringClass?.slice(state?.length + 1) : stringClass
70
- return {
71
- cleanedClass,
72
- state,
73
- }
74
- }
75
-
76
- const getPrefixInfos = (stringClass: string): { cleanedColor: string, prefix: string } => {
77
- const prefixes = [
78
- ...config.presets?.map(q => q.prefix),
79
- ...config.qol?.map(q => q.prefix)
80
- ]
81
-
82
- for (const prefix of prefixes) {
83
- if (!stringClass.startsWith(prefix)) { continue }
84
-
85
- return {
86
- cleanedColor: stringClass?.slice(prefix.length + 1),
87
- prefix
88
- }
89
- }
90
-
91
- console.log({ prefixes, stringClass })
92
- return { cleanedColor: stringClass, prefix: null }
93
- }
94
-
95
- const getPresetInfos = ({ cleanedColor, prefix }: { cleanedColor: string, prefix?: string }) => {
96
- /**
97
- * _ Find preset variants matching the prefix from the config
98
- * _ Since a prefix can be in multiple presets and qol, filter every matching prefixes then flatten everything
99
- * TODO fix first default occurence getting picked when duplicate
100
- * */
101
- const possiblePresets = [...config.presets, ...config.qol]
102
- ?.filter(p => p.prefix === prefix)
103
- ?.flat()
104
- if (!possiblePresets?.length) { return { matchingPreset: null, variation: null } }
105
-
106
- const { colorExists } = checkOpacity(cleanedColor)
107
-
108
- /**
109
- * Find the preset where the variations exists
110
- * If the color exists, it is a preset, so use the preset
111
- * */
112
- const matchingPreset = colorExists || !cleanedColor
113
- ? possiblePresets[0]
114
- : possiblePresets?.find(({ variations }) => !variations || Object.keys(variations)?.find(v => cleanedColor.endsWith(v)))
115
-
116
- if (!matchingPreset) {
117
- log(`No preset found for ${cleanedColor || prefix}`)
118
-
119
- return {
120
- matchingPreset,
121
- variation: null
122
- }
123
- }
124
-
125
- const possibleVariations = (matchingPreset.variations || { default: '' })
126
-
127
- const defaultVariation = 'default'
128
- const matchingVariation = Object.keys(possibleVariations)
129
- ?.find(v => cleanedColor === v || cleanedColor.endsWith(v))
130
-
131
- const variation = possibleVariations[matchingVariation || defaultVariation]
132
- const baseColor = matchingVariation
133
- ? cleanedColor?.slice(0, -matchingVariation?.length - 1)
134
- : cleanedColor
135
-
136
- return {
137
- baseColor,
138
- preset: matchingPreset,
139
- variationName: matchingVariation,
140
- variation,
141
- }
142
- }
143
-
144
- const mapIntoRule = ({ state, prefix, color, preset, variation, variationName }) => {
145
- // _ Set state selector
146
- let stateSelector = ''
147
- switch (state) {
148
- case 'hover':
149
- stateSelector = ':hover'
150
- break
151
-
152
- case 'not-hover':
153
- stateSelector = ':not(:hover)'
154
- break
155
- }
156
-
157
- let selector = `${prefix}${color ? `-${color}` : ''}${variationName ? `-${variationName}` : ''}`
158
- if (state) {
159
- selector = `${state}\\:${selector}${stateSelector}`
160
- }
161
-
162
- const colorVar = `var(--${color})`
163
- let declaration = preset.declaration
164
- ?.replace('${value}', variation)
165
- ?.replace('${color}', colorVar)
166
-
167
- if (!declaration.endsWith(';')) {
168
- declaration += ';'
169
- }
170
-
171
- if (!declaration.includes('!important')) {
172
- declaration = declaration
173
- ?.replace(';', ' !important;')
174
- }
175
-
176
- const rule = `.${selector} { ${declaration} }`
177
- return rule
178
- }
85
+ const state = config.states.find(configState =>
86
+ stringClass.startsWith(configState)
87
+ );
88
+
89
+ const cleanedClass = state
90
+ ? stringClass.slice(state.length + 1)
91
+ : stringClass;
92
+
93
+ return {
94
+ cleanedClass,
95
+ state,
96
+ };
97
+ };
98
+
99
+ const getPrefixInfos = (
100
+ stringClass: string
101
+ ): { cleanedColor: string; prefix: string } => {
102
+ const prefixes = [
103
+ ...config.utilities.map(u => u.prefix)
104
+ ];
105
+
106
+ for (const prefix of prefixes) {
107
+ if (!stringClass.startsWith(prefix)) {
108
+ continue;
109
+ }
110
+
111
+ return {
112
+ cleanedColor: stringClass.slice(prefix.length + 1),
113
+ prefix,
114
+ };
115
+ }
116
+
117
+ log(`No matching prefix found for class: ${stringClass}`);
118
+ return { cleanedColor: stringClass, prefix: null };
119
+ };
120
+
121
+ const getUtilityInfos = ({
122
+ cleanedColor,
123
+ prefix,
124
+ }: {
125
+ cleanedColor: string;
126
+ prefix?: string;
127
+ }) => {
128
+ /**
129
+ * Find utility variations matching the prefix from the config
130
+ * Since a prefix can be in multiple utilitys and qol, filter every matching prefixes
131
+ * TODO fix first default occurence getting picked when duplicate
132
+ */
133
+ const possibleUtility = [...config.utilities].filter(
134
+ p => p.prefix === prefix
135
+ );
136
+
137
+ if (!possibleUtility.length) {
138
+ return { matchingUtility: null, variation: null };
139
+ }
140
+
141
+ const { colorExists } = getColorInfos(cleanedColor);
142
+
143
+ /**
144
+ * Find the utility where the variations exist
145
+ * Logic:
146
+ * 1. If we have a valid color or no color specified, use the first utility
147
+ * 2. Otherwise, find a utility with a matching variation
148
+ */
149
+ let matchingUtility;
150
+
151
+ if (colorExists || !cleanedColor) {
152
+ // Valid color exists or no color specified - use first utility
153
+ matchingUtility = possibleUtility[0];
154
+ } else {
155
+ // Find utility with matching variation
156
+ matchingUtility = possibleUtility.find(({ variations }) => {
157
+ if (!variations) return true;
158
+
159
+ const mappedVariations = Array.isArray(variations) ? variations : Object.keys(variations)
160
+
161
+ return mappedVariations.some(
162
+ v => cleanedColor === v || cleanedColor.endsWith(v)
163
+ );
164
+ });
165
+ }
166
+
167
+ if (!matchingUtility) {
168
+ log(`No utility found for ${cleanedColor || prefix}`);
169
+
170
+ return {
171
+ matchingUtility,
172
+ variation: null,
173
+ };
174
+ }
175
+
176
+ if (!colorExists && !matchingUtility.variations) {
177
+ log(`Unknow stuff -> ${[prefix, cleanedColor].join('-')}`);
178
+
179
+ return {
180
+ matchingUtility,
181
+ variation: null,
182
+ };
183
+ }
184
+
185
+ const possibleVariations = matchingUtility.variations || { default: '' };
186
+
187
+ const defaultVariation = 'default';
188
+ /**
189
+ * Variation matching logic:
190
+ * 1. Check for exact match first (prevents "xl" matching when looking for "2xl")
191
+ * 2. Fall back to endsWith for edge cases where variation is a suffix
192
+ */
193
+ const exactVariation = Object.keys(possibleVariations).find(
194
+ v => cleanedColor === v
195
+ );
196
+ const closestVariation = Object.keys(possibleVariations).find(v =>
197
+ cleanedColor.endsWith(v)
198
+ );
199
+
200
+ const matchingVariation = exactVariation || closestVariation;
201
+
202
+ const variation = possibleVariations[matchingVariation || defaultVariation];
203
+ const color = matchingVariation
204
+ ? cleanedColor.slice(0, -matchingVariation.length - 1)
205
+ : cleanedColor;
206
+
207
+ const { baseColor } = getColorInfos(color)
208
+
209
+ return {
210
+ color,
211
+ baseColor,
212
+ utility: matchingUtility,
213
+ variationName: matchingVariation,
214
+ variation,
215
+ };
216
+ };
217
+
218
+ // Map state names to CSS pseudo-selectors
219
+ const stateSelectors: Record<string, string> = {
220
+ hover: ':hover',
221
+ 'not-hover': ':not(:hover)',
222
+ };
223
+
224
+ const buildRuleInfo = ({
225
+ state,
226
+ prefix,
227
+ color,
228
+ baseColor,
229
+ utility,
230
+ variation,
231
+ variationName,
232
+ }): IRuleInfo | null => {
233
+ // Get state selector from mapping
234
+ const stateSelector = state ? stateSelectors[state] || '' : '';
235
+
236
+ let selector = `${prefix}${color ? `-${color}` : ''}${
237
+ variationName ? `-${variationName}` : ''
238
+ }`;
239
+
240
+ if (state) {
241
+ selector = `${state}\\:${selector}${stateSelector}`;
242
+ }
243
+
244
+ // Vérifier que la couleur existe dans la config
245
+ if (baseColor && !config.colors[baseColor]) {
246
+ log(
247
+ `Color '${baseColor}' not found in colors config - skipping rule generation`
248
+ );
249
+ return null;
250
+ }
251
+
252
+ // Gérer les variations avec variables CSS ou valeurs directes
253
+ let variableToUse = variation;
254
+ let variantInfo = undefined;
255
+
256
+ // Vérifier si on doit exporter les variations en tant que variables CSS
257
+ const exportVariations = utility['export-variations'];
258
+ const useVariables = exportVariations === true;
259
+
260
+ if (variationName && variationName !== 'default') {
261
+ const variablePrefix = prefix;
262
+ const variableName = `${variablePrefix}-${variationName}`;
263
+
264
+ // Si export-variations: true, utiliser une variable CSS, sinon la valeur directe
265
+ if (useVariables) {
266
+ variableToUse = `var(--${variableName})`;
267
+ variantInfo = {
268
+ prefix,
269
+ variantName: variationName,
270
+ variantValue: variation,
271
+ shouldExport: true,
272
+ };
273
+ } else {
274
+ // Utiliser la valeur directe, pas de variable CSS
275
+ variableToUse = variation;
276
+ }
277
+ } else if (variation && variationName === 'default') {
278
+ // Pour les variations par défaut
279
+ const variableName = `${prefix}-default`;
280
+
281
+ if (useVariables) {
282
+ variableToUse = `var(--${variableName})`;
283
+ variantInfo = {
284
+ prefix,
285
+ variantName: 'default',
286
+ variantValue: variation,
287
+ shouldExport: true,
288
+ };
289
+ } else {
290
+ variableToUse = variation;
291
+ }
292
+ }
293
+
294
+ let declaration = utility.declaration
295
+ .replace('${value}', variableToUse)
296
+ .replace('${color}', color ? `var(--${color})` : '');
297
+
298
+ if (!declaration.endsWith(';')) {
299
+ declaration += ';';
300
+ }
301
+
302
+ if (!declaration.includes('!important')) {
303
+ declaration = declaration.replace(';', ' !important;');
304
+ }
305
+
306
+ return {
307
+ selector,
308
+ declaration,
309
+ color: color || undefined,
310
+ variant: variantInfo,
311
+ };
312
+ };
179
313
 
180
314
  /**
181
315
  * _ Check if a color includes opacity (ends with 2 digits)
182
316
  * * Opacity is included in the color name during mixin declaration
183
317
  * */
184
- const checkOpacity = (color: string) => {
185
- const opacityDetectionRegex = new RegExp(/(?:(\w-?)+)-\d{2}$/, 'gm') // Strings that end with two digits
186
- const isOpacity = opacityDetectionRegex.test(color)
187
-
188
- const baseColor = isOpacity ? color?.slice(0, -3) : color
189
- const colorExists = config.colors?.some(configColor => configColor === baseColor)
190
-
191
- return {
192
- colorExists,
193
- isOpacity,
194
- baseColor
195
- }
196
- }
197
-
198
- export {
199
- mapClassesIntoRules,
200
- mapClassIntoRule
201
- }
318
+ // Cache regex outside function to avoid recompilation on every call
319
+ const OPACITY_DETECTION_REGEX = /(?:(\w-?)+)-\d{2}$/; // Strings that end with two digits (e.g., primary-50)
320
+ const OPACITY_SUFFIX_LENGTH = 3; // Length of "-NN" format
321
+
322
+ const getColorInfos = (color: string) => {
323
+ const isOpacity = OPACITY_DETECTION_REGEX.test(color);
324
+
325
+ const baseColor = isOpacity
326
+ ? color.slice(0, -OPACITY_SUFFIX_LENGTH)
327
+ : color;
328
+ const colorExists = Object.keys(config.colors).some(
329
+ configColor => configColor === baseColor
330
+ );
331
+ color === 'primary-10' && console.log({ colorExists, baseColor })
332
+
333
+ return {
334
+ colorExists,
335
+ isOpacity,
336
+ baseColor,
337
+ };
338
+ };
339
+
340
+ export { mapClassesIntoRules };
@@ -0,0 +1,14 @@
1
+ import { IColor } from '@interfaces/color.interface';
2
+ import { defineColor } from '@tools/output/css.output';
3
+
4
+ const mapColorsIntoMixinDeclaration = (colors: IColor) => {
5
+ const mappedColors = Object.entries(colors)
6
+ ?.map(([colorName, { light, dark }]) =>
7
+ defineColor(colorName, light, dark)
8
+ )
9
+ ?.join('\n');
10
+
11
+ return mappedColors;
12
+ };
13
+
14
+ export { mapColorsIntoMixinDeclaration };
@@ -0,0 +1,105 @@
1
+ import packageJson from '../../../package.json';
2
+ const { version } = packageJson;
3
+
4
+ const header = `/**
5
+ * Anubis v.${version}
6
+ * Improba
7
+ * Released under the MIT License.
8
+ */`;
9
+
10
+ const mixin = `
11
+ $background-opacity: (
12
+ 10: 0.1,
13
+ 20: 0.2,
14
+ 30: 0.3,
15
+ 40: 0.4,
16
+ 50: 0.5,
17
+ 60: 0.6,
18
+ 70: 0.7,
19
+ 80: 0.8,
20
+ 90: 0.9
21
+ );
22
+
23
+ // Mixin that will automatically generate colors for light and/or dark themes (with opacity variations)
24
+ @mixin setRootColors ($name, $lightColor: null, $darkColor: null) {
25
+ :root {
26
+ @if $lightColor != null {
27
+ body.body--light {
28
+ #{"--"+$name}: $lightColor;
29
+
30
+ // Only generate opacity variations for non transparent colors
31
+ @if $lightColor != transparent {
32
+ @each $opacity, $multiplier in $background-opacity {
33
+ #{"--"+$name+"-"+$opacity}: #{rgba(red($lightColor), green($lightColor), blue($lightColor), $multiplier)};
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ @if $darkColor != null {
40
+ body.body--dark {
41
+ #{"--"+$name}: $darkColor;
42
+
43
+ // Only generate opacity variations for non transparent colors
44
+ @if $darkColor != transparent {
45
+ @each $opacity, $multiplier in $background-opacity {
46
+ #{"--"+$name+"-"+$opacity}: #{rgba(red($darkColor), green($darkColor), blue($darkColor), $multiplier)};
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ `;
54
+
55
+ const COLOR_COMMENT = `/**
56
+ * These colours will be mapped to css variables in
57
+ * the :root element on the html page.
58
+ *
59
+ * It allows you to write custom css/scss classes in your web
60
+ * components.
61
+ */`;
62
+
63
+ const VARIANT_COMMENT = `/**
64
+ * These css variables are generated automatically when Anubis
65
+ * detects that they are used in preset/qol in your config.
66
+ *
67
+ * It allows you to write custom css/scss classes in your web
68
+ * components like:
69
+ *
70
+ * .paragraph-small {
71
+ * font-size: var(--size-xs);
72
+ * font-weight: var(--weight-light);
73
+ * }
74
+ *
75
+ * (You can also force the generation of all variants from a
76
+ * preset/qol by setting the 'export-variations' to 'true')
77
+ */`;
78
+
79
+ const CLASS_COMMENT = `/**
80
+ * These are the css classes generated by Anubis based on your config
81
+ * and what was detected in your source files.
82
+ */`;
83
+
84
+ const defineColor = (colorName: string, light?: string, dark?: string) => {
85
+ // Handle cases where only dark is provided
86
+ if (!light && dark) {
87
+ return `@include setRootColors('${colorName}', null, ${dark});`;
88
+ }
89
+ // Handle cases where only light is provided or both are provided
90
+ return `@include setRootColors('${colorName}', ${light}${
91
+ dark ? ', ' + dark : ''
92
+ });`;
93
+ };
94
+
95
+ const getHeader = () => {
96
+ return `${header}${mixin}`;
97
+ };
98
+
99
+ export {
100
+ getHeader,
101
+ defineColor,
102
+ COLOR_COMMENT,
103
+ VARIANT_COMMENT,
104
+ CLASS_COMMENT,
105
+ };