@openedx/paragon 23.0.0-alpha.1 → 23.0.0-alpha.3

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 (124) hide show
  1. package/README.md +31 -22
  2. package/dist/Button/index.d.ts +35 -0
  3. package/dist/Button/index.js +37 -15
  4. package/dist/Button/index.js.map +1 -1
  5. package/dist/Button/index.scss +0 -2
  6. package/dist/Chip/ChipIcon.d.ts +13 -8
  7. package/dist/Chip/ChipIcon.js +0 -2
  8. package/dist/Chip/ChipIcon.js.map +1 -1
  9. package/dist/Chip/constants.d.ts +4 -0
  10. package/dist/Chip/constants.js +3 -2
  11. package/dist/Chip/constants.js.map +1 -0
  12. package/dist/Chip/index.d.ts +4 -3
  13. package/dist/Chip/index.js +2 -4
  14. package/dist/Chip/index.js.map +1 -1
  15. package/dist/Chip/index.scss +6 -5
  16. package/dist/Chip/mixins.scss +4 -4
  17. package/dist/ChipCarousel/index.js +0 -2
  18. package/dist/ChipCarousel/index.js.map +1 -1
  19. package/dist/Collapsible/index.scss +3 -3
  20. package/dist/Hyperlink/index.d.ts +24 -0
  21. package/dist/Hyperlink/index.js +20 -32
  22. package/dist/Hyperlink/index.js.map +1 -1
  23. package/dist/Icon/index.d.ts +4 -2
  24. package/dist/Icon/index.js +1 -1
  25. package/dist/Icon/index.js.map +1 -1
  26. package/dist/IconButton/index.d.ts +342 -0
  27. package/dist/IconButton/index.js +18 -26
  28. package/dist/IconButton/index.js.map +1 -1
  29. package/dist/Modal/ModalPopup.js +7 -1
  30. package/dist/Modal/ModalPopup.js.map +1 -1
  31. package/dist/Modal/_ModalDialog.scss +4 -0
  32. package/dist/Overlay/index.d.ts +128 -0
  33. package/dist/Overlay/index.js +8 -2
  34. package/dist/Overlay/index.js.map +1 -1
  35. package/dist/Stepper/index.scss +3 -2
  36. package/dist/Tabs/index.scss +9 -6
  37. package/dist/Tooltip/index.d.ts +7 -0
  38. package/dist/Tooltip/index.js.map +1 -1
  39. package/dist/core.css +22 -30
  40. package/dist/core.css.map +1 -1
  41. package/dist/core.min.css +1 -1
  42. package/dist/index.d.ts +5 -5
  43. package/dist/index.js +7 -7
  44. package/dist/light.css +11 -11
  45. package/dist/light.css.map +1 -1
  46. package/dist/light.min.css +1 -1
  47. package/dist/setupTest.d.ts +2 -0
  48. package/dist/setupTest.js.map +1 -0
  49. package/dist/utils/types/bootstrap.d.ts +39 -0
  50. package/dist/utils/types/bootstrap.js +2 -0
  51. package/dist/utils/types/bootstrap.js.map +1 -0
  52. package/lib/build-tokens.js +18 -4
  53. package/package.json +6 -5
  54. package/src/Button/{Button.test.jsx → Button.test.tsx} +14 -2
  55. package/src/Button/__snapshots__/{Button.test.jsx.snap → Button.test.tsx.snap} +19 -2
  56. package/src/Button/index.scss +0 -2
  57. package/src/Button/{index.jsx → index.tsx} +58 -16
  58. package/src/Chip/{Chip.test.jsx → Chip.test.tsx} +5 -7
  59. package/src/Chip/ChipIcon.tsx +8 -8
  60. package/src/Chip/{constants.js → constants.ts} +1 -1
  61. package/src/Chip/index.scss +6 -5
  62. package/src/Chip/index.tsx +6 -8
  63. package/src/Chip/mixins.scss +4 -4
  64. package/src/ChipCarousel/index.tsx +0 -2
  65. package/src/Collapsible/index.scss +3 -3
  66. package/src/Hyperlink/{Hyperlink.test.jsx → Hyperlink.test.tsx} +21 -10
  67. package/src/Hyperlink/{index.jsx → index.tsx} +41 -37
  68. package/src/Icon/index.d.ts +4 -2
  69. package/src/Icon/index.jsx +1 -1
  70. package/src/IconButton/{IconButton.test.jsx → IconButton.test.tsx} +24 -3
  71. package/src/IconButton/__snapshots__/IconButton.test.tsx.snap +90 -0
  72. package/src/IconButton/{index.jsx → index.tsx} +66 -26
  73. package/src/Modal/ModalPopup.jsx +9 -1
  74. package/src/Modal/_ModalDialog.scss +4 -0
  75. package/src/Modal/tests/ModalPopupNoMock.test.jsx +29 -0
  76. package/src/Overlay/{index.jsx → index.tsx} +13 -8
  77. package/src/Stepper/index.scss +3 -2
  78. package/src/Tabs/index.scss +9 -6
  79. package/src/Tooltip/{index.jsx → index.tsx} +9 -3
  80. package/src/index.d.ts +5 -5
  81. package/src/index.js +7 -7
  82. package/src/{setupTest.js → setupTest.ts} +1 -0
  83. package/src/utils/types/bootstrap.test.tsx +86 -0
  84. package/src/utils/types/bootstrap.ts +43 -0
  85. package/styles/css/core/variables.css +11 -22
  86. package/styles/css/themes/light/variables.css +11 -11
  87. package/styles/scss/core/_variables.scss +4 -5
  88. package/styles/scss/core/core.scss +1 -1
  89. package/tokens/README.md +1 -2
  90. package/tokens/src/core/alias/size.json +3 -3
  91. package/tokens/src/core/components/Breadcrumb.json +0 -14
  92. package/tokens/src/core/components/Card.json +6 -1
  93. package/tokens/src/core/components/Chip.json +4 -6
  94. package/tokens/src/core/components/ColorPicker.json +2 -2
  95. package/tokens/src/core/components/DataTable.json +1 -1
  96. package/tokens/src/core/components/Form/size.json +3 -7
  97. package/tokens/src/core/components/Nav.json +0 -3
  98. package/tokens/src/core/components/Pagination.json +0 -4
  99. package/tokens/src/core/components/ProductTour.json +0 -5
  100. package/tokens/src/core/global/display.json +2 -1
  101. package/tokens/src/core/global/spacing.json +7 -5
  102. package/tokens/src/themes/light/alias/color.json +2 -2
  103. package/tokens/src/themes/light/components/Alert.json +0 -9
  104. package/tokens/src/themes/light/components/Annotation.json +11 -11
  105. package/tokens/src/themes/light/components/Avatar.json +1 -1
  106. package/tokens/src/themes/light/components/Breadcrumb.json +0 -1
  107. package/tokens/src/themes/light/components/Card.json +2 -6
  108. package/tokens/src/themes/light/components/DataTable.json +1 -1
  109. package/tokens/src/themes/light/components/Form/color.json +4 -4
  110. package/tokens/src/themes/light/components/Form/elevation.json +1 -1
  111. package/tokens/src/themes/light/components/Form/other.json +3 -3
  112. package/tokens/src/themes/light/components/general/input.json +1 -1
  113. package/tokens/src/themes/light/components/general/link.json +1 -1
  114. package/tokens/src/themes/light/components/general/list.json +1 -1
  115. package/tokens/src/themes/light/components/general/text.json +7 -1
  116. package/tokens/src/themes/light/global/color.json +2 -2
  117. package/tokens/style-dictionary.js +6 -0
  118. package/src/IconButton/__snapshots__/IconButton.test.jsx.snap +0 -20
  119. /package/src/Button/{ButtonGroup.test.jsx → ButtonGroup.test.tsx} +0 -0
  120. /package/src/Button/{ButtonToolbar.test.jsx → ButtonToolbar.test.tsx} +0 -0
  121. /package/src/Button/__snapshots__/{ButtonGroup.test.jsx.snap → ButtonGroup.test.tsx.snap} +0 -0
  122. /package/src/Button/__snapshots__/{ButtonToolbar.test.jsx.snap → ButtonToolbar.test.tsx.snap} +0 -0
  123. /package/src/Chip/__snapshots__/{Chip.test.jsx.snap → Chip.test.tsx.snap} +0 -0
  124. /package/src/Tooltip/{Tooltip.test.jsx → Tooltip.test.tsx} +0 -0
