@openedx/paragon 22.14.0 → 22.15.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.
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
- export declare const HYPER_LINK_EXTERNAL_LINK_ALT_TEXT = "in a new tab";
3
- export declare const HYPER_LINK_EXTERNAL_LINK_TITLE = "Opens in a new tab";
4
- interface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {
2
+ import { type BsPrefixRefForwardingComponent as ComponentWithAsProp, type BsPrefixProps } from 'react-bootstrap/esm/helpers';
3
+ export interface HyperlinkProps extends BsPrefixProps, Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {
5
4
  /** specifies the URL */
6
- destination: string;
5
+ destination?: string;
7
6
  /** Content of the hyperlink */
8
7
  children: React.ReactNode;
9
8
  /** Custom class names for the hyperlink */
@@ -18,7 +17,13 @@ interface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'
18
17
  isInline?: boolean;
19
18
  /** specify if we need to show launch Icon. By default, it will be visible. */
20
19
  showLaunchIcon?: boolean;
20
+ /** specifies where the link should open. The default behavior is `_self`, which means that the URL will be
21
+ * loaded into the same browsing context as the current one.
22
+ * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent
23
+ * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).
24
+ */
21
25
  target?: '_blank' | '_self';
22
26
  }
23
- declare const Hyperlink: React.ForwardRefExoticComponent<Omit<Props, "ref"> & React.RefAttributes<HTMLAnchorElement>>;
27
+ export type HyperlinkType = ComponentWithAsProp<'a', HyperlinkProps>;
28
+ declare const Hyperlink: React.ForwardRefExoticComponent<Omit<HyperlinkProps, "ref"> & React.RefAttributes<HTMLAnchorElement>>;
24
29
  export default Hyperlink;
@@ -1,24 +1,37 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
+ import { defineMessages, useIntl } from 'react-intl';
4
5
  import { Launch } from '../../icons';
5
6
  import Icon from '../Icon';
6
- export const HYPER_LINK_EXTERNAL_LINK_ALT_TEXT = 'in a new tab';
7
- export const HYPER_LINK_EXTERNAL_LINK_TITLE = 'Opens in a new tab';
8
- const Hyperlink = /*#__PURE__*/React.forwardRef((_ref, ref) => {
7
+ // @ts-ignore
8
+ import { customPropTypeRequirement } from '../utils/propTypes/utils';
9
+ const messages = defineMessages({
10
+ externalLinkAltText: {
11
+ id: 'Hyperlink.externalLinkAltText',
12
+ defaultMessage: 'in a new tab'
13
+ },
14
+ externalLinkTitle: {
15
+ id: 'Hyperlink.externalLinkTitle',
16
+ defaultMessage: 'Opens in a new tab'
17
+ }
18
+ });
19
+ const Hyperlink = /*#__PURE__*/forwardRef((_ref, ref) => {
9
20
  let {
21
+ as: Component = 'a',
10
22
  className,
11
23
  destination,
12
24
  children,
13
- target,
25
+ target = '_self',
14
26
  onClick,
15
27
  externalLinkAlternativeText,
16
28
  externalLinkTitle,
17
- variant,
18
- isInline,
19
- showLaunchIcon,
29
+ variant = 'default',
30
+ isInline = false,
31
+ showLaunchIcon = true,
20
32
  ...attrs
21
33
  } = _ref;
34
+ const intl = useIntl();
22
35
  let externalLinkIcon;
23
36
  if (target === '_blank') {
24
37
  const generateRel = () => {
@@ -42,10 +55,10 @@ const Hyperlink = /*#__PURE__*/React.forwardRef((_ref, ref) => {
42
55
  if (showLaunchIcon) {
43
56
  externalLinkIcon = /*#__PURE__*/React.createElement("span", {
44
57
  className: "pgn__hyperlink__external-icon",
45
- title: externalLinkTitle
58
+ title: externalLinkTitle || intl.formatMessage(messages.externalLinkTitle)
46
59
  }, /*#__PURE__*/React.createElement(Icon, {
47
60
  src: Launch,
48
- screenReaderText: externalLinkAlternativeText,
61
+ screenReaderText: externalLinkAlternativeText || intl.formatMessage(messages.externalLinkAltText),
49
62
  style: {
50
63
  height: '1em',
51
64
  width: '1em'
@@ -54,31 +67,36 @@ const Hyperlink = /*#__PURE__*/React.forwardRef((_ref, ref) => {
54
67
  }));
55
68
  }
56
69
  }
57
- return /*#__PURE__*/React.createElement("a", {
70
+ const additionalProps = {
71
+ ...attrs
72
+ };
73
+ if (destination) {
74
+ additionalProps.href = destination;
75
+ }
76
+ return /*#__PURE__*/React.createElement(Component, {
58
77
  ref: ref,
59
78
  className: classNames('pgn__hyperlink', `${variant}-link`, {
60
79
  'standalone-link': !isInline,
61
80
  'inline-link': isInline
62
81
  }, className),
63
- href: destination,
64
82
  target: target,
65
83
  onClick: onClick,
66
- ...attrs
84
+ ...additionalProps
67
85
  }, children, externalLinkIcon);
68
86
  });
69
- Hyperlink.defaultProps = {
70
- className: undefined,
71
- target: '_self',
72
- onClick: () => {},
73
- externalLinkAlternativeText: HYPER_LINK_EXTERNAL_LINK_ALT_TEXT,
74
- externalLinkTitle: HYPER_LINK_EXTERNAL_LINK_TITLE,
75
- variant: 'default',
76
- isInline: false,
77
- showLaunchIcon: true
78
- };
79
87
  Hyperlink.propTypes = {
80
- /** specifies the URL */
81
- destination: PropTypes.string.isRequired,
88
+ /** specifies the component element type to render for the hyperlink */
89
+ // @ts-ignore
90
+ as: PropTypes.elementType,
91
+ /** specifies the URL; required iff `as` prop is a standard anchor tag */
92
+ destination: customPropTypeRequirement(PropTypes.string, _ref2 => {
93
+ let {
94
+ as
95
+ } = _ref2;
96
+ return as && as === 'a';
97
+ },
98
+ // "[`destination` is required when]..."
99
+ 'the `as` prop is a standard anchor element (i.e., "a")'),
82
100
  /** Content of the hyperlink */
83
101
  // @ts-ignore
84
102
  children: PropTypes.node.isRequired,
@@ -103,5 +121,18 @@ Hyperlink.propTypes = {
103
121
  /** specify if we need to show launch Icon. By default, it will be visible. */
104
122
  showLaunchIcon: PropTypes.bool
105
123
  };
124
+ Hyperlink.defaultProps = {
125
+ as: 'a',
126
+ className: undefined,
127
+ destination: undefined,
128
+ externalLinkAlternativeText: undefined,
129
+ externalLinkTitle: undefined,
130
+ isInline: false,
131
+ onClick: undefined,
132
+ showLaunchIcon: true,
133
+ target: '_self',
134
+ variant: 'default'
135
+ };
136
+ Hyperlink.displayName = 'Hyperlink';
106
137
  export default Hyperlink;
107
138
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["React","PropTypes","classNames","Launch","Icon","HYPER_LINK_EXTERNAL_LINK_ALT_TEXT","HYPER_LINK_EXTERNAL_LINK_TITLE","Hyperlink","forwardRef","_ref","ref","className","destination","children","target","onClick","externalLinkAlternativeText","externalLinkTitle","variant","isInline","showLaunchIcon","attrs","externalLinkIcon","generateRel","rel","includes","createElement","title","src","screenReaderText","style","height","width","href","defaultProps","undefined","propTypes","string","isRequired","node","oneOf","func","bool"],"sources":["../../src/Hyperlink/index.tsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { Launch } from '../../icons';\nimport Icon from '../Icon';\n\nexport const HYPER_LINK_EXTERNAL_LINK_ALT_TEXT = 'in a new tab';\nexport const HYPER_LINK_EXTERNAL_LINK_TITLE = 'Opens in a new tab';\n\ninterface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {\n /** specifies the URL */\n destination: string;\n /** Content of the hyperlink */\n children: React.ReactNode;\n /** Custom class names for the hyperlink */\n className?: string;\n /** Alt text for the icon indicating that this link opens in a new tab, if target=\"_blank\". e.g. _(\"in a new tab\") */\n externalLinkAlternativeText?: string;\n /** Tooltip text for the \"opens in new tab\" icon, if target=\"_blank\". e.g. _(\"Opens in a new tab\"). */\n externalLinkTitle?: string;\n /** type of hyperlink */\n variant?: 'default' | 'muted' | 'brand';\n /** Display the link with an underline. By default, it is only underlined on hover. */\n isInline?: boolean;\n /** specify if we need to show launch Icon. By default, it will be visible. */\n showLaunchIcon?: boolean;\n target?: '_blank' | '_self';\n}\n\nconst Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({\n className,\n destination,\n children,\n target,\n onClick,\n externalLinkAlternativeText,\n externalLinkTitle,\n variant,\n isInline,\n showLaunchIcon,\n ...attrs\n}, ref) => {\n let externalLinkIcon;\n\n if (target === '_blank') {\n const generateRel = () => {\n let { rel } = attrs;\n if (!rel) {\n return 'noopener noreferrer';\n }\n if (!rel.includes('noopener')) {\n rel += ' noopener';\n }\n if (!rel.includes('noreferrer')) {\n rel += ' noreferrer';\n }\n return rel;\n };\n\n // Add this rel attribute to prevent Reverse Tabnabbing\n attrs.rel = generateRel();\n if (showLaunchIcon) {\n externalLinkIcon = (\n <span\n className=\"pgn__hyperlink__external-icon\"\n title={externalLinkTitle}\n >\n <Icon\n src={Launch}\n screenReaderText={externalLinkAlternativeText}\n style={{ height: '1em', width: '1em' }}\n data-testid=\"hyperlink-icon\"\n />\n </span>\n );\n }\n }\n\n return (\n <a\n ref={ref}\n className={classNames(\n 'pgn__hyperlink',\n `${variant}-link`,\n {\n 'standalone-link': !isInline,\n 'inline-link': isInline,\n },\n className,\n )}\n href={destination}\n target={target}\n onClick={onClick}\n {...attrs}\n >\n {children}\n {externalLinkIcon}\n </a>\n );\n});\n\nHyperlink.defaultProps = {\n className: undefined,\n target: '_self',\n onClick: () => {},\n externalLinkAlternativeText: HYPER_LINK_EXTERNAL_LINK_ALT_TEXT,\n externalLinkTitle: HYPER_LINK_EXTERNAL_LINK_TITLE,\n variant: 'default',\n isInline: false,\n showLaunchIcon: true,\n};\n\nHyperlink.propTypes = {\n /** specifies the URL */\n destination: PropTypes.string.isRequired,\n /** Content of the hyperlink */\n // @ts-ignore\n children: PropTypes.node.isRequired,\n /** Custom class names for the hyperlink */\n className: PropTypes.string,\n /** specifies where the link should open. The default behavior is `_self`, which means that the URL will be\n * loaded into the same browsing context as the current one.\n * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent\n * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).\n */\n target: PropTypes.oneOf(['_blank', '_self']),\n /** specifies the callback function when the link is clicked */\n onClick: PropTypes.func,\n /** Alt text for the icon indicating that this link opens in a new tab, if target=\"_blank\". e.g. _(\"in a new tab\") */\n externalLinkAlternativeText: PropTypes.string,\n /** Tooltip text for the \"opens in new tab\" icon, if target=\"_blank\". e.g. _(\"Opens in a new tab\"). */\n externalLinkTitle: PropTypes.string,\n /** type of hyperlink */\n variant: PropTypes.oneOf(['default', 'muted', 'brand']),\n /** Display the link with an underline. By default, it is only underlined on hover. */\n isInline: PropTypes.bool,\n /** specify if we need to show launch Icon. By default, it will be visible. */\n showLaunchIcon: PropTypes.bool,\n};\n\nexport default Hyperlink;\n"],"mappings":"AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,OAAOC,SAAS,MAAM,YAAY;AAClC,OAAOC,UAAU,MAAM,YAAY;AACnC,SAASC,MAAM,QAAQ,aAAa;AACpC,OAAOC,IAAI,MAAM,SAAS;AAE1B,OAAO,MAAMC,iCAAiC,GAAG,cAAc;AAC/D,OAAO,MAAMC,8BAA8B,GAAG,oBAAoB;AAsBlE,MAAMC,SAAS,gBAAGP,KAAK,CAACQ,UAAU,CAA2B,CAAAC,IAAA,EAY1DC,GAAG,KAAK;EAAA,IAZmD;IAC5DC,SAAS;IACTC,WAAW;IACXC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,2BAA2B;IAC3BC,iBAAiB;IACjBC,OAAO;IACPC,QAAQ;IACRC,cAAc;IACd,GAAGC;EACL,CAAC,GAAAZ,IAAA;EACC,IAAIa,gBAAgB;EAEpB,IAAIR,MAAM,KAAK,QAAQ,EAAE;IACvB,MAAMS,WAAW,GAAGA,CAAA,KAAM;MACxB,IAAI;QAAEC;MAAI,CAAC,GAAGH,KAAK;MACnB,IAAI,CAACG,GAAG,EAAE;QACR,OAAO,qBAAqB;MAC9B;MACA,IAAI,CAACA,GAAG,CAACC,QAAQ,CAAC,UAAU,CAAC,EAAE;QAC7BD,GAAG,IAAI,WAAW;MACpB;MACA,IAAI,CAACA,GAAG,CAACC,QAAQ,CAAC,YAAY,CAAC,EAAE;QAC/BD,GAAG,IAAI,aAAa;MACtB;MACA,OAAOA,GAAG;IACZ,CAAC;;IAED;IACAH,KAAK,CAACG,GAAG,GAAGD,WAAW,CAAC,CAAC;IACzB,IAAIH,cAAc,EAAE;MAClBE,gBAAgB,gBACdtB,KAAA,CAAA0B,aAAA;QACEf,SAAS,EAAC,+BAA+B;QACzCgB,KAAK,EAAEV;MAAkB,gBAEzBjB,KAAA,CAAA0B,aAAA,CAACtB,IAAI;QACHwB,GAAG,EAAEzB,MAAO;QACZ0B,gBAAgB,EAAEb,2BAA4B;QAC9Cc,KAAK,EAAE;UAAEC,MAAM,EAAE,KAAK;UAAEC,KAAK,EAAE;QAAM,CAAE;QACvC,eAAY;MAAgB,CAC7B,CACG,CACP;IACH;EACF;EAEA,oBACEhC,KAAA,CAAA0B,aAAA;IACEhB,GAAG,EAAEA,GAAI;IACTC,SAAS,EAAET,UAAU,CACnB,gBAAgB,EAChB,GAAGgB,OAAO,OAAO,EACjB;MACE,iBAAiB,EAAE,CAACC,QAAQ;MAC5B,aAAa,EAAEA;IACjB,CAAC,EACDR,SACF,CAAE;IACFsB,IAAI,EAAErB,WAAY;IAClBE,MAAM,EAAEA,MAAO;IACfC,OAAO,EAAEA,OAAQ;IAAA,GACbM;EAAK,GAERR,QAAQ,EACRS,gBACA,CAAC;AAER,CAAC,CAAC;AAEFf,SAAS,CAAC2B,YAAY,GAAG;EACvBvB,SAAS,EAAEwB,SAAS;EACpBrB,MAAM,EAAE,OAAO;EACfC,OAAO,EAAEA,CAAA,KAAM,CAAC,CAAC;EACjBC,2BAA2B,EAAEX,iCAAiC;EAC9DY,iBAAiB,EAAEX,8BAA8B;EACjDY,OAAO,EAAE,SAAS;EAClBC,QAAQ,EAAE,KAAK;EACfC,cAAc,EAAE;AAClB,CAAC;AAEDb,SAAS,CAAC6B,SAAS,GAAG;EACpB;EACAxB,WAAW,EAAEX,SAAS,CAACoC,MAAM,CAACC,UAAU;EACxC;EACA;EACAzB,QAAQ,EAAEZ,SAAS,CAACsC,IAAI,CAACD,UAAU;EACnC;EACA3B,SAAS,EAAEV,SAAS,CAACoC,MAAM;EAC3B;AACF;AACA;AACA;AACA;EACEvB,MAAM,EAAEb,SAAS,CAACuC,KAAK,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;EAC5C;EACAzB,OAAO,EAAEd,SAAS,CAACwC,IAAI;EACvB;EACAzB,2BAA2B,EAAEf,SAAS,CAACoC,MAAM;EAC7C;EACApB,iBAAiB,EAAEhB,SAAS,CAACoC,MAAM;EACnC;EACAnB,OAAO,EAAEjB,SAAS,CAACuC,KAAK,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;EACvD;EACArB,QAAQ,EAAElB,SAAS,CAACyC,IAAI;EACxB;EACAtB,cAAc,EAAEnB,SAAS,CAACyC;AAC5B,CAAC;AAED,eAAenC,SAAS","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["React","forwardRef","PropTypes","classNames","defineMessages","useIntl","Launch","Icon","customPropTypeRequirement","messages","externalLinkAltText","id","defaultMessage","externalLinkTitle","Hyperlink","_ref","ref","as","Component","className","destination","children","target","onClick","externalLinkAlternativeText","variant","isInline","showLaunchIcon","attrs","intl","externalLinkIcon","generateRel","rel","includes","createElement","title","formatMessage","src","screenReaderText","style","height","width","additionalProps","href","propTypes","elementType","string","_ref2","node","isRequired","oneOf","func","bool","defaultProps","undefined","displayName"],"sources":["../../src/Hyperlink/index.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport {\n type BsPrefixRefForwardingComponent as ComponentWithAsProp,\n type BsPrefixProps,\n} from 'react-bootstrap/esm/helpers';\nimport { defineMessages, useIntl } from 'react-intl';\nimport { Launch } from '../../icons';\nimport Icon from '../Icon';\n// @ts-ignore\nimport { customPropTypeRequirement } from '../utils/propTypes/utils';\n\nexport interface HyperlinkProps extends BsPrefixProps, Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {\n /** specifies the URL */\n destination?: string;\n /** Content of the hyperlink */\n children: React.ReactNode;\n /** Custom class names for the hyperlink */\n className?: string;\n /** Alt text for the icon indicating that this link opens in a new tab, if target=\"_blank\". e.g. _(\"in a new tab\") */\n externalLinkAlternativeText?: string;\n /** Tooltip text for the \"opens in new tab\" icon, if target=\"_blank\". e.g. _(\"Opens in a new tab\"). */\n externalLinkTitle?: string;\n /** type of hyperlink */\n variant?: 'default' | 'muted' | 'brand';\n /** Display the link with an underline. By default, it is only underlined on hover. */\n isInline?: boolean;\n /** specify if we need to show launch Icon. By default, it will be visible. */\n showLaunchIcon?: boolean;\n /** specifies where the link should open. The default behavior is `_self`, which means that the URL will be\n * loaded into the same browsing context as the current one.\n * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent\n * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).\n */\n target?: '_blank' | '_self';\n}\n\nexport type HyperlinkType = ComponentWithAsProp<'a', HyperlinkProps>;\n\nconst messages = defineMessages({\n externalLinkAltText: {\n id: 'Hyperlink.externalLinkAltText',\n defaultMessage: 'in a new tab',\n },\n externalLinkTitle: {\n id: 'Hyperlink.externalLinkTitle',\n defaultMessage: 'Opens in a new tab',\n },\n});\n\nconst Hyperlink = forwardRef<HTMLAnchorElement, HyperlinkProps>(({\n as: Component = 'a',\n className,\n destination,\n children,\n target = '_self',\n onClick,\n externalLinkAlternativeText,\n externalLinkTitle,\n variant = 'default',\n isInline = false,\n showLaunchIcon = true,\n ...attrs\n}, ref) => {\n const intl = useIntl();\n let externalLinkIcon;\n\n if (target === '_blank') {\n const generateRel = () => {\n let { rel } = attrs;\n if (!rel) {\n return 'noopener noreferrer';\n }\n if (!rel.includes('noopener')) {\n rel += ' noopener';\n }\n if (!rel.includes('noreferrer')) {\n rel += ' noreferrer';\n }\n return rel;\n };\n\n // Add this rel attribute to prevent Reverse Tabnabbing\n attrs.rel = generateRel();\n if (showLaunchIcon) {\n externalLinkIcon = (\n <span\n className=\"pgn__hyperlink__external-icon\"\n title={externalLinkTitle || intl.formatMessage(messages.externalLinkTitle)}\n >\n <Icon\n src={Launch}\n screenReaderText={externalLinkAlternativeText || intl.formatMessage(messages.externalLinkAltText)}\n style={{ height: '1em', width: '1em' }}\n data-testid=\"hyperlink-icon\"\n />\n </span>\n );\n }\n }\n\n const additionalProps: Record<string, any> = { ...attrs };\n if (destination) {\n additionalProps.href = destination;\n }\n\n return (\n <Component\n ref={ref}\n className={classNames(\n 'pgn__hyperlink',\n `${variant}-link`,\n {\n 'standalone-link': !isInline,\n 'inline-link': isInline,\n },\n className,\n )}\n target={target}\n onClick={onClick}\n {...additionalProps}\n >\n {children}\n {externalLinkIcon}\n </Component>\n );\n});\n\nHyperlink.propTypes = {\n /** specifies the component element type to render for the hyperlink */\n // @ts-ignore\n as: PropTypes.elementType,\n /** specifies the URL; required iff `as` prop is a standard anchor tag */\n destination: customPropTypeRequirement(\n PropTypes.string,\n ({ as }: { as: React.ElementType }) => as && as === 'a',\n // \"[`destination` is required when]...\"\n 'the `as` prop is a standard anchor element (i.e., \"a\")',\n ),\n /** Content of the hyperlink */\n // @ts-ignore\n children: PropTypes.node.isRequired,\n /** Custom class names for the hyperlink */\n className: PropTypes.string,\n /** specifies where the link should open. The default behavior is `_self`, which means that the URL will be\n * loaded into the same browsing context as the current one.\n * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent\n * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).\n */\n target: PropTypes.oneOf(['_blank', '_self']),\n /** specifies the callback function when the link is clicked */\n onClick: PropTypes.func,\n /** Alt text for the icon indicating that this link opens in a new tab, if target=\"_blank\". e.g. _(\"in a new tab\") */\n externalLinkAlternativeText: PropTypes.string,\n /** Tooltip text for the \"opens in new tab\" icon, if target=\"_blank\". e.g. _(\"Opens in a new tab\"). */\n externalLinkTitle: PropTypes.string,\n /** type of hyperlink */\n variant: PropTypes.oneOf(['default', 'muted', 'brand']),\n /** Display the link with an underline. By default, it is only underlined on hover. */\n isInline: PropTypes.bool,\n /** specify if we need to show launch Icon. By default, it will be visible. */\n showLaunchIcon: PropTypes.bool,\n};\n\nHyperlink.defaultProps = {\n as: 'a',\n className: undefined,\n destination: undefined,\n externalLinkAlternativeText: undefined,\n externalLinkTitle: undefined,\n isInline: false,\n onClick: undefined,\n showLaunchIcon: true,\n target: '_self',\n variant: 'default',\n};\n\nHyperlink.displayName = 'Hyperlink';\n\nexport default Hyperlink;\n"],"mappings":"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","ignoreList":[]}
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export {
31
31
  FormAutosuggestOption,
32
32
  InputGroup,
33
33
  } from './Form';
34
- export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
34
+ export { default as Hyperlink } from './Hyperlink';
35
35
  export { default as Icon } from './Icon';
36
36
  export { default as IconButton, IconButtonWithTooltip } from './IconButton';
37
37
  export { default as ModalContext } from './Modal/ModalContext';
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ export {
31
31
  FormAutosuggestOption,
32
32
  InputGroup,
33
33
  } from './Form';
34
- export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
34
+ export { default as Hyperlink } from './Hyperlink';
35
35
  export { default as Icon } from './Icon';
36
36
  export { default as IconButton, IconButtonWithTooltip } from './IconButton';
37
37
  export { default as ModalContext } from './Modal/ModalContext';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openedx/paragon",
3
- "version": "22.14.0",
3
+ "version": "22.15.0",
4
4
  "description": "Accessible, responsive UI component library based on Bootstrap.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { IntlProvider } from 'react-intl';
2
3
  import { render, screen } from '@testing-library/react';
3
4
  import userEvent from '@testing-library/user-event';
4
5
  import renderer from 'react-test-renderer';
@@ -96,7 +97,11 @@ describe('<Button />', () => {
96
97
  test('test button as hyperlink', () => {
97
98
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
98
99
  const ref = (_current: HTMLAnchorElement) => {}; // Check typing of a ref - should not show type errors.
99
- render(<Button as={Hyperlink} ref={ref} destination="https://www.poop.com/💩">Button</Button>);
100
+ render(
101
+ <IntlProvider locale="en">
102
+ <Button as={Hyperlink} ref={ref} destination="https://www.poop.com/💩">Button</Button>
103
+ </IntlProvider>,
104
+ );
100
105
  expect(screen.getByRole('link').getAttribute('href')).toEqual('https://www.poop.com/💩');
101
106
  });
102
107
  });
@@ -1,12 +1,12 @@
1
- import React from 'react';
1
+ import { IntlProvider } from 'react-intl';
2
2
  import { render } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
 
5
- import Hyperlink from '.';
5
+ import Hyperlink, { HyperlinkProps } from '.';
6
6
 
7
- const destination = 'destination';
7
+ const destination = 'http://destination.example';
8
8
  const content = 'content';
9
- const onClick = jest.fn();
9
+ const onClick = jest.fn().mockImplementation((e) => e.preventDefault());
10
10
  const props = {
11
11
  destination,
12
12
  onClick,
@@ -20,13 +20,37 @@ const externalLinkProps = {
20
20
  ...props,
21
21
  };
22
22
 
23
+ interface LinkProps extends HyperlinkProps {
24
+ to: string;
25
+ }
26
+
27
+ function Link({ to, children, ...rest }: LinkProps) {
28
+ return (
29
+ <a
30
+ data-testid="custom-hyperlink-element"
31
+ href={to}
32
+ {...rest}
33
+ >
34
+ {children}
35
+ </a>
36
+ );
37
+ }
38
+
39
+ function HyperlinkWrapper({ children, ...rest }: HyperlinkProps) {
40
+ return (
41
+ <IntlProvider locale="en">
42
+ <Hyperlink {...rest}>{children}</Hyperlink>
43
+ </IntlProvider>
44
+ );
45
+ }
46
+
23
47
  describe('correct rendering', () => {
24
48
  beforeEach(() => {
25
- onClick.mockClear();
49
+ jest.clearAllMocks();
26
50
  });
27
51
 
28
52
  it('renders Hyperlink', async () => {
29
- const { getByRole } = render(<Hyperlink {...props}>{content}</Hyperlink>);
53
+ const { getByRole } = render(<HyperlinkWrapper {...props}>{content}</HyperlinkWrapper>);
30
54
  const wrapper = getByRole('link');
31
55
  expect(wrapper).toBeInTheDocument();
32
56
 
@@ -36,12 +60,29 @@ describe('correct rendering', () => {
36
60
  expect(wrapper).toHaveAttribute('href', destination);
37
61
  expect(wrapper).toHaveAttribute('target', '_self');
38
62
 
63
+ // Clicking on the link should call the onClick handler
39
64
  await userEvent.click(wrapper);
40
65
  expect(onClick).toHaveBeenCalledTimes(1);
41
66
  });
42
67
 
68
+ it('renders with custom element type via "as" prop', () => {
69
+ const propsWithoutDestination = {
70
+ to: destination, // `to` simulates common `Link` components' prop
71
+ };
72
+ const { getByRole } = render(<HyperlinkWrapper as={Link} {...propsWithoutDestination}>{content}</HyperlinkWrapper>);
73
+ const wrapper = getByRole('link');
74
+ expect(wrapper).toBeInTheDocument();
75
+
76
+ expect(wrapper).toHaveClass('pgn__hyperlink');
77
+ expect(wrapper).toHaveClass('standalone-link');
78
+ expect(wrapper).toHaveTextContent(content);
79
+ expect(wrapper).toHaveAttribute('href', destination);
80
+ expect(wrapper).toHaveAttribute('target', '_self');
81
+ expect(wrapper).toHaveAttribute('data-testid', 'custom-hyperlink-element');
82
+ });
83
+
43
84
  it('renders an underlined Hyperlink', async () => {
44
- const { getByRole } = render(<Hyperlink isInline {...props}>{content}</Hyperlink>);
85
+ const { getByRole } = render(<HyperlinkWrapper isInline {...props}>{content}</HyperlinkWrapper>);
45
86
  const wrapper = getByRole('link');
46
87
  expect(wrapper).toBeInTheDocument();
47
88
  expect(wrapper).toHaveClass('pgn__hyperlink');
@@ -50,7 +91,7 @@ describe('correct rendering', () => {
50
91
  });
51
92
 
52
93
  it('renders external Hyperlink', () => {
53
- const { getByRole, getByTestId } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
94
+ const { getByRole, getByTestId } = render(<HyperlinkWrapper {...externalLinkProps}>{content}</HyperlinkWrapper>);
54
95
  const wrapper = getByRole('link');
55
96
  const icon = getByTestId('hyperlink-icon');
56
97
  const iconSvg = icon.querySelector('svg');
@@ -66,19 +107,8 @@ describe('correct rendering', () => {
66
107
 
67
108
  describe('security', () => {
68
109
  it('prevents reverse tabnabbing for links with target="_blank"', () => {
69
- const { getByRole } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
110
+ const { getByRole } = render(<HyperlinkWrapper {...externalLinkProps}>{content}</HyperlinkWrapper>);
70
111
  const wrapper = getByRole('link');
71
112
  expect(wrapper).toHaveAttribute('rel', 'noopener noreferrer');
72
113
  });
73
114
  });
74
-
75
- describe('event handlers are triggered correctly', () => {
76
- it('should fire onClick', async () => {
77
- const spy = jest.fn();
78
- const { getByRole } = render(<Hyperlink {...props} onClick={spy}>{content}</Hyperlink>);
79
- const wrapper = getByRole('link');
80
- expect(spy).toHaveBeenCalledTimes(0);
81
- await userEvent.click(wrapper);
82
- expect(spy).toHaveBeenCalledTimes(1);
83
- });
84
- });
@@ -7,7 +7,7 @@ categories:
7
7
  - Buttonlike
8
8
  status: 'Needs Work'
9
9
  designStatus: 'Done'
10
- devStatus: 'To Do'
10
+ devStatus: 'Done'
11
11
  notes: |
12
12
  Improve prop naming. Deprecate content prop.
13
13
  Use React.forwardRef for ref forwarding.
@@ -100,3 +100,16 @@ notes: |
100
100
  </div>
101
101
  </div>
102
102
  ```
103
+
104
+ ## with custom link element (e.g., using a router)
105
+
106
+ ``Hyperlink`` typically relies on the standard HTML anchor tag (i.e., ``a``); however, this behavior may be overriden when the destination link is to an internal route where it should be using routing instead (e.g., ``Link`` from React Router).
107
+
108
+ ```jsx live
109
+ <Hyperlink
110
+ as={GatsbyLink}
111
+ to="/components/button"
112
+ >
113
+ Button
114
+ </Hyperlink>
115
+ ```
@@ -1,15 +1,19 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
+ import {
5
+ type BsPrefixRefForwardingComponent as ComponentWithAsProp,
6
+ type BsPrefixProps,
7
+ } from 'react-bootstrap/esm/helpers';
8
+ import { defineMessages, useIntl } from 'react-intl';
4
9
  import { Launch } from '../../icons';
5
10
  import Icon from '../Icon';
11
+ // @ts-ignore
12
+ import { customPropTypeRequirement } from '../utils/propTypes/utils';
6
13
 
7
- export const HYPER_LINK_EXTERNAL_LINK_ALT_TEXT = 'in a new tab';
8
- export const HYPER_LINK_EXTERNAL_LINK_TITLE = 'Opens in a new tab';
9
-
10
- interface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {
14
+ export interface HyperlinkProps extends BsPrefixProps, Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {
11
15
  /** specifies the URL */
12
- destination: string;
16
+ destination?: string;
13
17
  /** Content of the hyperlink */
14
18
  children: React.ReactNode;
15
19
  /** Custom class names for the hyperlink */
@@ -24,22 +28,42 @@ interface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'
24
28
  isInline?: boolean;
25
29
  /** specify if we need to show launch Icon. By default, it will be visible. */
26
30
  showLaunchIcon?: boolean;
31
+ /** specifies where the link should open. The default behavior is `_self`, which means that the URL will be
32
+ * loaded into the same browsing context as the current one.
33
+ * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent
34
+ * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).
35
+ */
27
36
  target?: '_blank' | '_self';
28
37
  }
29
38
 
30
- const Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({
39
+ export type HyperlinkType = ComponentWithAsProp<'a', HyperlinkProps>;
40
+
41
+ const messages = defineMessages({
42
+ externalLinkAltText: {
43
+ id: 'Hyperlink.externalLinkAltText',
44
+ defaultMessage: 'in a new tab',
45
+ },
46
+ externalLinkTitle: {
47
+ id: 'Hyperlink.externalLinkTitle',
48
+ defaultMessage: 'Opens in a new tab',
49
+ },
50
+ });
51
+
52
+ const Hyperlink = forwardRef<HTMLAnchorElement, HyperlinkProps>(({
53
+ as: Component = 'a',
31
54
  className,
32
55
  destination,
33
56
  children,
34
- target,
57
+ target = '_self',
35
58
  onClick,
36
59
  externalLinkAlternativeText,
37
60
  externalLinkTitle,
38
- variant,
39
- isInline,
40
- showLaunchIcon,
61
+ variant = 'default',
62
+ isInline = false,
63
+ showLaunchIcon = true,
41
64
  ...attrs
42
65
  }, ref) => {
66
+ const intl = useIntl();
43
67
  let externalLinkIcon;
44
68
 
45
69
  if (target === '_blank') {
@@ -63,11 +87,11 @@ const Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({
63
87
  externalLinkIcon = (
64
88
  <span
65
89
  className="pgn__hyperlink__external-icon"
66
- title={externalLinkTitle}
90
+ title={externalLinkTitle || intl.formatMessage(messages.externalLinkTitle)}
67
91
  >
68
92
  <Icon
69
93
  src={Launch}
70
- screenReaderText={externalLinkAlternativeText}
94
+ screenReaderText={externalLinkAlternativeText || intl.formatMessage(messages.externalLinkAltText)}
71
95
  style={{ height: '1em', width: '1em' }}
72
96
  data-testid="hyperlink-icon"
73
97
  />
@@ -76,8 +100,13 @@ const Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({
76
100
  }
77
101
  }
78
102
 
103
+ const additionalProps: Record<string, any> = { ...attrs };
104
+ if (destination) {
105
+ additionalProps.href = destination;
106
+ }
107
+
79
108
  return (
80
- <a
109
+ <Component
81
110
  ref={ref}
82
111
  className={classNames(
83
112
  'pgn__hyperlink',
@@ -88,31 +117,27 @@ const Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({
88
117
  },
89
118
  className,
90
119
  )}
91
- href={destination}
92
120
  target={target}
93
121
  onClick={onClick}
94
- {...attrs}
122
+ {...additionalProps}
95
123
  >
96
124
  {children}
97
125
  {externalLinkIcon}
98
- </a>
126
+ </Component>
99
127
  );
100
128
  });
101
129
 
102
- Hyperlink.defaultProps = {
103
- className: undefined,
104
- target: '_self',
105
- onClick: () => {},
106
- externalLinkAlternativeText: HYPER_LINK_EXTERNAL_LINK_ALT_TEXT,
107
- externalLinkTitle: HYPER_LINK_EXTERNAL_LINK_TITLE,
108
- variant: 'default',
109
- isInline: false,
110
- showLaunchIcon: true,
111
- };
112
-
113
130
  Hyperlink.propTypes = {
114
- /** specifies the URL */
115
- destination: PropTypes.string.isRequired,
131
+ /** specifies the component element type to render for the hyperlink */
132
+ // @ts-ignore
133
+ as: PropTypes.elementType,
134
+ /** specifies the URL; required iff `as` prop is a standard anchor tag */
135
+ destination: customPropTypeRequirement(
136
+ PropTypes.string,
137
+ ({ as }: { as: React.ElementType }) => as && as === 'a',
138
+ // "[`destination` is required when]..."
139
+ 'the `as` prop is a standard anchor element (i.e., "a")',
140
+ ),
116
141
  /** Content of the hyperlink */
117
142
  // @ts-ignore
118
143
  children: PropTypes.node.isRequired,
@@ -138,4 +163,19 @@ Hyperlink.propTypes = {
138
163
  showLaunchIcon: PropTypes.bool,
139
164
  };
140
165
 
166
+ Hyperlink.defaultProps = {
167
+ as: 'a',
168
+ className: undefined,
169
+ destination: undefined,
170
+ externalLinkAlternativeText: undefined,
171
+ externalLinkTitle: undefined,
172
+ isInline: false,
173
+ onClick: undefined,
174
+ showLaunchIcon: true,
175
+ target: '_self',
176
+ variant: 'default',
177
+ };
178
+
179
+ Hyperlink.displayName = 'Hyperlink';
180
+
141
181
  export default Hyperlink;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
3
4
 
4
5
  import MailtoLink from '.';
5
6
 
@@ -11,10 +12,18 @@ const content = 'content';
11
12
 
12
13
  const baseProps = { subject, body, content };
13
14
 
15
+ function MailtoLinkWrapper(props) {
16
+ return (
17
+ <IntlProvider locale="en">
18
+ <MailtoLink {...props} />
19
+ </IntlProvider>
20
+ );
21
+ }
22
+
14
23
  describe('correct rendering', () => {
15
24
  it('renders MailtoLink with single to, cc, and bcc recipient', () => {
16
25
  const singleRecipientLink = (
17
- <MailtoLink
26
+ <MailtoLinkWrapper
18
27
  {...baseProps}
19
28
  to={emailAddress}
20
29
  cc={emailAddress}
@@ -31,7 +40,7 @@ describe('correct rendering', () => {
31
40
 
32
41
  it('renders mailtoLink with many to, cc, and bcc recipients', () => {
33
42
  const multiRecipientLink = (
34
- <MailtoLink
43
+ <MailtoLinkWrapper
35
44
  {...baseProps}
36
45
  to={emailAddresses}
37
46
  cc={emailAddresses}
@@ -46,7 +55,7 @@ describe('correct rendering', () => {
46
55
  });
47
56
 
48
57
  it('renders empty mailtoLink', () => {
49
- const { getByText } = render(<MailtoLink content={content} />);
58
+ const { getByText } = render(<MailtoLinkWrapper content={content} />);
50
59
  const linkElement = getByText('content');
51
60
  expect(linkElement.getAttribute('href')).toEqual('mailto:');
52
61
  });
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { IntlProvider } from 'react-intl';
2
3
  import { render, screen } from '@testing-library/react';
3
4
  import renderer from 'react-test-renderer';
4
5
  import userEvent from '@testing-library/user-event';
@@ -20,15 +21,17 @@ describe('Menu Item renders correctly', () => {
20
21
 
21
22
  it('renders as expected with menu items', () => {
22
23
  const tree = renderer.create((
23
- <Menu>
24
- <MenuItem> A Menu Item</MenuItem>
25
- <MenuItem iconBefore={Add} stoven>A Menu Item With an Icon Before</MenuItem>
26
- <MenuItem iconAfter={Check}>A Menu Item With an Icon After </MenuItem>
27
- <MenuItem disabled>A Disabled Menu Item</MenuItem>
28
- <MenuItem as={Hyperlink} destination="https://en.wikipedia.org/wiki/Hyperlink">A Link Menu Item</MenuItem>
29
- <MenuItem as={Button} variant="primary" size="inline">A Button Menu Item</MenuItem>
30
- <MenuItem as={Form.Checkbox}>A Checkbox Menu Item</MenuItem>
31
- </Menu>
24
+ <IntlProvider locale="en">
25
+ <Menu>
26
+ <MenuItem> A Menu Item</MenuItem>
27
+ <MenuItem iconBefore={Add} stoven>A Menu Item With an Icon Before</MenuItem>
28
+ <MenuItem iconAfter={Check}>A Menu Item With an Icon After </MenuItem>
29
+ <MenuItem disabled>A Disabled Menu Item</MenuItem>
30
+ <MenuItem as={Hyperlink} destination="https://en.wikipedia.org/wiki/Hyperlink">A Link Menu Item</MenuItem>
31
+ <MenuItem as={Button} variant="primary" size="inline">A Button Menu Item</MenuItem>
32
+ <MenuItem as={Form.Checkbox}>A Checkbox Menu Item</MenuItem>
33
+ </Menu>
34
+ </IntlProvider>
32
35
  )).toJSON();
33
36
  expect(tree).toMatchSnapshot();
34
37
  });
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { IntlProvider } from 'react-intl';
3
+ import PropTypes from 'prop-types';
2
4
  import { render, screen } from '@testing-library/react';
3
5
  import renderer from 'react-test-renderer';
4
6
  import userEvent from '@testing-library/user-event';
@@ -10,27 +12,44 @@ import Hyperlink from '../Hyperlink';
10
12
  const app = document.createElement('div');
11
13
  document.body.appendChild(app);
12
14
 
15
+ function SelectMenuWrapper({ children }) {
16
+ return (
17
+ <IntlProvider locale="en">
18
+ {children}
19
+ </IntlProvider>
20
+ );
21
+ }
22
+ SelectMenuWrapper.propTypes = {
23
+ children: PropTypes.node.isRequired,
24
+ };
25
+
13
26
  function DefaultSelectMenu(props) {
14
- return <SelectMenu {...props}><MenuItem>A Menu Item</MenuItem></SelectMenu>;
27
+ return (
28
+ <SelectMenuWrapper>
29
+ <SelectMenu {...props}><MenuItem>A Menu Item</MenuItem></SelectMenu>
30
+ </SelectMenuWrapper>
31
+ );
15
32
  }
16
33
 
17
34
  function defaultSelectMenu() {
18
35
  return (
19
- <SelectMenu>
20
- <MenuItem>A Menu Item</MenuItem>
21
- <MenuItem iconBefore={Add}>A Menu Item With an Icon Before</MenuItem>
22
- <MenuItem iconAfter={Check}>A Menu Item With an Icon After </MenuItem>
23
- <MenuItem disabled>A Disabled Menu Item</MenuItem>
24
- <MenuItem as={Hyperlink} destination="https://en.wikipedia.org/wiki/Hyperlink">A Link Menu Item</MenuItem>
25
- <MenuItem>Falstaff</MenuItem>
26
- <MenuItem>Scipio</MenuItem>
27
- <MenuItem>Faustus</MenuItem>
28
- <MenuItem>Cordelia</MenuItem>
29
- <MenuItem>Renfrancine</MenuItem>
30
- <MenuItem>Stovern</MenuItem>
31
- <MenuItem>Kainian</MenuItem>
32
- <MenuItem>M. Hortens</MenuItem>
33
- </SelectMenu>
36
+ <SelectMenuWrapper>
37
+ <SelectMenu>
38
+ <MenuItem>A Menu Item</MenuItem>
39
+ <MenuItem iconBefore={Add}>A Menu Item With an Icon Before</MenuItem>
40
+ <MenuItem iconAfter={Check}>A Menu Item With an Icon After </MenuItem>
41
+ <MenuItem disabled>A Disabled Menu Item</MenuItem>
42
+ <MenuItem as={Hyperlink} destination="https://en.wikipedia.org/wiki/Hyperlink">A Link Menu Item</MenuItem>
43
+ <MenuItem>Falstaff</MenuItem>
44
+ <MenuItem>Scipio</MenuItem>
45
+ <MenuItem>Faustus</MenuItem>
46
+ <MenuItem>Cordelia</MenuItem>
47
+ <MenuItem>Renfrancine</MenuItem>
48
+ <MenuItem>Stovern</MenuItem>
49
+ <MenuItem>Kainian</MenuItem>
50
+ <MenuItem>M. Hortens</MenuItem>
51
+ </SelectMenu>
52
+ </SelectMenuWrapper>
34
53
  );
35
54
  }
36
55
 
@@ -95,7 +95,6 @@ exports[`Menu Item renders correctly renders as expected with menu items 1`] = `
95
95
  <a
96
96
  className="pgn__hyperlink default-link standalone-link pgn__menu-item"
97
97
  href="https://en.wikipedia.org/wiki/Hyperlink"
98
- onClick={[Function]}
99
98
  target="_self"
100
99
  >
101
100
  <span
package/src/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export {
31
31
  FormAutosuggestOption,
32
32
  InputGroup,
33
33
  } from './Form';
34
- export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
34
+ export { default as Hyperlink } from './Hyperlink';
35
35
  export { default as Icon } from './Icon';
36
36
  export { default as IconButton, IconButtonWithTooltip } from './IconButton';
37
37
  export { default as ModalContext } from './Modal/ModalContext';
package/src/index.js CHANGED
@@ -31,7 +31,7 @@ export {
31
31
  FormAutosuggestOption,
32
32
  InputGroup,
33
33
  } from './Form';
34
- export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
34
+ export { default as Hyperlink } from './Hyperlink';
35
35
  export { default as Icon } from './Icon';
36
36
  export { default as IconButton, IconButtonWithTooltip } from './IconButton';
37
37
  export { default as ModalContext } from './Modal/ModalContext';