@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.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
|
|
41
|
+
const VERSION = "3.2.0";
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/components/ui/accordion.tsx
|
|
@@ -2952,6 +2952,457 @@ function AddressField({ id, label, placeholder, helperText, control, name, width
|
|
|
2952
2952
|
}
|
|
2953
2953
|
AddressField.displayName = "AddressField";
|
|
2954
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
|
+
addSingleAddress: "Add",
|
|
3061
|
+
invalidSingleAddress: "Enter a valid address",
|
|
3062
|
+
enableBulkEntry: "Bulk paste",
|
|
3063
|
+
enableSingleEntry: "Single entry",
|
|
3064
|
+
addAddresses: "Add addresses",
|
|
3065
|
+
addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
|
|
3066
|
+
invalidPrefix: "Invalid:",
|
|
3067
|
+
invalidMore: (extraCount) => `(+${extraCount} more)`,
|
|
3068
|
+
maxItemsReached: (maxItems) => `Maximum of ${maxItems} addresses reached.`,
|
|
3069
|
+
addressesAdded: (count) => `${count} address${count === 1 ? "" : "es"} added`,
|
|
3070
|
+
removeAddress: (index) => `Remove address ${index + 1}`,
|
|
3071
|
+
previewReadyToAdd: (count) => `${count} ready to add`,
|
|
3072
|
+
previewInvalid: (count) => `${count} invalid`,
|
|
3073
|
+
previewAlreadyListed: (count) => `${count} already listed`,
|
|
3074
|
+
previewDuplicateInPaste: (count) => `${count} duplicate in paste`,
|
|
3075
|
+
bulkNothingAdded: "No addresses were added. Check formatting and try again.",
|
|
3076
|
+
bulkAdded: (count) => `Added ${count} address${count === 1 ? "" : "es"}`,
|
|
3077
|
+
bulkInvalidFormats: (count) => `${count} invalid format${count === 1 ? "" : "s"}`,
|
|
3078
|
+
bulkAlreadyListed: (count) => `${count} already listed`,
|
|
3079
|
+
bulkDuplicatesInPaste: (count) => `${count} duplicate${count === 1 ? "" : "s"} in paste`
|
|
3080
|
+
};
|
|
3081
|
+
/** Merges {@link DEFAULT_ADDRESS_LIST_FIELD_LABELS} with optional overrides. */
|
|
3082
|
+
function resolveAddressListFieldLabels(overrides) {
|
|
3083
|
+
return {
|
|
3084
|
+
...DEFAULT_ADDRESS_LIST_FIELD_LABELS,
|
|
3085
|
+
...overrides
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
/** Builds the inline helper preview shown while the user types in the draft textarea. */
|
|
3089
|
+
function buildAddressListPreviewSummary(classification, labels) {
|
|
3090
|
+
if (!classification) return null;
|
|
3091
|
+
const skipped = classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length;
|
|
3092
|
+
if (classification.accepted.length === 0 && skipped === 0) return null;
|
|
3093
|
+
const parts = [];
|
|
3094
|
+
if (classification.accepted.length > 0) parts.push(labels.previewReadyToAdd(classification.accepted.length));
|
|
3095
|
+
if (classification.invalid.length > 0) parts.push(labels.previewInvalid(classification.invalid.length));
|
|
3096
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.previewAlreadyListed(classification.alreadyListed.length));
|
|
3097
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.previewDuplicateInPaste(classification.duplicatesInInput.length));
|
|
3098
|
+
return parts.join(" · ");
|
|
3099
|
+
}
|
|
3100
|
+
/** Formats post-add feedback after the user commits parsed addresses to the list. */
|
|
3101
|
+
function formatAddressBulkSummary(classification, addedCount, labels) {
|
|
3102
|
+
if (addedCount === 0 && classification.accepted.length === 0) {
|
|
3103
|
+
if (classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length === 0) return null;
|
|
3104
|
+
return labels.bulkNothingAdded;
|
|
3105
|
+
}
|
|
3106
|
+
const parts = [];
|
|
3107
|
+
if (addedCount > 0) parts.push(labels.bulkAdded(addedCount));
|
|
3108
|
+
if (classification.invalid.length > 0) parts.push(labels.bulkInvalidFormats(classification.invalid.length));
|
|
3109
|
+
if (classification.alreadyListed.length > 0) parts.push(labels.bulkAlreadyListed(classification.alreadyListed.length));
|
|
3110
|
+
if (classification.duplicatesInInput.length > 0) parts.push(labels.bulkDuplicatesInPaste(classification.duplicatesInInput.length));
|
|
3111
|
+
if (parts.length === 0) return null;
|
|
3112
|
+
return parts.join(". ") + ".";
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
//#endregion
|
|
3116
|
+
//#region src/components/fields/AddressListField/AddressListBulkEntry.tsx
|
|
3117
|
+
/** Bulk paste entry — info callout, textarea, and add action in a compact stack. */
|
|
3118
|
+
function AddressListBulkEntry({ fieldId, value, onAdd, placeholder, formatHint, addressing, maxItems, disabled = false, maxInvalidPreview = 3, labels, modeToggle }) {
|
|
3119
|
+
const atLimit = maxItems != null && value.length >= maxItems;
|
|
3120
|
+
const inputDisabled = disabled || atLimit;
|
|
3121
|
+
const [feedback, setFeedback] = (0, react.useState)(null);
|
|
3122
|
+
const { control, reset, watch } = (0, react_hook_form.useForm)({
|
|
3123
|
+
defaultValues: { input: "" },
|
|
3124
|
+
mode: "onChange"
|
|
3125
|
+
});
|
|
3126
|
+
const rawInput = watch("input");
|
|
3127
|
+
const classification = (0, react.useMemo)(() => {
|
|
3128
|
+
if (!rawInput.trim()) return null;
|
|
3129
|
+
return (0, _openzeppelin_ui_utils.classifyAddressCandidates)((0, _openzeppelin_ui_utils.parseDelimitedAddressInput)(rawInput), value, addressing, maxItems);
|
|
3130
|
+
}, [
|
|
3131
|
+
rawInput,
|
|
3132
|
+
value,
|
|
3133
|
+
addressing,
|
|
3134
|
+
maxItems
|
|
3135
|
+
]);
|
|
3136
|
+
const previewSummary = (0, react.useMemo)(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
|
|
3137
|
+
const statusMessage = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
|
|
3138
|
+
const handleAdd = (0, react.useCallback)(() => {
|
|
3139
|
+
if (!classification || classification.accepted.length === 0 || atLimit) return;
|
|
3140
|
+
onAdd(classification.accepted);
|
|
3141
|
+
reset({ input: "" });
|
|
3142
|
+
setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
|
|
3143
|
+
}, [
|
|
3144
|
+
classification,
|
|
3145
|
+
atLimit,
|
|
3146
|
+
onAdd,
|
|
3147
|
+
reset,
|
|
3148
|
+
labels
|
|
3149
|
+
]);
|
|
3150
|
+
const acceptedCount = classification?.accepted.length ?? 0;
|
|
3151
|
+
const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
|
|
3152
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3153
|
+
className: "space-y-3",
|
|
3154
|
+
children: [
|
|
3155
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3156
|
+
className: "flex items-start gap-2 rounded-lg border border-border/70 bg-muted/30 px-3 py-2.5",
|
|
3157
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Info, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3158
|
+
className: "text-xs leading-relaxed text-muted-foreground",
|
|
3159
|
+
children: formatHint
|
|
3160
|
+
})]
|
|
3161
|
+
}),
|
|
3162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3163
|
+
className: "relative",
|
|
3164
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
|
|
3165
|
+
id: `address-list-bulk-${fieldId}`,
|
|
3166
|
+
name: "input",
|
|
3167
|
+
label: "",
|
|
3168
|
+
placeholder,
|
|
3169
|
+
helperText: statusMessage,
|
|
3170
|
+
control,
|
|
3171
|
+
rows: 4,
|
|
3172
|
+
validation: { required: false },
|
|
3173
|
+
readOnly: inputDisabled
|
|
3174
|
+
})
|
|
3175
|
+
}), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3176
|
+
className: "flex justify-end pr-2",
|
|
3177
|
+
children: modeToggle
|
|
3178
|
+
}) : null] }),
|
|
3179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3180
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
3181
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3182
|
+
className: "min-w-0 flex-1",
|
|
3183
|
+
children: [classification && classification.invalid.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3184
|
+
className: "text-xs text-destructive",
|
|
3185
|
+
children: [
|
|
3186
|
+
labels.invalidPrefix,
|
|
3187
|
+
" ",
|
|
3188
|
+
classification.invalid.slice(0, maxInvalidPreview).join(", "),
|
|
3189
|
+
classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
|
|
3190
|
+
]
|
|
3191
|
+
}) : null, atLimit && maxItems != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3192
|
+
className: "text-xs text-muted-foreground",
|
|
3193
|
+
children: labels.maxItemsReached(maxItems)
|
|
3194
|
+
}) : null]
|
|
3195
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
3196
|
+
type: "button",
|
|
3197
|
+
size: "sm",
|
|
3198
|
+
className: "shrink-0",
|
|
3199
|
+
disabled: inputDisabled || acceptedCount === 0,
|
|
3200
|
+
onClick: () => handleAdd(),
|
|
3201
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), addButtonLabel]
|
|
3202
|
+
})]
|
|
3203
|
+
})
|
|
3204
|
+
]
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
//#endregion
|
|
3209
|
+
//#region src/components/fields/AddressListField/AddressListEntries.tsx
|
|
3210
|
+
/** Renders committed addresses with remove actions. Shared by single and bulk entry modes. */
|
|
3211
|
+
function AddressListEntries({ value, getExplorerUrl, disabled = false, labels, onRemove }) {
|
|
3212
|
+
if (value.length === 0) return null;
|
|
3213
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3214
|
+
className: "space-y-1",
|
|
3215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3216
|
+
className: "text-xs text-muted-foreground",
|
|
3217
|
+
children: labels.addressesAdded(value.length)
|
|
3218
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3219
|
+
className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
|
|
3220
|
+
children: value.map((address, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3221
|
+
className: "flex items-center justify-between rounded bg-muted p-2",
|
|
3222
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressDisplay, {
|
|
3223
|
+
address,
|
|
3224
|
+
variant: "inline",
|
|
3225
|
+
truncateWhenLabeled: true,
|
|
3226
|
+
showCopyButton: true,
|
|
3227
|
+
explorerUrl: getExplorerUrl?.(address) ?? void 0
|
|
3228
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
3229
|
+
type: "button",
|
|
3230
|
+
variant: "ghost",
|
|
3231
|
+
size: "sm",
|
|
3232
|
+
className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
|
|
3233
|
+
onClick: () => onRemove(index),
|
|
3234
|
+
disabled,
|
|
3235
|
+
"aria-label": labels.removeAddress(index),
|
|
3236
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-3.5" })
|
|
3237
|
+
})]
|
|
3238
|
+
}, `${address}-${index}`))
|
|
3239
|
+
})]
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
//#endregion
|
|
3244
|
+
//#region src/components/fields/AddressListField/AddressListSingleEntry.tsx
|
|
3245
|
+
/** Single-address entry — inline {@link AddressField} with an Add button (address-book aware). */
|
|
3246
|
+
function AddressListSingleEntry({ fieldId, value, onAdd, placeholder, addressing, maxItems, disabled = false, labels, modeToggle }) {
|
|
3247
|
+
const atLimit = maxItems != null && value.length >= maxItems;
|
|
3248
|
+
const inputDisabled = disabled || atLimit;
|
|
3249
|
+
const { control, reset, getValues, handleSubmit } = (0, react_hook_form.useForm)({
|
|
3250
|
+
defaultValues: { draft: "" },
|
|
3251
|
+
mode: "onChange"
|
|
3252
|
+
});
|
|
3253
|
+
const trimmedDraft = ((0, react_hook_form.useWatch)({
|
|
3254
|
+
control,
|
|
3255
|
+
name: "draft"
|
|
3256
|
+
}) ?? "").trim();
|
|
3257
|
+
const validationState = (0, react.useMemo)(() => {
|
|
3258
|
+
if (!trimmedDraft) return {
|
|
3259
|
+
canAdd: false,
|
|
3260
|
+
showInvalid: false
|
|
3261
|
+
};
|
|
3262
|
+
if (value.includes(trimmedDraft)) return {
|
|
3263
|
+
canAdd: false,
|
|
3264
|
+
showInvalid: false
|
|
3265
|
+
};
|
|
3266
|
+
if (atLimit) return {
|
|
3267
|
+
canAdd: false,
|
|
3268
|
+
showInvalid: false
|
|
3269
|
+
};
|
|
3270
|
+
if (addressing && !addressing.isValidAddress(trimmedDraft)) return {
|
|
3271
|
+
canAdd: false,
|
|
3272
|
+
showInvalid: true
|
|
3273
|
+
};
|
|
3274
|
+
return {
|
|
3275
|
+
canAdd: true,
|
|
3276
|
+
showInvalid: false
|
|
3277
|
+
};
|
|
3278
|
+
}, [
|
|
3279
|
+
trimmedDraft,
|
|
3280
|
+
value,
|
|
3281
|
+
atLimit,
|
|
3282
|
+
addressing
|
|
3283
|
+
]);
|
|
3284
|
+
const submitAdd = (0, react.useCallback)(() => {
|
|
3285
|
+
if (!validationState.canAdd) return;
|
|
3286
|
+
onAdd(getValues("draft").trim());
|
|
3287
|
+
reset({ draft: "" });
|
|
3288
|
+
}, [
|
|
3289
|
+
validationState.canAdd,
|
|
3290
|
+
onAdd,
|
|
3291
|
+
getValues,
|
|
3292
|
+
reset
|
|
3293
|
+
]);
|
|
3294
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3295
|
+
className: "space-y-1",
|
|
3296
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3297
|
+
className: "flex items-start gap-2",
|
|
3298
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3299
|
+
className: "min-w-0 flex-1",
|
|
3300
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressField, {
|
|
3301
|
+
id: `address-list-single-${fieldId}`,
|
|
3302
|
+
name: "draft",
|
|
3303
|
+
label: "",
|
|
3304
|
+
placeholder,
|
|
3305
|
+
helperText: validationState.showInvalid ? labels.invalidSingleAddress : void 0,
|
|
3306
|
+
control,
|
|
3307
|
+
addressing,
|
|
3308
|
+
validation: { required: false },
|
|
3309
|
+
readOnly: inputDisabled
|
|
3310
|
+
}), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3311
|
+
className: "-mt-px flex justify-end pr-2",
|
|
3312
|
+
children: modeToggle
|
|
3313
|
+
}) : null]
|
|
3314
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
3315
|
+
type: "button",
|
|
3316
|
+
className: "h-10 shrink-0",
|
|
3317
|
+
disabled: inputDisabled || !validationState.canAdd,
|
|
3318
|
+
onClick: handleSubmit(submitAdd),
|
|
3319
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), labels.addSingleAddress]
|
|
3320
|
+
})]
|
|
3321
|
+
}), atLimit && maxItems != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3322
|
+
className: "text-xs text-muted-foreground",
|
|
3323
|
+
children: labels.maxItemsReached(maxItems)
|
|
3324
|
+
})]
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
//#endregion
|
|
3329
|
+
//#region src/components/fields/AddressListField/AddressListField.tsx
|
|
3330
|
+
/**
|
|
3331
|
+
* Multi-address list field with single-entry default and optional bulk paste.
|
|
3332
|
+
*/
|
|
3333
|
+
function AddressListField({ value, onChange, placeholder, bulkPlaceholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, defaultEntryMode = "single", allowModeToggle = true, labels: labelOverrides }) {
|
|
3334
|
+
const fieldId = (0, react.useId)();
|
|
3335
|
+
const labels = (0, react.useMemo)(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
|
|
3336
|
+
const [entryMode, setEntryMode] = (0, react.useState)(defaultEntryMode);
|
|
3337
|
+
const resolvedBulkPlaceholder = bulkPlaceholder ?? placeholder;
|
|
3338
|
+
const handleRemove = (0, react.useCallback)((index) => {
|
|
3339
|
+
onChange(value.filter((_, currentIndex) => currentIndex !== index));
|
|
3340
|
+
}, [onChange, value]);
|
|
3341
|
+
const handleAddSingle = (0, react.useCallback)((address) => {
|
|
3342
|
+
onChange([...value, address]);
|
|
3343
|
+
}, [onChange, value]);
|
|
3344
|
+
const handleAddBulk = (0, react.useCallback)((addresses) => {
|
|
3345
|
+
onChange([...value, ...addresses]);
|
|
3346
|
+
}, [onChange, value]);
|
|
3347
|
+
const switchToBulk = (0, react.useCallback)(() => setEntryMode("bulk"), []);
|
|
3348
|
+
const switchToSingle = (0, react.useCallback)(() => setEntryMode("single"), []);
|
|
3349
|
+
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";
|
|
3350
|
+
const modeToggle = !allowModeToggle ? void 0 : entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3351
|
+
type: "button",
|
|
3352
|
+
onClick: switchToBulk,
|
|
3353
|
+
className: toggleClassName,
|
|
3354
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ClipboardPaste, { className: "size-3" }), labels.enableBulkEntry]
|
|
3355
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3356
|
+
type: "button",
|
|
3357
|
+
onClick: switchToSingle,
|
|
3358
|
+
className: toggleClassName,
|
|
3359
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PencilLine, { className: "size-3" }), labels.enableSingleEntry]
|
|
3360
|
+
});
|
|
3361
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3362
|
+
className: (0, _openzeppelin_ui_utils.cn)("space-y-3", className),
|
|
3363
|
+
children: [
|
|
3364
|
+
label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
3365
|
+
className: "text-sm font-medium leading-none",
|
|
3366
|
+
children: label
|
|
3367
|
+
}) : null,
|
|
3368
|
+
helperText && entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3369
|
+
className: "text-sm leading-relaxed text-muted-foreground",
|
|
3370
|
+
children: helperText
|
|
3371
|
+
}) : null,
|
|
3372
|
+
entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListSingleEntry, {
|
|
3373
|
+
fieldId,
|
|
3374
|
+
value,
|
|
3375
|
+
onAdd: handleAddSingle,
|
|
3376
|
+
placeholder,
|
|
3377
|
+
addressing,
|
|
3378
|
+
maxItems,
|
|
3379
|
+
disabled,
|
|
3380
|
+
labels,
|
|
3381
|
+
modeToggle
|
|
3382
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListBulkEntry, {
|
|
3383
|
+
fieldId,
|
|
3384
|
+
value,
|
|
3385
|
+
onAdd: handleAddBulk,
|
|
3386
|
+
placeholder: resolvedBulkPlaceholder,
|
|
3387
|
+
formatHint,
|
|
3388
|
+
addressing,
|
|
3389
|
+
maxItems,
|
|
3390
|
+
disabled,
|
|
3391
|
+
maxInvalidPreview,
|
|
3392
|
+
labels,
|
|
3393
|
+
modeToggle
|
|
3394
|
+
}),
|
|
3395
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListEntries, {
|
|
3396
|
+
value,
|
|
3397
|
+
getExplorerUrl,
|
|
3398
|
+
disabled,
|
|
3399
|
+
labels,
|
|
3400
|
+
onRemove: handleRemove
|
|
3401
|
+
})
|
|
3402
|
+
]
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
|
|
2955
3406
|
//#endregion
|
|
2956
3407
|
//#region src/components/fields/address-suggestion/address-suggestion-context.tsx
|
|
2957
3408
|
/**
|
|
@@ -5744,105 +6195,6 @@ function SelectGroupedField({ id, label, placeholder = "Select an option", helpe
|
|
|
5744
6195
|
}
|
|
5745
6196
|
SelectGroupedField.displayName = "SelectGroupedField";
|
|
5746
6197
|
|
|
5747
|
-
//#endregion
|
|
5748
|
-
//#region src/components/fields/TextAreaField.tsx
|
|
5749
|
-
/**
|
|
5750
|
-
* Multi-line text input field component specifically designed for React Hook Form integration.
|
|
5751
|
-
*
|
|
5752
|
-
* Architecture flow:
|
|
5753
|
-
* 1. Form schemas are generated from contract functions using adapters
|
|
5754
|
-
* 2. TransactionForm renders the overall form structure with React Hook Form
|
|
5755
|
-
* 3. DynamicFormField selects the appropriate field component based on field type
|
|
5756
|
-
* 4. BaseField provides consistent layout and hook form integration
|
|
5757
|
-
* 5. This component handles textarea-specific rendering and validation
|
|
5758
|
-
*
|
|
5759
|
-
* The component includes:
|
|
5760
|
-
* - Integration with React Hook Form
|
|
5761
|
-
* - Resizable multi-line text input
|
|
5762
|
-
* - Character limit support
|
|
5763
|
-
* - Customizable validation through adapter integration
|
|
5764
|
-
* - Automatic error handling and reporting
|
|
5765
|
-
* - Full accessibility support with ARIA attributes
|
|
5766
|
-
* - Keyboard navigation with Escape to clear
|
|
5767
|
-
*/
|
|
5768
|
-
function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea }) {
|
|
5769
|
-
const isRequired = !!validation?.required;
|
|
5770
|
-
const errorId = `${id}-error`;
|
|
5771
|
-
const descriptionId = `${id}-description`;
|
|
5772
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5773
|
-
className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
|
|
5774
|
-
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
|
|
5775
|
-
htmlFor: id,
|
|
5776
|
-
children: [
|
|
5777
|
-
label,
|
|
5778
|
-
" ",
|
|
5779
|
-
isRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5780
|
-
className: "text-destructive",
|
|
5781
|
-
children: "*"
|
|
5782
|
-
})
|
|
5783
|
-
]
|
|
5784
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_hook_form.Controller, {
|
|
5785
|
-
control,
|
|
5786
|
-
name,
|
|
5787
|
-
rules: { validate: (value) => {
|
|
5788
|
-
if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
|
|
5789
|
-
if (validateTextArea && value) {
|
|
5790
|
-
const validation$1 = validateTextArea(value);
|
|
5791
|
-
if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
|
|
5792
|
-
}
|
|
5793
|
-
if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
|
|
5794
|
-
return true;
|
|
5795
|
-
} },
|
|
5796
|
-
render: ({ field, fieldState: { error, isTouched } }) => {
|
|
5797
|
-
const hasError = !!error;
|
|
5798
|
-
const shouldShowError = hasError && isTouched;
|
|
5799
|
-
const validationClasses = require_ErrorMessage.getValidationStateClasses(error, isTouched);
|
|
5800
|
-
const accessibilityProps = getAccessibilityProps({
|
|
5801
|
-
id,
|
|
5802
|
-
hasError,
|
|
5803
|
-
isRequired,
|
|
5804
|
-
hasHelperText: !!helperText
|
|
5805
|
-
});
|
|
5806
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5807
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Textarea, {
|
|
5808
|
-
...field,
|
|
5809
|
-
id,
|
|
5810
|
-
placeholder,
|
|
5811
|
-
rows,
|
|
5812
|
-
maxLength,
|
|
5813
|
-
className: validationClasses,
|
|
5814
|
-
value: field.value ?? "",
|
|
5815
|
-
...accessibilityProps,
|
|
5816
|
-
"aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
|
|
5817
|
-
onKeyDown: handleEscapeKey((value) => {
|
|
5818
|
-
if (typeof field.onChange === "function") field.onChange(value);
|
|
5819
|
-
}, field.value)
|
|
5820
|
-
}),
|
|
5821
|
-
maxLength && typeof field.value === "string" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5822
|
-
className: "text-muted-foreground text-right text-xs",
|
|
5823
|
-
children: [
|
|
5824
|
-
field.value.length,
|
|
5825
|
-
"/",
|
|
5826
|
-
maxLength
|
|
5827
|
-
]
|
|
5828
|
-
}),
|
|
5829
|
-
helperText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5830
|
-
id: descriptionId,
|
|
5831
|
-
className: "text-muted-foreground text-sm",
|
|
5832
|
-
children: helperText
|
|
5833
|
-
}),
|
|
5834
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ErrorMessage.ErrorMessage, {
|
|
5835
|
-
error,
|
|
5836
|
-
id: errorId,
|
|
5837
|
-
message: shouldShowError ? error?.message : void 0
|
|
5838
|
-
})
|
|
5839
|
-
] });
|
|
5840
|
-
}
|
|
5841
|
-
})]
|
|
5842
|
-
});
|
|
5843
|
-
}
|
|
5844
|
-
TextAreaField.displayName = "TextAreaField";
|
|
5845
|
-
|
|
5846
6198
|
//#endregion
|
|
5847
6199
|
//#region src/components/fields/TextField.tsx
|
|
5848
6200
|
/**
|
|
@@ -6441,6 +6793,7 @@ exports.AccordionTrigger = AccordionTrigger;
|
|
|
6441
6793
|
exports.AddressDisplay = AddressDisplay;
|
|
6442
6794
|
exports.AddressField = AddressField;
|
|
6443
6795
|
exports.AddressLabelProvider = AddressLabelProvider;
|
|
6796
|
+
exports.AddressListField = AddressListField;
|
|
6444
6797
|
exports.AddressSuggestionProvider = AddressSuggestionProvider;
|
|
6445
6798
|
exports.Alert = Alert;
|
|
6446
6799
|
exports.AlertDescription = AlertDescription;
|
|
@@ -6462,6 +6815,7 @@ exports.CardFooter = CardFooter;
|
|
|
6462
6815
|
exports.CardHeader = CardHeader;
|
|
6463
6816
|
exports.CardTitle = CardTitle;
|
|
6464
6817
|
exports.Checkbox = Checkbox;
|
|
6818
|
+
exports.DEFAULT_ADDRESS_LIST_FIELD_LABELS = DEFAULT_ADDRESS_LIST_FIELD_LABELS;
|
|
6465
6819
|
exports.DateRangePicker = DateRangePicker;
|
|
6466
6820
|
exports.DateTimeField = DateTimeField;
|
|
6467
6821
|
exports.Dialog = Dialog;
|
|
@@ -6567,10 +6921,12 @@ exports.ViewContractStateButton = ViewContractStateButton;
|
|
|
6567
6921
|
exports.WizardLayout = WizardLayout;
|
|
6568
6922
|
exports.WizardNavigation = WizardNavigation;
|
|
6569
6923
|
exports.WizardStepper = WizardStepper;
|
|
6924
|
+
exports.buildAddressListPreviewSummary = buildAddressListPreviewSummary;
|
|
6570
6925
|
exports.buttonVariants = buttonVariants;
|
|
6571
6926
|
exports.computeChildTouched = computeChildTouched;
|
|
6572
6927
|
exports.createFocusManager = createFocusManager;
|
|
6573
6928
|
exports.createValidationResult = require_ErrorMessage.createValidationResult;
|
|
6929
|
+
exports.formatAddressBulkSummary = formatAddressBulkSummary;
|
|
6574
6930
|
exports.formatValidationError = require_ErrorMessage.formatValidationError;
|
|
6575
6931
|
exports.getAccessibilityProps = getAccessibilityProps;
|
|
6576
6932
|
exports.getDescribedById = getDescribedById;
|
|
@@ -6584,6 +6940,7 @@ exports.handleToggleKeys = handleToggleKeys;
|
|
|
6584
6940
|
exports.handleValidationError = require_ErrorMessage.handleValidationError;
|
|
6585
6941
|
exports.hasFieldError = require_ErrorMessage.hasFieldError;
|
|
6586
6942
|
exports.isDuplicateMapKey = require_ErrorMessage.isDuplicateMapKey;
|
|
6943
|
+
exports.resolveAddressListFieldLabels = resolveAddressListFieldLabels;
|
|
6587
6944
|
exports.useAddressLabel = useAddressLabel;
|
|
6588
6945
|
exports.useAddressSuggestions = useAddressSuggestions;
|
|
6589
6946
|
exports.useDuplicateKeyIndexes = useDuplicateKeyIndexes;
|