@codecademy/brand 3.0.0-alpha.2ba7921cce.0 → 3.0.0-alpha.39691db5eb.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.
Files changed (74) hide show
  1. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogDropdown/index.js +26 -5
  2. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/NavPanels.d.ts +4 -0
  3. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/NavPanels.js +98 -0
  4. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/NavSection.d.ts +15 -0
  5. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/NavSection.js +67 -0
  6. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/consts.d.ts +7 -0
  7. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/consts.js +39 -0
  8. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/index.d.ts +11 -0
  9. package/dist/AppHeader/AppHeaderElements/AppHeaderCatalogSectionVariant/index.js +68 -0
  10. package/dist/AppHeader/AppHeaderElements/AppHeaderLinkSections/index.js +0 -0
  11. package/dist/Banner/EnhancedBanner.d.ts +1 -1
  12. package/dist/Banner/EnhancedBanner.js +5 -5
  13. package/dist/Banner/index.d.ts +1 -11
  14. package/dist/Banner/index.js +3 -24
  15. package/dist/CareerPathCard/index.d.ts +4 -1
  16. package/dist/CareerPathCard/index.js +15 -7
  17. package/dist/CertificationPathCard/index.d.ts +4 -1
  18. package/dist/CertificationPathCard/index.js +17 -9
  19. package/dist/ContentGroupBaseCard/index.d.ts +5 -3
  20. package/dist/ContentGroupBaseCard/index.js +129 -72
  21. package/dist/ContentGroupBaseCard/shared.js +29 -23
  22. package/dist/CourseCard/index.d.ts +4 -1
  23. package/dist/CourseCard/index.js +15 -11
  24. package/dist/CourseRecommendationsLink/index.d.ts +3 -0
  25. package/dist/CourseRecommendationsLink/index.js +27 -0
  26. package/dist/GlobalHeader/GlobalHeaderItems.js +1 -1
  27. package/dist/GlobalHeader/context.d.ts +8 -0
  28. package/dist/GlobalHeader/context.js +9 -0
  29. package/dist/GlobalHeader/index.d.ts +1 -0
  30. package/dist/GlobalHeader/index.js +47 -37
  31. package/dist/GlobalHeader/types.d.ts +1 -0
  32. package/dist/GlobalPage/index.d.ts +2 -2
  33. package/dist/GlobalPage/index.js +2 -2
  34. package/dist/LayoutMenu/AccordionMenu.d.ts +1 -0
  35. package/dist/LayoutMenu/AccordionMenu.js +5 -2
  36. package/dist/LayoutMenu/LayoutMenu.d.ts +2 -1
  37. package/dist/LayoutMenu/LayoutMenu.js +9 -7
  38. package/dist/LayoutMenuVariant/AccordionMenu.js +4 -4
  39. package/dist/Logos/shared.d.ts +7 -0
  40. package/dist/Recurly/context/NoProvider.d.ts +8 -0
  41. package/dist/Recurly/context/NoProvider.js +7 -0
  42. package/dist/Recurly/context/elements/index.d.ts +26 -0
  43. package/dist/Recurly/context/elements/index.js +46 -0
  44. package/dist/Recurly/context/index.d.ts +2 -0
  45. package/dist/Recurly/context/index.js +2 -0
  46. package/dist/Recurly/context/recurly/index.d.ts +30 -0
  47. package/dist/Recurly/context/recurly/index.js +50 -0
  48. package/dist/Recurly/context/recurly/loadRecurly.d.ts +6 -0
  49. package/dist/Recurly/context/recurly/loadRecurly.js +22 -0
  50. package/dist/Recurly/context/recurly/w.d.ts +14 -0
  51. package/dist/Recurly/context/recurly/w.js +4 -0
  52. package/dist/Recurly/fields/RecurlyField.d.ts +9 -0
  53. package/dist/Recurly/fields/RecurlyField.js +57 -0
  54. package/dist/Recurly/fields/elements.d.ts +17 -0
  55. package/dist/Recurly/fields/elements.js +106 -0
  56. package/dist/Recurly/fields/elements.test.js +79 -0
  57. package/dist/Recurly/fields/index.d.ts +2 -0
  58. package/dist/Recurly/fields/index.js +2 -0
  59. package/dist/Recurly/index.d.ts +2 -0
  60. package/dist/Recurly/index.js +2 -0
  61. package/dist/SkillPathCard/index.d.ts +4 -1
  62. package/dist/SkillPathCard/index.js +15 -7
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.js +2 -0
  65. package/dist/stories/Molecules/ContentGroupBaseCard.stories.js +3 -1
  66. package/dist/stories/Molecules/CurriculumCards/CareerPathCard.stories.js +3 -1
  67. package/dist/stories/Molecules/CurriculumCards/CertificationPathCard.stories.js +3 -1
  68. package/dist/stories/Molecules/CurriculumCards/CourseCard.stories.js +3 -1
  69. package/dist/stories/Molecules/CurriculumCards/SkillPathCard.stories.js +3 -1
  70. package/dist/stories/Molecules/EnhancedBanner.stories.d.ts +3 -3
  71. package/dist/stories/Molecules/EnhancedBanner.stories.js +2 -12
  72. package/dist/stories/Molecules/Recurly.stories.d.ts +5 -0
  73. package/dist/stories/Molecules/Recurly.stories.js +56 -0
  74. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ import type { Recurly } from '@recurly/recurly-js';
