@telus-uds/components-web 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/lib/Image/Image.js +126 -0
  3. package/lib/Image/index.js +13 -0
  4. package/lib/Modal/Modal.js +142 -0
  5. package/lib/Modal/ModalContent.js +195 -0
  6. package/lib/Modal/index.js +13 -0
  7. package/lib/Paragraph/Paragraph.js +107 -0
  8. package/lib/Paragraph/index.js +13 -0
  9. package/lib/Table/Body.js +29 -0
  10. package/lib/Table/Cell.js +200 -0
  11. package/lib/Table/Header.js +39 -0
  12. package/lib/Table/Row.js +35 -0
  13. package/lib/Table/SubHeading.js +37 -0
  14. package/lib/Table/Table.js +121 -0
  15. package/lib/Table/index.js +28 -0
  16. package/lib/Toast/Toast.js +136 -0
  17. package/lib/Toast/index.js +13 -0
  18. package/lib/WaffleGrid/WaffleGrid.js +176 -0
  19. package/lib/WaffleGrid/index.js +13 -0
  20. package/lib/baseExports.js +0 -6
  21. package/lib/index.js +55 -1
  22. package/lib-module/Image/Image.js +108 -0
  23. package/lib-module/Image/index.js +2 -0
  24. package/lib-module/Modal/Modal.js +128 -0
  25. package/lib-module/Modal/ModalContent.js +174 -0
  26. package/lib-module/Modal/index.js +2 -0
  27. package/lib-module/Paragraph/Paragraph.js +89 -0
  28. package/lib-module/Paragraph/index.js +2 -0
  29. package/lib-module/Table/Body.js +17 -0
  30. package/lib-module/Table/Cell.js +176 -0
  31. package/lib-module/Table/Header.js +22 -0
  32. package/lib-module/Table/Row.js +19 -0
  33. package/lib-module/Table/SubHeading.js +20 -0
  34. package/lib-module/Table/Table.js +93 -0
  35. package/lib-module/Table/index.js +12 -0
  36. package/lib-module/Toast/Toast.js +117 -0
  37. package/lib-module/Toast/index.js +2 -0
  38. package/lib-module/WaffleGrid/WaffleGrid.js +155 -0
  39. package/lib-module/WaffleGrid/index.js +2 -0
  40. package/lib-module/baseExports.js +1 -1
  41. package/lib-module/index.js +6 -0
  42. package/package.json +4 -3
  43. package/src/Image/Image.jsx +95 -0
  44. package/src/Image/index.js +3 -0
  45. package/src/Modal/Modal.jsx +111 -0
  46. package/src/Modal/ModalContent.jsx +161 -0
  47. package/src/Modal/index.js +3 -0
  48. package/src/Paragraph/Paragraph.jsx +79 -0
  49. package/src/Paragraph/index.js +3 -0
  50. package/src/Table/Body.jsx +12 -0
  51. package/src/Table/Cell.jsx +148 -0
  52. package/src/Table/Header.jsx +17 -0
  53. package/src/Table/Row.jsx +18 -0
  54. package/src/Table/SubHeading.jsx +17 -0
  55. package/src/Table/Table.jsx +90 -0
  56. package/src/Table/index.js +14 -0
  57. package/src/Toast/Toast.jsx +151 -0
  58. package/src/Toast/index.js +3 -0
  59. package/src/WaffleGrid/WaffleGrid.jsx +137 -0
  60. package/src/WaffleGrid/index.js +3 -0
  61. package/src/baseExports.js +0 -1
  62. package/src/index.js +6 -0
  63. package/types/Cell.d.ts +13 -0
  64. package/types/Table.d.ts +12 -0
