@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.mjs CHANGED
@@ -4,7 +4,7 @@ import { cva } from "class-variance-authority";
4
4
  import { AlertCircle, Calendar as Calendar$1, Check, CheckCircle, CheckCircle2, CheckIcon, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CloudOff, Copy, DollarSign, ExternalLink as ExternalLink$1, ExternalLinkIcon, Eye, EyeOff, File, FileText, GripVertical, Hash, Info, Loader2, Menu, MoreHorizontal, Network, Pencil, Plus, Search, Settings, Timer, Upload, X } from "lucide-react";
5
5
  import * as React$1 from "react";
6
6
  import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
7
- import { cn, getDefaultValueForType, getInvalidUrlMessage, getServiceDisplayName, isValidUrl, truncateMiddle, validateBytesSimple } from "@openzeppelin/ui-utils";
7
+ import { classifyAddressCandidates, cn, getDefaultValueForType, getHostedNetworkAvailabilityNoticeCopy, getInvalidUrlMessage, getServiceDisplayName, isNetworkAvailabilityPolicyActive, isValidUrl, parseDelimitedAddressInput, truncateMiddle, validateBytesSimple } from "@openzeppelin/ui-utils";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
10
10
  import { Slot, Slottable } from "@radix-ui/react-slot";
@@ -14,7 +14,7 @@ import { format } from "date-fns";
14
14
  import * as PopoverPrimitive from "@radix-ui/react-popover";
15
15
  import * as DialogPrimitive from "@radix-ui/react-dialog";
16
16
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
17
- import { Controller, FormProvider, useFieldArray, useFormContext, useWatch } from "react-hook-form";
17
+ import { Controller, FormProvider, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
18
18
  import * as LabelPrimitive from "@radix-ui/react-label";
19
19
  import * as ProgressPrimitive from "@radix-ui/react-progress";
20
20
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
@@ -26,7 +26,7 @@ import { Toaster as Toaster$1, toast } from "sonner";
26
26
  import { useTheme } from "next-themes";
27
27
 
28
28
  //#region src/version.ts
29
- const VERSION = "3.0.0";
29
+ const VERSION = "3.1.0";
30
30
 
31
31
  //#endregion
32
32
  //#region src/components/ui/accordion.tsx
@@ -1336,7 +1336,7 @@ function NetworkIcon({ network, className, size = 16, variant = "branded" }) {
1336
1336
  //#endregion
1337
1337
  //#region src/components/ui/network-selector.tsx
1338
1338
  /** Searchable dropdown selector for blockchain networks with optional grouping and multi-select. */
1339
- function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetworkType, getNetworkId, groupByEcosystem = false, getEcosystem, filterNetwork, className, placeholder = "Select Network", ...modeProps }) {
1339
+ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetworkType, getNetworkId, groupByEcosystem = false, getEcosystem, filterNetwork, isNetworkDisabled, getNetworkDisabledLabel, className, placeholder = "Select Network", ...modeProps }) {
1340
1340
  const [open, setOpen] = React$1.useState(false);
1341
1341
  const [searchQuery, setSearchQuery] = React$1.useState("");
1342
1342
  const isMultiple = modeProps.multiple === true;
@@ -1377,6 +1377,7 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
1377
1377
  getNetworkId
1378
1378
  ]);
