@telus-uds/components-web 1.2.0 → 1.4.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 +34 -2
- package/lib/Breadcrumbs/Breadcrumbs.js +247 -0
- package/lib/Breadcrumbs/Item/Item.js +165 -0
- package/lib/Breadcrumbs/index.js +15 -0
- package/lib/Callout/Callout.js +121 -0
- package/lib/Callout/index.js +13 -0
- package/lib/DatePicker/CalendarContainer.js +221 -0
- package/lib/DatePicker/DatePicker.js +329 -0
- package/lib/DatePicker/dictionary.js +134 -0
- package/lib/DatePicker/index.js +13 -0
- package/lib/DatePicker/reactDatesCss.js +12 -0
- package/lib/ExpandCollapseMini/ExpandCollapseMini.js +75 -0
- package/lib/ExpandCollapseMini/ExpandCollapseMiniControl.js +95 -0
- package/lib/ExpandCollapseMini/index.js +13 -0
- package/lib/Footnote/Footnote.js +571 -0
- package/lib/Footnote/FootnoteLink.js +149 -0
- package/lib/Footnote/dictionary.js +19 -0
- package/lib/Footnote/index.js +16 -0
- package/lib/OrderedList/Item.js +162 -0
- package/lib/OrderedList/ItemBase.js +42 -0
- package/lib/OrderedList/OrderedList.js +94 -0
- package/lib/OrderedList/OrderedListBase.js +68 -0
- package/lib/OrderedList/constants.js +9 -0
- package/lib/OrderedList/index.js +16 -0
- package/lib/PreviewCard/AuthorDate.js +64 -0
- package/lib/PreviewCard/PreviewCard.js +236 -0
- package/lib/PreviewCard/index.js +13 -0
- package/lib/PriceLockup/PriceLockup.js +237 -0
- package/lib/PriceLockup/index.js +13 -0
- package/lib/PriceLockup/tokens.js +131 -0
- package/lib/ResponsiveImage/ResponsiveImage.js +115 -0
- package/lib/ResponsiveImage/index.js +13 -0
- package/lib/Ribbon/Ribbon.js +0 -1
- package/lib/Span/Span.js +88 -0
- package/lib/Span/index.js +13 -0
- package/lib/index.js +91 -1
- package/lib/shared/FullBleedContent/FullBleedContent.js +121 -0
- package/lib/shared/FullBleedContent/getFullBleedBorderRadius.js +73 -0
- package/lib/shared/FullBleedContent/index.js +29 -0
- package/lib/shared/FullBleedContent/useFullBleedContentProps.js +73 -0
- package/lib/utils/index.js +32 -0
- package/lib/utils/logger.js +31 -0
- package/lib/utils/media.js +54 -0
- package/lib/utils/renderStructuredContent.js +89 -0
- package/lib/utils/useTypographyTheme.js +32 -0
- package/lib-module/Breadcrumbs/Breadcrumbs.js +228 -0
- package/lib-module/Breadcrumbs/Item/Item.js +141 -0
- package/lib-module/Breadcrumbs/index.js +1 -0
- package/lib-module/Callout/Callout.js +106 -0
- package/lib-module/Callout/index.js +2 -0
- package/lib-module/DatePicker/CalendarContainer.js +208 -0
- package/lib-module/DatePicker/DatePicker.js +302 -0
- package/lib-module/DatePicker/dictionary.js +127 -0
- package/lib-module/DatePicker/index.js +2 -0
- package/lib-module/DatePicker/reactDatesCss.js +3 -0
- package/lib-module/ExpandCollapseMini/ExpandCollapseMini.js +56 -0
- package/lib-module/ExpandCollapseMini/ExpandCollapseMiniControl.js +80 -0
- package/lib-module/ExpandCollapseMini/index.js +2 -0
- package/lib-module/Footnote/Footnote.js +541 -0
- package/lib-module/Footnote/FootnoteLink.js +130 -0
- package/lib-module/Footnote/dictionary.js +12 -0
- package/lib-module/Footnote/index.js +4 -0
- package/lib-module/OrderedList/Item.js +139 -0
- package/lib-module/OrderedList/ItemBase.js +28 -0
- package/lib-module/OrderedList/OrderedList.js +71 -0
- package/lib-module/OrderedList/OrderedListBase.js +48 -0
- package/lib-module/OrderedList/constants.js +2 -0
- package/lib-module/OrderedList/index.js +4 -0
- package/lib-module/PreviewCard/AuthorDate.js +53 -0
- package/lib-module/PreviewCard/PreviewCard.js +211 -0
- package/lib-module/PreviewCard/index.js +2 -0
- package/lib-module/PriceLockup/PriceLockup.js +213 -0
- package/lib-module/PriceLockup/index.js +2 -0
- package/lib-module/PriceLockup/tokens.js +120 -0
- package/lib-module/ResponsiveImage/ResponsiveImage.js +100 -0
- package/lib-module/ResponsiveImage/index.js +2 -0
- package/lib-module/Ribbon/Ribbon.js +1 -2
- package/lib-module/Span/Span.js +70 -0
- package/lib-module/Span/index.js +2 -0
- package/lib-module/index.js +10 -0
- package/lib-module/shared/FullBleedContent/FullBleedContent.js +106 -0
- package/lib-module/shared/FullBleedContent/getFullBleedBorderRadius.js +65 -0
- package/lib-module/shared/FullBleedContent/index.js +4 -0
- package/lib-module/shared/FullBleedContent/useFullBleedContentProps.js +65 -0
- package/lib-module/utils/index.js +5 -1
- package/lib-module/utils/logger.js +18 -0
- package/lib-module/utils/media.js +46 -0
- package/lib-module/utils/renderStructuredContent.js +77 -0
- package/lib-module/utils/useTypographyTheme.js +24 -0
- package/package.json +9 -4
- package/src/Breadcrumbs/Breadcrumbs.jsx +222 -0
- package/src/Breadcrumbs/Item/Item.jsx +127 -0
- package/src/Breadcrumbs/index.js +1 -0
- package/src/Callout/Callout.jsx +76 -0
- package/src/Callout/index.js +3 -0
- package/src/DatePicker/CalendarContainer.jsx +210 -0
- package/src/DatePicker/DatePicker.jsx +303 -0
- package/src/DatePicker/dictionary.js +92 -0
- package/src/DatePicker/index.js +3 -0
- package/src/DatePicker/reactDatesCss.js +892 -0
- package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +48 -0
- package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +67 -0
- package/src/ExpandCollapseMini/index.js +3 -0
- package/src/Footnote/Footnote.jsx +468 -0
- package/src/Footnote/FootnoteLink.jsx +120 -0
- package/src/Footnote/dictionary.js +12 -0
- package/src/Footnote/index.js +6 -0
- package/src/OrderedList/Item.jsx +121 -0
- package/src/OrderedList/ItemBase.jsx +18 -0
- package/src/OrderedList/OrderedList.jsx +61 -0
- package/src/OrderedList/OrderedListBase.jsx +38 -0
- package/src/OrderedList/constants.js +2 -0
- package/src/OrderedList/index.js +6 -0
- package/src/PreviewCard/AuthorDate.jsx +31 -0
- package/src/PreviewCard/PreviewCard.jsx +201 -0
- package/src/PreviewCard/index.js +3 -0
- package/src/PriceLockup/PriceLockup.jsx +210 -0
- package/src/PriceLockup/index.js +3 -0
- package/src/PriceLockup/tokens.js +58 -0
- package/src/ResponsiveImage/ResponsiveImage.jsx +77 -0
- package/src/ResponsiveImage/index.js +3 -0
- package/src/Ribbon/Ribbon.jsx +0 -1
- package/src/Span/Span.jsx +66 -0
- package/src/Span/index.js +3 -0
- package/src/index.js +10 -0
- package/src/shared/FullBleedContent/FullBleedContent.jsx +90 -0
- package/src/shared/FullBleedContent/getFullBleedBorderRadius.js +55 -0
- package/src/shared/FullBleedContent/index.js +6 -0
- package/src/shared/FullBleedContent/useFullBleedContentProps.js +63 -0
- package/src/utils/index.js +5 -1
- package/src/utils/logger.js +20 -0
- package/src/utils/media.js +40 -0
- package/src/utils/renderStructuredContent.jsx +73 -0
- package/src/utils/useTypographyTheme.js +14 -0
- package/types/Callout.d.ts +13 -0
- package/types/DatePicker.d.ts +21 -0
- package/types/Footnote.d.ts +21 -0
- package/types/FootnoteLink.d.ts +20 -0
- package/types/PriceLockup.d.ts +22 -0
- package/types/common.d.ts +14 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the border radius values for an item of content that goes right up to
|
|
3
|
+
* the edges of its container, to the top, bottom, left or right of other content.
|
|
4
|
+
*
|
|
5
|
+
* Gives the full bleed item the same border radius as the container on the corners
|
|
6
|
+
* that are flush with the corners of the container.
|
|
7
|
+
*
|
|
8
|
+
* @param {number} borderRadius
|
|
9
|
+
* @param {'top'|'bottom'|'left'|'right'} position
|
|
10
|
+
* @param {boolean} hasFooter
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
const getFullBleedBorderRadius = function (borderRadius, position) {
|
|
14
|
+
let hasFooter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
15
|
+
const innerBorderRadius = {
|
|
16
|
+
borderBottomLeftRadius: 0,
|
|
17
|
+
borderBottomRightRadius: 0,
|
|
18
|
+
borderTopLeftRadius: 0,
|
|
19
|
+
borderTopRightRadius: 0
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (position && position !== 'none') {
|
|
23
|
+
if (borderRadius) {
|
|
24
|
+
// Depending on the position of the image, we need to round some
|
|
25
|
+
// corners, but not the others
|
|
26
|
+
switch (position) {
|
|
27
|
+
case 'bottom':
|
|
28
|
+
if (!hasFooter) {
|
|
29
|
+
innerBorderRadius.borderBottomLeftRadius = borderRadius;
|
|
30
|
+
innerBorderRadius.borderBottomRightRadius = borderRadius;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case 'left':
|
|
36
|
+
if (!hasFooter) {
|
|
37
|
+
innerBorderRadius.borderBottomLeftRadius = borderRadius;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
innerBorderRadius.borderTopLeftRadius = borderRadius;
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
case 'right':
|
|
44
|
+
if (!hasFooter) {
|
|
45
|
+
innerBorderRadius.borderBottomRightRadius = borderRadius;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
innerBorderRadius.borderTopRightRadius = borderRadius;
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'top':
|
|
52
|
+
innerBorderRadius.borderTopLeftRadius = borderRadius;
|
|
53
|
+
innerBorderRadius.borderTopRightRadius = borderRadius;
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return innerBorderRadius;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default getFullBleedBorderRadius;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useResponsiveProp } from '@telus-uds/components-base';
|
|
2
|
+
|
|
3
|
+
const getContentStackDirection = fullBleedContentPosition => {
|
|
4
|
+
switch (fullBleedContentPosition) {
|
|
5
|
+
case 'left':
|
|
6
|
+
return 'row-reverse';
|
|
7
|
+
|
|
8
|
+
case 'right':
|
|
9
|
+
return 'row';
|
|
10
|
+
|
|
11
|
+
case 'top':
|
|
12
|
+
return 'column-reverse';
|
|
13
|
+
|
|
14
|
+
default:
|
|
15
|
+
return 'column';
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getContentStackAlign = fullBleedContentAlign => {
|
|
20
|
+
switch (fullBleedContentAlign) {
|
|
21
|
+
case 'center':
|
|
22
|
+
return 'center';
|
|
23
|
+
|
|
24
|
+
case 'end':
|
|
25
|
+
case 'flex-end':
|
|
26
|
+
return 'flex-end';
|
|
27
|
+
|
|
28
|
+
case 'start':
|
|
29
|
+
case 'flex-start':
|
|
30
|
+
return 'flex-start';
|
|
31
|
+
|
|
32
|
+
default:
|
|
33
|
+
return 'stretch';
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a set of `FullBleedContent` props into the variables a container needs to
|
|
38
|
+
* correctly position a `FullBleedContent` component for the current viewport.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} [fullBleedContent] - a set of valid proptypes for FullBleedContent
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const useFullBleedContentProps = fullBleedContent => {
|
|
46
|
+
const {
|
|
47
|
+
align: fullBleedContentAlignProp,
|
|
48
|
+
position: fullBleedContentPositionProp,
|
|
49
|
+
...fullBleedContentProps
|
|
50
|
+
} = fullBleedContent ?? {
|
|
51
|
+
position: 'none'
|
|
52
|
+
};
|
|
53
|
+
const fullBleedContentPosition = useResponsiveProp(fullBleedContentPositionProp, 'none');
|
|
54
|
+
const contentStackDirection = getContentStackDirection(fullBleedContentPosition);
|
|
55
|
+
const fullBleedContentAlign = useResponsiveProp(fullBleedContentAlignProp, 'stretch');
|
|
56
|
+
const contentStackAlign = getContentStackAlign(fullBleedContentAlign);
|
|
57
|
+
return {
|
|
58
|
+
contentStackAlign,
|
|
59
|
+
contentStackDirection,
|
|
60
|
+
fullBleedContentPosition,
|
|
61
|
+
fullBleedContentProps
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default useFullBleedContentProps;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { transformGradient } from './transforms';
|
|
2
|
+
import useTypographyTheme from './useTypographyTheme';
|
|
2
3
|
import htmlAttrs from './htmlAttrs';
|
|
3
|
-
|
|
4
|
+
import { warn } from './logger';
|
|
5
|
+
import media from './media';
|
|
6
|
+
import renderStructuredContent from './renderStructuredContent';
|
|
7
|
+
export { htmlAttrs, transformGradient, useTypographyTheme, warn, media, renderStructuredContent };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const deprecate = (componentName, message) => {
|
|
2
|
+
if (process.env.NODE_ENV === 'production') {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
console.warn(`[Allium] [Deprecate] ${componentName}: ${message}`); // eslint-disable-line no-console
|
|
7
|
+
};
|
|
8
|
+
export const warn = (componentName, message) => {
|
|
9
|
+
if (process.env.NODE_ENV === 'production') {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.warn(`[Allium] ${componentName}: ${message}`); // eslint-disable-line no-console
|
|
14
|
+
};
|
|
15
|
+
export default {
|
|
16
|
+
deprecate,
|
|
17
|
+
warn
|
|
18
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { viewports } from '@telus-uds/system-constants';
|
|
2
|
+
export default function media() {
|
|
3
|
+
return {
|
|
4
|
+
query: {},
|
|
5
|
+
|
|
6
|
+
from(breakpoint) {
|
|
7
|
+
if (breakpoint !== viewports.xs) {
|
|
8
|
+
this.query.minWidth = breakpoint;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return this;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
until(breakpoint) {
|
|
15
|
+
this.query.maxWidth = breakpoint;
|
|
16
|
+
return this;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
and(custom) {
|
|
20
|
+
this.query.and = custom;
|
|
21
|
+
return this;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
css(style) {
|
|
25
|
+
const {
|
|
26
|
+
minWidth,
|
|
27
|
+
maxWidth,
|
|
28
|
+
and
|
|
29
|
+
} = this.query;
|
|
30
|
+
const min = minWidth ? `(min-width: ${viewports.map.get(minWidth)}px)` : undefined;
|
|
31
|
+
const max = maxWidth ? `(max-width: ${viewports.map.get(maxWidth) - 1}px)` : undefined;
|
|
32
|
+
|
|
33
|
+
if (typeof min !== 'undefined' || typeof max !== 'undefined' || typeof and !== 'undefined') {
|
|
34
|
+
const mediaQuery = `@media ${[min, max, and].filter(a => a).join(' and ')}`;
|
|
35
|
+
this.query = {};
|
|
36
|
+
return {
|
|
37
|
+
[mediaQuery]: { ...(typeof style === 'function' ? style() : style)
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return typeof style === 'function' ? style() : style;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from '@telus-uds/components-base';
|
|
3
|
+
/**
|
|
4
|
+
* Takes a string content and marks up all the links in it by wrapping them
|
|
5
|
+
* in `Link` component.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createElement as _createElement } from "react";
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
|
|
11
|
+
const generateLinks = content => {
|
|
12
|
+
const linkRegex = /<\s*a([^>]*)>(.*?)<\s*\/\s*a>/g;
|
|
13
|
+
const attributeRegex = /(\w+)\s*=\s*((["'])(.*?)\3|(?=\s|\/>))/g;
|
|
14
|
+
const parts = content.split(linkRegex);
|
|
15
|
+
|
|
16
|
+
if (parts.length === 1) {
|
|
17
|
+
return parts;
|
|
18
|
+
} // Start with first anchor text, attributes will be in the previous part
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
for (let i = 2; i < parts.length; i += 3) {
|
|
22
|
+
const o = {}; // Get attributes from previous part
|
|
23
|
+
|
|
24
|
+
const attributes = parts[i - 1].trim(); // Create object from attributes
|
|
25
|
+
|
|
26
|
+
const matchedAttributes = attributes.match(attributeRegex);
|
|
27
|
+
|
|
28
|
+
if (matchedAttributes) {
|
|
29
|
+
matchedAttributes.forEach(attribute => {
|
|
30
|
+
const split = attribute.split('=');
|
|
31
|
+
o[split[0]] = split[1].substr(1, split[1].length - 2);
|
|
32
|
+
});
|
|
33
|
+
} // Remove anchor attributes from parts
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
parts[i - 1] = undefined; // Replace anchor text with Link in parts
|
|
37
|
+
|
|
38
|
+
parts[i] = /*#__PURE__*/_createElement(Link, { ...o,
|
|
39
|
+
key: i
|
|
40
|
+
}, parts[i]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return parts;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Takes an array of strings and in each element replaces the breaks with `<br>` tags.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const generateBreaks = parts => {
|
|
51
|
+
const breakRegex = /<br\s?\/*>/g;
|
|
52
|
+
const partsWithBreaks = parts;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < partsWithBreaks.length; i += 1) {
|
|
55
|
+
if (typeof partsWithBreaks[i] === 'string' && partsWithBreaks[i].search(breakRegex) !== -1) {
|
|
56
|
+
const toSplit = partsWithBreaks[i].split(breakRegex);
|
|
57
|
+
|
|
58
|
+
for (let x = 1; x < toSplit.length; x += 2) {
|
|
59
|
+
toSplit.splice(x, 0, /*#__PURE__*/_jsx("br", {}, `break-${i}-${x}`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
partsWithBreaks[i] = toSplit;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return partsWithBreaks;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const renderStructuredContent = content => {
|
|
70
|
+
if (typeof content !== 'string') {
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return generateBreaks(generateLinks(content));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default renderStructuredContent;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { applyTextStyles, useTheme, useThemeTokens, useViewport } from '@telus-uds/components-base';
|
|
2
|
+
|
|
3
|
+
const useTypographyTheme = (variant, tokens) => {
|
|
4
|
+
const viewport = useViewport();
|
|
5
|
+
const themeTokens = useThemeTokens('Typography', tokens, variant, {
|
|
6
|
+
viewport
|
|
7
|
+
});
|
|
8
|
+
const {
|
|
9
|
+
themeOptions
|
|
10
|
+
} = useTheme();
|
|
11
|
+
const {
|
|
12
|
+
fontSize,
|
|
13
|
+
lineHeight,
|
|
14
|
+
...rnStyles
|
|
15
|
+
} = applyTextStyles({ ...themeTokens,
|
|
16
|
+
themeOptions
|
|
17
|
+
});
|
|
18
|
+
return { ...rnStyles,
|
|
19
|
+
fontSize: `${fontSize}px`,
|
|
20
|
+
lineHeight: `${lineHeight}px`
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default useTypographyTheme;
|
package/package.json
CHANGED
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
"extends @telus-uds/browserslist-config"
|
|
5
5
|
],
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@
|
|
7
|
+
"@gorhom/portal": "^1.0.14",
|
|
8
|
+
"@telus-uds/components-base": "1.34.0",
|
|
8
9
|
"@telus-uds/system-constants": "^1.2.0",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
10
|
+
"react-dates": "^21.8.0",
|
|
11
|
+
"react-moment-proptypes": "^1.8.1",
|
|
12
|
+
"@telus-uds/system-theme-tokens": "^2.18.0",
|
|
13
|
+
"prop-types": "^15.7.2",
|
|
14
|
+
"lodash.omit": "^4.5.0",
|
|
15
|
+
"react-helmet-async": "^1.3.0"
|
|
11
16
|
},
|
|
12
17
|
"description": "UDS mult-brand web components",
|
|
13
18
|
"devDependencies": {
|
|
@@ -53,5 +58,5 @@
|
|
|
53
58
|
"skip": true
|
|
54
59
|
},
|
|
55
60
|
"types": "types/index.d.ts",
|
|
56
|
-
"version": "1.
|
|
61
|
+
"version": "1.4.0"
|
|
57
62
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
|
|
5
|
+
import { Helmet, HelmetProvider } from 'react-helmet-async'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
componentPropType,
|
|
9
|
+
selectSystemProps,
|
|
10
|
+
unpackFragment,
|
|
11
|
+
withLinkRouter
|
|
12
|
+
} from '@telus-uds/components-base'
|
|
13
|
+
import { htmlAttrs } from '../utils'
|
|
14
|
+
import Item from './Item/Item'
|
|
15
|
+
|
|
16
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
17
|
+
|
|
18
|
+
const StyledList = styled.ol({
|
|
19
|
+
display: 'flex',
|
|
20
|
+
flexDirection: 'row',
|
|
21
|
+
flexWrap: 'wrap',
|
|
22
|
+
listStyle: 'none',
|
|
23
|
+
listStylePosition: 'inside',
|
|
24
|
+
margin: 0,
|
|
25
|
+
padding: 0
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const omitProps = ({
|
|
29
|
+
current,
|
|
30
|
+
path,
|
|
31
|
+
breadcrumbName,
|
|
32
|
+
indexRoute,
|
|
33
|
+
childRoutes,
|
|
34
|
+
component,
|
|
35
|
+
...props
|
|
36
|
+
}) => props
|
|
37
|
+
|
|
38
|
+
const getBreadcrumbName = (item, params) => {
|
|
39
|
+
if (!item.breadcrumbName) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let { breadcrumbName } = item
|
|
44
|
+
|
|
45
|
+
Object.keys(params).forEach((key) => {
|
|
46
|
+
breadcrumbName = breadcrumbName.replace(`:${key}`, params[key])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return breadcrumbName
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getPath = (path, params, concatenatePaths, paths) => {
|
|
53
|
+
let p = path
|
|
54
|
+
if (concatenatePaths) {
|
|
55
|
+
p = p.replace(/^\//, '')
|
|
56
|
+
Object.keys(params).forEach((key) => {
|
|
57
|
+
p = p.replace(`:${key}`, params[key])
|
|
58
|
+
})
|
|
59
|
+
if (p) {
|
|
60
|
+
paths.push(p)
|
|
61
|
+
}
|
|
62
|
+
return `/${paths.join('/')}`
|
|
63
|
+
}
|
|
64
|
+
return p
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const getItems = (items, params, concatenatePaths) => {
|
|
68
|
+
const paths = []
|
|
69
|
+
|
|
70
|
+
return items
|
|
71
|
+
.filter((item) => item.path)
|
|
72
|
+
.map((item, i, filteredItems) => {
|
|
73
|
+
const isLast = i === filteredItems.length - 1
|
|
74
|
+
const breadcrumbName = getBreadcrumbName(item, params)
|
|
75
|
+
const href = getPath(item.path, params, concatenatePaths, paths)
|
|
76
|
+
return {
|
|
77
|
+
breadcrumbName,
|
|
78
|
+
href,
|
|
79
|
+
current: isLast,
|
|
80
|
+
...omitProps(selectProps(item))
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const getStructuredData = (items, baseUrl) => {
|
|
86
|
+
return items.map((item, index) => ({
|
|
87
|
+
'@type': 'ListItem',
|
|
88
|
+
position: index + 1,
|
|
89
|
+
item: {
|
|
90
|
+
'@id': `${baseUrl || ''}${item.href}`,
|
|
91
|
+
name: item.breadcrumbName
|
|
92
|
+
}
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Display a hierarchy of links, commonly used for navigation.
|
|
98
|
+
*/
|
|
99
|
+
const Breadcrumbs = ({
|
|
100
|
+
baseUrl,
|
|
101
|
+
children,
|
|
102
|
+
linkRouterProps,
|
|
103
|
+
params = {},
|
|
104
|
+
tokens,
|
|
105
|
+
reactRouterLinkComponent,
|
|
106
|
+
routes,
|
|
107
|
+
variant,
|
|
108
|
+
LinkRouter,
|
|
109
|
+
...rest
|
|
110
|
+
}) => {
|
|
111
|
+
// React Helmet can cause a memory leak in SSR if not properly configured.
|
|
112
|
+
// Only run it in SSR if theme options tells us to.
|
|
113
|
+
/* const {
|
|
114
|
+
themeOptions: { enableHelmetSSR }
|
|
115
|
+
} = useTheme() */
|
|
116
|
+
// const isHydrating = useHydrationContext()
|
|
117
|
+
// const isSSR = typeof window === 'undefined' || isHydrating
|
|
118
|
+
// const hasMetadata = isSSR ? enableHelmetSSR : true
|
|
119
|
+
const helmetContext = {}
|
|
120
|
+
|
|
121
|
+
const activeRoutes = children
|
|
122
|
+
? React.Children.map(
|
|
123
|
+
unpackFragment(children),
|
|
124
|
+
({ props: { href, children: breadcrumbName, ...itemRest }, ref }) => ({
|
|
125
|
+
path: href,
|
|
126
|
+
breadcrumbName,
|
|
127
|
+
ref,
|
|
128
|
+
...itemRest
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
: routes.filter((route) => route.path && route.breadcrumbName)
|
|
132
|
+
|
|
133
|
+
const items = getItems(activeRoutes, params, !children)
|
|
134
|
+
|
|
135
|
+
const metadata = (
|
|
136
|
+
<HelmetProvider context={helmetContext}>
|
|
137
|
+
<Helmet>
|
|
138
|
+
<script type="application/ld+json">
|
|
139
|
+
{`
|
|
140
|
+
{
|
|
141
|
+
"@context": "http://schema.org",
|
|
142
|
+
"@type": "BreadcrumbList",
|
|
143
|
+
"itemListElement": ${JSON.stringify(getStructuredData(items, baseUrl))}
|
|
144
|
+
}
|
|
145
|
+
`}
|
|
146
|
+
</script>
|
|
147
|
+
</Helmet>
|
|
148
|
+
</HelmetProvider>
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<nav {...selectProps(rest)}>
|
|
153
|
+
<StyledList>
|
|
154
|
+
{items.map(
|
|
155
|
+
({
|
|
156
|
+
href,
|
|
157
|
+
current,
|
|
158
|
+
breadcrumbName,
|
|
159
|
+
reactRouterLinkComponent: linkComponent = reactRouterLinkComponent,
|
|
160
|
+
LinkRouter: ItemLinkRouter = LinkRouter,
|
|
161
|
+
linkRouterProps: itemLinkRouterProps,
|
|
162
|
+
...itemRest
|
|
163
|
+
}) => (
|
|
164
|
+
<Item
|
|
165
|
+
{...itemRest}
|
|
166
|
+
current={current}
|
|
167
|
+
href={href}
|
|
168
|
+
tokens={tokens}
|
|
169
|
+
key={href}
|
|
170
|
+
linkRouterProps={{ ...linkRouterProps, itemLinkRouterProps }}
|
|
171
|
+
reactRouterLinkComponent={linkComponent}
|
|
172
|
+
variant={variant}
|
|
173
|
+
LinkRouter={ItemLinkRouter}
|
|
174
|
+
>
|
|
175
|
+
{breadcrumbName}
|
|
176
|
+
</Item>
|
|
177
|
+
)
|
|
178
|
+
)}
|
|
179
|
+
</StyledList>
|
|
180
|
+
{metadata}
|
|
181
|
+
</nav>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Breadcrumbs.propTypes = {
|
|
186
|
+
...selectedSystemPropTypes,
|
|
187
|
+
...withLinkRouter.propTypes,
|
|
188
|
+
/**
|
|
189
|
+
* Base URL used to build structured data.
|
|
190
|
+
*/
|
|
191
|
+
baseUrl: PropTypes.string,
|
|
192
|
+
/**
|
|
193
|
+
* A list of Items to be used as an alternative to the `routes` prop.
|
|
194
|
+
*/
|
|
195
|
+
children: componentPropType('Item'),
|
|
196
|
+
/**
|
|
197
|
+
* React Router params.
|
|
198
|
+
*/
|
|
199
|
+
params: PropTypes.object,
|
|
200
|
+
/**
|
|
201
|
+
* React Router Link component.
|
|
202
|
+
* @deprecated please use `LinkRouter` and `linkRouterProps` instead
|
|
203
|
+
*/
|
|
204
|
+
reactRouterLinkComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
205
|
+
/**
|
|
206
|
+
* An array of routes to be displayed as breadcrumbs.
|
|
207
|
+
*/
|
|
208
|
+
routes: PropTypes.arrayOf(
|
|
209
|
+
PropTypes.shape({
|
|
210
|
+
path: PropTypes.string,
|
|
211
|
+
breadcrumbName: PropTypes.string
|
|
212
|
+
})
|
|
213
|
+
),
|
|
214
|
+
/**
|
|
215
|
+
* Variant to render.
|
|
216
|
+
*/
|
|
217
|
+
variant: PropTypes.shape({ inverse: PropTypes.bool, light: PropTypes.bool })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Breadcrumbs.Item = Item
|
|
221
|
+
|
|
222
|
+
export default Breadcrumbs
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {
|
|
5
|
+
Link,
|
|
6
|
+
Typography,
|
|
7
|
+
clickProps,
|
|
8
|
+
selectSystemProps,
|
|
9
|
+
useThemeTokens,
|
|
10
|
+
withLinkRouter
|
|
11
|
+
} from '@telus-uds/components-base'
|
|
12
|
+
import { htmlAttrs } from '../../utils'
|
|
13
|
+
|
|
14
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
15
|
+
|
|
16
|
+
const StyledItemContainer = styled.li({
|
|
17
|
+
display: 'flex',
|
|
18
|
+
paddingLeft: ({ listItemPadding }) => `${listItemPadding}px`
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const IconContainer = styled.span({
|
|
22
|
+
display: 'inline-flex',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
paddingLeft: ({ iconPadding }) => `${iconPadding}px`,
|
|
25
|
+
paddingRight: ({ iconPadding }) => `${iconPadding}px`
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const Item = forwardRef(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
href,
|
|
32
|
+
reactRouterLinkComponent: ReactRouterLink,
|
|
33
|
+
children,
|
|
34
|
+
current = false,
|
|
35
|
+
tokens,
|
|
36
|
+
variant = { light: true }, // `light` variant (shared with the `Link` component) is default by design
|
|
37
|
+
...rest
|
|
38
|
+
},
|
|
39
|
+
ref
|
|
40
|
+
) => {
|
|
41
|
+
const {
|
|
42
|
+
iconColor,
|
|
43
|
+
icon: ChevronRightIcon,
|
|
44
|
+
...themeTokens
|
|
45
|
+
} = useThemeTokens('Breadcrumbs', tokens, variant)
|
|
46
|
+
|
|
47
|
+
const linkOptions = clickProps.toPressProps(selectProps(rest))
|
|
48
|
+
|
|
49
|
+
if (ReactRouterLink) {
|
|
50
|
+
linkOptions.to = href
|
|
51
|
+
} else {
|
|
52
|
+
linkOptions.href = href
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<StyledItemContainer {...themeTokens}>
|
|
57
|
+
{current ? (
|
|
58
|
+
<Typography variant={{ ...variant, secondary: true }}>{children}</Typography>
|
|
59
|
+
) : (
|
|
60
|
+
<>
|
|
61
|
+
{ReactRouterLink ? (
|
|
62
|
+
<ReactRouterLink
|
|
63
|
+
{...linkOptions}
|
|
64
|
+
// TODO refactor
|
|
65
|
+
// eslint-disable-next-line react/no-unstable-nested-components
|
|
66
|
+
component={(props) => {
|
|
67
|
+
return <Link {...props} variant={variant} />
|
|
68
|
+
}}
|
|
69
|
+
ref={ref}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</ReactRouterLink>
|
|
73
|
+
) : (
|
|
74
|
+
<Link {...linkOptions} ref={ref} variant={variant}>
|
|
75
|
+
{children}
|
|
76
|
+
</Link>
|
|
77
|
+
)}
|
|
78
|
+
<IconContainer {...themeTokens}>
|
|
79
|
+
<ChevronRightIcon size={16} color={iconColor} />
|
|
80
|
+
</IconContainer>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</StyledItemContainer>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
Item.displayName = 'Item'
|
|
89
|
+
|
|
90
|
+
Item.propTypes = {
|
|
91
|
+
...selectedSystemPropTypes,
|
|
92
|
+
/**
|
|
93
|
+
* Breadcrumb text
|
|
94
|
+
*/
|
|
95
|
+
children: PropTypes.node.isRequired,
|
|
96
|
+
/**
|
|
97
|
+
* @ignore
|
|
98
|
+
*
|
|
99
|
+
* Indicates whether or not the Item should be as current/active
|
|
100
|
+
*/
|
|
101
|
+
current: PropTypes.bool,
|
|
102
|
+
/**
|
|
103
|
+
* Target URL. This will be converted to `to` if the `reactRouterLinkComponent`
|
|
104
|
+
* prop is provided to the `Item` or parent `Breadcrumbs` element.
|
|
105
|
+
*/
|
|
106
|
+
href: PropTypes.string.isRequired,
|
|
107
|
+
/**
|
|
108
|
+
* React Router Link component. This will be passed down from the parent
|
|
109
|
+
* `<Breadcrumbs>` if the parent has a `reactRouterLinkComponent` provided.
|
|
110
|
+
* @deprecated please use `LinkRouter` and `linkRouterProps` instead
|
|
111
|
+
*/
|
|
112
|
+
reactRouterLinkComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
113
|
+
/**
|
|
114
|
+
* Variant to render.
|
|
115
|
+
*/
|
|
116
|
+
variant: PropTypes.shape({ inverse: PropTypes.bool })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Since react/require-default-props eslint rule doesn't pick up default arguments as default props when combined with `forwardRef`
|
|
120
|
+
// - probably related https://github.com/yannickcr/eslint-plugin-react/issues/2760
|
|
121
|
+
Item.defaultProps = {
|
|
122
|
+
current: false,
|
|
123
|
+
reactRouterLinkComponent: undefined,
|
|
124
|
+
variant: { light: true }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default withLinkRouter(Item)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Breadcrumbs'
|