@@ -0,0 +1,161 @@
1
+ /* eslint-disable react/jsx-filename-extension */
2
+ import React, { useState } from 'react'
3
+ import {
4
+ Box,
5
+ Button,
6
+ Spacer,
7
+ TextButton,
8
+ Typography,
9
+ useThemeTokens,
10
+ useViewport
11
+ } from '@telus-uds/components-base'
12
+ import styled from 'styled-components'
13
+ import PropTypes from 'prop-types'
14
+
15
+ const StyledModalContent = styled.div`
16
+ display: flex;
17
+ flex-direction: column;
18
+ min-height: 100%;
19
+ `
20
+
21
+ const StyledHeading = styled.header`
22
+ display: flex;
23
+ flex-direction: column;
24
+ padding-right: ${({ paddingRight }) => paddingRight}px;
25
+ `
26
+
27
+ const StyledSubHeading = styled.div`
28
+ margin-top: ${({ marginTop }) => marginTop}px;
29
+ `
30
+
31
+ const StyledFooter = styled.footer(
32
+ ({
33
+ hasBorder,
34
+ viewport,
35
+ paddingLeft,
36
+ paddingRight,
37
+ paddingTop,
38
+ marginLeft,
39
+ marginRight,
40
+ borderColor,
41
+ gap
42
+ }) => ({
43
+ display: 'flex',
44
+ flexDirection: viewport === 'xs' || viewport === 'sm' ? 'column' : 'row',
45
+ alignItems: 'center',
46
+ gap: `${gap}px`,
47
+ marginLeft: `calc(-1 * ${marginLeft}px)`,
48
+ marginRight: `calc(-1 * ${marginRight}px)`,
49
+ paddingLeft: `${paddingLeft}px`,
50
+ paddingRight: `${paddingRight}px`,
51
+ paddingTop: `${paddingTop}px`,
52
+ borderTop: hasBorder ? `1px solid ${borderColor}` : 'none'
53
+ })
54
+ )
55
+
56
+ const ModalContent = ({
57
+ heading,
58
+ headingLevel = 'h3',
59
+ subHeading,
60
+ subHeadingSize = 'medium',
61
+ bodyText,
62
+ confirmButtonText,
63
+ confirmButtonVariant = { priority: 'high' },
64
+ onConfirm,
65
+ tokens,
66
+ variant,
67
+ cancelButtonText,
68
+ cancelButtonType: CancelButton = TextButton,
69
+ onCancel
70
+ }) => {
71
+ const { headingColor, cancelButtonColor, ...themeTokens } = useThemeTokens(
72
+ 'Modal',
73
+ tokens,
74
+ variant
75
+ )
76
+ const [scrollContainerHeight, setScrollContainerHeight] = useState(0)
77
+ const [scrollContentHeight, setScrollContentHeight] = useState(0)
78
+ const viewport = useViewport()
79
+
80
+ const handleContainerLayout = ({
81
+ nativeEvent: {
82
+ layout: { height }
83
+ }
84
+ }) => setScrollContainerHeight(height)
85
+ const onContentSizeChange = (_, height) => setScrollContentHeight(height)
86
+ const isContentOverflowing = scrollContentHeight > scrollContainerHeight
87
+
88
+ const hasConfirmButton = confirmButtonText !== undefined && onConfirm !== undefined
89
+ const hasCancelButton = cancelButtonText !== undefined && onCancel !== undefined
90
+ const hasHeadingSection = Boolean(heading || subHeading)
91
+
92
+ return (
93
+ <StyledModalContent>
94
+ {hasHeadingSection && (
95
+ <StyledHeading paddingRight={themeTokens.headingPaddingRight}>
96
+ {heading && (
97
+ <Typography
98
+ variant={{ size: headingLevel }}
99
+ tokens={{ color: headingColor }}
100
+ heading={headingLevel}
101
+ >
102
+ {heading}
103
+ </Typography>
104
+ )}
105
+ {subHeading && (
106
+ <StyledSubHeading marginTop={themeTokens.subHeadingMarginTop}>
107
+ <Typography variant={{ size: subHeadingSize }}>{subHeading}</Typography>
108
+ </StyledSubHeading>
109
+ )}
110
+ {Boolean(bodyText && hasHeadingSection) && <Spacer space={3} />}
111
+ </StyledHeading>
112
+ )}
113
+ {bodyText && (
114
+ <Box
115
+ scroll={{ onContentSizeChange, showsVerticalScrollIndicator: true }}
116
+ onLayout={handleContainerLayout}
117
+ >
118
+ <Typography>{bodyText}</Typography>
119
+ </Box>
120
+ )}
121
+ {(hasConfirmButton || hasCancelButton) && (
122
+ <StyledFooter {...themeTokens} hasBorder={isContentOverflowing} viewport={viewport}>
123
+ {hasConfirmButton && (
124
+ <Button
125
+ variant={confirmButtonVariant}
126
+ tokens={{ width: viewport === 'xs' || viewport === 'sm' ? '100%' : undefined }}
127
+ onPress={onConfirm}
128
+ >
129
+ {confirmButtonText}
130
+ </Button>
131
+ )}
132
+ <div>
133
+ {hasCancelButton && (
134
+ <CancelButton tokens={{ color: cancelButtonColor }} onPress={onCancel}>
135
+ {cancelButtonText}
136
+ </CancelButton>
137
+ )}
138
+ </div>
139
+ </StyledFooter>
140
+ )}
141
+ </StyledModalContent>
142
+ )
143
+ }
144
+
145
+ ModalContent.propTypes = {
146
+ tokens: PropTypes.object,
147
+ variant: PropTypes.object,
148
+ heading: PropTypes.string,
149
+ headingLevel: PropTypes.oneOf(['h3', 'h4']),
150
+ subHeading: PropTypes.string,
151
+ subHeadingSize: PropTypes.oneOf(['small', 'medium', 'large']),
152
+ bodyText: PropTypes.string,
153
+ confirmButtonText: PropTypes.string,
154
+ confirmButtonVariant: PropTypes.object,
155
+ onConfirm: PropTypes.func,
156
+ cancelButtonText: PropTypes.string,
157
+ cancelButtonType: PropTypes.elementType, // TODO: figure out a way of passing an icon to the TextButton
158
+ onCancel: PropTypes.func
159
+ }
160
+
161
+ export default ModalContent
@@ -0,0 +1,3 @@
1
+ import Modal from './Modal'
2
+
3
+ export default Modal
@@ -0,0 +1,79 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+ import { selectSystemProps } from '@telus-uds/components-base'
5
+
6
+ import { htmlAttrs, useTypographyTheme } from '../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const StyledParagraph = styled.p`
11
+ ${({ align }) => (align ? `text-align: ${align};` : '')}
12
+ ${({ linesBetween }) => `
13
+ margin-block-start: ${linesBetween}em;
14
+ margin-block-end: ${linesBetween}em;
15
+ `}
16
+ &:first-child {
17
+ margin-block-start: 0em;
18
+ }
19
+ &:last-child {
20
+ margin-block-end: 0em;
21
+ }
22
+ `
23
+
24
+ /**
25
+ * Block text as an HTML ```<p>``` element.
26
+ *
27
+ * ##Usage criteria
28
+ *
29
+ * - All body text should be contained in a **Paragraph**, regardless of length.
30
+ * - If the Paragraph is on a dark background, variant **{ invert: true }** must be used to maintain sufficient colour
31
+ contrast.
32
+ * - All Allium Typography variants other than header size variants are supported.
33
+ */
34
+ const Paragraph = ({ children, variant, tokens, testID, align, linesBetween = 1, ...rest }) => {
35
+ const style = useTypographyTheme(variant, tokens)
36
+ return (
37
+ <StyledParagraph
38
+ linesBetween={linesBetween}
39
+ data-testid={testID}
40
+ align={align}
41
+ style={style}
42
+ {...selectProps(rest)}
43
+ >
44
+ {children}
45
+ </StyledParagraph>
46
+ )
47
+ }
48
+
49
+ Paragraph.propTypes = {
50
+ ...selectedSystemPropTypes,
51
+ children: PropTypes.node.isRequired,
52
+ testID: PropTypes.string,
53
+ /**
54
+ * Sets the alignment style for the paragraph. Same options as Typography's `align` prop.
55
+ * 'justify' should be avoided as it usually reduces ease of reading.
56
+ */
57
+ align: PropTypes.oneOf(['auto', 'left', 'right', 'center', 'justify']),
58
+ /**
59
+ * How much space between consecutive paragraphs, or between a paragraph and its siblings, in CSS
60
+ * `em` units: 1 gives space equal to one line of paragraph text, 0.5 would be half a line, etc.
61
+ * @default 1
62
+ */
63
+ linesBetween: PropTypes.number,
64
+ /**
65
+ * Paragraph takes the same tokens overrides as Typography
66
+ */
67
+ tokens: PropTypes.oneOf([PropTypes.object, PropTypes.func]),
68
+ /**
69
+ * Paragraph takes any of Typography's theme variants except for header sizes
70
+ */
71
+ variant: PropTypes.exact({
72
+ bold: PropTypes.bool,
73
+ colour: PropTypes.oneOf(['secondary', 'tertiary']),
74
+ inverse: PropTypes.bool,
75
+ size: PropTypes.oneOf(['micro', 'small', 'large'])
76
+ })
77
+ }
78
+
79
+ export default Paragraph
@@ -0,0 +1,3 @@
1
+ import Paragraph from './Paragraph'
2
+
3
+ export default Paragraph
@@ -0,0 +1,12 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ const Body = ({ children }) => {
5
+ return <tbody>{children}</tbody>
6
+ }
7
+
8
+ Body.propTypes = {
9
+ children: PropTypes.node
10
+ }
11
+
12
+ export default Body
@@ -0,0 +1,148 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled, { css } from 'styled-components'
4
+ import { Typography, useThemeTokens } from '@telus-uds/components-base'
5
+ import { useTableContext } from './Table'
6
+
7
+ const stickyStyles = css`
8
+ position: sticky;
9
+ left: 0; // needed for sticky to work
10
+ z-index: 2;
11
+ clip-path: inset(0 -8px 0 0); // use clip path to cut off the shadow rendered below
12
+
13
+ &::before {
14
+ // use a pseudo element for the sticky shadow, since we already use shadows for inner cells border
15
+ content: '';
16
+ box-shadow: ${({ cellStickyShadow }) => cellStickyShadow};
17
+ position: absolute;
18
+ top: 0;
19
+ left: 0;
20
+ right: 0;
21
+ bottom: 0;
22
+ pointer-events: none;
23
+ }
24
+ `
25
+ const sharedStyles = css`
26
+ ${({
27
+ isSticky,
28
+ cellBoxShadowColor,
29
+ align,
30
+ cellPaddingTop,
31
+ cellPaddingRight,
32
+ cellPaddingBottom,
33
+ cellPaddingLeft
34
+ }) => css`
35
+ ${isSticky ? stickyStyles : undefined};
36
+ box-shadow: inset 0 -1px 0 ${cellBoxShadowColor};
37
+ text-align: ${align};
38
+ padding: ${cellPaddingTop}px ${cellPaddingRight}px ${cellPaddingBottom}px ${cellPaddingLeft}px;
39
+ `}
40
+ `
41
+ const StyledHeading = styled.th`
42
+ ${sharedStyles};
43
+ ${({ cellHeadingBackground, cellHeadingBoxShadowColor }) => `
44
+ background-color: ${cellHeadingBackground};
45
+ box-shadow: inset 0 1px 0 ${cellHeadingBoxShadowColor}, inset 0 -1px 0 ${cellHeadingBoxShadowColor};`}
46
+ `
47
+
48
+ const StyledSubHeading = styled.th`
49
+ ${sharedStyles};
50
+ background-color: ${({ cellSubheadingBackground }) => cellSubheadingBackground};
51
+ `
52
+
53
+ const StyledCell = styled.td`
54
+ ${sharedStyles};
55
+ background-color: ${({ cellBackground }) => cellBackground};
56
+ `
57
+
58
+ const StyledRowHeading = styled.th`
59
+ ${sharedStyles};
60
+ background-color: ${({ cellRowHeadingBackground }) => cellRowHeadingBackground};
61
+ `
62
+
63
+ const Cell = ({ children, type = 'default', isFirstInRow, align = 'left' }) => {
64
+ const { text, isScrollable: isTableScrollable, variant, tokens } = useTableContext()
65
+ const {
66
+ cellHeadingBackground,
67
+ cellHeadingBoxShadowColor,
68
+ cellBoxShadowColor,
69
+ cellSubheadingBackground,
70
+ cellBackground,
71
+ cellRowHeadingBackground,
72
+ cellStickyShadow,
73
+ cellPaddingTop,
74
+ cellPaddingRight,
75
+ cellPaddingLeft,
76
+ cellPaddingBottom
77
+ } = useThemeTokens('Table', tokens, variant)
78
+ const sharedProps = {
79
+ align,
80
+ isSticky: isTableScrollable && isFirstInRow,
81
+ cellStickyShadow,
82
+ cellPaddingTop,
83
+ cellPaddingRight,
84
+ cellPaddingLeft,
85
+ cellPaddingBottom,
86
+ cellBoxShadowColor
87
+ }
88
+
89
+ switch (type) {
90
+ case 'heading':
91
+ return (
92
+ <StyledHeading
93
+ scope="col"
94
+ cellHeadingBackground={cellHeadingBackground}
95
+ cellHeadingBoxShadowColor={cellHeadingBoxShadowColor}
96
+ {...sharedProps}
97
+ >
98
+ <Typography variant={{ size: 'h4' }}>{children}</Typography>
99
+ </StyledHeading>
100
+ )
101
+ case 'subHeading':
102
+ return (
103
+ <StyledSubHeading
104
+ scope="col"
105
+ cellSubheadingBackground={cellSubheadingBackground}
106
+ {...sharedProps}
107
+ >
108
+ <Typography variant={{ size: 'h5' }}>{children}</Typography>
109
+ </StyledSubHeading>
110
+ )
111
+ case 'rowHeading':
112
+ return (
113
+ <StyledRowHeading
114
+ scope="row"
115
+ cellRowHeadingBackground={cellRowHeadingBackground}
116
+ {...sharedProps}
117
+ >
118
+ <Typography variant={{ size: text === 'small' ? 'h5' : 'h4' }}>{children}</Typography>
119
+ </StyledRowHeading>
120
+ )
121
+
122
+ default:
123
+ return (
124
+ <StyledCell cellBackground={cellBackground} {...sharedProps}>
125
+ <Typography variant={{ size: text }}>{children}</Typography>
126
+ </StyledCell>
127
+ )
128
+ }
129
+ }
130
+
131
+ Cell.propTypes = {
132
+ children: PropTypes.node,
133
+ /**
134
+ * Defines the visual styles of a cell, and whether it should be a `td` or `th` element
135
+ */
136
+ type: PropTypes.oneOf(['default', 'heading', 'subHeading', 'rowHeading']),
137
+ /**
138
+ * @ignore
139
+ * Used internally for making the first column sticky
140
+ */
141
+ isFirstInRow: PropTypes.bool,
142
+ /**
143
+ * Defines the text alignment within the cell
144
+ */
145
+ align: PropTypes.oneOf(['left', 'center', 'right'])
146
+ }
147
+
148
+ export default Cell
@@ -0,0 +1,17 @@
1
+ import React, { cloneElement } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import Row from './Row'
4
+
5
+ const Header = ({ children }) => {
6
+ return (
7
+ <thead>
8
+ <Row>{React.Children.map(children, (child) => cloneElement(child, { type: 'heading' }))}</Row>
9
+ </thead>
10
+ )
11
+ }
12
+
13
+ Header.propTypes = {
14
+ children: PropTypes.node
15
+ }
16
+
17
+ export default Header
@@ -0,0 +1,18 @@
1
+ import React, { cloneElement } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ const Row = ({ children }) => {
5
+ return (
6
+ <tr>
7
+ {React.Children.map(children, (child, index) =>
8
+ cloneElement(child, { isFirstInRow: index === 0 })
9
+ )}
10
+ </tr>
11
+ )
12
+ }
13
+
14
+ Row.propTypes = {
15
+ children: PropTypes.node
16
+ }
17
+
18
+ export default Row
@@ -0,0 +1,17 @@
1
+ import React, { cloneElement } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import Row from './Row'
4
+
5
+ const Header = ({ children }) => {
6
+ return (
7
+ <Row>
8
+ {React.Children.map(children, (child) => cloneElement(child, { type: 'subHeading' }))}
9
+ </Row>
10
+ )
11
+ }
12
+
13
+ Header.propTypes = {
14
+ children: PropTypes.node
15
+ }
16
+
17
+ export default Header
@@ -0,0 +1,90 @@
1
+ import React, { useContext, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+ import { selectSystemProps, useSafeLayoutEffect, useThemeTokens } from '@telus-uds/components-base'
5
+ import throttle from 'lodash.throttle'
6
+ import { htmlAttrs } from '../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const StyledContainer = styled.div`
11
+ overflow: auto;
12
+ padding-bottom: ${(props) => (props.isScrollable ? props.tablePaddingBottom : 0)};
13
+ `
14
+
15
+ const StyledTable = styled.table`
16
+ margin: 0;
17
+ padding: 0;
18
+ `
19
+
20
+ const TableContext = React.createContext({})
21
+
22
+ export const useTableContext = () => useContext(TableContext)
23
+
24
+ /**
25
+ * Use `Table` to display tabular data.
26
+ *
27
+ * - Takes an optional `spacing` variant (compact)
28
+ * - Available in 2 text styles (medium, small)
29
+ * - When `Table` exceeds viewport, the first column becomes sticky, enabling horizontal scrolling
30
+ * - Right-align prices and numbers that display decimal points
31
+ *
32
+ * ### Building up a `Table`
33
+ * - Use `Table.Header` and `Table.Body` as direct children of `Table`
34
+ * - Use `Table.SubHeading` to render an intermediate data heading row
35
+ * - Use `Table.Row` and `Table.Cell` to build up the tabular data
36
+ * - Use `Cell`'s `type` prop to visually mark it as a row heading (`type="rowHeading"`)
37
+ */
38
+ const Table = ({ children, text = 'medium', tokens, variant, ...rest }) => {
39
+ const { tablePaddingBottom } = useThemeTokens('Table', tokens, variant)
40
+ const containerRef = useRef()
41
+ const tableRef = useRef()
42
+
43
+ const [containerWidth, setContainerWidth] = useState(0)
44
+ const [tableWidth, setTableWidth] = useState(0)
45
+
46
+ useSafeLayoutEffect(() => {
47
+ const updateDimensions = () => {
48
+ setContainerWidth(containerRef.current.clientWidth)
49
+ setTableWidth(tableRef.current.clientWidth)
50
+ }
51
+
52
+ const throttledUpdateDimensions = throttle(updateDimensions, 100, { leading: false })
53
+
54
+ updateDimensions()
55
+
56
+ // a pretty naive way of repeating the measurement after the fonts are loaded
57
+ window.addEventListener('load', updateDimensions)
58
+ window.addEventListener('resize', throttledUpdateDimensions)
59
+
60
+ return () => {
61
+ window.removeEventListener('load', updateDimensions)
62
+ window.removeEventListener('resize', throttledUpdateDimensions)
63
+ }
64
+ }, [])
65
+
66
+ const isScrollable = tableWidth > containerWidth
67
+
68
+ return (
69
+ <StyledContainer
70
+ ref={containerRef}
71
+ isScrollable={isScrollable}
72
+ tablePaddingBottom={`${tablePaddingBottom}px`}
73
+ {...selectProps(rest)}
74
+ >
75
+ <TableContext.Provider value={{ text, isScrollable, tokens, variant }}>
76
+ <StyledTable cellSpacing={0} ref={tableRef}>
77
+ {children}
78
+ </StyledTable>
79
+ </TableContext.Provider>
80
+ </StyledContainer>
81
+ )
82
+ }
83
+
84
+ Table.propTypes = {
85
+ ...selectedSystemPropTypes,
86
+ children: PropTypes.node,
87
+ text: PropTypes.oneOf(['medium', 'small'])
88
+ }
89
+
90
+ export default Table
@@ -0,0 +1,14 @@
1
+ import Table from './Table'
2
+ import Header from './Header'
3
+ import Body from './Body'
4
+ import Row from './Row'
5
+ import SubHeading from './SubHeading'
6
+ import Cell from './Cell'
7
+
8
+ Table.Header = Header
9
+ Table.Body = Body
10
+ Table.Row = Row
11
+ Table.SubHeading = SubHeading
12
+ Table.Cell = Cell
13
+
14
+ export default Table