@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 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.0.0";
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) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenuItem, {
1491
- onSelect: (e) => {
1492
- if (isMultiple) e.preventDefault();
1493
- handleSelect(network);
1494
- },
1495
- className: "gap-2",
1496
- children: [
1497
- isMultiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1498
- 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"),
1499
- children: isSelected(network) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-3 w-3" })
1500
- }) : null,
1501
- getNetworkIcon?.(network),
1502
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1503
- className: "flex flex-1 items-center gap-2 min-w-0",
1504
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1505
- className: "truncate",
1506
- children: getNetworkLabel(network)
1507
- }), getNetworkType && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1508
- className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase text-muted-foreground",
1509
- children: getNetworkType(network)
1510
- })]
1511
- }),
1512
- !isMultiple && isSelected(network) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-4 w-4 text-selected opacity-100" })
1513
- ]
1514
- }, getNetworkId(network))) }),
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;