Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

feat: revamp currency input #262

Merged
merged 24 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c8943a3
progress on new components
kasperkristensen Jan 17, 2022
dbb4285
progress
kasperkristensen Jan 17, 2022
3f3cbfb
added responsive denomination grid
kasperkristensen Jan 17, 2022
df9bf24
Merge branch 'feat/revamp' into feat/revamp-banner-card
kasperkristensen Jan 17, 2022
5c602b6
changed to molecule
kasperkristensen Jan 17, 2022
88bfea6
progress
kasperkristensen Jan 18, 2022
7a979e8
requested changes
kasperkristensen Jan 21, 2022
aa8a593
merged revamp
kasperkristensen Jan 21, 2022
8c5b4d0
update
kasperkristensen Jan 21, 2022
a45b676
fixed currencies
kasperkristensen Jan 21, 2022
55a5e8d
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen Jan 21, 2022
a0ac5dd
push progress
kasperkristensen Jan 21, 2022
4a9de05
added new component to region settings + minor fixes to settings and …
kasperkristensen Jan 24, 2022
c83fa60
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen Jan 24, 2022
2c0619f
Merge branch 'feat/revamp' into feat/revamp-banner-card
kasperkristensen Jan 24, 2022
1c90a30
requested changes
kasperkristensen Jan 24, 2022
dc5149d
merged feat/revamp-banner-card
kasperkristensen Jan 24, 2022
704fa17
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen Jan 24, 2022
df3ed5e
fixed story
kasperkristensen Jan 25, 2022
b0c3979
removed comment
kasperkristensen Jan 25, 2022
cb41dda
fix type
kasperkristensen Jan 25, 2022
97957c4
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen Jan 25, 2022
dd0792f
fix merge conflicts
kasperkristensen Jan 25, 2022
81ec826
add Zak's fix to currency input
kasperkristensen Jan 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"react-collapse": "^5.0.1",
"react-collapsible": "^2.8.3",
"react-country-flag": "^2.3.0",
"react-currency-input-field": "^3.6.4",
"react-datepicker": "^3.6.0",
"react-dom": "^16.12.0",
"react-dropzone": "^11.3.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from "react"
import ShippingOption from "."

export default {
title: "Molecules/ShippingOption",
title: "Atoms/ShippingOption",
component: ShippingOption,
} as ComponentMeta<typeof ShippingOption>

Expand Down
41 changes: 41 additions & 0 deletions src/components/organisms/currency-input/currency-input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ComponentMeta } from "@storybook/react"
import React from "react"
import CurrencyInput from "."

export default {
title: "Organisms/CurrencyInput",
component: CurrencyInput,
} as ComponentMeta<typeof CurrencyInput>

const Template = (args) => <CurrencyInput {...args} />

export const Default = Template.bind({})
Default.args = {
currentCurrency: "usd",
currencyCodes: ["usd", "eur", "gbp"],
}

export const ReadOnly = Template.bind({})
ReadOnly.args = {
currentCurrency: "usd",
readonly: true,
}

const TemplateWithAmount = (args) => (
<CurrencyInput {...args.currencyArgs}>
<CurrencyInput.AmountInput {...args.amountArgs}></CurrencyInput.AmountInput>
</CurrencyInput>
)

export const WithAmount = TemplateWithAmount.bind({})
WithAmount.args = {
currencyArgs: {
currentCurrency: "usd",
currencyCodes: ["usd", "eur", "krw"],
size: "small",
},
amountArgs: {
label: "Price",
amount: 10000,
},
}
224 changes: 224 additions & 0 deletions src/components/organisms/currency-input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import clsx from "clsx"
import React, { useContext, useEffect, useRef, useState } from "react"
import AmountField from "react-currency-input-field"
import { Option } from "../../../types/shared"
import { currencies, CurrencyType } from "../../../utils/currencies"
import { displayAmount, persistedPrice } from "../../../utils/prices"
import MinusIcon from "../../fundamentals/icons/minus-icon"
import PlusIcon from "../../fundamentals/icons/plus-icon"
import InputContainer from "../../fundamentals/input-container"
import InputHeader from "../../fundamentals/input-header"
import Input from "../../molecules/input"
import Select from "../../molecules/select"

type CurrencyInputProps = {
currencyCodes?: string[]
currentCurrency?: string
size?: "small" | "medium" | "full"
readOnly?: boolean
onChange?: (currencyCode: string) => void
className?: React.HTMLAttributes<HTMLDivElement>["className"]
}

type CurrencyInputState = {
currencyInfo: CurrencyType | undefined
}

type AmountInputProps = {
label: string
amount: number | undefined
required?: boolean
step?: number
allowNegative?: boolean
onChange?: (amount: number | undefined) => void
}

const CurrencyContext = React.createContext<CurrencyInputState>({
currencyInfo: undefined,
})

