From 55363959efa08c4e0590021cbf979d10a080ead4 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Wed, 4 Jun 2025 23:29:23 +0530 Subject: [PATCH 01/11] fix(web): draggable-list-update-issue --- package.json | 2 + src/lib/draggable-list/index.tsx | 119 +++++++++++++------------ src/lib/draggable-list/useList.ts | 97 ++++++++++++++++++++ src/stories/draggable-list.stories.tsx | 24 ++++- yarn.lock | 9 ++ 5 files changed, 191 insertions(+), 60 deletions(-) create mode 100644 src/lib/draggable-list/useList.ts diff --git a/package.json b/package.json index 4d48010..1b6855d 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@storybook/test": "^8.6.4", "@tailwindcss/postcss": "^4.0.11", "@tailwindcss/vite": "^4.1.4", + "@types/lodash": "^4", "@types/node": "^22.13.10", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.3", @@ -93,6 +94,7 @@ "@internationalized/date": "^3.7.0", "bignumber.js": "^9.1.2", "clsx": "^2.1.1", + "lodash": "^4.17.21", "react": "^18.0.0", "react-aria-components": "^1.7.1", "react-dom": "^18.0.0", diff --git a/src/lib/draggable-list/index.tsx b/src/lib/draggable-list/index.tsx index 5d4b9a4..b652186 100644 --- a/src/lib/draggable-list/index.tsx +++ b/src/lib/draggable-list/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from "react"; -import { useListData } from "react-stately"; +import React from "react"; import { Button, ListBox, @@ -13,6 +12,7 @@ import { cn } from "../../utils"; import DragAndDropIcon from "../../assets/svgs/drag-and-drop.svg"; import Trash from "../../assets/svgs/trash.svg"; import clsx from "clsx"; +import { useList } from "./useList"; type ListItem = { id: string | number; @@ -53,24 +53,26 @@ function DraggableList({ deletionDisabled = false, ...props }: Readonly) { - const list = useListData({ + const { + items: list, + moveAfter, + moveBefore, + remove, + getItem, + } = useList({ initialItems: items, + onChange: updateCallback, }); - useEffect(() => { - if (!updateCallback) return; - updateCallback(list.items); - }, [list, updateCallback, items]); - const { dragAndDropHooks } = useDragAndDrop({ getItems: (keys) => - [...keys].map((key) => ({ "text/plain": list.getItem(key)!.name })), + [...keys].map((key) => ({ "text/plain": getItem(key)!.name })), getAllowedDropOperations: () => ["move"], onReorder(e) { if (e.target.dropPosition === "before") { - list.moveBefore(e.target.key, e.keys); + moveBefore(e.target.key, e.keys); } else if (e.target.dropPosition === "after") { - list.moveAfter(e.target.key, e.keys); + moveAfter(e.target.key, e.keys); } }, renderDragPreview, @@ -81,11 +83,11 @@ function DraggableList({ {...props} aria-label={props["aria-label"] ?? "Reorderable list"} selectionMode="single" - items={list.items} + items={list} dragAndDropHooks={dragDisabled ? undefined : dragAndDropHooks} onSelectionChange={(keys) => { const keyArr = Array.from(keys); - const selectedItem = list.getItem(keyArr[0]); + const selectedItem = getItem(keyArr[0]); if (selectionCallback && selectedItem) selectionCallback(selectedItem); }} @@ -96,50 +98,53 @@ function DraggableList({ className, )} > - {(item) => ( - - cn( - "h-11.25 w-full cursor-pointer border-l-3 border-l-transparent", - "flex items-center gap-4 px-4", - "focus-visible:outline-klerosUIComponentsPrimaryBlue focus-visible:outline", - (isHovered || isSelected) && "bg-klerosUIComponentsMediumBlue", - isSelected && "border-l-klerosUIComponentsPrimaryBlue", - isDragging && "cursor-grabbing opacity-60", - ) - } - > - {({ isHovered }) => ( - <> - {dragDisabled ? null : ( - - )} - - {item.name} - - {isHovered && !deletionDisabled ? ( - - ) : null} - - )} - - )} + {items.map((item) => { + return ( + + cn( + "h-11.25 w-full cursor-pointer border-l-3 border-l-transparent", + "flex items-center gap-4 px-4", + "focus-visible:outline-klerosUIComponentsPrimaryBlue focus-visible:outline", + (isHovered || isSelected) && "bg-klerosUIComponentsMediumBlue", + isSelected && "border-l-klerosUIComponentsPrimaryBlue", + isDragging && "cursor-grabbing opacity-60", + ) + } + > + {({ isHovered, isSelected }) => ( + <> + {dragDisabled ? null : ( + + )} + + {item.name} + + {isHovered && !deletionDisabled ? ( + + ) : null} + + )} + + ); + })} ); } diff --git a/src/lib/draggable-list/useList.ts b/src/lib/draggable-list/useList.ts new file mode 100644 index 0000000..bfd0e75 --- /dev/null +++ b/src/lib/draggable-list/useList.ts @@ -0,0 +1,97 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import _ from "lodash"; + +type Key = string | number; + +export interface ListItem { + id: Key; + name: string; + value: any; +} + +interface UseListOptions { + initialItems: ListItem[]; + onChange?: (updatedItems: ListItem[]) => void; +} + +export function useList({ initialItems, onChange }: UseListOptions) { + const [items, setItems] = useState(initialItems); + + useEffect(() => { + // preventing callback loop, we cannot rely on useEffect dependency since that does not utilize deep comparison + if (_.isEqual(initialItems, items)) return; + + setItems(initialItems); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialItems]); + + const updateItems = useCallback( + (newItems: ListItem[]) => { + setItems(newItems); + onChange?.(newItems); + }, + [onChange], + ); + + const itemsMap = useMemo(() => { + const map = new Map(); + for (const item of items) { + map.set(item.id, item); + } + return map; + }, [items]); + + const getItem = useCallback((key: Key) => itemsMap.get(key), [itemsMap]); + + const remove = useCallback( + (key: Key) => { + updateItems(items.filter((item) => key !== item.id)); + }, + [items, updateItems], + ); + + const moveBefore = useCallback( + (targetKey: Key, keys: Iterable) => { + const key = Array.from(keys)[0]; + if (key === targetKey) return; + + const indexFrom = items.findIndex((item) => item.id === key); + const indexTo = items.findIndex((item) => item.id === targetKey); + if (indexFrom === -1 || indexTo === -1) return; + + const reordered = [...items]; + const [movedItem] = reordered.splice(indexFrom, 1); + reordered.splice(indexTo, 0, movedItem); + updateItems(reordered); + }, + [items, updateItems], + ); + + const moveAfter = useCallback( + (targetKey: Key, keys: Iterable) => { + const key = Array.from(keys)[0]; + if (key === targetKey) return; + + const indexFrom = items.findIndex((item) => item.id === key); + const indexTo = items.findIndex((item) => item.id === targetKey); + if (indexFrom === -1 || indexTo === -1) return; + + const reordered = [...items]; + const [movedItem] = reordered.splice(indexFrom, 1); + + // Adjust if removing item before target index + const insertIndex = indexFrom < indexTo ? indexTo : indexTo + 1; + reordered.splice(insertIndex, 0, movedItem); + updateItems(reordered); + }, + [items, updateItems], + ); + + return { + items, + getItem, + remove, + moveBefore, + moveAfter, + }; +} diff --git a/src/stories/draggable-list.stories.tsx b/src/stories/draggable-list.stories.tsx index 7ccbc55..16118d7 100644 --- a/src/stories/draggable-list.stories.tsx +++ b/src/stories/draggable-list.stories.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import React, { useState } from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { IPreviewArgs } from "./utils"; import DraggableList from "../lib/draggable-list"; +import { Button } from "../lib"; const meta = { component: DraggableList, @@ -33,8 +34,25 @@ export const Default: Story = { { id: 3, name: "Acrobat", value: "" }, ], }, - render: (args) => { - return ; + render: function Render(args) { + const [items, setItems] = useState([ + { id: 1, name: "Illustrator", value: "" }, + { id: 2, name: "Premiere", value: "" }, + { id: 3, name: "Acrobat", value: "" }, + ]); + + const addItem = () => { + setItems([ + ...items, + { id: items.length + 1, name: "New Item", value: "" }, + ]); + }; + return ( +
+ +
+ ); }, }; diff --git a/yarn.lock b/yarn.lock index 77c823d..a61ab87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1132,6 +1132,7 @@ __metadata: "@storybook/test": "npm:^8.6.4" "@tailwindcss/postcss": "npm:^4.0.11" "@tailwindcss/vite": "npm:^4.1.4" + "@types/lodash": "npm:^4" "@types/node": "npm:^22.13.10" "@types/react": "npm:^18.0.9" "@types/react-dom": "npm:^18.0.3" @@ -1153,6 +1154,7 @@ __metadata: globals: "npm:^16.0.0" husky: "npm:^7.0.0" lint-staged: "npm:^12.1.2" + lodash: "npm:^4.17.21" prettier: "npm:^3.5.3" prettier-plugin-tailwindcss: "npm:^0.6.11" process: "npm:^0.11.10" @@ -4172,6 +4174,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4": + version: 4.17.17 + resolution: "@types/lodash@npm:4.17.17" + checksum: 10c0/8e75df02a15f04d4322c5a503e4efd0e7a92470570ce80f17e9f11ce2b1f1a7c994009c9bcff39f07e0f9ffd8ccaff09b3598997c404b801abd5a7eee5a639dc + languageName: node + linkType: hard + "@types/mdx@npm:^2.0.0": version: 2.0.13 resolution: "@types/mdx@npm:2.0.13" From b6a0fc11268ae8982e1ae44834ca69b7aa5d069f Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 5 Jun 2025 00:25:57 +0530 Subject: [PATCH 02/11] chore: update-documentation --- README.md | 21 +++++++++++++++------ src/lib/draggable-list/index.tsx | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c95b025..0ef71b4 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,20 @@ npm install @kleros/ui-components-library ### Setup -1. Import the CSS at the top level of your application: +1. Import the CSS: -```javascript -import "@kleros/ui-components-library/style.css"; -``` + a. For Non-tailwind apps, import the CSS at top level of your app. + + ```javascript + import "@kleros/ui-components-library/style.css"; + ``` + + b. For Tailwind apps, import the theme and mark the library as a source in your global.css file. + + ```css + @import "../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; + @source "../../../node_modules/@kleros/ui-components-library"; + ``` 2. Import and use components in your application: @@ -82,8 +91,8 @@ function MyComponent() { If you wish the use the library's tailwind theme variables in your tailwind app. You can utilize it by importing the theme file in your `global.css` file. ```css -@import tailwindcss @import - "../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; +@import tailwindcss; +@import "../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; ``` You can find the available theme variables [here](src/styles/theme.css). diff --git a/src/lib/draggable-list/index.tsx b/src/lib/draggable-list/index.tsx index b652186..2e14efa 100644 --- a/src/lib/draggable-list/index.tsx +++ b/src/lib/draggable-list/index.tsx @@ -101,6 +101,7 @@ function DraggableList({ {items.map((item) => { return ( From 483b499951afc79be4093138633489d3865b16c7 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 5 Jun 2025 12:09:43 +0530 Subject: [PATCH 03/11] chore: add-ids-accordion --- src/lib/accordion/accordion-item.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/accordion/accordion-item.tsx b/src/lib/accordion/accordion-item.tsx index f699275..7df4461 100644 --- a/src/lib/accordion/accordion-item.tsx +++ b/src/lib/accordion/accordion-item.tsx @@ -25,19 +25,24 @@ const AccordionItem: React.FC = ({ return (
= ({ "transition-[height] duration-(--klerosUIComponentsTransitionSpeed) ease-initial", )} > -
+
{body}
From 82d4aaa595bb241b33fdb7d5f65a18b5f6383861 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 5 Jun 2025 14:37:41 +0530 Subject: [PATCH 04/11] chore: add-aria-label-to-modal-dialog --- src/lib/container/modal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/container/modal.tsx b/src/lib/container/modal.tsx index 45e9a02..28b417b 100644 --- a/src/lib/container/modal.tsx +++ b/src/lib/container/modal.tsx @@ -14,6 +14,7 @@ interface ModalProps /** classname that applies to the modal overlay. */ modalOverlayClassname?: ModalOverlayProps["className"]; children?: DialogProps["children"]; + ariaLabel?: string; } /** A modal is an overlay element which blocks interaction with elements outside it. */ @@ -21,6 +22,7 @@ function Modal({ className, modalOverlayClassname, children, + ariaLabel, ...props }: Readonly) { return ( @@ -36,6 +38,7 @@ function Modal({ > Date: Thu, 5 Jun 2025 18:34:53 +0530 Subject: [PATCH 05/11] feat: field-error-messages --- src/lib/form/bignumber-field/index.tsx | 16 +++++++ .../bignumber-field/useBigNumberField.tsx | 47 +++++++++++++++++++ src/lib/form/number-field.tsx | 24 ++++++++++ src/lib/form/searchbar.tsx | 23 +++++++++ src/lib/form/text-area.tsx | 23 +++++++++ src/lib/form/text-field.tsx | 24 ++++++++++ src/stories/bignumber-field.stories.tsx | 32 +++++++++++++ src/stories/number-field.stories.tsx | 21 ++++++++- src/stories/searchbar.stories.tsx | 22 ++++++++- src/stories/text-area.stories.tsx | 21 ++++++++- src/stories/text-field.stories.tsx | 21 ++++++++- 11 files changed, 270 insertions(+), 4 deletions(-) diff --git a/src/lib/form/bignumber-field/index.tsx b/src/lib/form/bignumber-field/index.tsx index e0375ae..35e38f6 100644 --- a/src/lib/form/bignumber-field/index.tsx +++ b/src/lib/form/bignumber-field/index.tsx @@ -47,6 +47,7 @@ function BigNumberField({ groupProps, descriptionProps, errorMessageProps, + validationResult, } = useBigNumberField({ id, isDisabled, placeholder, isReadOnly, ...props }); return ( @@ -68,6 +69,8 @@ function BigNumberField({ <> @@ -181,6 +185,18 @@ function BigNumberField({ {message}
)} + {props.showFieldError && validationResult.isInvalid && ( + + {validationResult.validationError} + + )}
); } diff --git a/src/lib/form/bignumber-field/useBigNumberField.tsx b/src/lib/form/bignumber-field/useBigNumberField.tsx index 7c7b8c2..f8998e8 100644 --- a/src/lib/form/bignumber-field/useBigNumberField.tsx +++ b/src/lib/form/bignumber-field/useBigNumberField.tsx @@ -63,6 +63,15 @@ export interface BigNumberFieldProps { formatOptions?: FormatOptions; /** Additional props for the input element. */ inputProps?: React.InputHTMLAttributes; + /** A function that returns an error message if a given value is invalid. + * Return a string to denote invalid.*/ + validate?: (value: BigNumber | null) => true | null | undefined | string; + /** Flag to enable field errors, alternative to `message` + * This will show the validation errors from browser, or custom error in case `validate` is setup on Field. + */ + showFieldError?: boolean; + /** ClassName for field error message. */ + fieldErrorClassName?: string; } // Default format configuration @@ -124,6 +133,12 @@ export function useBigNumberField(props: BigNumberFieldProps) { // State to track if the input is currently formatted const [isFormatted, setIsFormatted] = useState(false); + // State to track input's validation + const [validationResult, setValidationResult] = useState<{ + isInvalid: boolean; + validationError?: string; + }>({ isInvalid: false }); + // State for the numeric value const [numberValue, setNumberValue] = useState(() => { try { @@ -231,6 +246,7 @@ export function useBigNumberField(props: BigNumberFieldProps) { setInputValue(newValue.toString()); setIsFormatted(false); onChange?.(newValue); + setValidationResult(getValidationResult(newValue)); }; const decrement = () => { @@ -252,6 +268,7 @@ export function useBigNumberField(props: BigNumberFieldProps) { setInputValue(newValue.toString()); setIsFormatted(false); onChange?.(newValue); + setValidationResult(getValidationResult(newValue)); }; // Helper function to escape special characters in regex @@ -415,6 +432,8 @@ export function useBigNumberField(props: BigNumberFieldProps) { } else if (inputValue !== "" && inputValue !== "-") { setInputValue(""); } + + setValidationResult(getValidationResult()); }; // Handle keyboard events @@ -552,6 +571,7 @@ export function useBigNumberField(props: BigNumberFieldProps) { readOnly: isReadOnly, required: props.isRequired, placeholder: props.placeholder, + "aria-invalid": validationResult?.isInvalid, ...getAriaAttributes(), ...props.inputProps, }); @@ -561,6 +581,32 @@ export function useBigNumberField(props: BigNumberFieldProps) { htmlFor: id, }); + // Field Error Render props + const getValidationResult = (value?: BigNumber) => { + const fieldErrorProps = { + isInvalid: false, + validationError: "", + }; + + if ( + props.isRequired && + (value ? value.toString() : inputValue).trim() === "" + ) { + fieldErrorProps.isInvalid = true; + fieldErrorProps.validationError = "Please fill out this field."; + } + + const validate = props.validate; + if (validate) { + const result = validate(value ?? numberValue); + if (typeof result === `string`) { + fieldErrorProps.isInvalid = true; + fieldErrorProps.validationError = result; + } + } + return fieldErrorProps; + }; + // Increment button props const getIncrementButtonProps = () => ({ type: "button" as const, @@ -603,6 +649,7 @@ export function useBigNumberField(props: BigNumberFieldProps) { groupProps: getGroupProps(), descriptionProps: getDescriptionProps(), errorMessageProps: getErrorMessageProps(), + validationResult, inputValue, numberValue, canIncrement: canIncrement(), diff --git a/src/lib/form/number-field.tsx b/src/lib/form/number-field.tsx index 3f38c84..e494abf 100644 --- a/src/lib/form/number-field.tsx +++ b/src/lib/form/number-field.tsx @@ -15,6 +15,8 @@ import { type NumberFieldProps as AriaNumberFieldProps, Label, Text, + type FieldErrorProps, + FieldError, } from "react-aria-components"; import { cn } from "../../utils"; import clsx from "clsx"; @@ -29,6 +31,14 @@ interface NumberFieldProps extends AriaNumberFieldProps { */ inputProps?: InputProps; label?: string; + /** Flag to enable field errors, alternative to `message` + * This will show the validation errors from browser, or custom error in case `validate` is setup on Field. + */ + showFieldError?: boolean; + /** Props for FieldError in case `showFieldError` is true. + * [See FieldErrorProps](https://react-spectrum.adobe.com/react-aria/NumberField.html#fielderror) + */ + fieldErrorProps?: FieldErrorProps; } /** A number field allows a user to enter a number, and increment or decrement the value using stepper buttons. */ @@ -41,6 +51,8 @@ function NumberField({ label, isDisabled, inputProps, + showFieldError, + fieldErrorProps, ...props }: Readonly) { return ( @@ -170,6 +182,18 @@ function NumberField({ {message} )} + + {showFieldError && ( + + {fieldErrorProps?.children} + + )} ); } diff --git a/src/lib/form/searchbar.tsx b/src/lib/form/searchbar.tsx index 1068958..957f31e 100644 --- a/src/lib/form/searchbar.tsx +++ b/src/lib/form/searchbar.tsx @@ -1,6 +1,8 @@ import React from "react"; import SearchIcon from "../../assets/svgs/form/search.svg"; import { + FieldError, + type FieldErrorProps, Group, Input, type InputProps, @@ -18,6 +20,14 @@ interface SearchbarProps extends SearchFieldProps { * [See InputProps](https://react-spectrum.adobe.com/react-aria/NumberField.html#input-1) */ inputProps?: InputProps; + /** Flag to enable field errors, alternative to `message` + * This will show the validation errors from browser, or custom error in case `validate` is setup on Field. + */ + showFieldError?: boolean; + /** Props for FieldError in case `showFieldError` is true. + * [See FieldErrorProps](https://react-spectrum.adobe.com/react-aria/SearchField.html#fielderror) + */ + fieldErrorProps?: FieldErrorProps; } /** A search field allows a user to enter and clear a search query. */ function Searchbar({ @@ -25,6 +35,8 @@ function Searchbar({ placeholder, inputProps, className, + showFieldError, + fieldErrorProps, ...props }: Readonly) { return ( @@ -57,6 +69,17 @@ function Searchbar({ )} /> + {showFieldError && ( + + {fieldErrorProps?.children} + + )} ); } diff --git a/src/lib/form/text-area.tsx b/src/lib/form/text-area.tsx index f22e103..eeaf80f 100644 --- a/src/lib/form/text-area.tsx +++ b/src/lib/form/text-area.tsx @@ -12,6 +12,8 @@ import { type TextFieldProps, type TextAreaProps as AriaTextAreaProps, Text, + type FieldErrorProps, + FieldError, } from "react-aria-components"; import { cn } from "../../utils"; @@ -28,6 +30,14 @@ interface TextAreaProps extends TextFieldProps { resizeX?: boolean; /** Allow resizing along y-axis */ resizeY?: boolean; + /** Flag to enable field errors, alternative to `message` + * This will show the validation errors from browser, or custom error in case `validate` is setup on Field. + */ + showFieldError?: boolean; + /** Props for FieldError in case `showFieldError` is true. + * [See FieldErrorProps](https://react-spectrum.adobe.com/react-aria/TextField.html#fielderror) + */ + fieldErrorProps?: FieldErrorProps; } /** TextArea components supports multiline input and can be configured to resize. */ @@ -39,6 +49,8 @@ function TextArea({ placeholder, resizeX = false, resizeY = false, + showFieldError, + fieldErrorProps, ...props }: Readonly) { return ( @@ -101,6 +113,17 @@ function TextArea({ {message} )} + {showFieldError && ( + + {fieldErrorProps?.children} + + )} ); } diff --git a/src/lib/form/text-field.tsx b/src/lib/form/text-field.tsx index 1a8fcb8..82e7145 100644 --- a/src/lib/form/text-field.tsx +++ b/src/lib/form/text-field.tsx @@ -12,6 +12,8 @@ import { type TextFieldProps as AriaTextFieldProps, Label, Group, + FieldError, + type FieldErrorProps, } from "react-aria-components"; import { cn } from "../../utils"; @@ -25,6 +27,14 @@ interface TextFieldProps extends AriaTextFieldProps { */ inputProps?: InputProps; label?: string; + /** Flag to enable field errors, alternative to `message` + * This will show the validation errors from browser, or custom error in case `validate` is setup on Field. + */ + showFieldError?: boolean; + /** Props for FieldError in case `showFieldError` is true. + * [See FieldErrorProps](https://react-spectrum.adobe.com/react-aria/TextField.html#fielderror) + */ + fieldErrorProps?: FieldErrorProps; } /** A text field allows a user to enter a plain text value with a keyboard. */ function TextField({ @@ -34,6 +44,8 @@ function TextField({ className, placeholder, label, + showFieldError, + fieldErrorProps, ...props }: Readonly) { return ( @@ -109,6 +121,18 @@ function TextField({ {message} )} + + {showFieldError && ( + + {fieldErrorProps?.children} + + )} ); } diff --git a/src/stories/bignumber-field.stories.tsx b/src/stories/bignumber-field.stories.tsx index 0a0d6ac..25e11ea 100644 --- a/src/stories/bignumber-field.stories.tsx +++ b/src/stories/bignumber-field.stories.tsx @@ -1,8 +1,10 @@ +import React from "react"; import { Meta, StoryObj } from "@storybook/react"; import BigNumberField from "../lib/form/bignumber-field"; import Telegram from "../assets/svgs/telegram.svg"; import BigNumber from "bignumber.js"; import { IPreviewArgs } from "./utils"; +import { Button, Form } from "../lib"; const meta: Meta = { title: "Form/BigNumberField", @@ -183,3 +185,33 @@ export const ReadOnly: Story = { defaultValue: "42", }, }; + +/** Make a field required. Optionally you can choose to show the validation error and customize their style. */ +export const Required: Story = { + args: { + ...Default.args, + isRequired: true, + }, + render: (args) => ( +
{ + e.preventDefault(); + }} + > + (value?.eq(0) ? "Zero not allowed" : null)} + /> +