@openedx/paragon 22.5.0 → 22.6.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.
- package/dist/Button/index.d.ts +35 -0
- package/dist/Button/index.js +38 -15
- package/dist/Button/index.js.map +1 -1
- package/dist/Chip/ChipIcon.d.ts +2 -2
- package/dist/Chip/ChipIcon.js.map +1 -1
- package/dist/Chip/index.d.ts +2 -2
- package/dist/Chip/index.js +2 -2
- package/dist/Chip/index.js.map +1 -1
- package/dist/Hyperlink/index.d.ts +1 -1
- package/dist/Icon/index.d.ts +4 -2
- package/dist/Icon/index.js +1 -1
- package/dist/Icon/index.js.map +1 -1
- package/dist/Modal/_ModalDialog.scss +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/paragon.css +1 -1
- package/dist/utils/types/bootstrap.d.ts +39 -0
- package/dist/utils/types/bootstrap.js +2 -0
- package/dist/utils/types/bootstrap.js.map +1 -0
- package/package.json +3 -3
- package/src/Button/{Button.test.jsx → Button.test.tsx} +14 -2
- package/src/Button/__snapshots__/{Button.test.jsx.snap → Button.test.tsx.snap} +19 -2
- package/src/Button/{index.jsx → index.tsx} +59 -16
- package/src/Chip/ChipIcon.tsx +1 -1
- package/src/Chip/index.tsx +5 -5
- package/src/Icon/index.d.ts +4 -2
- package/src/Icon/index.jsx +1 -1
- package/src/Modal/_ModalDialog.scss +4 -0
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -3
- package/src/utils/types/bootstrap.test.tsx +86 -0
- package/src/utils/types/bootstrap.ts +43 -0
- /package/src/Button/{ButtonGroup.test.jsx → ButtonGroup.test.tsx} +0 -0
- /package/src/Button/{ButtonToolbar.test.jsx → ButtonToolbar.test.tsx} +0 -0
- /package/src/Button/__snapshots__/{ButtonGroup.test.jsx.snap → ButtonGroup.test.tsx.snap} +0 -0
- /package/src/Button/__snapshots__/{ButtonToolbar.test.jsx.snap → ButtonToolbar.test.tsx.snap} +0 -0
|
@@ -30,7 +30,7 @@ describe('<Button />', () => {
|
|
|
30
30
|
|
|
31
31
|
it('renders with props iconAfter and size', () => {
|
|
32
32
|
const tree = renderer.create((
|
|
33
|
-
<Button iconAfter={Close} size="
|
|
33
|
+
<Button iconAfter={Close} size="sm">Button</Button>
|
|
34
34
|
)).toJSON();
|
|
35
35
|
expect(tree).toMatchSnapshot();
|
|
36
36
|
});
|
|
@@ -94,9 +94,21 @@ describe('<Button />', () => {
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test('test button as hyperlink', () => {
|
|
97
|
-
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
98
|
+
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>);
|
|
98
100
|
expect(screen.getByRole('link').getAttribute('href')).toEqual('https://www.poop.com/💩');
|
|
99
101
|
});
|
|
100
102
|
});
|
|
103
|
+
|
|
104
|
+
test('with size="inline"', () => {
|
|
105
|
+
const tree = renderer.create((
|
|
106
|
+
<p>
|
|
107
|
+
<span className="mr-1">2 items selected.</span>
|
|
108
|
+
<Button variant="link" size="inline">Clear</Button>
|
|
109
|
+
</p>
|
|
110
|
+
)).toJSON();
|
|
111
|
+
expect(tree).toMatchSnapshot();
|
|
112
|
+
});
|
|
101
113
|
});
|
|
102
114
|
});
|
|
@@ -55,13 +55,13 @@ exports[`<Button /> correct rendering renders with props iconAfter 1`] = `
|
|
|
55
55
|
|
|
56
56
|
exports[`<Button /> correct rendering renders with props iconAfter and size 1`] = `
|
|
57
57
|
<button
|
|
58
|
-
className="btn btn-primary btn-
|
|
58
|
+
className="btn btn-primary btn-sm"
|
|
59
59
|
disabled={false}
|
|
60
60
|
type="button"
|
|
61
61
|
>
|
|
62
62
|
Button
|
|
63
63
|
<span
|
|
64
|
-
className="pgn__icon
|
|
64
|
+
className="pgn__icon pgn__icon__sm btn-icon-after"
|
|
65
65
|
>
|
|
66
66
|
<svg
|
|
67
67
|
aria-hidden={true}
|
|
@@ -197,3 +197,20 @@ exports[`<Button /> correct rendering renders without props 1`] = `
|
|
|
197
197
|
Button
|
|
198
198
|
</button>
|
|
199
199
|
`;
|
|
200
|
+
|
|
201
|
+
exports[`<Button /> correct rendering with size="inline" 1`] = `
|
|
202
|
+
<p>
|
|
203
|
+
<span
|
|
204
|
+
className="mr-1"
|
|
205
|
+
>
|
|
206
|
+
2 items selected.
|
|
207
|
+
</span>
|
|
208
|
+
<button
|
|
209
|
+
className="btn btn-link btn-inline"
|
|
210
|
+
disabled={false}
|
|
211
|
+
type="button"
|
|
212
|
+
>
|
|
213
|
+
Clear
|
|
214
|
+
</button>
|
|
215
|
+
</p>
|
|
216
|
+
`;
|
|
@@ -1,32 +1,57 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
2
|
+
import PropTypes, { type Requireable } from 'prop-types';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import BaseButton from 'react-bootstrap/Button';
|
|
5
|
-
import BaseButtonGroup from 'react-bootstrap/ButtonGroup';
|
|
6
|
-
import BaseButtonToolbar from 'react-bootstrap/ButtonToolbar';
|
|
4
|
+
import BaseButton, { type ButtonProps as BaseButtonProps } from 'react-bootstrap/Button';
|
|
5
|
+
import BaseButtonGroup, { type ButtonGroupProps as BaseButtonGroupProps } from 'react-bootstrap/ButtonGroup';
|
|
6
|
+
import BaseButtonToolbar, { type ButtonToolbarProps } from 'react-bootstrap/ButtonToolbar';
|
|
7
|
+
import type { ComponentWithAsProp } from '../utils/types/bootstrap';
|
|
8
|
+
// @ts-ignore - we're not going to bother adding types for the deprecated button
|
|
7
9
|
import ButtonDeprecated from './deprecated';
|
|
8
10
|
|
|
9
11
|
import Icon from '../Icon';
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
interface ButtonProps extends Omit<BaseButtonProps, 'size'> {
|
|
14
|
+
/**
|
|
15
|
+
* An icon component to render. Example:
|
|
16
|
+
* ```
|
|
17
|
+
* import { Close } from '@openedx/paragon/icons';
|
|
18
|
+
* <Button iconBefore={Close}>Close</Button>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
iconBefore?: React.ComponentType;
|
|
22
|
+
/**
|
|
23
|
+
* An icon component to render. Example:
|
|
24
|
+
* ```
|
|
25
|
+
* import { Close } from '@openedx/paragon/icons';
|
|
26
|
+
* <Button iconAfter={Close}>Close</Button>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
iconAfter?: React.ComponentType;
|
|
30
|
+
size?: 'sm' | 'md' | 'lg' | 'inline';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type ButtonType = ComponentWithAsProp<'button', ButtonProps> & { Deprecated?: any };
|
|
34
|
+
|
|
35
|
+
const Button: ButtonType = React.forwardRef<HTMLButtonElement, ButtonProps>(({
|
|
12
36
|
children,
|
|
13
37
|
iconAfter,
|
|
14
38
|
iconBefore,
|
|
39
|
+
size,
|
|
15
40
|
...props
|
|
16
41
|
}, ref) => (
|
|
17
42
|
<BaseButton
|
|
43
|
+
size={size as 'sm' | 'lg' | undefined} // Bootstrap's <Button> types do not allow 'md' or 'inline', but we do.
|
|
18
44
|
{...props}
|
|
19
45
|
className={classNames(props.className)}
|
|
20
46
|
ref={ref}
|
|
21
47
|
>
|
|
22
|
-
{iconBefore && <Icon className="btn-icon-before" size={
|
|
48
|
+
{iconBefore && <Icon className="btn-icon-before" size={size} src={iconBefore} />}
|
|
23
49
|
{children}
|
|
24
|
-
{iconAfter && <Icon className="btn-icon-after" size={
|
|
50
|
+
{iconAfter && <Icon className="btn-icon-after" size={size} src={iconAfter} />}
|
|
25
51
|
</BaseButton>
|
|
26
52
|
));
|
|
27
53
|
|
|
28
54
|
Button.propTypes = {
|
|
29
|
-
...Button.propTypes,
|
|
30
55
|
/** Specifies class name to apply to the button */
|
|
31
56
|
className: PropTypes.string,
|
|
32
57
|
/** Disables the Button, preventing mouse events, even if the underlying component is an `<a>` element */
|
|
@@ -52,10 +77,13 @@ Button.propTypes = {
|
|
|
52
77
|
variant: PropTypes.string,
|
|
53
78
|
/** An icon component to render.
|
|
54
79
|
* Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';` */
|
|
55
|
-
iconBefore: PropTypes.
|
|
80
|
+
iconBefore: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
56
81
|
/** An icon component to render.
|
|
57
82
|
* Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';` */
|
|
58
|
-
iconAfter: PropTypes.
|
|
83
|
+
iconAfter: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
84
|
+
// The 'as' type casting above is required for TypeScript checking, because the 'PropTypes.elementType' type normally
|
|
85
|
+
// allows strings as a value (for use cases like 'div') but we don't support that for <Icon />/iconBefore/iconAfter.
|
|
86
|
+
// The React TypeScript type definitions are more specific (React.ComponentType vs React.ElementType).
|
|
59
87
|
};
|
|
60
88
|
|
|
61
89
|
Button.defaultProps = {
|
|
@@ -69,20 +97,29 @@ Button.defaultProps = {
|
|
|
69
97
|
|
|
70
98
|
Button.Deprecated = ButtonDeprecated;
|
|
71
99
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
// We could just re-export 'ButtonGroup' and 'ButtonToolbar', but we currently
|
|
101
|
+
// override them to add propTypes validation at runtime, since most Paragon
|
|
102
|
+
// consumers aren't using TypeScript yet. We also force ButtonGroup's 'size'
|
|
103
|
+
// prop to accept our custom values of 'md' and 'inline' which are used in
|
|
104
|
+
// Paragon but not used in the base Bootstrap classes.
|
|
105
|
+
|
|
106
|
+
interface ButtonGroupProps extends Omit<BaseButtonGroupProps, 'size'> {
|
|
107
|
+
size?: 'sm' | 'md' | 'lg' | 'inline';
|
|
77
108
|
}
|
|
78
109
|
|
|
110
|
+
const ButtonGroup: ComponentWithAsProp<'div', ButtonGroupProps> = (
|
|
111
|
+
React.forwardRef<HTMLButtonElement, ButtonGroupProps>(({ size, ...props }, ref) => (
|
|
112
|
+
<BaseButtonGroup size={size as 'sm' | 'lg'} {...props} ref={ref} />
|
|
113
|
+
))
|
|
114
|
+
);
|
|
115
|
+
|
|
79
116
|
ButtonGroup.propTypes = {
|
|
80
117
|
/** Specifies element type for this component. */
|
|
81
118
|
as: PropTypes.elementType,
|
|
82
119
|
/** An ARIA role describing the button group. */
|
|
83
120
|
role: PropTypes.string,
|
|
84
121
|
/** Specifies the size for all Buttons in the group. */
|
|
85
|
-
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
122
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg', 'inline']),
|
|
86
123
|
/** Display as a button toggle group. */
|
|
87
124
|
toggle: PropTypes.bool,
|
|
88
125
|
/** Specifies if the set of Buttons should appear vertically stacked. */
|
|
@@ -100,6 +137,12 @@ ButtonGroup.defaultProps = {
|
|
|
100
137
|
size: 'md',
|
|
101
138
|
};
|
|
102
139
|
|
|
140
|
+
const ButtonToolbar: ComponentWithAsProp<'div', ButtonToolbarProps> = (
|
|
141
|
+
React.forwardRef<HTMLButtonElement, ButtonToolbarProps>((props, ref) => (
|
|
142
|
+
<BaseButtonToolbar {...props} ref={ref} />
|
|
143
|
+
))
|
|
144
|
+
);
|
|
145
|
+
|
|
103
146
|
ButtonToolbar.propTypes = {
|
|
104
147
|
/** An ARIA role describing the button group. */
|
|
105
148
|
role: PropTypes.string,
|
package/src/Chip/ChipIcon.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { STYLE_VARIANTS } from './constants';
|
|
|
8
8
|
|
|
9
9
|
export interface ChipIconProps {
|
|
10
10
|
className: string,
|
|
11
|
-
src: React.
|
|
11
|
+
src: React.ComponentType,
|
|
12
12
|
onClick?: KeyboardEventHandler & MouseEventHandler,
|
|
13
13
|
alt?: string,
|
|
14
14
|
variant: string,
|
package/src/Chip/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { ForwardedRef, KeyboardEventHandler, MouseEventHandler } from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
2
|
+
import PropTypes, { type Requireable } from 'prop-types';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
import { requiredWhen } from '../utils/propTypes';
|
|
@@ -15,9 +15,9 @@ export interface IChip {
|
|
|
15
15
|
onClick?: KeyboardEventHandler & MouseEventHandler,
|
|
16
16
|
className?: string,
|
|
17
17
|
variant?: string,
|
|
18
|
-
iconBefore?: React.
|
|
18
|
+
iconBefore?: React.ComponentType,
|
|
19
19
|
iconBeforeAlt?: string,
|
|
20
|
-
iconAfter?: React.
|
|
20
|
+
iconAfter?: React.ComponentType,
|
|
21
21
|
iconAfterAlt?: string,
|
|
22
22
|
onIconBeforeClick?: KeyboardEventHandler & MouseEventHandler,
|
|
23
23
|
onIconAfterClick?: KeyboardEventHandler & MouseEventHandler,
|
|
@@ -111,7 +111,7 @@ Chip.propTypes = {
|
|
|
111
111
|
*
|
|
112
112
|
* `import { Check } from '@openedx/paragon/icons';`
|
|
113
113
|
*/
|
|
114
|
-
iconBefore: PropTypes.
|
|
114
|
+
iconBefore: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
115
115
|
/** Specifies icon alt text. */
|
|
116
116
|
iconBeforeAlt: requiredWhen(PropTypes.string, ['iconBefore', 'onIconBeforeClick']),
|
|
117
117
|
/** A click handler for the `Chip` icon before. */
|
|
@@ -122,7 +122,7 @@ Chip.propTypes = {
|
|
|
122
122
|
*
|
|
123
123
|
* `import { Check } from '@openedx/paragon/icons';`
|
|
124
124
|
*/
|
|
125
|
-
iconAfter: PropTypes.
|
|
125
|
+
iconAfter: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
126
126
|
/** Specifies icon alt text. */
|
|
127
127
|
iconAfterAlt: requiredWhen(PropTypes.string, ['iconAfter', 'onIconAfterClick']),
|
|
128
128
|
/** A click handler for the `Chip` icon after. */
|
package/src/Icon/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
export interface IconProps extends React.ComponentPropsWithoutRef<'span'> {
|
|
4
|
-
|
|
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;
|
package/src/Icon/index.jsx
CHANGED
|
@@ -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.
|
|
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,
|
package/src/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Things that have types
|
|
6
6
|
// // // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
7
7
|
export { default as Bubble } from './Bubble';
|
|
8
|
+
export { default as Button, ButtonGroup, ButtonToolbar } from './Button';
|
|
8
9
|
export { default as Chip, CHIP_PGN_CLASS } from './Chip';
|
|
9
10
|
export { default as ChipCarousel } from './ChipCarousel';
|
|
10
11
|
export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
|
|
@@ -21,7 +22,6 @@ export const Avatar: any; // from './Avatar';
|
|
|
21
22
|
export const AvatarButton: any; // from './AvatarButton';
|
|
22
23
|
export const Badge: any; // from './Badge';
|
|
23
24
|
export const Breadcrumb: any; // from './Breadcrumb';
|
|
24
|
-
export const Button: any, ButtonGroup: any, ButtonToolbar: any; // from './Button';
|
|
25
25
|
export const
|
|
26
26
|
Card: any,
|
|
27
27
|
CardColumns: any,
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
// and each line number is the same
|
|
1
|
+
// Keep this file in sync with the .d.ts file (manually). It's in the same order
|
|
2
|
+
// and each line number is the same, to make it easier.
|
|
3
3
|
|
|
4
4
|
// // // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
5
5
|
// Things that have types
|
|
6
6
|
// // // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
7
7
|
export { default as Bubble } from './Bubble';
|
|
8
|
+
export { default as Button, ButtonGroup, ButtonToolbar } from './Button';
|
|
8
9
|
export { default as Chip, CHIP_PGN_CLASS } from './Chip';
|
|
9
10
|
export { default as ChipCarousel } from './ChipCarousel';
|
|
10
11
|
export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink';
|
|
@@ -21,7 +22,6 @@ export { default as Avatar } from './Avatar';
|
|
|
21
22
|
export { default as AvatarButton } from './AvatarButton';
|
|
22
23
|
export { default as Badge } from './Badge';
|
|
23
24
|
export { default as Breadcrumb } from './Breadcrumb';
|
|
24
|
-
export { default as Button, ButtonGroup, ButtonToolbar } from './Button';
|
|
25
25
|
export {
|
|
26
26
|
default as Card,
|
|
27
27
|
CardColumns,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { BsPropsWithAs, ComponentWithAsProp } from './bootstrap';
|
|
4
|
+
|
|
5
|
+
// Note: these are type-only tests. They don't actually do much at runtime; the important checks are at transpile time.
|
|
6
|
+
|
|
7
|
+
describe('BsPropsWithAs', () => {
|
|
8
|
+
interface Props<As extends React.ElementType = 'table'> extends BsPropsWithAs<As> {
|
|
9
|
+
otherProp?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
it('defines optional bsPrefix, className, and as but no other props', () => {
|
|
13
|
+
const checkProps = <As extends React.ElementType = 'table'>(_props: Props<As>) => {};
|
|
14
|
+
// These are all valid props per the prop definition:
|
|
15
|
+
checkProps({ });
|
|
16
|
+
checkProps({ bsPrefix: 'bs' });
|
|
17
|
+
checkProps({ className: 'foo bar' });
|
|
18
|
+
checkProps({ as: 'tr' });
|
|
19
|
+
checkProps({ className: 'foo bar', as: 'button', otherProp: 15 });
|
|
20
|
+
// But these are all invalid:
|
|
21
|
+
// @ts-expect-error
|
|
22
|
+
checkProps({ newProp: 10 });
|
|
23
|
+
// @ts-expect-error
|
|
24
|
+
checkProps({ onClick: () => {} });
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
checkProps({ id: 'id' });
|
|
27
|
+
// @ts-expect-error
|
|
28
|
+
checkProps({ children: <tr /> });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('ComponentWithAsProp', () => {
|
|
33
|
+
interface MyProps extends BsPropsWithAs {
|
|
34
|
+
customProp?: string;
|
|
35
|
+
}
|
|
36
|
+
const MyComponent: ComponentWithAsProp<'div', MyProps> = (
|
|
37
|
+
React.forwardRef<HTMLDivElement, MyProps>(
|
|
38
|
+
({ as: Inner = 'div', ...props }, ref) => <Inner {...props} ref={ref} />,
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line react/function-component-definition
|
|
43
|
+
const CustomComponent: React.FC<{ requiredProp: string }> = () => <span />;
|
|
44
|
+
|
|
45
|
+
it('is defined to wrap a <div> by default, and accepts related props', () => {
|
|
46
|
+
// This is valid - by default it is a DIV so accepts props and ref related to DIV:
|
|
47
|
+
const divClick: React.MouseEventHandler<HTMLDivElement> = () => {};
|
|
48
|
+
const divRef: React.RefObject<HTMLDivElement> = { current: null };
|
|
49
|
+
const valid = <MyComponent ref={divRef} onClick={divClick} customProp="foo" />;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('is defined to wrap a <div> by default, and rejects unrelated props', () => {
|
|
53
|
+
const btnRef: React.RefObject<HTMLButtonElement> = { current: null };
|
|
54
|
+
// @ts-expect-error because the ref is to a <button> ref, but this is wrapping a <div>
|
|
55
|
+
const invalidRef = <MyComponent ref={btnRef} customProp="foo" />;
|
|
56
|
+
|
|
57
|
+
const btnClick: React.MouseEventHandler<HTMLButtonElement> = () => {};
|
|
58
|
+
// @ts-expect-error because the handler is for a <button> event, but this is wrapping a <div>
|
|
59
|
+
const invalidClick = <MyComponent onClick={btnClick} />;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('can be changed to wrap a <canvas>, and accepts related props', () => {
|
|
63
|
+
const canvasClick: React.MouseEventHandler<HTMLCanvasElement> = () => {};
|
|
64
|
+
const canvasRef: React.RefObject<HTMLCanvasElement> = { current: null };
|
|
65
|
+
const valid = <MyComponent as="canvas" ref={canvasRef} onClick={canvasClick} customProp="foo" />;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('can be changed to wrap a <canvas>, and rejects unrelated props', () => {
|
|
69
|
+
const btnRef: React.RefObject<HTMLButtonElement> = { current: null };
|
|
70
|
+
// @ts-expect-error because the ref is to a <button> ref, but this is wrapping an <canvas>
|
|
71
|
+
const invalidRef = <MyComponent as="canvas" ref={btnRef} customProp="foo" />;
|
|
72
|
+
|
|
73
|
+
const btnClick: React.MouseEventHandler<HTMLButtonElement> = () => {};
|
|
74
|
+
// @ts-expect-error because the handler is for a <button> event, but this is wrapping an <canvas>
|
|
75
|
+
const invalidClick = <MyComponent as="canvas" onClick={btnClick} />;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('can be changed to wrap a custom component, and accepts related props', () => {
|
|
79
|
+
const valid = <MyComponent as={CustomComponent} requiredProp="hello" />;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('can be changed to wrap a custom component, and rejects unrelated props', () => {
|
|
83
|
+
// @ts-expect-error The onClick prop has not been declared for our custom component.
|
|
84
|
+
const valid = <MyComponent as={CustomComponent} requiredProp="hello" onClick={() => {}} />;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types related to bootstrap components
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import type { BsPrefixProps, BsPrefixRefForwardingComponent } from 'react-bootstrap/esm/helpers';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type helper for defining props of a component that wraps a bootstrap
|
|
10
|
+
* component. This type defines three props:
|
|
11
|
+
* 1. `className`: this component accepts additional CSS classes.
|
|
12
|
+
* 2. `bsPrefix`: locally change the class name prefix used for this component.
|
|
13
|
+
* 3. `as`: optionally specify which HTML element or Component is used, e.g. `"div"`
|
|
14
|
+
*
|
|
15
|
+
* This type assumes no `children` are allowed, but you can extend it to allow children.
|
|
16
|
+
*/
|
|
17
|
+
export type BsPropsWithAs<As extends React.ElementType = React.ElementType> = BsPrefixProps<As>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This is a helper that can be used to define the type of a Paragon component
|
|
21
|
+
* that accepts an `as` prop.
|
|
22
|
+
*
|
|
23
|
+
* It:
|
|
24
|
+
* - assumes you are using `forwardRef`, and sets the type of the `ref` prop
|
|
25
|
+
* to match the type of the component passed in the `as` prop.
|
|
26
|
+
* - assumes you are passing all unused props to the component, so adds any
|
|
27
|
+
* props from the `as` component type to the props you specify as `Props`.
|
|
28
|
+
*
|
|
29
|
+
* Example;
|
|
30
|
+
* ```
|
|
31
|
+
* interface MyProps extends BsPropsWithAs {
|
|
32
|
+
* customProp?: string;
|
|
33
|
+
* }
|
|
34
|
+
* export const MyComponent: ComponentWithAsProp<'div', MyProps> = (
|
|
35
|
+
* React.forwardRef<HTMLDivElement, MyProps>(
|
|
36
|
+
* ({ as: Inner = 'div', ...props }, ref) => <Inner {...props} ref={ref} />,
|
|
37
|
+
* )
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
* Note that you need to define the default (e.g. `'div'`) in three different places.
|
|
41
|
+
*/
|
|
42
|
+
export type ComponentWithAsProp<DefaultElementType extends React.ElementType, Props>
|
|
43
|
+
= BsPrefixRefForwardingComponent<DefaultElementType, Props>;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/Button/__snapshots__/{ButtonToolbar.test.jsx.snap → ButtonToolbar.test.tsx.snap}
RENAMED
|
File without changes
|