Repository: interledger/web-monetization-extension
Pull request #740 (2024-11-29 09:55:45)

feat(popup/Input): support `trailingAddOn`
<!--
Pull request titles should follow conventional commit format.
https://www.conventionalcommits.org/en/v1.0.0/
-->

## Context

https://github.com/interledger/web-monetization-extension/pull/736#discussion_r1863022736

## Changes proposed in this pull request

- Add `trailingAddOn` prop
- Rename `addOn` to `leadingAddOn`, and remove `addOnPosition`
- Support `readOnly` prop correctly


github-actions[bot]: <!-- __WM_EXTENSION_BUILD_PREVIEWS__ -->
<h2>Extension builds preview</h2>

<table role="table">
<thead>
  <tr>
    <th>Name</th>
    <th>Link</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td align="center">Latest commit</td>
    <td align="center">40444bc340aacd3f5170861df3ab43a73d5ed35f</td>
  </tr>
  <tr>
    <td align="center">Latest job logs</td>
    <td align="center"><a href="https://github.com/interledger/web-monetization-extension/actions/runs/12082619746">Run #12082619746</a></td>
  </tr>
  <tr><td align="center"><img src="https://img.shields.io/badge/success-3fb950?style=for-the-badge&label=Chrome (563.3KB)" alt="Badge" /></td><td align="center"><a href="https://github.com/interledger/web-monetization-extension/suites/31473396363/artifacts/2253151557">Download</a></td></tr><tr><td align="center"><img src="https://img.shields.io/badge/success-3fb950?style=for-the-badge&label=Firefox (563.32KB)" alt="Badge" /></td><td align="center"><a href="https://github.com/interledger/web-monetization-extension/suites/31473396363/artifacts/2253152029">Download</a></td></tr>
</tbody>
</table>

