From 243aed4a5e9e8cbf16a6d130593f54cfc769c6c6 Mon Sep 17 00:00:00 2001 From: Philipp Rich Date: Sat, 24 Feb 2024 13:47:11 +0400 Subject: [PATCH] Implemented udate institution scenario - Tabs showing upd icon if institution being changed compared to default values --- components/AssetContainer/AssetContainer.jsx | 16 ++- components/AssetContainer/helperFunctions.js | 4 + .../InstitutionContainer.jsx | 21 ++- components/InstitutionTab/InstitutionTab.jsx | 48 +++++-- components/InstitutionTab/docs.md | 129 +++++++----------- components/InstitutionTab/learned.md | 89 ++++++++++++ .../InstitutionsTabsList.jsx | 1 + components/RecordForm/RecordForm.jsx | 2 + package-lock.json | 65 +++++++++ package.json | 1 + 10 files changed, 277 insertions(+), 99 deletions(-) create mode 100644 components/AssetContainer/helperFunctions.js create mode 100644 components/InstitutionTab/learned.md diff --git a/components/AssetContainer/AssetContainer.jsx b/components/AssetContainer/AssetContainer.jsx index dd71985..4ff4283 100644 --- a/components/AssetContainer/AssetContainer.jsx +++ b/components/AssetContainer/AssetContainer.jsx @@ -37,18 +37,28 @@ export function AssetContainer({ isCompact = false, onDeleteAsset, }) { - const { register } = useFormContext(); + const { register, setValue, getFieldState, formState } = useFormContext(); + // const { isDirty } = getFieldState(`${assetName}`, formState); const amountInput = ( + {/*

{`fieldState: ${isDirty}`}

*/} {isCompact || Amount} - + { + const upperCaseValue = e.target.value.toUpperCase(); + setValue(`${assetName}.currency`, upperCaseValue); + }, + })} placeholder="USD" flexShrink={0} w={14} diff --git a/components/AssetContainer/helperFunctions.js b/components/AssetContainer/helperFunctions.js new file mode 100644 index 0000000..2e0f446 --- /dev/null +++ b/components/AssetContainer/helperFunctions.js @@ -0,0 +1,4 @@ +export const formatCurrency = (assetName, event) => { + const modifiedValue = event.target.value.toUpperCase(); + setValue(`${assetName}.currency`, modifiedValue); +}; diff --git a/components/InstitutionContainer/InstitutionContainer.jsx b/components/InstitutionContainer/InstitutionContainer.jsx index a55c646..2310ca9 100644 --- a/components/InstitutionContainer/InstitutionContainer.jsx +++ b/components/InstitutionContainer/InstitutionContainer.jsx @@ -116,7 +116,14 @@ const AssetsList = ({ isInstitutionOpen, institutionName }) => { } = useFieldArray({ name: arrayName, }); - const { resetField } = useFormContext(); + const { + resetField, + getValues, + formState: { isDirty, dirtyFields, defaultValues }, + } = useFormContext(); + // const { fields: institutionFields } = useFieldArray({ + // name: "institutions.0.assets.0", + // }); return ( { isCompact={!isInstitutionOpen} /> ))} + {/* TEST HERE */} + + + {isInstitutionOpen && ( <> @@ -169,7 +184,9 @@ const InstitutionNameInput = ({ institutionName }) => { Institution Name diff --git a/components/InstitutionTab/InstitutionTab.jsx b/components/InstitutionTab/InstitutionTab.jsx index 9a1ab1a..4e26215 100644 --- a/components/InstitutionTab/InstitutionTab.jsx +++ b/components/InstitutionTab/InstitutionTab.jsx @@ -2,23 +2,38 @@ import { IconButton, Tab, forwardRef, Box } from "@chakra-ui/react"; import { CgUndo } from "react-icons/cg"; +import { useFormContext, useWatch } from "react-hook-form"; import classes from "./InstitutionTab.module.css"; export const InstitutionTab = forwardRef( - ( - { name = "Unnamed institution", isDeleted = false, state = null, ...props }, - ref - ) => ( - - {name} - - - ) + ({ isDeleted = false, institutionName, ...props }, ref) => { + const { formState, getValues, control } = useFormContext(); + const institutionDefaultValues = + formState.defaultValues.institutions[ + getInstitutionIndex(institutionName) + ]; + const institutionCurrentValues = getValues(institutionName); + const isChanged = + JSON.stringify(institutionDefaultValues) !== + JSON.stringify(institutionCurrentValues); + const state = isChanged ? "updated" : null; + const name = useWatch({ + control, + name: `${institutionName}.name`, + }); + + return ( + + {name} + + + ); + } ); function TabRightSection({ state, isDeleted }) { @@ -39,3 +54,8 @@ function TabRightSection({ state, isDeleted }) { ); } else return false; } + +function getInstitutionIndex(institutionName) { + const index = parseInt(institutionName.split(".")[1]); + return index; +} diff --git a/components/InstitutionTab/docs.md b/components/InstitutionTab/docs.md index f790fb7..0dc9159 100644 --- a/components/InstitutionTab/docs.md +++ b/components/InstitutionTab/docs.md @@ -1,89 +1,58 @@ -## Altering components style in chakra ui -[Build your own design system with chakraui | Youtube](https://youtu.be/epJuxo8FKFA?si=UEmVtkfPLerimLkN&t=1210) - -The fundamental approach: -1. Create components style file, according to Chakra style API -2. Use methods `definePartsStyle`, `defineMultiStyleConfig` -3. extend chacra's `theme` appending component styles with `extendTheme` ([theme.js](app/ChakraTheme.js)) -4. pass `theme` as prop to `ChakraProvider` - - -Tabs is a multipart component - [chackra docs | styling-multipart-components](https://chakra-ui.com/docs/styled-system/component-style#styling-multipart-components) and require `definePartsStyle`, `defineMultiStyleConfig` methods - - -- [ ] How to use props do be passed to styles? - -Chakra tab custom styles -```js -import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(["tablist", "tab"]); - -export const tabsTheme = defineMultiStyleConfig({ - variants: { - grid: { - tab: { - borderRadius: "base", - _selected: { - bg: "black", - }, +## InstitutionTab +### Props +```diff +//Props +- isNew +- isDeleted +- isUpdated +- text ++ insitutionName (string) +onRestore + +//Context ++ fields from useFieldArray() ++ getValue ++ getFieldState +``` - justifyContent: "flex-start", - }, - tablist: { - display: "grid", - gridTemplateColumns: "repeat(auto-fit, minmax(100px,1fr))", - gap: "3", - w: "100%", - }, - }, - }, -}); +### Listeners +- restoreButton click + +### States +```mermaid +--- +title: InstitutionTab +--- +stateDiagram-v2 + direction LR + +[*] --> existing +existing --> updated : prop changed +updated --> existing : prop changed +updated --> deleted : prop changed +existing --> deleted : prop changed +deleted --> updated : BUTTON_PRESS
Do / setState +deleted --> existing : BUTTON_PRESS
Do / setState +[*] --> new ``` +## Worklog +### Deriving the states +I want to minimize states number. By somehow derive from existing use-hook-form states -ChakraTheme.js -```js -import { tabsTheme } from "../components/InstitutionTab/Tabs.chakra"; -... -const theme = extendTheme({ - config, - components: { - Tabs: tabsTheme, - }, -}); +#### New state +Probably can be known via comparison between institution value and defaultValues(which are retrieved) +For this scenario probably the simplest reliable way is to generate unique ID for institution and store it in db. -export default theme; -``` +*defaultValues* can be retrieved from `fields` https://react-hook-form.com/docs/usefieldarray#:~:text=Description-,fields,-object%20%26%20%7B%20id +if `institutions.ID.name` from `fields` === '' than institution is new -## ForwardRef to extend component -`forwardRef` https://chakra-ui.com/community/recipes/as-prop#option-1-using-forwardref-from-chakra-uireact +#### Deleted state +can be set by disabling institution fields -Seems to work - -```js -const IntitutionTab = forwardRef((props, ref) => ( - - Custom Tab - -)); -... - -``` +#### Updated state +can be retrived from `isDirty` -```js -const IntitutionTab = forwardRef( - ( - { name = "Unnamed institution", isDeleted = false, state = null, ...props }, - ref - ) => { -... - -``` \ No newline at end of file +### Text prop +`text`(`InstitutionName`) can be retrieved by `getValues()`, **but there is problem of retreiving value from disabled inputs**(which i planned to use for deleted state) https://github.com/orgs/react-hook-form/discussions/11533 diff --git a/components/InstitutionTab/learned.md b/components/InstitutionTab/learned.md new file mode 100644 index 0000000..f790fb7 --- /dev/null +++ b/components/InstitutionTab/learned.md @@ -0,0 +1,89 @@ +## Altering components style in chakra ui +[Build your own design system with chakraui | Youtube](https://youtu.be/epJuxo8FKFA?si=UEmVtkfPLerimLkN&t=1210) + +The fundamental approach: +1. Create components style file, according to Chakra style API +2. Use methods `definePartsStyle`, `defineMultiStyleConfig` +3. extend chacra's `theme` appending component styles with `extendTheme` ([theme.js](app/ChakraTheme.js)) +4. pass `theme` as prop to `ChakraProvider` + + +Tabs is a multipart component - [chackra docs | styling-multipart-components](https://chakra-ui.com/docs/styled-system/component-style#styling-multipart-components) and require `definePartsStyle`, `defineMultiStyleConfig` methods + + +- [ ] How to use props do be passed to styles? + +Chakra tab custom styles +```js +import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; +const { definePartsStyle, defineMultiStyleConfig } = + createMultiStyleConfigHelpers(["tablist", "tab"]); + +export const tabsTheme = defineMultiStyleConfig({ + variants: { + grid: { + tab: { + borderRadius: "base", + _selected: { + bg: "black", + }, + + justifyContent: "flex-start", + }, + tablist: { + display: "grid", + gridTemplateColumns: "repeat(auto-fit, minmax(100px,1fr))", + gap: "3", + w: "100%", + }, + }, + }, +}); +``` + + +ChakraTheme.js +```js +import { tabsTheme } from "../components/InstitutionTab/Tabs.chakra"; +... +const theme = extendTheme({ + config, + components: { + Tabs: tabsTheme, + }, +}); + +export default theme; +``` + + +## ForwardRef to extend component +`forwardRef` https://chakra-ui.com/community/recipes/as-prop#option-1-using-forwardref-from-chakra-uireact + +Seems to work + +```js +const IntitutionTab = forwardRef((props, ref) => ( + + Custom Tab + +)); +... + +``` + +```js +const IntitutionTab = forwardRef( + ( + { name = "Unnamed institution", isDeleted = false, state = null, ...props }, + ref + ) => { +... + +``` \ No newline at end of file diff --git a/components/InstitutionsTabsList/InstitutionsTabsList.jsx b/components/InstitutionsTabsList/InstitutionsTabsList.jsx index 807451f..8920d82 100644 --- a/components/InstitutionsTabsList/InstitutionsTabsList.jsx +++ b/components/InstitutionsTabsList/InstitutionsTabsList.jsx @@ -44,6 +44,7 @@ export function InstitutionsTabsList({ // as={motion.div} // layout="position" width={isKeyboardOpened && "180px"} + institutionName={`institutions.${id}`} key={`institutionTab-${id}`} name={institution.name} state="new" diff --git a/components/RecordForm/RecordForm.jsx b/components/RecordForm/RecordForm.jsx index e3a9fb3..492cfb5 100644 --- a/components/RecordForm/RecordForm.jsx +++ b/components/RecordForm/RecordForm.jsx @@ -13,6 +13,7 @@ import { CgMinimizeAlt } from "react-icons/cg"; import { InstitutionsList } from "../InstitutionsList"; import { FormHeader } from "~/FormHeader"; +import { DevTool } from "@hookform/devtools"; const prevRecord = { institutions: [ @@ -142,6 +143,7 @@ export function RecordForm({ onSubmit }) { // institutions={prevRecord.institutions} /> + ); } diff --git a/package-lock.json b/package-lock.json index af38804..59d9aaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-icons": "^5.0.1" }, "devDependencies": { + "@hookform/devtools": "^4.3.1", "@storybook/addon-essentials": "^8.0.0-alpha.12", "@storybook/addon-interactions": "^8.0.0-alpha.12", "@storybook/addon-links": "^8.0.0-alpha.12", @@ -4079,6 +4080,35 @@ "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", "dev": true }, + "node_modules/@hookform/devtools": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@hookform/devtools/-/devtools-4.3.1.tgz", + "integrity": "sha512-CrWxEoHQZaOXJZVQ8KBgOuAa8p2LI8M0DAN5GTRTmdCieRwFVjVDEmuTAVazWVRRkpEQSgSt3KYp7VmmqXdEnw==", + "dev": true, + "dependencies": { + "@emotion/react": "^11.1.5", + "@emotion/styled": "^11.3.0", + "@types/lodash": "^4.14.168", + "little-state-machine": "^4.1.0", + "lodash": "^4.17.21", + "react-simple-animate": "^3.3.12", + "use-deep-compare-effect": "^1.8.1", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/@hookform/devtools/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -12895,6 +12925,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/little-state-machine": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/little-state-machine/-/little-state-machine-4.8.0.tgz", + "integrity": "sha512-xfi5+iDxTLhu0hbnNubUs+qoQQqxhtEZeObP5ELjUlHnl74bbasY7mOonsGQrAouyrbag3ebNLSse5xX1T7buQ==", + "dev": true, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -15354,6 +15393,15 @@ } } }, + "node_modules/react-simple-animate": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/react-simple-animate/-/react-simple-animate-3.5.2.tgz", + "integrity": "sha512-xLE65euP920QMTOmv5haPlml+hmOPDkbIr5WeF7ADIXWBYt5kW/vwpNfWg8EKMab8aeDxIZ6QjffVh8v2dUyhg==", + "dev": true, + "peerDependencies": { + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -17694,6 +17742,23 @@ } } }, + "node_modules/use-deep-compare-effect": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/use-deep-compare-effect/-/use-deep-compare-effect-1.8.1.tgz", + "integrity": "sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "dequal": "^2.0.2" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index e4224ac..e644c31 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "react-icons": "^5.0.1" }, "devDependencies": { + "@hookform/devtools": "^4.3.1", "@storybook/addon-essentials": "^8.0.0-alpha.12", "@storybook/addon-interactions": "^8.0.0-alpha.12", "@storybook/addon-links": "^8.0.0-alpha.12",