@blocklet/payment-react 1.18.18 → 1.18.19
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/README.md +23 -13
- package/es/components/livemode.js +2 -2
- package/es/hooks/keyboard.d.ts +44 -0
- package/es/hooks/keyboard.js +86 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/locales/en.js +1 -1
- package/es/payment/form/index.js +11 -0
- package/es/payment/product-donation.js +223 -115
- package/lib/components/livemode.js +2 -2
- package/lib/hooks/keyboard.d.ts +44 -0
- package/lib/hooks/keyboard.js +85 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/locales/en.js +1 -1
- package/lib/payment/form/index.js +11 -0
- package/lib/payment/product-donation.js +148 -40
- package/package.json +6 -6
- package/src/components/livemode.tsx +2 -2
- package/src/hooks/keyboard.ts +159 -0
- package/src/index.ts +1 -0
- package/src/locales/en.tsx +1 -1
- package/src/payment/form/index.tsx +20 -0
- package/src/payment/product-donation.tsx +180 -72
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useCallback, KeyboardEvent, useRef, RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type TabNavigationOptions<T> = {
|
|
4
|
+
/** whether to include custom option as the last item */
|
|
5
|
+
includeCustom?: boolean;
|
|
6
|
+
/** the value or index of the current selected item */
|
|
7
|
+
currentValue?: T | number;
|
|
8
|
+
/** whether the current selected item is custom */
|
|
9
|
+
isCustomSelected?: boolean;
|
|
10
|
+
/** a function to compare values, used to determine the current selected item */
|
|
11
|
+
compareValue?: (item: T, value: any) => boolean;
|
|
12
|
+
/** whether to allow Tab key navigation */
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
/** a selector to find navigable elements */
|
|
15
|
+
selector?: string;
|
|
16
|
+
/** an element container reference, limiting the query DOM range to improve performance */
|
|
17
|
+
containerRef?: RefObject<HTMLElement>;
|
|
18
|
+
/** the type of the current value, can be 'index' or 'value' */
|
|
19
|
+
valueType?: 'index' | 'value';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tab key navigation hook - implement Tab key circular navigation between a set of options
|
|
24
|
+
*
|
|
25
|
+
* @param items an array of options, can be a simple type (string, number) array or an object array
|
|
26
|
+
* @param onSelect callback when an item is selected
|
|
27
|
+
* @param options configuration options
|
|
28
|
+
* @returns an object containing the event handler and control functions
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // simple string array
|
|
32
|
+
* const { handleKeyDown } = useTabNavigation(['10', '20', '50'], handleSelect);
|
|
33
|
+
*
|
|
34
|
+
* // object array
|
|
35
|
+
* const { handleKeyDown } = useTabNavigation(
|
|
36
|
+
* [{id: 1, name: 'A'}, {id: 2, name: 'B'}],
|
|
37
|
+
* handleSelect,
|
|
38
|
+
* { compareValue: (item, value) => item.id === value.id }
|
|
39
|
+
* );
|
|
40
|
+
*/
|
|
41
|
+
export const useTabNavigation = <T>(
|
|
42
|
+
items: T[],
|
|
43
|
+
onSelect: (item: T | 'custom', index: number) => void,
|
|
44
|
+
options?: TabNavigationOptions<T>
|
|
45
|
+
) => {
|
|
46
|
+
const {
|
|
47
|
+
valueType = 'value',
|
|
48
|
+
includeCustom = false,
|
|
49
|
+
currentValue,
|
|
50
|
+
isCustomSelected = false,
|
|
51
|
+
compareValue = (item: T, value: any) => item === value,
|
|
52
|
+
enabled = true,
|
|
53
|
+
selector = '.tab-navigable-card button',
|
|
54
|
+
containerRef,
|
|
55
|
+
} = options || {};
|
|
56
|
+
|
|
57
|
+
const hasTabbed = useRef(false);
|
|
58
|
+
|
|
59
|
+
const findNavigableElements = useCallback(() => {
|
|
60
|
+
if (containerRef?.current) {
|
|
61
|
+
return containerRef.current.querySelectorAll(selector);
|
|
62
|
+
}
|
|
63
|
+
return document.querySelectorAll(selector);
|
|
64
|
+
}, [containerRef, selector]);
|
|
65
|
+
|
|
66
|
+
// get current index
|
|
67
|
+
const determineCurrentIndex = useCallback(() => {
|
|
68
|
+
const allOptions = includeCustom ? [...items, 'custom' as any] : items;
|
|
69
|
+
if (allOptions.length === 0) return -1;
|
|
70
|
+
|
|
71
|
+
// if not tabbed, determine start point by currentValue
|
|
72
|
+
if (!hasTabbed.current) {
|
|
73
|
+
if (isCustomSelected && includeCustom) {
|
|
74
|
+
return items.length; // current selected custom option
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (currentValue !== undefined) {
|
|
78
|
+
if (valueType === 'index' && typeof currentValue === 'number') {
|
|
79
|
+
// if currentValue is index
|
|
80
|
+
return currentValue >= 0 && currentValue < items.length ? currentValue : -1;
|
|
81
|
+
}
|
|
82
|
+
// if currentValue is value, find matched item
|
|
83
|
+
return items.findIndex((item) => compareValue(item, currentValue));
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// if tabbed, find current focused element
|
|
87
|
+
const focusedElement = document.activeElement;
|
|
88
|
+
const navigableElements = findNavigableElements();
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < navigableElements.length; i++) {
|
|
91
|
+
if (navigableElements[i] === focusedElement) {
|
|
92
|
+
return i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return -1;
|
|
98
|
+
}, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);
|
|
99
|
+
|
|
100
|
+
// get next index
|
|
101
|
+
const getNextIndex = useCallback(
|
|
102
|
+
(currentIndex: number, isShiftKey: boolean) => {
|
|
103
|
+
const totalOptions = includeCustom ? items.length + 1 : items.length;
|
|
104
|
+
|
|
105
|
+
if (currentIndex === -1) {
|
|
106
|
+
return 0; // no current selected item, start from first item
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isShiftKey) {
|
|
110
|
+
// Shift+Tab forward
|
|
111
|
+
return currentIndex === 0 ? totalOptions - 1 : currentIndex - 1;
|
|
112
|
+
}
|
|
113
|
+
// Tab backward
|
|
114
|
+
return currentIndex === totalOptions - 1 ? 0 : currentIndex + 1;
|
|
115
|
+
},
|
|
116
|
+
[items, includeCustom]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const handleKeyDown = useCallback(
|
|
120
|
+
(e: KeyboardEvent) => {
|
|
121
|
+
// if navigation is disabled or not Tab key, do not handle event
|
|
122
|
+
if (!enabled || e.key !== 'Tab') return;
|
|
123
|
+
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
e.stopPropagation();
|
|
126
|
+
|
|
127
|
+
// determine current index and next index
|
|
128
|
+
const currentIndex = determineCurrentIndex();
|
|
129
|
+
const nextIndex = getNextIndex(currentIndex, e.shiftKey);
|
|
130
|
+
|
|
131
|
+
// mark as handled Tab event
|
|
132
|
+
hasTabbed.current = true;
|
|
133
|
+
|
|
134
|
+
// execute select callback
|
|
135
|
+
const selectedItem = nextIndex === items.length ? 'custom' : items[nextIndex];
|
|
136
|
+
onSelect(selectedItem, nextIndex);
|
|
137
|
+
|
|
138
|
+
// focus to next element
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
const elements = findNavigableElements();
|
|
141
|
+
if (elements[nextIndex]) {
|
|
142
|
+
(elements[nextIndex] as HTMLElement).focus();
|
|
143
|
+
}
|
|
144
|
+
}, 0);
|
|
145
|
+
},
|
|
146
|
+
[items, onSelect, enabled, determineCurrentIndex, getNextIndex, findNavigableElements]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// reset Tab state method
|
|
150
|
+
const resetTabNavigation = useCallback(() => {
|
|
151
|
+
hasTabbed.current = false;
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
handleKeyDown,
|
|
156
|
+
resetTabNavigation,
|
|
157
|
+
isTabNavigationActive: hasTabbed.current,
|
|
158
|
+
};
|
|
159
|
+
};
|
package/src/index.ts
CHANGED
package/src/locales/en.tsx
CHANGED
|
@@ -164,7 +164,7 @@ export default flat({
|
|
|
164
164
|
payment: 'Thanks for your purchase',
|
|
165
165
|
subscription: 'Thanks for your subscribing',
|
|
166
166
|
setup: 'Thanks for your subscribing',
|
|
167
|
-
donate: 'Thanks for your
|
|
167
|
+
donate: 'Thanks for your tip',
|
|
168
168
|
tip: 'A payment to {payee} has been completed. You can view the details of this payment in your account.',
|
|
169
169
|
},
|
|
170
170
|
confirm: 'Confirming allows {payee} to charge or reduce your staking. You can cancel or revoke staking anytime.',
|
|
@@ -429,6 +429,26 @@ export default function PaymentForm({
|
|
|
429
429
|
setState({ stripePaying: false });
|
|
430
430
|
};
|
|
431
431
|
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
434
|
+
if (
|
|
435
|
+
e.key === 'Enter' &&
|
|
436
|
+
!state.submitting &&
|
|
437
|
+
!state.paying &&
|
|
438
|
+
!state.stripePaying &&
|
|
439
|
+
quantityInventoryStatus &&
|
|
440
|
+
payable
|
|
441
|
+
) {
|
|
442
|
+
onAction();
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
447
|
+
return () => {
|
|
448
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
449
|
+
};
|
|
450
|
+
}, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
451
|
+
|
|
432
452
|
if (onlyShowBtn) {
|
|
433
453
|
return (
|
|
434
454
|
<>
|
|
@@ -7,6 +7,15 @@ import { useEffect, useRef } from 'react';
|
|
|
7
7
|
import { formatAmountPrecisionLimit } from '../libs/util';
|
|
8
8
|
import { usePaymentContext } from '../contexts/payment';
|
|
9
9
|
import { usePreventWheel } from '../hooks/scroll';
|
|
10
|
+
import { useTabNavigation } from '../hooks/keyboard';
|
|
11
|
+
|
|
12
|
+
// LocalStorage key base for preset selection
|
|
13
|
+
const DONATION_PRESET_KEY_BASE = 'payment-donation-preset';
|
|
14
|
+
const DONATION_CUSTOM_AMOUNT_KEY_BASE = 'payment-donation-custom-amount';
|
|
15
|
+
|
|
16
|
+
const formatAmount = (amount: string | number): string => {
|
|
17
|
+
return String(amount);
|
|
18
|
+
};
|
|
10
19
|
|
|
11
20
|
export default function ProductDonation({
|
|
12
21
|
item,
|
|
@@ -20,32 +29,129 @@ export default function ProductDonation({
|
|
|
20
29
|
currency: TPaymentCurrency;
|
|
21
30
|
}) {
|
|
22
31
|
const { t, locale } = useLocaleContext();
|
|
23
|
-
const { setPayable } = usePaymentContext();
|
|
32
|
+
const { setPayable, session } = usePaymentContext();
|
|
24
33
|
usePreventWheel();
|
|
25
|
-
const presets = settings?.amount?.presets || [];
|
|
26
|
-
|
|
34
|
+
const presets = (settings?.amount?.presets || []).map(formatAmount);
|
|
35
|
+
|
|
36
|
+
const getUserStorageKey = (base: string) => {
|
|
37
|
+
const userDid = session?.user?.did;
|
|
38
|
+
return userDid ? `${base}:${userDid}` : base;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getSavedCustomAmount = () => {
|
|
42
|
+
try {
|
|
43
|
+
return localStorage.getItem(getUserStorageKey(DONATION_CUSTOM_AMOUNT_KEY_BASE)) || '';
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn('Failed to access localStorage', e);
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getDefaultPreset = () => {
|
|
51
|
+
if (settings?.amount?.preset) {
|
|
52
|
+
return formatAmount(settings.amount.preset);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const savedPreset = localStorage.getItem(getUserStorageKey(DONATION_PRESET_KEY_BASE));
|
|
57
|
+
if (savedPreset) {
|
|
58
|
+
if (presets.includes(formatAmount(savedPreset))) {
|
|
59
|
+
return formatAmount(savedPreset);
|
|
60
|
+
}
|
|
61
|
+
if (savedPreset === 'custom' && supportCustom) {
|
|
62
|
+
return 'custom';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.warn('Failed to access localStorage', e);
|
|
67
|
+
}
|
|
68
|
+
if (presets.length > 0) {
|
|
69
|
+
const middleIndex = Math.floor(presets.length / 2);
|
|
70
|
+
return presets[middleIndex] || presets[0];
|
|
71
|
+
}
|
|
72
|
+
return '0';
|
|
73
|
+
};
|
|
74
|
+
|
|
27
75
|
const supportPreset = presets.length > 0;
|
|
28
76
|
const supportCustom = !!settings?.amount?.custom;
|
|
77
|
+
const defaultPreset = getDefaultPreset();
|
|
78
|
+
const defaultCustomAmount = defaultPreset === 'custom' ? getSavedCustomAmount() : '';
|
|
79
|
+
|
|
29
80
|
const [state, setState] = useSetState({
|
|
30
|
-
selected:
|
|
31
|
-
input:
|
|
32
|
-
custom: !supportPreset,
|
|
81
|
+
selected: defaultPreset === 'custom' ? '' : defaultPreset,
|
|
82
|
+
input: defaultCustomAmount,
|
|
83
|
+
custom: !supportPreset || defaultPreset === 'custom',
|
|
33
84
|
error: '',
|
|
34
85
|
});
|
|
86
|
+
|
|
35
87
|
const customInputRef = useRef<HTMLInputElement>(null);
|
|
88
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
89
|
+
|
|
90
|
+
const handleSelect = (amount: string) => {
|
|
91
|
+
setPayable(true);
|
|
92
|
+
setState({ selected: formatAmount(amount), custom: false, error: '' });
|
|
93
|
+
onChange({ priceId: item.price_id, amount: formatAmount(amount) });
|
|
94
|
+
localStorage.setItem(getUserStorageKey(DONATION_PRESET_KEY_BASE), formatAmount(amount));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleCustomSelect = () => {
|
|
98
|
+
setState({ custom: true, selected: '', error: '' });
|
|
99
|
+
const savedCustomAmount = getSavedCustomAmount();
|
|
100
|
+
if (savedCustomAmount) {
|
|
101
|
+
setState({ input: savedCustomAmount });
|
|
102
|
+
onChange({ priceId: item.price_id, amount: savedCustomAmount });
|
|
103
|
+
setPayable(true);
|
|
104
|
+
} else if (!state.input) {
|
|
105
|
+
setPayable(false);
|
|
106
|
+
}
|
|
107
|
+
localStorage.setItem(getUserStorageKey(DONATION_PRESET_KEY_BASE), 'custom');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleTabSelect = (selectedItem: string | 'custom') => {
|
|
111
|
+
if (selectedItem === 'custom') {
|
|
112
|
+
handleCustomSelect();
|
|
113
|
+
} else {
|
|
114
|
+
handleSelect(selectedItem as string);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// 使用useTabNavigation进行键盘导航
|
|
119
|
+
const { handleKeyDown } = useTabNavigation(presets, handleTabSelect, {
|
|
120
|
+
includeCustom: supportCustom,
|
|
121
|
+
currentValue: state.custom ? undefined : state.selected,
|
|
122
|
+
isCustomSelected: state.custom,
|
|
123
|
+
enabled: true,
|
|
124
|
+
selector: '.tab-navigable-card button',
|
|
125
|
+
containerRef,
|
|
126
|
+
});
|
|
36
127
|
|
|
37
|
-
// Set default amount
|
|
38
128
|
useEffect(() => {
|
|
39
129
|
if (settings.amount.preset) {
|
|
40
130
|
setState({ selected: settings.amount.preset, custom: false });
|
|
41
131
|
onChange({ priceId: item.price_id, amount: settings.amount.preset });
|
|
42
132
|
} else if (settings.amount.presets && settings.amount.presets.length > 0) {
|
|
43
|
-
|
|
44
|
-
|
|
133
|
+
const isCustom = defaultPreset === 'custom';
|
|
134
|
+
setState({
|
|
135
|
+
selected: isCustom ? '' : defaultPreset,
|
|
136
|
+
custom: isCustom,
|
|
137
|
+
input: isCustom ? getSavedCustomAmount() : '',
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!isCustom) {
|
|
141
|
+
onChange({ priceId: item.price_id, amount: defaultPreset });
|
|
142
|
+
} else if (defaultCustomAmount) {
|
|
143
|
+
onChange({ priceId: item.price_id, amount: defaultCustomAmount });
|
|
144
|
+
setPayable(true);
|
|
145
|
+
} else {
|
|
146
|
+
setPayable(false);
|
|
147
|
+
}
|
|
45
148
|
}
|
|
46
149
|
}, [settings.amount.preset, settings.amount.presets]); // eslint-disable-line
|
|
47
150
|
|
|
48
151
|
useEffect(() => {
|
|
152
|
+
if (containerRef.current) {
|
|
153
|
+
containerRef.current.focus();
|
|
154
|
+
}
|
|
49
155
|
if (state.custom) {
|
|
50
156
|
setTimeout(() => {
|
|
51
157
|
customInputRef.current?.focus();
|
|
@@ -53,12 +159,6 @@ export default function ProductDonation({
|
|
|
53
159
|
}
|
|
54
160
|
}, [state.custom]);
|
|
55
161
|
|
|
56
|
-
const handleSelect = (amount: string) => {
|
|
57
|
-
setPayable(true);
|
|
58
|
-
setState({ selected: amount, custom: false, error: '' });
|
|
59
|
-
onChange({ priceId: item.price_id, amount });
|
|
60
|
-
};
|
|
61
|
-
|
|
62
162
|
const handleInput = (event: any) => {
|
|
63
163
|
const { value } = event.target;
|
|
64
164
|
const min = parseFloat(settings.amount.minimum || '0');
|
|
@@ -78,69 +178,75 @@ export default function ProductDonation({
|
|
|
78
178
|
}
|
|
79
179
|
setPayable(true);
|
|
80
180
|
setState({ error: '', input: value });
|
|
81
|
-
onChange({ priceId: item.price_id, amount: value });
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const handleCustomSelect = () => {
|
|
85
|
-
setState({ custom: true, error: '' });
|
|
86
|
-
if (!state.input) {
|
|
87
|
-
setPayable(false);
|
|
88
|
-
}
|
|
181
|
+
onChange({ priceId: item.price_id, amount: formatAmount(value) });
|
|
182
|
+
localStorage.setItem(getUserStorageKey(DONATION_CUSTOM_AMOUNT_KEY_BASE), formatAmount(value));
|
|
89
183
|
};
|
|
90
184
|
|
|
91
185
|
return (
|
|
92
|
-
<Box
|
|
186
|
+
<Box
|
|
187
|
+
ref={containerRef}
|
|
188
|
+
display="flex"
|
|
189
|
+
flexDirection="column"
|
|
190
|
+
alignItems="flex-start"
|
|
191
|
+
gap={1.5}
|
|
192
|
+
onKeyDown={handleKeyDown}
|
|
193
|
+
tabIndex={0}
|
|
194
|
+
sx={{ outline: 'none' }}>
|
|
93
195
|
{supportPreset && (
|
|
94
196
|
<Grid container spacing={2}>
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
197
|
+
{presets.map((amount) => (
|
|
198
|
+
<Grid item xs={6} sm={3} key={amount}>
|
|
199
|
+
<Card
|
|
200
|
+
variant="outlined"
|
|
201
|
+
className="tab-navigable-card"
|
|
202
|
+
sx={{
|
|
203
|
+
minWidth: 115,
|
|
204
|
+
textAlign: 'center',
|
|
205
|
+
transition: 'all 0.3s',
|
|
206
|
+
cursor: 'pointer',
|
|
207
|
+
'&:hover': {
|
|
208
|
+
transform: 'translateY(-4px)',
|
|
209
|
+
boxShadow: 3,
|
|
210
|
+
},
|
|
211
|
+
'.MuiCardActionArea-focusHighlight': {
|
|
212
|
+
backgroundColor: 'transparent',
|
|
213
|
+
},
|
|
214
|
+
height: '42px',
|
|
215
|
+
...(formatAmount(state.selected) === formatAmount(amount) && !state.custom
|
|
216
|
+
? { borderColor: 'primary.main', borderWidth: 1 }
|
|
217
|
+
: {}),
|
|
218
|
+
}}>
|
|
219
|
+
<CardActionArea
|
|
220
|
+
onClick={() => handleSelect(amount)}
|
|
221
|
+
tabIndex={0}
|
|
222
|
+
aria-selected={formatAmount(state.selected) === formatAmount(amount) && !state.custom}>
|
|
223
|
+
<Stack
|
|
224
|
+
direction="row"
|
|
225
|
+
sx={{ py: 1.5, px: 1.5 }}
|
|
226
|
+
spacing={0.5}
|
|
227
|
+
alignItems="center"
|
|
228
|
+
justifyContent="center">
|
|
229
|
+
<Avatar src={currency?.logo} sx={{ width: 16, height: 16, mr: 0.5 }} alt={currency?.symbol} />
|
|
230
|
+
<Typography
|
|
231
|
+
component="strong"
|
|
232
|
+
lineHeight={1}
|
|
233
|
+
variant="h3"
|
|
234
|
+
sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 400 }}>
|
|
235
|
+
{amount}
|
|
236
|
+
</Typography>
|
|
237
|
+
<Typography lineHeight={1} fontSize={14} color="text.secondary">
|
|
238
|
+
{currency?.symbol}
|
|
239
|
+
</Typography>
|
|
240
|
+
</Stack>
|
|
241
|
+
</CardActionArea>
|
|
242
|
+
</Card>
|
|
243
|
+
</Grid>
|
|
244
|
+
))}
|
|
139
245
|
{supportCustom && (
|
|
140
246
|
<Grid item xs={6} sm={3} key="custom">
|
|
141
247
|
<Card
|
|
142
|
-
key="custom"
|
|
143
248
|
variant="outlined"
|
|
249
|
+
className="tab-navigable-card"
|
|
144
250
|
sx={{
|
|
145
251
|
textAlign: 'center',
|
|
146
252
|
transition: 'all 0.3s',
|
|
@@ -149,10 +255,13 @@ export default function ProductDonation({
|
|
|
149
255
|
transform: 'translateY(-4px)',
|
|
150
256
|
boxShadow: 3,
|
|
151
257
|
},
|
|
258
|
+
'.MuiCardActionArea-focusHighlight': {
|
|
259
|
+
backgroundColor: 'transparent',
|
|
260
|
+
},
|
|
152
261
|
height: '42px',
|
|
153
262
|
...(state.custom ? { borderColor: 'primary.main', borderWidth: 1 } : {}),
|
|
154
263
|
}}>
|
|
155
|
-
<CardActionArea onClick={
|
|
264
|
+
<CardActionArea onClick={handleCustomSelect} tabIndex={0} aria-selected={state.custom}>
|
|
156
265
|
<Stack
|
|
157
266
|
direction="row"
|
|
158
267
|
sx={{ py: 1.5, px: 1.5 }}
|
|
@@ -179,7 +288,6 @@ export default function ProductDonation({
|
|
|
179
288
|
error={!!state.error}
|
|
180
289
|
helperText={state.error}
|
|
181
290
|
inputRef={customInputRef}
|
|
182
|
-
// eslint-disable-next-line react/jsx-no-duplicate-props
|
|
183
291
|
InputProps={{
|
|
184
292
|
endAdornment: (
|
|
185
293
|
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ ml: 1 }}>
|
|
@@ -190,7 +298,7 @@ export default function ProductDonation({
|
|
|
190
298
|
autoComplete: 'off',
|
|
191
299
|
}}
|
|
192
300
|
sx={{
|
|
193
|
-
mt:
|
|
301
|
+
mt: defaultPreset !== '0' ? 0 : 1,
|
|
194
302
|
}}
|
|
195
303
|
/>
|
|
196
304
|
)}
|