Patch:
diff --git a/src/popup/components/ConnectWalletForm.tsx b/src/popup/components/ConnectWalletForm.tsx
index 9b00510..4aebc51 100644
--- a/src/popup/components/ConnectWalletForm.tsx
+++ b/src/popup/components/ConnectWalletForm.tsx
@@ -256,12 +256,11 @@ export const ConnectWalletForm = ({
         placeholder="https://ilp.interledger-test.dev/johndoe"
         errorMessage={errors.walletAddressUrl?.message}
         defaultValue={walletAddressUrl}
-        addOn={
+        trailingAddOn={
           isValidating.walletAddressUrl ? (
             <LoadingSpinner color="gray" size="md" />
           ) : null
         }
-        addOnPosition="right"
         required={true}
         autoComplete="on"
         spellCheck={false}
diff --git a/src/popup/components/InputAmount.tsx b/src/popup/components/InputAmount.tsx
index 6c721e2..28ce488 100644
--- a/src/popup/components/InputAmount.tsx
+++ b/src/popup/components/InputAmount.tsx
@@ -75,7 +75,7 @@ export const InputAmount = ({
       className={className}
       defaultValue={amount}
       readOnly={readOnly}
-      addOn={<span className="text-weak">{currencySymbol}</span>}
+      leadingAddOn={<span className="text-weak">{currencySymbol}</span>}
       errorMessage={errorHidden ? '' : errorMessage}
       aria-invalid={errorHidden ? !!errorMessage : false}
       required={true}
diff --git a/src/popup/components/Settings/Budget.tsx b/src/popup/components/Settings/Budget.tsx
index 5eaf113..fcb4723 100644
--- a/src/popup/components/Settings/Budget.tsx
+++ b/src/popup/components/Settings/Budget.tsx
@@ -214,7 +214,7 @@ const RemainingBalance = ({
       <Input
         label="Remaining balance"
         className="max-w-56"
-        addOn={
+        leadingAddOn={
           <span className="text-weak">
             {getCurrencySymbol(walletAddress.assetCode)}
           </span>
diff --git a/src/popup/components/ui/Input.tsx b/src/popup/components/ui/Input.tsx
index 2692811..c8ab272 100644
--- a/src/popup/components/ui/Input.tsx
+++ b/src/popup/components/ui/Input.tsx
@@ -34,22 +34,25 @@ export interface InputProps
   errorMessage?: string;
   disabled?: boolean;
   readOnly?: boolean;
-  addOn?: React.ReactNode;
-  addOnPosition?: 'left' | 'right';
+  leadingAddOn?: React.ReactNode;
+  trailingAddOn?: React.ReactNode;
   label?: React.ReactNode;
   description?: React.ReactNode;
+  wrapperClassName?: string;
 }
 
 export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
   {
     type = 'text',
-    addOn,
-    addOnPosition = 'left',
+    leadingAddOn,
+    trailingAddOn,
     label,
     description,
     errorMessage,
     disabled,
+    readOnly,
     className,
+    wrapperClassName,
     id,
     ...props
   },
@@ -62,15 +65,10 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
     <div className="space-y-2">
       {label ? <Label htmlFor={id}>{label}</Label> : null}
       {description ? <p className="px-2 text-xs">{description}</p> : null}
-      <div className="relative">
-        {addOn ? (
-          <div
-            className={cn(
-              'pointer-events-none absolute inset-y-0 flex w-10 items-center justify-center text-sm font-medium',
-              addOnPosition === 'left' ? 'left-0' : 'right-0',
-            )}
-          >
-            {addOn}
+      <div className={cn('relative', wrapperClassName)}>
+        {leadingAddOn ? (
+          <div className="pointer-events-none absolute inset-y-0 left-0 flex w-10 items-center justify-center text-sm font-medium">
+            {leadingAddOn}
           </div>
         ) : null}
         <input
@@ -78,17 +76,24 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
           ref={ref}
           type={type}
           className={cn(
-            inputVariants({ disabled }),
-            addOn && (addOnPosition === 'left' ? 'pl-10' : 'pr-10'),
+            inputVariants({ disabled, readOnly }),
+            leadingAddOn && 'pl-10',
+            trailingAddOn && 'pr-10',
             errorMessage && 'border-error',
             className,
           )}
-          disabled={disabled ?? false}
+          disabled={disabled}
+          readOnly={readOnly}
           aria-disabled={disabled ?? false}
           aria-invalid={!!errorMessage}
           aria-describedby={errorMessage}
           {...props}
         />
+        {trailingAddOn ? (
+          <div className="absolute inset-y-0 right-0 flex w-10 items-center justify-center text-sm font-medium">
+            {trailingAddOn}
+          </div>
+        ) : null}
       </div>
       {errorMessage && (
         <p className="px-2 text-sm text-error">{errorMessage}</p>


Test patch:
diff --git a/src/popup/components/ui/__tests__/Input.tsx b/src/popup/components/ui/__tests__/Input.tsx
index d4f120a..5344c14 100644
--- a/src/popup/components/ui/__tests__/Input.tsx
+++ b/src/popup/components/ui/__tests__/Input.tsx
@@ -30,15 +30,24 @@ describe('Input', () => {
     expect(queryByLabelText('test input')).toHaveClass('border-base');
   });
 
-  it('should have the `pl-10` class when the `addOn` variant is passed', () => {
+  it('should have the `pl-10` class when the `leadingAddOn` variant is passed', () => {
     const { queryByLabelText } = render(
-      <Input aria-label="test input" addOn="$" />,
+      <Input aria-label="test input" leadingAddOn="$" />,
     );
 
     expect(queryByLabelText('test input')).toBeInTheDocument();
     expect(queryByLabelText('test input')).toHaveClass('pl-10');
   });
 
+  it('should have the `pr-10` class when the `trailingAddon` variant is passed', () => {
+    const { queryByLabelText } = render(
+      <Input aria-label="test input" trailingAddOn="$" />,
+    );
+
+    expect(queryByLabelText('test input')).toBeInTheDocument();
+    expect(queryByLabelText('test input')).toHaveClass('pr-10');
+  });
+
   it('should have the `bg-disabled` and `border-transparent` classes when the `disabled` variant is passed', () => {
     const { queryByLabelText } = render(
       <Input aria-label="test input" disabled />,