|
| 1 | +# BuyActionHelperText - Disabled Reason Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The `BuyActionHelperText` component now supports displaying a disabled reason message when an action cannot be performed. This feature was added in issue-67 to provide users with clear feedback about why a market action is unavailable. |
| 6 | + |
| 7 | +## Usage |
| 8 | + |
| 9 | +### Basic Example |
| 10 | + |
| 11 | +```tsx |
| 12 | +import BuyActionHelperText from '@/components/common/BuyActionHelperText'; |
| 13 | + |
| 14 | +<BuyActionHelperText |
| 15 | + state="idle" |
| 16 | + disabledReason="Insufficient balance to complete this purchase" |
| 17 | + className="mt-4" |
| 18 | +/> |
| 19 | +``` |
| 20 | + |
| 21 | +### With CreatorCard |
| 22 | + |
| 23 | +```tsx |
| 24 | +const CreatorCard: React.FC<CreatorCardProps> = ({ creator, className }) => { |
| 25 | + const { isConnected } = useAccount(); |
| 26 | + const [transactionState, setTransactionState] = useState< |
| 27 | + 'idle' | 'submitting' | 'failed' | 'success' |
| 28 | + >('idle'); |
| 29 | + |
| 30 | + // Example: Check if user has sufficient balance |
| 31 | + const hasInsufficientBalance = true; // Replace with actual balance check |
| 32 | + const disabledReason = hasInsufficientBalance |
| 33 | + ? 'Insufficient balance to complete this purchase' |
| 34 | + : undefined; |
| 35 | + |
| 36 | + return ( |
| 37 | + <div> |
| 38 | + {/* ... other card content ... */} |
| 39 | + |
| 40 | + <BuyActionHelperText |
| 41 | + state={transactionState} |
| 42 | + disabledReason={disabledReason} |
| 43 | + className="mt-4" |
| 44 | + /> |
| 45 | + </div> |
| 46 | + ); |
| 47 | +}; |
| 48 | +``` |
| 49 | + |
| 50 | +## Props |
| 51 | + |
| 52 | +| Prop | Type | Required | Description | |
| 53 | +|------|------|----------|-------------| |
| 54 | +| `state` | `'idle' \| 'submitting' \| 'failed' \| 'success'` | Yes | Current transaction state | |
| 55 | +| `className` | `string` | No | Additional CSS classes | |
| 56 | +| `disabledReason` | `string` | No | Reason message when action is disabled | |
| 57 | + |
| 58 | +## Behavior |
| 59 | + |
| 60 | +- **When `disabledReason` is provided**: Displays the reason text below the main state message with subtle styling (`text-white/40`) |
| 61 | +- **When `disabledReason` is empty/null/undefined**: Nothing renders (no empty space or placeholder) |
| 62 | +- **Animation**: Smooth fade-in/fade-out with height animation when reason appears/disappears |
| 63 | + |
| 64 | +## Styling |
| 65 | + |
| 66 | +The disabled reason text uses: |
| 67 | +- Font size: `0.72rem` (matching the main message) |
| 68 | +- Color: `text-white/40` (subtle, non-intrusive) |
| 69 | +- Animation: Framer Motion with opacity and height transitions |
| 70 | +- Layout: Stacked below the main message with `space-y-2` gap |
| 71 | + |
| 72 | +## Examples of Disabled Reasons |
| 73 | + |
| 74 | +```tsx |
| 75 | +// Insufficient balance |
| 76 | +disabledReason="Insufficient balance to complete this purchase" |
| 77 | + |
| 78 | +// Network issue |
| 79 | +disabledReason="Network connection required to proceed" |
| 80 | + |
| 81 | +// Wallet not connected |
| 82 | +disabledReason="Connect your wallet to enable purchases" |
| 83 | + |
| 84 | +// Creator unavailable |
| 85 | +disabledReason="This creator is currently unavailable" |
| 86 | + |
| 87 | +// Maximum keys reached |
| 88 | +disabledReason="Maximum number of keys already purchased" |
| 89 | +``` |
| 90 | + |
| 91 | +## Safe Empty Content Handling |
| 92 | + |
| 93 | +The component safely handles all empty cases: |
| 94 | + |
| 95 | +```tsx |
| 96 | +// All of these will render nothing for the disabled reason |
| 97 | +<BuyActionHelperText state="idle" disabledReason={undefined} /> |
| 98 | +<BuyActionHelperText state="idle" disabledReason={null} /> |
| 99 | +<BuyActionHelperText state="idle" disabledReason="" /> |
| 100 | +<BuyActionHelperText state="idle" disabledReason=" "} /> // whitespace only |
| 101 | +<BuyActionHelperText state="idle" /> // prop not provided |
| 102 | +``` |
| 103 | + |
| 104 | +## Implementation Details |
| 105 | + |
| 106 | +The component uses a conditional check: |
| 107 | + |
| 108 | +```tsx |
| 109 | +const hasDisabledReason = disabledReason && disabledReason.trim().length > 0; |
| 110 | +``` |
| 111 | + |
| 112 | +This ensures: |
| 113 | +- Null/undefined values are handled |
| 114 | +- Empty strings are ignored |
| 115 | +- Whitespace-only strings are ignored |
| 116 | +- No empty elements are rendered |
| 117 | + |
| 118 | +## Accessibility |
| 119 | + |
| 120 | +- The disabled reason text is part of the same container as the main message |
| 121 | +- Screen readers will announce both the state message and disabled reason |
| 122 | +- Color contrast meets WCAG guidelines with `text-white/40` on dark backgrounds |
| 123 | + |
| 124 | +## Complete Example |
| 125 | + |
| 126 | +```tsx |
| 127 | +import { useState } from 'react'; |
| 128 | +import { useAccount } from 'wagmi'; |
| 129 | +import BuyActionHelperText from '@/components/common/BuyActionHelperText'; |
| 130 | +import { Button } from '@/components/ui/button'; |
| 131 | + |
| 132 | +export function BuyActionExample() { |
| 133 | + const { isConnected } = useAccount(); |
| 134 | + const [transactionState, setTransactionState] = useState< |
| 135 | + 'idle' | 'submitting' | 'failed' | 'success' |
| 136 | + >('idle'); |
| 137 | + |
| 138 | + // Example conditions that might disable an action |
| 139 | + const userBalance = 0.5; // ETH |
| 140 | + const requiredAmount = 1.0; // ETH |
| 141 | + const isNetworkAvailable = true; |
| 142 | + const hasReachedLimit = false; |
| 143 | + |
| 144 | + // Determine disabled reason based on conditions |
| 145 | + const getDisabledReason = () => { |
| 146 | + if (!isConnected) { |
| 147 | + return 'Connect your wallet to enable purchases'; |
| 148 | + } |
| 149 | + if (userBalance < requiredAmount) { |
| 150 | + return `Insufficient balance. You need ${requiredAmount - userBalance} ETH more`; |
| 151 | + } |
| 152 | + if (!isNetworkAvailable) { |
| 153 | + return 'Network connection required to proceed'; |
| 154 | + } |
| 155 | + if (hasReachedLimit) { |
| 156 | + return 'Maximum number of keys already purchased'; |
| 157 | + } |
| 158 | + return undefined; // No disabled reason |
| 159 | + }; |
| 160 | + |
| 161 | + const disabledReason = getDisabledReason(); |
| 162 | + const isDisabled = !!disabledReason || transactionState === 'submitting'; |
| 163 | + |
| 164 | + return ( |
| 165 | + <div className="space-y-4"> |
| 166 | + <Button |
| 167 | + onClick={() => setTransactionState('submitting')} |
| 168 | + disabled={isDisabled} |
| 169 | + variant={isConnected ? 'default' : 'outline'} |
| 170 | + > |
| 171 | + Buy Key |
| 172 | + </Button> |
| 173 | + |
| 174 | + <BuyActionHelperText |
| 175 | + state={transactionState} |
| 176 | + disabledReason={disabledReason} |
| 177 | + className="mt-4" |
| 178 | + /> |
| 179 | + </div> |
| 180 | + ); |
| 181 | +} |
| 182 | +``` |
0 commit comments