@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.
- package/CHANGELOG.md +25 -2
- package/lib/Image/Image.js +126 -0
- package/lib/Image/index.js +13 -0
- package/lib/Modal/Modal.js +142 -0
- package/lib/Modal/ModalContent.js +195 -0
- package/lib/Modal/index.js +13 -0
- package/lib/Paragraph/Paragraph.js +107 -0
- package/lib/Paragraph/index.js +13 -0
- package/lib/Table/Body.js +29 -0
- package/lib/Table/Cell.js +200 -0
- package/lib/Table/Header.js +39 -0
- package/lib/Table/Row.js +35 -0
- package/lib/Table/SubHeading.js +37 -0
- package/lib/Table/Table.js +121 -0
- package/lib/Table/index.js +28 -0
- package/lib/Toast/Toast.js +136 -0
- package/lib/Toast/index.js +13 -0
- package/lib/WaffleGrid/WaffleGrid.js +176 -0
- package/lib/WaffleGrid/index.js +13 -0
- package/lib/baseExports.js +0 -6
- package/lib/index.js +55 -1
- package/lib-module/Image/Image.js +108 -0
- package/lib-module/Image/index.js +2 -0
- package/lib-module/Modal/Modal.js +128 -0
- package/lib-module/Modal/ModalContent.js +174 -0
- package/lib-module/Modal/index.js +2 -0
- package/lib-module/Paragraph/Paragraph.js +89 -0
- package/lib-module/Paragraph/index.js +2 -0
- package/lib-module/Table/Body.js +17 -0
- package/lib-module/Table/Cell.js +176 -0
- package/lib-module/Table/Header.js +22 -0
- package/lib-module/Table/Row.js +19 -0
- package/lib-module/Table/SubHeading.js +20 -0
- package/lib-module/Table/Table.js +93 -0
- package/lib-module/Table/index.js +12 -0
- package/lib-module/Toast/Toast.js +117 -0
- package/lib-module/Toast/index.js +2 -0
- package/lib-module/WaffleGrid/WaffleGrid.js +155 -0
- package/lib-module/WaffleGrid/index.js +2 -0
- package/lib-module/baseExports.js +1 -1
- package/lib-module/index.js +6 -0
- package/package.json +4 -3
- package/src/Image/Image.jsx +95 -0
- package/src/Image/index.js +3 -0
- package/src/Modal/Modal.jsx +111 -0
- package/src/Modal/ModalContent.jsx +161 -0
- package/src/Modal/index.js +3 -0
- package/src/Paragraph/Paragraph.jsx +79 -0
- package/src/Paragraph/index.js +3 -0
- package/src/Table/Body.jsx +12 -0
- package/src/Table/Cell.jsx +148 -0
- package/src/Table/Header.jsx +17 -0
- package/src/Table/Row.jsx +18 -0
- package/src/Table/SubHeading.jsx +17 -0
- package/src/Table/Table.jsx +90 -0
- package/src/Table/index.js +14 -0
- package/src/Toast/Toast.jsx +151 -0
- package/src/Toast/index.js +3 -0
- package/src/WaffleGrid/WaffleGrid.jsx +137 -0
- package/src/WaffleGrid/index.js +3 -0
- package/src/baseExports.js +0 -1
- package/src/index.js +6 -0
- package/types/Cell.d.ts +13 -0
- 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,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,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
|