diff --git a/docs/publishing.md b/docs/publishing.md index 97fdc607419f..3df3938f33fc 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -7,6 +7,7 @@ - [Pre-release](#pre-release) - [Release](#release) +- [Publishing older library versions](#publishing-older-library-versions) - [FAQ](#faq) - [How do I fix the repo state if I cancel during a publish?](#how-do-i-fix-the-repo-state-if-i-cancel-during-a-publish) @@ -38,6 +39,68 @@ 8. Run `./tasks/publish.sh ---exact --conventional-commits --github-release --git-remote upstream` +## Publishing older library versions + +We offer ad-hoc backwards-support for older version of the system. This work is +primarly driven by external contributors who may still need these older versions +for legacy code. When updates are received and merged into the codebase, the +release process will be a bit different than the one described above. + +For example, with +[`carbon-components-react`](https://github.com/carbon-design-system/carbon-components-react) +we have specific branches for older major versions like `v5` or `v6`. If we +wanted to publish an update to either of these major versions, this process +would look like: + +- Checkout the branch locally, making sure to pull in the latest from upstream +- Manually update `package.json` with the new version to publish in a branch + called `chore/release-vX.Y.Z` and a commit message: `chore(release): vX.Y.Z` +- Create a Pull Request with this new branch and commit message +- Once this is merged into the branch, checkout locally and pull latest. Now we + can publish by running `npm publish .`, if you want to do a dry run first you + can do `npm publish . --dry-run`. This is helpful when dependencies may be + different than in newer versions of the system + +One important thing to verify is that `package.json` has a `publishConfig` field +that looks like the following: + +```json +{ + "publishConfig": { + "tag": ".x" + } +} +``` + +For example, `carbon-components-react` v5 would look like: + +```json +{ + "publishConfig": { + "tag": "5.x" + } +} +``` + +This tag verifies that when we publish we do not publish to the `latest` tag but +instead to the major-specific tag for the package. + +After running `npm publish .` and seeing the package publish to the registry, +you could create a git tag by running: + +```bash +git tag -a vX.Y.Z # The commit message should match vX.Y.Z +``` + +You should then push this tag to the project by running: + +```bash +git push upstream vX.Y.Z +``` + +This helps keep track of what versions have been published and snapshotting the +code at that point in time. + ## FAQ #### How do I fix the repo state if I cancel during a publish? diff --git a/packages/components/src/globals/scss/_layout.scss b/packages/components/src/globals/scss/_layout.scss index 39c30d47b85b..f06a300d3d24 100644 --- a/packages/components/src/globals/scss/_layout.scss +++ b/packages/components/src/globals/scss/_layout.scss @@ -13,7 +13,7 @@ $z-indexes: ( modal: 9000, overlay: 8000, - dropdown: 7000, + dropdown: 9100, header: 6000, footer: 5000, hidden: - 1, diff --git a/packages/react/.storybook/Container.js b/packages/react/.storybook/Container.js index 18b11df7d3c0..92479895e3ea 100644 --- a/packages/react/.storybook/Container.js +++ b/packages/react/.storybook/Container.js @@ -1,7 +1,9 @@ import React, { Component } from 'react'; import './polyfills'; import './_container.scss'; +import { settings } from 'carbon-components'; +const { prefix } = settings; export default class Container extends Component { componentDidMount() { if (process.env.CARBON_REACT_STORYBOOK_USE_RTL === 'true') { @@ -28,7 +30,7 @@ export default class Container extends Component { ); diff --git a/packages/react/.storybook/_container.scss b/packages/react/.storybook/_container.scss index a61ac81c70de..a00d94d2747e 100644 --- a/packages/react/.storybook/_container.scss +++ b/packages/react/.storybook/_container.scss @@ -1,4 +1,5 @@ $css--font-face: true; $css--reset: true; +$prefix: 'bx'; @import '~carbon-components/src/globals/scss/styles.scss'; diff --git a/packages/react/.storybook/settings.js b/packages/react/.storybook/settings.js new file mode 100644 index 000000000000..d6d2f9c707dc --- /dev/null +++ b/packages/react/.storybook/settings.js @@ -0,0 +1,4 @@ +const settings = { + prefix: 'bx', +}; +module.exports = settings; diff --git a/packages/react/.storybook/webpack.config.js b/packages/react/.storybook/webpack.config.js index e3aa10cfdc5b..73a0d62424f5 100644 --- a/packages/react/.storybook/webpack.config.js +++ b/packages/react/.storybook/webpack.config.js @@ -45,6 +45,25 @@ const styleLoaders = [ }, ]; +class FeatureFlagProxyPlugin { + /** + * A WebPack resolver plugin that proxies module request + * for `carbon-components/es/globals/js/settings` to `./settings`. + */ + constructor() { + this.source = 'before-described-relative'; + } + + apply(resolver) { + resolver.plugin(this.source, (request, callback) => { + if (/[\\/]globals[\\/]js[\\/]settings$/.test(request.path)) { + request.path = path.resolve(__dirname, './settings'); + } + callback(); + }); + } +} + module.exports = (baseConfig, env, defaultConfig) => { defaultConfig.devtool = useStyleSourceMap ? 'source-map' : ''; defaultConfig.optimization = { @@ -96,5 +115,10 @@ module.exports = (baseConfig, env, defaultConfig) => { ); } + defaultConfig.resolve = { + modules: ['node_modules'], + plugins: [new FeatureFlagProxyPlugin()], + }; + return defaultConfig; }; diff --git a/packages/react/src/components/Accordion/Accordion-test.js b/packages/react/src/components/Accordion/Accordion-test.js index 713fa86d670b..04fe39f63f83 100644 --- a/packages/react/src/components/Accordion/Accordion-test.js +++ b/packages/react/src/components/Accordion/Accordion-test.js @@ -10,6 +10,9 @@ import Accordion from '../Accordion'; import AccordionSkeleton from '../Accordion/Accordion.Skeleton'; import SkeletonText from '../SkeletonText'; import { shallow, mount } from 'enzyme'; +import { settings } from 'carbon-components'; + +const { prefix } = settings; describe('Accordion', () => { describe('Renders as expected', () => { @@ -24,7 +27,7 @@ describe('Accordion', () => { }); it('has the expected classes', () => { - expect(wrapper.hasClass('bx--accordion')).toEqual(true); + expect(wrapper.hasClass(`${prefix}--accordion`)).toEqual(true); }); it('renders extra classes passed in via className', () => { @@ -38,8 +41,8 @@ describe('AccordionSkeleton', () => { const wrapper = shallow(); it('Has the expected classes', () => { - expect(wrapper.hasClass('bx--skeleton')).toEqual(true); - expect(wrapper.hasClass('bx--accordion')).toEqual(true); + expect(wrapper.hasClass(`${prefix}--skeleton`)).toEqual(true); + expect(wrapper.hasClass(`${prefix}--accordion`)).toEqual(true); }); it('Renders first item as expected', () => { @@ -55,12 +58,12 @@ describe('AccordionSkeleton', () => { it('Renders number of items as expected', () => { const fullWrapper = mount(); - expect(fullWrapper.find('.bx--accordion__item')).toHaveLength(4); + expect(fullWrapper.find(`.${prefix}--accordion__item`)).toHaveLength(4); }); it('Renders custom number of items', () => { const fullWrapper = mount(); - expect(fullWrapper.find('.bx--accordion__item')).toHaveLength(8); + expect(fullWrapper.find(`.${prefix}--accordion__item`)).toHaveLength(8); }); }); }); diff --git a/packages/react/src/components/AccordionItem/AccordionItem-test.js b/packages/react/src/components/AccordionItem/AccordionItem-test.js index 926c11aa12e0..8c26699b3414 100644 --- a/packages/react/src/components/AccordionItem/AccordionItem-test.js +++ b/packages/react/src/components/AccordionItem/AccordionItem-test.js @@ -9,6 +9,9 @@ import React from 'react'; import AccordionItem from '../AccordionItem'; import ChevronRight16 from '@carbon/icons-react/lib/chevron--right/16'; import { shallow, mount } from 'enzyme'; +import { settings } from 'carbon-components'; + +const { prefix } = settings; describe('AccordionItem', () => { describe('Renders as expected', () => { @@ -19,27 +22,31 @@ describe('AccordionItem', () => { ); it('renders children as expected', () => { - expect(wrapper.find('.bx--accordion__content').text()).toBe( + expect(wrapper.find(`.${prefix}--accordion__content`).text()).toBe( 'Lorem ipsum.' ); }); it('renders heading as expected', () => { - const heading = wrapper.find('.bx--accordion__heading'); + const heading = wrapper.find(`.${prefix}--accordion__heading`); const icon = ChevronRight16; expect(heading.length).toBe(1); expect(heading.find(icon).length).toBe(1); - expect(heading.find('.bx--accordion__title').text()).toBe('A heading'); + expect(heading.find(`.${prefix}--accordion__title`).text()).toBe( + 'A heading' + ); }); it('should use correct icon', () => { - const heading = wrapper.find('.bx--accordion__heading'); + const heading = wrapper.find(`.${prefix}--accordion__heading`); expect(heading.find(ChevronRight16).length).toBe(1); }); it('has the expected classes', () => { - expect(wrapper.hasClass('bx--accordion__item')).toEqual(true); - expect(wrapper.hasClass('bx--accordion__item--active')).toEqual(false); + expect(wrapper.hasClass(`${prefix}--accordion__item`)).toEqual(true); + expect(wrapper.hasClass(`${prefix}--accordion__item--active`)).toEqual( + false + ); }); it('renders extra classes passed in via className', () => { @@ -52,7 +59,9 @@ describe('AccordionItem', () => { Lorem ipsum. ); - expect(openItem.hasClass('bx--accordion__item--active')).toEqual(true); + expect(openItem.hasClass(`${prefix}--accordion__item--active`)).toEqual( + true + ); expect(openItem.state().open).toEqual(true); openItem.setState({ open: true }); openItem.setProps({ open: false }); @@ -73,10 +82,12 @@ describe('AccordionItem', () => { it('should apply the active class when the state is open', () => { const toggler = mount(); const item = toggler.find('li'); - expect(item.hasClass('bx--accordion__item--active')).toEqual(false); + expect(item.hasClass(`${prefix}--accordion__item--active`)).toEqual( + false + ); toggler.setState({ open: true }); expect( - toggler.find('li').hasClass('bx--accordion__item--active') + toggler.find('li').hasClass(`${prefix}--accordion__item--active`) ).toEqual(true); }); }); @@ -94,10 +105,10 @@ describe('AccordionItem', () => { ); it('renders heading as expected', () => { - const heading = wrapper.find('.bx--accordion__heading'); + const heading = wrapper.find(`.${prefix}--accordion__heading`); expect(heading.length).toBe(1); expect(heading.find(ChevronRight16).length).toBe(1); - const title = heading.find('.bx--accordion__title'); + const title = heading.find(`.${prefix}--accordion__title`); expect(title.text()).toBe('A heading'); expect(title.find('h2').exists()).toEqual(true); expect(title.find('h2').hasClass('TitleClass')).toEqual(true); @@ -115,7 +126,7 @@ describe('AccordionItem', () => { const wrapper = mount( ); - const heading = wrapper.find('button.bx--accordion__heading'); + const heading = wrapper.find(`button.${prefix}--accordion__heading`); it('should call onClick', () => { wrapper.simulate('click'); @@ -132,7 +143,7 @@ describe('AccordionItem', () => { const toggler = mount( Lorem ipsum. ); - const heading = toggler.find('button.bx--accordion__heading'); + const heading = toggler.find(`button.${prefix}--accordion__heading`); it('should set state to open when clicked', () => { expect(toggler.state().open).toEqual(false); @@ -152,7 +163,7 @@ describe('AccordionItem', () => { ); - heading = toggler.find('button.bx--accordion__heading'); + heading = toggler.find(`button.${prefix}--accordion__heading`); }); it('should close open AccordionItem when using Esc', () => { diff --git a/packages/react/src/components/Breadcrumb/__tests__/Breadcrumb-test.js b/packages/react/src/components/Breadcrumb/__tests__/Breadcrumb-test.js index f1b727a4eb6c..82691c9ad85f 100644 --- a/packages/react/src/components/Breadcrumb/__tests__/Breadcrumb-test.js +++ b/packages/react/src/components/Breadcrumb/__tests__/Breadcrumb-test.js @@ -7,6 +7,9 @@ import React from 'react'; import { mount } from 'enzyme'; +import { settings } from 'carbon-components'; + +const { prefix } = settings; describe('Breadcrumb', () => { let Breadcrumb; @@ -63,7 +66,7 @@ describe('Breadcrumb', () => { expect(CustomComponent).toHaveBeenCalled(); expect(CustomComponent).toHaveBeenCalledWith( expect.objectContaining({ - className: 'bx--link', + className: `${prefix}--link`, }), {} ); diff --git a/packages/react/src/components/Button/Button-test.js b/packages/react/src/components/Button/Button-test.js index f524c118494b..e582ba985879 100644 --- a/packages/react/src/components/Button/Button-test.js +++ b/packages/react/src/components/Button/Button-test.js @@ -11,6 +11,9 @@ import Button from '../Button'; import Link from '../Link'; import ButtonSkeleton from '../Button/Button.Skeleton'; import { shallow, mount } from 'enzyme'; +import { settings } from 'carbon-components'; + +const { prefix } = settings; describe('Button', () => { describe('Renders common props as expected', () => { @@ -123,7 +126,7 @@ describe('Button', () => { const icon = iconButton.find('svg'); it('should have the appropriate icon', () => { - expect(icon.hasClass('bx--btn__icon')).toBe(true); + expect(icon.hasClass(`${prefix}--btn__icon`)).toBe(true); }); it('should return error if icon given without description', () => { @@ -148,7 +151,7 @@ describe('Button', () => { const icon = iconButton.find('svg'); it('should have the appropriate icon', () => { - expect(icon.hasClass('bx--btn__icon')).toBe(true); + expect(icon.hasClass(`${prefix}--btn__icon`)).toBe(true); expect(icon.find(':not(svg):not(title)').html()).toBe( originalIcon.children().html() ); @@ -172,7 +175,7 @@ describe('Primary Button', () => { const wrapper = shallow(