@openedx/paragon 23.0.0-alpha.2 → 23.0.0-alpha.4

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.
Files changed (274) hide show
  1. package/bin/paragon-scripts.js +10 -0
  2. package/dist/Annotation/index.scss +16 -0
  3. package/dist/Button/index.d.ts +35 -0
  4. package/dist/Button/index.js +37 -15
  5. package/dist/Button/index.js.map +1 -1
  6. package/dist/Card/CardDeck.js +0 -2
  7. package/dist/Card/CardDeck.js.map +1 -1
  8. package/dist/Card/index.scss +6 -6
  9. package/dist/Carousel/index.scss +24 -1
  10. package/dist/Chip/ChipIcon.d.ts +13 -8
  11. package/dist/Chip/ChipIcon.js +0 -2
  12. package/dist/Chip/ChipIcon.js.map +1 -1
  13. package/dist/Chip/constants.d.ts +4 -0
  14. package/dist/Chip/constants.js +3 -2
  15. package/dist/Chip/constants.js.map +1 -0
  16. package/dist/Chip/index.d.ts +4 -3
  17. package/dist/Chip/index.js +2 -4
  18. package/dist/Chip/index.js.map +1 -1
  19. package/dist/ChipCarousel/index.js +0 -2
  20. package/dist/ChipCarousel/index.js.map +1 -1
  21. package/dist/CloseButton/index.scss +8 -0
  22. package/dist/ColorPicker/index.scss +1 -1
  23. package/dist/DataTable/index.scss +12 -0
  24. package/dist/Dropdown/dropdown-bootstrap.scss +6 -0
  25. package/dist/Dropzone/index.scss +34 -0
  26. package/dist/Form/_FormText.scss +1 -1
  27. package/dist/Form/_bootstrap-custom-forms.scss +40 -0
  28. package/dist/Form/_index.scss +9 -0
  29. package/dist/Form/_mixins.scss +22 -0
  30. package/dist/Hyperlink/index.d.ts +24 -0
  31. package/dist/Hyperlink/index.js +20 -32
  32. package/dist/Hyperlink/index.js.map +1 -1
  33. package/dist/Icon/index.d.ts +4 -2
  34. package/dist/Icon/index.js +1 -1
  35. package/dist/Icon/index.js.map +1 -1
  36. package/dist/IconButton/index.d.ts +342 -0
  37. package/dist/IconButton/index.js +18 -26
  38. package/dist/IconButton/index.js.map +1 -1
  39. package/dist/IconButton/index.scss +146 -0
  40. package/dist/Menu/index.scss +8 -0
  41. package/dist/Modal/ModalDialog.js +8 -4
  42. package/dist/Modal/ModalDialog.js.map +1 -1
  43. package/dist/Modal/ModalPopup.js +7 -1
  44. package/dist/Modal/ModalPopup.js.map +1 -1
  45. package/dist/Modal/_ModalDialog.scss +26 -2
  46. package/dist/Nav/index.scss +8 -0
  47. package/dist/Overlay/index.d.ts +128 -0
  48. package/dist/Overlay/index.js +8 -2
  49. package/dist/Overlay/index.js.map +1 -1
  50. package/dist/PageBanner/index.scss +2 -2
  51. package/dist/Pagination/pagination-bootstrap.scss +9 -0
  52. package/dist/Popover/index.scss +1 -1
  53. package/dist/ProductTour/Checkpoint.scss +1 -1
  54. package/dist/ProgressBar/bootstrap-progress.scss +20 -5
  55. package/dist/ProgressBar/index.scss +3 -3
  56. package/dist/Stepper/index.scss +1 -1
  57. package/dist/Sticky/index.scss +12 -0
  58. package/dist/Toast/index.scss +13 -1
  59. package/dist/Tooltip/index.d.ts +7 -0
  60. package/dist/Tooltip/index.js.map +1 -1
  61. package/dist/Tooltip/index.scss +16 -0
  62. package/dist/core.css +914 -470
  63. package/dist/core.css.map +1 -1
  64. package/dist/core.min.css +1 -1
  65. package/dist/index.d.ts +5 -5
  66. package/dist/index.js +7 -7
  67. package/dist/light.css +2035 -1315
  68. package/dist/light.css.map +1 -1
  69. package/dist/light.min.css +1 -1
  70. package/dist/setupTest.d.ts +2 -0
  71. package/dist/setupTest.js.map +1 -0
  72. package/dist/utils/types/bootstrap.d.ts +39 -0
  73. package/dist/utils/types/bootstrap.js +2 -0
  74. package/dist/utils/types/bootstrap.js.map +1 -0
  75. package/lib/build-tokens.js +67 -31
  76. package/package.json +11 -8
  77. package/src/Annotation/index.scss +16 -0
  78. package/src/Button/{Button.test.jsx → Button.test.tsx} +14 -2
  79. package/src/Button/__snapshots__/{Button.test.jsx.snap → Button.test.tsx.snap} +19 -2
  80. package/src/Button/{index.jsx → index.tsx} +58 -16
  81. package/src/Card/CardDeck.jsx +0 -3
  82. package/src/Card/README.md +0 -31
  83. package/src/Card/index.scss +6 -6
  84. package/src/Carousel/index.scss +24 -1
  85. package/src/Chip/{Chip.test.jsx → Chip.test.tsx} +5 -7
  86. package/src/Chip/ChipIcon.tsx +8 -8
  87. package/src/Chip/{constants.js → constants.ts} +1 -1
  88. package/src/Chip/index.tsx +6 -8
  89. package/src/ChipCarousel/index.tsx +0 -2
  90. package/src/CloseButton/index.scss +8 -0
  91. package/src/ColorPicker/index.scss +1 -1
  92. package/src/DataTable/index.scss +12 -0
  93. package/src/Dropdown/dropdown-bootstrap.scss +6 -0
  94. package/src/Dropzone/index.scss +34 -0
  95. package/src/Form/_FormText.scss +1 -1
  96. package/src/Form/_bootstrap-custom-forms.scss +40 -0
  97. package/src/Form/_index.scss +9 -0
  98. package/src/Form/_mixins.scss +22 -0
  99. package/src/Hyperlink/{Hyperlink.test.jsx → Hyperlink.test.tsx} +21 -10
  100. package/src/Hyperlink/{index.jsx → index.tsx} +41 -37
  101. package/src/Icon/index.d.ts +4 -2
  102. package/src/Icon/index.jsx +1 -1
  103. package/src/IconButton/{IconButton.test.jsx → IconButton.test.tsx} +24 -3
  104. package/src/IconButton/__snapshots__/IconButton.test.tsx.snap +90 -0
  105. package/src/IconButton/index.scss +146 -0
  106. package/src/IconButton/{index.jsx → index.tsx} +66 -26
  107. package/src/Menu/index.scss +8 -0
  108. package/src/Modal/ModalDialog.jsx +7 -3
  109. package/src/Modal/ModalPopup.jsx +9 -1
  110. package/src/Modal/_ModalDialog.scss +26 -2
  111. package/src/Modal/modal-dialog.mdx +95 -6
  112. package/src/Modal/tests/ModalDialog.test.jsx +2 -0
  113. package/src/Modal/tests/ModalPopupNoMock.test.jsx +29 -0
  114. package/src/Nav/index.scss +8 -0
  115. package/src/Overlay/{index.jsx → index.tsx} +13 -8
  116. package/src/PageBanner/index.scss +2 -2
  117. package/src/Pagination/pagination-bootstrap.scss +9 -0
  118. package/src/Popover/index.scss +1 -1
  119. package/src/ProductTour/Checkpoint.scss +1 -1
  120. package/src/ProgressBar/bootstrap-progress.scss +20 -5
  121. package/src/ProgressBar/index.scss +3 -3
  122. package/src/Stepper/index.scss +1 -1
  123. package/src/Sticky/index.scss +12 -0
  124. package/src/Toast/index.scss +13 -1
  125. package/src/Tooltip/index.scss +16 -0
  126. package/src/Tooltip/{index.jsx → index.tsx} +9 -3
  127. package/src/index.d.ts +5 -5
  128. package/src/index.js +7 -7
  129. package/src/{setupTest.js → setupTest.ts} +1 -0
  130. package/src/utils/types/bootstrap.test.tsx +86 -0
  131. package/src/utils/types/bootstrap.ts +43 -0
  132. package/styles/css/core/abstraction-variables.css +44 -0
  133. package/styles/css/core/custom-media-breakpoints.css +3 -4
  134. package/styles/css/core/index.css +2 -1
  135. package/styles/css/core/variables.css +494 -430
  136. package/styles/css/themes/light/abstraction-variables.css +304 -0
  137. package/styles/css/themes/light/index.css +1 -0
  138. package/styles/css/themes/light/utility-classes.css +2 -3
  139. package/styles/css/themes/light/variables.css +1753 -1334
  140. package/styles/scss/core/_typography.scss +16 -4
  141. package/styles/scss/core/_utilities.scss +7 -3
  142. package/styles/scss/core/_variables.scss +43 -30
  143. package/styles/scss/core/core.scss +1 -0
  144. package/tokens/src/core/alias/size.json +6 -5
  145. package/tokens/src/core/components/ActionRow.json +3 -2
  146. package/tokens/src/core/components/Alert.json +12 -10
  147. package/tokens/src/core/components/Annotation.json +9 -7
  148. package/tokens/src/core/components/Avatar.json +9 -9
  149. package/tokens/src/core/components/AvatarButton.json +4 -3
  150. package/tokens/src/core/components/Badge.json +12 -9
  151. package/tokens/src/core/components/Breadcrumb.json +7 -5
  152. package/tokens/src/core/components/Bubble.json +4 -3
  153. package/tokens/src/core/components/Button/core.json +35 -59
  154. package/tokens/src/core/components/Card.json +33 -44
  155. package/tokens/src/core/components/Carousel.json +39 -13
  156. package/tokens/src/core/components/Chip.json +13 -21
  157. package/tokens/src/core/components/ChipCarousel.json +4 -5
  158. package/tokens/src/core/components/CloseButton.json +2 -6
  159. package/tokens/src/core/components/Code.json +9 -8
  160. package/tokens/src/core/components/Collapsible.json +10 -13
  161. package/tokens/src/core/components/ColorPicker.json +3 -2
  162. package/tokens/src/core/components/Container.json +6 -5
  163. package/tokens/src/core/components/DataTable.json +17 -9
  164. package/tokens/src/core/components/Dropdown.json +24 -29
  165. package/tokens/src/core/components/Dropzone.json +5 -7
  166. package/tokens/src/core/components/Form/other.json +5 -4
  167. package/tokens/src/core/components/Form/size.json +72 -119
  168. package/tokens/src/core/components/Form/spacing.json +39 -83
  169. package/tokens/src/core/components/Form/transition.json +43 -7
  170. package/tokens/src/core/components/Form/typography.json +24 -88
  171. package/tokens/src/core/components/Icon.json +6 -5
  172. package/tokens/src/core/components/IconButton.json +4 -7
  173. package/tokens/src/core/components/Image.json +7 -6
  174. package/tokens/src/core/components/Menu.json +14 -12
  175. package/tokens/src/core/components/Modal.json +26 -21
  176. package/tokens/src/core/components/Nav.json +14 -16
  177. package/tokens/src/core/components/Navbar.json +15 -30
  178. package/tokens/src/core/components/Pagination.json +23 -24
  179. package/tokens/src/core/components/Popover.json +18 -14
  180. package/tokens/src/core/components/ProductTour.json +8 -14
  181. package/tokens/src/core/components/ProgressBar.json +29 -14
  182. package/tokens/src/core/components/SearchField.json +7 -9
  183. package/tokens/src/core/components/SelectableBox.json +4 -3
  184. package/tokens/src/core/components/Sheet.json +3 -2
  185. package/tokens/src/core/components/Spinner.json +9 -7
  186. package/tokens/src/core/components/Stack.json +2 -1
  187. package/tokens/src/core/components/Stepper.json +12 -14
  188. package/tokens/src/core/components/Sticky.json +2 -1
  189. package/tokens/src/core/components/Tab.json +8 -7
  190. package/tokens/src/core/components/Tabs.json +5 -5
  191. package/tokens/src/core/components/Toast.json +11 -8
  192. package/tokens/src/core/components/Tooltip.json +13 -11
  193. package/tokens/src/core/components/general/caret.json +5 -3
  194. package/tokens/src/core/components/general/headings.json +5 -4
  195. package/tokens/src/core/components/general/hr.json +3 -2
  196. package/tokens/src/core/components/general/input.json +19 -19
  197. package/tokens/src/core/components/general/link.json +13 -12
  198. package/tokens/src/core/components/general/list.json +9 -6
  199. package/tokens/src/core/components/general/text.json +6 -12
  200. package/tokens/src/core/global/breakpoints.json +25 -6
  201. package/tokens/src/core/global/elevation.json +55 -13
  202. package/tokens/src/core/global/other.json +5 -1
  203. package/tokens/src/core/global/spacing.json +70 -17
  204. package/tokens/src/core/global/transition.json +41 -4
  205. package/tokens/src/core/global/typography.json +248 -53
  206. package/tokens/src/core/utilities/color.json +35 -4
  207. package/tokens/src/themes/light/alias/color.json +276 -75
  208. package/tokens/src/themes/light/components/Alert.json +15 -26
  209. package/tokens/src/themes/light/components/Annotation.json +27 -13
  210. package/tokens/src/themes/light/components/Avatar.json +2 -1
  211. package/tokens/src/themes/light/components/Badge.json +57 -122
  212. package/tokens/src/themes/light/components/Breadcrumb.json +6 -5
  213. package/tokens/src/themes/light/components/Bubble.json +9 -8
  214. package/tokens/src/themes/light/components/Button/brand.json +171 -119
  215. package/tokens/src/themes/light/components/Button/core.json +8 -9
  216. package/tokens/src/themes/light/components/Button/danger.json +171 -112
  217. package/tokens/src/themes/light/components/Button/dark.json +188 -106
  218. package/tokens/src/themes/light/components/Button/info.json +186 -112
  219. package/tokens/src/themes/light/components/Button/light.json +186 -110
  220. package/tokens/src/themes/light/components/Button/primary.json +178 -116
  221. package/tokens/src/themes/light/components/Button/secondary.json +166 -132
  222. package/tokens/src/themes/light/components/Button/success.json +176 -117
  223. package/tokens/src/themes/light/components/Button/tertiary.json +34 -60
  224. package/tokens/src/themes/light/components/Button/warning.json +164 -128
  225. package/tokens/src/themes/light/components/Card.json +10 -21
  226. package/tokens/src/themes/light/components/Carousel.json +12 -11
  227. package/tokens/src/themes/light/components/Chip.json +14 -26
  228. package/tokens/src/themes/light/components/CloseButton.json +12 -2
  229. package/tokens/src/themes/light/components/Code.json +7 -9
  230. package/tokens/src/themes/light/components/DataTable.json +7 -11
  231. package/tokens/src/themes/light/components/Dropdown.json +17 -20
  232. package/tokens/src/themes/light/components/Dropzone.json +49 -11
  233. package/tokens/src/themes/light/components/Form/color.json +101 -155
  234. package/tokens/src/themes/light/components/Form/elevation.json +38 -42
  235. package/tokens/src/themes/light/components/Form/other.json +44 -41
  236. package/tokens/src/themes/light/components/IconButton.json +408 -256
  237. package/tokens/src/themes/light/components/Image.json +7 -4
  238. package/tokens/src/themes/light/components/Menu.json +12 -10
  239. package/tokens/src/themes/light/components/Modal.json +22 -12
  240. package/tokens/src/themes/light/components/Nav.json +82 -94
  241. package/tokens/src/themes/light/components/Navbar.json +32 -76
  242. package/tokens/src/themes/light/components/OverflowScroll.json +3 -1
  243. package/tokens/src/themes/light/components/PageBanner.json +11 -10
  244. package/tokens/src/themes/light/components/Pagination.json +19 -23
  245. package/tokens/src/themes/light/components/Popover.json +22 -27
  246. package/tokens/src/themes/light/components/ProductTour.json +9 -20
  247. package/tokens/src/themes/light/components/ProgressBar.json +12 -10
  248. package/tokens/src/themes/light/components/Scrollable.json +3 -3
  249. package/tokens/src/themes/light/components/SearchField.json +9 -9
  250. package/tokens/src/themes/light/components/Sheet.json +6 -7
  251. package/tokens/src/themes/light/components/Stepper.json +12 -17
  252. package/tokens/src/themes/light/components/Sticky.json +31 -6
  253. package/tokens/src/themes/light/components/Tab.json +47 -24
  254. package/tokens/src/themes/light/components/Toast.json +26 -14
  255. package/tokens/src/themes/light/components/Tooltip.json +25 -10
  256. package/tokens/src/themes/light/components/general/body.json +3 -2
  257. package/tokens/src/themes/light/components/general/headings.json +2 -1
  258. package/tokens/src/themes/light/components/general/hr.json +3 -6
  259. package/tokens/src/themes/light/components/general/input.json +11 -4
  260. package/tokens/src/themes/light/components/general/link.json +34 -43
  261. package/tokens/src/themes/light/components/general/list.json +15 -19
  262. package/tokens/src/themes/light/components/general/text.json +5 -6
  263. package/tokens/src/themes/light/global/color.json +1592 -867
  264. package/tokens/src/themes/light/global/elevation.json +481 -93
  265. package/tokens/style-dictionary.js +342 -144
  266. package/tokens/utils.js +176 -6
  267. package/src/IconButton/__snapshots__/IconButton.test.jsx.snap +0 -20
  268. package/tokens/src/core/global/display.json +0 -22
  269. /package/src/Button/{ButtonGroup.test.jsx → ButtonGroup.test.tsx} +0 -0
  270. /package/src/Button/{ButtonToolbar.test.jsx → ButtonToolbar.test.tsx} +0 -0
  271. /package/src/Button/__snapshots__/{ButtonGroup.test.jsx.snap → ButtonGroup.test.tsx.snap} +0 -0
  272. /package/src/Button/__snapshots__/{ButtonToolbar.test.jsx.snap → ButtonToolbar.test.tsx.snap} +0 -0
  273. /package/src/Chip/__snapshots__/{Chip.test.jsx.snap → Chip.test.tsx.snap} +0 -0
  274. /package/src/Tooltip/{Tooltip.test.jsx → Tooltip.test.tsx} +0 -0