1379
1379
  const handleSelect = React$1.useCallback((network) => {
1380
+ if (isNetworkDisabled?.(network)) return;
1380
1381
  if (isMultiple && selectedNetworkIds && onSelectionChange) {
1381
1382
  const id = getNetworkId(network);
1382
1383
  onSelectionChange(selectedNetworkIds.includes(id) ? selectedNetworkIds.filter((x) => x !== id) : [...selectedNetworkIds, id]);
@@ -1389,7 +1390,8 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
1389
1390
  selectedNetworkIds,
1390
1391
  onSelectionChange,
1391
1392
  onSelectNetwork,
1392
- getNetworkId
1393
+ getNetworkId,
1394
+ isNetworkDisabled
1393
1395
  ]);
1394
1396
  const handleClearAll = React$1.useCallback(() => {
1395
1397
  if (isMultiple && onSelectionChange) onSelectionChange([]);
@@ -1475,31 +1477,47 @@ function NetworkSelector({ networks, getNetworkLabel, getNetworkIcon, getNetwork
1475
1477
  className: "text-xs font-medium text-muted-foreground",
1476
1478
  children: group
1477
1479
  }),
1478
- /* @__PURE__ */ jsx(DropdownMenuGroup, { children: groupNetworks.map((network) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
1479
- onSelect: (e) => {
1480
- if (isMultiple) e.preventDefault();
1481
- handleSelect(network);
1482
- },
1483
- className: "gap-2",
1484
- children: [
1485
- isMultiple ? /* @__PURE__ */ jsx("div", {
1486
- className: 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"),
1487
- children: isSelected(network) && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" })
1488
- }) : null,
1489
- getNetworkIcon?.(network),
1490
- /* @__PURE__ */ jsxs("div", {
1491
- className: "flex flex-1 items-center gap-2 min-w-0",
1492
- children: [/* @__PURE__ */ jsx("span", {
1493
- className: "truncate",
1494
- children: getNetworkLabel(network)
1495
- }), getNetworkType && /* @__PURE__ */ jsx("span", {
1496
- className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase text-muted-foreground",
1497
- children: getNetworkType(network)
1498
- })]
1499
- }),
1500
- !isMultiple && isSelected(network) && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-selected opacity-100" })
1501
- ]
1502
- }, getNetworkId(network))) }),
1480
+ /* @__PURE__ */ jsx(DropdownMenuGroup, { children: groupNetworks.map((network) => {
1481
+ const disabled = isNetworkDisabled?.(network) ?? false;
1482
+ const disabledLabel = disabled ? getNetworkDisabledLabel?.(network) : void 0;
1483
+ return /* @__PURE__ */ jsxs(DropdownMenuItem, {
1484
+ disabled,
1485
+ onSelect: (e) => {
1486
+ if (disabled) {
1487
+ e.preventDefault();
1488
+ return;
1489
+ }
1490
+ if (isMultiple) e.preventDefault();
1491
+ handleSelect(network);
1492
+ },
1493
+ className: cn("gap-2", disabled && "cursor-not-allowed opacity-60"),
1494
+ children: [
1495
+ isMultiple ? /* @__PURE__ */ jsx("div", {
1496
+ className: 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"),
1497
+ children: isSelected(network) && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" })
1498
+ }) : null,
1499
+ getNetworkIcon?.(network),
1500
+ /* @__PURE__ */ jsxs("div", {
1501
+ className: "flex flex-1 items-center gap-2 min-w-0",
1502
+ children: [
1503
+ /* @__PURE__ */ jsx("span", {
1504
+ className: "truncate",
1505
+ children: getNetworkLabel(network)
1506
+ }),
1507
+ getNetworkType && /* @__PURE__ */ 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
+ disabledLabel ? /* @__PURE__ */ jsx("span", {
1512
+ className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
1513
+ children: disabledLabel
1514
+ }) : null
1515
+ ]
1516
+ }),
1517
+ !isMultiple && isSelected(network) && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-selected opacity-100" })
1518
+ ]
1519
+ }, getNetworkId(network));
1520
+ }) }),
1503
1521
  index < Object.keys(groupedNetworks).length - 1 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
1504
1522
  ] }, group))]
1505
1523
  })]
