@openzeppelin/ui-components 3.0.1 → 3.2.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 +457 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +114 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +456 -104
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as getValidationStateClasses, c as isDuplicateMapKey, d as INTEGER_HTML_PATTERN, f as INTEGER_INPUT_PATTERN, i as getErrorMessage, l as validateField, n as createValidationResult, o as handleValidationError, p as INTEGER_PATTERN, r as formatValidationError, s as hasFieldError, t as ErrorMessage, u as validateMapEntries } from "./ErrorMessage-BqOEJm84.mjs";
|
|
2
2
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
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";
|
|
4
|
+
import { AlertCircle, Calendar as Calendar$1, Check, CheckCircle, CheckCircle2, CheckIcon, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, ClipboardPaste, CloudOff, Copy, DollarSign, ExternalLink as ExternalLink$1, ExternalLinkIcon, Eye, EyeOff, File, FileText, GripVertical, Hash, Info, Loader2, Menu, MoreHorizontal, Network, Pencil, PencilLine, 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, getHostedNetworkAvailabilityNoticeCopy, getInvalidUrlMessage, getServiceDisplayName, isNetworkAvailabilityPolicyActive, 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
|
|
29
|
+
const VERSION = "3.2.0";
|
|
30
30
|
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region src/components/ui/accordion.tsx
|
|
@@ -2940,6 +2940,457 @@ function AddressField({ id, label, placeholder, helperText, control, name, width
|
|
|
2940
2940
|
}
|
|
2941
2941
|
AddressField.displayName = "AddressField";
|
|
2942
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
|
+
addSingleAddress: "Add",
|
|
3049
|
+
invalidSingleAddress: "Enter a valid address",
|
|
3050
|
+
enableBulkEntry: "Bulk paste",
|
|
3051
|
+
enableSingleEntry: "Single entry",
|
|
3052
|
+
addAddresses: "Add addresses",
|
|
3053
|
+
addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
|
|
3054
|
+
invalidPrefix: "Invalid:",
|
|
3055
|
+
invalidMore: (extraCount) => `(+${extraCount} more)`,
|
|
3056
|
+
maxItemsReached: (maxItems) => `Maximum of ${maxItems} addresses reached.`,
|
|
3057
|
+
addressesAdded: (count) => `${count} address${count === 1 ? "" : "es"} added`,
|
|
3058
|
+
removeAddress: (index) => `Remove address ${index + 1}`,
|
|
3059
|
+
previewReadyToAdd: (count) => `${count} ready to add`,
|
|
3060
|
+
previewInvalid: (count) => `${count} invalid`,
|
|
3061
|
+
previewAlreadyListed: (count) => `${count} already listed`,
|
|
3062
|
+
previewDuplicateInPaste: (count) => `${count} duplicate in paste`,
|
|
3063
|
+
bulkNothingAdded: "No addresses were added. Check formatting and try again.",
|
|
3064
|
+
bulkAdded: (count) => `Added ${count} address${count === 1 ? "" : "es"}`,
|
|
3065
|
+
bulkInvalidFormats: (count) => `${count} invalid format${count === 1 ? "" : "s"}`,
|
|
3066
|
+
bulkAlreadyListed: (count) => `${count} already listed`,
|
|
3067
|
+
bulkDuplicatesInPaste: (count) => `${count} duplicate${count === 1 ? "" : "s"} in paste`
|
|
3068
|
+
};
|
|
3069
|
+
/** Merges {@link DEFAULT_ADDRESS_LIST_FIELD_LABELS} with optional overrides. */
|
|
3070
|
+
function resolveAddressListFieldLabels(overrides) {
|
|
3071
|
+
return {
|
|
3072
|
+
...DEFAULT_ADDRESS_LIST_FIELD_LABELS,
|
|
3073
|
+
...overrides
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
/** Builds the inline helper preview shown while the user types in the draft textarea. */
|
|
3077
|
+
function buildAddressListPreviewSummary(classification, labels) {
|
|
3078
|
+
if (!classification) return null;
|
|
3079
|
+
const skipped = classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length;
|
|
3080
|
+
if (classification.accepted.length === 0 && skipped === 0) return null;
|
|
3081
|
+
const parts = [];
|
|
3082
|
+
if (classification.accepted.length > 0) parts.push(labels.previewReadyToAdd(classification.accepted.length));
|
|
3083
|
+
if (classification.invalid.length > 0) parts.push(labels.previewInvalid(classification.invalid.length));
|
|
3084
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.previewAlreadyListed(classification.alreadyListed.length));
|
|
3085
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.previewDuplicateInPaste(classification.duplicatesInInput.length));
|
|
3086
|
+
return parts.join(" · ");
|
|
3087
|
+
}
|
|
3088
|
+
/** Formats post-add feedback after the user commits parsed addresses to the list. */
|
|
3089
|
+
function formatAddressBulkSummary(classification, addedCount, labels) {
|
|
3090
|
+
if (addedCount === 0 && classification.accepted.length === 0) {
|
|
3091
|
+
if (classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length === 0) return null;
|
|
3092
|
+
return labels.bulkNothingAdded;
|
|
3093
|
+
}
|
|
3094
|
+
const parts = [];
|
|
3095
|
+
if (addedCount > 0) parts.push(labels.bulkAdded(addedCount));
|
|
3096
|
+
if (classification.invalid.length > 0) parts.push(labels.bulkInvalidFormats(classification.invalid.length));
|
|
3097
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.bulkAlreadyListed(classification.alreadyListed.length));
|
|
3098
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.bulkDuplicatesInPaste(classification.duplicatesInInput.length));
|
|
3099
|
+
if (parts.length === 0) return null;
|
|
3100
|
+
return parts.join(". ") + ".";
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
//#endregion
|
|
3104
|
+
//#region src/components/fields/AddressListField/AddressListBulkEntry.tsx
|
|
3105
|
+
/** Bulk paste entry — info callout, textarea, and add action in a compact stack. */
|
|
3106
|
+
function AddressListBulkEntry({ fieldId, value, onAdd, placeholder, formatHint, addressing, maxItems, disabled = false, maxInvalidPreview = 3, labels, modeToggle }) {
|
|
3107
|
+
const atLimit = maxItems != null && value.length >= maxItems;
|
|
3108
|
+
const inputDisabled = disabled || atLimit;
|
|
3109
|
+
const [feedback, setFeedback] = useState(null);
|
|
3110
|
+
const { control, reset, watch } = useForm({
|
|
3111
|
+
defaultValues: { input: "" },
|
|
3112
|
+
mode: "onChange"
|
|
3113
|
+
});
|
|
3114
|
+
const rawInput = watch("input");
|
|
3115
|
+
const classification = useMemo(() => {
|
|
3116
|
+
if (!rawInput.trim()) return null;
|
|
3117
|
+
return classifyAddressCandidates(parseDelimitedAddressInput(rawInput), value, addressing, maxItems);
|
|
3118
|
+
}, [
|
|
3119
|
+
rawInput,
|
|
3120
|
+
value,
|
|
3121
|
+
addressing,
|
|
3122
|
+
maxItems
|
|
3123
|
+
]);
|
|
3124
|
+
const previewSummary = useMemo(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
|
|
3125
|
+
const statusMessage = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
|
|
3126
|
+
const handleAdd = useCallback(() => {
|
|
3127
|
+
if (!classification || classification.accepted.length === 0 || atLimit) return;
|
|
3128
|
+
onAdd(classification.accepted);
|
|
3129
|
+
reset({ input: "" });
|
|
3130
|
+
setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
|
|
3131
|
+
}, [
|
|
3132
|
+
classification,
|
|
3133
|
+
atLimit,
|
|
3134
|
+
onAdd,
|
|
3135
|
+
reset,
|
|
3136
|
+
labels
|
|
3137
|
+
]);
|
|
3138
|
+
const acceptedCount = classification?.accepted.length ?? 0;
|
|
3139
|
+
const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
|
|
3140
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3141
|
+
className: "space-y-3",
|
|
3142
|
+
children: [
|
|
3143
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3144
|
+
className: "flex items-start gap-2 rounded-lg border border-border/70 bg-muted/30 px-3 py-2.5",
|
|
3145
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("p", {
|
|
3146
|
+
className: "text-xs leading-relaxed text-muted-foreground",
|
|
3147
|
+
children: formatHint
|
|
3148
|
+
})]
|
|
3149
|
+
}),
|
|
3150
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
3151
|
+
className: "relative",
|
|
3152
|
+
children: /* @__PURE__ */ jsx(TextAreaField, {
|
|
3153
|
+
id: `address-list-bulk-${fieldId}`,
|
|
3154
|
+
name: "input",
|
|
3155
|
+
label: "",
|
|
3156
|
+
placeholder,
|
|
3157
|
+
helperText: statusMessage,
|
|
3158
|
+
control,
|
|
3159
|
+
rows: 4,
|
|
3160
|
+
validation: { required: false },
|
|
3161
|
+
readOnly: inputDisabled
|
|
3162
|
+
})
|
|
3163
|
+
}), modeToggle ? /* @__PURE__ */ jsx("div", {
|
|
3164
|
+
className: "flex justify-end pr-2",
|
|
3165
|
+
children: modeToggle
|
|
3166
|
+
}) : null] }),
|
|
3167
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3168
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
3169
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3170
|
+
className: "min-w-0 flex-1",
|
|
3171
|
+
children: [classification && classification.invalid.length > 0 ? /* @__PURE__ */ jsxs("p", {
|
|
3172
|
+
className: "text-xs text-destructive",
|
|
3173
|
+
children: [
|
|
3174
|
+
labels.invalidPrefix,
|
|
3175
|
+
" ",
|
|
3176
|
+
classification.invalid.slice(0, maxInvalidPreview).join(", "),
|
|
3177
|
+
classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
|
|
3178
|
+
]
|
|
3179
|
+
}) : null, atLimit && maxItems != null ? /* @__PURE__ */ jsx("p", {
|
|
3180
|
+
className: "text-xs text-muted-foreground",
|
|
3181
|
+
children: labels.maxItemsReached(maxItems)
|
|
3182
|
+
}) : null]
|
|
3183
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
3184
|
+
type: "button",
|
|
3185
|
+
size: "sm",
|
|
3186
|
+
className: "shrink-0",
|
|
3187
|
+
disabled: inputDisabled || acceptedCount === 0,
|
|
3188
|
+
onClick: () => handleAdd(),
|
|
3189
|
+
children: [/* @__PURE__ */ jsx(Plus, { className: "mr-1 size-4" }), addButtonLabel]
|
|
3190
|
+
})]
|
|
3191
|
+
})
|
|
3192
|
+
]
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
//#endregion
|
|
3197
|
+
//#region src/components/fields/AddressListField/AddressListEntries.tsx
|
|
3198
|
+
/** Renders committed addresses with remove actions. Shared by single and bulk entry modes. */
|
|
3199
|
+
function AddressListEntries({ value, getExplorerUrl, disabled = false, labels, onRemove }) {
|
|
3200
|
+
if (value.length === 0) return null;
|
|
3201
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3202
|
+
className: "space-y-1",
|
|
3203
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
3204
|
+
className: "text-xs text-muted-foreground",
|
|
3205
|
+
children: labels.addressesAdded(value.length)
|
|
3206
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3207
|
+
className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
|
|
3208
|
+
children: value.map((address, index) => /* @__PURE__ */ jsxs("div", {
|
|
3209
|
+
className: "flex items-center justify-between rounded bg-muted p-2",
|
|
3210
|
+
children: [/* @__PURE__ */ jsx(AddressDisplay, {
|
|
3211
|
+
address,
|
|
3212
|
+
variant: "inline",
|
|
3213
|
+
truncateWhenLabeled: true,
|
|
3214
|
+
showCopyButton: true,
|
|
3215
|
+
explorerUrl: getExplorerUrl?.(address) ?? void 0
|
|
3216
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
3217
|
+
type: "button",
|
|
3218
|
+
variant: "ghost",
|
|
3219
|
+
size: "sm",
|
|
3220
|
+
className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
|
|
3221
|
+
onClick: () => onRemove(index),
|
|
3222
|
+
disabled,
|
|
3223
|
+
"aria-label": labels.removeAddress(index),
|
|
3224
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
|
|
3225
|
+
})]
|
|
3226
|
+
}, `${address}-${index}`))
|
|
3227
|
+
})]
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
//#endregion
|
|
3232
|
+
//#region src/components/fields/AddressListField/AddressListSingleEntry.tsx
|
|
3233
|
+
/** Single-address entry — inline {@link AddressField} with an Add button (address-book aware). */
|
|
3234
|
+
function AddressListSingleEntry({ fieldId, value, onAdd, placeholder, addressing, maxItems, disabled = false, labels, modeToggle }) {
|
|
3235
|
+
const atLimit = maxItems != null && value.length >= maxItems;
|
|
3236
|
+
const inputDisabled = disabled || atLimit;
|
|
3237
|
+
const { control, reset, getValues, handleSubmit } = useForm({
|
|
3238
|
+
defaultValues: { draft: "" },
|
|
3239
|
+
mode: "onChange"
|
|
3240
|
+
});
|
|
3241
|
+
const trimmedDraft = (useWatch({
|
|
3242
|
+
control,
|
|
3243
|
+
name: "draft"
|
|
3244
|
+
}) ?? "").trim();
|
|
3245
|
+
const validationState = useMemo(() => {
|
|
3246
|
+
if (!trimmedDraft) return {
|
|
3247
|
+
canAdd: false,
|
|
3248
|
+
showInvalid: false
|
|
3249
|
+
};
|
|
3250
|
+
if (value.includes(trimmedDraft)) return {
|
|
3251
|
+
canAdd: false,
|
|
3252
|
+
showInvalid: false
|
|
3253
|
+
};
|
|
3254
|
+
if (atLimit) return {
|
|
3255
|
+
canAdd: false,
|
|
3256
|
+
showInvalid: false
|
|
3257
|
+
};
|
|
3258
|
+
if (addressing && !addressing.isValidAddress(trimmedDraft)) return {
|
|
3259
|
+
canAdd: false,
|
|
3260
|
+
showInvalid: true
|
|
3261
|
+
};
|
|
3262
|
+
return {
|
|
3263
|
+
canAdd: true,
|
|
3264
|
+
showInvalid: false
|
|
3265
|
+
};
|
|
3266
|
+
}, [
|
|
3267
|
+
trimmedDraft,
|
|
3268
|
+
value,
|
|
3269
|
+
atLimit,
|
|
3270
|
+
addressing
|
|
3271
|
+
]);
|
|
3272
|
+
const submitAdd = useCallback(() => {
|
|
3273
|
+
if (!validationState.canAdd) return;
|
|
3274
|
+
onAdd(getValues("draft").trim());
|
|
3275
|
+
reset({ draft: "" });
|
|
3276
|
+
}, [
|
|
3277
|
+
validationState.canAdd,
|
|
3278
|
+
onAdd,
|
|
3279
|
+
getValues,
|
|
3280
|
+
reset
|
|
3281
|
+
]);
|
|
3282
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3283
|
+
className: "space-y-1",
|
|
3284
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3285
|
+
className: "flex items-start gap-2",
|
|
3286
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3287
|
+
className: "min-w-0 flex-1",
|
|
3288
|
+
children: [/* @__PURE__ */ jsx(AddressField, {
|
|
3289
|
+
id: `address-list-single-${fieldId}`,
|
|
3290
|
+
name: "draft",
|
|
3291
|
+
label: "",
|
|
3292
|
+
placeholder,
|
|
3293
|
+
helperText: validationState.showInvalid ? labels.invalidSingleAddress : void 0,
|
|
3294
|
+
control,
|
|
3295
|
+
addressing,
|
|
3296
|
+
validation: { required: false },
|
|
3297
|
+
readOnly: inputDisabled
|
|
3298
|
+
}), modeToggle ? /* @__PURE__ */ jsx("div", {
|
|
3299
|
+
className: "-mt-px flex justify-end pr-2",
|
|
3300
|
+
children: modeToggle
|
|
3301
|
+
}) : null]
|
|
3302
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
3303
|
+
type: "button",
|
|
3304
|
+
className: "h-10 shrink-0",
|
|
3305
|
+
disabled: inputDisabled || !validationState.canAdd,
|
|
3306
|
+
onClick: handleSubmit(submitAdd),
|
|
3307
|
+
children: [/* @__PURE__ */ jsx(Plus, { className: "mr-1 size-4" }), labels.addSingleAddress]
|
|
3308
|
+
})]
|
|
3309
|
+
}), atLimit && maxItems != null && /* @__PURE__ */ jsx("p", {
|
|
3310
|
+
className: "text-xs text-muted-foreground",
|
|
3311
|
+
children: labels.maxItemsReached(maxItems)
|
|
3312
|
+
})]
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
//#endregion
|
|
3317
|
+
//#region src/components/fields/AddressListField/AddressListField.tsx
|
|
3318
|
+
/**
|
|
3319
|
+
* Multi-address list field with single-entry default and optional bulk paste.
|
|
3320
|
+
*/
|
|
3321
|
+
function AddressListField({ value, onChange, placeholder, bulkPlaceholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, defaultEntryMode = "single", allowModeToggle = true, labels: labelOverrides }) {
|
|
3322
|
+
const fieldId = useId();
|
|
3323
|
+
const labels = useMemo(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
|
|
3324
|
+
const [entryMode, setEntryMode] = useState(defaultEntryMode);
|
|
3325
|
+
const resolvedBulkPlaceholder = bulkPlaceholder ?? placeholder;
|
|
3326
|
+
const handleRemove = useCallback((index) => {
|
|
3327
|
+
onChange(value.filter((_, currentIndex) => currentIndex !== index));
|
|
3328
|
+
}, [onChange, value]);
|
|
3329
|
+
const handleAddSingle = useCallback((address) => {
|
|
3330
|
+
onChange([...value, address]);
|
|
3331
|
+
}, [onChange, value]);
|
|
3332
|
+
const handleAddBulk = useCallback((addresses) => {
|
|
3333
|
+
onChange([...value, ...addresses]);
|
|
3334
|
+
}, [onChange, value]);
|
|
3335
|
+
const switchToBulk = useCallback(() => setEntryMode("bulk"), []);
|
|
3336
|
+
const switchToSingle = useCallback(() => setEntryMode("single"), []);
|
|
3337
|
+
const toggleClassName = "inline-flex items-center gap-1 rounded-b-md rounded-t-none border border-t-0 border-input bg-muted/40 px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground";
|
|
3338
|
+
const modeToggle = !allowModeToggle ? void 0 : entryMode === "single" ? /* @__PURE__ */ jsxs("button", {
|
|
3339
|
+
type: "button",
|
|
3340
|
+
onClick: switchToBulk,
|
|
3341
|
+
className: toggleClassName,
|
|
3342
|
+
children: [/* @__PURE__ */ jsx(ClipboardPaste, { className: "size-3" }), labels.enableBulkEntry]
|
|
3343
|
+
}) : /* @__PURE__ */ jsxs("button", {
|
|
3344
|
+
type: "button",
|
|
3345
|
+
onClick: switchToSingle,
|
|
3346
|
+
className: toggleClassName,
|
|
3347
|
+
children: [/* @__PURE__ */ jsx(PencilLine, { className: "size-3" }), labels.enableSingleEntry]
|
|
3348
|
+
});
|
|
3349
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3350
|
+
className: cn("space-y-3", className),
|
|
3351
|
+
children: [
|
|
3352
|
+
label ? /* @__PURE__ */ jsx(Label, {
|
|
3353
|
+
className: "text-sm font-medium leading-none",
|
|
3354
|
+
children: label
|
|
3355
|
+
}) : null,
|
|
3356
|
+
helperText && entryMode === "single" ? /* @__PURE__ */ jsx("p", {
|
|
3357
|
+
className: "text-sm leading-relaxed text-muted-foreground",
|
|
3358
|
+
children: helperText
|
|
3359
|
+
}) : null,
|
|
3360
|
+
entryMode === "single" ? /* @__PURE__ */ jsx(AddressListSingleEntry, {
|
|
3361
|
+
fieldId,
|
|
3362
|
+
value,
|
|
3363
|
+
onAdd: handleAddSingle,
|
|
3364
|
+
placeholder,
|
|
3365
|
+
addressing,
|
|
3366
|
+
maxItems,
|
|
3367
|
+
disabled,
|
|
3368
|
+
labels,
|
|
3369
|
+
modeToggle
|
|
3370
|
+
}) : /* @__PURE__ */ jsx(AddressListBulkEntry, {
|
|
3371
|
+
fieldId,
|
|
3372
|
+
value,
|
|
3373
|
+
onAdd: handleAddBulk,
|
|
3374
|
+
placeholder: resolvedBulkPlaceholder,
|
|
3375
|
+
formatHint,
|
|
3376
|
+
addressing,
|
|
3377
|
+
maxItems,
|
|
3378
|
+
disabled,
|
|
3379
|
+
maxInvalidPreview,
|
|
3380
|
+
labels,
|
|
3381
|
+
modeToggle
|
|
3382
|
+
}),
|
|
3383
|
+
/* @__PURE__ */ jsx(AddressListEntries, {
|
|
3384
|
+
value,
|
|
3385
|
+
getExplorerUrl,
|
|
3386
|
+
disabled,
|
|
3387
|
+
labels,
|
|
3388
|
+
onRemove: handleRemove
|
|
3389
|
+
})
|
|
3390
|
+
]
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
|
|
2943
3394
|
//#endregion
|
|
2944
3395
|
//#region src/components/fields/address-suggestion/address-suggestion-context.tsx
|
|
2945
3396
|
/**
|
|
@@ -5732,105 +6183,6 @@ function SelectGroupedField({ id, label, placeholder = "Select an option", helpe
|
|
|
5732
6183
|
}
|
|
5733
6184
|
SelectGroupedField.displayName = "SelectGroupedField";
|
|
5734
6185
|
|
|
5735
|
-
//#endregion
|
|
5736
|
-
//#region src/components/fields/TextAreaField.tsx
|
|
5737
|
-
/**
|
|
5738
|
-
* Multi-line text input field component specifically designed for React Hook Form integration.
|
|
5739
|
-
*
|
|
5740
|
-
* Architecture flow:
|
|
5741
|
-
* 1. Form schemas are generated from contract functions using adapters
|
|
5742
|
-
* 2. TransactionForm renders the overall form structure with React Hook Form
|
|
5743
|
-
* 3. DynamicFormField selects the appropriate field component based on field type
|
|
5744
|
-
* 4. BaseField provides consistent layout and hook form integration
|
|
5745
|
-
* 5. This component handles textarea-specific rendering and validation
|
|
5746
|
-
*
|
|
5747
|
-
* The component includes:
|
|
5748
|
-
* - Integration with React Hook Form
|
|
5749
|
-
* - Resizable multi-line text input
|
|
5750
|
-
* - Character limit support
|
|
5751
|
-
* - Customizable validation through adapter integration
|
|
5752
|
-
* - Automatic error handling and reporting
|
|
5753
|
-
* - Full accessibility support with ARIA attributes
|
|
5754
|
-
* - Keyboard navigation with Escape to clear
|
|
5755
|
-
*/
|
|
5756
|
-
function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea }) {
|
|
5757
|
-
const isRequired = !!validation?.required;
|
|
5758
|
-
const errorId = `${id}-error`;
|
|
5759
|
-
const descriptionId = `${id}-description`;
|
|
5760
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5761
|
-
className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
|
|
5762
|
-
children: [label && /* @__PURE__ */ jsxs(Label, {
|
|
5763
|
-
htmlFor: id,
|
|
5764
|
-
children: [
|
|
5765
|
-
label,
|
|
5766
|
-
" ",
|
|
5767
|
-
isRequired && /* @__PURE__ */ jsx("span", {
|
|
5768
|
-
className: "text-destructive",
|
|
5769
|
-
children: "*"
|
|
5770
|
-
})
|
|
5771
|
-
]
|
|
5772
|
-
}), /* @__PURE__ */ jsx(Controller, {
|
|
5773
|
-
control,
|
|
5774
|
-
name,
|
|
5775
|
-
rules: { validate: (value) => {
|
|
5776
|
-
if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
|
|
5777
|
-
if (validateTextArea && value) {
|
|
5778
|
-
const validation$1 = validateTextArea(value);
|
|
5779
|
-
if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
|
|
5780
|
-
}
|
|
5781
|
-
if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
|
|
5782
|
-
return true;
|
|
5783
|
-
} },
|
|
5784
|
-
render: ({ field, fieldState: { error, isTouched } }) => {
|
|
5785
|
-
const hasError = !!error;
|
|
5786
|
-
const shouldShowError = hasError && isTouched;
|
|
5787
|
-
const validationClasses = getValidationStateClasses(error, isTouched);
|
|
5788
|
-
const accessibilityProps = getAccessibilityProps({
|
|
5789
|
-
id,
|
|
5790
|
-
hasError,
|
|
5791
|
-
isRequired,
|
|
5792
|
-
hasHelperText: !!helperText
|
|
5793
|
-
});
|
|
5794
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5795
|
-
/* @__PURE__ */ jsx(Textarea, {
|
|
5796
|
-
...field,
|
|
5797
|
-
id,
|
|
5798
|
-
placeholder,
|
|
5799
|
-
rows,
|
|
5800
|
-
maxLength,
|
|
5801
|
-
className: validationClasses,
|
|
5802
|
-
value: field.value ?? "",
|
|
5803
|
-
...accessibilityProps,
|
|
5804
|
-
"aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
|
|
5805
|
-
onKeyDown: handleEscapeKey((value) => {
|
|
5806
|
-
if (typeof field.onChange === "function") field.onChange(value);
|
|
5807
|
-
}, field.value)
|
|
5808
|
-
}),
|
|
5809
|
-
maxLength && typeof field.value === "string" && /* @__PURE__ */ jsxs("div", {
|
|
5810
|
-
className: "text-muted-foreground text-right text-xs",
|
|
5811
|
-
children: [
|
|
5812
|
-
field.value.length,
|
|
5813
|
-
"/",
|
|
5814
|
-
maxLength
|
|
5815
|
-
]
|
|
5816
|
-
}),
|
|
5817
|
-
helperText && /* @__PURE__ */ jsx("div", {
|
|
5818
|
-
id: descriptionId,
|
|
5819
|
-
className: "text-muted-foreground text-sm",
|
|
5820
|
-
children: helperText
|
|
5821
|
-
}),
|
|
5822
|
-
/* @__PURE__ */ jsx(ErrorMessage, {
|
|
5823
|
-
error,
|
|
5824
|
-
id: errorId,
|
|
5825
|
-
message: shouldShowError ? error?.message : void 0
|
|
5826
|
-
})
|
|
5827
|
-
] });
|
|
5828
|
-
}
|
|
5829
|
-
})]
|
|
5830
|
-
});
|
|
5831
|
-
}
|
|
5832
|
-
TextAreaField.displayName = "TextAreaField";
|
|
5833
|
-
|
|
5834
6186
|
//#endregion
|
|
5835
6187
|
//#region src/components/fields/TextField.tsx
|
|
5836
6188
|
/**
|
|
@@ -6422,5 +6774,5 @@ const Toaster = ({ ...props }) => {
|
|
|
6422
6774
|
};
|
|
6423
6775
|
|
|
6424
6776
|
//#endregion
|
|
6425
|
-
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, 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, 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 };
|
|
6777
|
+
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 };
|
|
6426
6778
|
//# sourceMappingURL=index.mjs.map
|