2
+ type Doc = {
3
+ createElement: Document['createElement'];
4
+ head: {
5
+ appendChild: Document['head']['appendChild'];
6
+ };
7
+ };
8
+ export type W = {
9
+ recurly?: Recurly;
10
+ _recurlyPromise?: Promise<Recurly>;
11
+ document: Doc;
12
+ };
13
+ export declare const w: W | undefined;
14
+ export {};
@@ -0,0 +1,4 @@
1
+ // By importing/exporting w as a window object, we can easily mock it in tests.
2
+
3
+ // Exported for easy mocking
4
+ export const w = typeof window === 'undefined' ? undefined : window;
@@ -0,0 +1,9 @@
1
+ import { InputWrapperProps } from '@codecademy/gamut';
2
+ export type RecurlyFieldProps = InputWrapperProps & {
3
+ label: string;
4
+ };
5
+ /**
6
+ * A wrapper around a Recurly element (or two elements in the case of MM/YY) to
7
+ * add Gamut border styling and a label.
8
+ */
9
+ export declare function RecurlyField({ label, children, ...props }: RecurlyFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,57 @@
1
+ import _styled from "@emotion/styled/base";
2
+ function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
3
+ import { Box, Input, Text } from '@codecademy/gamut';
4
+ import { conditionalStyles, formFieldStyles } from '@codecademy/gamut/dist/Form/styles';
5
+ import { theme } from '@codecademy/gamut-styles';
6
+ import { useCallback, useRef } from 'react';
7
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const Slash = /*#__PURE__*/_styled("span", {
9
+ target: "e1ndrvyp1",
10
+ label: "Slash"
11
+ })(process.env.NODE_ENV === "production" ? {
12
+ name: "18v1ai1",
13
+ styles: "width:0;font-family:'Suisse';transform:translate(-6px, 1px);font-size:18px"
14
+ } : {
15
+ name: "18v1ai1",
16
+ styles: "width:0;font-family:'Suisse';transform:translate(-6px, 1px);font-size:18px/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9SZWN1cmx5L2ZpZWxkcy9SZWN1cmx5RmllbGQudHN4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWV5QiIsImZpbGUiOiIuLi8uLi8uLi9zcmMvUmVjdXJseS9maWVsZHMvUmVjdXJseUZpZWxkLnRzeCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEJveCxcbiAgSW5wdXQsXG4gIElucHV0V3JhcHBlclByb3BzLFxuICBTdHlsZWRJbnB1dFByb3BzLFxuICBUZXh0LFxufSBmcm9tICdAY29kZWNhZGVteS9nYW11dCc7XG5pbXBvcnQge1xuICBjb25kaXRpb25hbFN0eWxlcyxcbiAgZm9ybUZpZWxkU3R5bGVzLFxufSBmcm9tICdAY29kZWNhZGVteS9nYW11dC9kaXN0L0Zvcm0vc3R5bGVzJztcbmltcG9ydCB7IHRoZW1lIH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCBzdHlsZWQgZnJvbSAnQGVtb3Rpb24vc3R5bGVkJztcbmltcG9ydCB7IHVzZUNhbGxiYWNrLCB1c2VSZWYgfSBmcm9tICdyZWFjdCc7XG5cbmNvbnN0IFNsYXNoID0gc3R5bGVkLnNwYW5gXG4gIHdpZHRoOiAwO1xuICBmb250LWZhbWlseTogJ1N1aXNzZSc7XG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlKC02cHgsIDFweCk7XG4gIGZvbnQtc2l6ZTogMThweDtcbmA7XG5cbmZ1bmN0aW9uIGZvY3VzQm9yZGVyQ29sb3IoeyB2YXJpYW50IH06IFN0eWxlZElucHV0UHJvcHMpIHtcbiAgcmV0dXJuIHZhcmlhbnQgPT09ICdlcnJvcidcbiAgICA/IHRoZW1lLmNvbG9yc1snZmVlZGJhY2stZXJyb3InXVxuICAgIDogdGhlbWUuY29sb3JzLnByaW1hcnk7XG59XG5cbmNvbnN0IElucHV0SW5uZXJCb3ggPSBzdHlsZWQuZGl2PFN0eWxlZElucHV0UHJvcHM+YFxuICAke2Zvcm1GaWVsZFN0eWxlc31cbiAgJHtjb25kaXRpb25hbFN0eWxlc31cbiAgJiA+IGRpdiB7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbiAgcGFkZGluZzogMDtcbiAgZGlzcGxheTogZmxleDtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgaGVpZ2h0OiAzLjEyNXJlbTtcbiAgLnJlY3VybHktZWxlbWVudCB7XG4gICAgaGVpZ2h0OiAzLjEyNXJlbTtcbiAgfVxuICAmOmhhcygucmVjdXJseS1lbGVtZW50LWZvY3VzKSB7XG4gICAgYm9yZGVyLWNvbG9yOiAke2ZvY3VzQm9yZGVyQ29sb3J9O1xuICAgIGJveC1zaGFkb3c6IGluc2V0IDAgMCAwIDFweCAke2ZvY3VzQm9yZGVyQ29sb3J9O1xuICB9XG4gICY6aGFzKCR7U2xhc2h9KSA+IGRpdjpmaXJzdC1vZi10eXBlIHtcbiAgICBtYXgtd2lkdGg6IDIuNzVyZW07XG4gICAgbWluLXdpZHRoOiAyLjc1cmVtO1xuICB9XG5gO1xuXG5leHBvcnQgdHlwZSBSZWN1cmx5RmllbGRQcm9wcyA9IElucHV0V3JhcHBlclByb3BzICYgeyBsYWJlbDogc3RyaW5nIH07XG5cbi8qKlxuICogQSB3cmFwcGVyIGFyb3VuZCBhIFJlY3VybHkgZWxlbWVudCAob3IgdHdvIGVsZW1lbnRzIGluIHRoZSBjYXNlIG9mIE1NL1lZKSB0b1xuICogYWRkIEdhbXV0IGJvcmRlciBzdHlsaW5nIGFuZCBhIGxhYmVsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVjdXJseUZpZWxkKHsgbGFiZWwsIGNoaWxkcmVuLCAuLi5wcm9wcyB9OiBSZWN1cmx5RmllbGRQcm9wcykge1xuICBjb25zdCByZWYgPSB1c2VSZWY8SFRNTERpdkVsZW1lbnQ+KG51bGwpO1xuXG4gIGNvbnN0IHNlbGVjdElmcmFtZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICByZWYuY3VycmVudD8ucXVlcnlTZWxlY3RvcignaWZyYW1lJyk/LmZvY3VzKCk7XG4gIH0sIFtdKTtcblxuICByZXR1cm4gKFxuICAgIDxCb3ggcmVmPXtyZWZ9PlxuICAgICAgPFRleHQgYXM9XCJsYWJlbFwiIG9uQ2xpY2s9e3NlbGVjdElmcmFtZX0+XG4gICAgICAgIHtsYWJlbH1cbiAgICAgIDwvVGV4dD5cbiAgICAgIDxJbnB1dCBhcz17SW5wdXRJbm5lckJveH0gey4uLnByb3BzfT5cbiAgICAgICAge0FycmF5LmlzQXJyYXkoY2hpbGRyZW4pICYmIGNoaWxkcmVuLmxlbmd0aCA9PT0gMiA/IChcbiAgICAgICAgICA8PlxuICAgICAgICAgICAge2NoaWxkcmVuWzBdfVxuICAgICAgICAgICAgPFNsYXNoPi88L1NsYXNoPlxuICAgICAgICAgICAge2NoaWxkcmVuWzFdfVxuICAgICAgICAgIDwvPlxuICAgICAgICApIDogKFxuICAgICAgICAgIGNoaWxkcmVuXG4gICAgICAgICl9XG4gICAgICA8L0lucHV0PlxuICAgIDwvQm94PlxuICApO1xufVxuIl19 */",
17
+ toString: _EMOTION_STRINGIFIED_CSS_ERROR__
18
+ });
19
+ function focusBorderColor({
20
+ variant
21
+ }) {
22
+ return variant === 'error' ? theme.colors['feedback-error'] : theme.colors.primary;
23
+ }
24
+ const InputInnerBox = /*#__PURE__*/_styled("div", {
25
+ target: "e1ndrvyp0",
26
+ label: "InputInnerBox"
27
+ })(formFieldStyles, " ", conditionalStyles, " &>div{width:100%;}padding:0;display:flex;align-items:center;height:3.125rem;.recurly-element{height:3.125rem;}&:has(.recurly-element-focus){border-color:", focusBorderColor, ";box-shadow:inset 0 0 0 1px ", focusBorderColor, ";}&:has(", Slash, ")>div:first-of-type{max-width:2.75rem;min-width:2.75rem;}" + (process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9SZWN1cmx5L2ZpZWxkcy9SZWN1cmx5RmllbGQudHN4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQTRCa0QiLCJmaWxlIjoiLi4vLi4vLi4vc3JjL1JlY3VybHkvZmllbGRzL1JlY3VybHlGaWVsZC50c3giLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBCb3gsXG4gIElucHV0LFxuICBJbnB1dFdyYXBwZXJQcm9wcyxcbiAgU3R5bGVkSW5wdXRQcm9wcyxcbiAgVGV4dCxcbn0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQnO1xuaW1wb3J0IHtcbiAgY29uZGl0aW9uYWxTdHlsZXMsXG4gIGZvcm1GaWVsZFN0eWxlcyxcbn0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQvZGlzdC9Gb3JtL3N0eWxlcyc7XG5pbXBvcnQgeyB0aGVtZSB9IGZyb20gJ0Bjb2RlY2FkZW15L2dhbXV0LXN0eWxlcyc7XG5pbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCc7XG5pbXBvcnQgeyB1c2VDYWxsYmFjaywgdXNlUmVmIH0gZnJvbSAncmVhY3QnO1xuXG5jb25zdCBTbGFzaCA9IHN0eWxlZC5zcGFuYFxuICB3aWR0aDogMDtcbiAgZm9udC1mYW1pbHk6ICdTdWlzc2UnO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNnB4LCAxcHgpO1xuICBmb250LXNpemU6IDE4cHg7XG5gO1xuXG5mdW5jdGlvbiBmb2N1c0JvcmRlckNvbG9yKHsgdmFyaWFudCB9OiBTdHlsZWRJbnB1dFByb3BzKSB7XG4gIHJldHVybiB2YXJpYW50ID09PSAnZXJyb3InXG4gICAgPyB0aGVtZS5jb2xvcnNbJ2ZlZWRiYWNrLWVycm9yJ11cbiAgICA6IHRoZW1lLmNvbG9ycy5wcmltYXJ5O1xufVxuXG5jb25zdCBJbnB1dElubmVyQm94ID0gc3R5bGVkLmRpdjxTdHlsZWRJbnB1dFByb3BzPmBcbiAgJHtmb3JtRmllbGRTdHlsZXN9XG4gICR7Y29uZGl0aW9uYWxTdHlsZXN9XG4gICYgPiBkaXYge1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG4gIHBhZGRpbmc6IDA7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGhlaWdodDogMy4xMjVyZW07XG4gIC5yZWN1cmx5LWVsZW1lbnQge1xuICAgIGhlaWdodDogMy4xMjVyZW07XG4gIH1cbiAgJjpoYXMoLnJlY3VybHktZWxlbWVudC1mb2N1cykge1xuICAgIGJvcmRlci1jb2xvcjogJHtmb2N1c0JvcmRlckNvbG9yfTtcbiAgICBib3gtc2hhZG93OiBpbnNldCAwIDAgMCAxcHggJHtmb2N1c0JvcmRlckNvbG9yfTtcbiAgfVxuICAmOmhhcygke1NsYXNofSkgPiBkaXY6Zmlyc3Qtb2YtdHlwZSB7XG4gICAgbWF4LXdpZHRoOiAyLjc1cmVtO1xuICAgIG1pbi13aWR0aDogMi43NXJlbTtcbiAgfVxuYDtcblxuZXhwb3J0IHR5cGUgUmVjdXJseUZpZWxkUHJvcHMgPSBJbnB1dFdyYXBwZXJQcm9wcyAmIHsgbGFiZWw6IHN0cmluZyB9O1xuXG4vKipcbiAqIEEgd3JhcHBlciBhcm91bmQgYSBSZWN1cmx5IGVsZW1lbnQgKG9yIHR3byBlbGVtZW50cyBpbiB0aGUgY2FzZSBvZiBNTS9ZWSkgdG9cbiAqIGFkZCBHYW11dCBib3JkZXIgc3R5bGluZyBhbmQgYSBsYWJlbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlY3VybHlGaWVsZCh7IGxhYmVsLCBjaGlsZHJlbiwgLi4ucHJvcHMgfTogUmVjdXJseUZpZWxkUHJvcHMpIHtcbiAgY29uc3QgcmVmID0gdXNlUmVmPEhUTUxEaXZFbGVtZW50PihudWxsKTtcblxuICBjb25zdCBzZWxlY3RJZnJhbWUgPSB1c2VDYWxsYmFjaygoKSA9PiB7XG4gICAgcmVmLmN1cnJlbnQ/LnF1ZXJ5U2VsZWN0b3IoJ2lmcmFtZScpPy5mb2N1cygpO1xuICB9LCBbXSk7XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IHJlZj17cmVmfT5cbiAgICAgIDxUZXh0IGFzPVwibGFiZWxcIiBvbkNsaWNrPXtzZWxlY3RJZnJhbWV9PlxuICAgICAgICB7bGFiZWx9XG4gICAgICA8L1RleHQ+XG4gICAgICA8SW5wdXQgYXM9e0lucHV0SW5uZXJCb3h9IHsuLi5wcm9wc30+XG4gICAgICAgIHtBcnJheS5pc0FycmF5KGNoaWxkcmVuKSAmJiBjaGlsZHJlbi5sZW5ndGggPT09IDIgPyAoXG4gICAgICAgICAgPD5cbiAgICAgICAgICAgIHtjaGlsZHJlblswXX1cbiAgICAgICAgICAgIDxTbGFzaD4vPC9TbGFzaD5cbiAgICAgICAgICAgIHtjaGlsZHJlblsxXX1cbiAgICAgICAgICA8Lz5cbiAgICAgICAgKSA6IChcbiAgICAgICAgICBjaGlsZHJlblxuICAgICAgICApfVxuICAgICAgPC9JbnB1dD5cbiAgICA8L0JveD5cbiAgKTtcbn1cbiJdfQ== */"));
28
+ /**
29
+ * A wrapper around a Recurly element (or two elements in the case of MM/YY) to
30
+ * add Gamut border styling and a label.
31
+ */
32
+ export function RecurlyField({
33
+ label,
34
+ children,
35
+ ...props
36
+ }) {
37
+ const ref = useRef(null);
38
+ const selectIframe = useCallback(() => {
39
+ ref.current?.querySelector('iframe')?.focus();
40
+ }, []);
41
+ return /*#__PURE__*/_jsxs(Box, {
42
+ ref: ref,
43
+ children: [/*#__PURE__*/_jsx(Text, {
44
+ as: "label",
45
+ onClick: selectIframe,
46
+ children: label
47
+ }), /*#__PURE__*/_jsx(Input, {
48
+ as: InputInnerBox,
49
+ ...props,
50
+ children: Array.isArray(children) && children.length === 2 ? /*#__PURE__*/_jsxs(_Fragment, {
51
+ children: [children[0], /*#__PURE__*/_jsx(Slash, {
52
+ children: "/"
53
+ }), children[1]]
54
+ }) : children
55
+ })]
56
+ });
57
+ }
@@ -0,0 +1,17 @@
1
+ import type { Emitter } from '@recurly/recurly-js/lib/emitter';
2
+ export declare const CardNumber: import("react").ForwardRefExoticComponent<EventHandlers & import("react").RefAttributes<Focusable>>;
3
+ export declare const ExpMonth: import("react").ForwardRefExoticComponent<EventHandlers & import("react").RefAttributes<Focusable>>;
4
+ export declare const ExpYear: import("react").ForwardRefExoticComponent<EventHandlers & import("react").RefAttributes<Focusable>>;
5
+ export declare const CVV: import("react").ForwardRefExoticComponent<EventHandlers & import("react").RefAttributes<Focusable>>;
6
+ export type ElementType = 'CardNumberElement' | 'CardMonthElement' | 'CardYearElement' | 'CardCvvElement';
7
+ export type EventHandler = Parameters<Emitter['on']>[1];
8
+ export type EventHandlers = {
9
+ onReady?: EventHandler;
10
+ onChange?: EventHandler;
11
+ onBlur?: EventHandler;
12
+ onFocus?: EventHandler;
13
+ onSubmit?: EventHandler;
14
+ };
15
+ export type Focusable = {
16
+ focus: () => void;
17
+ };
@@ -0,0 +1,106 @@
1
+ import { formFieldPaddingStyles } from '@codecademy/gamut/dist/Form/styles';
2
+ import { forwardRef, useMemo, useRef } from 'react';
3
+ import { useElements } from '../context';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ export const CardNumber = element('CardNumberElement', 'Card number');
6
+ export const ExpMonth = element('CardMonthElement', 'Expiration month', 'MM');
7
+ export const ExpYear = element('CardYearElement', 'Expiration year', 'YY');
8
+ export const CVV = element('CardCvvElement', 'CVV');
9
+
10
+ /* These match methods on `ElementsInstance`. 'CardElement' is omitted since we
11
+ only need individual elements (number,month,year,cvv). */
12
+
13
+ // The type that `element.on` expects. See `bindEventHandlers`.
14
+
15
+ /* Our only reason for allowing folks to pass in a ref is so that they can call
16
+ `.focus()` from the outside. This type satisfies `HTMLElement` (the type one
17
+ might try to use) and `ElementInstance` (what we actually set the ref to). */
18
+
19
+ /**
20
+ * Defines a Recurly element component for a given type.
21
+ */
22
+ function element(type, title, placeholder) {
23
+ return /*#__PURE__*/forwardRef((eventHandlers, ref) => {
24
+ const elements = useElements({
25
+ noProviderError: 'Recurly element must be used within a Elements provider.'
26
+ });
27
+
28
+ // Keep ref updated with current event handlers. See `bindEventHandlers`.
29
+ const h = useRef({});
30
+ h.current = eventHandlers;
31
+ const callback = useMemo(() => {
32
+ if (!elements) return null;
33
+ let el;
34
+ return div => {
35
+ /* This is called once (with the div) when `elements` is defined and
36
+ once (with null) on component unmount. */
37
+ if (div) {
38
+ el = elements[type]({
39
+ style: getStyle(title, placeholder)
40
+ });
41
+ el.attach(div);
42
+ bindEventHandlers(el, h);
43
+ allowFocus(div, ref, el);
44
+ } else {
45
+ cleanup(el);
46
+ }
47
+ };
48
+ }, [elements, ref]);
49
+ return /*#__PURE__*/_jsx("div", {
50
+ ref: callback
51
+ });
52
+ });
53
+ }
54
+ function cleanup(el) {
55
+ /* `.destroy()` is not documented or included in the IndividualElement type,
56
+ but there is an example of @recurly/react-recurly using it here:
57
+ https://github.com/recurly/react-recurly/blob/main/lib/element/element.jsx#L112 */
58
+ el?.destroy();
59
+ }
60
+ function allowFocus(div, ref, el) {
61
+ // Forward focus from iframe to element. (used by RecurlyField label onClick)
62
+ div.querySelector('iframe')?.addEventListener('focus', () => el.focus());
63
+
64
+ // Set ref to our Recurly element instance so that one can call `.focus()`.
65
+ if (ref instanceof Function) {
66
+ ref(el); // call RefCallback
67
+ } else if (ref) {
68
+ ref.current = el; // set RefObject
69
+ }
70
+ }
71
+ function getStyle(title, placeholder) {
72
+ const {
73
+ px,
74
+ py
75
+ } = formFieldPaddingStyles;
76
+ return {
77
+ /* We can't use Apercu in the iframe, but Recurly will pull in any Google
78
+ font. JetBrains Mono seems appropriate for card info. */
79
+ fontFamily: 'JetBrains Mono',
80
+ /* Most Gamut styling is applied outside the iframe by RecurlyField. We
81
+ apply padding in the iframe to ensure the entire element is clickable. */
82
+ padding: `${px}px ${py}px`,
83
+ /* For some bewildered reason, Recurly takes `title` and `placeholder` as
84
+ style properties. Title is necessary for screen readers. */
85
+ title,
86
+ /* The spread syntax for `placeholder` is only necessary because Recurly is
87
+ persnickety about only including it when defined. */
88
+ ...(placeholder ? {
89
+ placeholder
90
+ } : {})
91
+ };
92
+ }
93
+
94
+ /* When event handlers are passed as inline function props, they technically are
95
+ new function instances on each render. Rather than re-attaching the handlers on
96
+ each render, we keep a ref updated with the latest handlers and bind static
97
+ handlers that call the dynamic handlers from the ref. @recurly/react-recurly
98
+ essentially uses the same approach here:
99
+ https://github.com/recurly/react-recurly/blob/main/lib/element/element.jsx#L102 */
100
+ function bindEventHandlers(el, h) {
101
+ el.on('attach', (...args) => h.current?.onReady?.(...args));
102
+ el.on('change', (...args) => h.current?.onChange?.(...args));
103
+ el.on('blur', (...args) => h.current?.onBlur?.(...args));
104
+ el.on('focus', (...args) => h.current?.onFocus?.(...args));
105
+ el.on('submit', (...args) => h.current?.onSubmit?.(...args));
106
+ }
@@ -0,0 +1,79 @@
1
+ import { setupRtl } from '@codecademy/gamut-tests';
2
+ import { render } from '@testing-library/react';
3
+ import { CardNumber } from './elements';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ class MockElement {
6
+ attach = jest.fn();
7
+ handlers = {};
8
+ on = jest.fn((evt, handler) => {
9
+ this.handlers[evt] = handler;
10
+ });
11
+ configure = jest.fn();
12
+ destroy = jest.fn();
13
+ focus = jest.fn();
14
+ }
15
+ const mockElements = {};
16
+ jest.mock('../context', () => ({
17
+ useElements: jest.fn(() => mockElements)
18
+ }));
19
+ describe('CardNumber', () => {
20
+ it('creates the Recurly element and attaches it to the DOM', () => {
21
+ const mockElement = new MockElement();
22
+ mockElements.CardNumberElement = jest.fn(() => mockElement);
23
+ const renderView = setupRtl(CardNumber);
24
+ renderView();
25
+ expect(mockElements.CardNumberElement).toHaveBeenCalled();
26
+ expect(mockElement.attach).toHaveBeenCalled();
27
+ });
28
+ it('sets and updates event handlers', () => {
29
+ const mockElement = new MockElement();
30
+ mockElements.CardNumberElement = () => mockElement;
31
+ const renderView = setupRtl(CardNumber);
32
+ const createHandlers = () => ({
33
+ onReady: jest.fn(),
34
+ onChange: jest.fn(),
35
+ onBlur: jest.fn(),
36
+ onFocus: jest.fn(),
37
+ onSubmit: jest.fn()
38
+ });
39
+ const handlersA = createHandlers();
40
+ const {
41
+ update
42
+ } = renderView(handlersA);
43
+ for (const handler of Object.values(mockElement.handlers)) {
44
+ handler('test-1');
45
+ }
46
+ const handlersB = createHandlers();
47
+ update(handlersB);
48
+ for (const handler of Object.values(mockElement.handlers)) {
49
+ handler('test-2');
50
+ }
51
+ for (const handler of Object.values(handlersA)) {
52
+ expect(handler).toHaveBeenCalledTimes(1);
53
+ expect(handler).toHaveBeenCalledWith('test-1');
54
+ }
55
+ for (const handler of Object.values(handlersB)) {
56
+ expect(handler).toHaveBeenCalledTimes(1);
57
+ expect(handler).toHaveBeenCalledWith('test-2');
58
+ }
59
+ });
60
+ it('allows focus from the outside via ref', () => {
61
+ const mockElement = new MockElement();
62
+ mockElements.CardNumberElement = jest.fn(() => mockElement);
63
+ const ref = {};
64
+ render(/*#__PURE__*/_jsx(CardNumber, {
65
+ ref: ref
66
+ }));
67
+ ref.current?.focus();
68
+ expect(mockElement.focus).toHaveBeenCalled();
69
+ });
70
+ it('destroys the Recurly element when the component unmounts', () => {
71
+ const mockElement = new MockElement();
72
+ mockElements.CardNumberElement = jest.fn(() => mockElement);
73
+ const {
74
+ unmount
75
+ } = render(/*#__PURE__*/_jsx(CardNumber, {}));
76
+ unmount();
77
+ expect(mockElement.destroy).toHaveBeenCalled();
78
+ });
79
+ });
@@ -0,0 +1,2 @@
1
+ export * from './elements';
2
+ export * from './RecurlyField';
@@ -0,0 +1,2 @@
1
+ export * from './elements';
2
+ export * from './RecurlyField';
@@ -0,0 +1,2 @@
1
+ export * from './fields';
2
+ export { Elements, RecurlyProvider, useRecurly } from './context';
@@ -0,0 +1,2 @@
1
+ export * from './fields';
2
+ export { Elements, RecurlyProvider, useRecurly } from './context';
@@ -13,5 +13,8 @@ export interface SkillPathCardProps {
13
13
  timeToComplete?: number | null;
14
14
  header?: React.ReactNode;
15
15
  footer?: React.ReactNode;
16
+ openInNewTab?: boolean;
17
+ href: string;
18
+ onClick: () => void;
16
19
  }
17
- export declare const SkillPathCard: React.FC<SkillPathCardProps>;
20
+ export declare const SkillPathCard: import("react").ForwardRefExoticComponent<SkillPathCardProps & import("react").RefAttributes<HTMLAnchorElement>>;
@@ -1,8 +1,9 @@
1
+ import { forwardRef } from 'react';
1
2
  import { ContentGroupBaseCard } from '../ContentGroupBaseCard';
2
3
  import { getPathImageUrl } from '../ContentGroupBaseCard/helpers';
3
- import { CertificateComponent, CourseCountComponent, Divider } from '../ContentGroupBaseCard/shared';
4
+ import { CertificateComponent, CourseCountComponent } from '../ContentGroupBaseCard/shared';
4
5
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
- export const SkillPathCard = ({
6
+ export const SkillPathCard = /*#__PURE__*/forwardRef(({
6
7
  title,
7
8
  shortDescription,
8
9
  difficulty,
@@ -14,8 +15,11 @@ export const SkillPathCard = ({
14
15
  isFullSize = false,
15
16
  timeToComplete,
16
17
  header,
17
- footer
18
- }) => {
18
+ footer,
19
+ openInNewTab = false,
20
+ href,
21
+ onClick
22
+ }, ref) => {
19
23
  return /*#__PURE__*/_jsxs(ContentGroupBaseCard, {
20
24
  headerBackgroundColor: "blue-100",
21
25
  headerText: "Skill path",
@@ -31,8 +35,12 @@ export const SkillPathCard = ({
31
35
  timeToComplete: timeToComplete,
32
36
  header: header,
33
37
  footer: footer,
34
- children: [/*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx(CourseCountComponent, {
38
+ openInNewTab: openInNewTab,
39
+ href: href,
40
+ onClick: onClick,
41
+ ref: ref,
42
+ children: [/*#__PURE__*/_jsx(CourseCountComponent, {
35
43
  count: courseCount
36
- }), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx(CertificateComponent, {})]
44
+ }), /*#__PURE__*/_jsx(CertificateComponent, {})]
37
45
  });
38
- };
46
+ });
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export * from './ContentGroupBaseCard';
27
27
  export * from './ContentGroupBaseCard/types';
28
28
  export * from './ContentGroupBaseCard/helpers';
29
29
  export * from './CourseCard';
30
+ export * from './CourseRecommendationsLink';
30
31
  export * from './CurriculumCard';
31
32
  export * from './CurriculumCard/Difficulty';
32
33
  export * from './CurriculumCard/Difficulty/types';
@@ -73,6 +74,7 @@ export * from './PricingSection';
73
74
  export * from './ProductFeatureShowcase';
74
75
  export * from './ProLogoAlt';
75
76
  export * from './RatingsBar';
77
+ export * from './Recurly';
76
78
  export * from './ResourceCard';
77
79
  export * from './ScoreSummary';
78
80
  export * from './ScoreSummary/SubScores';
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ export * from './ContentGroupBaseCard';
27
27
  export * from './ContentGroupBaseCard/types';
28
28
  export * from './ContentGroupBaseCard/helpers';
29
29
  export * from './CourseCard';
30
+ export * from './CourseRecommendationsLink';
30
31
  export * from './CurriculumCard';
31
32
  export * from './CurriculumCard/Difficulty';
32
33
  export * from './CurriculumCard/Difficulty/types';
@@ -73,6 +74,7 @@ export * from './PricingSection';
73
74
  export * from './ProductFeatureShowcase';
74
75
  export * from './ProLogoAlt';
75
76
  export * from './RatingsBar';
77
+ export * from './Recurly';
76
78
  export * from './ResourceCard';
77
79
  export * from './ScoreSummary';
78
80
  export * from './ScoreSummary/SubScores';
@@ -8,7 +8,9 @@ const meta = {
8
8
  headerBackgroundColor: 'pink',
9
9
  headerText: 'Course',
10
10
  title: 'Python 101',
11
- numLessons: 20
11
+ numLessons: 20,
12
+ href: '/learn/python-101',
13
+ onClick: () => {}
12
14
  }
13
15
  };
14
16
  export default meta;
@@ -9,7 +9,9 @@ const meta = {
9
9
  difficulty: CourseDifficulty.Beginner,
10
10
  title: 'Python 101',
11
11
  imageUrl: 'https://static-assets.codecademy.com/components/curriculum/path/front-end-engineer-career-path/curriculum-card.svg',
12
- timeToComplete: 15
12
+ timeToComplete: 15,
13
+ href: '/learn/paths/python-101',
14
+ onClick: () => {}
13
15
  }
14
16
  };
15
17
  export default meta;
@@ -7,7 +7,9 @@ const meta = {
7
7
  title: 'CLF-C02: AWS Certified Cloud Practitioner',
8
8
  courseCount: 16,
9
9
  shortDescription: 'Master AWS Cloud fundamentals, security, and migration strategies to prepare for the AWS Certified Cloud Practitioner exam (CLF-C02) and enhance your cloud skills.',
10
- difficulty: CourseDifficulty.Beginner
10
+ difficulty: CourseDifficulty.Beginner,
11
+ href: '/learn/ext-paths/certification-prep-clf-c02',
12
+ onClick: () => {}
11
13
  }
12
14
  };
13
15
  export default meta;
@@ -9,7 +9,9 @@ const meta = {
9
9
  pro: false,
10
10
  shortDescription: 'Learn how to use JavaScript — a powerful and flexible programming language for adding website interactivity.',
11
11
  difficulty: CourseDifficulty.Beginner,
12
- timeToComplete: 3
12
+ timeToComplete: 3,
13
+ href: '/learn/javascript',
14
+ onClick: () => {}
13
15
  }
14
16
  };