@@ -1,29 +1,45 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
- import isRequiredIf from 'react-proptype-conditional-require';
5
4
  import { Launch } from '../../icons';
6
5
  import Icon from '../Icon';
7
6
 
8
- import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps';
9
-
10
7
  export const HYPER_LINK_EXTERNAL_LINK_ALT_TEXT = 'in a new tab';
11
8
  export const HYPER_LINK_EXTERNAL_LINK_TITLE = 'Opens in a new tab';
12
9
 
13
- const Hyperlink = React.forwardRef((props, ref) => {
14
- const {
15
- className,
16
- destination,
17
- children,
18
- target,
19
- onClick,
20
- externalLinkAlternativeText,
21
- externalLinkTitle,
22
- variant,
23
- isInline,
24
- showLaunchIcon,
25
- ...attrs
26
- } = props;
10
+ interface Props extends Omit<React.ComponentPropsWithRef<'a'>, 'href' | 'target'> {
11
+ /** specifies the URL */
12
+ destination: string;
13
+ /** Content of the hyperlink */
14
+ children: React.ReactNode;
15
+ /** Custom class names for the hyperlink */
16
+ className?: string;
17
+ /** Alt text for the icon indicating that this link opens in a new tab, if target="_blank". e.g. _("in a new tab") */
18
+ externalLinkAlternativeText?: string;
19
+ /** Tooltip text for the "opens in new tab" icon, if target="_blank". e.g. _("Opens in a new tab"). */
20
+ externalLinkTitle?: string;
21
+ /** type of hyperlink */
22
+ variant?: 'default' | 'muted' | 'brand';
23
+ /** Display the link with an underline. By default, it is only underlined on hover. */
24
+ isInline?: boolean;
25
+ /** specify if we need to show launch Icon. By default, it will be visible. */
26
+ showLaunchIcon?: boolean;
27
+ target?: '_blank' | '_self';
28
+ }
29
+
30
+ const Hyperlink = React.forwardRef<HTMLAnchorElement, Props>(({
31
+ className,
32
+ destination,
33
+ children,
34
+ target,
35
+ onClick,
36
+ externalLinkAlternativeText,
37
+ externalLinkTitle,
38
+ variant,
39
+ isInline,
40
+ showLaunchIcon,
41
+ ...attrs
42
+ }, ref) => {
27
43
  let externalLinkIcon;
28
44
 
29
45
  if (target === '_blank') {
@@ -105,32 +121,20 @@ Hyperlink.propTypes = {
105
121
  * loaded into the same browsing context as the current one.
106
122
  * If the target is `_blank` (opening a new window) `rel='noopener'` will be added to the anchor tag to prevent
107
123
  * any potential [reverse tabnabbing attack](https://www.owasp.org/index.php/Reverse_Tabnabbing).
108
- */
109
- target: PropTypes.string,
124
+ */
125
+ target: PropTypes.oneOf(['_blank', '_self']),
110
126
  /** specifies the callback function when the link is clicked */
111
127
  onClick: PropTypes.func,
112
- /** specifies the text for links with a `_blank` target (which loads the URL in a new browsing context). */
113
- externalLinkAlternativeText: isRequiredIf(
114
- PropTypes.string,
115
- props => props.target === '_blank',
116
- ),
117
- /** specifies the title for links with a `_blank` target (which loads the URL in a new browsing context). */
118
- externalLinkTitle: isRequiredIf(
119
- PropTypes.string,
120
- props => props.target === '_blank',
121
- ),
128
+ /** Alt text for the icon indicating that this link opens in a new tab, if target="_blank". e.g. _("in a new tab") */
129
+ externalLinkAlternativeText: PropTypes.string,
130
+ /** Tooltip text for the "opens in new tab" icon, if target="_blank". e.g. _("Opens in a new tab"). */
131
+ externalLinkTitle: PropTypes.string,
122
132
  /** type of hyperlink */
123
133
  variant: PropTypes.oneOf(['default', 'muted', 'brand']),
124
- /** specify the link style. By default, it will be underlined. */
134
+ /** Display the link with an underline. By default, it is only underlined on hover. */
125
135
  isInline: PropTypes.bool,
126
136
  /** specify if we need to show launch Icon. By default, it will be visible. */
127
137
  showLaunchIcon: PropTypes.bool,
128
138
  };
129
139
 
130
- export default withDeprecatedProps(Hyperlink, 'Hyperlink', {
131
- /** specifies the text or element that a URL should be associated with */
132
- content: {
133
- deprType: DeprTypes.MOVED,
134
- newName: 'children',
135
- },
136
- });
140
+ export default Hyperlink;
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
 
3
3
  export interface IconProps extends React.ComponentPropsWithoutRef<'span'> {
4
- src?: React.ReactElement | Function;
4
+ // Note: React.ComponentType is what we want here. React.ElementType would allow some element type strings like "div",
5
+ // but we only want to allow components like 'Add' (a specific icon component function/class)
6
+ src?: React.ComponentType;
5
7
  svgAttrs?: {
6
8
  'aria-label'?: string;
7
9
  'aria-labelledby'?: string;
8
10
  };
9
11
  id?: string | null;
10
- size?: 'xs' | 'sm' | 'md' | 'lg';
12
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'inline';
11
13
  className?: string | string[];
12
14
  hidden?: boolean;
13
15
  screenReaderText?: React.ReactNode;
@@ -74,7 +74,7 @@ Icon.propTypes = {
74
74
  * An icon component to render.
75
75
  * Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';`
76
76
  */
77
- src: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
77
+ src: PropTypes.elementType,
78
78
  /** HTML element attributes to pass through to the underlying svg element */
79
79
  svgAttrs: PropTypes.shape({
80
80
  'aria-label': PropTypes.string,
@@ -11,21 +11,27 @@ describe('<IconButton />', () => {
11
11
  const alt = 'alternative';
12
12
  const iconAs = Icon;
13
13
  const src = InfoOutline;
14
- const variant = 'secondary';
14
+ const variant = 'secondary' as const;
15
15
  const props = {
16
16
  alt,
17
17
  src,
18
18
  iconAs,
19
19
  variant,
20
20
  };
21
- const iconParams = {
21
+ const deprecatedFontAwesomeExample = {
22
22
  prefix: 'pgn',
23
23
  iconName: 'InfoOutlineIcon',
24
24
  icon: [InfoOutline],
25
25
  };
26
26
  it('renders with required props', () => {
27
27
  const tree = renderer.create((
28
- <IconButton icon={iconParams} alt={alt} />
28
+ <IconButton iconAs={Icon} src={InfoOutline} alt={alt} />
29
+ )).toJSON();
30
+ expect(tree).toMatchSnapshot();
31
+ });
32
+ it('renders with deprecated props', () => {
33
+ const tree = renderer.create((
34
+ <IconButton icon={deprecatedFontAwesomeExample} alt={alt} />
29
35
  )).toJSON();
30
36
  expect(tree).toMatchSnapshot();
31
37
  });
@@ -94,4 +100,19 @@ describe('<IconButton />', () => {
94
100
  expect(spy2).toHaveBeenCalledTimes(1);
95
101
  });
96
102
  });
103
+
104
+ describe('<IconButton.IconButtonWithTooltip>', () => {
105
+ it('renders with required props', () => {
106
+ const tree = renderer.create((
107
+ <IconButton.IconButtonWithTooltip
108
+ iconAs={Icon}
109
+ src={InfoOutline}
110
+ alt={alt}
111
+ tooltipContent="Hello"
112
+ tooltipPlacement="left-end"
113
+ />
114
+ )).toJSON();
115
+ expect(tree).toMatchSnapshot();
116
+ });
117
+ });
97
118
  });
@@ -0,0 +1,90 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<IconButton /> <IconButton.IconButtonWithTooltip> renders with required props 1`] = `
4
+ <button
5
+ aria-label="alternative"
6
+ className="btn-icon btn-icon-primary btn-icon-md"
7
+ onBlur={[Function]}
8
+ onClick={[Function]}
9
+ onFocus={[Function]}
10
+ onMouseOut={[Function]}
11
+ onMouseOver={[Function]}
12
+ type="button"
13
+ >
14
+ <span
15
+ className="btn-icon__icon-container"
16
+ >
17
+ <span
18
+ className="pgn__icon btn-icon__icon"
19
+ >
20
+ <svg
21
+ aria-hidden={true}
22
+ fill="none"
23
+ focusable={false}
24
+ height={24}
25
+ role="img"
26
+ viewBox="0 0 24 24"
27
+ width={24}
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ >
30
+ <path
31
+ d="M11 7h2v2h-2V7Zm0 4h2v6h-2v-6Zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8Z"
32
+ fill="currentColor"
33
+ />
34
+ </svg>
35
+ </span>
36
+ </span>
37
+ </button>
38
+ `;
39
+
40
+ exports[`<IconButton /> renders with deprecated props 1`] = `
41
+ <button
42
+ aria-label="alternative"
43
+ className="btn-icon btn-icon-primary btn-icon-md"
44
+ onClick={[Function]}
45
+ type="button"
46
+ >
47
+ <span
48
+ className="btn-icon__icon-container"
49
+ >
50
+ <span
51
+ aria-hidden={true}
52
+ className="btn-icon__icon"
53
+ id="Icon1"
54
+ />
55
+ </span>
56
+ </button>
57
+ `;
58
+
59
+ exports[`<IconButton /> renders with required props 1`] = `
60
+ <button
61
+ aria-label="alternative"
62
+ className="btn-icon btn-icon-primary btn-icon-md"
63
+ onClick={[Function]}
64
+ type="button"
65
+ >
66
+ <span
67
+ className="btn-icon__icon-container"
68
+ >
69
+ <span
70
+ className="pgn__icon btn-icon__icon"
71
+ >
72
+ <svg
73
+ aria-hidden={true}
74
+ fill="none"
75
+ focusable={false}
76
+ height={24}
77
+ role="img"
78
+ viewBox="0 0 24 24"
79
+ width={24}
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ >
82
+ <path
83
+ d="M11 7h2v2h-2V7Zm0 4h2v6h-2v-6Zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8Z"
84
+ fill="currentColor"
85
+ />
86
+ </svg>
87
+ </span>
88
+ </span>
89
+ </button>
90
+ `;
@@ -1,12 +1,44 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
-
4
+ import { type Placement } from 'react-bootstrap/Overlay';
5
5
  import Icon from '../Icon';
6
6
  import { OverlayTrigger } from '../Overlay';
7
7
  import Tooltip from '../Tooltip';
8
8
 
9
- const IconButton = React.forwardRef(({
9
+ interface Props extends React.HTMLAttributes<HTMLButtonElement> {
10
+ iconAs?: React.ComponentType<any>,
11
+ /** Additional CSS class[es] to apply to this button */
12
+ className?: string;
13
+ /** Alt text for your icon. For best practice, avoid using alt text to describe
14
+ * the image in the `IconButton`. Instead, we recommend describing the function
15
+ * of the button. */
16
+ alt: string;
17
+ /** Changes icon styles for dark background */
18
+ invertColors?: boolean;
19
+ /** An icon component to render. Example import of a Paragon icon component:
20
+ * `import { Check } from '@openedx/paragon/icons';`
21
+ * */
22
+ // Note: React.ComponentType is what we want here. React.ElementType would allow some element type strings like "div",
23
+ // but we only want to allow components like 'Add' (a specific icon component function/class)
24
+ src?: React.ComponentType;
25
+ /** Extra class names that will be added to the icon */
26
+ iconClassNames?: string;
27
+ /** Click handler for the button */
28
+ onClick?: React.MouseEventHandler<HTMLButtonElement>;
29
+ /** whether to show the `IconButton` in an active state, whose styling is distinct from default state */
30
+ isActive?: boolean;
31
+ /** @deprecated Using FontAwesome icons is deprecated. Instead, pass iconAs={Icon} src={...} */
32
+ icon?: { prefix?: string; iconName?: string, icon?: any[] },
33
+ /** Type of button (uses Bootstrap options) */
34
+ variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'light' | 'dark' | 'black' | 'brand';
35
+ /** size of button to render */
36
+ size?: 'sm' | 'md' | 'inline';
37
+ /** no children */
38
+ children?: never;
39
+ }
40
+
41
+ const IconButton = React.forwardRef<HTMLButtonElement, Props>(({
10
42
  className,
11
43
  alt,
12
44
  invertColors,
@@ -18,6 +50,7 @@ const IconButton = React.forwardRef(({
18
50
  variant,
19
51
  iconAs,
20
52
  isActive,
53
+ children, // unused, just here because we don't want it to be part of 'attrs'
21
54
  ...attrs
22
55
  }, ref) => {
23
56
  const invert = invertColors ? 'inverse-' : '';
@@ -42,11 +75,13 @@ const IconButton = React.forwardRef(({
42
75
  {...attrs}
43
76
  >
44
77
  <span className="btn-icon__icon-container">
45
- <IconComponent
46
- className={classNames('btn-icon__icon', iconClassNames)}
47
- icon={icon}
48
- src={src}
49
- />
78
+ {IconComponent && (
79
+ <IconComponent
80
+ className={classNames('btn-icon__icon', iconClassNames)}
81
+ icon={icon as any}
82
+ src={src}
83
+ />
84
+ )}
50
85
  </span>
51
86
  </button>
52
87
  );
@@ -54,7 +89,7 @@ const IconButton = React.forwardRef(({
54
89
 
55
90
  IconButton.defaultProps = {
56
91
  iconAs: Icon,
57
- src: null,
92
+ src: undefined,
58
93
  icon: undefined,
59
94
  iconClassNames: undefined,
60
95
  className: undefined,
@@ -63,17 +98,18 @@ IconButton.defaultProps = {
63
98
  size: 'md',
64
99
  onClick: () => {},
65
100
  isActive: false,
101
+ children: undefined,
66
102
  };
67
103
 
68
104
  IconButton.propTypes = {
69
105
  /** A custom class name. */
70
106
  className: PropTypes.string,
71
107
  /** Component that renders the icon, currently defaults to `Icon` */
72
- iconAs: PropTypes.elementType,
108
+ iconAs: PropTypes.elementType as any,
73
109
  /** An icon component to render. Example import of a Paragon icon component:
74
- * `import { Check } from '@openedx/paragon/dist/icon';`
110
+ * `import { Check } from '@openedx/paragon/icons';`
75
111
  * */
76
- src: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
112
+ src: PropTypes.elementType as any,
77
113
  /** Alt text for your icon. For best practice, avoid using alt text to describe
78
114
  * the image in the `IconButton`. Instead, we recommend describing the function
79
115
  * of the button. */
@@ -86,7 +122,7 @@ IconButton.propTypes = {
86
122
  iconName: PropTypes.string,
87
123
  // eslint-disable-next-line react/forbid-prop-types
88
124
  icon: PropTypes.array,
89
- }),
125
+ }) as any,
90
126
  /** Extra class names that will be added to the icon */
91
127
  iconClassNames: PropTypes.string,
92
128
  /** Click handler for the button */
@@ -99,38 +135,40 @@ IconButton.propTypes = {
99
135
  isActive: PropTypes.bool,
100
136
  };
101
137
 
138
+ interface PropsWithTooltip extends Props {
139
+ /** choose from https://popper.js.org/docs/v2/constructors/#options */
140
+ tooltipPlacement: Placement,
141
+ /** any content to pass to tooltip content area */
142
+ tooltipContent: React.ReactNode,
143
+ }
144
+
102
145
  /**
103
- *
104
- * @param { object } args Arguments
105
- * @param { string } args.tooltipPlacement choose from https://popper.js.org/docs/v2/constructors/#options
106
- * @param { React.Component } args.tooltipContent any content to pass to tooltip content area
107
- * @returns { IconButton } a button wrapped in overlaytrigger
146
+ * An icon button wrapped in overlaytrigger to display a tooltip.
108
147
  */
109
148
  function IconButtonWithTooltip({
110
- tooltipPlacement, tooltipContent, variant, invertColors, ...props
111
- }) {
112
- const invert = invertColors ? 'inverse-' : '';
149
+ tooltipPlacement, tooltipContent, ...props
150
+ }: PropsWithTooltip) {
151
+ const invert = props.invertColors ? 'inverse-' : '';
113
152
  return (
114
153
  <OverlayTrigger
115
154
  placement={tooltipPlacement}
116
155
  overlay={(
117
156
  <Tooltip
118
157
  id={`iconbutton-tooltip-${tooltipPlacement}`}
119
- variant={invert ? 'light' : ''}
158
+ variant={invert ? 'light' : undefined}
120
159
  >
121
160
  {tooltipContent}
122
161
  </Tooltip>
123
162
  )}
124
163
  >
125
- <IconButton variant={variant} invertColors={invertColors} {...props} />
164
+ <IconButton {...props} />
126
165
  </OverlayTrigger>
127
166
  );
128
167
  }
129
168
 
130
169
  IconButtonWithTooltip.defaultProps = {
170
+ ...IconButton.defaultProps,
131
171
  tooltipPlacement: 'top',
132
- variant: 'primary',
133
- invertColors: false,
134
172
  };
135
173
 
136
174
  IconButtonWithTooltip.propTypes = {
@@ -144,7 +182,9 @@ IconButtonWithTooltip.propTypes = {
144
182
  invertColors: PropTypes.bool,
145
183
  };
146
184
 
147
- IconButton.IconButtonWithTooltip = IconButtonWithTooltip;
185
+ (IconButton as any).IconButtonWithTooltip = IconButtonWithTooltip;
148
186
 
149
- export default IconButton;
187
+ export default IconButton as typeof IconButton & {
188
+ IconButtonWithTooltip: typeof IconButtonWithTooltip,
189
+ };
150
190
  export { IconButtonWithTooltip };
@@ -34,6 +34,14 @@ function ModalPopup({
34
34
  },
35
35
  ];
36
36
 
37
+ const handleOnClickOutside = (e) => {
38
+ if (e.type === 'touchstart') {
39
+ return;
40
+ }
41
+
42
+ onClose();
43
+ };
44
+
37
45
  return (
38
46
  <ModalContextProvider onClose={onClose} isOpen={isOpen} isBlocking={isBlocking}>
39
47
  <RootComponent>
@@ -47,7 +55,7 @@ function ModalPopup({
47
55
  scrollLock={false}
48
56
  enabled={isOpen}
49
57
  onEscapeKey={onClose}
50
- onClickOutside={onClose}
58
+ onClickOutside={handleOnClickOutside}
51
59
  >
52
60
  {isOpen && (
53
61
  <div className="pgn__modal-popup__tooltip">
@@ -76,6 +76,10 @@
76
76
  border-top: solid 1px var(--pgn-color-light-base);
77
77
  padding-top: var(--pgn-spacing-modal-footer-padding-y);
78
78
  }
79
+
80
+ .pgn__modal-header {
81
+ border-radius: 0;
82
+ }
79
83
  }
80
84
 
81
85
  // Made specific due to a selector in Modal.scss
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import ModalPopup from '../ModalPopup';
5
+
6
+ describe('<ModalPopup />', () => {
7
+ const mockPositionRef = React.createRef();
8
+
9
+ describe('when isOpen', () => {
10
+ const isOpen = true;
11
+ const closeFn = jest.fn();
12
+
13
+ it('calls close on click events but not touchstart events', async () => {
14
+ render(
15
+ <ModalPopup
16
+ positionRef={mockPositionRef}
17
+ isOpen={isOpen}
18
+ onClose={closeFn}
19
+ >
20
+ <div>Modal Contents</div>
21
+ </ModalPopup>,
22
+ );
23
+ await fireEvent.touchStart(document.body);
24
+ expect(closeFn).not.toHaveBeenCalled();
25
+ await userEvent.click(document.body);
26
+ expect(closeFn).toHaveBeenCalled();
27
+ });
28
+ });
29
+ });
@@ -1,9 +1,14 @@
1
1
  import React from 'react';
2
- import BaseOverlay from 'react-bootstrap/Overlay';
3
- import BaseOverlayTrigger from 'react-bootstrap/OverlayTrigger';
2
+ import BaseOverlay, { type OverlayProps, type Placement } from 'react-bootstrap/Overlay';
3
+ import BaseOverlayTrigger, { type OverlayTriggerProps, type OverlayTriggerType } from 'react-bootstrap/OverlayTrigger';
4
+ import Fade from 'react-bootstrap/Fade';
4
5
  import PropTypes from 'prop-types';
5
6
 
6
- const PLACEMENT_VARIANTS = [
7
+ // Note: The only thing this file adds to the base component is propTypes validation.
8
+ // As more Paragon consumers adopt TypeScript, we could consider removing almost all of this code
9
+ // and just re-export the Overlay and OverlayTrigger components from react-bootstrap unmodified.
10
+
11
+ const PLACEMENT_VARIANTS: Placement[] = [
7
12
  'auto-start',
8
13
  'auto',
9
14
  'auto-end',
@@ -21,16 +26,16 @@ const PLACEMENT_VARIANTS = [
21
26
  'left-start',
22
27
  ];
23
28
 
24
- const TRIGGER_VARIANTS = [
29
+ const TRIGGER_VARIANTS: OverlayTriggerType[] = [
25
30
  'hover',
26
31
  'click',
27
32
  'focus',
28
33
  ];
29
34
 
30
- function Overlay(props) {
35
+ function Overlay(props: OverlayProps) {
31
36
  return <BaseOverlay {...props} />;
32
37
  }
33
- function OverlayTrigger(props) {
38
+ function OverlayTrigger(props: OverlayTriggerProps) {
34
39
  return (
35
40
  <BaseOverlayTrigger {...props}>
36
41
  {props.children}
@@ -88,7 +93,7 @@ Overlay.propTypes = {
88
93
  * Animate the entering and exiting of the Overlay. `true` will use the `<Fade>` transition,
89
94
  * or a custom react-transition-group `<Transition>` component can be provided.
90
95
  */
91
- transition: PropTypes.oneOfType([PropTypes.bool, PropTypes.elementType]),
96
+ transition: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
92
97
  };
93
98
 
94
99
  OverlayTrigger.propTypes = {
@@ -144,7 +149,7 @@ Overlay.defaultProps = {
144
149
  rootCloseEvent: undefined,
145
150
  show: false,
146
151
  target: undefined,
147
- transition: true,
152
+ transition: Fade,
148
153
  };
149
154
 
150
155
  OverlayTrigger.defaultProps = {
@@ -8,8 +8,9 @@
8
8
 
9
9
  .pgn__stepper-header-step-list {
10
10
  list-style: none;
11
- // stylelint-disable-next-line max-line-length
12
- padding: var(--pgn-spacing-stepper-header-step-list-padding-y) var(--pgn-spacing-stepper-header-step-list-padding-x);
11
+ padding:
12
+ var(--pgn-spacing-stepper-header-step-list-padding-y)
13
+ var(--pgn-spacing-stepper-header-step-list-padding-x);
13
14
  display: flex;
14
15
  align-items: center;
15
16
  margin: var(--pgn-spacing-stepper-header-step-list-margin);
@@ -30,8 +30,9 @@
30
30
  margin: 0;
31
31
 
32
32
  .dropdown .dropdown-toggle {
33
- // stylelint-disable-next-line max-line-length
34
- padding: var(--pgn-spacing-tab-more-link-dropdown-toggle-padding-x) var(--pgn-spacing-tab-more-link-dropdown-toggle-padding-y);
33
+ padding:
34
+ var(--pgn-spacing-tab-more-link-dropdown-toggle-padding-x)
35
+ var(--pgn-spacing-tab-more-link-dropdown-toggle-padding-y);
35
36
 
36
37
  &:focus {
37
38
  background-color: var(--pgn-color-tab-more-link-dropdown-toggle-bg-focus);
@@ -62,8 +63,9 @@
62
63
  // Nav inverse pills
63
64
  &.nav-inverse-pills .pgn__tab_more.nav-link {
64
65
  .dropdown .dropdown-toggle {
65
- // stylelint-disable-next-line max-line-length
66
- padding: var(--pgn-spacing-tab-inverse-pills-link-dropdown-toggle-padding-x) var(--pgn-spacing-tab-inverse-pills-link-dropdown-toggle-padding-y);
66
+ padding:
67
+ var(--pgn-spacing-tab-inverse-pills-link-dropdown-toggle-padding-x)
68
+ var(--pgn-spacing-tab-inverse-pills-link-dropdown-toggle-padding-y);
67
69
  border: none;
68
70
 
69
71
  &:focus {
@@ -97,8 +99,9 @@
97
99
 
98
100
  // Nav inverse tabs
99
101
  &.nav-inverse-tabs .pgn__tab_more.nav-link .dropdown .dropdown-toggle {
100
- // stylelint-disable-next-line max-line-length
101
- padding: var(--pgn-spacing-tab-inverse-tabs-link-dropdown-toggle-padding-x) var(--pgn-spacing-tab-inverse-tabs-link-dropdown-toggle-padding-y);
102
+ padding:
103
+ var(--pgn-spacing-tab-inverse-tabs-link-dropdown-toggle-padding-x)
104
+ var(--pgn-spacing-tab-inverse-tabs-link-dropdown-toggle-padding-y);
102
105
 
103
106
  &:hover {
104
107
  background-color: var(--pgn-color-tab-inverse-pills-link-dropdown-toggle-bg-hover);
@@ -1,9 +1,15 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
- import BaseTooltip from 'react-bootstrap/Tooltip';
4
+ import BaseTooltip, { type TooltipProps as BaseTooltipProps } from 'react-bootstrap/Tooltip';
5
+ import { type Placement } from 'react-bootstrap/Overlay';
6
+ import type { ComponentWithAsProp } from '../utils/types/bootstrap';
5
7
 
6
- const PLACEMENT_VARIANTS = [
8
+ interface TooltipProps extends BaseTooltipProps {
9
+ variant?: 'light';
10
+ }
11
+
12
+ const PLACEMENT_VARIANTS: Placement[] = [
7
13
  'auto-start',
8
14
  'auto',
9
15
  'auto-end',
@@ -21,7 +27,7 @@ const PLACEMENT_VARIANTS = [
21
27
  'left-start',
22
28
  ];
23
29
 
24
- const Tooltip = React.forwardRef(({
30
+ const Tooltip: ComponentWithAsProp<'div', TooltipProps> = React.forwardRef(({
25
31
  children,
26
32
  variant,
27
33
  ...props