@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.
- package/README.md +31 -22
- package/dist/Button/index.d.ts +35 -0
- package/dist/Button/index.js +37 -15
- package/dist/Button/index.js.map +1 -1
- package/dist/Button/index.scss +0 -2
- package/dist/Chip/ChipIcon.d.ts +13 -8
- package/dist/Chip/ChipIcon.js +0 -2
- package/dist/Chip/ChipIcon.js.map +1 -1
- package/dist/Chip/constants.d.ts +4 -0
- package/dist/Chip/constants.js +3 -2
- package/dist/Chip/constants.js.map +1 -0
- package/dist/Chip/index.d.ts +4 -3
- package/dist/Chip/index.js +2 -4
- package/dist/Chip/index.js.map +1 -1
- package/dist/Chip/index.scss +6 -5
- package/dist/Chip/mixins.scss +4 -4
- package/dist/ChipCarousel/index.js +0 -2
- package/dist/ChipCarousel/index.js.map +1 -1
- package/dist/Collapsible/index.scss +3 -3
- package/dist/Hyperlink/index.d.ts +24 -0
- package/dist/Hyperlink/index.js +20 -32
- package/dist/Hyperlink/index.js.map +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/IconButton/index.d.ts +342 -0
- package/dist/IconButton/index.js +18 -26
- package/dist/IconButton/index.js.map +1 -1
- package/dist/Modal/ModalPopup.js +7 -1
- package/dist/Modal/ModalPopup.js.map +1 -1
- package/dist/Modal/_ModalDialog.scss +4 -0
- package/dist/Overlay/index.d.ts +128 -0
- package/dist/Overlay/index.js +8 -2
- package/dist/Overlay/index.js.map +1 -1
- package/dist/Stepper/index.scss +3 -2
- package/dist/Tabs/index.scss +9 -6
- package/dist/Tooltip/index.d.ts +7 -0
- package/dist/Tooltip/index.js.map +1 -1
- package/dist/core.css +22 -30
- package/dist/core.css.map +1 -1
- package/dist/core.min.css +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -7
- package/dist/light.css +11 -11
- package/dist/light.css.map +1 -1
- package/dist/light.min.css +1 -1
- package/dist/setupTest.d.ts +2 -0
- package/dist/setupTest.js.map +1 -0
- 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/lib/build-tokens.js +18 -4
- package/package.json +6 -5
- 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.scss +0 -2
- package/src/Button/{index.jsx → index.tsx} +58 -16
- package/src/Chip/{Chip.test.jsx → Chip.test.tsx} +5 -7
- package/src/Chip/ChipIcon.tsx +8 -8
- package/src/Chip/{constants.js → constants.ts} +1 -1
- package/src/Chip/index.scss +6 -5
- package/src/Chip/index.tsx +6 -8
- package/src/Chip/mixins.scss +4 -4
- package/src/ChipCarousel/index.tsx +0 -2
- package/src/Collapsible/index.scss +3 -3
- package/src/Hyperlink/{Hyperlink.test.jsx → Hyperlink.test.tsx} +21 -10
- package/src/Hyperlink/{index.jsx → index.tsx} +41 -37
- package/src/Icon/index.d.ts +4 -2
- package/src/Icon/index.jsx +1 -1
- package/src/IconButton/{IconButton.test.jsx → IconButton.test.tsx} +24 -3
- package/src/IconButton/__snapshots__/IconButton.test.tsx.snap +90 -0
- package/src/IconButton/{index.jsx → index.tsx} +66 -26
- package/src/Modal/ModalPopup.jsx +9 -1
- package/src/Modal/_ModalDialog.scss +4 -0
- package/src/Modal/tests/ModalPopupNoMock.test.jsx +29 -0
- package/src/Overlay/{index.jsx → index.tsx} +13 -8
- package/src/Stepper/index.scss +3 -2
- package/src/Tabs/index.scss +9 -6
- package/src/Tooltip/{index.jsx → index.tsx} +9 -3
- package/src/index.d.ts +5 -5
- package/src/index.js +7 -7
- package/src/{setupTest.js → setupTest.ts} +1 -0
- package/src/utils/types/bootstrap.test.tsx +86 -0
- package/src/utils/types/bootstrap.ts +43 -0
- package/styles/css/core/variables.css +11 -22
- package/styles/css/themes/light/variables.css +11 -11
- package/styles/scss/core/_variables.scss +4 -5
- package/styles/scss/core/core.scss +1 -1
- package/tokens/README.md +1 -2
- package/tokens/src/core/alias/size.json +3 -3
- package/tokens/src/core/components/Breadcrumb.json +0 -14
- package/tokens/src/core/components/Card.json +6 -1
- package/tokens/src/core/components/Chip.json +4 -6
- package/tokens/src/core/components/ColorPicker.json +2 -2
- package/tokens/src/core/components/DataTable.json +1 -1
- package/tokens/src/core/components/Form/size.json +3 -7
- package/tokens/src/core/components/Nav.json +0 -3
- package/tokens/src/core/components/Pagination.json +0 -4
- package/tokens/src/core/components/ProductTour.json +0 -5
- package/tokens/src/core/global/display.json +2 -1
- package/tokens/src/core/global/spacing.json +7 -5
- package/tokens/src/themes/light/alias/color.json +2 -2
- package/tokens/src/themes/light/components/Alert.json +0 -9
- package/tokens/src/themes/light/components/Annotation.json +11 -11
- package/tokens/src/themes/light/components/Avatar.json +1 -1
- package/tokens/src/themes/light/components/Breadcrumb.json +0 -1
- package/tokens/src/themes/light/components/Card.json +2 -6
- package/tokens/src/themes/light/components/DataTable.json +1 -1
- package/tokens/src/themes/light/components/Form/color.json +4 -4
- package/tokens/src/themes/light/components/Form/elevation.json +1 -1
- package/tokens/src/themes/light/components/Form/other.json +3 -3
- package/tokens/src/themes/light/components/general/input.json +1 -1
- package/tokens/src/themes/light/components/general/link.json +1 -1
- package/tokens/src/themes/light/components/general/list.json +1 -1
- package/tokens/src/themes/light/components/general/text.json +7 -1
- package/tokens/src/themes/light/global/color.json +2 -2
- package/tokens/style-dictionary.js +6 -0
- package/src/IconButton/__snapshots__/IconButton.test.jsx.snap +0 -20
- /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
- /package/src/Chip/__snapshots__/{Chip.test.jsx.snap → Chip.test.tsx.snap} +0 -0
- /package/src/Tooltip/{Tooltip.test.jsx → Tooltip.test.tsx} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupTest.js","names":["ResizeObserver","_classCallCheck","_createClass","key","value","observe","unobserve","disconnect","window"],"sources":["../src/setupTest.ts"],"sourcesContent":["/* eslint-disable import/no-extraneous-dependencies */\nimport 'regenerator-runtime/runtime';\n\nimport '@testing-library/jest-dom';\n\nclass ResizeObserver {\n observe() {\n // do nothing\n }\n\n unobserve() {\n // do nothing\n }\n\n disconnect() {\n // do nothing\n }\n}\n\nwindow.ResizeObserver = ResizeObserver;\n"],"mappings":";;;;;;AAAA;AACA,OAAO,6BAA6B;AAEpC,OAAO,2BAA2B;AAAC,IAE7BA,cAAc;EAAA,SAAAA,eAAA;IAAAC,eAAA,OAAAD,cAAA;EAAA;EAAAE,YAAA,CAAAF,cAAA;IAAAG,GAAA;IAAAC,KAAA,EAClB,SAAAC,QAAA,EAAU;MACR;IAAA;EACD;IAAAF,GAAA;IAAAC,KAAA,EAED,SAAAE,UAAA,EAAY;MACV;IAAA;EACD;IAAAH,GAAA;IAAAC,KAAA,EAED,SAAAG,WAAA,EAAa;MACX;IAAA;EACD;EAAA,OAAAP,cAAA;AAAA;AAGHQ,MAAM,CAACR,cAAc,GAAGA,cAAc","ignoreList":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types related to bootstrap components
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import type { BsPrefixProps, BsPrefixRefForwardingComponent } from 'react-bootstrap/esm/helpers';
|
|
6
|
+
/**
|
|
7
|
+
* Type helper for defining props of a component that wraps a bootstrap
|
|
8
|
+
* component. This type defines three props:
|
|
9
|
+
* 1. `className`: this component accepts additional CSS classes.
|
|
10
|
+
* 2. `bsPrefix`: locally change the class name prefix used for this component.
|
|
11
|
+
* 3. `as`: optionally specify which HTML element or Component is used, e.g. `"div"`
|
|
12
|
+
*
|
|
13
|
+
* This type assumes no `children` are allowed, but you can extend it to allow children.
|
|
14
|
+
*/
|
|
15
|
+
export type BsPropsWithAs<As extends React.ElementType = React.ElementType> = BsPrefixProps<As>;
|
|
16
|
+
/**
|
|
17
|
+
* This is a helper that can be used to define the type of a Paragon component
|
|
18
|
+
* that accepts an `as` prop.
|
|
19
|
+
*
|
|
20
|
+
* It:
|
|
21
|
+
* - assumes you are using `forwardRef`, and sets the type of the `ref` prop
|
|
22
|
+
* to match the type of the component passed in the `as` prop.
|
|
23
|
+
* - assumes you are passing all unused props to the component, so adds any
|
|
24
|
+
* props from the `as` component type to the props you specify as `Props`.
|
|
25
|
+
*
|
|
26
|
+
* Example;
|
|
27
|
+
* ```
|
|
28
|
+
* interface MyProps extends BsPropsWithAs {
|
|
29
|
+
* customProp?: string;
|
|
30
|
+
* }
|
|
31
|
+
* export const MyComponent: ComponentWithAsProp<'div', MyProps> = (
|
|
32
|
+
* React.forwardRef<HTMLDivElement, MyProps>(
|
|
33
|
+
* ({ as: Inner = 'div', ...props }, ref) => <Inner {...props} ref={ref} />,
|
|
34
|
+
* )
|
|
35
|
+
* );
|
|
36
|
+
* ```
|
|
37
|
+
* Note that you need to define the default (e.g. `'div'`) in three different places.
|
|
38
|
+
*/
|
|
39
|
+
export type ComponentWithAsProp<DefaultElementType extends React.ElementType, Props> = BsPrefixRefForwardingComponent<DefaultElementType, Props>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","names":[],"sources":["../../../src/utils/types/bootstrap.ts"],"sourcesContent":["/**\n * Types related to bootstrap components\n */\nimport React from 'react';\n\nimport type { BsPrefixProps, BsPrefixRefForwardingComponent } from 'react-bootstrap/esm/helpers';\n\n/**\n * Type helper for defining props of a component that wraps a bootstrap\n * component. This type defines three props:\n * 1. `className`: this component accepts additional CSS classes.\n * 2. `bsPrefix`: locally change the class name prefix used for this component.\n * 3. `as`: optionally specify which HTML element or Component is used, e.g. `\"div\"`\n *\n * This type assumes no `children` are allowed, but you can extend it to allow children.\n */\nexport type BsPropsWithAs<As extends React.ElementType = React.ElementType> = BsPrefixProps<As>;\n\n/**\n * This is a helper that can be used to define the type of a Paragon component\n * that accepts an `as` prop.\n *\n * It:\n * - assumes you are using `forwardRef`, and sets the type of the `ref` prop\n * to match the type of the component passed in the `as` prop.\n * - assumes you are passing all unused props to the component, so adds any\n * props from the `as` component type to the props you specify as `Props`.\n *\n * Example;\n * ```\n * interface MyProps extends BsPropsWithAs {\n * customProp?: string;\n * }\n * export const MyComponent: ComponentWithAsProp<'div', MyProps> = (\n * React.forwardRef<HTMLDivElement, MyProps>(\n * ({ as: Inner = 'div', ...props }, ref) => <Inner {...props} ref={ref} />,\n * )\n * );\n * ```\n * Note that you need to define the default (e.g. `'div'`) in three different places.\n */\nexport type ComponentWithAsProp<DefaultElementType extends React.ElementType, Props>\n = BsPrefixRefForwardingComponent<DefaultElementType, Props>;\n"],"mappings":"","ignoreList":[]}
|
package/lib/build-tokens.js
CHANGED
|
@@ -31,8 +31,13 @@ async function buildTokensCommand(commandArgs) {
|
|
|
31
31
|
} = minimist(commandArgs, { alias, default: defaultParams, boolean: 'source-tokens-only' });
|
|
32
32
|
|
|
33
33
|
const coreConfig = {
|
|
34
|
-
include: [
|
|
35
|
-
|
|
34
|
+
include: [
|
|
35
|
+
path.resolve(__dirname, '../tokens/src/core/**/*.json'),
|
|
36
|
+
path.resolve(__dirname, '../tokens/src/core/**/*.toml'),
|
|
37
|
+
],
|
|
38
|
+
source: tokensSource
|
|
39
|
+
? [`${tokensSource}/core/**/*.json`, `${tokensSource}/core/**/*.toml`]
|
|
40
|
+
: [],
|
|
36
41
|
platforms: {
|
|
37
42
|
css: {
|
|
38
43
|
prefix: 'pgn',
|
|
@@ -67,8 +72,17 @@ async function buildTokensCommand(commandArgs) {
|
|
|
67
72
|
|
|
68
73
|
const getStyleDictionaryConfig = (themeVariant) => ({
|
|
69
74
|
...coreConfig,
|
|
70
|
-
include: [
|
|
71
|
-
|
|
75
|
+
include: [
|
|
76
|
+
...coreConfig.include,
|
|
77
|
+
path.resolve(__dirname, `../tokens/src/themes/${themeVariant}/**/*.json`),
|
|
78
|
+
path.resolve(__dirname, `../tokens/src/themes/${themeVariant}/**/*.toml`),
|
|
79
|
+
],
|
|
80
|
+
source: tokensSource
|
|
81
|
+
? [
|
|
82
|
+
`${tokensSource}/themes/${themeVariant}/**/*.json`,
|
|
83
|
+
`${tokensSource}/themes/${themeVariant}/**/*.toml`,
|
|
84
|
+
]
|
|
85
|
+
: [],
|
|
72
86
|
transform: {
|
|
73
87
|
'color/sass-color-functions': {
|
|
74
88
|
...StyleDictionary.transform['color/sass-color-functions'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openedx/paragon",
|
|
3
|
-
"version": "23.0.0-alpha.
|
|
3
|
+
"version": "23.0.0-alpha.3",
|
|
4
4
|
"description": "Accessible, responsive UI component library based on Bootstrap.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"file-selector": "^0.6.0",
|
|
69
69
|
"glob": "^8.0.3",
|
|
70
70
|
"inquirer": "^8.2.5",
|
|
71
|
+
"js-toml": "^1.0.0",
|
|
71
72
|
"lodash.uniqby": "^4.7.0",
|
|
72
73
|
"log-update": "^4.0.0",
|
|
73
74
|
"mailto-link": "^2.0.0",
|
|
@@ -122,8 +123,8 @@
|
|
|
122
123
|
"@testing-library/react-hooks": "^8.0.1",
|
|
123
124
|
"@testing-library/user-event": "^13.5.0",
|
|
124
125
|
"@types/jest": "^29.5.10",
|
|
125
|
-
"@types/react": "17.0.
|
|
126
|
-
"@types/react-dom": "17.0.11",
|
|
126
|
+
"@types/react": "^17.0.80",
|
|
127
|
+
"@types/react-dom": "^17.0.11",
|
|
127
128
|
"@types/react-responsive": "^8.0.8",
|
|
128
129
|
"@types/react-table": "^7.7.19",
|
|
129
130
|
"@types/react-test-renderer": "^18.0.0",
|
|
@@ -168,7 +169,7 @@
|
|
|
168
169
|
"^.+\\.tsx?$": "ts-jest"
|
|
169
170
|
},
|
|
170
171
|
"setupFilesAfterEnv": [
|
|
171
|
-
"./src/setupTest.
|
|
172
|
+
"./src/setupTest.ts"
|
|
172
173
|
],
|
|
173
174
|
"moduleNameMapper": {
|
|
174
175
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
|
|
@@ -179,7 +180,7 @@
|
|
|
179
180
|
],
|
|
180
181
|
"coveragePathIgnorePatterns": [
|
|
181
182
|
"/node_modules/",
|
|
182
|
-
"src/setupTest.
|
|
183
|
+
"src/setupTest.ts",
|
|
183
184
|
"src/index.js",
|
|
184
185
|
"/tests/",
|
|
185
186
|
"/www/",
|
|
@@ -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
|
+
`;
|
package/src/Button/index.scss
CHANGED
|
@@ -1,31 +1,55 @@
|
|
|
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';
|
|
7
8
|
|
|
8
9
|
import Icon from '../Icon';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
interface ButtonProps extends Omit<BaseButtonProps, 'size'> {
|
|
12
|
+
/**
|
|
13
|
+
* An icon component to render. Example:
|
|
14
|
+
* ```
|
|
15
|
+
* import { Close } from '@openedx/paragon/icons';
|
|
16
|
+
* <Button iconBefore={Close}>Close</Button>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
iconBefore?: React.ComponentType;
|
|
20
|
+
/**
|
|
21
|
+
* An icon component to render. Example:
|
|
22
|
+
* ```
|
|
23
|
+
* import { Close } from '@openedx/paragon/icons';
|
|
24
|
+
* <Button iconAfter={Close}>Close</Button>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
iconAfter?: React.ComponentType;
|
|
28
|
+
size?: 'sm' | 'md' | 'lg' | 'inline';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ButtonType = ComponentWithAsProp<'button', ButtonProps> & { Deprecated?: any };
|
|
32
|
+
|
|
33
|
+
const Button: ButtonType = React.forwardRef<HTMLButtonElement, ButtonProps>(({
|
|
11
34
|
children,
|
|
12
35
|
iconAfter,
|
|
13
36
|
iconBefore,
|
|
37
|
+
size,
|
|
14
38
|
...props
|
|
15
39
|
}, ref) => (
|
|
16
40
|
<BaseButton
|
|
41
|
+
size={size as 'sm' | 'lg' | undefined} // Bootstrap's <Button> types do not allow 'md' or 'inline', but we do.
|
|
17
42
|
{...props}
|
|
18
43
|
className={classNames(props.className)}
|
|
19
44
|
ref={ref}
|
|
20
45
|
>
|
|
21
|
-
{iconBefore && <Icon className="btn-icon-before" size={
|
|
46
|
+
{iconBefore && <Icon className="btn-icon-before" size={size} src={iconBefore} />}
|
|
22
47
|
{children}
|
|
23
|
-
{iconAfter && <Icon className="btn-icon-after" size={
|
|
48
|
+
{iconAfter && <Icon className="btn-icon-after" size={size} src={iconAfter} />}
|
|
24
49
|
</BaseButton>
|
|
25
50
|
));
|
|
26
51
|
|
|
27
52
|
Button.propTypes = {
|
|
28
|
-
...Button.propTypes,
|
|
29
53
|
/** Specifies class name to apply to the button */
|
|
30
54
|
className: PropTypes.string,
|
|
31
55
|
/** Disables the Button, preventing mouse events, even if the underlying component is an `<a>` element */
|
|
@@ -51,10 +75,13 @@ Button.propTypes = {
|
|
|
51
75
|
variant: PropTypes.string,
|
|
52
76
|
/** An icon component to render.
|
|
53
77
|
* Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';` */
|
|
54
|
-
iconBefore: PropTypes.
|
|
78
|
+
iconBefore: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
55
79
|
/** An icon component to render.
|
|
56
80
|
* Example import of a Paragon icon component: `import { Check } from '@openedx/paragon/icons';` */
|
|
57
|
-
iconAfter: PropTypes.
|
|
81
|
+
iconAfter: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
82
|
+
// The 'as' type casting above is required for TypeScript checking, because the 'PropTypes.elementType' type normally
|
|
83
|
+
// allows strings as a value (for use cases like 'div') but we don't support that for <Icon />/iconBefore/iconAfter.
|
|
84
|
+
// The React TypeScript type definitions are more specific (React.ComponentType vs React.ElementType).
|
|
58
85
|
};
|
|
59
86
|
|
|
60
87
|
Button.defaultProps = {
|
|
@@ -66,20 +93,29 @@ Button.defaultProps = {
|
|
|
66
93
|
disabled: false,
|
|
67
94
|
};
|
|
68
95
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
// We could just re-export 'ButtonGroup' and 'ButtonToolbar', but we currently
|
|
97
|
+
// override them to add propTypes validation at runtime, since most Paragon
|
|
98
|
+
// consumers aren't using TypeScript yet. We also force ButtonGroup's 'size'
|
|
99
|
+
// prop to accept our custom values of 'md' and 'inline' which are used in
|
|
100
|
+
// Paragon but not used in the base Bootstrap classes.
|
|
101
|
+
|
|
102
|
+
interface ButtonGroupProps extends Omit<BaseButtonGroupProps, 'size'> {
|
|
103
|
+
size?: 'sm' | 'md' | 'lg' | 'inline';
|
|
74
104
|
}
|
|
75
105
|
|
|
106
|
+
const ButtonGroup: ComponentWithAsProp<'div', ButtonGroupProps> = (
|
|
107
|
+
React.forwardRef<HTMLButtonElement, ButtonGroupProps>(({ size, ...props }, ref) => (
|
|
108
|
+
<BaseButtonGroup size={size as 'sm' | 'lg'} {...props} ref={ref} />
|
|
109
|
+
))
|
|
110
|
+
);
|
|
111
|
+
|
|
76
112
|
ButtonGroup.propTypes = {
|
|
77
113
|
/** Specifies element type for this component. */
|
|
78
114
|
as: PropTypes.elementType,
|
|
79
115
|
/** An ARIA role describing the button group. */
|
|
80
116
|
role: PropTypes.string,
|
|
81
117
|
/** Specifies the size for all Buttons in the group. */
|
|
82
|
-
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
118
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg', 'inline']),
|
|
83
119
|
/** Display as a button toggle group. */
|
|
84
120
|
toggle: PropTypes.bool,
|
|
85
121
|
/** Specifies if the set of Buttons should appear vertically stacked. */
|
|
@@ -97,6 +133,12 @@ ButtonGroup.defaultProps = {
|
|
|
97
133
|
size: 'md',
|
|
98
134
|
};
|
|
99
135
|
|
|
136
|
+
const ButtonToolbar: ComponentWithAsProp<'div', ButtonToolbarProps> = (
|
|
137
|
+
React.forwardRef<HTMLButtonElement, ButtonToolbarProps>((props, ref) => (
|
|
138
|
+
<BaseButtonToolbar {...props} ref={ref} />
|
|
139
|
+
))
|
|
140
|
+
);
|
|
141
|
+
|
|
100
142
|
ButtonToolbar.propTypes = {
|
|
101
143
|
/** An ARIA role describing the button group. */
|
|
102
144
|
role: PropTypes.string,
|
|
@@ -7,7 +7,7 @@ import { Close } from '../../icons';
|
|
|
7
7
|
import { STYLE_VARIANTS } from './constants';
|
|
8
8
|
import Chip from '.';
|
|
9
9
|
|
|
10
|
-
function TestChip(props) {
|
|
10
|
+
function TestChip(props: Omit<React.ComponentProps<typeof Chip>, 'children'>) {
|
|
11
11
|
return (
|
|
12
12
|
<Chip {...props}>
|
|
13
13
|
Test
|
|
@@ -42,15 +42,13 @@ describe('<Chip />', () => {
|
|
|
42
42
|
iconBeforeAlt="close icon"
|
|
43
43
|
iconAfter={Close}
|
|
44
44
|
iconAfterAlt="close icon"
|
|
45
|
-
|
|
46
|
-
Chip
|
|
47
|
-
</TestChip>
|
|
45
|
+
/>
|
|
48
46
|
)).toJSON();
|
|
49
47
|
expect(tree).toMatchSnapshot();
|
|
50
48
|
});
|
|
51
49
|
it('renders div with "button" role when onClick is provided', () => {
|
|
52
50
|
const tree = renderer.create((
|
|
53
|
-
<TestChip onClick={jest.fn}
|
|
51
|
+
<TestChip onClick={jest.fn} />
|
|
54
52
|
)).toJSON();
|
|
55
53
|
expect(tree).toMatchSnapshot();
|
|
56
54
|
});
|
|
@@ -104,7 +102,7 @@ describe('<Chip />', () => {
|
|
|
104
102
|
/>,
|
|
105
103
|
);
|
|
106
104
|
const iconAfter = screen.getByLabelText('icon-after');
|
|
107
|
-
await userEvent.click(iconAfter
|
|
105
|
+
await userEvent.click(iconAfter);
|
|
108
106
|
expect(func).toHaveBeenCalledTimes(1);
|
|
109
107
|
});
|
|
110
108
|
it('onIconBeforeClick is triggered', async () => {
|
|
@@ -130,7 +128,7 @@ describe('<Chip />', () => {
|
|
|
130
128
|
/>,
|
|
131
129
|
);
|
|
132
130
|
const iconBefore = screen.getByLabelText('icon-before');
|
|
133
|
-
await userEvent.click(iconBefore
|
|
131
|
+
await userEvent.click(iconBefore);
|
|
134
132
|
expect(func).toHaveBeenCalledTimes(1);
|
|
135
133
|
});
|
|
136
134
|
it('checks the absence of the `selected` class in the chip', async () => {
|
package/src/Chip/ChipIcon.tsx
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import React, { KeyboardEventHandler, MouseEventHandler } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import Icon from '../Icon';
|
|
4
|
-
// @ts-ignore
|
|
5
4
|
import IconButton from '../IconButton';
|
|
6
|
-
// @ts-ignore
|
|
7
5
|
import { STYLE_VARIANTS } from './constants';
|
|
8
6
|
|
|
9
|
-
export
|
|
7
|
+
export type ChipIconProps = {
|
|
10
8
|
className: string,
|
|
11
|
-
src: React.
|
|
12
|
-
|
|
13
|
-
alt?: string,
|
|
14
|
-
variant: string,
|
|
9
|
+
src: React.ComponentType,
|
|
10
|
+
variant: typeof STYLE_VARIANTS[keyof typeof STYLE_VARIANTS],
|
|
15
11
|
disabled?: boolean,
|
|
16
|
-
}
|
|
12
|
+
} & (
|
|
13
|
+
// Either _both_ onClick and alt are provided, or neither is:
|
|
14
|
+
| { onClick: KeyboardEventHandler<HTMLButtonElement> & MouseEventHandler<HTMLButtonElement>, alt: string }
|
|
15
|
+
| { onClick?: undefined, alt?: undefined }
|
|
16
|
+
);
|
|
17
17
|
|
|
18
18
|
function ChipIcon({
|
|
19
19
|
className, src, onClick, alt, variant, disabled,
|
package/src/Chip/index.scss
CHANGED
|
@@ -93,11 +93,12 @@
|
|
|
93
93
|
background-color: var(--pgn-color-chip-bg-dark);
|
|
94
94
|
|
|
95
95
|
&.selected {
|
|
96
|
-
@include chip-outline(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
@include chip-outline(
|
|
97
|
+
var(--pgn-color-chip-outline-dark),
|
|
98
|
+
calc(var(--pgn-spacing-chip-outline-selected-distance-dark) * -1),
|
|
99
|
+
calc(var(--pgn-size-chip-border-radius) + var(--pgn-spacing-chip-outline-width)),
|
|
100
|
+
var(--pgn-spacing-chip-outline-selected-distance-dark)
|
|
101
|
+
);
|
|
101
102
|
|
|
102
103
|
&:focus {
|
|
103
104
|
border: 1px solid var(--pgn-color-chip-border-focus-selected-dark);
|
package/src/Chip/index.tsx
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
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';
|
|
6
|
-
// @ts-ignore
|
|
7
6
|
import { STYLE_VARIANTS } from './constants';
|
|
8
|
-
// @ts-ignore
|
|
9
7
|
import ChipIcon from './ChipIcon';
|
|
10
8
|
|
|
11
9
|
export const CHIP_PGN_CLASS = 'pgn__chip';
|
|
@@ -14,10 +12,10 @@ export interface IChip {
|
|
|
14
12
|
children: React.ReactNode,
|
|
15
13
|
onClick?: KeyboardEventHandler & MouseEventHandler,
|
|
16
14
|
className?: string,
|
|
17
|
-
variant?:
|
|
18
|
-
iconBefore?: React.
|
|
15
|
+
variant?: typeof STYLE_VARIANTS[keyof typeof STYLE_VARIANTS],
|
|
16
|
+
iconBefore?: React.ComponentType,
|
|
19
17
|
iconBeforeAlt?: string,
|
|
20
|
-
iconAfter?: React.
|
|
18
|
+
iconAfter?: React.ComponentType,
|
|
21
19
|
iconAfterAlt?: string,
|
|
22
20
|
onIconBeforeClick?: KeyboardEventHandler & MouseEventHandler,
|
|
23
21
|
onIconAfterClick?: KeyboardEventHandler & MouseEventHandler,
|
|
@@ -111,7 +109,7 @@ Chip.propTypes = {
|
|
|
111
109
|
*
|
|
112
110
|
* `import { Check } from '@openedx/paragon/icons';`
|
|
113
111
|
*/
|
|
114
|
-
iconBefore: PropTypes.
|
|
112
|
+
iconBefore: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
115
113
|
/** Specifies icon alt text. */
|
|
116
114
|
iconBeforeAlt: requiredWhen(PropTypes.string, ['iconBefore', 'onIconBeforeClick']),
|
|
117
115
|
/** A click handler for the `Chip` icon before. */
|
|
@@ -122,7 +120,7 @@ Chip.propTypes = {
|
|
|
122
120
|
*
|
|
123
121
|
* `import { Check } from '@openedx/paragon/icons';`
|
|
124
122
|
*/
|
|
125
|
-
iconAfter: PropTypes.
|
|
123
|
+
iconAfter: PropTypes.elementType as Requireable<React.ComponentType>,
|
|
126
124
|
/** Specifies icon alt text. */
|
|
127
125
|
iconAfterAlt: requiredWhen(PropTypes.string, ['iconAfter', 'onIconAfterClick']),
|
|
128
126
|
/** A click handler for the `Chip` icon after. */
|
package/src/Chip/mixins.scss
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
@mixin chip-outline(
|
|
2
2
|
$outline-color: var(--pgn-color-white),
|
|
3
3
|
$distance-to-border: 0,
|
|
4
|
-
$border-radius: 50%,
|
|
5
|
-
$border-width
|
|
4
|
+
$outline-border-radius: 50%,
|
|
5
|
+
$outline-border-width: .125rem
|
|
6
6
|
) {
|
|
7
7
|
&::before {
|
|
8
8
|
content: "";
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
right: $distance-to-border;
|
|
12
12
|
bottom: $distance-to-border;
|
|
13
13
|
left: $distance-to-border;
|
|
14
|
-
border: solid $border-width
|
|
15
|
-
border-radius:
|
|
14
|
+
border: solid $outline-border-width $outline-color;
|
|
15
|
+
border-radius: $outline-border-radius;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -4,9 +4,7 @@ import { useIntl } from 'react-intl';
|
|
|
4
4
|
import classNames from 'classnames';
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
import { OverflowScroll, OverflowScrollContext } from '../OverflowScroll';
|
|
7
|
-
// @ts-ignore
|
|
8
7
|
import IconButton from '../IconButton';
|
|
9
|
-
// @ts-ignore
|
|
10
8
|
import Icon from '../Icon';
|
|
11
9
|
// @ts-ignore
|
|
12
10
|
import { ArrowForward, ArrowBack } from '../../icons';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
.collapsible-trigger {
|
|
8
8
|
padding: var(--pgn-spacing-collapsible-card-spacer-y-base) var(--pgn-spacing-collapsible-card-spacer-x-base);
|
|
9
|
-
border-radius:
|
|
9
|
+
border-radius: var(--pgn-size-card-border-radius-inner);
|
|
10
10
|
border-bottom: var(--pgn-size-card-border-width) solid transparent;
|
|
11
11
|
transition: border-color 100ms ease 150ms;
|
|
12
12
|
cursor: pointer;
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
&[aria-expanded="true"] {
|
|
24
24
|
border-radius:
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
var(--pgn-size-card-border-radius-inner)
|
|
26
|
+
var(--pgn-size-card-border-radius-inner) 0 0;
|
|
27
27
|
border-color: var(--pgn-color-card-border-base);
|
|
28
28
|
transition: none;
|
|
29
29
|
text-align: start;
|
|
@@ -4,30 +4,34 @@ import userEvent from '@testing-library/user-event';
|
|
|
4
4
|
|
|
5
5
|
import Hyperlink from '.';
|
|
6
6
|
|
|
7
|
-
const content = 'content';
|
|
8
7
|
const destination = 'destination';
|
|
8
|
+
const content = 'content';
|
|
9
9
|
const onClick = jest.fn();
|
|
10
10
|
const props = {
|
|
11
|
-
content,
|
|
12
11
|
destination,
|
|
13
12
|
onClick,
|
|
14
13
|
};
|
|
15
14
|
const externalLinkAlternativeText = 'externalLinkAlternativeText';
|
|
16
15
|
const externalLinkTitle = 'externalLinkTitle';
|
|
17
16
|
const externalLinkProps = {
|
|
18
|
-
target: '_blank',
|
|
17
|
+
target: '_blank' as const,
|
|
19
18
|
externalLinkAlternativeText,
|
|
20
19
|
externalLinkTitle,
|
|
21
20
|
...props,
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
describe('correct rendering', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
onClick.mockClear();
|
|
26
|
+
});
|
|
27
|
+
|
|
25
28
|
it('renders Hyperlink', async () => {
|
|
26
|
-
const { getByRole } = render(<Hyperlink {...props}
|
|
29
|
+
const { getByRole } = render(<Hyperlink {...props}>{content}</Hyperlink>);
|
|
27
30
|
const wrapper = getByRole('link');
|
|
28
31
|
expect(wrapper).toBeInTheDocument();
|
|
29
32
|
|
|
30
33
|
expect(wrapper).toHaveClass('pgn__hyperlink');
|
|
34
|
+
expect(wrapper).toHaveClass('standalone-link');
|
|
31
35
|
expect(wrapper).toHaveTextContent(content);
|
|
32
36
|
expect(wrapper).toHaveAttribute('href', destination);
|
|
33
37
|
expect(wrapper).toHaveAttribute('target', '_self');
|
|
@@ -36,8 +40,17 @@ describe('correct rendering', () => {
|
|
|
36
40
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
37
41
|
});
|
|
38
42
|
|
|
43
|
+
it('renders an underlined Hyperlink', async () => {
|
|
44
|
+
const { getByRole } = render(<Hyperlink isInline {...props}>{content}</Hyperlink>);
|
|
45
|
+
const wrapper = getByRole('link');
|
|
46
|
+
expect(wrapper).toBeInTheDocument();
|
|
47
|
+
expect(wrapper).toHaveClass('pgn__hyperlink');
|
|
48
|
+
expect(wrapper).not.toHaveClass('standalone-link');
|
|
49
|
+
expect(wrapper).toHaveClass('inline-link');
|
|
50
|
+
});
|
|
51
|
+
|
|
39
52
|
it('renders external Hyperlink', () => {
|
|
40
|
-
const { getByRole, getByTestId } = render(<Hyperlink {...externalLinkProps}
|
|
53
|
+
const { getByRole, getByTestId } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
|
|
41
54
|
const wrapper = getByRole('link');
|
|
42
55
|
const icon = getByTestId('hyperlink-icon');
|
|
43
56
|
const iconSvg = icon.querySelector('svg');
|
|
@@ -53,18 +66,16 @@ describe('correct rendering', () => {
|
|
|
53
66
|
|
|
54
67
|
describe('security', () => {
|
|
55
68
|
it('prevents reverse tabnabbing for links with target="_blank"', () => {
|
|
56
|
-
const { getByRole } = render(<Hyperlink {...externalLinkProps}
|
|
69
|
+
const { getByRole } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
|
|
57
70
|
const wrapper = getByRole('link');
|
|
58
71
|
expect(wrapper).toHaveAttribute('rel', 'noopener noreferrer');
|
|
59
72
|
});
|
|
60
73
|
});
|
|
61
74
|
|
|
62
75
|
describe('event handlers are triggered correctly', () => {
|
|
63
|
-
let spy;
|
|
64
|
-
beforeEach(() => { spy = jest.fn(); });
|
|
65
|
-
|
|
66
76
|
it('should fire onClick', async () => {
|
|
67
|
-
const
|
|
77
|
+
const spy = jest.fn();
|
|
78
|
+
const { getByRole } = render(<Hyperlink {...props} onClick={spy}>{content}</Hyperlink>);
|
|
68
79
|
const wrapper = getByRole('link');
|
|
69
80
|
expect(spy).toHaveBeenCalledTimes(0);
|
|
70
81
|
await userEvent.click(wrapper);
|