15
17
  export default meta;
@@ -9,7 +9,9 @@ const meta = {
9
9
  shortDescription: 'Web applications are constantly under threat. Authentication and authorization, protected data, and secure code will protect your web app.',
10
10
  difficulty: CourseDifficulty.Intermediate,
11
11
  imageUrl: 'https://static-assets.codecademy.com/components/curriculum/path/securing-express-applications/curriculum-card.svg',
12
- timeToComplete: 10
12
+ timeToComplete: 10,
13
+ href: '/learn/paths/securing-express-applications',
14
+ onClick: () => {}
13
15
  }
14
16
  };
15
17
  export default meta;
@@ -1,8 +1,8 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
- import { Banner } from '../../index';
3
- declare const meta: Meta<typeof Banner>;
2
+ import { EnhancedBanner } from '../../Banner/EnhancedBanner';
3
+ declare const meta: Meta<typeof EnhancedBanner>;
4
4
  export default meta;
5
- type Story = StoryObj<typeof Banner>;
5
+ type Story = StoryObj<typeof EnhancedBanner>;
6
6
  export declare const WithCountdownTimer: Story;
7
7
  export declare const WithoutCountdownTimer: Story;
8
8
  export declare const YellowVariant: Story;
@@ -1,8 +1,8 @@
1
1
  import { action } from '@storybook/addon-actions';
