Skip to content

Commit

Permalink
added interactivity to AssetContainer
Browse files Browse the repository at this point in the history
- Added FormProvider to RecordForm
- Changed InstitutionContainer to useFieldArray
- Updated story for AssetContainer
- Updated AssetContainer docs
  • Loading branch information
skorphil committed Feb 20, 2024
1 parent b62f40c commit ac80382
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 123 deletions.
23 changes: 3 additions & 20 deletions app/page.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
// import AssetContainer from "../components/AssetContainer/AssetContainer";
import dynamic from "next/dynamic";

const AssetContainer = dynamic(
() => import("../components/AssetContainer/AssetContainer"),
{
ssr: true,
}
);
import { RecordForm } from "~/RecordForm";

export default function Home() {
// const response = await fetch(
// "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/eur/jpy.json"
// );
// const data = await response.json();

// async function search(formData) {
// "use server";
// const query = formData.get("query");
// console.log(formData);
// }

const isClient = typeof window !== "undefined";
// const isClient = typeof window !== "undefined";
return (
<main>
<AssetContainer />
<RecordForm />
</main>
);
}
1 change: 1 addition & 0 deletions app/providers.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";

import theme from "./ChakraTheme.js";
import { ChakraProvider } from "@chakra-ui/react";

Expand Down
67 changes: 40 additions & 27 deletions components/AssetContainer/AssetContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
Component represent the instance of asset inside of the institution.
Fields
{
amount: number,
currency: string,
description: string,
isEarning: bool,
}
Listeners
deleteAssetButton
Output
expanded or compact variant
*/

"use client";

