@conveyorhq/arrow-ds 1.170.0 → 1.171.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@conveyorhq/arrow-ds",
3
3
  "author": "Conveyor",
4
4
  "license": "MIT",
5
- "version": "1.170.0",
5
+ "version": "1.171.0",
6
6
  "description": "Arrow Design System",
7
7
  "repository": "https://github.com/conveyor/arrow-ds",
8
8
  "publishConfig": {
@@ -32,6 +32,7 @@ const react_1 = __importStar(require("react"));
32
32
  const Icon_1 = require("../Icon");
33
33
  const Link_1 = require("../Link");
34
34
  const bem_1 = require("../../utilities/bem");
35
+ const strings_1 = require("../../utilities/strings");
35
36
  const ButtonGroup_1 = require("./ButtonGroup");
36
37
  const contexts_1 = require("../../contexts");
37
38
  const cn = "Button";
@@ -70,7 +71,7 @@ function getButtonSizeClasses(size, icon, children) {
70
71
  ? buttonIconOnlySizeClasses[size]
71
72
  : buttonSizeClasses[size];
72
73
  }
73
- const Button = (0, react_1.forwardRef)(({ as: Component = "button", children, className, block = false, size = BUTTON_SIZE.MEDIUM, variant = BUTTON_VARIANT.PRIMARY, depressed = false, isLoading = false, theme: themeProp, icon, iconColor, iconSpin = false, iconPosition = BUTTON_ICON_POSITION.LEFT, iconPrefix, disabled, type: typeProp, ...rest }, ref) => {
74
+ const Button = (0, react_1.forwardRef)(({ as: Component = "button", children, className, block = false, size = BUTTON_SIZE.MEDIUM, variant = BUTTON_VARIANT.PRIMARY, depressed = false, isLoading = false, theme: themeProp, icon, iconColor, iconSpin = false, iconPosition = BUTTON_ICON_POSITION.LEFT, iconPrefix, disabled, type: typeProp, href, ...rest }, ref) => {
74
75
  const isDisabled = disabled || isLoading;
75
76
  const themeContext = (0, react_1.useContext)(contexts_1.ThemeContext);
76
77
  const theme = themeProp || themeContext;
@@ -90,10 +91,11 @@ const Button = (0, react_1.forwardRef)(({ as: Component = "button", children, cl
90
91
  type = "button";
91
92
  }
92
93
  const propsIfLink = Component === Link_1.Link ? { noStyles: true } : {};
94
+ const safeHref = (0, strings_1.isSafeUrl)(href) ? href : "#";
93
95
  if (variant === BUTTON_VARIANT.PLAIN) {
94
96
  return (react_1.default.createElement(Component, { type: type, className: className, disabled: disabled, ref: ref, ...rest }, children));
95
97
  }
96
- return (react_1.default.createElement(Component, { type: type, className: buttonClassNames, disabled: isDisabled, ref: ref, ...propsIfLink, ...rest },
98
+ return (react_1.default.createElement(Component, { type: type, className: buttonClassNames, disabled: isDisabled, ref: ref, href: safeHref, ...propsIfLink, ...rest },
97
99
  icon && iconProps && iconPosition === BUTTON_ICON_POSITION.LEFT && (react_1.default.createElement(Icon_1.Icon, { ...iconProps })),
98
100
  children && (react_1.default.createElement("span", { className: (0, classnames_1.default)((0, bem_1.bem)(cn, { e: "content" }), isLoading && (0, bem_1.bem)(cn, { e: "content", m: "invisible" })) }, children)),
99
101
  icon && iconProps && iconPosition === BUTTON_ICON_POSITION.RIGHT && (react_1.default.createElement(Icon_1.Icon, { ...iconProps })),
@@ -30,12 +30,14 @@ exports.Link = void 0;
30
30
  const react_1 = __importStar(require("react"));
31
31
  const classnames_1 = __importDefault(require("classnames"));
32
32
  const bem_1 = require("../../utilities/bem");
33
+ const strings_1 = require("../../utilities/strings");
33
34
  const status_1 = require("../../contexts/status");
34
35
  const cn = "Link";
35
36
  exports.Link = (0, react_1.forwardRef)((props, ref) => {
36
- const { as: Component = "a", className, disabled, noStyles, statusVariant: statusVariantProp, target, ...rest } = props;
37
+ const { as: Component = "a", className, disabled, noStyles, statusVariant: statusVariantProp, target, href, ...rest } = props;
37
38
  const context = (0, react_1.useContext)(status_1.StatusContext);
38
39
  const calculatedVariant = statusVariantProp || context;
39
40
  const classes = (0, classnames_1.default)(!noStyles && (0, bem_1.bem)(cn), calculatedVariant && (0, bem_1.bem)(cn, { m: calculatedVariant }), disabled && "isDisabled", className);
40
- return (react_1.default.createElement(Component, { className: classes, disabled: Component === "button" && disabled, target: target, rel: `${target === "_blank" ? "noopener noreferrer" : ""}`, ref: ref, ...rest }));
41
+ const safeHref = (0, strings_1.isSafeUrl)(href) ? href : "#";
42
+ return (react_1.default.createElement(Component, { className: classes, disabled: Component === "button" && disabled, target: target, rel: `${target === "_blank" ? "noopener noreferrer" : ""}`, ref: ref, href: safeHref, ...rest }));
41
43
  });
@@ -31,14 +31,18 @@ const react_1 = __importStar(require("react"));
31
31
  const classnames_1 = __importDefault(require("classnames"));
32
32
  const Box_1 = require("../Box");
33
33
  const bem_1 = require("../../utilities/bem");
34
+ const strings_1 = require("../../utilities/strings");
34
35
  const Icon_1 = require("../Icon");
35
36
  const Badge_1 = require("../Badge");
36
37
  const cn = "Reference";
37
38
  const ReferenceGroup = ({ className, ...rest }) => (react_1.default.createElement(Box_1.Box, { className: (0, classnames_1.default)((0, bem_1.bem)(`${cn}Group`), className), ...rest }));
38
- const Reference = (0, react_1.forwardRef)(({ as: Component = "a", disabled, className, icon, iconPrefix, badgeVariant, badgeLabel, children, target, noLink, reverse = false, ...rest }, ref) => (react_1.default.createElement(Component, { className: (0, classnames_1.default)((0, bem_1.bem)(cn), !noLink && (0, bem_1.bem)(cn, { m: "isLink" }), disabled && (0, bem_1.bem)(cn, { m: "disabled" }), reverse && "flex-row-reverse", className), disabled: Component === "button" && disabled, target: target, rel: `${target === "_blank" ? "noopener noreferrer" : ""}`, ref: ref, ...rest },
39
- icon && (react_1.default.createElement(Icon_1.Icon, { className: (0, classnames_1.default)((0, bem_1.bem)(cn, { e: "icon" })), icon: icon, prefix: iconPrefix })),
40
- children,
41
- badgeLabel && (react_1.default.createElement(Badge_1.Badge, { className: (0, classnames_1.default)((0, bem_1.bem)(cn, { e: "badge" })), variant: badgeVariant, size: Badge_1.BADGE_SIZE.MICRO }, badgeLabel)))));
39
+ const Reference = (0, react_1.forwardRef)(({ as: Component = "a", disabled, className, icon, iconPrefix, badgeVariant, badgeLabel, children, target, noLink, reverse = false, href, ...rest }, ref) => {
40
+ const safeHref = (0, strings_1.isSafeUrl)(href) ? href : "#";
41
+ return (react_1.default.createElement(Component, { className: (0, classnames_1.default)((0, bem_1.bem)(cn), !noLink && (0, bem_1.bem)(cn, { m: "isLink" }), disabled && (0, bem_1.bem)(cn, { m: "disabled" }), reverse && "flex-row-reverse", className), disabled: Component === "button" && disabled, target: target, rel: `${target === "_blank" ? "noopener noreferrer" : ""}`, ref: ref, href: safeHref, ...rest },
42
+ icon && (react_1.default.createElement(Icon_1.Icon, { className: (0, classnames_1.default)((0, bem_1.bem)(cn, { e: "icon" })), icon: icon, prefix: iconPrefix })),
43
+ children,
44
+ badgeLabel && (react_1.default.createElement(Badge_1.Badge, { className: (0, classnames_1.default)((0, bem_1.bem)(cn, { e: "badge" })), variant: badgeVariant, size: Badge_1.BADGE_SIZE.MICRO }, badgeLabel))));
45
+ });
42
46
  const CompoundReference = Object.assign(Reference, {
43
47
  Group: ReferenceGroup,
44
48
  });
@@ -1,2 +1,3 @@
1
1
  export declare const capitalize: (s: string) => string;
2
2
  export declare const camelToTitleCase: (s: string) => string;
3
+ export declare const isSafeUrl: (url: string) => boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.camelToTitleCase = exports.capitalize = void 0;
3
+ exports.isSafeUrl = exports.camelToTitleCase = exports.capitalize = void 0;
4
4
  const capitalize = (s) => {
5
5
  return s.charAt(0).toUpperCase() + s.slice(1);
6
6
  };
@@ -10,3 +10,14 @@ const camelToTitleCase = (s) => {
10
10
  return result.charAt(0).toUpperCase() + result.slice(1);
11
11
  };
12
12
  exports.camelToTitleCase = camelToTitleCase;
13
+ const isSafeUrl = (url) => {
14
+ const rejectedProtocols = ["javascript:"];
15
+ try {
16
+ const parsedUrl = new URL(url, window.location.origin);
17
+ return !rejectedProtocols.includes(parsedUrl.protocol);
18
+ }
19
+ catch (e) {
20
+ return false;
21
+ }
22
+ };
23
+ exports.isSafeUrl = isSafeUrl;
@@ -8,6 +8,7 @@ import React, {
8
8
  import { Icon, IconType, ICON_TYPE, ICON_STYLE_PREFIX } from "../Icon";
9
9
  import { Link } from "../Link";
10
10
  import { bem } from "../../utilities/bem";
11
+ import { isSafeUrl } from "../../utilities/strings";
11
12
  import { ButtonGroup } from "./ButtonGroup";
12
13
  import { THEME } from "../../types";
13
14
  import { ThemeContext } from "../../contexts";
@@ -131,6 +132,7 @@ const Button = forwardRef<HTMLElement, ButtonProps>(
131
132
  disabled,
132
133
  type: typeProp,
133
134
  // Other
135
+ href,
134
136
  ...rest
135
137
  }: ButtonProps,
136
138
  ref,
@@ -173,6 +175,7 @@ const Button = forwardRef<HTMLElement, ButtonProps>(
173
175
  type = "button";
174
176
  }
175
177
  const propsIfLink = Component === Link ? { noStyles: true } : {};
178
+ const safeHref = isSafeUrl(href) ? href : "#";
176
179
 
177
180
  if (variant === BUTTON_VARIANT.PLAIN) {
178
181
  return (
@@ -194,6 +197,7 @@ const Button = forwardRef<HTMLElement, ButtonProps>(
194
197
  className={buttonClassNames}
195
198
  disabled={isDisabled}
196
199
  ref={ref}
200
+ href={safeHref}
197
201
  {...propsIfLink}
198
202
  {...rest}
199
203
  >
@@ -1,6 +1,7 @@
1
1
  import React, { forwardRef, useContext } from "react";
2
2
  import classNames from "classnames";
3
3
  import { bem } from "../../utilities/bem";
4
+ import { isSafeUrl } from "../../utilities/strings";
4
5
  import { STATUS_VARIANT } from "../../types";
5
6
  import { StatusContext } from "../../contexts/status";
6
7
 
@@ -24,6 +25,7 @@ export const Link = forwardRef<
24
25
  noStyles,
25
26
  statusVariant: statusVariantProp,
26
27
  target,
28
+ href,
27
29
  ...rest
28
30
  } = props;
29
31
  const context = useContext(StatusContext);
@@ -36,6 +38,8 @@ export const Link = forwardRef<
36
38
  className,
37
39
  );
38
40
 
41
+ const safeHref = isSafeUrl(href) ? href : "#";
42
+
39
43
  return (
40
44
  <Component
41
45
  className={classes}
@@ -43,6 +47,7 @@ export const Link = forwardRef<
43
47
  target={target}
44
48
  rel={`${target === "_blank" ? "noopener noreferrer" : ""}`}
45
49
  ref={ref}
50
+ href={safeHref}
46
51
  {...rest}
47
52
  />
48
53
  );
@@ -2,6 +2,7 @@ import React, { forwardRef } from "react";
2
2
  import classNames from "classnames";
3
3
  import { Box, BoxProps } from "../Box";
4
4
  import { bem } from "../../utilities/bem";
5
+ import { isSafeUrl } from "../../utilities/strings";
5
6
  import { ICON_STYLE_PREFIX, Icon, IconType } from "../Icon";
6
7
  import { STATUS_VARIANT } from "../../types";
7
8
  import { Badge, BADGE_SIZE } from "../Badge";
@@ -42,43 +43,48 @@ const Reference = forwardRef<HTMLElement, ReferenceProps>(
42
43
  target,
43
44
  noLink,
44
45
  reverse = false,
46
+ href,
45
47
  ...rest
46
48
  },
47
49
  ref,
48
- ) => (
49
- <Component
50
- className={classNames(
51
- bem(cn),
52
- !noLink && bem(cn, { m: "isLink" }),
53
- disabled && bem(cn, { m: "disabled" }),
54
- reverse && "flex-row-reverse",
55
- className,
56
- )}
57
- disabled={Component === "button" && disabled}
58
- target={target}
59
- rel={`${target === "_blank" ? "noopener noreferrer" : ""}`}
60
- ref={ref}
61
- {...rest}
62
- >
63
- {icon && (
64
- <Icon
65
- className={classNames(bem(cn, { e: "icon" }))}
66
- icon={icon}
67
- prefix={iconPrefix}
68
- />
69
- )}
70
- {children}
71
- {badgeLabel && (
72
- <Badge
73
- className={classNames(bem(cn, { e: "badge" }))}
74
- variant={badgeVariant}
75
- size={BADGE_SIZE.MICRO}
76
- >
77
- {badgeLabel}
78
- </Badge>
79
- )}
80
- </Component>
81
- ),
50
+ ) => {
51
+ const safeHref = isSafeUrl(href) ? href : "#";
52
+ return (
53
+ <Component
54
+ className={classNames(
55
+ bem(cn),
56
+ !noLink && bem(cn, { m: "isLink" }),
57
+ disabled && bem(cn, { m: "disabled" }),
58
+ reverse && "flex-row-reverse",
59
+ className,
60
+ )}
61
+ disabled={Component === "button" && disabled}
62
+ target={target}
63
+ rel={`${target === "_blank" ? "noopener noreferrer" : ""}`}
64
+ ref={ref}
65
+ href={safeHref}
66
+ {...rest}
67
+ >
68
+ {icon && (
69
+ <Icon
70
+ className={classNames(bem(cn, { e: "icon" }))}
71
+ icon={icon}
72
+ prefix={iconPrefix}
73
+ />
74
+ )}
75
+ {children}
76
+ {badgeLabel && (
77
+ <Badge
78
+ className={classNames(bem(cn, { e: "badge" }))}
79
+ variant={badgeVariant}
80
+ size={BADGE_SIZE.MICRO}
81
+ >
82
+ {badgeLabel}
83
+ </Badge>
84
+ )}
85
+ </Component>
86
+ );
87
+ },
82
88
  );
83
89
 
84
90
  const CompoundReference = Object.assign(Reference, {
@@ -6,3 +6,14 @@ export const camelToTitleCase = (s: string) => {
6
6
  const result = s.replace(/([A-Z])/g, " $1");
7
7
  return result.charAt(0).toUpperCase() + result.slice(1);
8
8
  };
9
+
10
+ export const isSafeUrl = (url: string) => {
11
+ // eslint-disable-next-line no-script-url
12
+ const rejectedProtocols = ["javascript:"];
13
+ try {
14
+ const parsedUrl = new URL(url, window.location.origin);
15
+ return !rejectedProtocols.includes(parsedUrl.protocol);
16
+ } catch (e) {
17
+ return false;
18
+ }
19
+ };