diff --git a/packages/react/__tests__/PublicAPI-test.js b/packages/react/__tests__/PublicAPI-test.js
index dc2d28489108..3f688a56813d 100644
--- a/packages/react/__tests__/PublicAPI-test.js
+++ b/packages/react/__tests__/PublicAPI-test.js
@@ -124,6 +124,8 @@ beforeEach(() => {
* the components that we export and their corresponding API.
*/
test('Public API should only change with a semver change', () => {
+ jest.mock('../src/internal/deprecateFieldOnObject');
+
const CarbonReact = require('../src');
const PublicAPI = new Map();
diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
index a78ea9f33be3..5cff6abca646 100644
--- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
+++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
@@ -4331,6 +4331,249 @@ Map {
},
"render": [Function],
},
+ "ControlledPasswordInput" => Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "className": "\${prefix}--text__input",
+ "disabled": false,
+ "helperText": "",
+ "invalid": false,
+ "invalidText": "",
+ "light": false,
+ "onChange": [Function],
+ "onClick": [Function],
+ "size": "",
+ },
+ "propTypes": Object {
+ "className": Object {
+ "type": "string",
+ },
+ "defaultValue": Object {
+ "args": Array [
+ Array [
+ Object {
+ "type": "string",
+ },
+ Object {
+ "type": "number",
+ },
+ ],
+ ],
+ "type": "oneOfType",
+ },
+ "disabled": Object {
+ "type": "bool",
+ },
+ "helperText": Object {
+ "type": "node",
+ },
+ "hideLabel": Object {
+ "type": "bool",
+ },
+ "hidePasswordLabel": Object {
+ "type": "string",
+ },
+ "id": Object {
+ "isRequired": true,
+ "type": "string",
+ },
+ "invalid": Object {
+ "type": "bool",
+ },
+ "invalidText": Object {
+ "type": "node",
+ },
+ "labelText": Object {
+ "isRequired": true,
+ "type": "node",
+ },
+ "light": Object {
+ "type": "bool",
+ },
+ "onChange": Object {
+ "type": "func",
+ },
+ "onClick": Object {
+ "type": "func",
+ },
+ "placeholder": Object {
+ "type": "string",
+ },
+ "showPasswordLabel": Object {
+ "type": "string",
+ },
+ "size": Object {
+ "type": "string",
+ },
+ "tooltipAlignment": Object {
+ "args": Array [
+ Array [
+ "start",
+ "center",
+ "end",
+ ],
+ ],
+ "type": "oneOf",
+ },
+ "tooltipPosition": Object {
+ "args": Array [
+ Array [
+ "top",
+ "right",
+ "bottom",
+ "left",
+ ],
+ ],
+ "type": "oneOf",
+ },
+ "value": Object {
+ "args": Array [
+ Array [
+ Object {
+ "type": "string",
+ },
+ Object {
+ "type": "number",
+ },
+ ],
+ ],
+ "type": "oneOfType",
+ },
+ },
+ "render": [Function],
+ },
+ "PasswordInput" => Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "className": "\${prefix}--text__input",
+ "disabled": false,
+ "helperText": "",
+ "invalid": false,
+ "invalidText": "",
+ "light": false,
+ "onChange": [Function],
+ "onClick": [Function],
+ "size": "",
+ },
+ "propTypes": Object {
+ "className": Object {
+ "type": "string",
+ },
+ "defaultValue": Object {
+ "args": Array [
+ Array [
+ Object {
+ "type": "string",
+ },
+ Object {
+ "type": "number",
+ },
+ ],
+ ],
+ "type": "oneOfType",
+ },
+ "disabled": Object {
+ "type": "bool",
+ },
+ "helperText": Object {
+ "type": "node",
+ },
+ "hideLabel": Object {
+ "type": "bool",
+ },
+ "hidePasswordLabel": Object {
+ "type": "string",
+ },
+ "id": Object {
+ "isRequired": true,
+ "type": "string",
+ },
+ "inline": Object {
+ "type": "bool",
+ },
+ "invalid": Object {
+ "type": "bool",
+ },
+ "invalidText": Object {
+ "type": "node",
+ },
+ "labelText": Object {
+ "isRequired": true,
+ "type": "node",
+ },
+ "light": Object {
+ "type": "bool",
+ },
+ "onChange": Object {
+ "type": "func",
+ },
+ "onClick": Object {
+ "type": "func",
+ },
+ "onTogglePasswordVisibility": Object {
+ "type": "func",
+ },
+ "placeholder": Object {
+ "type": "string",
+ },
+ "showPasswordLabel": Object {
+ "type": "string",
+ },
+ "size": Object {
+ "type": "string",
+ },
+ "tooltipAlignment": Object {
+ "args": Array [
+ Array [
+ "start",
+ "center",
+ "end",
+ ],
+ ],
+ "type": "oneOf",
+ },
+ "tooltipPosition": Object {
+ "args": Array [
+ Array [
+ "top",
+ "right",
+ "bottom",
+ "left",
+ ],
+ ],
+ "type": "oneOf",
+ },
+ "type": Object {
+ "args": Array [
+ Array [
+ "password",
+ "text",
+ ],
+ ],
+ "type": "oneOf",
+ },
+ "value": Object {
+ "args": Array [
+ Array [
+ Object {
+ "type": "string",
+ },
+ Object {
+ "type": "number",
+ },
+ ],
+ ],
+ "type": "oneOfType",
+ },
+ "warn": Object {
+ "type": "bool",
+ },
+ "warnText": Object {
+ "type": "node",
+ },
+ },
+ "render": [Function],
+ },
"PrimaryButton" => Object {},
"ProgressIndicator" => Object {
"defaultProps": Object {
diff --git a/packages/react/docs/migration/11.x-namespaced-exports.md b/packages/react/docs/migration/11.x-namespaced-exports.md
new file mode 100644
index 000000000000..b34408159e58
--- /dev/null
+++ b/packages/react/docs/migration/11.x-namespaced-exports.md
@@ -0,0 +1,44 @@
+# Namespaced exports
+
+
+
+
+## Table of Contents
+
+- [Overview](#overview)
+- [Process](#process)
+- [Changes:](#changes)
+
+
+
+## Overview
+
+In v10.x there were a few exported components that were only exposed under the
+namespace of another component. These have been deprecated, and a new export has
+been made available for each so these can be imported directly. In v11.x the
+namespaced exports will be removed.
+
+## Process
+
+1. Update imports for the components listed in the table below.
+
+```diff
+- import { TextInput } from 'carbon-components-react';
++ import { PasswordInput } from 'carbon-components-react';
+```
+
+2. Update usages of the components listed in the table below, they no longer
+ need the namespace
+
+```diff
+-
++
+```
+
+## Changes:
+
+| v10.x | v11.x |
+| ----------------------------------- | ------------------------- |
+| `TextInput.ControlledPasswordInput` | `ControlledPasswordInput` |
+| `TextInput.PasswordInput` | `PasswordInput` |
+| `MultiSelect.Filterable` | `FilterableMultiSelect` |
diff --git a/packages/react/src/__tests__/index-test.js b/packages/react/src/__tests__/index-test.js
index 4ad9a349281b..8ba8e2097c99 100644
--- a/packages/react/src/__tests__/index-test.js
+++ b/packages/react/src/__tests__/index-test.js
@@ -35,6 +35,7 @@ describe('Carbon Components React', () => {
"ComposedModal",
"Content",
"ContentSwitcher",
+ "ControlledPasswordInput",
"Copy",
"CopyButton",
"DangerButton",
@@ -96,6 +97,7 @@ describe('Carbon Components React', () => {
"Pagination",
"PaginationNav",
"PaginationSkeleton",
+ "PasswordInput",
"PrimaryButton",
"ProgressIndicator",
"ProgressIndicatorSkeleton",
diff --git a/packages/react/src/components/FilterableMultiSelect/index.js b/packages/react/src/components/FilterableMultiSelect/index.js
new file mode 100644
index 000000000000..e24ee932dcc1
--- /dev/null
+++ b/packages/react/src/components/FilterableMultiSelect/index.js
@@ -0,0 +1 @@
+export FilterableMultiSelect from '../MultiSelect/FilterableMultiSelect';
diff --git a/packages/react/src/components/MultiSelect/MultiSelect-story.js b/packages/react/src/components/MultiSelect/MultiSelect-story.js
index b1c532f34572..97303f5660ae 100644
--- a/packages/react/src/components/MultiSelect/MultiSelect-story.js
+++ b/packages/react/src/components/MultiSelect/MultiSelect-story.js
@@ -17,6 +17,7 @@ import {
import { withReadme } from 'storybook-readme';
import readme from './README.md';
import MultiSelect from '../MultiSelect';
+import FilterableMultiSelect from '../MultiSelect/FilterableMultiSelect';
import Checkbox from '../Checkbox';
import mdx from './MultiSelect.mdx';
@@ -116,7 +117,7 @@ export default {
page: mdx,
},
subcomponents: {
- 'MultiSelect.Filterable': MultiSelect.Filterable,
+ FilterableMultiSelect,
},
},
};
@@ -190,7 +191,7 @@ export const _Filterable = withReadme(readme, () => {
return (
- (item ? item.text : '')}
diff --git a/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js b/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js
index 61bb0707d19d..4c4474840704 100644
--- a/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js
+++ b/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js
@@ -7,7 +7,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import MultiSelect from '../../MultiSelect';
+import FilterableMultiSelect from '../../MultiSelect/FilterableMultiSelect';
import {
assertMenuOpen,
assertMenuClosed,
@@ -21,10 +21,12 @@ const openMenu = (wrapper) => {
wrapper.find(`[role="combobox"]`).simulate('click');
};
-describe('MultiSelect.Filterable', () => {
+describe('FilterableMultiSelect', () => {
let mockProps;
beforeEach(() => {
+ // jest.mock('../../../internal/deprecateFieldOnObject');
+
mockProps = {
id: 'test-filterable-multiselect',
disabled: false,
@@ -37,23 +39,23 @@ describe('MultiSelect.Filterable', () => {
});
it('should render', () => {
- const wrapper = mount();
+ const wrapper = mount();
expect(wrapper).toMatchSnapshot();
});
it('should display all items when the menu is open initially', () => {
- const wrapper = mount();
+ const wrapper = mount();
openMenu(wrapper);
expect(wrapper.find(listItemName).length).toBe(mockProps.items.length);
});
it('should initially have the menu open when open prop is provided', () => {
- const wrapper = mount();
+ const wrapper = mount();
assertMenuOpen(wrapper, mockProps);
});
it('should open the menu with a down arrow', () => {
- const wrapper = mount();
+ const wrapper = mount();
const menuIconNode = findMenuIconNode(wrapper);
menuIconNode.simulate('keyDown', { key: 'ArrowDown' });
@@ -61,7 +63,7 @@ describe('MultiSelect.Filterable', () => {
});
it('should let the user toggle the menu by the menu icon', () => {
- const wrapper = mount();
+ const wrapper = mount();
findMenuIconNode(wrapper).simulate('click');
assertMenuOpen(wrapper, mockProps);
findMenuIconNode(wrapper).simulate('click');
@@ -69,7 +71,7 @@ describe('MultiSelect.Filterable', () => {
});
it('should not close the menu after a user makes a selection', () => {
- const wrapper = mount();
+ const wrapper = mount();
openMenu(wrapper);
const firstListItem = wrapper.find(listItemName).at(0);
@@ -79,7 +81,7 @@ describe('MultiSelect.Filterable', () => {
});
it('should filter a list of items by the input value', () => {
- const wrapper = mount();
+ const wrapper = mount();
openMenu(wrapper);
expect(wrapper.find(listItemName).length).toBe(mockProps.items.length);
@@ -93,7 +95,7 @@ describe('MultiSelect.Filterable', () => {
it('should call `onChange` with each update to selected items', () => {
const wrapper = mount(
-
+
);
openMenu(wrapper);
@@ -128,7 +130,7 @@ describe('MultiSelect.Filterable', () => {
it('should let items stay at their position after selecting', () => {
const wrapper = mount(
-
+
);
openMenu(wrapper);
@@ -149,7 +151,7 @@ describe('MultiSelect.Filterable', () => {
});
it('should not clear input value after a user makes a selection', () => {
- const wrapper = mount();
+ const wrapper = mount();
openMenu(wrapper);
wrapper
diff --git a/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js b/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js
index 69274c14bc32..7d512303d656 100644
--- a/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js
+++ b/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js
@@ -14,6 +14,9 @@ import MultiSelect from '../';
import { generateItems, generateGenericItem } from '../../ListBox/test-helpers';
describe('MultiSelect', () => {
+ beforeEach(() => {
+ jest.mock('../../../internal/deprecateFieldOnObject');
+ });
afterEach(cleanup);
describe.skip('automated accessibility tests', () => {
diff --git a/packages/react/src/components/MultiSelect/__tests__/__snapshots__/FilterableMultiSelect-test.js.snap b/packages/react/src/components/MultiSelect/__tests__/__snapshots__/FilterableMultiSelect-test.js.snap
index 28d3d3e6b560..92728708ec62 100644
--- a/packages/react/src/components/MultiSelect/__tests__/__snapshots__/FilterableMultiSelect-test.js.snap
+++ b/packages/react/src/components/MultiSelect/__tests__/__snapshots__/FilterableMultiSelect-test.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`MultiSelect.Filterable should render 1`] = `
-
-
+
`;
diff --git a/packages/react/src/components/MultiSelect/index.js b/packages/react/src/components/MultiSelect/index.js
index 112c85925dc6..c61b45aec598 100644
--- a/packages/react/src/components/MultiSelect/index.js
+++ b/packages/react/src/components/MultiSelect/index.js
@@ -4,6 +4,7 @@
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
+import { deprecateFieldOnObject } from '../../internal/deprecateFieldOnObject';
import MultiSelect from './MultiSelect';
import FilterableMultiSelect from './FilterableMultiSelect';
@@ -11,4 +12,8 @@ import FilterableMultiSelect from './FilterableMultiSelect';
FilterableMultiSelect.displayName = 'MultiSelect.Filterable';
MultiSelect.Filterable = FilterableMultiSelect;
+if (__DEV__) {
+ deprecateFieldOnObject(MultiSelect, 'Filterable', FilterableMultiSelect);
+}
+
export default MultiSelect;
diff --git a/packages/react/src/components/PasswordInput/index.js b/packages/react/src/components/PasswordInput/index.js
new file mode 100644
index 000000000000..1f07aa31bc41
--- /dev/null
+++ b/packages/react/src/components/PasswordInput/index.js
@@ -0,0 +1,2 @@
+export ControlledPasswordInput from '../TextInput/ControlledPasswordInput';
+export PasswordInput from '../TextInput/PasswordInput';
diff --git a/packages/react/src/components/TextInput/TextInput-story.js b/packages/react/src/components/TextInput/TextInput-story.js
index 10b6f7610129..51b58a98d4b7 100644
--- a/packages/react/src/components/TextInput/TextInput-story.js
+++ b/packages/react/src/components/TextInput/TextInput-story.js
@@ -8,7 +8,7 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, select, text } from '@storybook/addon-knobs';
-import TextInput from '../TextInput';
+import { TextInput } from '../../index';
import TextInputSkeleton from '../TextInput/TextInput.Skeleton';
import FluidForm from '../FluidForm/FluidForm';
import mdx from './TextInput.mdx';
diff --git a/packages/react/src/components/TextInput/index.js b/packages/react/src/components/TextInput/index.js
index 58347c19513b..a4bad4443c14 100644
--- a/packages/react/src/components/TextInput/index.js
+++ b/packages/react/src/components/TextInput/index.js
@@ -4,8 +4,23 @@
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
+import { deprecateFieldOnObject } from '../../internal/deprecateFieldOnObject';
export * from './TextInput.Skeleton';
-export ControlledPasswordInput from './ControlledPasswordInput';
-export PasswordInput from './PasswordInput';
-export default from './TextInput';
+import ControlledPasswordInput from './ControlledPasswordInput';
+import PasswordInput from './PasswordInput';
+import TextInput from './TextInput';
+
+TextInput.ControlledPasswordInput = ControlledPasswordInput;
+TextInput.PasswordInput = PasswordInput;
+
+if (__DEV__) {
+ deprecateFieldOnObject(
+ TextInput,
+ 'ControlledPasswordInput',
+ ControlledPasswordInput
+ );
+ deprecateFieldOnObject(TextInput, 'PasswordInput', PasswordInput);
+}
+
+export default TextInput;
diff --git a/packages/react/src/index.js b/packages/react/src/index.js
index 2f92fb3ff5a7..449904a87a84 100644
--- a/packages/react/src/index.js
+++ b/packages/react/src/index.js
@@ -4,7 +4,6 @@
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
-
export Accordion from './components/Accordion';
export AccordionItem from './components/AccordionItem';
export { AspectRatio } from './components/AspectRatio';
@@ -86,6 +85,10 @@ export OverflowMenu from './components/OverflowMenu';
export OverflowMenuItem from './components/OverflowMenuItem';
export Pagination from './components/Pagination';
export PaginationNav from './components/PaginationNav';
+export {
+ ControlledPasswordInput,
+ PasswordInput,
+} from './components/PasswordInput';
export PrimaryButton from './components/PrimaryButton';
export {
ProgressIndicator,
diff --git a/packages/react/src/internal/__mocks__/deprecateFieldOnObject.js b/packages/react/src/internal/__mocks__/deprecateFieldOnObject.js
new file mode 100644
index 000000000000..a1888682b4a7
--- /dev/null
+++ b/packages/react/src/internal/__mocks__/deprecateFieldOnObject.js
@@ -0,0 +1,10 @@
+/**
+ * Copyright IBM Corp. 2016, 2018
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+module.exports = {
+ deprecateFieldOnObject: jest.fn(),
+};
diff --git a/packages/react/src/internal/deprecateFieldOnObject.js b/packages/react/src/internal/deprecateFieldOnObject.js
new file mode 100644
index 000000000000..43090160295b
--- /dev/null
+++ b/packages/react/src/internal/deprecateFieldOnObject.js
@@ -0,0 +1,32 @@
+/**
+ * Copyright IBM Corp. 2016, 2018
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import { warning } from '../internal/warning';
+
+const didWarnAboutDeprecation = {};
+
+function deprecateFieldOnObject(object, field, Component, message) {
+ Object.defineProperty(object, field, {
+ enumerable: true,
+ get() {
+ if (!didWarnAboutDeprecation[field]) {
+ warning(
+ false,
+ message ||
+ `The ${field} field has been deprecated on the ${object.displayName} object. ` +
+ `Please import and use ${
+ Component.displayName || Component.name || 'the field'
+ } directly.`
+ );
+ didWarnAboutDeprecation[field] = true;
+ }
+
+ return Component;
+ },
+ });
+}
+
+export { deprecateFieldOnObject };