Skip to content

Commit

Permalink
add CurrencyInput component
Browse files Browse the repository at this point in the history
  • Loading branch information
ukorvl committed Dec 16, 2024
1 parent c1969eb commit 19d5099
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 0 deletions.
103 changes: 103 additions & 0 deletions src/components/currency-input/CurrencyInput.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Canvas, Meta, Story, ArgsTable, Source } from "@storybook/blocks";
import CurrencyInput from "./CurrencyInput";
import { useState } from "react";
import { useStyletron } from "baseui";
import { INPUT_SIZE, INPUT_KIND } from "../input";
import { HeartIcon } from "../icons";

<Meta title="Form/CurrencyInput" component={CurrencyInput} />

export const Template = ({ ...args }) => {
const [value, setValue] = useState({
currency: "USDT",
amount: "",
});
const [css] = useStyletron();
const currencies = [
"USDT",
"BTC",
"ETH",
"BNB",
"ADA",
"XRP",
];

const onChange = ({ currency, amount }) => {
setValue({ currency, amount });
};

return (

<div
className={css({
width: "400px",
})}
>
<CurrencyInput {...args} value={value} currencies={currencies} onChange={onChange} />
</div>
); };

# Input

<Canvas isColumn>
<Story name="Small" args={{ placeholder: "Input Field", size: INPUT_SIZE.small }}>
{Template.bind({})}
</Story>
<Story name="Medium" args={{ placeholder: "Input Field" }}>
{Template.bind({})}
</Story>
<Story name="Disabled" args={{ placeholder: "Disabled Field", disabled: true }}>
{Template.bind({})}
</Story>
<Story
name="Secondary disabled"
args={{
placeholder: "Disabled Secondary Small",
disabled: true,
size: INPUT_SIZE.small,
kind: INPUT_KIND.secondary,
}}
>
{Template.bind({})}
</Story>
<Story name="Secondary" args={{ placeholder: "Secondary input", kind: INPUT_KIND.secondary }}>
{Template.bind({})}
</Story>
<Story name="Error" args={{ placeholder: "Error Field", error: true }}>
{Template.bind({})}
</Story>
<Story name="With Start Enhancer" args={{ placeholder: "With start enchancer", startEnhancer: <HeartIcon /> }}>
{Template.bind({})}
</Story>
</Canvas>

<ArgsTable of={CurrencyInput} />

### Usage:

To use, import the component `CurrencyInput` from `@nilfoundation/ui-kit`.

<Source
language="tsx"
dark
format={true}
code={`
import {CurrencyInput} from "@nilfoundation/ui-kit";
...
const onChange = ({ currency, amount }) => {
setValue({ currency, amount });
};
const currencies = [
"USDT",
"BTC",
"ETH",
"BNB",
"ADA",
"XRP",
];
<CurrencyInput placeholder="Currency input field" size={INPUT_SIZE.small} currencies={currencies} onChange={onChange} />
`}
/>
40 changes: 40 additions & 0 deletions src/components/currency-input/CurrencyInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { render } from "../../test-utils/render";
import { createComponentSSRTest } from "../../test-utils/createComponentSSRTest";
import CurrencyInput from "./CurrencyInput";
import { INPUT_SIZE } from "../input";

