@openzeppelin/ui-components 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ let sonner = require("sonner");
38
38
  let next_themes = require("next-themes");
39
39
 
40
40
  //#region src/version.ts
41
- const VERSION = "3.1.0";
41
+ const VERSION = "3.3.0";
42
42
 
43
43
  //#endregion
44
44
  //#region src/components/ui/accordion.tsx
@@ -470,32 +470,55 @@ const variantStyles = {
470
470
  icon: "text-red-600",
471
471
  title: "text-red-900",
472
472
  body: "text-red-800"
473
+ },
474
+ neutral: {
475
+ container: "border-border bg-muted/30",
476
+ icon: "text-muted-foreground",
477
+ title: "text-foreground",
478
+ body: "text-muted-foreground"
479
+ }
480
+ };
481
+ const sizeStyles = {
482
+ default: {
483
+ container: "p-4",
484
+ gap: "gap-3",
485
+ title: "mb-2 text-sm font-semibold",
486
+ body: "text-sm whitespace-pre-line",
487
+ dismissIcon: "h-5 w-5"
488
+ },
489
+ compact: {
490
+ container: "px-4 py-3",
491
+ gap: "gap-2",
492
+ title: "mb-1 text-xs font-medium",
493
+ body: "text-xs leading-relaxed whitespace-pre-line",
494
+ dismissIcon: "h-4 w-4"
473
495
  }
474
496
  };
475
497
  /**
476
498
  * Dismissible banner component for notifications and alerts
477
- * Can be used with various variants (info, success, warning, error)
499
+ * Can be used with various variants (info, success, warning, error, neutral)
478
500
  */
479
- const Banner = react.forwardRef(({ className, variant = "info", title, children, dismissible = true, onDismiss, icon }, ref) => {
480
- const styles = variantStyles[variant] || variantStyles.info;
501
+ const Banner = react.forwardRef(({ className, variant = "info", size = "default", title, children, dismissible = true, onDismiss, icon }, ref) => {
502
+ const styles = variantStyles[variant];
503
+ const sizing = sizeStyles[size];
481
504
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
482
505
  ref,
483
506
  role: "alert",
484
- className: (0, _openzeppelin_ui_utils.cn)("rounded-md border p-4", styles.container, className),
507
+ className: (0, _openzeppelin_ui_utils.cn)("rounded-md border", sizing.container, styles.container, className),
485
508
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
486
- className: "flex gap-3",
509
+ className: (0, _openzeppelin_ui_utils.cn)("flex", sizing.gap),
487
510
  children: [
488
511
  icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
489
512
  className: (0, _openzeppelin_ui_utils.cn)("mt-0.5 shrink-0", styles.icon),
490
513
  children: icon
491
514
  }),
492
515
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
493
- className: "flex-1",
516
+ className: "min-w-0 flex-1",
494
517
  children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
495
- className: (0, _openzeppelin_ui_utils.cn)("mb-2 font-semibold text-sm", styles.title),
518
+ className: (0, _openzeppelin_ui_utils.cn)(sizing.title, styles.title),
496
519
  children: title
497
520
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
498
- className: (0, _openzeppelin_ui_utils.cn)("text-sm whitespace-pre-line", styles.body),
521
+ className: (0, _openzeppelin_ui_utils.cn)(sizing.body, styles.body),
499
522
  children
500
523
  })]
501
524
  }),
@@ -504,7 +527,7 @@ const Banner = react.forwardRef(({ className, variant = "info", title, children,
504
527
  onClick: onDismiss,
505
528
  className: (0, _openzeppelin_ui_utils.cn)("self-start shrink-0 transition-colors hover:opacity-70", styles.icon),
506
529
  "aria-label": "Dismiss banner",
507
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "h-5 w-5" })
530
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: sizing.dismissIcon })
508
531
  })
509
532
  ]
510
533
  })
@@ -3057,6 +3080,10 @@ TextAreaField.displayName = "TextAreaField";
3057
3080
  //#region src/components/fields/AddressListField/labels.ts
3058
3081
  /** Default English labels for {@link AddressListField}. */
