Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): add unstable_Stack component #9876

Merged
2 changes: 2 additions & 0 deletions packages/carbon-react/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ module.exports = {
'./Welcome/Welcome.stories.js',
'../src/**/*.stories.js',
'../src/**/*.stories.mdx',
'../../react/src/components/Stack/*.stories.js',
'../../react/src/components/Stack/*.stories.mdx',
],
webpack(config) {
const babelLoader = config.module.rules.find((rule) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/carbon-react/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Array [
"FormItem",
"FormLabel",
"Grid",
"HStack",
"Header",
"HeaderContainer",
"HeaderGlobalAction",
Expand Down Expand Up @@ -134,6 +135,7 @@ Array [
"SkipToContent",
"Slider",
"SliderSkeleton",
"Stack",
"StructuredListBody",
"StructuredListCell",
"StructuredListHead",
Expand Down Expand Up @@ -197,6 +199,7 @@ Array [
"TooltipDefinition",
"TooltipIcon",
"UnorderedList",
"VStack",
"unstable_Heading",
"unstable_PageSelector",
"unstable_Pagination",
Expand Down
3 changes: 3 additions & 0 deletions packages/carbon-react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ export {
unstable_useContextMenu,
unstable_Heading,
unstable_Section,
unstable_HStack as HStack,
unstable_Stack as Stack,
unstable_VStack as VStack,
} from 'carbon-components-react';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/react/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module.exports = {
'./Welcome/Welcome.stories.js',
'../src/**/*-story.js',
'../src/**/*.stories.mdx',
'!../src/components/Stack',
],

webpack(config) {
Expand Down
61 changes: 61 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8936,5 +8936,66 @@ Map {
},
},
"unstable_usePrefix" => Object {},
"unstable_HStack" => Object {
"$$typeof": Symbol(react.forward_ref),
"render": [Function],
},
"unstable_Stack" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"as": Object {
"type": "elementType",
},
"children": Object {
"type": "node",
},
"className": Object {
"type": "string",
},
"gap": Object {
"args": Array [
Array [
Object {
"type": "string",
},
Object {
"args": Array [
Array [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
],
],
"type": "oneOf",
},
],
],
"type": "oneOfType",
},
"orientation": Object {
"args": Array [
Array [
"horizontal",
"vertical",
],
],
"type": "oneOf",
},
},
"render": [Function],
},
"unstable_VStack" => Object {
"$$typeof": Symbol(react.forward_ref),
"render": [Function],
},
}
`;
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"dependencies": {
"@carbon/feature-flags": "^0.6.0",
"@carbon/icons-react": "^10.41.0",
"@carbon/layout": "^10.33.0",
"@carbon/telemetry": "0.0.0-alpha.6",
"classnames": "2.3.1",
"copy-to-clipboard": "^3.3.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ Array [
"TooltipIcon",
"UnorderedList",
"unstable_FeatureFlags",
"unstable_HStack",
"unstable_Heading",
"unstable_Menu",
"unstable_MenuDivider",
Expand All @@ -211,8 +212,10 @@ Array [
"unstable_Pagination",
"unstable_ProgressBar",
"unstable_Section",
"unstable_Stack",
"unstable_TreeNode",
"unstable_TreeView",
"unstable_VStack",
"unstable_useContextMenu",
"unstable_useFeatureFlag",
"unstable_useFeatureFlags",
Expand Down
100 changes: 100 additions & 0 deletions packages/react/src/components/Stack/Stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* 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 { spacing } from '@carbon/layout';
import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { usePrefix } from '../../internal/usePrefix';

/**
* The steps in the spacing scale
* @type {Array<number>}
*/
const SPACING_STEPS = Array.from({ length: spacing.length - 1 }).map(
(_, step) => {
return step + 1;
}
);

/**
* The Stack component is a useful layout utility in a component-based model.
* This allows components to not use margin and instead delegate the
* responsibility of positioning and layout to parent components.
*
* In the case of the Stack component, it uses the spacing scale from the
* Design Language in order to determine how much space there should be between
* items rendered by the Stack component. It also supports a custom `gap` prop
* which will allow a user to provide a custom value for the gap of the layout.
*
* This component supports both horizontal and vertical orientations.
*
* Inspiration for this component:
*
* - https://paste.twilio.design/layout/stack/
* - https://github.com/Workday/canvas-kit/blob/f2f599654876700f483a1d8c5de82a41315c76f1/modules/labs-react/layout/lib/Stack.tsx
*/
const Stack = React.forwardRef(function Stack(props, ref) {
const {
as: BaseComponent = 'div',
children,
className: customClassName,
gap,
orientation = 'vertical',
...rest
} = props;
const prefix = usePrefix();
const className = cx(customClassName, {
[`${prefix}--stack-${orientation}`]: true,
[`${prefix}--stack-scale-${gap}`]: typeof gap === 'number',
});
const style = {};

if (typeof gap === 'string') {
style[`--${prefix}-stack-gap`] = gap;
}

return (
<BaseComponent {...rest} ref={ref} className={className} style={style}>
{children}
</BaseComponent>
);
});

Stack.propTypes = {
/**
* Provide a custom element type to render as the outermost element in
* the Stack component. By default, this component will render a `div`.
*/
as: PropTypes.elementType,

/**
* Provide the elements that will be rendered as children inside of the Stack
* component. These elements will have having spacing between them according
* to the `step` and `orientation` prop
*/
children: PropTypes.node,

/**
* Provide a custom class name to be used by the outermost element rendered by
* Stack
*/
className: PropTypes.string,

/**
* Provide either a custom value or a step from the spacing scale to be used
* as the gap in the layout
*/
gap: PropTypes.oneOfType([PropTypes.string, PropTypes.oneOf(SPACING_STEPS)]),

/**
* Specify the orientation of them items in the Stack
*/
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
};

export { Stack };
74 changes: 74 additions & 0 deletions packages/react/src/components/Stack/__tests__/Stack-test.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* 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 '@carbon/styles/scss/components/stack/_index.scss';

import { mount } from '@cypress/react';
import { spacing } from '@carbon/layout';
import React from 'react';
import { Stack } from '../../Stack';
import { PrefixContext } from '../../../internal/usePrefix';

const SPACING_STEPS = Array.from({ length: spacing.length - 1 }).map(
(_, step) => {
return step + 1;
}
);

describe('Stack', () => {
it('should default to the vertical orientation', () => {
mount(
<PrefixContext.Provider value="cds">
{SPACING_STEPS.map((step) => {
return (
<Stack key={step} gap={step}>
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
</Stack>
);
})}
</PrefixContext.Provider>
);

cy.percySnapshot();
});

it('should support a horizontal orientation', () => {
mount(
<PrefixContext.Provider value="cds">
{SPACING_STEPS.map((step) => {
return (
<div key={step}>
<Stack gap={step} orientation="horizontal">
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
</Stack>
</div>
);
})}
</PrefixContext.Provider>
);

cy.percySnapshot();
});

it('should support a custom gap with the `gap` prop', () => {
mount(
<PrefixContext.Provider value="cds">
<Stack gap="20px">
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
</Stack>
</PrefixContext.Provider>
);

cy.percySnapshot();
});
});
Loading