mirror of https://github.com/openedx/paragon.git
feat!: implement i18n in Paragon components and in docs site (#1100)
Adds support for i18n translations in Paragon components, including the commands to push/pull translations from Transifex in an automated process. This change itself does not guarantee the components are truly translated yet as they may still need a translator and/or a reviewer to approve the translation in Transifex before a translation is pulled. To upgrade, ensure you are using at least `react-intl@5.25.0` in your application. BREAKING CHANGE: By adding i18n support to the Paragon design system, we are introducing a peer dependency on `react-intl@5.25.0` or greater. This may be a breaking change for some consumers, if your repository: * Uses v1 of `@edx/frontend-platform` * Uses older version of `react-intl` than v5.25.0 directly. * Does not use `react-intl`.
This commit is contained in:
parent
aecdf1f211
commit
53e0ac632b
|
|
@ -5,6 +5,7 @@ npm-debug.log
|
||||||
coverage
|
coverage
|
||||||
jest*
|
jest*
|
||||||
dist
|
dist
|
||||||
|
src/i18n/transifex_input.json
|
||||||
|
|
||||||
# gatsby files
|
# gatsby files
|
||||||
www/.cache/
|
www/.cache/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
|
||||||
|
[edx-platform.paragon]
|
||||||
|
file_filter = src/i18n/messages/<lang>.json
|
||||||
|
source_file = src/i18n/transifex_input.json
|
||||||
|
source_lang = en
|
||||||
|
type = STRUCTURED_JSON
|
||||||
43
Makefile
43
Makefile
|
|
@ -7,3 +7,46 @@ build:
|
||||||
rm -rf dist/__mocks__
|
rm -rf dist/__mocks__
|
||||||
rm -rf dist/setupTest.js
|
rm -rf dist/setupTest.js
|
||||||
node build-scss.js
|
node build-scss.js
|
||||||
|
|
||||||
|
transifex_langs = "ar,fr,es_419,zh_CN"
|
||||||
|
i18n = ./src/i18n
|
||||||
|
transifex_input = $(i18n)/transifex_input.json
|
||||||
|
|
||||||
|
NPM_TESTS=build i18n_extract lint test
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
|
||||||
|
|
||||||
|
.PHONY: test.npm.*
|
||||||
|
test.npm.%: validate-no-uncommitted-package-lock-changes
|
||||||
|
test -d node_modules || $(MAKE) requirements
|
||||||
|
npm run $(*)
|
||||||
|
|
||||||
|
.PHONY: requirements
|
||||||
|
requirements: ## install ci requirements
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
i18n.extract:
|
||||||
|
# Pulling display strings from .jsx files into .json files...
|
||||||
|
npm run-script i18n_extract
|
||||||
|
|
||||||
|
extract_translations: | requirements i18n.extract
|
||||||
|
|
||||||
|
# Despite the name, we actually need this target to detect changes in the incoming translated message files as well.
|
||||||
|
detect_changed_source_translations:
|
||||||
|
# Checking for changed translations...
|
||||||
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
|
# Pushes translations to Transifex. You must run make extract_translations first.
|
||||||
|
push_translations:
|
||||||
|
# Pushing strings to Transifex...
|
||||||
|
tx push -s
|
||||||
|
|
||||||
|
# Pulls translations from Transifex.
|
||||||
|
pull_translations:
|
||||||
|
tx pull -f --mode reviewed --language=$(transifex_langs)
|
||||||
|
|
||||||
|
# This target is used by Travis.
|
||||||
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
# Checking for package-lock.json changes...
|
||||||
|
git diff --exit-code package-lock.json
|
||||||
|
|
|
||||||
134
README.md
134
README.md
|
|
@ -167,6 +167,140 @@ JSX code blocks in the markdown file can be made interactive with the live attri
|
||||||
|
|
||||||
Visit the documentation at [http://localhost:8000](http://localhost:8000) and navigate to see your README.md powered page and workbench. Changes to the README.md file will auto refresh the page.
|
Visit the documentation at [http://localhost:8000](http://localhost:8000) and navigate to see your README.md powered page and workbench. Changes to the README.md file will auto refresh the page.
|
||||||
|
|
||||||
|
### Internationalization
|
||||||
|
|
||||||
|
Paragon supports internationalization for its components out of the box with the support of [react-intl](https://formatjs.io/docs/react-intl/). You can view translated strings for each component on the docs site after switching language on a settings tab.
|
||||||
|
|
||||||
|
#### For consumers
|
||||||
|
|
||||||
|
Since we are using ``react-intl`` that means that your whole app needs to be wrapped in its context, e.g.
|
||||||
|
```javascript
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { messages as paragonMessages } from '@edx/paragon';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<IntlProvider locale={usersLocale} messages={paragonMessages[usersLocale]}>
|
||||||
|
<App />
|
||||||
|
</IntlProvider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if you are using ``@edx/frontend-platform``'s ``AppProvider`` component you don't need a separate context,
|
||||||
|
you would only need to add Paragon's messages like this
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { APP_READY, subscribe, initialize } from '@edx/frontend-platform';
|
||||||
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
import { messages as paragonMessages } from '@edx/paragon';
|
||||||
|
import App from './App';
|
||||||
|
// this is your app's i18n messages
|
||||||
|
import appMessages from './i18n';
|
||||||
|
|
||||||
|
subscribe(APP_READY, () => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppProvider>
|
||||||
|
<App />
|
||||||
|
</AppProvider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
initialize({
|
||||||
|
// this will add your app's messages as well as Paragon's messages to your app
|
||||||
|
messages: [
|
||||||
|
appMessages,
|
||||||
|
paragonMessages,
|
||||||
|
],
|
||||||
|
// here you will typically provide other configurations for you app
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For developers
|
||||||
|
|
||||||
|
When developing a new component you should generally follow three rules:
|
||||||
|
1. The component should not have **any** hardcoded strings as it would be impossible for consumers to translate it
|
||||||
|
2. Internationalize all default values of props that expect strings, i.e.
|
||||||
|
|
||||||
|
- For places where you need to display a string, and it's okay if it is a React element use ``FormattedMessage``, e.g. (see [Alert](src/Alert/index.jsx) component for a full example)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.Alert.closeLabel"
|
||||||
|
defaultMessage="Dismiss"
|
||||||
|
description="Label of a close button on Alert component"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- For places where the display string has to be a plain JavaScript string use ``formatMessage``, this would require access to ``intl`` object from ``react-intl``, e.g.
|
||||||
|
|
||||||
|
- For class components use ``injectIntl`` HOC
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
class MyClassComponent extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { altText, intl } = this.props;
|
||||||
|
const intlAltText = altText || intl.formatMessage({
|
||||||
|
id: 'pgn.MyComponent.altText',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
description: 'Close label for Toast component',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
alt={intlCloseLabel}
|
||||||
|
onClick={() => {}}
|
||||||
|
variant="primary"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectIntl(MyClassComponent);
|
||||||
|
```
|
||||||
|
|
||||||
|
- For functional components use ``useIntl`` hook
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const MyFunctionComponent = ({ altText }) => {
|
||||||
|
const intls = useIntl();
|
||||||
|
const intlAltText = altText || intl.formatMessage({
|
||||||
|
id: 'pgn.MyComponent.altText',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
description: 'Close label for Toast component',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
alt={intlCloseLabel}
|
||||||
|
onClick={() => {}}
|
||||||
|
variant="primary"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default MyFunctionComponent;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes on the format above**:
|
||||||
|
- `id` is required and must be a dot-separated string of the format `pgn.<componentName>.<subcomponentName>.<propName>`
|
||||||
|
- The `defaultMessage` is required, and should be the English display string.
|
||||||
|
- The `description` is optional, but highly recommended, this text gives context to translators about the string.
|
||||||
|
|
||||||
|
|
||||||
|
3. If your component expects a string as a prop, allow the prop to also be an element since consumers may want to also pass instance of their own translated string, for example you might define a string prop like this:
|
||||||
|
```javascript
|
||||||
|
MyComponent.PropTypes = {
|
||||||
|
myProp: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Developing locally against MFE
|
### Developing locally against MFE
|
||||||
|
|
||||||
If you want to test the changes with local MFE setup, you need to create a "module.config.js" file in your MFE's directory containing local module overrides. After that the webpack build for your application will automatically pick your local version of Paragon and use it. The example of module.config.js file looks like this (for more details about module.config.js, refer to the [frontend-build documentation](https://github.com/edx/frontend-build#local-module-configuration-for-webpack).):
|
If you want to test the changes with local MFE setup, you need to create a "module.config.js" file in your MFE's directory containing local module overrides. After that the webpack build for your application will automatically pick your local version of Paragon and use it. The example of module.config.js file looks like this (for more details about module.config.js, refer to the [frontend-build documentation](https://github.com/edx/frontend-build#local-module-configuration-for-webpack).):
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,10 @@ Consequences
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
<App />
|
<App />
|
||||||
</IntlProvider>,
|
</AppProvider>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
|
|
||||||
initialize({
|
initialize({
|
||||||
// this will add your app's messages as well as Paragon's messages to your app
|
// this will add your app's messages as well as Paragon's messages to your app
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,7 +18,7 @@
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^16.14.0",
|
||||||
"@edx/brand-openedx": "^1.1.0",
|
"@edx/brand-openedx": "^1.1.0",
|
||||||
"@edx/frontend-platform": "^1.15.6",
|
"@edx/frontend-platform": "^2.0.0",
|
||||||
"core-js": "^3.22.2",
|
"core-js": "^3.22.2",
|
||||||
"regenerator-runtime": "^0.13.9"
|
"regenerator-runtime": "^0.13.9"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,7 +39,8 @@
|
||||||
"test:watch": "npm run test -- --watch",
|
"test:watch": "npm run test -- --watch",
|
||||||
"generate-component": "cd component-generator && npm start",
|
"generate-component": "cd component-generator && npm start",
|
||||||
"generate-component-lint": "cd component-generator && npm run lint",
|
"generate-component-lint": "cd component-generator && npm run lint",
|
||||||
"generate-changelog": "node generate-changelog.js"
|
"generate-changelog": "node generate-changelog.js",
|
||||||
|
"i18n_extract": "formatjs extract 'src/**/*.jsx' --out-file ./src/i18n/transifex_input.json --format transifex"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
|
|
@ -64,7 +65,8 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.6 || ^17.0.0",
|
"react": "^16.8.6 || ^17.0.0",
|
||||||
"react-dom": "^16.8.6 || ^17.0.0"
|
"react-dom": "^16.8.6 || ^17.0.0",
|
||||||
|
"react-intl": "^5.25.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.16.8",
|
"@babel/cli": "^7.16.8",
|
||||||
|
|
@ -74,6 +76,7 @@
|
||||||
"@babel/preset-env": "^7.16.8",
|
"@babel/preset-env": "^7.16.8",
|
||||||
"@babel/preset-react": "^7.16.7",
|
"@babel/preset-react": "^7.16.7",
|
||||||
"@edx/eslint-config": "^3.0.0",
|
"@edx/eslint-config": "^3.0.0",
|
||||||
|
"@formatjs/cli": "^4.8.4",
|
||||||
"@semantic-release/changelog": "^5.0.1",
|
"@semantic-release/changelog": "^5.0.1",
|
||||||
"@semantic-release/git": "^9.0.1",
|
"@semantic-release/git": "^9.0.1",
|
||||||
"@testing-library/jest-dom": "^5.16.3",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
import renderer, { act } from 'react-test-renderer';
|
import renderer, { act } from 'react-test-renderer';
|
||||||
import { Context as ResponsiveContext } from 'react-responsive';
|
import { Context as ResponsiveContext } from 'react-responsive';
|
||||||
import breakpoints from '../utils/breakpoints';
|
import breakpoints from '../utils/breakpoints';
|
||||||
|
|
@ -7,56 +8,65 @@ import Button from '../Button';
|
||||||
import Alert from './index';
|
import Alert from './index';
|
||||||
import { Info } from '../../icons';
|
import { Info } from '../../icons';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const AlertWrapper = ({ children, ...props }) => (
|
||||||
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<Alert {...props}>
|
||||||
|
{children}
|
||||||
|
</Alert>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<Alert />', () => {
|
describe('<Alert />', () => {
|
||||||
it('renders without any props', () => {
|
it('renders without any props', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert>Alert</Alert>
|
<AlertWrapper>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('renders with icon prop', () => {
|
it('renders with icon prop', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert icon={Info}>Alert</Alert>
|
<AlertWrapper icon={Info}>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('renders with dismissible prop', () => {
|
it('renders with dismissible prop', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert dismissible>Alert</Alert>
|
<AlertWrapper dismissible>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('handles dismissible onClose', () => {
|
it('handles dismissible onClose', () => {
|
||||||
const mockOnClose = jest.fn();
|
const mockOnClose = jest.fn();
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Alert onClose={mockOnClose} dismissible>Alert</Alert>
|
<AlertWrapper onClose={mockOnClose} dismissible>Alert</AlertWrapper>
|
||||||
));
|
));
|
||||||
wrapper.find('.btn').simulate('click');
|
wrapper.find('.btn').simulate('click');
|
||||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
it('renders with button prop', () => {
|
it('renders with button prop', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert actions={[<Button>Hello</Button>]}>Alert</Alert>
|
<AlertWrapper actions={[<Button>Hello</Button>]}>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('handles button onClick', () => {
|
it('handles button onClick', () => {
|
||||||
const mockOnClick = jest.fn();
|
const mockOnClick = jest.fn();
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Alert actions={[<Button onClick={mockOnClick}>Hello</Button>]}>Alert</Alert>
|
<AlertWrapper actions={[<Button onClick={mockOnClick}>Hello</Button>]}>Alert</AlertWrapper>
|
||||||
));
|
));
|
||||||
wrapper.find('.btn').simulate('click');
|
wrapper.find('.btn').simulate('click');
|
||||||
expect(mockOnClick).toHaveBeenCalledTimes(1);
|
expect(mockOnClick).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
it('renders with button and dismissible props', () => {
|
it('renders with button and dismissible props', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert actions={[<Button>Hello</Button>]} dismissible>Alert</Alert>
|
<AlertWrapper actions={[<Button>Hello</Button>]} dismissible>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('renders with stacked prop', () => {
|
it('renders with stacked prop', () => {
|
||||||
const tree = renderer.create((
|
const tree = renderer.create((
|
||||||
<Alert stacked actions={[<Button>Hello</Button>]} dismissible>Alert</Alert>
|
<AlertWrapper stacked actions={[<Button>Hello</Button>]} dismissible>Alert</AlertWrapper>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
@ -65,7 +75,7 @@ describe('<Alert />', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
tree = renderer.create((
|
tree = renderer.create((
|
||||||
<ResponsiveContext.Provider value={{ width: breakpoints.extraSmall.maxWidth }}>
|
<ResponsiveContext.Provider value={{ width: breakpoints.extraSmall.maxWidth }}>
|
||||||
<Alert dismissible>Alert</Alert>
|
<AlertWrapper dismissible>Alert</AlertWrapper>
|
||||||
</ResponsiveContext.Provider>
|
</ResponsiveContext.Provider>
|
||||||
)).toJSON();
|
)).toJSON();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import BaseAlert from 'react-bootstrap/Alert';
|
import BaseAlert from 'react-bootstrap/Alert';
|
||||||
import divWithClassName from 'react-bootstrap/divWithClassName';
|
import divWithClassName from 'react-bootstrap/divWithClassName';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useMediaQuery } from 'react-responsive';
|
import { useMediaQuery } from 'react-responsive';
|
||||||
import { Icon } from '..';
|
import { Icon } from '..';
|
||||||
import breakpoints from '../utils/breakpoints';
|
import breakpoints from '../utils/breakpoints';
|
||||||
|
|
@ -66,7 +67,13 @@ const Alert = React.forwardRef(({
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
{closeLabel}
|
{closeLabel || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.Alert.closeLabel"
|
||||||
|
defaultMessage="Dismiss"
|
||||||
|
description="Label of a close button on Alert component"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{actions && actions.map(cloneActionElement)}
|
{actions && actions.map(cloneActionElement)}
|
||||||
|
|
@ -122,8 +129,8 @@ Alert.propTypes = {
|
||||||
actions: PropTypes.arrayOf(PropTypes.element),
|
actions: PropTypes.arrayOf(PropTypes.element),
|
||||||
/** Position of the dismiss and call-to-action buttons. Defaults to ``false``. */
|
/** Position of the dismiss and call-to-action buttons. Defaults to ``false``. */
|
||||||
stacked: PropTypes.bool,
|
stacked: PropTypes.bool,
|
||||||
/** Sets the text for alert close button. */
|
/** Sets the text for alert close button, defaults to 'Dismiss'. */
|
||||||
closeLabel: PropTypes.string,
|
closeLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
Alert.defaultProps = {
|
Alert.defaultProps = {
|
||||||
|
|
@ -133,7 +140,7 @@ Alert.defaultProps = {
|
||||||
actions: undefined,
|
actions: undefined,
|
||||||
dismissible: false,
|
dismissible: false,
|
||||||
onClose: () => {},
|
onClose: () => {},
|
||||||
closeLabel: ALERT_CLOSE_LABEL_TEXT,
|
closeLabel: undefined,
|
||||||
show: true,
|
show: true,
|
||||||
stacked: false,
|
stacked: false,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import SidebarFilters from './SidebarFilters';
|
||||||
import DataTableContext from './DataTableContext';
|
import DataTableContext from './DataTableContext';
|
||||||
|
|
||||||
const DataTableLayout = ({
|
const DataTableLayout = ({
|
||||||
|
filtersTitle,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -15,7 +16,7 @@ const DataTableLayout = ({
|
||||||
<div className={classNames('pgn__data-table-layout-wrapper', className)}>
|
<div className={classNames('pgn__data-table-layout-wrapper', className)}>
|
||||||
{(showFiltersInSidebar && setFilter) && (
|
{(showFiltersInSidebar && setFilter) && (
|
||||||
<div className="pgn__data-table-layout-sidebar">
|
<div className="pgn__data-table-layout-sidebar">
|
||||||
<SidebarFilters />
|
<SidebarFilters title={filtersTitle} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="pgn__data-table-layout-main">
|
<div className="pgn__data-table-layout-main">
|
||||||
|
|
@ -27,11 +28,13 @@ const DataTableLayout = ({
|
||||||
|
|
||||||
DataTableLayout.defaultProps = {
|
DataTableLayout.defaultProps = {
|
||||||
className: null,
|
className: null,
|
||||||
|
filtersTitle: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
DataTableLayout.propTypes = {
|
DataTableLayout.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
|
filtersTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataTableLayout;
|
export default DataTableLayout;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Button } from '..';
|
import { Button } from '..';
|
||||||
|
|
||||||
const ExpandAll = ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => (
|
const ExpandAll = ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => (
|
||||||
<span {...getToggleAllRowsExpandedProps()}>
|
<span {...getToggleAllRowsExpandedProps()}>
|
||||||
{isAllRowsExpanded
|
{isAllRowsExpanded ? (
|
||||||
? <Button variant="link" size="inline">Collapse all</Button>
|
<Button variant="link" size="inline">
|
||||||
: <Button variant="link" size="inline">Expand all</Button>}
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.ExpandAll.collapseAllLabel"
|
||||||
|
defaultMessage="Collapse all"
|
||||||
|
description="Label of an action button that collapses all expandable rows of DataTable."
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button variant="link" size="inline">
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.ExpandAll.expandAllLabel"
|
||||||
|
defaultMessage="Expand all"
|
||||||
|
description="Label of an action button that expands all expandable rows of DataTable."
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Button } from '..';
|
import { Button } from '..';
|
||||||
import DataTableContext from './DataTableContext';
|
import DataTableContext from './DataTableContext';
|
||||||
|
|
||||||
|
|
@ -23,7 +24,15 @@ const FilterStatus = ({
|
||||||
size={size}
|
size={size}
|
||||||
onClick={() => setAllFilters([])}
|
onClick={() => setAllFilters([])}
|
||||||
>
|
>
|
||||||
{clearFiltersText}
|
{clearFiltersText === undefined
|
||||||
|
? (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.FilterStatus.clearFiltersText"
|
||||||
|
defaultMessage="Clear filters"
|
||||||
|
description="A text that appears on the `Clear filters` button"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: clearFiltersText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -38,8 +47,8 @@ FilterStatus.defaultProps = {
|
||||||
variant: 'link',
|
variant: 'link',
|
||||||
/** The size of the `FilterStatus`. */
|
/** The size of the `FilterStatus`. */
|
||||||
size: 'inline',
|
size: 'inline',
|
||||||
/** A text that appears on the `Clear filters` button. */
|
/** A text that appears on the `Clear filters` button, defaults to 'Clear filters'. */
|
||||||
clearFiltersText: 'Clear Filters',
|
clearFiltersText: undefined,
|
||||||
/** Whether to display applied filters. */
|
/** Whether to display applied filters. */
|
||||||
showFilteredFields: true,
|
showFilteredFields: true,
|
||||||
};
|
};
|
||||||
|
|
@ -49,7 +58,7 @@ FilterStatus.propTypes = {
|
||||||
buttonClassName: PropTypes.string,
|
buttonClassName: PropTypes.string,
|
||||||
variant: PropTypes.string,
|
variant: PropTypes.string,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string,
|
||||||
clearFiltersText: PropTypes.string,
|
clearFiltersText: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
||||||
showFilteredFields: PropTypes.bool,
|
showFilteredFields: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,39 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import DataTableContext from './DataTableContext';
|
import DataTableContext from './DataTableContext';
|
||||||
|
|
||||||
const RowStatus = ({ className }) => {
|
const RowStatus = ({ className, statusText }) => {
|
||||||
const { page, rows, itemCount } = useContext(DataTableContext);
|
const { page, rows, itemCount } = useContext(DataTableContext);
|
||||||
const pageSize = page?.length || rows?.length;
|
const pageSize = page?.length || rows?.length;
|
||||||
|
|
||||||
if (!pageSize) {
|
if (!pageSize) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (<div className={className}>Showing {pageSize} of {itemCount}.</div>);
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{statusText || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.RowStatus.statusText"
|
||||||
|
defaultMessage="Showing {pageSize} of {itemCount}."
|
||||||
|
description="A text describing how many rows is shown in the table"
|
||||||
|
values={{ itemCount, pageSize }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
RowStatus.propTypes = {
|
RowStatus.propTypes = {
|
||||||
/** Specifies class name to append to the base element. */
|
/** Specifies class name to append to the base element. */
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
/** A text describing how many rows is shown in the table. */
|
||||||
|
statusText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
RowStatus.defaultProps = {
|
RowStatus.defaultProps = {
|
||||||
className: undefined,
|
className: undefined,
|
||||||
|
statusText: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RowStatus;
|
export default RowStatus;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import DataTableContext from './DataTableContext';
|
import DataTableContext from './DataTableContext';
|
||||||
import FilterStatus from './FilterStatus';
|
import FilterStatus from './FilterStatus';
|
||||||
|
|
||||||
const SidebarFilters = () => {
|
const SidebarFilters = ({ title }) => {
|
||||||
const { state, columns } = useContext(DataTableContext);
|
const { state, columns } = useContext(DataTableContext);
|
||||||
const availableFilters = useMemo(() => columns.filter((column) => column.canFilter), [columns]);
|
const availableFilters = useMemo(() => columns.filter((column) => column.canFilter), [columns]);
|
||||||
const filtersApplied = state?.filters && state.filters.length > 0;
|
const filtersApplied = state?.filters && state.filters.length > 0;
|
||||||
return (
|
return (
|
||||||
<div className="pgn__data-table-side-filters">
|
<div className="pgn__data-table-side-filters">
|
||||||
<h3 className="pgn__data-table-side-filters-title">Filters</h3>
|
<h3 className="pgn__data-table-side-filters-title">
|
||||||
|
{title || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.SidebarFilters.title"
|
||||||
|
defaultMessage="Filters"
|
||||||
|
description="Title for the sidebar filters component"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
<hr />
|
<hr />
|
||||||
{availableFilters.map(column => (
|
{availableFilters.map(column => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -32,4 +42,13 @@ const SidebarFilters = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SidebarFilters.propTypes = {
|
||||||
|
/** Specifies the title to show near the filters, default to 'Filters'. */
|
||||||
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
|
};
|
||||||
|
|
||||||
|
SidebarFilters.defaultProps = {
|
||||||
|
title: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default SidebarFilters;
|
export default SidebarFilters;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Button } from '../..';
|
import { Button } from '../..';
|
||||||
import DataTableContext from '../DataTableContext';
|
import DataTableContext from '../DataTableContext';
|
||||||
|
|
@ -14,12 +15,31 @@ const BaseSelectionStatus = ({
|
||||||
numSelectedRows,
|
numSelectedRows,
|
||||||
onSelectAll,
|
onSelectAll,
|
||||||
onClear,
|
onClear,
|
||||||
|
selectAllText,
|
||||||
|
allSelectedText,
|
||||||
|
selectedText,
|
||||||
}) => {
|
}) => {
|
||||||
const { itemCount } = useContext(DataTableContext);
|
const { itemCount } = useContext(DataTableContext);
|
||||||
const isAllRowsSelected = numSelectedRows === itemCount;
|
const isAllRowsSelected = numSelectedRows === itemCount;
|
||||||
|
const intlAllSelectedText = allSelectedText || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.BaseSelectionStatus.allSelectedText"
|
||||||
|
defaultMessage="All {numSelectedRows} selected"
|
||||||
|
description="Text for all selected label"
|
||||||
|
values={{ numSelectedRows }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const intlSelectedText = selectedText || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.BaseSelectionStatus.selectedText"
|
||||||
|
defaultMessage="{numSelectedRows} selected"
|
||||||
|
description="Text for selected label"
|
||||||
|
values={{ numSelectedRows }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<span>{isAllRowsSelected && 'All '}{numSelectedRows} selected </span>
|
<span>{isAllRowsSelected ? intlAllSelectedText : intlSelectedText}</span>
|
||||||
{!isAllRowsSelected && (
|
{!isAllRowsSelected && (
|
||||||
<Button
|
<Button
|
||||||
className={SELECT_ALL_TEST_ID}
|
className={SELECT_ALL_TEST_ID}
|
||||||
|
|
@ -27,7 +47,14 @@ const BaseSelectionStatus = ({
|
||||||
size="inline"
|
size="inline"
|
||||||
onClick={onSelectAll}
|
onClick={onSelectAll}
|
||||||
>
|
>
|
||||||
Select all {itemCount}
|
{selectAllText || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.BaseSelectionStatus.selectAllText"
|
||||||
|
defaultMessage="Select all {itemCount}"
|
||||||
|
description="A label for select all button."
|
||||||
|
values={{ itemCount }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{numSelectedRows > 0 && (
|
{numSelectedRows > 0 && (
|
||||||
|
|
@ -37,7 +64,13 @@ const BaseSelectionStatus = ({
|
||||||
size="inline"
|
size="inline"
|
||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
>
|
>
|
||||||
{clearSelectionText}
|
{clearSelectionText || (
|
||||||
|
<FormattedMessage
|
||||||
|
id="pgn.DataTable.BaseSelectionStatus.clearSelectionText"
|
||||||
|
defaultMessage="Clear selection"
|
||||||
|
description="A label of clear all selection button."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -46,14 +79,29 @@ const BaseSelectionStatus = ({
|
||||||
|
|
||||||
BaseSelectionStatus.defaultProps = {
|
BaseSelectionStatus.defaultProps = {
|
||||||
className: undefined,
|
className: undefined,
|
||||||
|
selectAllText: undefined,
|
||||||
|
allSelectedText: undefined,
|
||||||
|
selectedText: undefined,
|
||||||
|
clearSelectionText: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
BaseSelectionStatus.propTypes = {
|
BaseSelectionStatus.propTypes = {
|
||||||
|
/** A class name to append to the base element */
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
clearSelectionText: PropTypes.string.isRequired,
|
/** A text that appears on the `Clear selection` button, defaults to 'Clear selection' */
|
||||||
|
clearSelectionText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
|
/** Count of selected rows in the table. */
|
||||||
numSelectedRows: PropTypes.number.isRequired,
|
numSelectedRows: PropTypes.number.isRequired,
|
||||||
|
/** A handler for 'Select all' button. */
|
||||||
onSelectAll: PropTypes.func.isRequired,
|
onSelectAll: PropTypes.func.isRequired,
|
||||||
|
/** A handler for 'Clear selection' button. */
|
||||||
onClear: PropTypes.func.isRequired,
|
onClear: PropTypes.func.isRequired,
|
||||||
|
/** A text that appears on the `Select all` button, defaults to 'Select All {itemCount}' */
|
||||||
|
selectAllText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
|
/** A text that appears when all items have been selected, defaults to 'All {numSelectedRows} selected' */
|
||||||
|
allSelectedText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
|
/** A text that appears when some items have been selected, defaults to '{numSelectedRows} selected' */
|
||||||
|
selectedText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BaseSelectionStatus;
|
export default BaseSelectionStatus;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
getUnselectedPageRows,
|
getUnselectedPageRows,
|
||||||
getRowIds,
|
getRowIds,
|
||||||
} from './data/helpers';
|
} from './data/helpers';
|
||||||
import { CLEAR_SELECTION_TEXT } from './data/constants';
|
|
||||||
|
|
||||||
const ControlledSelectionStatus = ({ className, clearSelectionText }) => {
|
const ControlledSelectionStatus = ({ className, clearSelectionText }) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -49,12 +48,14 @@ const ControlledSelectionStatus = ({ className, clearSelectionText }) => {
|
||||||
|
|
||||||
ControlledSelectionStatus.defaultProps = {
|
ControlledSelectionStatus.defaultProps = {
|
||||||
className: undefined,
|
className: undefined,
|
||||||
clearSelectionText: CLEAR_SELECTION_TEXT,
|
clearSelectionText: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlledSelectionStatus.propTypes = {
|
ControlledSelectionStatus.propTypes = {
|
||||||
|
/** A class name to append to the base element */
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
clearSelectionText: PropTypes.string,
|
/** A text that appears on the `Clear selection` button, defaults to 'Clear Selection' */
|
||||||
|
clearSelectionText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ControlledSelectionStatus;
|
export default ControlledSelectionStatus;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import DataTableContext from '../DataTableContext';
|
import DataTableContext from '../DataTableContext';
|
||||||
import BaseSelectionStatus from './BaseSelectionStatus';
|
import BaseSelectionStatus from './BaseSelectionStatus';
|
||||||
import { CLEAR_SELECTION_TEXT } from './data/constants';
|
|
||||||
|
|
||||||
const SelectionStatus = ({ className, clearSelectionText }) => {
|
const SelectionStatus = ({ className, clearSelectionText }) => {
|
||||||
const { toggleAllRowsSelected, selectedFlatRows } = useContext(DataTableContext);
|
const { toggleAllRowsSelected, selectedFlatRows } = useContext(DataTableContext);
|
||||||
|
|
@ -21,13 +20,13 @@ const SelectionStatus = ({ className, clearSelectionText }) => {
|
||||||
SelectionStatus.propTypes = {
|
SelectionStatus.propTypes = {
|
||||||
/** A class name to append to the base element */
|
/** A class name to append to the base element */
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
/** A text that appears on the `Clear selection` button */
|
/** A text that appears on the `Clear selection` button, defaults to 'Clear Selection' */
|
||||||
clearSelectionText: PropTypes.string,
|
clearSelectionText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectionStatus.defaultProps = {
|
SelectionStatus.defaultProps = {
|
||||||
className: undefined,
|
className: undefined,
|
||||||
clearSelectionText: CLEAR_SELECTION_TEXT,
|
clearSelectionText: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectionStatus;
|
export default SelectionStatus;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import ControlledSelectionStatus from '../ControlledSelectionStatus';
|
import ControlledSelectionStatus from '../ControlledSelectionStatus';
|
||||||
import { clearSelectionAction, setSelectAllRowsAllPagesAction, setSelectedRowsAction } from '../data/actions';
|
import { clearSelectionAction, setSelectAllRowsAllPagesAction, setSelectedRowsAction } from '../data/actions';
|
||||||
|
|
@ -24,7 +25,11 @@ const instance = {
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const ControlledSelectionStatusWrapper = ({ value, props = {} }) => (
|
const ControlledSelectionStatusWrapper = ({ value, props = {} }) => (
|
||||||
<DataTableContext.Provider value={value}><ControlledSelectionStatus {...props} /></DataTableContext.Provider>
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTableContext.Provider value={value}>
|
||||||
|
<ControlledSelectionStatus {...props} />
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<ControlledSelectionStatus />', () => {
|
describe('<ControlledSelectionStatus />', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import SelectionStatus from '../SelectionStatus';
|
import SelectionStatus from '../SelectionStatus';
|
||||||
import DataTableContext from '../../DataTableContext';
|
import DataTableContext from '../../DataTableContext';
|
||||||
|
|
@ -17,7 +18,11 @@ const instance = {
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const SelectionStatusWrapper = ({ value, props = {} }) => (
|
const SelectionStatusWrapper = ({ value, props = {} }) => (
|
||||||
<DataTableContext.Provider value={value}><SelectionStatus {...props} /></DataTableContext.Provider>
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTableContext.Provider value={value}>
|
||||||
|
<SelectionStatus {...props} />
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<SelectionStatus />', () => {
|
describe('<SelectionStatus />', () => {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React, { useContext } from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import * as reactTable from 'react-table';
|
import * as reactTable from 'react-table';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import DataTable from '..';
|
import DataTable from '..';
|
||||||
import TableControlBar from '../TableControlBar';
|
import TableControlBar from '../TableControlBar';
|
||||||
|
|
@ -97,43 +98,52 @@ const DataTableContextProviderChild = ({ children }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const DataTableWrapper = ({ children, ...tableProps }) => (
|
||||||
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTable {...tableProps}>
|
||||||
|
{children}
|
||||||
|
</DataTable>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<DataTable />', () => {
|
describe('<DataTable />', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
it('displays the empty table component if empty', () => {
|
it('displays the empty table component if empty', () => {
|
||||||
const wrapper = mount(<DataTable {...props} data={[]} />);
|
const wrapper = mount(<DataTableWrapper {...props} data={[]} />);
|
||||||
expect(wrapper.find(EmptyTable).length).toEqual(1);
|
expect(wrapper.find(EmptyTable).length).toEqual(1);
|
||||||
});
|
});
|
||||||
it('accepts an empty table component', () => {
|
it('accepts an empty table component', () => {
|
||||||
const wrapper = mount(<DataTable {...props} data={[]} EmptyTableComponent={EmptyTest} />);
|
const wrapper = mount(<DataTableWrapper {...props} data={[]} EmptyTableComponent={EmptyTest} />);
|
||||||
expect(wrapper.find(EmptyTable).length).toEqual(0);
|
expect(wrapper.find(EmptyTable).length).toEqual(0);
|
||||||
expect(wrapper.find(EmptyTest).length).toEqual(1);
|
expect(wrapper.find(EmptyTest).length).toEqual(1);
|
||||||
});
|
});
|
||||||
it('displays a control bar', () => {
|
it('displays a control bar', () => {
|
||||||
const wrapper = mount(<DataTable {...props} />);
|
const wrapper = mount(<DataTableWrapper {...props} />);
|
||||||
const controlBar = wrapper.find(TableControlBar);
|
const controlBar = wrapper.find(TableControlBar);
|
||||||
expect(controlBar.length).toEqual(1);
|
expect(controlBar.length).toEqual(1);
|
||||||
expect(controlBar.text()).toEqual('Showing 7 of 7.');
|
expect(controlBar.text()).toEqual('Showing 7 of 7.');
|
||||||
});
|
});
|
||||||
it('displays a table', () => {
|
it('displays a table', () => {
|
||||||
const wrapper = mount(<DataTable {...props} />);
|
const wrapper = mount(<DataTableWrapper {...props} />);
|
||||||
const table = wrapper.find(Table);
|
const table = wrapper.find(Table);
|
||||||
expect(table.length).toEqual(1);
|
expect(table.length).toEqual(1);
|
||||||
expect(table.find('th').length).toEqual(3);
|
expect(table.find('th').length).toEqual(3);
|
||||||
expect(table.find('tr').length).toEqual(8);
|
expect(table.find('tr').length).toEqual(8);
|
||||||
});
|
});
|
||||||
it('displays a table footer', () => {
|
it('displays a table footer', () => {
|
||||||
const wrapper = mount(<DataTable {...props} />);
|
const wrapper = mount(<DataTableWrapper {...props} />);
|
||||||
expect(wrapper.find(TableFooter).length).toEqual(1);
|
expect(wrapper.find(TableFooter).length).toEqual(1);
|
||||||
});
|
});
|
||||||
it('adds a column when table is selectable', () => {
|
it('adds a column when table is selectable', () => {
|
||||||
const wrapper = mount(<DataTable {...props} isSelectable />);
|
const wrapper = mount(<DataTableWrapper {...props} isSelectable />);
|
||||||
const tableHeaders = wrapper.find(Table).find('th');
|
const tableHeaders = wrapper.find(Table).find('th');
|
||||||
expect(tableHeaders.length).toEqual(props.columns.length + 1);
|
expect(tableHeaders.length).toEqual(props.columns.length + 1);
|
||||||
});
|
});
|
||||||
it('adds additional columns', () => {
|
it('adds additional columns', () => {
|
||||||
const wrapper = mount(<DataTable {...props} additionalColumns={additionalColumns} />);
|
const wrapper = mount(<DataTableWrapper {...props} additionalColumns={additionalColumns} />);
|
||||||
const tableHeaders = wrapper.find(Table).find('th');
|
const tableHeaders = wrapper.find(Table).find('th');
|
||||||
expect(tableHeaders.length).toEqual(props.columns.length + additionalColumns.length);
|
expect(tableHeaders.length).toEqual(props.columns.length + additionalColumns.length);
|
||||||
expect(wrapper.text()).toContain(additionalColumns[0].Header);
|
expect(wrapper.text()).toContain(additionalColumns[0].Header);
|
||||||
|
|
@ -141,7 +151,7 @@ describe('<DataTable />', () => {
|
||||||
});
|
});
|
||||||
test('calls useTable with the data and columns', () => {
|
test('calls useTable with the data and columns', () => {
|
||||||
const spy = jest.spyOn(reactTable, 'useTable');
|
const spy = jest.spyOn(reactTable, 'useTable');
|
||||||
mount(<DataTable {...props} />);
|
mount(<DataTableWrapper {...props} />);
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
expect(spy.mock.calls[0][0].columns).toEqual(props.columns);
|
expect(spy.mock.calls[0][0].columns).toEqual(props.columns);
|
||||||
expect(spy.mock.calls[0][0].data).toEqual(props.data);
|
expect(spy.mock.calls[0][0].data).toEqual(props.data);
|
||||||
|
|
@ -157,7 +167,7 @@ describe('<DataTable />', () => {
|
||||||
[{ manualSortBy: true, manualFilters: true, manualPagination: true, pageCount: 1 }, { manualFilters: true, manualPagination: true, manualSortBy: true }],
|
[{ manualSortBy: true, manualFilters: true, manualPagination: true, pageCount: 1 }, { manualFilters: true, manualPagination: true, manualSortBy: true }],
|
||||||
])('calls useTable with the correct manual settings %#', (additionalProps, expected) => {
|
])('calls useTable with the correct manual settings %#', (additionalProps, expected) => {
|
||||||
const spy = jest.spyOn(reactTable, 'useTable');
|
const spy = jest.spyOn(reactTable, 'useTable');
|
||||||
mount(<DataTable {...props} {...additionalProps} />);
|
mount(<DataTableWrapper {...props} {...additionalProps} />);
|
||||||
expect(spy.mock.calls[0][0].manualFilters).toEqual(expected.manualFilters);
|
expect(spy.mock.calls[0][0].manualFilters).toEqual(expected.manualFilters);
|
||||||
expect(spy.mock.calls[0][0].manualPagination).toEqual(expected.manualPagination);
|
expect(spy.mock.calls[0][0].manualPagination).toEqual(expected.manualPagination);
|
||||||
expect(spy.mock.calls[0][0].manualSortBy).toEqual(expected.manualSortBy);
|
expect(spy.mock.calls[0][0].manualSortBy).toEqual(expected.manualSortBy);
|
||||||
|
|
@ -165,11 +175,11 @@ describe('<DataTable />', () => {
|
||||||
it('passes the initial state to useTable', () => {
|
it('passes the initial state to useTable', () => {
|
||||||
const spy = jest.spyOn(reactTable, 'useTable');
|
const spy = jest.spyOn(reactTable, 'useTable');
|
||||||
const initialState = { foo: 'bar' };
|
const initialState = { foo: 'bar' };
|
||||||
mount(<DataTable {...props} initialState={initialState} />);
|
mount(<DataTableWrapper {...props} initialState={initialState} />);
|
||||||
expect(spy.mock.calls[0][0].initialState).toEqual(initialState);
|
expect(spy.mock.calls[0][0].initialState).toEqual(initialState);
|
||||||
});
|
});
|
||||||
it('displays loading state', () => {
|
it('displays loading state', () => {
|
||||||
const wrapper = mount(<DataTable {...props} isLoading />);
|
const wrapper = mount(<DataTableWrapper {...props} isLoading />);
|
||||||
const tableContainer = wrapper.find('.pgn__data-table-container');
|
const tableContainer = wrapper.find('.pgn__data-table-container');
|
||||||
const spinner = wrapper.find('.pgn__data-table-spinner');
|
const spinner = wrapper.find('.pgn__data-table-spinner');
|
||||||
expect(tableContainer.hasClass('is-loading')).toEqual(true);
|
expect(tableContainer.hasClass('is-loading')).toEqual(true);
|
||||||
|
|
@ -182,9 +192,9 @@ describe('<DataTable />', () => {
|
||||||
describe('controlled table selections', () => {
|
describe('controlled table selections', () => {
|
||||||
it('passes initial controlledTableSelections to context', () => {
|
it('passes initial controlledTableSelections to context', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<DataTable {...props}>
|
<DataTableWrapper {...props}>
|
||||||
<DataTableContextProviderChild />
|
<DataTableContextProviderChild />
|
||||||
</DataTable>,
|
</DataTableWrapper>,
|
||||||
);
|
);
|
||||||
const contextValue = wrapper.find('div.context-value').prop('data-contextvalue');
|
const contextValue = wrapper.find('div.context-value').prop('data-contextvalue');
|
||||||
const { controlledTableSelections } = contextValue;
|
const { controlledTableSelections } = contextValue;
|
||||||
|
|
@ -195,7 +205,7 @@ describe('<DataTable />', () => {
|
||||||
});
|
});
|
||||||
it('passes appropriate selection props to context with active selections', () => {
|
it('passes appropriate selection props to context with active selections', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<DataTable {...props}><DataTableContextProviderChild /></DataTable>,
|
<DataTableWrapper {...props}><DataTableContextProviderChild /></DataTableWrapper>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// verify there are no current selections
|
// verify there are no current selections
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
import ExpandAll from '../ExpandAll';
|
import ExpandAll from '../ExpandAll';
|
||||||
|
|
||||||
|
const ExpandAllWrapper = (props) => (
|
||||||
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<ExpandAll {...props} />
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<ExpandAll />', () => {
|
describe('<ExpandAll />', () => {
|
||||||
it('renders expand all element if not all rows are expanded', () => {
|
it('renders expand all element if not all rows are expanded', () => {
|
||||||
const wrapper = mount(<ExpandAll getToggleAllRowsExpandedProps={() => {}} isAllRowsExpanded={false} />);
|
const wrapper = mount(<ExpandAllWrapper getToggleAllRowsExpandedProps={() => {}} isAllRowsExpanded={false} />);
|
||||||
const labelWrapper = wrapper.find('span');
|
const labelWrapper = wrapper.find('span');
|
||||||
expect(labelWrapper.exists()).toEqual(true);
|
expect(labelWrapper.exists()).toEqual(true);
|
||||||
const collapseButton = wrapper.find('button');
|
const collapseButton = wrapper.find('button');
|
||||||
|
|
@ -12,7 +19,7 @@ describe('<ExpandAll />', () => {
|
||||||
expect(collapseButton.text()).toContain('Expand all');
|
expect(collapseButton.text()).toContain('Expand all');
|
||||||
});
|
});
|
||||||
it('renders collapse all element if all rows are expanded', () => {
|
it('renders collapse all element if all rows are expanded', () => {
|
||||||
const wrapper = mount(<ExpandAll getToggleAllRowsExpandedProps={() => {}} isAllRowsExpanded />);
|
const wrapper = mount(<ExpandAllWrapper getToggleAllRowsExpandedProps={() => {}} isAllRowsExpanded />);
|
||||||
const labelWrapper = wrapper.find('span');
|
const labelWrapper = wrapper.find('span');
|
||||||
expect(labelWrapper.exists()).toEqual(true);
|
expect(labelWrapper.exists()).toEqual(true);
|
||||||
const collapseButton = wrapper.find('button');
|
const collapseButton = wrapper.find('button');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import FilterStatus from '../FilterStatus';
|
import FilterStatus from '../FilterStatus';
|
||||||
import { Button } from '../..';
|
import { Button } from '../..';
|
||||||
|
|
@ -17,7 +18,6 @@ const filterProps = {
|
||||||
className: 'filterClass',
|
className: 'filterClass',
|
||||||
showFilteredFields: true,
|
showFilteredFields: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterPropsNoFiltered = {
|
const filterPropsNoFiltered = {
|
||||||
...filterProps,
|
...filterProps,
|
||||||
showFilteredFields: false,
|
showFilteredFields: false,
|
||||||
|
|
@ -26,7 +26,11 @@ const filterPropsNoFiltered = {
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const FilterStatusWrapper = ({ value, props }) => (
|
const FilterStatusWrapper = ({ value, props }) => (
|
||||||
<DataTableContext.Provider value={value}><FilterStatus {...props} /></DataTableContext.Provider>
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTableContext.Provider value={value}>
|
||||||
|
<FilterStatus {...props} />
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<FilterStatus />', () => {
|
describe('<FilterStatus />', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import RowStatus from '../RowStatus';
|
import RowStatus from '../RowStatus';
|
||||||
import DataTableContext from '../DataTableContext';
|
import DataTableContext from '../DataTableContext';
|
||||||
|
|
@ -14,7 +15,12 @@ const statusProps = {
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const RowStatusWrapper = ({ value = instance, props = statusProps }) => (
|
const RowStatusWrapper = ({ value = instance, props = statusProps }) => (
|
||||||
<DataTableContext.Provider value={value}><RowStatus {...props} /></DataTableContext.Provider>);
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTableContext.Provider value={value}>
|
||||||
|
<RowStatus {...props} />
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<RowStatus />', () => {
|
describe('<RowStatus />', () => {
|
||||||
it('returns null if there is no pageSize', () => {
|
it('returns null if there is no pageSize', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import SmartStatus from '../SmartStatus';
|
import SmartStatus from '../SmartStatus';
|
||||||
import DataTableContext from '../DataTableContext';
|
import DataTableContext from '../DataTableContext';
|
||||||
|
|
@ -25,7 +26,12 @@ const instance = {
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const SmartStatusWrapper = ({ value, props }) => (
|
const SmartStatusWrapper = ({ value, props }) => (
|
||||||
<DataTableContext.Provider value={value}><SmartStatus {...props} /></DataTableContext.Provider>);
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
<DataTableContext.Provider value={value}>
|
||||||
|
<SmartStatus {...props} />
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<SmartStatus />', () => {
|
describe('<SmartStatus />', () => {
|
||||||
it('Shows the selection status if rows are selected', () => {
|
it('Shows the selection status if rows are selected', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
import Toast from './index';
|
import Toast from './index';
|
||||||
|
|
||||||
|
/* eslint-disable-next-line react/prop-types */
|
||||||
|
const ToastWrapper = ({ children, ...props }) => (
|
||||||
|
<IntlProvider>
|
||||||
|
<Toast {...props}>
|
||||||
|
{children}
|
||||||
|
</Toast>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('<Toast />', () => {
|
describe('<Toast />', () => {
|
||||||
const onCloseHandler = () => {};
|
const onCloseHandler = () => {};
|
||||||
const props = {
|
const props = {
|
||||||
|
|
@ -10,7 +20,7 @@ describe('<Toast />', () => {
|
||||||
};
|
};
|
||||||
it('renders optional action as link', () => {
|
it('renders optional action as link', () => {
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Toast
|
<ToastWrapper
|
||||||
{...props}
|
{...props}
|
||||||
action={{
|
action={{
|
||||||
label: 'Optional action',
|
label: 'Optional action',
|
||||||
|
|
@ -18,13 +28,13 @@ describe('<Toast />', () => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Success message.
|
Success message.
|
||||||
</Toast>));
|
</ToastWrapper>));
|
||||||
const toastLink = wrapper.find('a.btn');
|
const toastLink = wrapper.find('a.btn');
|
||||||
expect(toastLink).toHaveLength(1);
|
expect(toastLink).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('renders optional action as button', () => {
|
it('renders optional action as button', () => {
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Toast
|
<ToastWrapper
|
||||||
{...props}
|
{...props}
|
||||||
action={{
|
action={{
|
||||||
label: 'Optional action',
|
label: 'Optional action',
|
||||||
|
|
@ -32,17 +42,17 @@ describe('<Toast />', () => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Success message.
|
Success message.
|
||||||
</Toast>));
|
</ToastWrapper>));
|
||||||
const toastButton = wrapper.find('button.btn');
|
const toastButton = wrapper.find('button.btn');
|
||||||
expect(toastButton).toHaveLength(1);
|
expect(toastButton).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('autohide is set to false on onMouseOver and true on onMouseLeave', () => {
|
it('autohide is set to false on onMouseOver and true on onMouseLeave', () => {
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Toast
|
<ToastWrapper
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Success message.
|
Success message.
|
||||||
</Toast>));
|
</ToastWrapper>));
|
||||||
wrapper.prop('onMouseOver');
|
wrapper.prop('onMouseOver');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const toast = wrapper.find(Toast);
|
const toast = wrapper.find(Toast);
|
||||||
|
|
@ -58,11 +68,11 @@ describe('<Toast />', () => {
|
||||||
});
|
});
|
||||||
it('autohide is set to false onFocus and true onBlur', () => {
|
it('autohide is set to false onFocus and true onBlur', () => {
|
||||||
const wrapper = mount((
|
const wrapper = mount((
|
||||||
<Toast
|
<ToastWrapper
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Success message.
|
Success message.
|
||||||
</Toast>));
|
</ToastWrapper>));
|
||||||
wrapper.prop('onFocus');
|
wrapper.prop('onFocus');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const toast = wrapper.find(Toast);
|
const toast = wrapper.find(Toast);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import BaseToast from 'react-bootstrap/Toast';
|
import BaseToast from 'react-bootstrap/Toast';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import ToastContainer from './ToastContainer';
|
import ToastContainer from './ToastContainer';
|
||||||
import { Button, IconButton, Icon } from '..';
|
import { Button, IconButton, Icon } from '..';
|
||||||
import { Close } from '../../icons';
|
import { Close } from '../../icons';
|
||||||
|
|
@ -13,7 +15,13 @@ export const TOAST_DELAY = 5000;
|
||||||
function Toast({
|
function Toast({
|
||||||
action, children, className, closeLabel, onClose, show, ...rest
|
action, children, className, closeLabel, onClose, show, ...rest
|
||||||
}) {
|
}) {
|
||||||
|
const intl = useIntl();
|
||||||
const [autoHide, setAutoHide] = useState(true);
|
const [autoHide, setAutoHide] = useState(true);
|
||||||
|
const intlCloseLabel = closeLabel || intl.formatMessage({
|
||||||
|
id: 'pgn.Toast.closeLabel',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
description: 'Close label for Toast component',
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<ToastContainer>
|
<ToastContainer>
|
||||||
<BaseToast
|
<BaseToast
|
||||||
|
|
@ -34,7 +42,7 @@ function Toast({
|
||||||
<div className="toast-header-btn-container">
|
<div className="toast-header-btn-container">
|
||||||
<IconButton
|
<IconButton
|
||||||
iconAs={Icon}
|
iconAs={Icon}
|
||||||
alt={closeLabel}
|
alt={intlCloseLabel}
|
||||||
className="align-self-start"
|
className="align-self-start"
|
||||||
src={Close}
|
src={Close}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|
@ -61,7 +69,7 @@ function Toast({
|
||||||
|
|
||||||
Toast.defaultProps = {
|
Toast.defaultProps = {
|
||||||
action: null,
|
action: null,
|
||||||
closeLabel: TOAST_CLOSE_LABEL_TEXT,
|
closeLabel: undefined,
|
||||||
delay: TOAST_DELAY,
|
delay: TOAST_DELAY,
|
||||||
className: undefined,
|
className: undefined,
|
||||||
};
|
};
|
||||||
|
|
@ -89,8 +97,7 @@ Toast.propTypes = {
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
}),
|
}),
|
||||||
/**
|
/**
|
||||||
* Alt text for the `Toast`'s dismiss button. The recommended use is an i18n value.
|
* Alt text for the `Toast`'s dismiss button. Defaults to 'Close'.
|
||||||
* The default is an English string.
|
|
||||||
*/
|
*/
|
||||||
closeLabel: PropTypes.string,
|
closeLabel: PropTypes.string,
|
||||||
/** Time in milliseconds for which the `Toast` will display. */
|
/** Time in milliseconds for which the `Toast` will display. */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import arMessages from './messages/ar.json';
|
||||||
|
import caMessages from './messages/ca.json';
|
||||||
|
import es419Messages from './messages/es_419.json';
|
||||||
|
import frMessages from './messages/fr.json';
|
||||||
|
import heMessages from './messages/he.json';
|
||||||
|
import idMessages from './messages/id.json';
|
||||||
|
import kokrMessages from './messages/ko_KR.json';
|
||||||
|
import plMessages from './messages/pl.json';
|
||||||
|
import ptbrMessages from './messages/pt_BR.json';
|
||||||
|
import ruMessages from './messages/ru.json';
|
||||||
|
import thMessages from './messages/th.json';
|
||||||
|
import ukMessages from './messages/uk.json';
|
||||||
|
import zhcnMessages from './messages/zh_CN.json';
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
ar: arMessages,
|
||||||
|
ca: caMessages,
|
||||||
|
'es-419': es419Messages,
|
||||||
|
fr: frMessages,
|
||||||
|
he: heMessages,
|
||||||
|
id: idMessages,
|
||||||
|
'ko-kr': kokrMessages,
|
||||||
|
pl: plMessages,
|
||||||
|
'pt-br': ptbrMessages,
|
||||||
|
ru: ruMessages,
|
||||||
|
th: thMessages,
|
||||||
|
uk: ukMessages,
|
||||||
|
'zh-cn': zhcnMessages,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default messages;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -186,3 +186,5 @@ export {
|
||||||
useFlexLayout,
|
useFlexLayout,
|
||||||
} from 'react-table';
|
} from 'react-table';
|
||||||
export { default as Bubble } from './Bubble';
|
export { default as Bubble } from './Bubble';
|
||||||
|
|
||||||
|
export { default as messages } from './i18n';
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
FEATURE_LANGUAGE_SWITCHER=true
|
||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
FEATURE_ENABLE_AXE='true'
|
FEATURE_ENABLE_AXE='true'
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ exports.onCreateWebpackConfig = ({ actions }) => {
|
||||||
// in ./node_modules
|
// in ./node_modules
|
||||||
react: path.resolve(__dirname, 'node_modules/react/'),
|
react: path.resolve(__dirname, 'node_modules/react/'),
|
||||||
'react-dom': path.resolve(__dirname, 'node_modules/react-dom/'),
|
'react-dom': path.resolve(__dirname, 'node_modules/react-dom/'),
|
||||||
|
'react-intl': path.resolve(__dirname, 'node_modules/react-intl/'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
"prism-react-renderer": "^1.2.1",
|
"prism-react-renderer": "^1.2.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
|
||||||
"react-docgen": "^5.4.0",
|
"react-docgen": "^5.4.0",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-focus-on": "^3.5.4",
|
"react-focus-on": "^3.5.4",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-intl": "^5.25.0",
|
||||||
"react-live": "^2.4.0",
|
"react-live": "^2.4.0",
|
||||||
"rehype-autolink-headings": "^5.1.0",
|
"rehype-autolink-headings": "^5.1.0",
|
||||||
"rehype-slug": "^4.0.1",
|
"rehype-slug": "^4.0.1",
|
||||||
|
|
@ -4761,6 +4761,132 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-vgvyUOOrzqVaOFYzTf2d3+ToSkH2JpR7x/4U1RyoHQLmvEaTQvXJ7A2qm1Iy3brGNXC/+/7bUlc3lpH+h/LOJA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"@formatjs/intl-displaynames": "5.4.3",
|
||||||
|
"@formatjs/intl-listformat": "6.5.3",
|
||||||
|
"intl-messageformat": "9.13.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^4.5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-displaynames": {
|
||||||
|
"version": "5.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-5.4.3.tgz",
|
||||||
|
"integrity": "sha512-4r12A3mS5dp5hnSaQCWBuBNfi9Amgx2dzhU4lTFfhSxgb5DOAiAbMpg6+7gpWZgl4ahsj3l2r/iHIjdmdXOE2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-displaynames/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-listformat": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-ozpz515F/+3CU+HnLi5DYPsLa6JoCfBggBSSg/8nOB5LYSFW9+ZgNQJxJ8tdhKYeODT+4qVHX27EeJLoxLGLNg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-listformat/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||||
|
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
"version": "0.2.36",
|
"version": "0.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
||||||
|
|
@ -5819,6 +5945,15 @@
|
||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/http-cache-semantics": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||||
|
|
@ -8999,14 +9134,6 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/copy-to-clipboard": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
|
|
||||||
"dependencies": {
|
|
||||||
"toggle-selection": "^1.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/core-js": {
|
"node_modules/core-js": {
|
||||||
"version": "3.20.3",
|
"version": "3.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz",
|
||||||
|
|
@ -15944,6 +16071,14 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hosted-git-info": {
|
"node_modules/hosted-git-info": {
|
||||||
"version": "2.8.9",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
|
|
@ -16264,6 +16399,22 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "9.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||||
|
"integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
"node_modules/invariant": {
|
"node_modules/invariant": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
|
@ -21429,18 +21580,6 @@
|
||||||
"react": "^15.3.0 || ^16.0.0 || ^17.0.0"
|
"react": "^15.3.0 || ^16.0.0 || ^17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-copy-to-clipboard": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
|
|
||||||
"dependencies": {
|
|
||||||
"copy-to-clipboard": "^3.3.1",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^15.3.0 || 16 || 17 || 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dev-utils": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "11.0.4",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
||||||
|
|
@ -21792,6 +21931,37 @@
|
||||||
"react": ">=16.3.0"
|
"react": ">=16.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-intl": {
|
||||||
|
"version": "5.25.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz",
|
||||||
|
"integrity": "sha512-pkjdQDvpJROoXLMltkP/5mZb0/XqrqLoPGKUCfbdkP8m6U9xbK40K51Wu+a4aQqTEvEK5lHBk0fWzUV72SJ3Hg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"@formatjs/intl": "2.2.1",
|
||||||
|
"@formatjs/intl-displaynames": "5.4.3",
|
||||||
|
"@formatjs/intl-listformat": "6.5.3",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/react": "16 || 17 || 18",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"intl-messageformat": "9.13.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.3.0 || 17 || 18",
|
||||||
|
"typescript": "^4.5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-intl/node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|
@ -25007,11 +25177,6 @@
|
||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/toggle-selection": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
|
||||||
},
|
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
|
@ -30021,6 +30186,140 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/fast-memoize": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/intl": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-vgvyUOOrzqVaOFYzTf2d3+ToSkH2JpR7x/4U1RyoHQLmvEaTQvXJ7A2qm1Iy3brGNXC/+/7bUlc3lpH+h/LOJA==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"@formatjs/intl-displaynames": "5.4.3",
|
||||||
|
"@formatjs/intl-listformat": "6.5.3",
|
||||||
|
"intl-messageformat": "9.13.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/intl-displaynames": {
|
||||||
|
"version": "5.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-5.4.3.tgz",
|
||||||
|
"integrity": "sha512-4r12A3mS5dp5hnSaQCWBuBNfi9Amgx2dzhU4lTFfhSxgb5DOAiAbMpg6+7gpWZgl4ahsj3l2r/iHIjdmdXOE2Q==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/intl-listformat": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-ozpz515F/+3CU+HnLi5DYPsLa6JoCfBggBSSg/8nOB5LYSFW9+ZgNQJxJ8tdhKYeODT+4qVHX27EeJLoxLGLNg==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||||
|
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@fortawesome/fontawesome-common-types": {
|
"@fortawesome/fontawesome-common-types": {
|
||||||
"version": "0.2.36",
|
"version": "0.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
||||||
|
|
@ -30873,6 +31172,15 @@
|
||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/http-cache-semantics": {
|
"@types/http-cache-semantics": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||||
|
|
@ -33380,14 +33688,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
||||||
},
|
},
|
||||||
"copy-to-clipboard": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
|
|
||||||
"requires": {
|
|
||||||
"toggle-selection": "^1.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.20.3",
|
"version": "3.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz",
|
||||||
|
|
@ -38657,6 +38957,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||||
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
|
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
|
||||||
},
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.9",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
|
|
@ -38886,6 +39194,24 @@
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"intl-messageformat": {
|
||||||
|
"version": "9.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||||
|
"integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"invariant": {
|
"invariant": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
|
@ -42577,15 +42903,6 @@
|
||||||
"@babel/runtime": "^7.12.13"
|
"@babel/runtime": "^7.12.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-copy-to-clipboard": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
|
|
||||||
"requires": {
|
|
||||||
"copy-to-clipboard": "^3.3.1",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "11.0.4",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
||||||
|
|
@ -42851,6 +43168,30 @@
|
||||||
"react-side-effect": "^2.1.0"
|
"react-side-effect": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-intl": {
|
||||||
|
"version": "5.25.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz",
|
||||||
|
"integrity": "sha512-pkjdQDvpJROoXLMltkP/5mZb0/XqrqLoPGKUCfbdkP8m6U9xbK40K51Wu+a4aQqTEvEK5lHBk0fWzUV72SJ3Hg==",
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"@formatjs/intl": "2.2.1",
|
||||||
|
"@formatjs/intl-displaynames": "5.4.3",
|
||||||
|
"@formatjs/intl-listformat": "6.5.3",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/react": "16 || 17 || 18",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"intl-messageformat": "9.13.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|
@ -45311,11 +45652,6 @@
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toggle-selection": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
|
||||||
},
|
|
||||||
"toidentifier": {
|
"toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
"description": "Paragon Pattern Library Documentation",
|
"description": "Paragon Pattern Library Documentation",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand-edx.org": "^2.0.6",
|
|
||||||
"@docsearch/react": "^3.0.0",
|
"@docsearch/react": "^3.0.0",
|
||||||
|
"@edx/brand-edx.org": "^2.0.6",
|
||||||
"@edx/brand-openedx": "^1.1.0",
|
"@edx/brand-openedx": "^1.1.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@mdx-js/mdx": "^1.6.22",
|
"@mdx-js/mdx": "^1.6.22",
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"react-focus-on": "^3.5.4",
|
"react-focus-on": "^3.5.4",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-live": "^2.4.0",
|
"react-live": "^2.4.0",
|
||||||
|
"react-intl": "^5.25.0",
|
||||||
"rehype-autolink-headings": "^5.1.0",
|
"rehype-autolink-headings": "^5.1.0",
|
||||||
"rehype-slug": "^4.0.1",
|
"rehype-slug": "^4.0.1",
|
||||||
"sass": "^1.32.13",
|
"sass": "^1.32.13",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import theme from 'prism-react-renderer/themes/duotoneDark';
|
||||||
import {
|
import {
|
||||||
LiveProvider, LiveEditor, LiveError, LivePreview,
|
LiveProvider, LiveEditor, LiveError, LivePreview,
|
||||||
} from 'react-live';
|
} from 'react-live';
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
import * as ParagonReact from '~paragon-react'; // eslint-disable-line
|
import * as ParagonReact from '~paragon-react'; // eslint-disable-line
|
||||||
import * as ParagonIcons from '~paragon-icons'; // eslint-disable-line
|
import * as ParagonIcons from '~paragon-icons'; // eslint-disable-line
|
||||||
import MiyazakiCard from './exampleComponents/MiyazakiCard';
|
import MiyazakiCard from './exampleComponents/MiyazakiCard';
|
||||||
|
|
@ -41,7 +42,12 @@ CollapsibleLiveEditor.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CodeBlock({ children, className, live }) {
|
function CodeBlock({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
live,
|
||||||
|
}) {
|
||||||
|
const intl = useIntl();
|
||||||
const language = className ? className.replace(/language-/, '') : 'jsx';
|
const language = className ? className.replace(/language-/, '') : 'jsx';
|
||||||
|
|
||||||
if (live) {
|
if (live) {
|
||||||
|
|
@ -58,6 +64,8 @@ function CodeBlock({ children, className, live }) {
|
||||||
useMemo,
|
useMemo,
|
||||||
MiyazakiCard,
|
MiyazakiCard,
|
||||||
HipsterIpsum,
|
HipsterIpsum,
|
||||||
|
FormattedMessage,
|
||||||
|
formatMessage: intl.formatMessage,
|
||||||
MenuIcon: ParagonIcons.Menu,
|
MenuIcon: ParagonIcons.Menu,
|
||||||
}}
|
}}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { MDXRenderer } from 'gatsby-plugin-mdx';
|
||||||
import PropType from './PropType';
|
import PropType from './PropType';
|
||||||
import { Badge, Card } from '~paragon-react'; // eslint-disable-line
|
import { Badge, Card } from '~paragon-react'; // eslint-disable-line
|
||||||
|
|
||||||
|
const IGNORED_COMPONENT_PROPS = ['intl'];
|
||||||
|
|
||||||
const DefaultValue = ({ value }) => {
|
const DefaultValue = ({ value }) => {
|
||||||
if (!value || value === 'undefined') { return null; }
|
if (!value || value === 'undefined') { return null; }
|
||||||
return (
|
return (
|
||||||
|
|
@ -69,7 +71,9 @@ const PropsTable = ({ props: componentProps, displayName, content }) => (
|
||||||
{content && <div className="small mb-3">{content}</div>}
|
{content && <div className="small mb-3">{content}</div>}
|
||||||
{componentProps.length > 0 ? (
|
{componentProps.length > 0 ? (
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
{componentProps.map(metadata => <Prop key={metadata.name} {...metadata} />)}
|
{componentProps
|
||||||
|
.filter(metadata => !IGNORED_COMPONENT_PROPS.includes(metadata.name))
|
||||||
|
.map(metadata => <Prop key={metadata.name} {...metadata} />)}
|
||||||
</ul>
|
</ul>
|
||||||
) : <div className="pb-3 pl-4">This component does not receive any props.</div>}
|
) : <div className="pb-3 pl-4">This component does not receive any props.</div>}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,20 @@ import {
|
||||||
Form,
|
Form,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Stack,
|
||||||
} from '~paragon-react';
|
} from '~paragon-react';
|
||||||
import { Close } from '~paragon-icons';
|
import { Close } from '~paragon-icons';
|
||||||
|
|
||||||
|
import { FEATURES, LANGUAGES } from '../config';
|
||||||
import SettingsContext from '../context/SettingsContext';
|
import SettingsContext from '../context/SettingsContext';
|
||||||
import { THEMES } from '../../theme-config';
|
import { THEMES } from '../../theme-config';
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const {
|
const {
|
||||||
theme: currentTheme,
|
settings,
|
||||||
onThemeChange,
|
handleSettingsChange,
|
||||||
showSettings,
|
showSettings,
|
||||||
closeSettings,
|
closeSettings,
|
||||||
direction,
|
|
||||||
onDirectionChange,
|
|
||||||
} = useContext(SettingsContext);
|
} = useContext(SettingsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -38,12 +38,12 @@ const Settings = () => {
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="pgn__settings-form-wrapper">
|
<Stack gap={3}>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
value={currentTheme}
|
value={settings.theme}
|
||||||
onChange={onThemeChange}
|
onChange={(e) => handleSettingsChange('theme', e.target.value)}
|
||||||
floatingLabel="Theme"
|
floatingLabel="Theme"
|
||||||
>
|
>
|
||||||
{THEMES.map(theme => (
|
{THEMES.map(theme => (
|
||||||
|
|
@ -59,15 +59,34 @@ const Settings = () => {
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
value={direction}
|
value={settings.direction}
|
||||||
onChange={onDirectionChange}
|
onChange={(e) => handleSettingsChange('direction', e.target.value)}
|
||||||
floatingLabel="Direction"
|
floatingLabel="Direction"
|
||||||
>
|
>
|
||||||
<option value="ltr">Left to right</option>
|
<option value="ltr">Left to right</option>
|
||||||
<option value="rtl">Right to left</option>
|
<option value="rtl">Right to left</option>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</div>
|
{FEATURES.LANGUAGE_SWITCHER && (
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
value={settings.language}
|
||||||
|
onChange={(e) => handleSettingsChange('language', e.target.value)}
|
||||||
|
floatingLabel="Component Language"
|
||||||
|
>
|
||||||
|
{LANGUAGES.map(lang => (
|
||||||
|
<option
|
||||||
|
key={lang.code}
|
||||||
|
value={lang.code}
|
||||||
|
>
|
||||||
|
{lang.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// import hasFeatureFlagEnabled from './utils/hasFeatureFlagEnabled';
|
const hasFeatureFlagEnabled = require('./utils/hasFeatureFlagEnabled');
|
||||||
|
|
||||||
// export const EXAMPLE_FEATURE = 'EXAMPLE_FEATURE';
|
const FEATURE_LANGUAGE_SWITCHER = 'LANGUAGE_SWITCHER';
|
||||||
|
|
||||||
// Feature flags used throughout the site should be configured here.
|
// Feature flags used throughout the site should be configured here.
|
||||||
// You should generally allow two ways of enabling a feature flag:
|
// You should generally allow two ways of enabling a feature flag:
|
||||||
|
|
@ -11,12 +11,70 @@
|
||||||
// See DIRECTION_SWITCHER feature for example of configuring feature flags this way.
|
// See DIRECTION_SWITCHER feature for example of configuring feature flags this way.
|
||||||
// 2. As a query parameter in the URL, using hasFeatureFlagEnabled util function.
|
// 2. As a query parameter in the URL, using hasFeatureFlagEnabled util function.
|
||||||
// This will allow to enable feature flag by providing its name as a feature?
|
// This will allow to enable feature flag by providing its name as a feature?
|
||||||
// query parameter in the URL. (e.g. to enable DIRECTION_SWITCHER feature you would append
|
// query parameter in the URL. (e.g. to enable LANGUAGE_SWITCHER feature you would append
|
||||||
// '?feature=DIRECTION_SWITCHER' to the URL)
|
// '?feature=LANGUAGE_SWITCHER' to the URL)
|
||||||
|
const FEATURES = {
|
||||||
|
LANGUAGE_SWITCHER: process.env.FEATURE_LANGUAGE_SWITCHER || hasFeatureFlagEnabled(FEATURE_LANGUAGE_SWITCHER),
|
||||||
|
};
|
||||||
|
|
||||||
// export const FEATURES = {
|
const LANGUAGES = [
|
||||||
// EXAMPLE_FEATURE: process.env.EXAMPLE_FEATURE || hasFeatureFlagEnabled(EXAMPLE_FEATURE),
|
{
|
||||||
// };
|
label: 'English',
|
||||||
|
code: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Arabic',
|
||||||
|
code: 'ar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Catalan',
|
||||||
|
code: 'ca',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Chinese',
|
||||||
|
code: 'zh-cn',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'French',
|
||||||
|
code: 'fr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hebrew',
|
||||||
|
code: 'he',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Indonesian',
|
||||||
|
code: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Polish',
|
||||||
|
code: 'pl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Russian',
|
||||||
|
code: 'ru',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Thai',
|
||||||
|
code: 'th',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Ukrainian',
|
||||||
|
code: 'uk',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spanish',
|
||||||
|
code: 'es-419',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Korean',
|
||||||
|
code: 'ko-kr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Portuguese',
|
||||||
|
code: 'pt-br',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const INSIGHTS_TABS = Object.freeze({
|
const INSIGHTS_TABS = Object.freeze({
|
||||||
SUMMARY: 'Summary',
|
SUMMARY: 'Summary',
|
||||||
|
|
@ -42,4 +100,7 @@ const INSIGHTS_PAGES = [
|
||||||
module.exports = {
|
module.exports = {
|
||||||
INSIGHTS_TABS,
|
INSIGHTS_TABS,
|
||||||
INSIGHTS_PAGES,
|
INSIGHTS_PAGES,
|
||||||
|
FEATURES,
|
||||||
|
LANGUAGES,
|
||||||
|
FEATURE_LANGUAGE_SWITCHER,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import React, { createContext, useState, useEffect } from 'react';
|
import React, { createContext, useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { messages } from '~paragon-react';
|
||||||
|
|
||||||
import { THEMES } from '../../theme-config';
|
import { THEMES } from '../../theme-config';
|
||||||
|
|
||||||
const defaultValue = {
|
const defaultValue = {
|
||||||
theme: 'openedx-theme',
|
settings: {},
|
||||||
onThemeChange: () => {},
|
handleSettingsChange: () => {},
|
||||||
direction: 'ltr',
|
|
||||||
onDirectionChange: () => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsContext = createContext(defaultValue);
|
export const SettingsContext = createContext(defaultValue);
|
||||||
|
|
@ -15,38 +16,33 @@ export const SettingsContext = createContext(defaultValue);
|
||||||
const SettingsContextProvider = ({ children }) => {
|
const SettingsContextProvider = ({ children }) => {
|
||||||
// gatsby does not have access to the localStorage during the build (and first render)
|
// gatsby does not have access to the localStorage during the build (and first render)
|
||||||
// so sadly we cannot initialize theme with value from localStorage
|
// so sadly we cannot initialize theme with value from localStorage
|
||||||
const [theme, setTheme] = useState('openedx-theme');
|
const [settings, setSettings] = useState({
|
||||||
|
theme: 'openedx-theme',
|
||||||
|
direction: 'ltr',
|
||||||
|
language: 'en',
|
||||||
|
});
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [direction, setDirection] = useState('ltr');
|
|
||||||
|
|
||||||
const handleDirectionChange = (e) => {
|
const handleSettingsChange = (key, value) => {
|
||||||
document.body.setAttribute('dir', e.target.value);
|
if (key === 'direction') {
|
||||||
setDirection(e.target.value);
|
document.body.setAttribute('dir', value);
|
||||||
global.localStorage.setItem('pgn__direction', e.target.value);
|
}
|
||||||
global.analytics.track('Direction change', { direction: e.target.value });
|
setSettings(prevState => ({ ...prevState, [key]: value }));
|
||||||
|
global.localStorage.setItem('pgn__settings', JSON.stringify({ ...settings, [key]: value }));
|
||||||
|
global.analytics.track(`${key[0].toUpperCase() + key.slice(1)} change`, { [key]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThemeChange = (e) => {
|
const toggleSettings = (value) => {
|
||||||
setTheme(e.target.value);
|
|
||||||
global.localStorage.setItem('pgn__theme', e.target.value);
|
|
||||||
global.analytics.track('Theme change', { theme: e.target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSettingsChange = (value) => {
|
|
||||||
setShowSettings(value);
|
setShowSettings(value);
|
||||||
global.analytics.track('Toggle Settings', { value: value ? 'show' : 'hide' });
|
global.analytics.track('Toggle Settings', { value: value ? 'show' : 'hide' });
|
||||||
};
|
};
|
||||||
|
|
||||||
// this hook will be called after the first render, so we can safely access localStorage
|
// this hook will be called after the first render, so we can safely access localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedTheme = global.localStorage.getItem('pgn__theme');
|
const savedSettings = JSON.parse(global.localStorage.getItem('pgn__settings'));
|
||||||
if (savedTheme) {
|
if (savedSettings) {
|
||||||
setTheme(savedTheme);
|
setSettings(savedSettings);
|
||||||
}
|
document.body.setAttribute('dir', savedSettings.direction);
|
||||||
const savedDirection = global.localStorage.getItem('pgn__direction');
|
|
||||||
if (savedDirection) {
|
|
||||||
document.body.setAttribute('dir', savedDirection);
|
|
||||||
setDirection(savedDirection);
|
|
||||||
}
|
}
|
||||||
if (!global.analytics) {
|
if (!global.analytics) {
|
||||||
global.analytics = {};
|
global.analytics = {};
|
||||||
|
|
@ -55,13 +51,11 @@ const SettingsContextProvider = ({ children }) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
theme,
|
settings,
|
||||||
direction,
|
|
||||||
showSettings,
|
showSettings,
|
||||||
onThemeChange: handleThemeChange,
|
handleSettingsChange,
|
||||||
onDirectionChange: handleDirectionChange,
|
closeSettings: () => toggleSettings(false),
|
||||||
closeSettings: () => handleSettingsChange(false),
|
openSettings: () => toggleSettings(true),
|
||||||
openSettings: () => handleSettingsChange(true),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -77,12 +71,14 @@ const SettingsContextProvider = ({ children }) => {
|
||||||
<link
|
<link
|
||||||
key={themeInfo.stylesheet}
|
key={themeInfo.stylesheet}
|
||||||
href={`/static/${themeInfo.stylesheet}.css`}
|
href={`/static/${themeInfo.stylesheet}.css`}
|
||||||
rel={`stylesheet${theme === themeInfo.stylesheet ? '' : ' alternate'}`}
|
rel={`stylesheet${settings.theme === themeInfo.stylesheet ? '' : ' alternate'}`}
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{children}
|
<IntlProvider messages={messages[settings.language]} locale={settings.language.split('-')[0]}>
|
||||||
|
{children}
|
||||||
|
</IntlProvider>
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* @param {string} featureFlag
|
* @param {string} featureFlag
|
||||||
* @returns true if feature flag is in `?feature` query parameter
|
* @returns true if feature flag is in `?feature` query parameter
|
||||||
*/
|
*/
|
||||||
export default function hasFeatureFlagEnabled(featureFlag) {
|
function hasFeatureFlagEnabled(featureFlag) {
|
||||||
const { location } = global;
|
const { location } = global;
|
||||||
if (!location) {
|
if (!location) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -13,3 +13,5 @@ export default function hasFeatureFlagEnabled(featureFlag) {
|
||||||
const features = searchParams.getAll('feature');
|
const features = searchParams.getAll('feature');
|
||||||
return features.includes(featureFlag);
|
return features.includes(featureFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = hasFeatureFlagEnabled;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue