@openzeppelin/ui-components 3.0.0 → 3.1.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/dist/index.cjs +387 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -8
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +144 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +384 -130
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ let sonner = require("sonner");
|
|
|
38
38
|
let next_themes = require("next-themes");
|
|
39
39
|
|
|
40
40
|
//#region src/version.ts
|
|
41
|
-
const VERSION = "3.
|
|
41
|
+
const VERSION = "3.1.0";
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/components/ui/accordion.tsx
|
|
@@ -1348,7 +1348,7 @@ function NetworkIcon({ network, className, size = 16, variant = "branded" }) {
|
|
|
1348
1348
|
//#endregion
|
|
1349
1349
|
//#region src/components/ui/network-selector.tsx
|
|
1350
1350
|
/** Searchable dropdown selector for blockchain networks with optional grouping and multi-select. */
|
|
1351
|
-
function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetworkType, getNetworkId, groupByEcosystem = false, getEcosystem, filterNetwork, className, placeholder = "Select Network", ...modeProps }) {
|
|
1351
|
+
function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetworkType, getNetworkId, groupByEcosystem = false, getEcosystem, filterNetwork, isNetworkDisabled, getNetworkDisabledLabel, className, placeholder = "Select Network", ...modeProps }) {
|
|
1352
1352
|
const [open, setOpen] = react.useState(false);
|
|
1353
1353
|
const [searchQuery, setSearchQuery] = react.useState("");
|
|
1354
1354
|
const isMultiple = modeProps.multiple === true;
|
|
@@ -1389,6 +1389,7 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
|
|
|
1389
1389
|
getNetworkId
|
|
1390
1390
|
]);
|
|
1391
1391
|
const handleSelect = react.useCallback((network) => {
|
|
1392
|
+
if (isNetworkDisabled?.(network)) return;
|
|
1392
1393
|
if (isMultiple && selectedNetworkIds && onSelectionChange) {
|
|
1393
1394
|
const id = getNetworkId(network);
|
|
1394
1395
|
onSelectionChange(selectedNetworkIds.includes(id) ? selectedNetworkIds.filter((x) => x !== id) : [...selectedNetworkIds, id]);
|
|
@@ -1401,7 +1402,8 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
|
|
|
1401
1402
|
selectedNetworkIds,
|
|
1402
1403
|
onSelectionChange,
|
|
1403
1404
|
onSelectNetwork,
|
|
1404
|
-
getNetworkId
|
|
1405
|
+
getNetworkId,
|
|
1406
|
+
isNetworkDisabled
|
|
1405
1407
|
]);
|
|
1406
1408
|
const handleClearAll = react.useCallback(() => {
|
|
1407
1409
|
if (isMultiple && onSelectionChange) onSelectionChange([]);
|
|
@@ -1487,31 +1489,47 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
|
|
|
1487
1489
|
className: "text-xs font-medium text-muted-foreground",
|
|
1488
1490
|
children: group
|
|
1489
1491
|
}),
|
|
1490
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuGroup, { children: groupNetworks.map((network) =>
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuGroup, { children: groupNetworks.map((network) => {
|
|
1493
|
+
const disabled = isNetworkDisabled?.(network) ?? false;
|
|
1494
|
+
const disabledLabel = disabled ? getNetworkDisabledLabel?.(network) : void 0;
|
|
1495
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
1496
|
+
disabled,
|
|
1497
|
+
onSelect: (e) => {
|
|
1498
|
+
if (disabled) {
|
|
1499
|
+
e.preventDefault();
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
if (isMultiple) e.preventDefault();
|
|
1503
|
+
handleSelect(network);
|
|
1504
|
+
},
|
|
1505
|
+
className: (0, _openzeppelin_ui_utils.cn)("gap-2", disabled && "cursor-not-allowed opacity-60"),
|
|
1506
|
+
children: [
|
|
1507
|
+
isMultiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1508
|
+
className: (0, _openzeppelin_ui_utils.cn)("flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border transition-colors", isSelected(network) ? "border-selected bg-selected text-selected-foreground" : "border-input bg-background", disabled && "opacity-50"),
|
|
1509
|
+
children: isSelected(network) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-3 w-3" })
|
|
1510
|
+
}) : null,
|
|
1511
|
+
getNetworkIcon?.(network),
|
|
1512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1513
|
+
className: "flex flex-1 items-center gap-2 min-w-0",
|
|
1514
|
+
children: [
|
|
1515
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1516
|
+
className: "truncate",
|
|
1517
|
+
children: getNetworkLabel(network)
|
|
1518
|
+
}),
|
|
1519
|
+
getNetworkType && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1520
|
+
className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase text-muted-foreground",
|
|
1521
|
+
children: getNetworkType(network)
|
|
1522
|
+
}),
|
|
1523
|
+
disabledLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1524
|
+
className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
|
|
1525
|
+
children: disabledLabel
|
|
1526
|
+
}) : null
|
|
1527
|
+
]
|
|
1528
|
+
}),
|
|
1529
|
+
!isMultiple && isSelected(network) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-4 w-4 text-selected opacity-100" })
|
|
1530
|
+
]
|
|
1531
|
+
}, getNetworkId(network));
|
|
1532
|
+
}) }),
|
|
1515
1533
|
index < Object.keys(groupedNetworks).length - 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuSeparator, {})
|
|
1516
1534
|
] }, group))]
|
|
1517
1535
|
})]
|
|
@@ -2934,6 +2952,312 @@ function AddressField({ id, label, placeholder, helperText, control, name, width
|
|
|
2934
2952
|
}
|
|
2935
2953
|
AddressField.displayName = "AddressField";
|
|
2936
2954
|
|
|
2955
|
+
//#endregion
|
|
2956
|
+
//#region src/components/fields/TextAreaField.tsx
|
|
2957
|
+
/**
|
|
2958
|
+
* Multi-line text input field component specifically designed for React Hook Form integration.
|
|
2959
|
+
*
|
|
2960
|
+
* Architecture flow:
|
|
2961
|
+
* 1. Form schemas are generated from contract functions using adapters
|
|
2962
|
+
* 2. TransactionForm renders the overall form structure with React Hook Form
|
|
2963
|
+
* 3. DynamicFormField selects the appropriate field component based on field type
|
|
2964
|
+
* 4. BaseField provides consistent layout and hook form integration
|
|
2965
|
+
* 5. This component handles textarea-specific rendering and validation
|
|
2966
|
+
*
|
|
2967
|
+
* The component includes:
|
|
2968
|
+
* - Integration with React Hook Form
|
|
2969
|
+
* - Resizable multi-line text input
|
|
2970
|
+
* - Character limit support
|
|
2971
|
+
* - Customizable validation through adapter integration
|
|
2972
|
+
* - Automatic error handling and reporting
|
|
2973
|
+
* - Full accessibility support with ARIA attributes
|
|
2974
|
+
* - Keyboard navigation with Escape to clear
|
|
2975
|
+
*/
|
|
2976
|
+
function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea, readOnly }) {
|
|
2977
|
+
const isRequired = !!validation?.required;
|
|
2978
|
+
const errorId = `${id}-error`;
|
|
2979
|
+
const descriptionId = `${id}-description`;
|
|
2980
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2981
|
+
className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
|
|
2982
|
+
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
|
|
2983
|
+
htmlFor: id,
|
|
2984
|
+
children: [
|
|
2985
|
+
label,
|
|
2986
|
+
" ",
|
|
2987
|
+
isRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2988
|
+
className: "text-destructive",
|
|
2989
|
+
children: "*"
|
|
2990
|
+
})
|
|
2991
|
+
]
|
|
2992
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_hook_form.Controller, {
|
|
2993
|
+
control,
|
|
2994
|
+
name,
|
|
2995
|
+
rules: { validate: (value) => {
|
|
2996
|
+
if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
|
|
2997
|
+
if (validateTextArea && value) {
|
|
2998
|
+
const validation$1 = validateTextArea(value);
|
|
2999
|
+
if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
|
|
3000
|
+
}
|
|
3001
|
+
if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
|
|
3002
|
+
return true;
|
|
3003
|
+
} },
|
|
3004
|
+
disabled: readOnly,
|
|
3005
|
+
render: ({ field, fieldState: { error, isTouched } }) => {
|
|
3006
|
+
const hasError = !!error;
|
|
3007
|
+
const shouldShowError = hasError && isTouched;
|
|
3008
|
+
const validationClasses = require_ErrorMessage.getValidationStateClasses(error, isTouched);
|
|
3009
|
+
const accessibilityProps = getAccessibilityProps({
|
|
3010
|
+
id,
|
|
3011
|
+
hasError,
|
|
3012
|
+
isRequired,
|
|
3013
|
+
hasHelperText: !!helperText
|
|
3014
|
+
});
|
|
3015
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3016
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Textarea, {
|
|
3017
|
+
...field,
|
|
3018
|
+
id,
|
|
3019
|
+
placeholder,
|
|
3020
|
+
rows,
|
|
3021
|
+
maxLength,
|
|
3022
|
+
className: validationClasses,
|
|
3023
|
+
value: field.value ?? "",
|
|
3024
|
+
disabled: readOnly,
|
|
3025
|
+
...accessibilityProps,
|
|
3026
|
+
"aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
|
|
3027
|
+
onKeyDown: handleEscapeKey((value) => {
|
|
3028
|
+
if (typeof field.onChange === "function") field.onChange(value);
|
|
3029
|
+
}, field.value)
|
|
3030
|
+
}),
|
|
3031
|
+
maxLength && typeof field.value === "string" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3032
|
+
className: "text-muted-foreground text-right text-xs",
|
|
3033
|
+
children: [
|
|
3034
|
+
field.value.length,
|
|
3035
|
+
"/",
|
|
3036
|
+
maxLength
|
|
3037
|
+
]
|
|
3038
|
+
}),
|
|
3039
|
+
helperText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3040
|
+
id: descriptionId,
|
|
3041
|
+
className: "text-muted-foreground text-sm",
|
|
3042
|
+
children: helperText
|
|
3043
|
+
}),
|
|
3044
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ErrorMessage.ErrorMessage, {
|
|
3045
|
+
error,
|
|
3046
|
+
id: errorId,
|
|
3047
|
+
message: shouldShowError ? error?.message : void 0
|
|
3048
|
+
})
|
|
3049
|
+
] });
|
|
3050
|
+
}
|
|
3051
|
+
})]
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
TextAreaField.displayName = "TextAreaField";
|
|
3055
|
+
|
|
3056
|
+
//#endregion
|
|
3057
|
+
//#region src/components/fields/AddressListField/labels.ts
|
|
3058
|
+
/** Default English labels for {@link AddressListField}. */
|
|
3059
|
+
const DEFAULT_ADDRESS_LIST_FIELD_LABELS = {
|
|
3060
|
+
addAddresses: "Add addresses",
|
|
3061
|
+
addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
|
|
3062
|
+
invalidPrefix: "Invalid:",
|
|
3063
|
+
invalidMore: (extraCount) => `(+${extraCount} more)`,
|
|
3064
|
+
maxItemsReached: (maxItems) => `Maximum of ${maxItems} addresses reached.`,
|
|
3065
|
+
addressesAdded: (count) => `${count} address${count === 1 ? "" : "es"} added`,
|
|
3066
|
+
removeAddress: (index) => `Remove address ${index + 1}`,
|
|
3067
|
+
previewReadyToAdd: (count) => `${count} ready to add`,
|
|
3068
|
+
previewInvalid: (count) => `${count} invalid`,
|
|
3069
|
+
previewAlreadyListed: (count) => `${count} already listed`,
|
|
3070
|
+
previewDuplicateInPaste: (count) => `${count} duplicate in paste`,
|
|
3071
|
+
bulkNothingAdded: "No addresses were added. Check formatting and try again.",
|
|
3072
|
+
bulkAdded: (count) => `Added ${count} address${count === 1 ? "" : "es"}`,
|
|
3073
|
+
bulkInvalidFormats: (count) => `${count} invalid format${count === 1 ? "" : "s"}`,
|
|
3074
|
+
bulkAlreadyListed: (count) => `${count} already listed`,
|
|
3075
|
+
bulkDuplicatesInPaste: (count) => `${count} duplicate${count === 1 ? "" : "s"} in paste`
|
|
3076
|
+
};
|
|
3077
|
+
/** Merges {@link DEFAULT_ADDRESS_LIST_FIELD_LABELS} with optional overrides. */
|
|
3078
|
+
function resolveAddressListFieldLabels(overrides) {
|
|
3079
|
+
return {
|
|
3080
|
+
...DEFAULT_ADDRESS_LIST_FIELD_LABELS,
|
|
3081
|
+
...overrides
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
/** Builds the inline helper preview shown while the user types in the draft textarea. */
|
|
3085
|
+
function buildAddressListPreviewSummary(classification, labels) {
|
|
3086
|
+
if (!classification) return null;
|
|
3087
|
+
const skipped = classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length;
|
|
3088
|
+
if (classification.accepted.length === 0 && skipped === 0) return null;
|
|
3089
|
+
const parts = [];
|
|
3090
|
+
if (classification.accepted.length > 0) parts.push(labels.previewReadyToAdd(classification.accepted.length));
|
|
3091
|
+
if (classification.invalid.length > 0) parts.push(labels.previewInvalid(classification.invalid.length));
|
|
3092
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.previewAlreadyListed(classification.alreadyListed.length));
|
|
3093
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.previewDuplicateInPaste(classification.duplicatesInInput.length));
|
|
3094
|
+
return parts.join(" · ");
|
|
3095
|
+
}
|
|
3096
|
+
/** Formats post-add feedback after the user commits parsed addresses to the list. */
|
|
3097
|
+
function formatAddressBulkSummary(classification, addedCount, labels) {
|
|
3098
|
+
if (addedCount === 0 && classification.accepted.length === 0) {
|
|
3099
|
+
if (classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length === 0) return null;
|
|
3100
|
+
return labels.bulkNothingAdded;
|
|
3101
|
+
}
|
|
3102
|
+
const parts = [];
|
|
3103
|
+
if (addedCount > 0) parts.push(labels.bulkAdded(addedCount));
|
|
3104
|
+
if (classification.invalid.length > 0) parts.push(labels.bulkInvalidFormats(classification.invalid.length));
|
|
3105
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.bulkAlreadyListed(classification.alreadyListed.length));
|
|
3106
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.bulkDuplicatesInPaste(classification.duplicatesInInput.length));
|
|
3107
|
+
if (parts.length === 0) return null;
|
|
3108
|
+
return parts.join(". ") + ".";
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
//#endregion
|
|
3112
|
+
//#region src/components/fields/AddressListField/AddressListField.tsx
|
|
3113
|
+
/**
|
|
3114
|
+
* Multi-address list field for bulk paste workflows.
|
|
3115
|
+
*
|
|
3116
|
+
* Architecture flow:
|
|
3117
|
+
* 1. App or wizard steps hold the committed address list in local or form state
|
|
3118
|
+
* 2. AddressListField renders a draft textarea plus the committed list rows
|
|
3119
|
+
* 3. Parsing and classification run via `@openzeppelin/ui-utils` helpers
|
|
3120
|
+
* 4. Optional {@link AddressingCapability} validates candidates before add
|
|
3121
|
+
* 5. {@link AddressDisplay} renders each committed row with copy and explorer links
|
|
3122
|
+
*
|
|
3123
|
+
* The component includes:
|
|
3124
|
+
* - Delimiter-aware bulk paste (newlines, commas, semicolons)
|
|
3125
|
+
* - Live preview of accepted, invalid, duplicate, and already-listed candidates
|
|
3126
|
+
* - Optional `maxItems` cap with disabled input at the limit
|
|
3127
|
+
* - Removable list rows with accessible remove buttons
|
|
3128
|
+
* - Caller-controlled copy for placeholders and format hints
|
|
3129
|
+
* - Optional `labels` overrides for built-in button and status strings
|
|
3130
|
+
*
|
|
3131
|
+
* @example
|
|
3132
|
+
* ```tsx
|
|
3133
|
+
* const [addresses, setAddresses] = useState<string[]>([]);
|
|
3134
|
+
*
|
|
3135
|
+
* <AddressListField
|
|
3136
|
+
* value={addresses}
|
|
3137
|
+
* onChange={setAddresses}
|
|
3138
|
+
* placeholder="Paste addresses (one per line or comma-separated)"
|
|
3139
|
+
* formatHint="Each entry is validated before it is added."
|
|
3140
|
+
* addressing={capabilities}
|
|
3141
|
+
* />
|
|
3142
|
+
* ```
|
|
3143
|
+
*/
|
|
3144
|
+
function AddressListField({ value, onChange, placeholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, labels: labelOverrides }) {
|
|
3145
|
+
const fieldId = (0, react.useId)();
|
|
3146
|
+
const labels = (0, react.useMemo)(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
|
|
3147
|
+
const atLimit = maxItems != null && value.length >= maxItems;
|
|
3148
|
+
const inputDisabled = disabled || atLimit;
|
|
3149
|
+
const [feedback, setFeedback] = (0, react.useState)(null);
|
|
3150
|
+
const { control, reset, watch } = (0, react_hook_form.useForm)({
|
|
3151
|
+
defaultValues: { input: "" },
|
|
3152
|
+
mode: "onChange"
|
|
3153
|
+
});
|
|
3154
|
+
const rawInput = watch("input");
|
|
3155
|
+
const classification = (0, react.useMemo)(() => {
|
|
3156
|
+
if (!rawInput.trim()) return null;
|
|
3157
|
+
return (0, _openzeppelin_ui_utils.classifyAddressCandidates)((0, _openzeppelin_ui_utils.parseDelimitedAddressInput)(rawInput), value, addressing, maxItems);
|
|
3158
|
+
}, [
|
|
3159
|
+
rawInput,
|
|
3160
|
+
value,
|
|
3161
|
+
addressing,
|
|
3162
|
+
maxItems
|
|
3163
|
+
]);
|
|
3164
|
+
const previewSummary = (0, react.useMemo)(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
|
|
3165
|
+
const inlineHelperText = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
|
|
3166
|
+
const handleAdd = (0, react.useCallback)(() => {
|
|
3167
|
+
if (!classification || classification.accepted.length === 0 || atLimit) return;
|
|
3168
|
+
onChange([...value, ...classification.accepted]);
|
|
3169
|
+
reset({ input: "" });
|
|
3170
|
+
setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
|
|
3171
|
+
}, [
|
|
3172
|
+
classification,
|
|
3173
|
+
atLimit,
|
|
3174
|
+
onChange,
|
|
3175
|
+
value,
|
|
3176
|
+
reset,
|
|
3177
|
+
labels
|
|
3178
|
+
]);
|
|
3179
|
+
const handleRemove = (0, react.useCallback)((index) => {
|
|
3180
|
+
onChange(value.filter((_, currentIndex) => currentIndex !== index));
|
|
3181
|
+
setFeedback(null);
|
|
3182
|
+
}, [onChange, value]);
|
|
3183
|
+
const acceptedCount = classification?.accepted.length ?? 0;
|
|
3184
|
+
const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
|
|
3185
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3186
|
+
className: (0, _openzeppelin_ui_utils.cn)("space-y-3", className),
|
|
3187
|
+
children: [
|
|
3188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3189
|
+
className: "space-y-1",
|
|
3190
|
+
children: [helperText ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3191
|
+
className: "text-xs text-muted-foreground",
|
|
3192
|
+
children: helperText
|
|
3193
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3194
|
+
className: "text-xs text-muted-foreground",
|
|
3195
|
+
children: formatHint
|
|
3196
|
+
})]
|
|
3197
|
+
}),
|
|
3198
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
|
|
3199
|
+
id: `address-list-field-${fieldId}`,
|
|
3200
|
+
name: "input",
|
|
3201
|
+
label: label ?? "",
|
|
3202
|
+
placeholder,
|
|
3203
|
+
helperText: inlineHelperText,
|
|
3204
|
+
control,
|
|
3205
|
+
rows: 4,
|
|
3206
|
+
validation: { required: false },
|
|
3207
|
+
readOnly: inputDisabled
|
|
3208
|
+
}),
|
|
3209
|
+
classification && classification.invalid.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3210
|
+
className: "text-xs text-destructive",
|
|
3211
|
+
children: [
|
|
3212
|
+
labels.invalidPrefix,
|
|
3213
|
+
" ",
|
|
3214
|
+
classification.invalid.slice(0, maxInvalidPreview).join(", "),
|
|
3215
|
+
classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
|
|
3216
|
+
]
|
|
3217
|
+
}),
|
|
3218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
3219
|
+
type: "button",
|
|
3220
|
+
size: "sm",
|
|
3221
|
+
disabled: inputDisabled || acceptedCount === 0,
|
|
3222
|
+
onClick: () => handleAdd(),
|
|
3223
|
+
children: addButtonLabel
|
|
3224
|
+
}),
|
|
3225
|
+
atLimit && maxItems != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3226
|
+
className: "text-xs text-muted-foreground",
|
|
3227
|
+
children: labels.maxItemsReached(maxItems)
|
|
3228
|
+
}),
|
|
3229
|
+
value.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3230
|
+
className: "space-y-1",
|
|
3231
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3232
|
+
className: "text-xs text-muted-foreground",
|
|
3233
|
+
children: labels.addressesAdded(value.length)
|
|
3234
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3235
|
+
className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
|
|
3236
|
+
children: value.map((address, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3237
|
+
className: "flex items-center justify-between rounded bg-muted p-2",
|
|
3238
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressDisplay, {
|
|
3239
|
+
address,
|
|
3240
|
+
variant: "inline",
|
|
3241
|
+
truncateWhenLabeled: true,
|
|
3242
|
+
showCopyButton: true,
|
|
3243
|
+
explorerUrl: getExplorerUrl?.(address) ?? void 0
|
|
3244
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
3245
|
+
type: "button",
|
|
3246
|
+
variant: "ghost",
|
|
3247
|
+
size: "sm",
|
|
3248
|
+
className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
|
|
3249
|
+
onClick: () => handleRemove(index),
|
|
3250
|
+
disabled,
|
|
3251
|
+
"aria-label": labels.removeAddress(index),
|
|
3252
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-3.5" })
|
|
3253
|
+
})]
|
|
3254
|
+
}, `${address}-${index}`))
|
|
3255
|
+
})]
|
|
3256
|
+
})
|
|
3257
|
+
]
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
|
|
2937
3261
|
//#endregion
|
|
2938
3262
|
//#region src/components/fields/address-suggestion/address-suggestion-context.tsx
|
|
2939
3263
|
/**
|
|
@@ -5726,105 +6050,6 @@ function SelectGroupedField({ id, label, placeholder = "Select an option", helpe
|
|
|
5726
6050
|
}
|
|
5727
6051
|
SelectGroupedField.displayName = "SelectGroupedField";
|
|
5728
6052
|
|
|
5729
|
-
//#endregion
|
|
5730
|
-
//#region src/components/fields/TextAreaField.tsx
|
|
5731
|
-
/**
|
|
5732
|
-
* Multi-line text input field component specifically designed for React Hook Form integration.
|
|
5733
|
-
*
|
|
5734
|
-
* Architecture flow:
|
|
5735
|
-
* 1. Form schemas are generated from contract functions using adapters
|
|
5736
|
-
* 2. TransactionForm renders the overall form structure with React Hook Form
|
|
5737
|
-
* 3. DynamicFormField selects the appropriate field component based on field type
|
|
5738
|
-
* 4. BaseField provides consistent layout and hook form integration
|
|
5739
|
-
* 5. This component handles textarea-specific rendering and validation
|
|
5740
|
-
*
|
|
5741
|
-
* The component includes:
|
|
5742
|
-
* - Integration with React Hook Form
|
|
5743
|
-
* - Resizable multi-line text input
|
|
5744
|
-
* - Character limit support
|
|
5745
|
-
* - Customizable validation through adapter integration
|
|
5746
|
-
* - Automatic error handling and reporting
|
|
5747
|
-
* - Full accessibility support with ARIA attributes
|
|
5748
|
-
* - Keyboard navigation with Escape to clear
|
|
5749
|
-
*/
|
|
5750
|
-
function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea }) {
|
|
5751
|
-
const isRequired = !!validation?.required;
|
|
5752
|
-
const errorId = `${id}-error`;
|
|
5753
|
-
const descriptionId = `${id}-description`;
|
|
5754
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5755
|
-
className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
|
|
5756
|
-
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
|
|
5757
|
-
htmlFor: id,
|
|
5758
|
-
children: [
|
|
5759
|
-
label,
|
|
5760
|
-
" ",
|
|
5761
|
-
isRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5762
|
-
className: "text-destructive",
|
|
5763
|
-
children: "*"
|
|
5764
|
-
})
|
|
5765
|
-
]
|
|
5766
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_hook_form.Controller, {
|
|
5767
|
-
control,
|
|
5768
|
-
name,
|
|
5769
|
-
rules: { validate: (value) => {
|
|
5770
|
-
if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
|
|
5771
|
-
if (validateTextArea && value) {
|
|
5772
|
-
const validation$1 = validateTextArea(value);
|
|
5773
|
-
if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
|
|
5774
|
-
}
|
|
5775
|
-
if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
|
|
5776
|
-
return true;
|
|
5777
|
-
} },
|
|
5778
|
-
render: ({ field, fieldState: { error, isTouched } }) => {
|
|
5779
|
-
const hasError = !!error;
|
|
5780
|
-
const shouldShowError = hasError && isTouched;
|
|
5781
|
-
const validationClasses = require_ErrorMessage.getValidationStateClasses(error, isTouched);
|
|
5782
|
-
const accessibilityProps = getAccessibilityProps({
|
|
5783
|
-
id,
|
|
5784
|
-
hasError,
|
|
5785
|
-
isRequired,
|
|
5786
|
-
hasHelperText: !!helperText
|
|
5787
|
-
});
|
|
5788
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5789
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Textarea, {
|
|
5790
|
-
...field,
|
|
5791
|
-
id,
|
|
5792
|
-
placeholder,
|
|
5793
|
-
rows,
|
|
5794
|
-
maxLength,
|
|
5795
|
-
className: validationClasses,
|
|
5796
|
-
value: field.value ?? "",
|
|
5797
|
-
...accessibilityProps,
|
|
5798
|
-
"aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
|
|
5799
|
-
onKeyDown: handleEscapeKey((value) => {
|
|
5800
|
-
if (typeof field.onChange === "function") field.onChange(value);
|
|
5801
|
-
}, field.value)
|
|
5802
|
-
}),
|
|
5803
|
-
maxLength && typeof field.value === "string" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5804
|
-
className: "text-muted-foreground text-right text-xs",
|
|
5805
|
-
children: [
|
|
5806
|
-
field.value.length,
|
|
5807
|
-
"/",
|
|
5808
|
-
maxLength
|
|
5809
|
-
]
|
|
5810
|
-
}),
|
|
5811
|
-
helperText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5812
|
-
id: descriptionId,
|
|
5813
|
-
className: "text-muted-foreground text-sm",
|
|
5814
|
-
children: helperText
|
|
5815
|
-
}),
|
|
5816
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ErrorMessage.ErrorMessage, {
|
|
5817
|
-
error,
|
|
5818
|
-
id: errorId,
|
|
5819
|
-
message: shouldShowError ? error?.message : void 0
|
|
5820
|
-
})
|
|
5821
|
-
] });
|
|
5822
|
-
}
|
|
5823
|
-
})]
|
|
5824
|
-
});
|
|
5825
|
-
}
|
|
5826
|
-
TextAreaField.displayName = "TextAreaField";
|
|
5827
|
-
|
|
5828
6053
|
//#endregion
|
|
5829
6054
|
//#region src/components/fields/TextField.tsx
|
|
5830
6055
|
/**
|
|
@@ -6368,6 +6593,35 @@ function NetworkServiceErrorBanner({ networkConfig, serviceType, errorMessage, t
|
|
|
6368
6593
|
});
|
|
6369
6594
|
}
|
|
6370
6595
|
|
|
6596
|
+
//#endregion
|
|
6597
|
+
//#region src/components/network-availability/NetworkAvailabilityNotice.tsx
|
|
6598
|
+
/**
|
|
6599
|
+
* Banner shown when the deployment disables mainnet or specific network IDs.
|
|
6600
|
+
* Hidden when no network availability policy is active.
|
|
6601
|
+
*/
|
|
6602
|
+
function NetworkAvailabilityNotice({ appName, selfHostRepoUrl, className }) {
|
|
6603
|
+
if (!(0, _openzeppelin_ui_utils.isNetworkAvailabilityPolicyActive)()) return null;
|
|
6604
|
+
const { title, descriptionBeforeLink, descriptionLinkLabel } = (0, _openzeppelin_ui_utils.getHostedNetworkAvailabilityNoticeCopy)(appName);
|
|
6605
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Alert, {
|
|
6606
|
+
className,
|
|
6607
|
+
children: [
|
|
6608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Info, { className: "h-4 w-4" }),
|
|
6609
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertTitle, { children: title }),
|
|
6610
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AlertDescription, { children: [
|
|
6611
|
+
descriptionBeforeLink,
|
|
6612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
6613
|
+
href: selfHostRepoUrl,
|
|
6614
|
+
target: "_blank",
|
|
6615
|
+
rel: "noopener noreferrer",
|
|
6616
|
+
className: "font-medium underline underline-offset-4 hover:text-foreground",
|
|
6617
|
+
children: descriptionLinkLabel
|
|
6618
|
+
}),
|
|
6619
|
+
"."
|
|
6620
|
+
] })
|
|
6621
|
+
]
|
|
6622
|
+
});
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6371
6625
|
//#endregion
|
|
6372
6626
|
//#region src/components/ui/sonner.tsx
|
|
6373
6627
|
const Toaster = ({ ...props }) => {
|
|
@@ -6394,6 +6648,7 @@ exports.AccordionTrigger = AccordionTrigger;
|
|
|
6394
6648
|
exports.AddressDisplay = AddressDisplay;
|
|
6395
6649
|
exports.AddressField = AddressField;
|
|
6396
6650
|
exports.AddressLabelProvider = AddressLabelProvider;
|
|
6651
|
+
exports.AddressListField = AddressListField;
|
|
6397
6652
|
exports.AddressSuggestionProvider = AddressSuggestionProvider;
|
|
6398
6653
|
exports.Alert = Alert;
|
|
6399
6654
|
exports.AlertDescription = AlertDescription;
|
|
@@ -6415,6 +6670,7 @@ exports.CardFooter = CardFooter;
|
|
|
6415
6670
|
exports.CardHeader = CardHeader;
|
|
6416
6671
|
exports.CardTitle = CardTitle;
|
|
6417
6672
|
exports.Checkbox = Checkbox;
|
|
6673
|
+
exports.DEFAULT_ADDRESS_LIST_FIELD_LABELS = DEFAULT_ADDRESS_LIST_FIELD_LABELS;
|
|
6418
6674
|
exports.DateRangePicker = DateRangePicker;
|
|
6419
6675
|
exports.DateTimeField = DateTimeField;
|
|
6420
6676
|
exports.Dialog = Dialog;
|
|
@@ -6467,6 +6723,7 @@ exports.LoadingButton = LoadingButton;
|
|
|
6467
6723
|
exports.MapEntryRow = MapEntryRow;
|
|
6468
6724
|
exports.MapField = MapField;
|
|
6469
6725
|
exports.MidnightIcon = MidnightIcon;
|
|
6726
|
+
exports.NetworkAvailabilityNotice = NetworkAvailabilityNotice;
|
|
6470
6727
|
exports.NetworkErrorNotificationProvider = NetworkErrorNotificationProvider;
|
|
6471
6728
|
exports.NetworkIcon = NetworkIcon;
|
|
6472
6729
|
exports.NetworkSelector = NetworkSelector;
|
|
@@ -6519,10 +6776,12 @@ exports.ViewContractStateButton = ViewContractStateButton;
|
|
|
6519
6776
|
exports.WizardLayout = WizardLayout;
|
|
6520
6777
|
exports.WizardNavigation = WizardNavigation;
|
|
6521
6778
|
exports.WizardStepper = WizardStepper;
|
|
6779
|
+
exports.buildAddressListPreviewSummary = buildAddressListPreviewSummary;
|
|
6522
6780
|
exports.buttonVariants = buttonVariants;
|
|
6523
6781
|
exports.computeChildTouched = computeChildTouched;
|
|
6524
6782
|
exports.createFocusManager = createFocusManager;
|
|
6525
6783
|
exports.createValidationResult = require_ErrorMessage.createValidationResult;
|
|
6784
|
+
exports.formatAddressBulkSummary = formatAddressBulkSummary;
|
|
6526
6785
|
exports.formatValidationError = require_ErrorMessage.formatValidationError;
|
|
6527
6786
|
exports.getAccessibilityProps = getAccessibilityProps;
|
|
6528
6787
|
exports.getDescribedById = getDescribedById;
|
|
@@ -6536,6 +6795,7 @@ exports.handleToggleKeys = handleToggleKeys;
|
|
|
6536
6795
|
exports.handleValidationError = require_ErrorMessage.handleValidationError;
|
|
6537
6796
|
exports.hasFieldError = require_ErrorMessage.hasFieldError;
|
|
6538
6797
|
exports.isDuplicateMapKey = require_ErrorMessage.isDuplicateMapKey;
|
|
6798
|
+
exports.resolveAddressListFieldLabels = resolveAddressListFieldLabels;
|
|
6539
6799
|
exports.useAddressLabel = useAddressLabel;
|
|
6540
6800
|
exports.useAddressSuggestions = useAddressSuggestions;
|
|
6541
6801
|
exports.useDuplicateKeyIndexes = useDuplicateKeyIndexes;
|