2
- import { Banner } from '../../index';
2
+ import { EnhancedBanner } from '../../Banner/EnhancedBanner';
3
3
  const meta = {
4
4
  title: 'Molecules/Enhanced Banner',
5
- component: Banner,
5
+ component: EnhancedBanner,
6
6
  argTypes: {
7
7
  header: {
8
8
  control: 'text',
@@ -25,15 +25,6 @@ const meta = {
25
25
  control: 'text',
26
26
  description: 'ISO date string for countdown timer (e.g., "2024-12-31T23:59:59Z")'
27
27
  },
28
- isInEnhancedBannerVariant: {
29
- control: 'boolean',
30
- description: 'Enable enhanced banner features',
31
- table: {
32
- defaultValue: {
33
- summary: 'true'
34
- }
35
- }
36
- },
37
28
  onClose: {
38
29
  action: 'closed',
39
30
  description: 'Callback when banner is closed'
@@ -48,7 +39,6 @@ const meta = {
48
39
  }
49
40
  },
50
41
  args: {
51
- isInEnhancedBannerVariant: true,
52
42
  variant: 'navy',
53
43
  onClose: action('banner-closed'),
54
44
  onCtaClick: action('cta-clicked'),
@@ -0,0 +1,5 @@
1
+ import type { StoryObj } from '@storybook/react';
2
+ declare const _default: {};
3
+ export default _default;
4
+ export declare const Default: StoryObj;
5
+ export declare const RecurlyFieldStory: StoryObj;
@@ -0,0 +1,56 @@
1
+ import { GridBox, Text } from '@codecademy/gamut';
2
+ import { CardNumber, CVV, Elements, ExpMonth, ExpYear, RecurlyField, RecurlyProvider } from '../../index';
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ export default {};
5
+ export const Default = {
6
+ render: () => /*#__PURE__*/_jsx(RecurlyProvider, {
7
+ publicKey: "test-key",
8
+ required: [],
9
+ children: /*#__PURE__*/_jsx(Elements, {
10
+ children: /*#__PURE__*/_jsxs(GridBox, {
11
+ gridTemplateColumns: "2fr 1fr 1fr",
12
+ gap: 16,
13
+ children: [/*#__PURE__*/_jsx(RecurlyField, {
14
+ label: "Card number*",
15
+ children: /*#__PURE__*/_jsx(CardNumber, {})
16
+ }), /*#__PURE__*/_jsxs(RecurlyField, {
17
+ label: "Expiration date*",
18
+ children: [/*#__PURE__*/_jsx(ExpMonth, {}), /*#__PURE__*/_jsx(ExpYear, {})]
19
+ }), /*#__PURE__*/_jsx(RecurlyField, {
20
+ label: "CVV*",
21
+ children: /*#__PURE__*/_jsx(CVV, {})
22
+ })]
23
+ })
24
+ })
25
+ })
26
+ };
27
+ export const RecurlyFieldStory = {
28
+ render: () => /*#__PURE__*/_jsx(RecurlyProvider, {
29
+ publicKey: "test-key",
30
+ required: [],
31
+ children: /*#__PURE__*/_jsx(Elements, {
32
+ children: /*#__PURE__*/_jsxs(GridBox, {
33
+ gridTemplateColumns: "4rem 1fr",
34
+ gap: 16,
35
+ children: [/*#__PURE__*/_jsx(Text, {
36
+ variant: "title-xs",
37
+ alignSelf: "center",
38
+ children: "Error"
39
+ }), /*#__PURE__*/_jsx(RecurlyField, {
40
+ label: "Card number*",
41
+ error: true,
42
+ children: /*#__PURE__*/_jsx(CardNumber, {})
43
+ }), /*#__PURE__*/_jsx(Text, {
44
+ variant: "title-xs",
45
+ alignSelf: "center",
46
+ children: "Valid"
47
+ }), /*#__PURE__*/_jsx(RecurlyField, {
48
+ label: "Card number*",
49
+ valid: true,
50
+ children: /*#__PURE__*/_jsx(CardNumber, {})
51
+ })]
52
+ })
53
+ })
54
+ }),
55
+ name: 'Validation'
56
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codecademy/brand",
3
3
  "description": "Brand component library for Codecademy",
4
- "version": "3.0.0-alpha.2ba7921cce.0",
4
+ "version": "3.0.0-alpha.39691db5eb.0",
5
5
  "author": "Codecademy Engineering <dev@codecademy.com>",
6
6
  "dependencies": {
7
7
  "@emotion/is-prop-valid": "^1.2.1",