@openedx/paragon 23.9.0 → 23.10.0

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.
@@ -21,6 +21,20 @@
21
21
  "info": { "source": "$alert-info-border-color", "$value": "{color.theme.border.info}" },
22
22
  "danger": { "source": "$alert-danger-border-color", "$value": "{color.theme.border.danger}" },
23
23
  "warning": { "source": "$alert-warning-border-color", "$value": "{color.theme.border.warning}" }
24
+ },
25
+ "actions": {
26
+ "overrides": {
27
+ "button": {
28
+ "variants": {}
29
+ },
30
+ "examples": {
31
+ "button": {
32
+ "variants": {
33
+ "primary": "brand"
34
+ }
35
+ }
36
+ }
37
+ }
24
38
  }
25
39
  }
26
40
  }
@@ -6,7 +6,7 @@ const chalk = require('chalk');
6
6
  const chroma = require('chroma-js');
7
7
  const { colorYiq, darken, lighten } = require('./sass-helpers');
8
8
  const cssUtilities = require('./css-utilities');
9
- const { composeBreakpointName, processAndUpdateTokens } = require('./utils');
9
+ const { composeBreakpointName, processAndUpdateTokens, generateButtonVariantProperties } = require('./utils');
10
10
 
11
11
  /* eslint-disable import/no-unresolved */
12
12
  const getStyleDictionary = async () => (await import('style-dictionary')).default;
@@ -330,6 +330,69 @@ const initializeStyleDictionary = async ({ themes }) => {
330
330
  },
331
331
  });
332
332
 