@@ -2,30 +2,92 @@
2
2
  * This module creates and exports custom StyleDictionary instance for Paragon.
3
3
  */
4
4
  const toml = require('js-toml');
5
- const StyleDictionary = require('style-dictionary');
5
+ 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 } = require('./utils');
9
+ const { composeBreakpointName, processAndUpdateTokens } = require('./utils');
10
10
 
11
- const { fileHeader, sortByReference } = StyleDictionary.formatHelpers;
11
+ /* eslint-disable import/no-unresolved */
12
+ const getStyleDictionary = async () => (await import('style-dictionary')).default;
13
+ const getStyleDictionaryUtils = async () => import('style-dictionary/utils');
14
+ const getTokensStudioTransforms = async () => import('@tokens-studio/sd-transforms');
15
+ /* eslint-enable import/no-unresolved */
12
16
 
13
- const colorTransform = (token, theme) => {
17
+ /**
18
+ * @typedef {import('style-dictionary/types').DesignToken} DesignToken
19
+ */
20
+
21
+ /**
22
+ * @typedef ModifyColorYiq
23
+ * @property {'color-yiq'} type - The type of modification.
24
+ * @property {number} [amount] - The amount of modification to apply.
25
+ * @property {string} [otherColor] - The other color to mix with.
26
+ * @property {number} [light] - The light color to use for color-yiq.
27
+ * @property {number} [dark] - The dark color to use for color-yiq.
28
+ * @property {number} [threshold] - The threshold to use for color-yiq.
29
+ */
30
+
31
+ /**
32
+ * @typedef ModifyColorDarken
33
+ * @property {'darken'} type - The type of modification.
34
+ * @property {number} amount - The amount of modification to apply.
35
+ */
36
+
37
+ /**
38
+ * @typedef ModifyColorLighten
39
+ * @property {'lighten'} type - The type of modification.
40
+ * @property {number} amount - The amount of modification to apply.
41
+ */
42
+
43
+ /**
44
+ * @typedef ModifyColorMix
45
+ * @property {'mix'} type - The type of modification.
46
+ * @property {number} amount - The amount of modification to apply.
47
+ * @property {string} otherColor - The other color to mix with.
48
+ */
49
+
50
+ /**
51
+ * @typedef ModifyColorAlpha
52
+ * @property {'alpha'} type - The type of modification.
53
+ * @property {number} amount - The amount of modification to apply.
54
+ */
55
+
56
+ /**
57
+ * @typedef DesignTokenModify
58
+ * @type {ModifyColorYiq | ModifyColorDarken | ModifyColorLighten | ModifyColorMix | ModifyColorAlpha}
59
+ */
60
+
61
+ /**
62
+ * @typedef {DesignToken & {
63
+ * outputReferences?: boolean;
64
+ * modify?: DesignTokenModify[];
65
+ * }} ParagonDesignToken
66
+ */
67
+
68
+ /**
69
+ * Transforms a color token based on various modifications.
70
+ *
71
+ * @param {ParagonDesignToken} token - The token object containing color information and modifications.
72
+ * @param {string} themeVariant - The themeVariant object containing additional information for color transformations.
73
+ * @returns {string} - The transformed color value in hexadecimal format, including alpha if applicable.
74
+ */
75
+ const colorTransform = (token, themeVariant) => {
14
76
  const {
15
77
  name: tokenName,
16
- value,
78
+ $value,
17
79
  original,
18
- modify = [],
80
+ modify,
19
81
  } = token;
20
- const reservedColorValues = ['inherit', 'initial', 'revert', 'unset', 'currentColor'];
82
+ const reservedColorValues = ['inherit', 'initial', 'revert', 'unset', 'currentColor', 'none'];
21
83
 
22
- if (reservedColorValues.includes(original.value)) {
23
- return original.value;
84
+ if (reservedColorValues.includes(original.$value)) {
85
+ return original.$value;
24
86
  }
25
87
 
26
- let color = chroma(value);
88
+ let color = chroma($value);
27
89
 
28
- if (modify && modify.length > 0) {
90
+ if (modify?.length > 0) {
29
91
  modify.forEach((modifier) => {
30
92
  const { type, amount, otherColor } = modifier;
31
93
  switch (type) {
@@ -40,7 +102,7 @@ const colorTransform = (token, theme) => {
40
102
  light,
41
103
  dark,
42
104
  threshold,
43
- theme,
105
+ themeVariant,
44
106
  });
45
107
  break;
46
108
  }
@@ -50,8 +112,16 @@ const colorTransform = (token, theme) => {
50
112
  case 'lighten':
51
113
  color = lighten(color, amount);
52
114
  break;
53
- default:
115
+ default: {
116
+ if (!color[type]) {
117
+ // eslint-disable-next-line no-console
118
+ console.warn(
119
+ chalk.keyword('orange').bold(`[Paragon] Warning: Invalid color modification type "${type}" for ${tokenName}.`),
120
+ );
121
+ return;
122
+ }
54
123
  color = color[type](amount);
124
+ }
55
125
  }
56
126
  });
57
127
  }
@@ -65,165 +135,293 @@ const colorTransform = (token, theme) => {
65
135
  * 2. 'theme' to output only theme's variables (e.g, 'light' or 'dark'), if theme is not provided - only
66
136
  * core tokens are built.
67
137
  */
68
- const createCustomCSSVariables = ({
69
- formatterArgs,
70
- themeVariant,
71
- }) => {
138
+ const createCustomCSSVariables = async ({ formatterArgs }) => {
139
+ const { fileHeader, formattedVariables } = await getStyleDictionaryUtils();
72
140
  const { dictionary, options, file } = formatterArgs;
141
+ const { outputReferences, formatting } = options;
142
+ const variables = formattedVariables({
143
+ format: 'css',
144
+ dictionary,
145
+ outputReferences: (token) => {
146
+ // Formatter options configured to never output references
147
+ if (!outputReferences) {
148
+ return false;
149
+ }
150
+ // Token has modifications (e.g., mix, darken, lighten); the computed
151
+ // value should be output instead of the reference.
152
+ if (token.modify) {
153
+ return false;
154
+ }
155
+ // Formatter options configured to show output references, but handle when individual tokens might opt-out.
156
+ return token.outputReferences ?? true;
157
+ },
158
+ usesDtcg: true,
159
+ });
160
+ const header = await fileHeader({ file, formatting });
161
+ return `${header}:root {\n${variables}\n}\n`;
162
+ };
163
+
164
+ /**
165
+ * @typedef {type import("style-dictionary/types").StyleDictionary} StyleDictionary
166
+ */
73
167
 
74
- const outputTokens = themeVariant
75
- ? dictionary.allTokens.filter(token => token.filePath.includes(themeVariant))
76
- : dictionary.allTokens;
168
+ /**
169
+ * Initializes and configures Style Dictionary with custom transforms, formatters, filters, and parsers.
170
+ *
171
+ * @returns {Promise<StyleDictionary>} - A promise that resolves to the configured Style Dictionary instance.
172
+ */
173
+ const initializeStyleDictionary = async ({ themes }) => {
174
+ const StyleDictionary = await getStyleDictionary();
175
+ const sdUtils = await getStyleDictionaryUtils();
176
+ const {
177
+ register: registerTokensStudioTransforms,
178
+ getTransforms: tokensStudioTransforms,
179
+ } = await getTokensStudioTransforms();
77
180
 
78
- const variables = outputTokens.sort(sortByReference(dictionary)).map(token => {
79
- let { value } = token;
181
+ StyleDictionary.registerPreprocessor({
182
+ name: 'pgn-annotate-token-extensions-with-references',
183
+ preprocessor: (dictionary) => {
184
+ // Define the extension properties to add to the tokens $extensions object
185
+ const extensionProperties = [
186
+ {
187
+ name: 'isReferencedBySourceToken',
188
+ filter: tkn => tkn.isSource,
189
+ referenceTokenFilter: tkn => !tkn.isSource,
190
+ },
191
+ {
192
+ name: 'isReferencedByThemeVariant',
193
+ filter: tkn => themes.some(theme => tkn.filePath.includes(theme)),
194
+ referenceTokenFilter: tkn => !themes.some(theme => tkn.filePath.includes(theme)),
195
+ },
196
+ ];
80
197
 
81
- const outputReferencesForToken = (token.original.outputReferences === false) ? false : options.outputReferences;
198
+ // Pass the dictionary to the recursive function to process and update tokens in place
199
+ const dictionaryCopy = { ...dictionary };
200
+ processAndUpdateTokens(dictionary, extensionProperties, sdUtils, dictionaryCopy);
82
201
 
83
- if (dictionary.usesReference(token.original.value) && outputReferencesForToken) {
84
- const refs = dictionary.getReferences(token.original.value);
85
- refs.forEach(ref => {
86
- value = value.replace(ref.value, `var(--${ref.name})`);
87
- });
88
- }
202
+ // Return the updated dictionary
203
+ return dictionary;
204
+ },
205
+ });
89
206
 
90
- return ` --${token.name}: ${value};`;
91
- }).join('\n');
207
+ /**
208
+ * Registers transforms from @tokens-studio/sd-transforms.
209
+ */
210
+ registerTokensStudioTransforms(StyleDictionary);
92
211
 
93
- return `${fileHeader({ file })}:root {\n${variables}\n}\n`;
94
- };
212
+ /**
213
+ * Transforms tokens by applying SASS color functions to tokens.
214
+ */
215
+ StyleDictionary.registerTransform({
216
+ name: 'color/sass-color-functions',
217
+ transitive: true,
218
+ type: 'value',
219
+ filter: (token) => token.attributes?.category === 'color' || token.$value.toString().startsWith('#'),
220
+ transform: (token) => colorTransform(token),
221
+ });
95
222
 
96
- /**
97
- * Transformer that applies SASS color functions to tokens.
98
- */
99
- StyleDictionary.registerTransform({
100
- name: 'color/sass-color-functions',
101
- transitive: true,
102
- type: 'value',
103
- matcher(token) {
104
- return token.attributes.category === 'color' || token.value?.toString().startsWith('#');
105
- },
106
- transformer: colorTransform,
107
- });
223
+ /**
224
+ * Transforms that implements str-replace from SASS.
225
+ */
226
+ StyleDictionary.registerTransform({
227
+ name: 'str-replace',
228
+ transitive: true,
229
+ type: 'value',
230
+ filter: (token) => token.modify && token.modify[0].type === 'str-replace',
231
+ transform: (token) => {
232
+ const { $value, modify } = token;
233
+ const { toReplace, replaceWith } = modify[0];
234
+ return $value.replaceAll(toReplace, replaceWith);
235
+ },
236
+ });
108
237
 
109
- /**
110
- * Transforms that implements str-replace from SASS.
111
- */
112
- StyleDictionary.registerTransform({
113
- name: 'str-replace',
114
- transitive: true,
115
- type: 'value',
116
- matcher(token) {
117
- return token.modify && token.modify[0].type === 'str-replace';
118
- },
119
- transformer(token) {
120
- const { value, modify } = token;
121
- const { toReplace, replaceWith } = modify[0];
122
- return value.replaceAll(toReplace, replaceWith);
123
- },
124
- });
238
+ /**
239
+ * Registers a custom transform group for Paragon CSS.
240
+ */
241
+ const customTransforms = [
242
+ 'color/sass-color-functions',
243
+ 'str-replace',
244
+ ];
245
+ StyleDictionary.registerTransformGroup({
246
+ name: 'paragon-css',
247
+ transforms: [
248
+ ...tokensStudioTransforms({ platform: 'css' }),
249
+ ...StyleDictionary.hooks.transformGroups.css,
250
+ ...customTransforms,
251
+ ],
252
+ });
125
253
 
126
- /**
127
- * The custom formatter to create CSS variables for core tokens.
128
- */
129
- StyleDictionary.registerFormat({
130
- name: 'css/custom-variables',
131
- formatter: formatterArgs => createCustomCSSVariables({ formatterArgs }),
132
- });
254
+ /**
255
+ * The custom formatter to create CSS variables for core tokens.
256
+ */
257
+ StyleDictionary.registerFormat({
258
+ name: 'css/custom-variables',
259
+ format: formatterArgs => createCustomCSSVariables({ formatterArgs }),
260
+ });
133
261
 
134
- /**
135
- * Formatter to generate CSS utility classes.
136
- * Looks in ./src/utilities/ to get utility classes configuration, filters tokens by 'filters' object attributes
137
- * (see https://amzn.github.io/style-dictionary/#/tokens?id=category-type-item for possible keys in the object,
138
- * each key should have a list of valid values) and generates CSS classes with using functions defined in
139
- * 'utilityFunctionsToApply' list, those functions must be located in css-utilities.js module and return string.
140
- */
141
- StyleDictionary.registerFormat({
142
- name: 'css/utility-classes',
143
- formatter({ dictionary, file }) {
144
- const { utilities } = dictionary.properties;
262
+ /**
263
+ * Formatter to generate CSS utility classes.
264
+ * Looks in ./src/utilities/ to get utility classes configuration, filters tokens by 'filters' object attributes
265
+ * (see https://amzn.github.io/style-dictionary/#/tokens?id=category-type-item for possible keys in the object,
266
+ * each key should have a list of valid values) and generates CSS classes with using functions defined in
267
+ * 'utilityFunctionsToApply' list, those functions must be located in css-utilities.js module and return string.
268
+ */
269
+ StyleDictionary.registerFormat({
270
+ name: 'css/utility-classes',
271
+ format: async ({ dictionary, file, options = {} }) => {
272
+ const { formatting } = options;
273
+ const { fileHeader } = await getStyleDictionaryUtils();
274
+ const { utilities } = dictionary.tokens;
275
+ if (!utilities) {
276
+ return '';
277
+ }
145
278
 
146
- if (!utilities) {
147
- return '';
148
- }
279
+ let utilityClasses = '';
149
280
 
150
- let utilityClasses = '';
281
+ utilities.forEach(({ filters, utilityFunctionsToApply }) => {
282
+ let tokens = dictionary.allTokens;
151
283
 
152
- utilities.forEach(({ filters, utilityFunctionsToApply }) => {
153
- let tokens = dictionary.allTokens;
284
+ Object.entries(filters).forEach(([attributeName, allowedValues]) => {
285
+ tokens = tokens.filter((token) => allowedValues.includes(token.attributes[attributeName]));
286
+ });
154
287
 
155
- Object.entries(filters).forEach(([attributeName, allowedValues]) => {
156
- tokens = tokens.filter((token) => allowedValues.includes(token.attributes[attributeName]));
288
+ // eslint-disable-next-line no-restricted-syntax
289
+ for (const token of tokens) {
290
+ // Get action token by reference
291
+ const ref = sdUtils.getReferences(token.original.actions.default, dictionary.tokens)[0];
292
+ token.actions = { default: `var(--${ref.name})` };
293
+ // eslint-disable-next-line no-restricted-syntax
294
+ for (const funcName of utilityFunctionsToApply) {
295
+ utilityClasses += cssUtilities[funcName](token);
296
+ }
297
+ }
157
298
  });
299
+ const header = await fileHeader({ file, formatting });
300
+ return `${header}${utilityClasses}`;
301
+ },
302
+ });
158
303
 
159
- // eslint-disable-next-line no-restricted-syntax
160
- for (const token of tokens) {
161
- // Get action token by reference
162
- const ref = dictionary.getReferences(token.original.actions.default)[0];
163
- token.actions = { default: `var(--${ref.name})` };
164
- // eslint-disable-next-line no-restricted-syntax
165
- for (const funcName of utilityFunctionsToApply) {
166
- utilityClasses += cssUtilities[funcName](token);
304
+ /**
305
+ * Formatter to generate CSS custom media queries for responsive breakpoints.
306
+ * Gets input about existing tokens of the 'size' category,
307
+ * 'breakpoints' subcategory, and generates a CSS custom media queries.
308
+ */
309
+ StyleDictionary.registerFormat({
310
+ name: 'css/custom-media-breakpoints',
311
+ format: async ({ dictionary, file, options = {} }) => {
312
+ const { fileHeader } = await getStyleDictionaryUtils();
313
+ const { formatting } = options;
314
+ const { breakpoint } = dictionary.tokens.size;
315
+
316
+ let customMediaVariables = '';
317
+ const breakpoints = Object.values(breakpoint || {});
318
+
319
+ for (let i = 0; i < breakpoints.length; i++) {
320
+ const [currentBreakpoint, nextBreakpoint] = [breakpoints[i], breakpoints[i + 1]];
321
+ customMediaVariables
322
+ += `${composeBreakpointName(currentBreakpoint.name, 'min')} (min-width: ${currentBreakpoint.$value});\n`;
323
+ if (nextBreakpoint) {
324
+ customMediaVariables
325
+ += `${composeBreakpointName(currentBreakpoint.name, 'max')} (max-width: ${nextBreakpoint.$value});\n`;
167
326
  }
168
327
  }
169
- });
328
+ const header = await fileHeader({ file, formatting });
329
+ return `${header}${customMediaVariables}`;
330
+ },
331
+ });
170
332
 
171
- return fileHeader({ file }) + utilityClasses;
172
- },
173
- });
333
+ /**
334
+ * @typedef {function} StyleDictionaryFilterFunction
335
+ * @param {import('style-dictionary/types').TransformedToken} token - The token object to filter.
336
+ * @param {object} [opts] - The options object passed to the filter.
337
+ */
174
338
 
175
- /**
176
- * Formatter to generate CSS custom media queries for responsive breakpoints.
177
- * Gets input about existing tokens of the 'size' category,
178
- * 'breakpoints' subcategory, and generates a CSS custom media queries.
179
- */
180
- StyleDictionary.registerFormat({
181
- name: 'css/custom-media-breakpoints',
182
- formatter({ dictionary, file }) {
183
- const { size: { breakpoint } } = dictionary.properties;
184
-
185
- let customMediaVariables = '';
186
- const breakpoints = Object.values(breakpoint || {});
187
-
188
- for (let i = 0; i < breakpoints.length; i++) {
189
- const [currentBreakpoint, nextBreakpoint] = [breakpoints[i], breakpoints[i + 1]];
190
- customMediaVariables += `${composeBreakpointName(currentBreakpoint.name, 'min')} (min-width: ${currentBreakpoint.value});\n`;
191
- if (nextBreakpoint) {
192
- customMediaVariables += `${composeBreakpointName(currentBreakpoint.name, 'max')} (max-width: ${nextBreakpoint.value});\n`;
193
- }
339
+ /**
340
+ * @typedef {object} StyleDictionaryFilterOptions
341
+ * @property {boolean} hasThemeVariants - Indicates whether the filter should also be registered with theme variants.
342
+ */
343
+
344
+ /**
345
+ * Registers a custom filter with Style Dictionary.
346
+ * @param {string} name Name for the filter.
347
+ * @param {StyleDictionaryFilterFunction} filter Filter value or function.
348
+ * @param {StyleDictionaryFilterOptions} [filterOptions] Custom options for the filter.
349
+ */
350
+ function registerStyleDictionaryFilter(name, filter, filterOptions = {}) {
351
+ StyleDictionary.registerFilter({ name, filter });
352
+ if (filterOptions.hasThemeVariants) {
353
+ themes.forEach((themeVariant) => {
354
+ StyleDictionary.registerFilter({
355
+ name: `${name}.${themeVariant}`,
356
+ filter: (token, opts) => {
357
+ const paragonExtensions = token.$extensions?.['org.openedx.paragon'];
358
+ const isReferencedByThemeVariant = !!paragonExtensions?.isReferencedByThemeVariant;
359
+ const baseFilterResult = typeof filter === 'function' ? filter(token, opts) || isReferencedByThemeVariant : filter;
360
+ if (!baseFilterResult) {
361
+ return false;
362
+ }
363
+ return token.filePath.includes(themeVariant) || isReferencedByThemeVariant;
364
+ },
365
+ });
366
+ });
194
367
  }
368
+ }
195
369
 
196
- return fileHeader({ file }) + customMediaVariables;
197
- },
198
- });
370
+ const paragonFilters = [
371
+ /**
372
+ * Registers a filter `isSource` that filters output to only include source tokens.
373
+ */
374
+ {
375
+ name: 'isSource',
376
+ filter: (token) => {
377
+ const paragonExtensions = token.$extensions?.['org.openedx.paragon'];
378
+ const isReferencedBySourceToken = !!paragonExtensions?.isReferencedBySourceToken;
379
+ return token.isSource || isReferencedBySourceToken;
380
+ },
381
+ opts: { hasThemeVariants: true },
382
+ },
383
+ /**
384
+ * Registers filter(s) `isThemeVariant.{variant}` that only include the requested theme variant tokens.
385
+ */
386
+ ...themes.map((themeVariant) => ({
387
+ name: `isThemeVariant.${themeVariant}`,
388
+ filter: (token) => {
389
+ const isThemeVariantToken = token.filePath.includes(themeVariant);
390
+ const paragonExtensions = token.$extensions?.['org.openedx.paragon'];
391
+ const isReferencedByThemeVariant = !!paragonExtensions?.isReferencedByThemeVariant;
392
+ return isThemeVariantToken || isReferencedByThemeVariant;
393
+ },
394
+ })),
395
+ ];
396
+ paragonFilters.forEach(({ name, filter, opts }) => registerStyleDictionaryFilter(name, filter, opts));
199
397
 
200
- /**
201
- * Custom file header for custom and built-in formatters.
202
- */
203
- StyleDictionary.registerFileHeader({
204
- name: 'customFileHeader',
205
- fileHeader: (defaultMessage) => [
206
- 'IMPORTANT: This file is the result of assembling design tokens',
207
- ...defaultMessage,
208
- ],
209
- });
398
+ /**
399
+ * Registers a custom TOML parser with Style Dictionary.
400
+ */
401
+ StyleDictionary.registerParser({
402
+ name: 'toml-parser',
403
+ pattern: /\.toml$/,
404
+ parser: ({ contents }) => toml.load(contents),
405
+ });
210
406
 
211
- /**
212
- * Registers a filter `isSource` that filters output to only include tokens
213
- * that are marked as `isSource` in their metadata.
214
- */
215
- StyleDictionary.registerFilter({
216
- name: 'isSource',
217
- matcher: token => token?.isSource === true,
218
- });
407
+ /**
408
+ * Registers a custom fileHeader.
409
+ */
410
+ StyleDictionary.registerFileHeader({
411
+ name: 'customFileHeader',
412
+ fileHeader: (defaultMessage) => [
413
+ `${defaultMessage} while transforming design tokens.`,
414
+ 'See <root>/tokens/README.md for more details.',
415
+ ],
416
+ });
219
417
 
220
- StyleDictionary.registerParser({
221
- pattern: /\.toml$/,
222
- parse: ({ contents }) => toml.load(contents),
223
- });
418
+ return StyleDictionary;
419
+ };
224
420
 
225
421
  module.exports = {
226
- StyleDictionary,
422
+ initializeStyleDictionary,
423
+ getTokensStudioTransforms,
227
424
  createCustomCSSVariables,
228
425
  colorTransform,
426
+ getStyleDictionaryUtils,
229
427
  };