-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react): add unstable_Stack component (#9876)
* feat(react): add unstable_Stack component * refactor(stack): update api and add tests * chore(react): update entrypoints with unstable_Stack * Update packages/react/src/components/Stack/index.js Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
- Loading branch information
1 parent
4c044be
commit 18ce43c
Showing
16 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
74
packages/react/src/components/Stack/__tests__/Stack-test.e2e.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
88 changes: 88 additions & 0 deletions
88
packages/react/src/components/Stack/__tests__/Stack-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* 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 { render } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { HStack, Stack, VStack } from '../../Stack'; | ||
|
||
describe('Stack', () => { | ||
it('should support alternate element types with the `as` prop', () => { | ||
const { container } = render( | ||
<Stack as="section"> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</Stack> | ||
); | ||
|
||
expect(container.firstChild.tagName).toBe('SECTION'); | ||
}); | ||
|
||
it('should support a custom className with the `className` prop', () => { | ||
const { container } = render( | ||
<Stack className="test"> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</Stack> | ||
); | ||
|
||
expect(container.firstChild).toHaveClass('test'); | ||
}); | ||
|
||
it('should apply additional props to the outermost element', () => { | ||
const { container } = render( | ||
<Stack data-testid="test"> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</Stack> | ||
); | ||
|
||
expect(container.firstChild).toHaveAttribute('data-testid', 'test'); | ||
}); | ||
|
||
it('should forward the given ref to the outermost element', () => { | ||
const ref = jest.fn(); | ||
const { container } = render( | ||
<Stack ref={ref}> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</Stack> | ||
); | ||
expect(ref).toHaveBeenCalledWith(container.firstChild); | ||
}); | ||
|
||
describe('HStack', () => { | ||
it('should forward the given ref to the outermost element', () => { | ||
const ref = jest.fn(); | ||
const { container } = render( | ||
<HStack ref={ref}> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</HStack> | ||
); | ||
expect(ref).toHaveBeenCalledWith(container.firstChild); | ||
}); | ||
}); | ||
|
||
describe('VStack', () => { | ||
it('should forward the given ref to the outermost element', () => { | ||
const ref = jest.fn(); | ||
const { container } = render( | ||
<VStack ref={ref}> | ||
<article>one</article> | ||
<article>two</article> | ||
<article>three</article> | ||
</VStack> | ||
); | ||
expect(ref).toHaveBeenCalledWith(container.firstChild); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.