describe("CurrencyInput", () => {
it("renders without crashing", () => {
render(<CurrencyInput size={INPUT_SIZE.medium} value={{ currency: "USDT", amount: "1" }} currencies={[]} />);

const inputElement = screen.getByRole("spinbutton");
expect(inputElement).toBeInTheDocument();
});

it("handles input changes", async () => {
const handleChange = jest.fn();
render(
<CurrencyInput
size={INPUT_SIZE.medium}
onChange={handleChange}
value={{ currency: "USDT", amount: "1" }}
currencies={[]}
></CurrencyInput>
);

const inputElement = screen.getByRole("spinbutton");
userEvent.type(inputElement, "100");

await waitFor(() => expect(handleChange).toHaveBeenCalled());
});

it("renders ssr without crashing", () => {
createComponentSSRTest(
<CurrencyInput size={INPUT_SIZE.medium} value={{ currency: "USDT", amount: "1" }} currencies={[]} />
);
});

// Add more tests as needed
});
80 changes: 80 additions & 0 deletions src/components/currency-input/CurrencyInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FormControl } from "../form-control";
import { INPUT_SIZE, Input } from "../input";
import { Select } from "../select";
import { CurrencyInputProps } from "./types";
import { getMergedOverrides } from "../../shared/utils/getMergedOverrides";
import { inputOverrides, selectOverrides } from "./overrides";
import { mapInputSizeToSelectSize } from "./utils";

const CurrencyInput = <Currency extends string = string>({
value,
onChange,
currencies,
className,
label,
disabled = false,
caption,
placeholder,
overrides,
size = INPUT_SIZE.medium,
startEnhancer,
error,
required,
kind,
}: CurrencyInputProps<Currency>) => {
const mergedInputOverrides = getMergedOverrides(inputOverrides, overrides);

return (
<div className={className}>
<FormControl label={label} caption={caption} error={error} required={required} size={size}>
<Input
startEnhancer={startEnhancer}
disabled={disabled}
overrides={mergedInputOverrides}
placeholder={placeholder}
type="number"
value={value.amount}
onChange={(e) => {
onChange &&
onChange({
currency: value.currency,
amount: e.currentTarget.value,
});
}}
size={size}
kind={kind}
endEnhancer={
<Select
disabled={disabled}
options={currencies.map((currency) => ({
label: currency,
id: currency,
}))}
searchable={false}
overrides={selectOverrides}
placeholder=""
clearable={false}
onChange={(params) => {
onChange &&
onChange({
currency: params.value[0].label as Currency,
amount: value.amount,
});
}}
value={[
{
label: value.currency,
id: value.currency,
},
]}
size={mapInputSizeToSelectSize(size)}
error={error !== undefined}
/>
}
/>
</FormControl>
</div>
);
};

export default CurrencyInput;
1 change: 1 addition & 0 deletions src/components/currency-input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as CurrencyInput } from "./CurrencyInput";
33 changes: 33 additions & 0 deletions src/components/currency-input/overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { InputOverrides } from "baseui/input";

export const inputOverrides: InputOverrides = {
Input: {
style: {
"::-webkit-outer-spin-button": {
WebkitAppearance: "none",
margin: 0,
},
"::-webkit-inner-spin-button": {
WebkitAppearance: "none",
margin: 0,
},
"-moz-appearance": "textfield",
},
},
};

export const selectOverrides = {
ControlContainer: {
style: {
width: "100px",
backgroundColor: "transparent",
boxShadow: "none",
":has(input:focus-within)": {
boxShadow: "none",
},
":hover": {
backgroundColor: "transparent",
},
},
},
};
13 changes: 13 additions & 0 deletions src/components/currency-input/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FormControlProps } from "../form-control";
import { InputProps } from "../input";

export type CurrencyInputProps<Currency extends string = string> = Pick<
InputProps,
"placeholder" | "disabled" | "overrides" | "size" | "startEnhancer" | "kind"
> &
Pick<FormControlProps, "caption" | "label" | "error" | "required"> & {
className?: string;
value: { currency: Currency; amount: string };
onChange?: (value: { currency: Currency; amount: string }) => void;
currencies: Currency[];
};
11 changes: 11 additions & 0 deletions src/components/currency-input/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { INPUT_SIZE } from "../input";
import { SELECT_SIZE } from "../select";

export const mapInputSizeToSelectSize = (size: INPUT_SIZE) => {
switch (size) {
case INPUT_SIZE.small:
return SELECT_SIZE.small;
case INPUT_SIZE.medium:
return SELECT_SIZE.medium;
}
};

0 comments on commit 19d5099

Please sign in to comment.