const getCurrencyInfo = (currencyCode?: string) => {
if (!currencyCode) return undefined
const currencyInfo = currencies[currencyCode.toUpperCase()]
return currencyInfo
}

const CurrencyInput: React.FC<CurrencyInputProps> & {
AmountInput: React.FC<AmountInputProps>
} = ({
currentCurrency,
currencyCodes,
size = "full",
readOnly = false,
onChange,
children,
className,
}) => {
const options: Option[] =
currencyCodes?.map((code) => ({
label: code.toUpperCase(),
value: code,
})) ?? []

const [selectedCurrency, setSelectedCurrency] = useState<
CurrencyType | undefined
>(getCurrencyInfo(currentCurrency))

const [value, setValue] = useState<Option | undefined>(
currentCurrency
? {
label: currentCurrency.toUpperCase(),
value: currentCurrency,
}
: undefined
)

const onCurrencyChange = (currency: Option) => {
// Should not be nescessary, but the component we use for select input
// has a bug where it passes a null object if you click on the label
// of the already selected value
if (!currency) return

setValue(currency)
setSelectedCurrency(getCurrencyInfo(currency.value))

if (onChange) {
onChange(currency.value)
}
}

return (
<CurrencyContext.Provider
value={{
currencyInfo: selectedCurrency,
}}
>
<div className={clsx("flex items-center gap-x-2xsmall", className)}>
<div
className={clsx(
{ "w-[144px]": size === "medium" },
{ "w-[120px]": size === "small" },
{ "w-full": size === "full" }
)}
>
{!readOnly ? (
<Select
enableSearch
label="Currency"
value={value}
onChange={onCurrencyChange}
options={options}
disabled={readOnly}
/>
) : (
<Input
label="Currency"
value={value?.label}
readOnly
className="pointer-events-none"
tabIndex={-1}
/>
)}
</div>
{children && <div className="w-full">{children}</div>}
</div>
</CurrencyContext.Provider>
)
}

const AmountInput: React.FC<AmountInputProps> = ({
label,
required = false,
amount,
step = 1,
allowNegative = false,
onChange,
}) => {
const { currencyInfo } = useContext(CurrencyContext)
const [value, setValue] = useState<string | undefined>(
amount ? `${amount}` : undefined
)
const inputRef = useRef<HTMLInputElement | null>(null)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [loading, setLoading] = useState(true)
useEffect(() => setLoading(false), [])

useEffect(() => {
inputRef.current?.dispatchEvent(new Event("blur"))
}, [currencyInfo?.decimal_digits])

/**
* Get display amount of the current currency and amount
*/
useEffect(() => {
if (currencyInfo && amount) {
setValue(`${displayAmount(currencyInfo.code, amount)}`)
}
}, [amount, currencyInfo])

/**
* Returns the persited amount for the current currency
*/
useEffect(() => {
let persistedAmount: number | undefined = undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let persistedAmount: number | undefined = undefined
if (loading) {
return
}
let persistedAmount: number | undefined = undefined


if (currencyInfo && value) {
const amount = parseFloat(value)
persistedAmount = persistedPrice(currencyInfo.code, amount)
}

if (onChange) {
onChange(persistedAmount)
}
}, [value, currencyInfo])

const handleManualValueChange = (val: number) => {
const newValue = parseFloat(value ?? "0") + val

if (!allowNegative && newValue < 0) return

setValue(`${newValue}`)
}

return (
<InputContainer onClick={() => inputRef.current?.focus()}>
<InputHeader label={label} required={required} />
<div className="flex items-center mt-2xsmall">
{currencyInfo?.symbol_native && (
<span className="inter-base-regular text-grey-40 mr-xsmall">
{currencyInfo.symbol_native}
</span>
)}
<AmountField
className="bg-inherit outline-none outline-0 w-full remove-number-spinner leading-base text-grey-90 font-normal caret-violet-60 placeholder-grey-40"
decimalScale={currencyInfo?.decimal_digits}
value={value}
onValueChange={(value) => {
setValue(value)
}}
ref={inputRef}
step={step}
allowNegativeValue={allowNegative}
placeholder="0.00"
/>
<div className="flex self-end">
<button
className="mr-2 text-grey-50 w-4 h-4 hover:bg-grey-10 rounded-soft cursor-pointer"
type="button"
onClick={() => handleManualValueChange(-step)}
>
<MinusIcon size={16} />
</button>
<button
type="button"
className="text-grey-50 w-4 h-4 hover:bg-grey-10 rounded-soft cursor-pointer"
onClick={() => handleManualValueChange(step)}
>
<PlusIcon size={16} />
</button>
</div>
</div>
</InputContainer>
)
}

CurrencyInput.AmountInput = AmountInput

export default CurrencyInput
Loading