3059
3082
  const DEFAULT_ADDRESS_LIST_FIELD_LABELS = {
3083
+ addSingleAddress: "Add",
3084
+ invalidSingleAddress: "Enter a valid address",
3085
+ enableBulkEntry: "Bulk paste",
3086
+ enableSingleEntry: "Single entry",
3060
3087
  addAddresses: "Add addresses",
3061
3088
  addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
3062
3089
  invalidPrefix: "Invalid:",
@@ -3109,41 +3136,9 @@ function formatAddressBulkSummary(classification, addedCount, labels) {
3109
3136
  }
3110
3137
 
3111
3138
  //#endregion
3112
- //#region src/components/fields/AddressListField/AddressListField.tsx
3113
- /**
3114
- * Multi-address list field for bulk paste workflows.
3115
- *
3116
- * Architecture flow:
3117
- * 1. App or wizard steps hold the committed address list in local or form state
3118
- * 2. AddressListField renders a draft textarea plus the committed list rows
3119
- * 3. Parsing and classification run via `@openzeppelin/ui-utils` helpers
3120
- * 4. Optional {@link AddressingCapability} validates candidates before add
3121
- * 5. {@link AddressDisplay} renders each committed row with copy and explorer links
3122
- *
3123
- * The component includes:
3124
- * - Delimiter-aware bulk paste (newlines, commas, semicolons)
3125
- * - Live preview of accepted, invalid, duplicate, and already-listed candidates
3126
- * - Optional `maxItems` cap with disabled input at the limit
3127
- * - Removable list rows with accessible remove buttons
3128
- * - Caller-controlled copy for placeholders and format hints
3129
- * - Optional `labels` overrides for built-in button and status strings
3130
- *
3131
- * @example
3132
- * ```tsx
3133
- * const [addresses, setAddresses] = useState<string[]>([]);
3134
- *
3135
- * <AddressListField
3136
- * value={addresses}
3137
- * onChange={setAddresses}
3138
- * placeholder="Paste addresses (one per line or comma-separated)"
3139
- * formatHint="Each entry is validated before it is added."
3140
- * addressing={capabilities}
3141
- * />
3142
- * ```
3143
- */
3144
- function AddressListField({ value, onChange, placeholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, labels: labelOverrides }) {
3145
- const fieldId = (0, react.useId)();
3146
- const labels = (0, react.useMemo)(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
3139
+ //#region src/components/fields/AddressListField/AddressListBulkEntry.tsx
3140
+ /** Bulk paste entry — info callout, textarea, and add action in a compact stack. */
3141
+ function AddressListBulkEntry({ fieldId, value, onAdd, placeholder, formatHint, addressing, maxItems, disabled = false, maxInvalidPreview = 3, labels, modeToggle }) {
3147
3142
  const atLimit = maxItems != null && value.length >= maxItems;
3148
3143
  const inputDisabled = disabled || atLimit;
3149
3144
  const [feedback, setFeedback] = (0, react.useState)(null);
@@ -3162,97 +3157,270 @@ function AddressListField({ value, onChange, placeholder, formatHint, addressing
3162
3157
  maxItems
3163
3158
  ]);
3164
3159
  const previewSummary = (0, react.useMemo)(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
3165
- const inlineHelperText = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
3160
+ const statusMessage = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
3166
3161
  const handleAdd = (0, react.useCallback)(() => {
3167
3162
  if (!classification || classification.accepted.length === 0 || atLimit) return;
3168
- onChange([...value, ...classification.accepted]);
3163
+ onAdd(classification.accepted);
3169
3164
  reset({ input: "" });
3170
3165
  setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
3171
3166
  }, [
3172
3167
  classification,
3173
3168
  atLimit,
3174
- onChange,
3175
- value,
3169
+ onAdd,
3176
3170
  reset,
3177
3171
  labels
3178
3172
  ]);
3179
- const handleRemove = (0, react.useCallback)((index) => {
3180
- onChange(value.filter((_, currentIndex) => currentIndex !== index));
3181
- setFeedback(null);
3182
- }, [onChange, value]);
3183
3173
  const acceptedCount = classification?.accepted.length ?? 0;
3184
3174
  const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
3185
3175
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3186
- className: (0, _openzeppelin_ui_utils.cn)("space-y-3", className),
3176
+ className: "space-y-3",
3187
3177
  children: [
3188
3178
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3189
- className: "space-y-1",
3190
- children: [helperText ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3191
- className: "text-xs text-muted-foreground",
3192
- children: helperText
3193
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3194
- className: "text-xs text-muted-foreground",
3179
+ className: "flex items-start gap-2 rounded-lg border border-border/70 bg-muted/30 px-3 py-2.5",
3180
+ 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", {
3181
+ className: "text-xs leading-relaxed text-muted-foreground",
3195
3182
  children: formatHint
3196
3183
  })]
3197
3184
  }),
3198
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
3199
- id: `address-list-field-${fieldId}`,
3200
- name: "input",
3201
- label: label ?? "",
3202
- placeholder,
3203
- helperText: inlineHelperText,
3204
- control,
3205
- rows: 4,
3206
- validation: { required: false },
3207
- readOnly: inputDisabled
3208
- }),
3209
- classification && classification.invalid.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3210
- className: "text-xs text-destructive",
3211
- children: [
3212
- labels.invalidPrefix,
3213
- " ",
3214
- classification.invalid.slice(0, maxInvalidPreview).join(", "),
3215
- classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
3216
- ]
3217
- }),
3218
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3186
+ className: "relative",
3187
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
3188
+ id: `address-list-bulk-${fieldId}`,
3189
+ name: "input",
3190
+ label: "",
3191
+ placeholder,
3192
+ helperText: statusMessage,
3193
+ control,
3194
+ rows: 4,
3195
+ validation: { required: false },
3196
+ readOnly: inputDisabled
3197
+ })
3198
+ }), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3199
+ className: "flex justify-end pr-2",
3200
+ children: modeToggle
3201
+ }) : null] }),
3202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3203
+ className: "flex flex-wrap items-center justify-between gap-2",
3204
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3205
+ className: "min-w-0 flex-1",
3206
+ children: [classification && classification.invalid.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3207
+ className: "text-xs text-destructive",
3208
+ children: [
3209
+ labels.invalidPrefix,
3210
+ " ",
3211
+ classification.invalid.slice(0, maxInvalidPreview).join(", "),
3212
+ classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
3213
+ ]
3214
+ }) : null, atLimit && maxItems != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3215
+ className: "text-xs text-muted-foreground",
3216
+ children: labels.maxItemsReached(maxItems)
3217
+ }) : null]
3218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
3219
+ type: "button",
3220
+ size: "sm",
3221
+ className: "shrink-0",
3222
+ disabled: inputDisabled || acceptedCount === 0,
3223
+ onClick: () => handleAdd(),
3224
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), addButtonLabel]
3225
+ })]
3226
+ })
3227
+ ]
3228
+ });
3229
+ }
3230
+
3231
+ //#endregion
3232
+ //#region src/components/fields/AddressListField/AddressListEntries.tsx
3233
+ /** Renders committed addresses with remove actions. Shared by single and bulk entry modes. */
3234
+ function AddressListEntries({ value, getExplorerUrl, disabled = false, labels, onRemove }) {
3235
+ if (value.length === 0) return null;
3236
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3237
+ className: "space-y-1",
3238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3239
+ className: "text-xs text-muted-foreground",
3240
+ children: labels.addressesAdded(value.length)
3241
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3242
+ className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
3243
+ children: value.map((address, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3244
+ className: "flex items-center justify-between rounded bg-muted p-2",
3245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressDisplay, {
3246
+ address,
3247
+ variant: "inline",
3248
+ truncateWhenLabeled: true,
3249
+ showCopyButton: true,
3250
+ explorerUrl: getExplorerUrl?.(address) ?? void 0
3251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3252
+ type: "button",
3253
+ variant: "ghost",
3254
+ size: "sm",
3255
+ className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
3256
+ onClick: () => onRemove(index),
3257
+ disabled,
3258
+ "aria-label": labels.removeAddress(index),
3259
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-3.5" })
3260
+ })]
3261
+ }, `${address}-${index}`))
3262
+ })]
3263
+ });
3264
+ }
3265
+
3266
+ //#endregion
3267
+ //#region src/components/fields/AddressListField/AddressListSingleEntry.tsx
3268
+ /** Single-address entry — inline {@link AddressField} with an Add button (address-book aware). */
3269
+ function AddressListSingleEntry({ fieldId, value, onAdd, placeholder, addressing, maxItems, disabled = false, labels, modeToggle }) {
3270
+ const atLimit = maxItems != null && value.length >= maxItems;
3271
+ const inputDisabled = disabled || atLimit;
3272
+ const { control, reset, getValues, handleSubmit } = (0, react_hook_form.useForm)({
3273
+ defaultValues: { draft: "" },
3274
+ mode: "onChange"
3275
+ });
3276
+ const trimmedDraft = ((0, react_hook_form.useWatch)({
3277
+ control,
3278
+ name: "draft"
3279
+ }) ?? "").trim();
3280
+ const validationState = (0, react.useMemo)(() => {
3281
+ if (!trimmedDraft) return {
3282
+ canAdd: false,
3283
+ showInvalid: false
3284
+ };
3285
+ if (value.includes(trimmedDraft)) return {
3286
+ canAdd: false,
3287
+ showInvalid: false
3288
+ };
3289
+ if (atLimit) return {
3290
+ canAdd: false,
3291
+ showInvalid: false
3292
+ };
3293
+ if (addressing && !addressing.isValidAddress(trimmedDraft)) return {
3294
+ canAdd: false,
3295
+ showInvalid: true
3296
+ };
3297
+ return {
3298
+ canAdd: true,
3299
+ showInvalid: false
3300
+ };
3301
+ }, [
3302
+ trimmedDraft,
3303
+ value,
3304
+ atLimit,
3305
+ addressing
3306
+ ]);
3307
+ const submitAdd = (0, react.useCallback)(() => {
3308
+ if (!validationState.canAdd) return;
3309
+ onAdd(getValues("draft").trim());
3310
+ reset({ draft: "" });
3311
+ }, [
3312
+ validationState.canAdd,
3313
+ onAdd,
3314
+ getValues,
3315
+ reset
3316
+ ]);
3317
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3318
+ className: "space-y-1",
3319
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3320
+ className: "flex items-start gap-2",
3321
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3322
+ className: "min-w-0 flex-1",
3323
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressField, {
3324
+ id: `address-list-single-${fieldId}`,
3325
+ name: "draft",
3326
+ label: "",
3327
+ placeholder,
3328
+ helperText: validationState.showInvalid ? labels.invalidSingleAddress : void 0,
3329
+ control,
3330
+ addressing,
3331
+ validation: { required: false },
3332
+ readOnly: inputDisabled
3333
+ }), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3334
+ className: "-mt-px flex justify-end pr-2",
3335
+ children: modeToggle
3336
+ }) : null]
3337
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
3219
3338
  type: "button",