@@ -2922,6 +2940,312 @@ function AddressField({ id, label, placeholder, helperText, control, name, width
2922
2940
  }
2923
2941
  AddressField.displayName = "AddressField";
2924
2942
 
2943
+ //#endregion
2944
+ //#region src/components/fields/TextAreaField.tsx
2945
+ /**
2946
+ * Multi-line text input field component specifically designed for React Hook Form integration.
2947
+ *
2948
+ * Architecture flow:
2949
+ * 1. Form schemas are generated from contract functions using adapters
2950
+ * 2. TransactionForm renders the overall form structure with React Hook Form
2951
+ * 3. DynamicFormField selects the appropriate field component based on field type
2952
+ * 4. BaseField provides consistent layout and hook form integration
2953
+ * 5. This component handles textarea-specific rendering and validation
2954
+ *
2955
+ * The component includes:
2956
+ * - Integration with React Hook Form
2957
+ * - Resizable multi-line text input
2958
+ * - Character limit support
2959
+ * - Customizable validation through adapter integration
2960
+ * - Automatic error handling and reporting
2961
+ * - Full accessibility support with ARIA attributes
2962
+ * - Keyboard navigation with Escape to clear
2963
+ */
2964
+ function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea, readOnly }) {
2965
+ const isRequired = !!validation?.required;
2966
+ const errorId = `${id}-error`;
2967
+ const descriptionId = `${id}-description`;
2968
+ return /* @__PURE__ */ jsxs("div", {
2969
+ className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
2970
+ children: [label && /* @__PURE__ */ jsxs(Label, {
2971
+ htmlFor: id,
2972
+ children: [
2973
+ label,
2974
+ " ",
2975
+ isRequired && /* @__PURE__ */ jsx("span", {
2976
+ className: "text-destructive",
2977
+ children: "*"
2978
+ })
2979
+ ]
2980
+ }), /* @__PURE__ */ jsx(Controller, {
2981
+ control,
2982
+ name,
2983
+ rules: { validate: (value) => {
2984
+ if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
2985
+ if (validateTextArea && value) {
2986
+ const validation$1 = validateTextArea(value);
2987
+ if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
2988
+ }
2989
+ if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
2990
+ return true;
2991
+ } },
2992
+ disabled: readOnly,
2993
+ render: ({ field, fieldState: { error, isTouched } }) => {
2994
+ const hasError = !!error;
2995
+ const shouldShowError = hasError && isTouched;
2996
+ const validationClasses = getValidationStateClasses(error, isTouched);
2997
+ const accessibilityProps = getAccessibilityProps({
2998
+ id,
2999
+ hasError,
3000
+ isRequired,
3001
+ hasHelperText: !!helperText
3002
+ });
3003
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3004
+ /* @__PURE__ */ jsx(Textarea, {
3005
+ ...field,
3006
+ id,
3007
+ placeholder,
3008
+ rows,
3009
+ maxLength,
3010
+ className: validationClasses,
3011
+ value: field.value ?? "",
3012
+ disabled: readOnly,
3013
+ ...accessibilityProps,
3014
+ "aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
3015
+ onKeyDown: handleEscapeKey((value) => {
3016
+ if (typeof field.onChange === "function") field.onChange(value);
3017
+ }, field.value)
3018
+ }),
3019
+ maxLength && typeof field.value === "string" && /* @__PURE__ */ jsxs("div", {
3020
+ className: "text-muted-foreground text-right text-xs",
3021
+ children: [
3022
+ field.value.length,
3023
+ "/",
3024
+ maxLength
3025
+ ]
3026
+ }),
3027
+ helperText && /* @__PURE__ */ jsx("div", {
3028
+ id: descriptionId,
3029
+ className: "text-muted-foreground text-sm",
3030
+ children: helperText
3031
+ }),
3032
+ /* @__PURE__ */ jsx(ErrorMessage, {
3033
+ error,
3034
+ id: errorId,
3035
+ message: shouldShowError ? error?.message : void 0
3036
+ })
3037
+ ] });
3038
+ }
3039
+ })]
3040
+ });
3041
+ }
3042
+ TextAreaField.displayName = "TextAreaField";
3043
+
3044
+ //#endregion
3045
+ //#region src/components/fields/AddressListField/labels.ts
3046
+ /** Default English labels for {@link AddressListField}. */
3047
+ const DEFAULT_ADDRESS_LIST_FIELD_LABELS = {
3048
+ addAddresses: "Add addresses",
3049
+ addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
3050
+ invalidPrefix: "Invalid:",
3051
+ invalidMore: (extraCount) => `(+${extraCount} more)`,
3052
+ maxItemsReached: (maxItems) => `Maximum of ${maxItems} addresses reached.`,
3053
+ addressesAdded: (count) => `${count} address${count === 1 ? "" : "es"} added`,
3054
+ removeAddress: (index) => `Remove address ${index + 1}`,
3055
+ previewReadyToAdd: (count) => `${count} ready to add`,
3056
+ previewInvalid: (count) => `${count} invalid`,
3057
+ previewAlreadyListed: (count) => `${count} already listed`,
3058
+ previewDuplicateInPaste: (count) => `${count} duplicate in paste`,
3059
+ bulkNothingAdded: "No addresses were added. Check formatting and try again.",
3060
+ bulkAdded: (count) => `Added ${count} address${count === 1 ? "" : "es"}`,
3061
+ bulkInvalidFormats: (count) => `${count} invalid format${count === 1 ? "" : "s"}`,
3062
+ bulkAlreadyListed: (count) => `${count} already listed`,
3063
+ bulkDuplicatesInPaste: (count) => `${count} duplicate${count === 1 ? "" : "s"} in paste`
3064
+ };
3065
+ /** Merges {@link DEFAULT_ADDRESS_LIST_FIELD_LABELS} with optional overrides. */
3066
+ function resolveAddressListFieldLabels(overrides) {
3067
+ return {
3068
+ ...DEFAULT_ADDRESS_LIST_FIELD_LABELS,
3069
+ ...overrides
3070
+ };
3071
+ }
3072
+ /** Builds the inline helper preview shown while the user types in the draft textarea. */
3073
+ function buildAddressListPreviewSummary(classification, labels) {
3074
+ if (!classification) return null;
3075
+ const skipped = classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length;
3076
+ if (classification.accepted.length === 0 && skipped === 0) return null;
3077
+ const parts = [];
3078
+ if (classification.accepted.length > 0) parts.push(labels.previewReadyToAdd(classification.accepted.length));
3079
+ if (classification.invalid.length > 0) parts.push(labels.previewInvalid(classification.invalid.length));
3080
+ if (classification.alreadyListed.length > 0) parts.push(labels.previewAlreadyListed(classification.alreadyListed.length));
3081
+ if (classification.duplicatesInInput.length > 0) parts.push(labels.previewDuplicateInPaste(classification.duplicatesInInput.length));
3082
+ return parts.join(" · ");
3083
+ }
3084
+ /** Formats post-add feedback after the user commits parsed addresses to the list. */
3085
+ function formatAddressBulkSummary(classification, addedCount, labels) {
3086
+ if (addedCount === 0 && classification.accepted.length === 0) {
3087
+ if (classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length === 0) return null;
3088
+ return labels.bulkNothingAdded;
3089
+ }
3090
+ const parts = [];
3091
+ if (addedCount > 0) parts.push(labels.bulkAdded(addedCount));
3092
+ if (classification.invalid.length > 0) parts.push(labels.bulkInvalidFormats(classification.invalid.length));
3093
+ if (classification.alreadyListed.length > 0) parts.push(labels.bulkAlreadyListed(classification.alreadyListed.length));
3094
+ if (classification.duplicatesInInput.length > 0) parts.push(labels.bulkDuplicatesInPaste(classification.duplicatesInInput.length));
3095
+ if (parts.length === 0) return null;
3096
+ return parts.join(". ") + ".";
3097
+ }
3098
+
3099
+ //#endregion
3100
+ //#region src/components/fields/AddressListField/AddressListField.tsx
3101
+ /**
3102
+ * Multi-address list field for bulk paste workflows.
3103
+ *
3104
+ * Architecture flow:
3105
+ * 1. App or wizard steps hold the committed address list in local or form state
3106
+ * 2. AddressListField renders a draft textarea plus the committed list rows
3107
+ * 3. Parsing and classification run via `@openzeppelin/ui-utils` helpers
3108
+ * 4. Optional {@link AddressingCapability} validates candidates before add
3109
+ * 5. {@link AddressDisplay} renders each committed row with copy and explorer links
3110
+ *
3111
+ * The component includes:
3112
+ * - Delimiter-aware bulk paste (newlines, commas, semicolons)
3113
+ * - Live preview of accepted, invalid, duplicate, and already-listed candidates
3114
+ * - Optional `maxItems` cap with disabled input at the limit
3115
+ * - Removable list rows with accessible remove buttons
3116
+ * - Caller-controlled copy for placeholders and format hints
3117
+ * - Optional `labels` overrides for built-in button and status strings
3118
+ *
3119
+ * @example
3120
+ * ```tsx
3121
+ * const [addresses, setAddresses] = useState<string[]>([]);
3122
+ *
3123
+ * <AddressListField
3124
+ * value={addresses}
3125
+ * onChange={setAddresses}
3126
+ * placeholder="Paste addresses (one per line or comma-separated)"
3127
+ * formatHint="Each entry is validated before it is added."
3128
+ * addressing={capabilities}
3129
+ * />
3130
+ * ```
3131
+ */
3132
+ function AddressListField({ value, onChange, placeholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, labels: labelOverrides }) {
3133
+ const fieldId = useId();
3134
+ const labels = useMemo(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
3135
+ const atLimit = maxItems != null && value.length >= maxItems;
3136
+ const inputDisabled = disabled || atLimit;
3137
+ const [feedback, setFeedback] = useState(null);
3138
+ const { control, reset, watch } = useForm({
3139
+ defaultValues: { input: "" },
3140
+ mode: "onChange"
3141
+ });
3142
+ const rawInput = watch("input");
3143
+ const classification = useMemo(() => {
3144
+ if (!rawInput.trim()) return null;
3145
+ return classifyAddressCandidates(parseDelimitedAddressInput(rawInput), value, addressing, maxItems);
3146
+ }, [
3147
+ rawInput,
3148
+ value,
3149
+ addressing,
3150
+ maxItems
3151
+ ]);
3152
+ const previewSummary = useMemo(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
3153
+ const inlineHelperText = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
3154
+ const handleAdd = useCallback(() => {
3155
+ if (!classification || classification.accepted.length === 0 || atLimit) return;
3156
+ onChange([...value, ...classification.accepted]);
3157
+ reset({ input: "" });
3158
+ setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
3159
+ }, [
3160
+ classification,
3161
+ atLimit,
3162
+ onChange,
3163
+ value,
3164
+ reset,
3165
+ labels
3166
+ ]);
3167
+ const handleRemove = useCallback((index) => {
3168
+ onChange(value.filter((_, currentIndex) => currentIndex !== index));
3169
+ setFeedback(null);
3170
+ }, [onChange, value]);
3171
+ const acceptedCount = classification?.accepted.length ?? 0;
3172
+ const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
3173
+ return /* @__PURE__ */ jsxs("div", {
3174
+ className: cn("space-y-3", className),
3175
+ children: [
3176
+ /* @__PURE__ */ jsxs("div", {
3177
+ className: "space-y-1",
3178
+ children: [helperText ? /* @__PURE__ */ jsx("p", {
3179
+ className: "text-xs text-muted-foreground",
3180
+ children: helperText
3181
+ }) : null, /* @__PURE__ */ jsx("p", {
3182
+ className: "text-xs text-muted-foreground",
3183
+ children: formatHint
3184
+ })]
3185
+ }),
3186
+ /* @__PURE__ */ jsx(TextAreaField, {
3187
+ id: `address-list-field-${fieldId}`,
3188
+ name: "input",
3189
+ label: label ?? "",
3190
+ placeholder,
3191
+ helperText: inlineHelperText,
3192
+ control,
3193
+ rows: 4,
3194
+ validation: { required: false },
3195
+ readOnly: inputDisabled
3196
+ }),
3197
+ classification && classification.invalid.length > 0 && /* @__PURE__ */ jsxs("p", {
3198
+ className: "text-xs text-destructive",
3199
+ children: [
3200
+ labels.invalidPrefix,
3201
+ " ",
3202
+ classification.invalid.slice(0, maxInvalidPreview).join(", "),
3203
+ classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
3204
+ ]
3205
+ }),
3206
+ /* @__PURE__ */ jsx(Button, {
3207
+ type: "button",
3208
+ size: "sm",
3209
+ disabled: inputDisabled || acceptedCount === 0,
3210
+ onClick: () => handleAdd(),
3211
+ children: addButtonLabel
3212
+ }),
3213
+ atLimit && maxItems != null && /* @__PURE__ */ jsx("p", {
3214
+ className: "text-xs text-muted-foreground",
3215
+ children: labels.maxItemsReached(maxItems)
3216
+ }),
3217
+ value.length > 0 && /* @__PURE__ */ jsxs("div", {
3218
+ className: "space-y-1",
3219
+ children: [/* @__PURE__ */ jsx("p", {
3220
+ className: "text-xs text-muted-foreground",
3221
+ children: labels.addressesAdded(value.length)
3222
+ }), /* @__PURE__ */ jsx("div", {
3223
+ className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
3224
+ children: value.map((address, index) => /* @__PURE__ */ jsxs("div", {
3225
+ className: "flex items-center justify-between rounded bg-muted p-2",
3226
+ children: [/* @__PURE__ */ jsx(AddressDisplay, {
3227
+ address,
3228
+ variant: "inline",
3229
+ truncateWhenLabeled: true,
3230
+ showCopyButton: true,
3231
+ explorerUrl: getExplorerUrl?.(address) ?? void 0
3232
+ }), /* @__PURE__ */ jsx(Button, {
3233
+ type: "button",
3234
+ variant: "ghost",
3235
+ size: "sm",
3236
+ className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
3237
+ onClick: () => handleRemove(index),
3238
+ disabled,
3239
+ "aria-label": labels.removeAddress(index),
3240
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
3241
+ })]
3242
+ }, `${address}-${index}`))
3243
+ })]
3244
+ })
3245
+ ]
3246
+ });
3247
+ }
3248
+
2925
3249
  //#endregion
2926
3250
  //#region src/components/fields/address-suggestion/address-suggestion-context.tsx
2927
3251
  /**
@@ -5714,105 +6038,6 @@ function SelectGroupedField({ id, label, placeholder = "Select an option", helpe
5714
6038
  }
5715
6039
  SelectGroupedField.displayName = "SelectGroupedField";
5716
6040
 
5717
- //#endregion
5718
- //#region src/components/fields/TextAreaField.tsx
5719
- /**
5720
- * Multi-line text input field component specifically designed for React Hook Form integration.
5721
- *
5722
- * Architecture flow:
5723
- * 1. Form schemas are generated from contract functions using adapters
5724
- * 2. TransactionForm renders the overall form structure with React Hook Form
5725
- * 3. DynamicFormField selects the appropriate field component based on field type
5726
- * 4. BaseField provides consistent layout and hook form integration
5727
- * 5. This component handles textarea-specific rendering and validation
5728
- *
5729
- * The component includes:
5730
- * - Integration with React Hook Form
5731
- * - Resizable multi-line text input
5732
- * - Character limit support
5733
- * - Customizable validation through adapter integration
5734
- * - Automatic error handling and reporting
5735
- * - Full accessibility support with ARIA attributes
5736
- * - Keyboard navigation with Escape to clear
5737
- */
5738
- function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea }) {
5739
- const isRequired = !!validation?.required;
5740
- const errorId = `${id}-error`;
5741
- const descriptionId = `${id}-description`;
5742
- return /* @__PURE__ */ jsxs("div", {
5743
- className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
5744
- children: [label && /* @__PURE__ */ jsxs(Label, {
5745
- htmlFor: id,
5746
- children: [
5747
- label,
5748
- " ",
5749
- isRequired && /* @__PURE__ */ jsx("span", {
5750
- className: "text-destructive",
5751
- children: "*"
5752
- })
5753
- ]
5754
- }), /* @__PURE__ */ jsx(Controller, {
5755
- control,
5756
- name,
5757
- rules: { validate: (value) => {
5758
- if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
5759
- if (validateTextArea && value) {
5760
- const validation$1 = validateTextArea(value);
5761
- if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
5762
- }
5763
- if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
5764
- return true;
5765
- } },
5766
- render: ({ field, fieldState: { error, isTouched } }) => {
5767
- const hasError = !!error;
5768
- const shouldShowError = hasError && isTouched;
5769
- const validationClasses = getValidationStateClasses(error, isTouched);
5770
- const accessibilityProps = getAccessibilityProps({
5771
- id,
5772
- hasError,
5773
- isRequired,
5774
- hasHelperText: !!helperText
5775
- });
5776
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5777
- /* @__PURE__ */ jsx(Textarea, {
5778
- ...field,
5779
- id,
5780
- placeholder,
5781
- rows,
5782
- maxLength,
5783
- className: validationClasses,
5784
- value: field.value ?? "",
5785
- ...accessibilityProps,
5786
- "aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
5787
- onKeyDown: handleEscapeKey((value) => {
5788
- if (typeof field.onChange === "function") field.onChange(value);
5789
- }, field.value)
5790
- }),
5791
- maxLength && typeof field.value === "string" && /* @__PURE__ */ jsxs("div", {
5792
- className: "text-muted-foreground text-right text-xs",
5793
- children: [
5794
- field.value.length,
5795
- "/",
5796
- maxLength
5797
- ]
5798
- }),
5799
- helperText && /* @__PURE__ */ jsx("div", {
5800
- id: descriptionId,
5801
- className: "text-muted-foreground text-sm",
5802
- children: helperText
5803
- }),
5804
- /* @__PURE__ */ jsx(ErrorMessage, {
5805
- error,
5806
- id: errorId,
5807
- message: shouldShowError ? error?.message : void 0
5808
- })
5809
- ] });
5810
- }
5811
- })]
5812
- });
5813
- }
5814
- TextAreaField.displayName = "TextAreaField";
5815
-
5816
6041
  //#endregion
