@canonical/react-components 3.9.1 → 3.11.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 (47) hide show
  1. package/dist/components/ConfirmationButton/ConfirmationButton.js +5 -5
  2. package/dist/components/ConfirmationModal/ConfirmationModal.d.ts +5 -1
  3. package/dist/components/ConfirmationModal/ConfirmationModal.js +9 -3
  4. package/dist/components/PrefixedInput/PrefixedInput.d.ts +13 -0
  5. package/dist/components/PrefixedInput/PrefixedInput.js +64 -0
  6. package/dist/components/PrefixedInput/PrefixedInput.scss +22 -0
  7. package/dist/components/PrefixedInput/PrefixedInput.stories.d.ts +11 -0
  8. package/dist/components/PrefixedInput/PrefixedInput.stories.js +58 -0
  9. package/dist/components/PrefixedInput/PrefixedInput.test.d.ts +1 -0
  10. package/dist/components/PrefixedInput/index.d.ts +1 -0
  11. package/dist/components/PrefixedInput/index.js +13 -0
  12. package/dist/components/PrefixedIpInput/PrefixedIpInput.d.ts +27 -0
  13. package/dist/components/PrefixedIpInput/PrefixedIpInput.js +65 -0
  14. package/dist/components/PrefixedIpInput/PrefixedIpInput.stories.d.ts +13 -0
  15. package/dist/components/PrefixedIpInput/PrefixedIpInput.stories.js +137 -0
  16. package/dist/components/PrefixedIpInput/PrefixedIpInput.test.d.ts +1 -0
  17. package/dist/components/PrefixedIpInput/index.d.ts +2 -0
  18. package/dist/components/PrefixedIpInput/index.js +56 -0
  19. package/dist/components/PrefixedIpInput/utils.d.ts +39 -0
  20. package/dist/components/PrefixedIpInput/utils.js +125 -0
  21. package/dist/components/PrefixedIpInput/utils.test.d.ts +1 -0
  22. package/dist/esm/components/ConfirmationButton/ConfirmationButton.js +5 -5
  23. package/dist/esm/components/ConfirmationModal/ConfirmationModal.d.ts +5 -1
  24. package/dist/esm/components/ConfirmationModal/ConfirmationModal.js +9 -3
  25. package/dist/esm/components/PrefixedInput/PrefixedInput.d.ts +13 -0
  26. package/dist/esm/components/PrefixedInput/PrefixedInput.js +57 -0
  27. package/dist/esm/components/PrefixedInput/PrefixedInput.scss +22 -0
  28. package/dist/esm/components/PrefixedInput/PrefixedInput.stories.d.ts +11 -0
  29. package/dist/esm/components/PrefixedInput/PrefixedInput.stories.js +51 -0
  30. package/dist/esm/components/PrefixedInput/PrefixedInput.test.d.ts +1 -0
  31. package/dist/esm/components/PrefixedInput/index.d.ts +1 -0
  32. package/dist/esm/components/PrefixedInput/index.js +1 -0
  33. package/dist/esm/components/PrefixedIpInput/PrefixedIpInput.d.ts +27 -0
  34. package/dist/esm/components/PrefixedIpInput/PrefixedIpInput.js +58 -0
  35. package/dist/esm/components/PrefixedIpInput/PrefixedIpInput.stories.d.ts +13 -0
  36. package/dist/esm/components/PrefixedIpInput/PrefixedIpInput.stories.js +128 -0
  37. package/dist/esm/components/PrefixedIpInput/PrefixedIpInput.test.d.ts +1 -0
  38. package/dist/esm/components/PrefixedIpInput/index.d.ts +2 -0
  39. package/dist/esm/components/PrefixedIpInput/index.js +2 -0
  40. package/dist/esm/components/PrefixedIpInput/utils.d.ts +39 -0
  41. package/dist/esm/components/PrefixedIpInput/utils.js +112 -0
  42. package/dist/esm/components/PrefixedIpInput/utils.test.d.ts +1 -0
  43. package/dist/esm/index.d.ts +4 -0
  44. package/dist/esm/index.js +2 -0
  45. package/dist/index.d.ts +4 -0
  46. package/dist/index.js +65 -0
  47. package/package.json +1 -1
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isIpInSubnet = exports.isIPv4 = exports.getIpRangeFromCidr = exports.getImmutableAndEditableOctets = exports.getImmutableAndEditable = exports.getFirstValidIp = exports.convertIpToUint32 = void 0;
7
+ /**
8
+ * Checks if a given IP address is a valid IPv4 address.
9
+ * @param ip The IP address to check
10
+ * @returns True if the IP is a valid IPv4 address, false otherwise
11
+ */
12
+ const isIPv4 = ip => {
13
+ const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
14
+ return ipv4Regex.test(ip);
15
+ };
16
+
17
+ /**
18
+ * Takes a subnet CIDR notation (IPv4) and returns the first and last IP of the subnet.
19
+ * The network and host addresses are excluded.
20
+ *
21
+ * @param cidr The CIDR notation of the subnet
22
+ * @returns The first and last valid IP addresses as two strings in a list.
23
+ */
24
+ exports.isIPv4 = isIPv4;
25
+ const getIpRangeFromCidr = cidr => {
26
+ // https://gist.github.com/binarymax/6114792
27
+
28
+ // Get start IP and number of valid addresses
29
+ const [startIp, mask] = cidr.split("/");
30
+ const numberOfAddresses = (1 << 32 - parseInt(mask)) - 1;
31
+
32
+ // IPv4 can be represented by an unsigned 32-bit integer, so we can use a Uint32Array to store the IP
33
+ const buffer = new ArrayBuffer(4); //4 octets
34
+ const int32 = new Uint32Array(buffer);
35
+
36
+ // Convert starting IP to Uint32 and add the number of addresses to get the end IP.
37
+ // Subtract 1 from the number of addresses to exclude the broadcast address.
38
+ int32[0] = convertIpToUint32(startIp) + numberOfAddresses - 1;
39
+
40
+ // Convert the buffer to a Uint8Array to get the octets, then convert it to an array
41
+ const arrayApplyBuffer = Array.from(new Uint8Array(buffer));
42
+
43
+ // Reverse the octets and join them with "." to get the end IP
44
+ const endIp = arrayApplyBuffer.reverse().join(".");
45
+ const firstValidIp = getFirstValidIp(startIp);
46
+ return [firstValidIp, endIp];
47
+ };
48
+ exports.getIpRangeFromCidr = getIpRangeFromCidr;
49
+ const getFirstValidIp = ip => {
50
+ const buffer = new ArrayBuffer(4); //4 octets
51
+ const int32 = new Uint32Array(buffer);
52
+
53
+ // add 1 because the first IP is the network address
54
+ int32[0] = convertIpToUint32(ip) + 1;
55
+ const arrayApplyBuffer = Array.from(new Uint8Array(buffer));
56
+ return arrayApplyBuffer.reverse().join(".");
57
+ };
58
+ exports.getFirstValidIp = getFirstValidIp;
59
+ const convertIpToUint32 = ip => {
60
+ const octets = ip.split(".").map(a => parseInt(a));
61
+ const buffer = new ArrayBuffer(4);
62
+ const int32 = new Uint32Array(buffer);
63
+ int32[0] = (octets[0] << 24) + (octets[1] << 16) + (octets[2] << 8) + octets[3];
64
+ return int32[0];
65
+ };
66
+
67
+ /**
68
+ * Checks if an IPv4 address is valid for the given subnet.
69
+ *
70
+ * @param ip The IPv4 address to check, as a string
71
+ * @param cidr The subnet's CIDR notation e.g. 192.168.0.0/24
72
+ * @returns True if the IP is in the subnet, false otherwise
73
+ */
74
+ exports.convertIpToUint32 = convertIpToUint32;
75
+ const isIpInSubnet = (ip, cidr) => {
76
+ const [startIP, endIP] = getIpRangeFromCidr(cidr);
77
+ const ipUint32 = convertIpToUint32(ip);
78
+ const startIPUint32 = convertIpToUint32(startIP);
79
+ const endIPUint32 = convertIpToUint32(endIP);
80
+ return ipUint32 >= startIPUint32 && ipUint32 <= endIPUint32;
81
+ };
82
+
83
+ /**
84
+ * Separates the immutable and editable octets of an IPv4 subnet range.
85
+ *
86
+ * @param startIp The start IP of the subnet
87
+ * @param endIp The end IP of the subnet
88
+ * @returns The immutable and editable octects as two strings in a list
89
+ */
90
+ exports.isIpInSubnet = isIpInSubnet;
91
+ const getImmutableAndEditableOctets = (startIp, endIp) => {
92
+ const startIpOctetList = startIp.split(".");
93
+ const endIpOctetList = endIp.split(".");
94
+ const immutable = [];
95
+ const editable = [];
96
+ startIpOctetList.forEach((octet, index) => {
97
+ if (octet === endIpOctetList[index]) {
98
+ immutable.push(octet);
99
+ } else {
100
+ editable.push("[".concat(octet, "-").concat(endIpOctetList[index], "]"));
101
+ }
102
+ });
103
+ return [immutable.join("."), editable.join(".")];
104
+ };
105
+
106
+ /**
107
+ * Get the immutable and editable parts of an IPv4 or IPv6 subnet.
108
+ *
109
+ * @param cidr The CIDR notation of the subnet
110
+ * @returns The immutable and editable as two strings in a list
111
+ */
112
+ exports.getImmutableAndEditableOctets = getImmutableAndEditableOctets;
113
+ const getImmutableAndEditable = cidr => {
114
+ const isIPV4 = isIPv4(cidr.split("/")[0]);
115
+ if (isIPV4) {
116
+ const [startIp, endIp] = getIpRangeFromCidr(cidr);
117
+ return getImmutableAndEditableOctets(startIp, endIp);
118
+ }
119
+ const [networkAddress] = cidr.split("/");
120
+ const immutableIPV6 = networkAddress.substring(0, networkAddress.lastIndexOf(":"));
121
+ const ipv6PlaceholderColons = 7 - (immutableIPV6.match(/:/g) || []).length; // 7 is the maximum number of colons in an IPv6 address
122
+ const editableIPV6 = "".concat("0000:".repeat(ipv6PlaceholderColons), "0000");
123
+ return [immutableIPV6, editableIPV6];
124
+ };
125
+ exports.getImmutableAndEditable = getImmutableAndEditable;
@@ -0,0 +1 @@
1
+ export {};
@@ -30,8 +30,7 @@ export var ConfirmationButton = _ref => {
30
30
  var {
31
31
  openPortal,
32
32
  closePortal,
33
- isOpen,
34
- Portal
33
+ isOpen
35
34
  } = usePortal();
36
35
  var handleCancelModal = () => {
37
36
  closePortal();
@@ -57,13 +56,14 @@ export var ConfirmationButton = _ref => {
57
56
  }
58
57
  shiftClickEnabled ? handleShiftClick(e) : openPortal(e);
59
58
  };
60
- return /*#__PURE__*/React.createElement(React.Fragment, null, isOpen && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(ConfirmationModal, _extends({}, confirmationModalProps, {
59
+ return /*#__PURE__*/React.createElement(React.Fragment, null, isOpen && /*#__PURE__*/React.createElement(ConfirmationModal, _extends({}, confirmationModalProps, {
61
60
  close: handleCancelModal,
62
61
  confirmButtonLabel: confirmationModalProps.confirmButtonLabel,
63
- onConfirm: handleConfirmModal
62
+ onConfirm: handleConfirmModal,
63
+ renderInPortal: true
64
64
  }), confirmationModalProps.children, showShiftClickHint && /*#__PURE__*/React.createElement("p", {
65
65
  className: "p-text--small u-text--muted u-hide--small"
66
- }, "Next time, you can skip this confirmation by holding", " ", /*#__PURE__*/React.createElement("code", null, "SHIFT"), " and clicking the action."))), /*#__PURE__*/React.createElement(ActionButton, _extends({}, actionButtonProps, {
66
+ }, "Next time, you can skip this confirmation by holding", " ", /*#__PURE__*/React.createElement("code", null, "SHIFT"), " and clicking the action.")), /*#__PURE__*/React.createElement(ActionButton, _extends({}, actionButtonProps, {
67
67
  onClick: handleClick,
68
68
  title: generateTitle(onHoverText !== null && onHoverText !== void 0 ? onHoverText : confirmationModalProps.confirmButtonLabel)
69
69
  }), actionButtonProps.children));
@@ -45,9 +45,13 @@ export type Props = PropsWithSpread<{
45
45
  * Whether the confirm button should be disabled.
46
46
  */
47
47
  confirmButtonDisabled?: boolean;
48
+ /**
49
+ * Whether to render the modal inside a Portal component.
50
+ */
51
+ renderInPortal?: boolean;
48
52
  }, Omit<ModalProps, "buttonRow">>;
49
53
  /**
50
54
  * `ConfirmationModal` is a specialised version of the [Modal](?path=/docs/modal--default-story) component to prompt a confirmation from the user before executing an action.
51
55
  */
52
- export declare const ConfirmationModal: ({ cancelButtonLabel, cancelButtonProps, children, confirmButtonAppearance, confirmButtonLabel, confirmExtra, onConfirm, confirmButtonLoading, confirmButtonDisabled, confirmButtonProps, ...props }: Props) => React.JSX.Element;
56
+ export declare const ConfirmationModal: ({ cancelButtonLabel, cancelButtonProps, children, confirmButtonAppearance, confirmButtonLabel, confirmExtra, onConfirm, confirmButtonLoading, confirmButtonDisabled, confirmButtonProps, renderInPortal, ...props }: Props) => React.JSX.Element;
53
57
  export default ConfirmationModal;
@@ -1,4 +1,4 @@
1
- var _excluded = ["cancelButtonLabel", "cancelButtonProps", "children", "confirmButtonAppearance", "confirmButtonLabel", "confirmExtra", "onConfirm", "confirmButtonLoading", "confirmButtonDisabled", "confirmButtonProps"];
1
+ var _excluded = ["cancelButtonLabel", "cancelButtonProps", "children", "confirmButtonAppearance", "confirmButtonLabel", "confirmExtra", "onConfirm", "confirmButtonLoading", "confirmButtonDisabled", "confirmButtonProps", "renderInPortal"];
2
2
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
3
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
4
4
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
@@ -6,6 +6,7 @@ import React from "react";
6
6
  import Button from "../Button";
7
7
  import Modal from "../Modal";
8
8
  import ActionButton from "../ActionButton";
9
+ import { usePortal } from "../../external";
9
10
  /**
10
11
  * `ConfirmationModal` is a specialised version of the [Modal](?path=/docs/modal--default-story) component to prompt a confirmation from the user before executing an action.
11
12
  */
@@ -21,9 +22,13 @@ export var ConfirmationModal = _ref => {
21
22
  onConfirm,
22
23
  confirmButtonLoading,
23
24
  confirmButtonDisabled,
24
- confirmButtonProps
25
+ confirmButtonProps,
26
+ renderInPortal = false
25
27
  } = _ref,
26
28
  props = _objectWithoutProperties(_ref, _excluded);
29
+ var {
30
+ Portal
31
+ } = usePortal();
27
32
  var handleClick = action => event => {
28
33
  if (!props.shouldPropagateClickEvent) {
29
34
  event.stopPropagation();
@@ -32,7 +37,7 @@ export var ConfirmationModal = _ref => {
32
37
  action(event);
33
38
  }
34
39
  };
35
- return /*#__PURE__*/React.createElement(Modal, _extends({
40
+ var ModalElement = /*#__PURE__*/React.createElement(Modal, _extends({
36
41
  buttonRow: /*#__PURE__*/React.createElement(React.Fragment, null, confirmExtra, /*#__PURE__*/React.createElement(Button, _extends({}, cancelButtonProps, {
37
42
  type: (_cancelButtonProps$ty = cancelButtonProps === null || cancelButtonProps === void 0 ? void 0 : cancelButtonProps.type) !== null && _cancelButtonProps$ty !== void 0 ? _cancelButtonProps$ty : "button",
38
43
  className: "u-no-margin--bottom",
@@ -45,5 +50,6 @@ export var ConfirmationModal = _ref => {
45
50
  disabled: confirmButtonDisabled
46
51
  }), confirmButtonLabel))
47
52
  }, props), children);
53
+ return renderInPortal ? /*#__PURE__*/React.createElement(Portal, null, ModalElement) : ModalElement;
48
54
  };
49
55
  export default ConfirmationModal;
@@ -0,0 +1,13 @@
1
+ import { type ReactElement } from "react";
2
+ import { type InputProps } from "../Input";
3
+ import "./PrefixedInput.scss";
4
+ import { PropsWithSpread } from "../../types";
5
+ export type PrefixedInputProps = PropsWithSpread<{
6
+ /**
7
+ * The immutable text that appears at the beginning of the input field.
8
+ * This text is not editable by the user and visually appears inside the input.
9
+ */
10
+ immutableText: string;
11
+ }, Omit<InputProps, "type">>;
12
+ declare const PrefixedInput: ({ immutableText, ...props }: PrefixedInputProps) => ReactElement;
13
+ export default PrefixedInput;
@@ -0,0 +1,57 @@
1
+ var _excluded = ["immutableText"];
2
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
4
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
5
+ import React from "react";
6
+ import { useLayoutEffect, useRef, useCallback } from "react";
7
+ import Input from "../Input";
8
+ import classNames from "classnames";
9
+ import "./PrefixedInput.scss";
10
+ import { useListener } from "../../hooks/useListener";
11
+
12
+ // export type PrefixedInputProps = Omit<InputProps, "type"> & {
13
+ // /**
14
+ // * The immutable text that appears at the beginning of the input field.
15
+ // * This text is not editable by the user and visually appears inside the input.
16
+ // */
17
+ // immutableText: string;
18
+ // };
19
+
20
+ var PrefixedInput = _ref => {
21
+ var {
22
+ immutableText
23
+ } = _ref,
24
+ props = _objectWithoutProperties(_ref, _excluded);
25
+ var prefixTextRef = useRef(null);
26
+ var inputWrapperRef = useRef(null);
27
+ var updatePadding = useCallback(() => {
28
+ var _inputWrapperRef$curr;
29
+ var prefixElement = prefixTextRef.current;
30
+ var inputElement = (_inputWrapperRef$curr = inputWrapperRef.current) === null || _inputWrapperRef$curr === void 0 ? void 0 : _inputWrapperRef$curr.querySelector("input");
31
+ if (prefixElement && inputElement) {
32
+ // Adjust the left padding of the input to be the same width as the immutable text.
33
+ // This displays the user input and the unchangeable text together as one combined string.
34
+ var prefixWidth = prefixElement.getBoundingClientRect().width;
35
+ inputElement.style.paddingLeft = "".concat(prefixWidth, "px");
36
+ }
37
+ }, []);
38
+ useListener(window, updatePadding, "resize", true);
39
+ useLayoutEffect(() => {
40
+ updatePadding();
41
+ }, [immutableText, props.label, updatePadding]);
42
+ return /*#__PURE__*/React.createElement("div", {
43
+ className: classNames("prefixed-input", {
44
+ "prefixed-input--with-label": !!props.label
45
+ })
46
+ }, /*#__PURE__*/React.createElement("div", {
47
+ className: "prefixed-input__text",
48
+ ref: prefixTextRef
49
+ }, immutableText), /*#__PURE__*/React.createElement("div", {
50
+ ref: inputWrapperRef
51
+ }, /*#__PURE__*/React.createElement(Input, _extends({}, props, {
52
+ className: classNames("prefixed-input__input", props.className),
53
+ type: "text",
54
+ wrapperClassName: classNames("prefixed-input__wrapper", props.wrapperClassName)
55
+ }))));
56
+ };
57
+ export default PrefixedInput;
@@ -0,0 +1,22 @@
1
+ @import "vanilla-framework";
2
+
3
+ .prefixed-input {
4
+ position: relative;
5
+
6
+ .prefixed-input__input {
7
+ padding-top: 0.25rem;
8
+ }
9
+
10
+ .prefixed-input__text {
11
+ padding-left: $spv--large;
12
+ padding-top: 0.3rem;
13
+ pointer-events: none;
14
+ position: absolute;
15
+ }
16
+
17
+ &--with-label {
18
+ .prefixed-input__text {
19
+ top: 2.5rem;
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import PrefixedInput from "./PrefixedInput";
3
+ declare const meta: Meta<typeof PrefixedInput>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PrefixedInput>;
6
+ export declare const Default: Story;
7
+ export declare const WithLabel: Story;
8
+ export declare const Disabled: Story;
9
+ export declare const WithError: Story;
10
+ export declare const WithHelpText: Story;
11
+ export declare const Required: Story;
@@ -0,0 +1,51 @@
1
+ import PrefixedInput from "./PrefixedInput";
2
+ var meta = {
3
+ component: PrefixedInput,
4
+ tags: ["autodocs"]
5
+ };
6
+ export default meta;
7
+ export var Default = {
8
+ args: {
9
+ immutableText: "https://",
10
+ placeholder: "example.com"
11
+ }
12
+ };
13
+ export var WithLabel = {
14
+ args: {
15
+ immutableText: "https://",
16
+ label: "Website URL",
17
+ placeholder: "example.com"
18
+ }
19
+ };
20
+ export var Disabled = {
21
+ args: {
22
+ immutableText: "@",
23
+ label: "Username",
24
+ placeholder: "username",
25
+ disabled: true
26
+ }
27
+ };
28
+ export var WithError = {
29
+ args: {
30
+ immutableText: "https://",
31
+ label: "Website URL",
32
+ placeholder: "example.com",
33
+ error: "Invalid URL format"
34
+ }
35
+ };
36
+ export var WithHelpText = {
37
+ args: {
38
+ immutableText: "User ID:",
39
+ label: "User Identifier",
40
+ placeholder: " Enter user ID",
41
+ help: "This will be used to identify your account"
42
+ }
43
+ };
44
+ export var Required = {
45
+ args: {
46
+ immutableText: "https://",
47
+ label: "Website URL",
48
+ placeholder: "example.com",
49
+ required: true
50
+ }
51
+ };
@@ -0,0 +1 @@
1
+ export { default, type PrefixedInputProps } from "./PrefixedInput";
@@ -0,0 +1 @@
1
+ export { default } from "./PrefixedInput";
@@ -0,0 +1,27 @@
1
+ import { type ReactElement } from "react";
2
+ import { type PrefixedInputProps } from "../PrefixedInput";
3
+ import { PropsWithSpread } from "../../types";
4
+ export type PrefixedIpInputProps = PropsWithSpread<{
5
+ /**
6
+ * The CIDR for the subnet (e.g., "192.168.1.0/24" or "2001:db8::/32").
7
+ * Used to calculate the immutable prefix and available IP range.
8
+ */
9
+ cidr: string;
10
+ /**
11
+ * The full IP address value (if available).
12
+ * For IPv4: e.g., "192.168.1.100"
13
+ * For IPv6: e.g., "2001:db8::1"
14
+ */
15
+ ip: string;
16
+ /**
17
+ * The name attribute for the input field.
18
+ */
19
+ name: string;
20
+ /**
21
+ * Callback function that is called when the IP address changes.
22
+ * Receives the full IP address as a string parameter.
23
+ */
24
+ onIpChange: (ip: string) => void;
25
+ }, Omit<PrefixedInputProps, "immutableText" | "maxLength" | "placeholder" | "name">>;
26
+ declare const PrefixedIpInput: ({ cidr, help, onIpChange, ip, name, ...props }: PrefixedIpInputProps) => ReactElement;
27
+ export default PrefixedIpInput;
@@ -0,0 +1,58 @@
1
+ var _excluded = ["cidr", "help", "onIpChange", "ip", "name"];
2
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
4
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
5
+ import React from "react";
6
+ import PrefixedInput from "../PrefixedInput";
7
+ import { getImmutableAndEditable, isIPv4 } from "./utils";
8
+ var PrefixedIpInput = _ref => {
9
+ var {
10
+ cidr,
11
+ help,
12
+ onIpChange,
13
+ ip,
14
+ name
15
+ } = _ref,
16
+ props = _objectWithoutProperties(_ref, _excluded);
17
+ var [networkAddress] = cidr.split("/");
18
+ var isIPV4 = isIPv4(networkAddress);
19
+ var [immutable, editable] = getImmutableAndEditable(cidr);
20
+ var inputValue = isIPV4 ? ip.split(".").slice(immutable.split(".").length).join(".") : ip.replace(immutable, "");
21
+ var getIPv4MaxLength = () => {
22
+ var immutableOctetsLength = immutable.split(".").length;
23
+ var lengths = [15, 11, 7, 3]; // Corresponding to 0-3 immutable octets
24
+ return lengths[immutableOctetsLength];
25
+ };
26
+ var maxLength = isIPV4 ? getIPv4MaxLength() : editable.length;
27
+ var placeholder = props.disabled ? "" : editable;
28
+ var setIp = editableValue => {
29
+ var fullIp = editableValue ? isIPV4 ? "".concat(immutable, ".").concat(editableValue) : "".concat(immutable).concat(editableValue) : "";
30
+ onIpChange(fullIp);
31
+ };
32
+ var handlePaste = e => {
33
+ e.preventDefault();
34
+ var pastedText = e.clipboardData.getData("text");
35
+ if (isIPV4) {
36
+ var octets = pastedText.split(".");
37
+ var trimmed = octets.slice(0 - editable.split(".").length);
38
+ var _ip = trimmed.join(".");
39
+ setIp(_ip);
40
+ } else {
41
+ var _ip2 = pastedText.replace(immutable, "");
42
+ setIp(_ip2);
43
+ }
44
+ };
45
+ return /*#__PURE__*/React.createElement(PrefixedInput, _extends({
46
+ help: help ? help : /*#__PURE__*/React.createElement(React.Fragment, null, " ", isIPV4 ? /*#__PURE__*/React.createElement(React.Fragment, null, " ", "The available range in this subnet is", " ", /*#__PURE__*/React.createElement("code", null, immutable, ".", editable, " ")) : /*#__PURE__*/React.createElement(React.Fragment, null, " ", "The available IPV6 address range is", " ", /*#__PURE__*/React.createElement("code", null, immutable, editable, " ")), "."),
47
+ immutableText: isIPV4 ? "".concat(immutable, ".") : immutable,
48
+ maxLength: maxLength,
49
+ name: name,
50
+ onPaste: handlePaste,
51
+ value: inputValue,
52
+ onChange: e => {
53
+ setIp(e.target.value);
54
+ },
55
+ placeholder: placeholder
56
+ }, props));
57
+ };
58
+ export default PrefixedIpInput;
@@ -0,0 +1,13 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import PrefixedIpInput from "./PrefixedIpInput";
3
+ declare const meta: Meta<typeof PrefixedIpInput>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PrefixedIpInput>;
6
+ export declare const IPv4Default: Story;
7
+ export declare const IPv4WithValue: Story;
8
+ export declare const IPv4WithError: Story;
9
+ export declare const IPv4Disabled: Story;
10
+ export declare const IPv6Default: Story;
11
+ export declare const IPv6WithValue: Story;
12
+ export declare const WithCustomHelp: Story;
13
+ export declare const Required: Story;
@@ -0,0 +1,128 @@
1
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+ import React, { useState } from "react";
3
+ import PrefixedIpInput from "./PrefixedIpInput";
4
+ var PrefixedIpInputWrapper = args => {
5
+ var [ip, setIp] = useState(args.ip);
6
+ return /*#__PURE__*/React.createElement(PrefixedIpInput, _extends({}, args, {
7
+ ip: ip,
8
+ onIpChange: newIp => {
9
+ var _args$onIpChange;
10
+ setIp(newIp);
11
+ (_args$onIpChange = args.onIpChange) === null || _args$onIpChange === void 0 || _args$onIpChange.call(args, newIp);
12
+ }
13
+ }));
14
+ };
15
+ var meta = {
16
+ component: PrefixedIpInput,
17
+ tags: ["autodocs"],
18
+ argTypes: {
19
+ ip: {
20
+ control: "text"
21
+ },
22
+ cidr: {
23
+ control: "text"
24
+ },
25
+ label: {
26
+ control: "text"
27
+ },
28
+ name: {
29
+ control: "text"
30
+ },
31
+ error: {
32
+ control: "text"
33
+ },
34
+ help: {
35
+ control: "text"
36
+ },
37
+ disabled: {
38
+ control: "boolean"
39
+ },
40
+ required: {
41
+ control: "boolean"
42
+ }
43
+ },
44
+ render: args => /*#__PURE__*/React.createElement(PrefixedIpInputWrapper, args)
45
+ };
46
+ export default meta;
47
+ export var IPv4Default = {
48
+ name: "IPv4 default",
49
+ args: {
50
+ cidr: "192.168.1.0/24",
51
+ ip: "",
52
+ name: "ip-address",
53
+ label: "IP Address",
54
+ onIpChange: ip => console.log("IP changed:", ip)
55
+ }
56
+ };
57
+ export var IPv4WithValue = {
58
+ name: "IPv4 with value",
59
+ args: {
60
+ cidr: "192.168.1.0/24",
61
+ ip: "192.168.1.100",
62
+ name: "ip-address",
63
+ label: "IP Address",
64
+ onIpChange: ip => console.log("IP changed:", ip)
65
+ }
66
+ };
67
+ export var IPv4WithError = {
68
+ name: "IPv4 with error",
69
+ args: {
70
+ cidr: "192.168.1.0/24",
71
+ ip: "192.168.1.256",
72
+ name: "ip-address",
73
+ label: "IP Address",
74
+ error: "Invalid IP address",
75
+ onIpChange: ip => console.log("IP changed:", ip)
76
+ }
77
+ };
78
+ export var IPv4Disabled = {
79
+ name: "IPv4 disabled",
80
+ args: {
81
+ cidr: "192.168.1.0/24",
82
+ ip: "192.168.1.50",
83
+ name: "ip-address",
84
+ label: "IP Address",
85
+ disabled: true,
86
+ onIpChange: ip => console.log("IP changed:", ip)
87
+ }
88
+ };
89
+ export var IPv6Default = {
90
+ name: "IPv6 default",
91
+ args: {
92
+ cidr: "2001:db8::/32",
93
+ ip: "",
94
+ name: "ipv6-address",
95
+ label: "IPv6 Address",
96
+ onIpChange: ip => console.log("IP changed:", ip)
97
+ }
98
+ };
99
+ export var IPv6WithValue = {
100
+ name: "IPv6 with value",
101
+ args: {
102
+ cidr: "2001:db8::/32",
103
+ ip: "2001:db8::1",
104
+ name: "ipv6-address",
105
+ label: "IPv6 Address",
106
+ onIpChange: ip => console.log("IP changed:", ip)
107
+ }
108
+ };
109
+ export var WithCustomHelp = {
110
+ args: {
111
+ cidr: "10.0.0.0/16",
112
+ ip: "",
113
+ name: "ip-address",
114
+ label: "IP Address",
115
+ help: "Enter a custom IP address for this device",
116
+ onIpChange: ip => console.log("IP changed:", ip)
117
+ }
118
+ };
119
+ export var Required = {
120
+ args: {
121
+ cidr: "192.168.0.0/24",
122
+ ip: "",
123
+ name: "ip-address",
124
+ label: "IP Address",
125
+ required: true,
126
+ onIpChange: ip => console.log("IP changed:", ip)
127
+ }
128
+ };
@@ -0,0 +1,2 @@
1
+ export { default, type PrefixedIpInputProps } from "./PrefixedIpInput";
2
+ export { isIPv4, getIpRangeFromCidr, getFirstValidIp, convertIpToUint32, isIpInSubnet, getImmutableAndEditableOctets, getImmutableAndEditable, } from "./utils";
@@ -0,0 +1,2 @@
1
+ export { default } from "./PrefixedIpInput";
2
+ export { isIPv4, getIpRangeFromCidr, getFirstValidIp, convertIpToUint32, isIpInSubnet, getImmutableAndEditableOctets, getImmutableAndEditable } from "./utils";