diff --git a/src/Container/Container.test.jsx b/src/Container/Container.test.tsx similarity index 62% rename from src/Container/Container.test.jsx rename to src/Container/Container.test.tsx index b3a6faaf7c..82efafa24b 100644 --- a/src/Container/Container.test.jsx +++ b/src/Container/Container.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { render } from '@testing-library/react'; -import Container from '.'; +import Container, { type ContainerSize } from '.'; -const getClassNames = (container) => container.className.split(' '); +const getClassNames = (container: HTMLElement) => container.className.split(' '); describe('', () => { it('displays children', () => { @@ -12,32 +12,38 @@ describe('', () => { it('adds the .container-fluid class', () => { const { container } = render(Content); - const containerElement = container.firstChild; + const containerElement = container.firstChild as HTMLElement; expect(getClassNames(containerElement)).toContain('container-fluid'); }); it('adds the .container class', () => { const { container } = render(Content); - const containerElement = container.firstChild; - expect(getClassNames(containerElement)).toContain('container'); + const containerElement = container.firstChild as HTMLElement; + expect(getClassNames(containerElement!)).toContain('container'); expect(getClassNames(containerElement)).not.toContain('container-fluid'); }); - ['xs', 'sm', 'md', 'lg', 'xl'].forEach((size) => { + ['xs', 'sm', 'md', 'lg', 'xl'].forEach((size: ContainerSize) => { it(`adds the .container-mw-${size} class`, () => { const { container } = render(Content); - const containerElement = container.firstChild; + const containerElement = container.firstChild as HTMLElement; expect(getClassNames(containerElement)).toContain(`container-mw-${size}`); }); }); + it('does not add a size class when size is not specified', () => { + const { container } = render(Content); + const containerElement = container.firstChild as HTMLElement; + expect(getClassNames(containerElement)).toEqual(['container-fluid']); + }); + it('preserves custom class names', () => { const { container } = render( Content , ); - const containerElement = container.firstChild; + const containerElement = container.firstChild as HTMLElement; expect(getClassNames(containerElement)).toContain('container-mw-md'); expect(getClassNames(containerElement)).toContain('container-fluid'); expect(getClassNames(containerElement)).toContain('custom-class'); diff --git a/src/Container/README.md b/src/Container/README.md index de2bdab76c..8c2f6a4775 100644 --- a/src/Container/README.md +++ b/src/Container/README.md @@ -19,6 +19,10 @@ The base container to contain, pad, and center content in the viewport. This com ```jsx live
+ + The content in this container doesn't have a max width + + The content in this container won't exceed the extra large width. diff --git a/src/Container/index.jsx b/src/Container/index.jsx deleted file mode 100644 index a2f38de7bb..0000000000 --- a/src/Container/index.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { forwardRef } from 'react'; -import classNames from 'classnames'; -import RBContainer from 'react-bootstrap/Container'; -import PropTypes from 'prop-types'; - -const SIZE_CLASS_NAMES = { - xs: 'container-mw-xs', - sm: 'container-mw-sm', - md: 'container-mw-md', - lg: 'container-mw-lg', - xl: 'container-mw-xl', -}; - -const Container = forwardRef(({ size, children, ...props }, ref) => ( - - {children} - -)); - -Container.propTypes = { - ...RBContainer.propTypes, - /** Override the base element */ - as: PropTypes.elementType, - /** Specifies the contents of the container */ - children: PropTypes.node, - /** Fill all available space at any breakpoint */ - fluid: PropTypes.bool, - /** Set the maximum width for the container */ - size: PropTypes.oneOf(Object.keys(SIZE_CLASS_NAMES)), - /** Overrides underlying component base CSS class name */ - bsPrefix: PropTypes.string, -}; - -Container.defaultProps = { - as: 'div', - children: undefined, - fluid: true, - size: undefined, - bsPrefix: 'container', -}; - -export default Container; diff --git a/src/Container/index.tsx b/src/Container/index.tsx new file mode 100644 index 0000000000..ea95d58867 --- /dev/null +++ b/src/Container/index.tsx @@ -0,0 +1,64 @@ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import RBContainer, { type ContainerProps as RBContainerProps } from 'react-bootstrap/Container'; + +import type { ComponentWithAsProp } from '../utils/types/bootstrap'; + +enum ContainerSizeClass { + xs = 'container-mw-xs', + sm = 'container-mw-sm', + md = 'container-mw-md', + lg = 'container-mw-lg', + xl = 'container-mw-xl', +} + +export type ContainerSize = keyof typeof ContainerSizeClass; + +interface ContainerProps extends RBContainerProps { + size?: ContainerSize; +} + +type ContainerType = ComponentWithAsProp<'div', ContainerProps>; + +const Container: ContainerType = React.forwardRef(({ + size, + children, + ...props +}, ref) => ( + + {children} + +)); + +Container.propTypes = { + ...RBContainer.propTypes, + /** Override the base element */ + as: PropTypes.elementType, + /** Specifies the contents of the container */ + children: PropTypes.node, + /** Fill all available space at any breakpoint */ + fluid: PropTypes.bool, + /** Set the maximum width for the container. Omiting the prop will remove the max-width */ + size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), + /** Overrides underlying component base CSS class name */ + bsPrefix: PropTypes.string, +}; + +Container.defaultProps = { + as: 'div', + children: undefined, + fluid: true, + size: undefined, + bsPrefix: 'container', +}; + +export default Container; diff --git a/src/index.d.ts b/src/index.d.ts index a680a1ac86..f28715349d 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -8,6 +8,7 @@ export { default as Bubble } from './Bubble'; export { default as Button, ButtonGroup, ButtonToolbar } from './Button'; export { default as Chip, CHIP_PGN_CLASS } from './Chip'; export { default as ChipCarousel } from './ChipCarousel'; +export { default as Container, ContainerSize } from './Container'; export { default as Hyperlink, HYPER_LINK_EXTERNAL_LINK_ALT_TEXT, HYPER_LINK_EXTERNAL_LINK_TITLE } from './Hyperlink'; export { default as Icon } from './Icon'; export { default as IconButton, IconButtonWithTooltip } from './IconButton'; @@ -41,7 +42,6 @@ export const export const CheckBox: any; // from './CheckBox'; export const CheckBoxGroup: any; // from './CheckBoxGroup'; export const CloseButton: any; // from './CloseButton'; -export const Container: any; // from './Container'; export const Layout: any, Col: any, Row: any; // from './Layout'; export const Collapse: any; // from './Collapse'; export const Collapsible: any; // from './Collapsible'; diff --git a/www/src/context/SettingsContext.tsx b/www/src/context/SettingsContext.tsx index 777c3a73bb..156ae8f3f3 100644 --- a/www/src/context/SettingsContext.tsx +++ b/www/src/context/SettingsContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; import { IntlProvider } from 'react-intl'; -import { messages } from '~paragon-react'; +import { messages, type ContainerSize } from '~paragon-react'; import { THEMES, DEFAULT_THEME } from '../../theme-config'; import { SETTINGS_EVENTS, sendUserAnalyticsEvent } from '../../segment-events'; @@ -12,7 +12,7 @@ export interface IDefaultValue { theme?: string, direction?: string, language?: string, - containerWidth?: string, + containerWidth?: ContainerSize, }, theme?: string, handleSettingsChange: Function, @@ -35,7 +35,7 @@ function SettingsContextProvider({ children }) { theme: DEFAULT_THEME, direction: 'ltr', language: 'en', - containerWidth: 'md', + containerWidth: 'md' as ContainerSize, }); const [showSettings, setShowSettings] = useState(false);