5817
6042
  //#region src/components/fields/TextField.tsx
5818
6043
  /**
@@ -6356,6 +6581,35 @@ function NetworkServiceErrorBanner({ networkConfig, serviceType, errorMessage, t
6356
6581
  });
6357
6582
  }
6358
6583
 
6584
+ //#endregion
6585
+ //#region src/components/network-availability/NetworkAvailabilityNotice.tsx
6586
+ /**
6587
+ * Banner shown when the deployment disables mainnet or specific network IDs.
6588
+ * Hidden when no network availability policy is active.
6589
+ */
6590
+ function NetworkAvailabilityNotice({ appName, selfHostRepoUrl, className }) {
6591
+ if (!isNetworkAvailabilityPolicyActive()) return null;
6592
+ const { title, descriptionBeforeLink, descriptionLinkLabel } = getHostedNetworkAvailabilityNoticeCopy(appName);
6593
+ return /* @__PURE__ */ jsxs(Alert, {
6594
+ className,
6595
+ children: [
6596
+ /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
6597
+ /* @__PURE__ */ jsx(AlertTitle, { children: title }),
6598
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
6599
+ descriptionBeforeLink,
6600
+ /* @__PURE__ */ jsx("a", {
6601
+ href: selfHostRepoUrl,
6602
+ target: "_blank",
6603
+ rel: "noopener noreferrer",
6604
+ className: "font-medium underline underline-offset-4 hover:text-foreground",
6605
+ children: descriptionLinkLabel
6606
+ }),
6607
+ "."
6608
+ ] })
6609
+ ]
6610
+ });
6611
+ }
6612
+
6359
6613
  //#endregion
