Skip to content

Commit

Permalink
Make FilterButton a controlled component (#6993)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickyrombo authored Jan 17, 2024
1 parent 1eb3aa1 commit fa7a9d9
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ const {
export const usePurchaseContentFormConfiguration = ({
track,
price,
presetValues
presetValues,
purchaseVendor
}: {
track?: Nullable<UserTrackMetadata>
price: number
presetValues: PayExtraAmountPresetValues
purchaseVendor?: PurchaseVendor
}) => {
const dispatch = useDispatch()
const stage = useSelector(getPurchaseContentFlowStage)
Expand All @@ -59,7 +61,7 @@ export const usePurchaseContentFormConfiguration = ({
balance >= BigInt(price * CENTS_TO_USDC_MULTIPLIER)
? PurchaseMethod.BALANCE
: PurchaseMethod.CARD,
[PURCHASE_VENDOR]: PurchaseVendor.STRIPE
[PURCHASE_VENDOR]: purchaseVendor ?? PurchaseVendor.STRIPE
}

const onSubmit = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const meta: Meta<typeof FilterButton> = {
component: FilterButton,
args: {
options: [
{ label: 'Red Rover' },
{ label: 'Green Goblin' },
{ label: 'Blue Man Group' }
{ value: 'Red Rover' },
{ value: 'Green Goblin' },
{ value: 'Blue Man Group' }
],
label: 'Choice',
popupAnchorOrigin: { horizontal: 'center', vertical: 'bottom' },
Expand All @@ -28,8 +28,8 @@ const meta: Meta<typeof FilterButton> = {
label: {
control: { type: 'text' }
},
initialSelectionIndex: {
control: { type: 'number' }
selection: {
control: { type: 'text' }
}
}
}
Expand All @@ -46,9 +46,9 @@ export const Primary: Story = {
<FilterButton
label='Choice'
options={[
{ label: 'Red Rover' },
{ label: 'Green Goblin' },
{ label: 'Blue Man Group' }
{ value: 'Red Rover' },
{ value: 'Green Goblin' },
{ value: 'Blue Man Group' }
]}
/>
</Flex>
Expand All @@ -63,9 +63,9 @@ export const FillContainer: Story = {
<FilterButton
label='Choice'
options={[
{ label: 'Red Rover' },
{ label: 'Green Goblin' },
{ label: 'Blue Man Group' }
{ value: 'Red Rover' },
{ value: 'Green Goblin' },
{ value: 'Blue Man Group' }
]}
/>
</Flex>
Expand All @@ -81,9 +81,9 @@ export const ReplaceLabel: Story = {
variant='replaceLabel'
label='Choice'
options={[
{ label: 'Red Leader' },
{ label: 'Green Juice' },
{ label: 'Blue Moon' }
{ value: 'Red Leader' },
{ value: 'Green Juice' },
{ value: 'Blue Moon' }
]}
/>
</Flex>
Expand All @@ -98,9 +98,9 @@ export const CustomIcon: Story = {
<FilterButton
iconRight={IconFilter}
options={[
{ label: 'Radar Option', icon: IconRadar },
{ label: 'Or A CD?', icon: IconAlbum },
{ label: "Ooh! We're Cookin Now!", icon: IconCampfire }
{ value: 'Radar Option', icon: IconRadar },
{ value: 'Or A CD?', icon: IconAlbum },
{ value: "Ooh! We're Cookin Now!", icon: IconCampfire }
]}
/>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import {
forwardRef,
RefObject,
useRef,
useState,
useCallback,
useEffect
} from 'react'
import { forwardRef, RefObject, useRef, useState, useCallback } from 'react'

import { CSSObject, useTheme } from '@emotion/react'

import { BaseButton } from 'components/button/BaseButton/BaseButton'
import { Box, Flex, Paper, Popup } from 'components/layout'
import { useControlled } from 'hooks/useControlled'
import { IconCaretDown, IconCloseAlt } from 'icons'

import { FilterButtonOption, FilterButtonProps } from './types'

export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
function FilterButton(props, ref) {
const {
initialSelectionIndex,
selection: selectionProp,
label,
options,
onSelect,
Expand All @@ -31,17 +25,14 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
popupZIndex
} = props
const { color, cornerRadius, spacing, typography } = useTheme()
const [selection, setSelection] = useState<FilterButtonOption | null>(
initialSelectionIndex !== undefined
? options[initialSelectionIndex]
: null
)

useEffect(() => {
if (onSelect && selection?.label) {
onSelect(selection.label)
}
}, [selection?.label, onSelect])
const [selection, setSelection] = useControlled({
controlledProp: selectionProp,
defaultValue: null,
stateName: 'selection',
componentName: 'FilterButton'
})
const selectedOption = options.find((option) => option.value === selection)
const selectedLabel = selectedOption?.label ?? selectedOption?.value

const [isOpen, setIsOpen] = useState(false)

Expand Down Expand Up @@ -156,9 +147,13 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
}
}, [selection, variant, setIsOpen, setSelection])

const handleOptionSelect = useCallback((option: FilterButtonOption) => {
setSelection(option)
}, [])
const handleOptionSelect = useCallback(
(option: FilterButtonOption) => {
setSelection(option.value)
onSelect?.(option.value)
},
[onSelect, setSelection]
)

const anchorRef = useRef<HTMLButtonElement>(null)

Expand All @@ -178,7 +173,7 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
aria-haspopup='listbox'
aria-expanded={isOpen}
>
{selection?.label ?? label}
{selectedLabel ?? label}
<Popup
anchorRef={(ref as RefObject<HTMLElement>) || anchorRef}
isVisible={isOpen}
Expand All @@ -195,22 +190,22 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
alignItems='flex-start'
justifyContent='center'
role='listbox'
aria-label={selection?.label ?? label ?? props['aria-label']}
aria-activedescendant={selection?.label}
aria-label={selectedLabel ?? label ?? props['aria-label']}
aria-activedescendant={selectedLabel}
>
{options.map((option) => (
<BaseButton
key={option.label}
key={option.value}
iconLeft={option.icon}
styles={{
button: optionCss,
icon: optionIconCss
}}
onClick={() => handleOptionSelect(option)}
aria-label={option.label}
aria-label={option.label ?? option.value}
role='option'
>
{option.label}
{option.label ?? option.value}
</BaseButton>
))}
</Flex>
Expand Down
10 changes: 7 additions & 3 deletions packages/harmony/src/components/button/FilterButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ export type FilterButtonSize = 'default' | 'small'
export type FilterButtonVariant = 'fillContainer' | 'replaceLabel'

export type FilterButtonOption = {
label: string
value: string
/**
* The label to display. If not provided, uses the value.
*/
label?: string
icon?: IconComponent
}

Expand All @@ -30,9 +34,9 @@ export type FilterButtonProps = {
'aria-label'?: string

/**
* An initial section (from the provided options)
* The selected value
*/
initialSelectionIndex?: number
selection?: string

/**
* The button size
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/components/add-funds/AddFunds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const AddFunds = ({
</Flex>
</Box>
<PaymentMethod
selectedVendor={selectedPurchaseVendor ?? null}
selectedMethod={selectedPurchaseMethod}
setSelectedMethod={setSelectedPurchaseMethod}
setSelectedVendor={setSelectedPurchaseVendor}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,34 @@ import { Box, Flex, IconCaretDown, Text } from '@audius/harmony'
import ActionDrawer from 'components/action-drawer/ActionDrawer'

type MobileFilterButtonTypes = {
options: { label: string }[]
options: { value: string; label?: string }[]
onClose?: () => void
onSelect?: (label: string) => void
initialSelectionIndex?: number
onSelect?: (value: string) => void
selection?: string
zIndex?: number
}

export const MobileFilterButton = ({
options,
onClose,
onSelect,
initialSelectionIndex,
selection,
zIndex
}: MobileFilterButtonTypes) => {
const [isOpen, setIsOpen] = useState(false)
const [selection, setSelection] = useState(
initialSelectionIndex !== undefined ? options[initialSelectionIndex] : null
)
const selectedOption = options.find((option) => option.value === selection)
const selectedLabel = selectedOption?.label ?? selectedOption?.value ?? ''
useEffect(() => {
if (selection && onSelect) {
onSelect(selection.label)
onSelect(selection)
}
}, [selection, onSelect])

const actions = options.map((option) => ({
text: option.label,
text: option.label ?? option.value,
onClick: () => {
setIsOpen(false)
setSelection(option)
onSelect?.(option.value)
}
}))
return (
Expand All @@ -52,7 +51,7 @@ export const MobileFilterButton = ({
onClick={() => setIsOpen((open) => !open)}
>
<Text variant='title' strength='weak' size='s'>
{selection?.label}
{selectedLabel}
</Text>
<IconCaretDown size='s' color='default' />
</Flex>
Expand Down
10 changes: 6 additions & 4 deletions packages/web/src/components/payment-method/PaymentMethod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const messages = {
type PaymentMethodProps = {
selectedMethod: Nullable<PurchaseMethod>
setSelectedMethod: (method: PurchaseMethod) => void
selectedVendor: Nullable<PurchaseVendor>
setSelectedVendor: (vendor: PurchaseVendor) => void
balance?: Nullable<BNUSDC>
isExistingBalanceDisabled?: boolean
Expand All @@ -44,6 +45,7 @@ type PaymentMethodProps = {
export const PaymentMethod = ({
selectedMethod,
setSelectedMethod,
selectedVendor,
setSelectedVendor,
balance,
isExistingBalanceDisabled,
Expand All @@ -56,8 +58,8 @@ export const PaymentMethod = ({
)
const balanceFormatted = formatCurrencyBalance(balanceCents / 100)
const vendorOptions = [
...(isCoinflowEnabled ? [{ label: PurchaseVendor.COINFLOW }] : []),
{ label: PurchaseVendor.STRIPE }
...(isCoinflowEnabled ? [{ value: PurchaseVendor.COINFLOW }] : []),
{ value: PurchaseVendor.STRIPE }
]

const handleSelectVendor = useCallback(
Expand Down Expand Up @@ -98,14 +100,14 @@ export const PaymentMethod = ({
mobile ? (
<MobileFilterButton
onSelect={handleSelectVendor}
initialSelectionIndex={0}
selection={selectedVendor?.toString()}
options={vendorOptions}
zIndex={zIndex.ADD_FUNDS_VENDOR_SELECTION_DRAWER}
/>
) : (
<FilterButton
onSelect={handleSelectVendor}
initialSelectionIndex={0}
selection={selectedVendor?.toString()}
variant='replaceLabel'
options={vendorOptions}
popupZIndex={zIndex.USDC_ADD_FUNDS_FILTER_BUTTON_POPUP}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import {
purchaseContentSelectors,
isContentPurchaseInProgress,
usePayExtraPresets,
PurchaseContentPage
PurchaseContentPage,
useFeatureFlag,
FeatureFlags,
PurchaseVendor
} from '@audius/common'
import { USDC } from '@audius/fixed-decimal'
import { Flex } from '@audius/harmony'
Expand Down Expand Up @@ -174,6 +177,8 @@ export const PremiumContentPurchaseModal = () => {
onClosed,
data: { contentId: trackId }
} = usePremiumContentPurchaseModal()
const { isEnabled: isCoinflowEnabled, isLoaded: isCoinflowEnabledLoaded } =
useFeatureFlag(FeatureFlags.BUY_WITH_COINFLOW)
const stage = useSelector(getPurchaseContentFlowStage)
const error = useSelector(getPurchaseContentError)
const isUnlocking = !error && isContentPurchaseInProgress(stage)
Expand All @@ -192,7 +197,10 @@ export const PremiumContentPurchaseModal = () => {
usePurchaseContentFormConfiguration({
track,
price,
presetValues
presetValues,
purchaseVendor: isCoinflowEnabled
? PurchaseVendor.COINFLOW
: PurchaseVendor.STRIPE
})

// Attempt recovery once on re-mount of the form
Expand Down Expand Up @@ -230,7 +238,7 @@ export const PremiumContentPurchaseModal = () => {
zIndex={zIndex.PREMIUM_CONTENT_PURCHASE_MODAL}
wrapperClassName={mobile ? styles.mobileWrapper : undefined}
>
{isValidTrack ? (
{isValidTrack && isCoinflowEnabledLoaded ? (
<Formik
initialValues={initialValues}
validationSchema={toFormikValidationSchema(validationSchema)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export const PurchaseContentFormFields = ({
<PaymentMethod
selectedMethod={purchaseMethod}
setSelectedMethod={handleChangeMethod}
selectedVendor={purchaseVendor}
setSelectedVendor={handleChangeVendor}
balance={balanceBN}
isExistingBalanceDisabled={isExistingBalanceDisabled}
Expand Down

0 comments on commit fa7a9d9

Please sign in to comment.