import {
Expand All @@ -11,30 +26,29 @@ import {
Checkbox,
IconButton,
} from "@chakra-ui/react";
import { useState } from "react";
import { DeleteIcon } from "@chakra-ui/icons";
import { useFormContext } from "react-hook-form";

export default function AssetContainer({ asset, isExpanded }) {
const [amount, setAmount] = useState(0);
const numFormat = (val) => val.toLocaleString();
const parse = (val) => Number(val.replace(/^\$/, ""));
// TODO format registered number input
// TODO format currency to all caps
// TODO currency validation and autocomplete based on external API
export function AssetContainer({
assetName,
isCompact = false,
onDeleteAsset,
}) {
const { register } = useFormContext();

// TODO what is better way to structur component with 2 very different ui states?
const amountInput = (
<HStack align="end" spacing={1} flex={1}>
<FormControl>
{isExpanded && <FormLabel>Amount</FormLabel>}
<NumberInput
onChange={(val) => setAmount(parse(val))}
value={numFormat(asset?.amount ?? 0)}
name="amount"
>
<NumberInputField px={2} />
{isCompact || <FormLabel>Amount</FormLabel>}
<NumberInput>
<NumberInputField {...register(`${assetName}.amount`)} px={2} />
</NumberInput>
</FormControl>
<Input
name="currency"
value={asset?.currency ?? ""}
{...register(`${assetName}.currency`)}
placeholder="USD"
flexShrink={0}
w={14}
Expand All @@ -47,30 +61,29 @@ export default function AssetContainer({ asset, isExpanded }) {
<VStack align="start" spacing={3} w="100%">
<HStack w="100%" align="end" spacing={4}>
{amountInput}
{isExpanded && (
<Checkbox
defaultChecked={asset?.isEarning ?? false}
name="isEarning"
h={10}
size="lg"
>
{isCompact || (
<Checkbox {...register(`${assetName}.isEarning`)} h={10} size="lg">
Earning
</Checkbox>
)}
<IconButton
onClick={onDeleteAsset}
variant="ghost"
aria-label="Delete asset"
icon={<DeleteIcon />}
/>
</HStack>
{isExpanded && (

{isCompact || (
<FormControl>
<FormLabel value={asset?.description ?? ""} name="description">
Description
</FormLabel>
<Input px={2} />
<FormLabel>Description</FormLabel>
<Input {...register(`${assetName}.description`)} px={2} />
</FormControl>
)}
</VStack>
);
}

// const [amount, setAmount] = useState(0);
// const numFormat = (val) => val.toLocaleString();
// const parse = (val) => Number(val.replace(/^\$/, ""));
19 changes: 0 additions & 19 deletions components/AssetContainer/AssetContainer.stories.jsx

This file was deleted.

65 changes: 30 additions & 35 deletions components/AssetContainer/docs.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
## Formatting numbers like 1,000.0000
For amount inputField I want numbers with thousands separator. It can be achieved with:
```js
const format = (val) => {formatting logic here}

<NumberInput value={format(value)} />
## AssetContainer
### Props
```diff
isCompact (bool). From [InstitutionContainer]
onDeleteAsset (function) From [RecordForm]
+ assetName (string `institutions.ID.assets.ID`) used for registering input

- Asset (array) From [InstitutionContainer] // used before use-hook-form
- InstitutionId
- AssetId // used for registering fields to useForm, but later joined replaced with single assetName prop
```
[Chakra docs](https://chakra-ui.com/docs/components/number-input#formatting-and-parsing-the-value)

It can be achieved with several approaches:
- Library for number formatting
- [react-number-format](https://s-yadav.github.io/react-number-format/docs/props) I dont like nesting approach. To use input with props `<Input name="currency" placeholder="USD />` inside `<NumericFormat customInput={input}>` i need to create separate variable. At first glance it seems not optimal.
- [numbrojs](https://numbrojs.com)
- [numeraljs](http://numeraljs.com) (I used this one before and like it, but library seems very outdated)

- Vanilla js with `toLocalString`
[stackoverflow](https://stackoverflow.com/a/48062039/15007541)

For RecordForm I sticked to vanilla JS, because seems easier and this way I reduce the number of dependencies.


```jsx
const [amount, setAmount] = useState(0);
const numFormat = (val) => val.toLocaleString();
const parse = (val) => Number(val.replace(/^\$/, "")); // val is string, so need to use Number() to make it work with .toLocaleString

<NumberInput // props go in chakra <NumberInput>, not <NumberInputField>
onChange={(val) => setAmount(parse(val))} // val returns string
value={numFormat(amount)}
name="amount"
px={2}
>
<NumberInputField />
</NumberInput>
### Listeners
- deleteButton onClick
- inputs onChange

### States
```mermaid
---
title: AssetContainer
---
stateDiagram-v2
direction LR
[*] --> expanded
[*] --> compact
expanded --> compact : prop iscompact changed
compact --> expanded : prop iscompact changed
```

`Number("") === 0` so when i clear input i got 0. For now i decided to let this be

This will be changed later when used with *react-hook-form*
## See also
- [next.js/examples/next-forms at canary · vercel/next.js · GitHub](https://github.com/vercel/next.js/tree/canary/examples/next-forms)
- [Data Fetching: Server Actions and Mutations | Next.js](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms)
- https://react-hook-form.com/docs/usefieldarray
1 change: 1 addition & 0 deletions components/AssetContainer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AssetContainer } from "./AssetContainer";
39 changes: 39 additions & 0 deletions components/AssetContainer/learned.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Formatting numbers like 1,000.0000
For amount inputField I want numbers with thousands separator. It can be achieved with:
```js
const format = (val) => {formatting logic here}

<NumberInput value={format(value)} />
```
[Chakra docs](https://chakra-ui.com/docs/components/number-input#formatting-and-parsing-the-value)

It can be achieved with several approaches:
- Library for number formatting
- [react-number-format](https://s-yadav.github.io/react-number-format/docs/props) I dont like nesting approach. To use input with props `<Input name="currency" placeholder="USD />` inside `<NumericFormat customInput={input}>` i need to create separate variable. At first glance it seems not optimal.
- [numbrojs](https://numbrojs.com)
- [numeraljs](http://numeraljs.com) (I used this one before and like it, but library seems very outdated)

- Vanilla js with `toLocalString`
[stackoverflow](https://stackoverflow.com/a/48062039/15007541)

For RecordForm I sticked to vanilla JS, because seems easier and this way I reduce the number of dependencies.


```jsx
const [amount, setAmount] = useState(0);
const numFormat = (val) => val.toLocaleString();
const parse = (val) => Number(val.replace(/^\$/, "")); // val is string, so need to use Number() to make it work with .toLocaleString

<NumberInput // props go in chakra <NumberInput>, not <NumberInputField>
onChange={(val) => setAmount(parse(val))} // val returns string
value={numFormat(amount)}
name="amount"
px={2}
>
<NumberInputField />
</NumberInput>
```

`Number("") === 0` so when i clear input i got 0. For now i decided to let this be

This will be changed later when used with *react-hook-form*
29 changes: 29 additions & 0 deletions components/AssetContainer/storybook/AssetContainer.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AssetContainer } from "~/AssetContainer";
import { AssetContainerDecorator } from "./AssetContainerDecorator";

const meta = {
title: "RecordForm/AssetContainer",
component: AssetContainer,
decorators: [
(Story, { args }) => (
<AssetContainerDecorator>
<Story />
</AssetContainerDecorator>
),
],
};
export default meta;

export const Expanded = {
args: {
isCompact: false,
assetName: "institutions.0.assets.0",
},
};

export const Compact = {
args: {
...Expanded.args,
isCompact: true,
},
};
Loading

0 comments on commit ac80382

Please sign in to comment.