6360
6614
  //#region src/components/ui/sonner.tsx
6361
6615
  const Toaster = ({ ...props }) => {
@@ -6375,5 +6629,5 @@ const Toaster = ({ ...props }) => {
6375
6629
  };
6376
6630
 
6377
6631
  //#endregion
6378
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddressDisplay, AddressField, AddressLabelProvider, AddressSuggestionProvider, Alert, AlertDescription, AlertTitle, AmountField, ArrayField, ArrayObjectField, Banner, BaseField, BigIntField, BooleanField, Button, BytesField, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DateRangePicker, DateTimeField, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EcosystemDropdown, EcosystemIcon, EmptyState, EnumField, ErrorMessage, ExternalLink, FileUploadField, Footer, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Header, INTEGER_HTML_PATTERN, INTEGER_INPUT_PATTERN, INTEGER_PATTERN, Input, Label, LoadingButton, MapEntryRow, MapField, MidnightIcon, NetworkErrorNotificationProvider, NetworkIcon, NetworkSelector, NetworkServiceErrorBanner, NetworkStatusBadge, NumberField, ObjectField, OverflowMenu, PasswordField, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioField, RadioGroup, RadioGroupItem, RelayerDetailsCard, Select, SelectContent, SelectField, SelectGroup, SelectGroupedField, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarButton, SidebarGroup, SidebarLayout, SidebarSection, Tabs, TabsContent, TabsList, TabsTrigger, TextAreaField, TextField, Textarea, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UrlField, VERSION, ViewContractStateButton, WizardLayout, WizardNavigation, WizardStepper, buttonVariants, computeChildTouched, createFocusManager, createValidationResult, formatValidationError, getAccessibilityProps, getDescribedById, getErrorMessage, getValidationStateClasses, getWidthClasses, handleEscapeKey, handleKeyboardEvent, handleNumericKeys, handleToggleKeys, handleValidationError, hasFieldError, isDuplicateMapKey, useAddressLabel, useAddressSuggestions, useDuplicateKeyIndexes, useMapFieldSync, useNetworkErrorAwareAdapter, useNetworkErrorReporter, useNetworkErrors, validateField, validateMapEntries, validateMapStructure };
6632
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddressDisplay, AddressField, AddressLabelProvider, AddressListField, AddressSuggestionProvider, Alert, AlertDescription, AlertTitle, AmountField, ArrayField, ArrayObjectField, Banner, BaseField, BigIntField, BooleanField, Button, BytesField, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DEFAULT_ADDRESS_LIST_FIELD_LABELS, DateRangePicker, DateTimeField, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EcosystemDropdown, EcosystemIcon, EmptyState, EnumField, ErrorMessage, ExternalLink, FileUploadField, Footer, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Header, INTEGER_HTML_PATTERN, INTEGER_INPUT_PATTERN, INTEGER_PATTERN, Input, Label, LoadingButton, MapEntryRow, MapField, MidnightIcon, NetworkAvailabilityNotice, NetworkErrorNotificationProvider, NetworkIcon, NetworkSelector, NetworkServiceErrorBanner, NetworkStatusBadge, NumberField, ObjectField, OverflowMenu, PasswordField, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioField, RadioGroup, RadioGroupItem, RelayerDetailsCard, Select, SelectContent, SelectField, SelectGroup, SelectGroupedField, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarButton, SidebarGroup, SidebarLayout, SidebarSection, Tabs, TabsContent, TabsList, TabsTrigger, TextAreaField, TextField, Textarea, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UrlField, VERSION, ViewContractStateButton, WizardLayout, WizardNavigation, WizardStepper, buildAddressListPreviewSummary, buttonVariants, computeChildTouched, createFocusManager, createValidationResult, formatAddressBulkSummary, formatValidationError, getAccessibilityProps, getDescribedById, getErrorMessage, getValidationStateClasses, getWidthClasses, handleEscapeKey, handleKeyboardEvent, handleNumericKeys, handleToggleKeys, handleValidationError, hasFieldError, isDuplicateMapKey, resolveAddressListFieldLabels, useAddressLabel, useAddressSuggestions, useDuplicateKeyIndexes, useMapFieldSync, useNetworkErrorAwareAdapter, useNetworkErrorReporter, useNetworkErrors, validateField, validateMapEntries, validateMapStructure };
6379
6633
  //# sourceMappingURL=index.mjs.map