3220
- size: "sm",
3221
- disabled: inputDisabled || acceptedCount === 0,
3222
- onClick: () => handleAdd(),
3223
- children: addButtonLabel
3224
- }),
3225
- atLimit && maxItems != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3226
- className: "text-xs text-muted-foreground",
3227
- children: labels.maxItemsReached(maxItems)
3339
+ className: "h-10 shrink-0",
3340
+ disabled: inputDisabled || !validationState.canAdd,
3341
+ onClick: handleSubmit(submitAdd),
3342
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), labels.addSingleAddress]
3343
+ })]
3344
+ }), atLimit && maxItems != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3345
+ className: "text-xs text-muted-foreground",
3346
+ children: labels.maxItemsReached(maxItems)
3347
+ })]
3348
+ });
3349
+ }
3350
+
3351
+ //#endregion
3352
+ //#region src/components/fields/AddressListField/AddressListField.tsx
3353
+ /**
3354
+ * Multi-address list field with single-entry default and optional bulk paste.
3355
+ */
3356
+ function AddressListField({ value, onChange, placeholder, bulkPlaceholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, defaultEntryMode = "single", allowModeToggle = true, labels: labelOverrides }) {
3357
+ const fieldId = (0, react.useId)();
3358
+ const labels = (0, react.useMemo)(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
3359
+ const [entryMode, setEntryMode] = (0, react.useState)(defaultEntryMode);
3360
+ const resolvedBulkPlaceholder = bulkPlaceholder ?? placeholder;
3361
+ const handleRemove = (0, react.useCallback)((index) => {
3362
+ onChange(value.filter((_, currentIndex) => currentIndex !== index));
3363
+ }, [onChange, value]);
3364
+ const handleAddSingle = (0, react.useCallback)((address) => {
3365
+ onChange([...value, address]);
3366
+ }, [onChange, value]);
3367
+ const handleAddBulk = (0, react.useCallback)((addresses) => {
3368
+ onChange([...value, ...addresses]);
3369
+ }, [onChange, value]);
3370
+ const switchToBulk = (0, react.useCallback)(() => setEntryMode("bulk"), []);
3371
+ const switchToSingle = (0, react.useCallback)(() => setEntryMode("single"), []);
3372
+ 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";
3373
+ const modeToggle = !allowModeToggle ? void 0 : entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3374
+ type: "button",
3375
+ onClick: switchToBulk,
3376
+ className: toggleClassName,
3377
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ClipboardPaste, { className: "size-3" }), labels.enableBulkEntry]
3378
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3379
+ type: "button",
3380
+ onClick: switchToSingle,
3381
+ className: toggleClassName,
3382
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PencilLine, { className: "size-3" }), labels.enableSingleEntry]
3383
+ });
3384
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3385
+ className: (0, _openzeppelin_ui_utils.cn)("space-y-3", className),
3386
+ children: [
3387
+ label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
3388
+ className: "text-sm font-medium leading-none",
3389
+ children: label
3390
+ }) : null,
3391
+ helperText && entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3392
+ className: "text-sm leading-relaxed text-muted-foreground",
3393
+ children: helperText
3394
+ }) : null,
3395
+ entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListSingleEntry, {
3396
+ fieldId,
3397
+ value,
3398
+ onAdd: handleAddSingle,
3399
+ placeholder,
3400
+ addressing,
3401
+ maxItems,
3402
+ disabled,
3403
+ labels,
3404
+ modeToggle
3405
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListBulkEntry, {
3406
+ fieldId,
3407
+ value,
3408
+ onAdd: handleAddBulk,
3409
+ placeholder: resolvedBulkPlaceholder,
3410
+ formatHint,
3411
+ addressing,
3412
+ maxItems,
3413
+ disabled,
3414
+ maxInvalidPreview,
3415
+ labels,
3416
+ modeToggle
3228
3417
  }),
3229
- value.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3230
- className: "space-y-1",
3231
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3232
- className: "text-xs text-muted-foreground",
3233
- children: labels.addressesAdded(value.length)
3234
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3235
- className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
3236
- children: value.map((address, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3237
- className: "flex items-center justify-between rounded bg-muted p-2",
3238
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressDisplay, {
3239
- address,
3240
- variant: "inline",
3241
- truncateWhenLabeled: true,
3242
- showCopyButton: true,
3243
- explorerUrl: getExplorerUrl?.(address) ?? void 0
3244
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3245
- type: "button",
3246
- variant: "ghost",
3247
- size: "sm",
3248
- className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
3249
- onClick: () => handleRemove(index),
3250
- disabled,
3251
- "aria-label": labels.removeAddress(index),
3252
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-3.5" })
3253
- })]
3254
- }, `${address}-${index}`))
3255
- })]
3418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListEntries, {
3419
+ value,
3420
+ getExplorerUrl,
3421
+ disabled,
3422
+ labels,
3423
+ onRemove: handleRemove
3256
3424
  })
3257
3425
  ]
3258
3426
  });