This repository has been archived by the owner on Jul 10, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 322
feat: revamp currency input #262
Merged
Merged
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 dbb4285
progress
kasperkristensen 3f3cbfb
added responsive denomination grid
kasperkristensen df9bf24
Merge branch 'feat/revamp' into feat/revamp-banner-card
kasperkristensen 5c602b6
changed to molecule
kasperkristensen 88bfea6
progress
kasperkristensen 7a979e8
requested changes
kasperkristensen aa8a593
merged revamp
kasperkristensen 8c5b4d0
update
kasperkristensen a45b676
fixed currencies
kasperkristensen 55a5e8d
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen a0ac5dd
push progress
kasperkristensen 4a9de05
added new component to region settings + minor fixes to settings and …
kasperkristensen c83fa60
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen 2c0619f
Merge branch 'feat/revamp' into feat/revamp-banner-card
kasperkristensen 1c90a30
requested changes
kasperkristensen dc5149d
merged feat/revamp-banner-card
kasperkristensen 704fa17
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen df3ed5e
fixed story
kasperkristensen b0c3979
removed comment
kasperkristensen cb41dda
fix type
kasperkristensen 97957c4
Merge branch 'feat/revamp' into feat/revamp-currency-input
kasperkristensen dd0792f
fix merge conflicts
kasperkristensen 81ec826
add Zak's fix to currency input
kasperkristensen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/components/organisms/currency-input/currency-input.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||||||||||||||
|
||||||||||||||
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 | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.