333
+ /**
334
+ * Configuration for button variant overrides by component type.
335
+ * Each entry defines:
336
+ * - getTokens: Function that receives the dictionary tokens and returns the button variant overrides
337
+ * - selectors: Array of CSS selectors where the button variants should be overridden
338
+ */
339
+ const BUTTON_VARIANT_OVERRIDES_CONFIG = [
340
+ {
341
+ name: 'Alert',
342
+ getTokens: (tokens) => tokens?.color?.alert?.actions?.overrides?.button?.variants,
343
+ selectors: [
344
+ '.pgn__alert-message-wrapper .pgn__alert-actions',
345
+ '.pgn__alert-message-wrapper-stacked .pgn__alert-actions',
346
+ ],
347
+ },
348
+ // Add new component configurations here!
349
+ ];
350
+
351
+ /**
352
+ * Registers a custom format for generating CSS style overrides for button variants in different components.
353
+ * All overrides are combined into a single CSS file.
354
+ */
355
+ StyleDictionary.registerFormat({
356
+ name: 'css/component-button-variant-overrides',
357
+ format: async ({ dictionary }) => {
358
+ const { fileHeader } = await getStyleDictionaryUtils();
359
+ const header = await fileHeader({
360
+ file: 'overrides/component-button-variants.css',
361
+ formatting: 'css',
362
+ });
363
+ let hasOutputHeader = false;
364
+ let output = '';
365
+
366
+ // Process each component configuration
367
+ BUTTON_VARIANT_OVERRIDES_CONFIG.forEach((config) => {
368
+ const buttonVariantOverrides = config.getTokens(dictionary.tokens);
369
+
370
+ if (!buttonVariantOverrides || typeof buttonVariantOverrides !== 'object' || Object.keys(buttonVariantOverrides).length === 0) {
371
+ return; // No overrides tokens found, skip.
372
+ }
373
+
374
+ if (!hasOutputHeader) {
375
+ output += header;
376
+ hasOutputHeader = true;
377
+ }
378
+
379
+ output += `// ${config.name}\n\n`;
380
+
381
+ Object.entries(buttonVariantOverrides).forEach(([originalVariant, overrideVariant]) => {
382
+ const selectorOutput = config.selectors
383
+ .map(selector => `${selector} .btn-${originalVariant}`)
384
+ .join(',\n');
385
+
386
+ output += `${selectorOutput} {\n`;
387
+ output += ` ${generateButtonVariantProperties(overrideVariant)}\n`;
388
+ output += '}\n\n';
389
+ });
390
+ });
391
+
392
+ return output;
393
+ },
394
+ });
395
+
333
396
  /**
334
397
  * @typedef {function} StyleDictionaryFilterFunction
335
398
  * @param {import('style-dictionary/types').TransformedToken} token - The token object to filter.
package/tokens/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const readline = require('readline');
3
3
  const path = require('path');
4
+ const chalk = require('chalk');
4
5
 
5
6
  const visitedTokens = {};
6
7
 
@@ -328,14 +329,23 @@ async function transformInPath(location, variablesMap, transformType = 'definiti
328
329
  function createIndexCssFile({ buildDir = path.resolve(__dirname, '../styles/css'), isThemeVariant, themeVariant }) {
329
330
  const directoryPath = isThemeVariant ? `${buildDir}/themes/${themeVariant}` : `${buildDir}/core`;
330
331
 
331
- fs.readdir(directoryPath, (errDir, files) => {
332
- if (errDir) {
333
- // eslint-disable-next-line no-console
334
- console.error('Error reading directory:', errDir);
335
- return;
336
- }
332
+ // Recursively read all files in the directory, including subdirectories
333
+ const getAllCssFiles = (dir) => {
334
+ const files = fs.readdirSync(dir, { withFileTypes: true });
335
+ return files.flatMap((file) => {
336
+ const fullPath = path.join(dir, file.name);
337
+ if (file.isDirectory()) {
338
+ return getAllCssFiles(fullPath); // Recursively get files in subdirectories
339
+ }
340
+ if (file.isFile() && file.name.endsWith('.css') && file.name !== 'index.css') {
341
+ return fullPath; // Include only CSS files, excluding `index.css`
342
+ }
343
+ return [];
344
+ });
345
+ };
337
346
 
338
- const outputCssFiles = files.filter(file => file !== 'index.css');
347
+ try {
348
+ const cssFiles = getAllCssFiles(directoryPath);
339
349
 
340
350
  // For theme variants, files are ordered with variables first, abstraction variables second,
341
351
  // and utility classes last. This ensures that variables are available before other files use them.
@@ -344,19 +354,28 @@ function createIndexCssFile({ buildDir = path.resolve(__dirname, '../styles/css'
344
354
  ? [...commonCssFiles, 'utility-classes.css']
345
355
  : [...commonCssFiles, 'custom-media-breakpoints.css'];
346
356
 
347
- const sortedCssFiles = outputCssFiles.sort((a, b) => sortOrder.indexOf(a) - sortOrder.indexOf(b));
357
+ // Sort files based on the defined order
358
+ const sortedCssFiles = cssFiles.sort((a, b) => {
359
+ const aName = path.basename(a);
360
+ const bName = path.basename(b);
361
+ return sortOrder.indexOf(aName) - sortOrder.indexOf(bName);
362
+ });
348
363
 
349
- const exportStatements = sortedCssFiles.map((file) => `@import "${file}";`);
364
+ // Generate @import statements with relative paths
365
+ const exportStatements = sortedCssFiles.map((file) => {
366
+ // Get the relative path from the directory path to the file
367
+ const relativePath = path.relative(directoryPath, file).replace(/\\/g, '/');
368
+ return `@import "${relativePath}";`;
369
+ });
350
370
 
351
371
  const indexContent = `${exportStatements.join('\n')}\n`;
352
372
 
353
- fs.writeFile(path.join(directoryPath, 'index.css'), indexContent, (errFile) => {
354
- if (errFile) {
355
- // eslint-disable-next-line no-console
356
- console.error('Error creating index file:', errFile);
357
- }
358
- });
359
- });
373
+ // Write the `index.css` file
374
+ fs.writeFileSync(path.join(directoryPath, 'index.css'), indexContent);
375
+ } catch (error) {
376
+ // eslint-disable-next-line no-console
377
+ console.error(chalk.red(`Error creating index.css file: ${error}`));
378
+ }
360
379
  }
361
380
 
362
381
  /**
@@ -370,6 +389,33 @@ function composeBreakpointName(breakpointName, format) {
370
389
  return `@custom-media --${breakpointName.replace(/breakpoint/g, `breakpoint-${format}-width`)}`;
371
390
  }
372
391
 
392
+ /**
393
+ * Generates CSS custom properties for a button variant that matches the button-variant mixin
394
+ * @param {string} variant - The variant name (e.g., 'primary', 'brand')
395
+ * @returns {string} CSS custom properties for the button variant
396
+ */
397
+ function generateButtonVariantProperties(variant) {
398
+ const properties = [
399
+ `--pgn-btn-color: var(--pgn-color-btn-text-${variant});`,
400
+ `--pgn-btn-bg: var(--pgn-color-btn-bg-${variant});`,
401
+ `--pgn-btn-border-color: var(--pgn-color-btn-border-${variant});`,
402
+ `--pgn-btn-hover-color: var(--pgn-color-btn-hover-text-${variant});`,
403
+ `--pgn-btn-hover-bg: var(--pgn-color-btn-hover-bg-${variant});`,
404
+ `--pgn-btn-hover-border-color: var(--pgn-color-btn-hover-border-${variant});`,
405
+ `--pgn-btn-disabled-color: var(--pgn-color-btn-disabled-text-${variant});`,
406
+ `--pgn-btn-disabled-bg: var(--pgn-color-btn-disabled-bg-${variant});`,
407
+ `--pgn-btn-disabled-border-color: var(--pgn-color-btn-disabled-border-${variant});`,
408
+ `--pgn-btn-active-color: var(--pgn-color-btn-active-text-${variant});`,
409
+ `--pgn-btn-active-bg: var(--pgn-color-btn-active-bg-${variant});`,
410
+ `--pgn-btn-active-border-color: var(--pgn-color-btn-active-border-${variant});`,
411
+ `--pgn-btn-focus-outline-color: var(--pgn-color-btn-focus-outline-${variant});`,
412
+ `--pgn-btn-focus-color: var(--pgn-color-btn-focus-text-${variant});`,
413
+ `--pgn-btn-focus-border-color: var(--pgn-color-btn-focus-border-${variant});`,
414
+ `--pgn-btn-focus-bg: var(--pgn-color-btn-focus-bg-${variant});`,
415
+ ];
416
+ return properties.join('\n ');
417
+ }
418
+
373
419
  module.exports = {
374
420
  createIndexCssFile,
375
421
  getFilesWithExtension,
@@ -377,4 +423,5 @@ module.exports = {
377
423
  transformInPath,
378
424
  composeBreakpointName,
379
425
  processAndUpdateTokens,
426
+ generateButtonVariantProperties,
380
427
  };