From 076416818d425530b665384eb7e3f6dc86f1e460 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 15 Dec 2024 19:04:56 +0800 Subject: [PATCH 001/131] feat(number-field): init structure --- packages/components/number-field/README.md | 24 +++ packages/components/number-field/package.json | 69 +++++++ packages/components/number-field/src/index.ts | 10 + .../components/number-field/tsconfig.json | 10 + .../components/number-field/tsup.config.ts | 8 + pnpm-lock.yaml | 180 ++++++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 packages/components/number-field/README.md create mode 100644 packages/components/number-field/package.json create mode 100644 packages/components/number-field/src/index.ts create mode 100644 packages/components/number-field/tsconfig.json create mode 100644 packages/components/number-field/tsup.config.ts diff --git a/packages/components/number-field/README.md b/packages/components/number-field/README.md new file mode 100644 index 0000000000..f51276fc6d --- /dev/null +++ b/packages/components/number-field/README.md @@ -0,0 +1,24 @@ +# @nextui-org/number-field + +NumberField is a component that allows users to enter number. It can be used to get user inputs in forms, search fields, and more. + +Please refer to the [documentation](https://nextui.org/docs/components/number-field) for more information. + +## Installation + +```sh +yarn add @nextui-org/number-field +# or +npm i @nextui-org/number-field +``` + +## Contribution + +Yes please! See the +[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md) +for details. + +## License + +This project is licensed under the terms of the +[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE). diff --git a/packages/components/number-field/package.json b/packages/components/number-field/package.json new file mode 100644 index 0000000000..44fceaf445 --- /dev/null +++ b/packages/components/number-field/package.json @@ -0,0 +1,69 @@ +{ + "name": "@nextui-org/number-field", + "version": "2.0.0", + "description": "The input component is designed for capturing user input within a text field.", + "keywords": [ + "input", + "number", + "numeric input" + ], + "author": "WK Wong ", + "homepage": "https://nextui.org", + "license": "MIT", + "main": "src/index.ts", + "sideEffects": false, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nextui-org/nextui.git", + "directory": "packages/components/input" + }, + "bugs": { + "url": "https://github.com/nextui-org/nextui/issues" + }, + "scripts": { + "build": "tsup src --dts", + "build:fast": "tsup src", + "dev": "pnpm build:fast --watch", + "clean": "rimraf dist .turbo", + "typecheck": "tsc --noEmit", + "prepack": "clean-package", + "postpack": "clean-package restore" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.3.0-beta.0", + "@nextui-org/system": ">=2.3.0-beta.0" + }, + "dependencies": { + "@nextui-org/form": "workspace:*", + "@nextui-org/react-utils": "workspace:*", + "@nextui-org/shared-icons": "workspace:*", + "@nextui-org/shared-utils": "workspace:*", + "@nextui-org/use-safe-layout-effect": "workspace:*", + "@react-aria/focus": "3.18.4", + "@react-aria/interactions": "3.22.4", + "@react-aria/numberfield": "3.11.9", + "@react-aria/utils": "3.25.3", + "@react-stately/utils": "3.10.4", + "@react-stately/numberfield": "3.9.8", + "@react-types/shared": "3.25.0", + "@react-types/numberfield": "3.8.6", + "react-textarea-autosize": "^8.5.3" + }, + "devDependencies": { + "@nextui-org/system": "workspace:*", + "@nextui-org/theme": "workspace:*", + "clean-package": "2.2.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-hook-form": "^7.51.3" + }, + "clean-package": "../../../clean-package.config.json" +} diff --git a/packages/components/number-field/src/index.ts b/packages/components/number-field/src/index.ts new file mode 100644 index 0000000000..6b04f20825 --- /dev/null +++ b/packages/components/number-field/src/index.ts @@ -0,0 +1,10 @@ +import NumberField from "./number-field"; + +// export types +export type {NumberFieldProps} from "./number-field"; + +// export hooks +export {useNumberField} from "./use-number-field"; + +// export component +export {NumberField}; diff --git a/packages/components/number-field/tsconfig.json b/packages/components/number-field/tsconfig.json new file mode 100644 index 0000000000..1f783ade25 --- /dev/null +++ b/packages/components/number-field/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "tailwind-variants": ["../../../node_modules/tailwind-variants"] + } + }, + "include": ["src", "index.ts"] +} diff --git a/packages/components/number-field/tsup.config.ts b/packages/components/number-field/tsup.config.ts new file mode 100644 index 0000000000..3e2bcff6cc --- /dev/null +++ b/packages/components/number-field/tsup.config.ts @@ -0,0 +1,8 @@ +import {defineConfig} from "tsup"; + +export default defineConfig({ + clean: true, + target: "es2019", + format: ["cjs", "esm"], + banner: {js: '"use client";'}, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a37dc0a492..12a1c66305 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2190,6 +2190,70 @@ importers: specifier: 0.13.0 version: 0.13.0(react@18.2.0) + packages/components/number-field: + dependencies: + '@nextui-org/form': + specifier: workspace:* + version: link:../form + '@nextui-org/react-utils': + specifier: workspace:* + version: link:../../utilities/react-utils + '@nextui-org/shared-icons': + specifier: workspace:* + version: link:../../utilities/shared-icons + '@nextui-org/shared-utils': + specifier: workspace:* + version: link:../../utilities/shared-utils + '@nextui-org/use-safe-layout-effect': + specifier: workspace:* + version: link:../../hooks/use-safe-layout-effect + '@react-aria/focus': + specifier: 3.18.4 + version: 3.18.4(react@18.2.0) + '@react-aria/interactions': + specifier: 3.22.4 + version: 3.22.4(react@18.2.0) + '@react-aria/numberfield': + specifier: 3.11.9 + version: 3.11.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@react-aria/utils': + specifier: 3.25.3 + version: 3.25.3(react@18.2.0) + '@react-stately/numberfield': + specifier: 3.9.8 + version: 3.9.8(react@18.2.0) + '@react-stately/utils': + specifier: 3.10.4 + version: 3.10.4(react@18.2.0) + '@react-types/numberfield': + specifier: 3.8.6 + version: 3.8.6(react@18.2.0) + '@react-types/shared': + specifier: 3.25.0 + version: 3.25.0(react@18.2.0) + react-textarea-autosize: + specifier: ^8.5.3 + version: 8.5.5(@types/react@18.2.8)(react@18.2.0) + devDependencies: + '@nextui-org/system': + specifier: workspace:* + version: link:../../core/system + '@nextui-org/theme': + specifier: workspace:* + version: link:../../core/theme + clean-package: + specifier: 2.2.0 + version: 2.2.0 + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.51.3 + version: 7.53.2(react@18.2.0) + packages/components/pagination: dependencies: '@nextui-org/react-utils': @@ -6565,6 +6629,11 @@ packages: react: 18.2.0 react-dom: 18.2.0 + '@react-aria/focus@3.18.4': + resolution: {integrity: sha512-91J35077w9UNaMK1cpMUEFRkNNz0uZjnSwiyBCFuRdaVuivO53wNC9XtWSDNDdcO5cGy87vfJRVAiyoCn/mjqA==} + peerDependencies: + react: 18.2.0 + '@react-aria/focus@3.19.0': resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==} peerDependencies: @@ -6586,6 +6655,11 @@ packages: peerDependencies: react: 18.2.0 + '@react-aria/interactions@3.22.4': + resolution: {integrity: sha512-E0vsgtpItmknq/MJELqYJwib+YN18Qag8nroqwjk1qOnBa9ROIkUhWJerLi1qs5diXq9LHKehZDXRlwPvdEFww==} + peerDependencies: + react: 18.2.0 + '@react-aria/interactions@3.22.5': resolution: {integrity: sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==} peerDependencies: @@ -6616,6 +6690,12 @@ packages: react: 18.2.0 react-dom: 18.2.0 + '@react-aria/numberfield@3.11.9': + resolution: {integrity: sha512-3tiGPx2y4zyOV7PmdBASes99ZZsFTZAJTnU45Z+p1CW4131lw7y2ZhbojBl7U6DaXAJvi1z6zY6cq2UE9w5a0Q==} + peerDependencies: + react: 18.2.0 + react-dom: 18.2.0 + '@react-aria/overlays@3.24.0': resolution: {integrity: sha512-0kAXBsMNTc/a3M07tK9Cdt/ea8CxTAEJ223g8YgqImlmoBBYAL7dl5G01IOj67TM64uWPTmZrOklBchHWgEm3A==} peerDependencies: @@ -6692,6 +6772,11 @@ packages: peerDependencies: react: 18.2.0 + '@react-aria/utils@3.25.3': + resolution: {integrity: sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA==} + peerDependencies: + react: 18.2.0 + '@react-aria/utils@3.26.0': resolution: {integrity: sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==} peerDependencies: @@ -6779,6 +6864,11 @@ packages: peerDependencies: react: 18.2.0 + '@react-stately/numberfield@3.9.8': + resolution: {integrity: sha512-J6qGILxDNEtu7yvd3/y+FpbrxEaAeIODwlrFo6z1kvuDlLAm/KszXAc75yoDi0OtakFTCMP6/HR5VnHaQdMJ3w==} + peerDependencies: + react: 18.2.0 + '@react-stately/overlays@3.6.12': resolution: {integrity: sha512-QinvZhwZgj8obUyPIcyURSCjTZlqZYRRCS60TF8jH8ZpT0tEAuDb3wvhhSXuYA3Xo9EHLwvLjEf3tQKKdAQArw==} peerDependencies: @@ -6829,6 +6919,11 @@ packages: peerDependencies: react: 18.2.0 + '@react-stately/utils@3.10.4': + resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==} + peerDependencies: + react: 18.2.0 + '@react-stately/utils@3.10.5': resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==} peerDependencies: @@ -6904,6 +6999,16 @@ packages: peerDependencies: react: 18.2.0 + '@react-types/numberfield@3.8.6': + resolution: {integrity: sha512-VtWEMAXUO1S9EEZI8whc7xv6DVccxhbWsRthMCg/LxiwU3U5KAveadNc2c5rtXkRpd3cnD5xFzz3dExXdmHkAg==} + peerDependencies: + react: 18.2.0 + + '@react-types/numberfield@3.8.7': + resolution: {integrity: sha512-KccMPi39cLoVkB2T0V7HW6nsxQVAwt89WWCltPZJVGzsebv/k0xTQlPVAgrUake4kDLoE687e3Fr/Oe3+1bDhw==} + peerDependencies: + react: 18.2.0 + '@react-types/overlays@3.8.11': resolution: {integrity: sha512-aw7T0rwVI3EuyG5AOaEIk8j7dZJQ9m34XAztXJVZ/W2+4pDDkLDbJ/EAPnuo2xGYRGhowuNDn4tDju01eHYi+w==} peerDependencies: @@ -6924,6 +7029,11 @@ packages: peerDependencies: react: 18.2.0 + '@react-types/shared@3.25.0': + resolution: {integrity: sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ==} + peerDependencies: + react: 18.2.0 + '@react-types/shared@3.26.0': resolution: {integrity: sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==} peerDependencies: @@ -18343,6 +18453,15 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + '@react-aria/focus@3.18.4(react@18.2.0)': + dependencies: + '@react-aria/interactions': 3.22.5(react@18.2.0) + '@react-aria/utils': 3.26.0(react@18.2.0) + '@react-types/shared': 3.26.0(react@18.2.0) + '@swc/helpers': 0.5.15 + clsx: 2.1.1 + react: 18.2.0 + '@react-aria/focus@3.19.0(react@18.2.0)': dependencies: '@react-aria/interactions': 3.22.5(react@18.2.0) @@ -18391,6 +18510,14 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 + '@react-aria/interactions@3.22.4(react@18.2.0)': + dependencies: + '@react-aria/ssr': 3.9.7(react@18.2.0) + '@react-aria/utils': 3.26.0(react@18.2.0) + '@react-types/shared': 3.26.0(react@18.2.0) + '@swc/helpers': 0.5.15 + react: 18.2.0 + '@react-aria/interactions@3.22.5(react@18.2.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.2.0) @@ -18453,6 +18580,22 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + '@react-aria/numberfield@3.11.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-aria/i18n': 3.12.4(react@18.2.0) + '@react-aria/interactions': 3.22.5(react@18.2.0) + '@react-aria/spinbutton': 3.6.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@react-aria/textfield': 3.15.0(react@18.2.0) + '@react-aria/utils': 3.26.0(react@18.2.0) + '@react-stately/form': 3.1.0(react@18.2.0) + '@react-stately/numberfield': 3.9.8(react@18.2.0) + '@react-types/button': 3.10.1(react@18.2.0) + '@react-types/numberfield': 3.8.7(react@18.2.0) + '@react-types/shared': 3.26.0(react@18.2.0) + '@swc/helpers': 0.5.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + '@react-aria/overlays@3.24.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@react-aria/focus': 3.19.0(react@18.2.0) @@ -18632,6 +18775,15 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 + '@react-aria/utils@3.25.3(react@18.2.0)': + dependencies: + '@react-aria/ssr': 3.9.7(react@18.2.0) + '@react-stately/utils': 3.10.5(react@18.2.0) + '@react-types/shared': 3.26.0(react@18.2.0) + '@swc/helpers': 0.5.15 + clsx: 2.1.1 + react: 18.2.0 + '@react-aria/utils@3.26.0(react@18.2.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.2.0) @@ -18790,6 +18942,15 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 + '@react-stately/numberfield@3.9.8(react@18.2.0)': + dependencies: + '@internationalized/number': 3.6.0 + '@react-stately/form': 3.1.0(react@18.2.0) + '@react-stately/utils': 3.10.5(react@18.2.0) + '@react-types/numberfield': 3.8.7(react@18.2.0) + '@swc/helpers': 0.5.15 + react: 18.2.0 + '@react-stately/overlays@3.6.12(react@18.2.0)': dependencies: '@react-stately/utils': 3.10.5(react@18.2.0) @@ -18877,6 +19038,11 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 + '@react-stately/utils@3.10.4(react@18.2.0)': + dependencies: + '@swc/helpers': 0.5.15 + react: 18.2.0 + '@react-stately/utils@3.10.5(react@18.2.0)': dependencies: '@swc/helpers': 0.5.15 @@ -18961,6 +19127,16 @@ snapshots: '@react-types/shared': 3.26.0(react@18.2.0) react: 18.2.0 + '@react-types/numberfield@3.8.6(react@18.2.0)': + dependencies: + '@react-types/shared': 3.26.0(react@18.2.0) + react: 18.2.0 + + '@react-types/numberfield@3.8.7(react@18.2.0)': + dependencies: + '@react-types/shared': 3.26.0(react@18.2.0) + react: 18.2.0 + '@react-types/overlays@3.8.11(react@18.2.0)': dependencies: '@react-types/shared': 3.26.0(react@18.2.0) @@ -18981,6 +19157,10 @@ snapshots: '@react-types/shared': 3.26.0(react@18.2.0) react: 18.2.0 + '@react-types/shared@3.25.0(react@18.2.0)': + dependencies: + react: 18.2.0 + '@react-types/shared@3.26.0(react@18.2.0)': dependencies: react: 18.2.0 From 0bfd74daaea55bbdf6f3526a95381ff78249fc9a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 15:06:44 +0800 Subject: [PATCH 002/131] feat(deps): add `@nextui-org/button` & `@react-types/button` --- packages/components/number-field/package.json | 2 ++ pnpm-lock.yaml | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/packages/components/number-field/package.json b/packages/components/number-field/package.json index 44fceaf445..1ab6800d78 100644 --- a/packages/components/number-field/package.json +++ b/packages/components/number-field/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@nextui-org/form": "workspace:*", + "@nextui-org/button": "workspace:*", "@nextui-org/react-utils": "workspace:*", "@nextui-org/shared-icons": "workspace:*", "@nextui-org/shared-utils": "workspace:*", @@ -55,6 +56,7 @@ "@react-stately/numberfield": "3.9.8", "@react-types/shared": "3.25.0", "@react-types/numberfield": "3.8.6", + "@react-types/button": "3.10.1", "react-textarea-autosize": "^8.5.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12a1c66305..85f4f4127b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2192,6 +2192,9 @@ importers: packages/components/number-field: dependencies: + '@nextui-org/button': + specifier: workspace:* + version: link:../button '@nextui-org/form': specifier: workspace:* version: link:../form @@ -2225,6 +2228,9 @@ importers: '@react-stately/utils': specifier: 3.10.4 version: 3.10.4(react@18.2.0) + '@react-types/button': + specifier: 3.10.1 + version: 3.10.1(react@18.2.0) '@react-types/numberfield': specifier: 3.8.6 version: 3.8.6(react@18.2.0) From 914783fdfb2886c542c754e3e6d25f2cf3270b66 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 15:07:03 +0800 Subject: [PATCH 003/131] feat(theme): export number-field --- packages/core/theme/src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/theme/src/components/index.ts b/packages/core/theme/src/components/index.ts index dee45f4401..d039745fbb 100644 --- a/packages/core/theme/src/components/index.ts +++ b/packages/core/theme/src/components/index.ts @@ -41,3 +41,4 @@ export * from "./date-picker"; export * from "./alert"; export * from "./drawer"; export * from "./form"; +export * from "./number-field"; From 18d713998467065d3b9d8568e1ec2b515c4a982c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 15:08:48 +0800 Subject: [PATCH 004/131] feat(number-field): storybook init structure --- .../stories/number-field.stories.tsx | 630 ++++++++++++++++++ 1 file changed, 630 insertions(+) create mode 100644 packages/components/number-field/stories/number-field.stories.tsx diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx new file mode 100644 index 0000000000..78f3b226dc --- /dev/null +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -0,0 +1,630 @@ +/* eslint-disable jsx-a11y/interactive-supports-focus */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import type {ValidationResult} from "@react-types/shared"; + +import React from "react"; +import {Meta} from "@storybook/react"; +import {button} from "@nextui-org/theme"; +import {Form} from "@nextui-org/form"; + +import {numberField} from "../../../core/theme"; +import {NumberField, NumberFieldProps} from "../src"; + +export default { + title: "Components/NumberField", + component: NumberField, + argTypes: { + variant: { + control: { + type: "select", + }, + options: ["flat", "faded", "bordered", "underlined"], + }, + color: { + control: { + type: "select", + }, + options: ["default", "primary", "secondary", "success", "warning", "danger"], + }, + radius: { + control: { + type: "select", + }, + options: ["none", "sm", "md", "lg", "full"], + }, + size: { + control: { + type: "select", + }, + options: ["sm", "md", "lg"], + }, + labelPlacement: { + control: { + type: "select", + }, + options: ["inside", "outside", "outside-left"], + }, + isDisabled: { + control: { + type: "boolean", + }, + }, + validationBehavior: { + control: { + type: "select", + }, + options: ["aria", "native"], + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} as Meta; + +const defaultProps = { + ...numberField.defaultVariants, + defaultValue: 1024, + label: "Width", +}; + +const Template = (args) => ( +
+ +
+); + +const FormTemplate = (args) => ( +
{ + alert(`Submitted value: ${e.target["example"].value}`); + e.preventDefault(); + }} + > + + + +); + +const ControlledTemplate = (args) => { + const [value, setValue] = React.useState(0); + + return ( +
+ +

NumberField value: {value}

+
+ ); +}; + +const LabelPlacementTemplate = (args) => ( +
+
+

Without placeholder

+
+ + + +
+
+
+

With placeholder

+
+ + + +
+
+
+); + +const StartContentTemplate = (args) => ( +
+ + $ +
+ } + /> + +); + +const EndContentTemplate = (args) => ( +
+ + +
+ } + label="Price" + placeholder="0.00" + /> + +); + +const StartAndEndContentTemplate = (args) => ( +
+ + + +
+ } + label="Price" + placeholder="0.00" + startContent={ +
+ $ +
+ } + /> + +); + +const CustomWithClassNamesTemplate = (args) => ( +
+
} + labelPlacement="outside" + placeholder="Enter the amount" + /> + +); + +// const CustomWithHooksTemplate = (args: NumberFieldProps) => { +// const { +// Component, +// label, +// domRef, +// description, +// isClearable, +// startContent, +// endContent, +// shouldLabelBeOutside, +// shouldLabelBeInside, +// errorMessage, +// getBaseProps, +// getLabelProps, +// getNumberFieldProps, +// getInnerWrapperProps, +// getNumberFieldWrapperProps, +// getDescriptionProps, +// getErrorMessageProps, +// getClearButtonProps, +// } = useNumberField({ +// ...args, +// classNames: { +// label: "text-black/50 dark:text-white/90", +// input: [ +// "bg-transparent", +// "text-black/90 dark:text-white/90", +// "placeholder:text-default-700/50 dark:placeholder:text-white/60", +// ], +// innerWrapper: "bg-transparent", +// inputWrapper: [ +// "shadow-xl", +// "bg-default-200/50", +// "dark:bg-default/60", +// "backdrop-blur-xl", +// "backdrop-saturate-200", +// "hover:bg-default-200/70", +// "focus-within:!bg-default-200/50", +// "dark:hover:bg-default/70", +// "dark:focus-within:!bg-default/60", +// "!cursor-text", +// ], +// }, +// }); + +// const labelContent = ; + +// const end = React.useMemo(() => { +// if (isClearable) { +// return {endContent || }; +// } + +// return endContent; +// }, [isClearable, getClearButtonProps]); + +// const innerWrapper = React.useMemo(() => { +// if (startContent || end) { +// return ( +//
+// {startContent} +// +// {end} +//
+// ); +// } + +// return ; +// }, [startContent, end, getNumberFieldProps, getInnerWrapperProps]); + +// return ( +//
+// +// {shouldLabelBeOutside ? labelContent : null} +//
{ +// domRef.current?.focus(); +// }} +// > +// {shouldLabelBeInside ? labelContent : null} +// {innerWrapper} +//
+// {description &&
{description}
} +// {errorMessage &&
{errorMessage}
} +//
+//
+// ); +// }; + +// const WithReactHookFormTemplate = (args: NumberFieldProps) => { +// const { +// register, +// formState: {errors}, +// handleSubmit, +// } = useForm({ +// defaultValues: { +// withDefaultValue: 1024, +// withoutDefaultValue: "", +// requiredField: "", +// }, +// }); + +// const onSubmit = (data: any) => { +// // eslint-disable-next-line no-console +// console.log(data); +// alert("Submitted value: " + JSON.stringify(data)); +// }; + +// return ( +//
+// +// +// +// {errors.requiredField && This field is required} +// +// +// ); +// }; + +const ServerValidationTemplate = (args: NumberFieldProps) => { + const [serverErrors, setServerErrors] = React.useState({}); + const onSubmit = (e) => { + e.preventDefault(); + setServerErrors({ + width: "Please provide a valid number.", + }); + }; + + return ( +
+ + + + ); +}; + +export const Default = { + render: Template, + + args: { + ...defaultProps, + }, +}; + +export const Required = { + render: FormTemplate, + + args: { + ...defaultProps, + isRequired: true, + }, +}; + +export const Disabled = { + render: Template, + + args: { + ...defaultProps, + variant: "faded", + isDisabled: true, + }, +}; + +export const ReadOnly = { + render: Template, + + args: { + ...defaultProps, + variant: "bordered", + isReadOnly: true, + }, +}; + +export const WithoutLabel = { + render: Template, + + args: { + ...defaultProps, + label: null, + "aria-label": "Email", + placeholder: "Enter a number", + }, +}; + +export const WithDescription = { + render: Template, + + args: { + ...defaultProps, + description: "Enter a number", + }, +}; + +export const WithFormatOptions = { + render: Template, + + args: { + ...defaultProps, + label: "Transaction amount", + formatOptions: { + style: "currency", + currency: "USD", + }, + }, +}; + +export const LabelPlacement = { + render: LabelPlacementTemplate, + + args: { + ...defaultProps, + }, +}; + +export const Clearable = { + render: Template, + + args: { + ...defaultProps, + variant: "bordered", + placeholder: "Enter a number", + // eslint-disable-next-line no-console + onClear: () => console.log("input cleared"), + }, +}; + +export const StartContent = { + render: StartContentTemplate, + + args: { + ...defaultProps, + variant: "bordered", + labelPlacement: "outside", + }, +}; + +export const EndContent = { + render: EndContentTemplate, + + args: { + ...defaultProps, + variant: "bordered", + labelPlacement: "outside", + }, +}; + +export const StartAndEndContent = { + render: StartAndEndContentTemplate, + + args: { + ...defaultProps, + variant: "bordered", + labelPlacement: "outside", + }, +}; + +export const WithErrorMessage = { + render: Template, + + args: { + ...defaultProps, + isInvalid: true, + errorMessage: "Please enter a valid number", + }, +}; + +export const WithErrorMessageFunction = { + render: FormTemplate, + + args: { + ...defaultProps, + min: "0", + max: "100", + type: "number", + isRequired: true, + label: "Number", + placeholder: "Enter a number(0-100)", + errorMessage: (value: ValidationResult) => { + if (value.validationDetails.rangeOverflow) { + return "Value is too high"; + } + if (value.validationDetails.rangeUnderflow) { + return "Value is too low"; + } + if (value.validationDetails.valueMissing) { + return "Value is required"; + } + }, + }, +}; + +export const WithValidation = { + render: FormTemplate, + + args: { + ...defaultProps, + validate: (value) => { + if (value < 0 || value > 100) { + return "Value must be between 0 and 100"; + } + }, + isRequired: true, + label: "Number", + placeholder: "Enter a number(0-100)", + }, +}; + +export const WithServerValidation = { + render: ServerValidationTemplate, + + args: { + ...defaultProps, + }, +}; + +export const IsInvalid = { + render: Template, + + args: { + ...defaultProps, + variant: "bordered", + isInvalid: true, + placeholder: "Enter a number", + errorMessage: "Please enter a valid range of numbers", + }, +}; + +export const Controlled = { + render: ControlledTemplate, + + args: { + ...defaultProps, + variant: "bordered", + }, +}; + +export const MinValue = { + render: Template, + + args: { + ...defaultProps, + label: "Enter a number (min value: 0)", + minValue: 0, + }, +}; + +export const MaxValue = { + render: Template, + + args: { + ...defaultProps, + label: "Enter a number (max value: 100)", + defaultValue: 0, + maxValue: 100, + }, +}; + +export const CustomWithClassNames = { + render: CustomWithClassNamesTemplate, + + args: { + ...defaultProps, + }, +}; + +// export const CustomWithHooks = { +// render: CustomWithHooksTemplate, + +// args: { +// ...defaultProps, +// label: "Search", +// type: "search", +// placeholder: "Type to search...", +// startContent: ( +// +// ), +// }, +// }; + +// export const WithReactHookForm = { +// render: WithReactHookFormTemplate, + +// args: { +// ...defaultProps, +// }, +// }; From 4dff6f14452d0d1ee26eed9a6453a65dc8ccdb67 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 15:09:55 +0800 Subject: [PATCH 005/131] feat(number-field): add NumberFieldHorizontalStepper --- .../src/number-field-vertical-stepper.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/components/number-field/src/number-field-vertical-stepper.tsx diff --git a/packages/components/number-field/src/number-field-vertical-stepper.tsx b/packages/components/number-field/src/number-field-vertical-stepper.tsx new file mode 100644 index 0000000000..f2eb92601c --- /dev/null +++ b/packages/components/number-field/src/number-field-vertical-stepper.tsx @@ -0,0 +1,52 @@ +import type {AriaButtonProps} from "@react-types/button"; + +import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; +import {Button} from "@nextui-org/button"; + +export interface Props extends Omit, keyof AriaButtonProps> { + direction: "up" | "down"; +} + +export type NumberFieldVerticalStepperProps = Props & AriaButtonProps; + +const StepperUpIcon = () => ( + + + +); + +const StepperDownIcon = () => ( + + + +); + +const NumberFieldVerticalStepper = forwardRef<"button", NumberFieldVerticalStepperProps>( + (props) => { + const {direction, ...otherProps} = props; + + return ( + + ); + }, +); + +NumberFieldVerticalStepper.displayName = "NextUI.NumberFieldVerticalStepper"; + +export default NumberFieldVerticalStepper; From d3ba207f05c77ad655a09bae1d9f6b6ada12c949 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 15:14:57 +0800 Subject: [PATCH 006/131] feat(number-field): add NumberFieldHorizontalStepper --- .../src/number-field-horiztonal-stepper.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/components/number-field/src/number-field-horiztonal-stepper.tsx diff --git a/packages/components/number-field/src/number-field-horiztonal-stepper.tsx b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx new file mode 100644 index 0000000000..c868a79754 --- /dev/null +++ b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx @@ -0,0 +1,52 @@ +import type {AriaButtonProps} from "@react-types/button"; + +import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; +import {Button} from "@nextui-org/button"; + +export interface Props extends Omit, keyof AriaButtonProps> { + direction: "left" | "right"; +} + +export type NumberFieldHorizontalStepperProps = Props & AriaButtonProps; + +const StepperLeftIcon = () => ( + + + +); + +const StepperRightIcon = () => ( + + + +); + +const NumberFieldHorizontalStepper = forwardRef<"button", NumberFieldHorizontalStepperProps>( + (props) => { + const {direction, ...otherProps} = props; + + return ( + + ); + }, +); + +NumberFieldHorizontalStepper.displayName = "NextUI.NumberFieldHorizontalStepper"; + +export default NumberFieldHorizontalStepper; From e9b3049bd55aadb01e812f36f61c2b969520aeba Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 23:12:03 +0800 Subject: [PATCH 007/131] feat(theme): init number field theme --- .../core/theme/src/components/number-field.ts | 600 ++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 packages/core/theme/src/components/number-field.ts diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts new file mode 100644 index 0000000000..a500e85054 --- /dev/null +++ b/packages/core/theme/src/components/number-field.ts @@ -0,0 +1,600 @@ +import type {VariantProps} from "tailwind-variants"; + +import {tv} from "../utils/tv"; +import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; + +/** + * NumberField wrapper **Tailwind Variants** component + * + * @example + * ```js + * const {base, label, inputWrapper, input, clearButton, description, errorMessage} = numberField({...}) + * + *
+ * + *
+ * + * + *
+ * Description + * Invalid input + *
+ * ``` + */ +const numberField = tv({ + slots: { + base: "group flex flex-col data-[hidden=true]:hidden", + label: [ + "absolute", + "z-10", + "pointer-events-none", + "origin-top-left", + "flex-shrink-0", + // Using RTL here as Tailwind CSS doesn't support `start` and `end` logical properties for transforms yet. + "rtl:origin-top-right", + "subpixel-antialiased", + "block", + "text-small", + "text-foreground-500", + ], + mainWrapper: "h-full", + inputWrapper: + "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-4 py-3 gap-3", + innerWrapper: "inline-flex w-full items-center h-full box-border", + input: [ + "w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none", + "data-[has-start-content=true]:ps-1.5", + "data-[has-end-content=true]:pe-1.5", + "autofill:bg-transparent bg-clip-text", + ], + clearButton: [ + "p-2", + "-m-2", + "z-10", + "absolute", + "end-3", + "start-auto", + "pointer-events-none", + "appearance-none", + "outline-none", + "select-none", + "opacity-0", + "hover:!opacity-100", + "cursor-pointer", + "active:!opacity-70", + "rounded-full", + // focus ring + ...dataFocusVisibleClasses, + ], + helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", + description: "text-tiny text-foreground-400", + errorMessage: "text-tiny text-danger", + }, + variants: { + variant: { + flat: { + inputWrapper: [ + "bg-default-100", + "data-[hover=true]:bg-default-50", + "group-data-[focus=true]:bg-default-100", + ], + }, + faded: { + inputWrapper: [ + "bg-default-100", + "border-medium", + "border-default-200", + "data-[hover=true]:border-default-400 focus-within:border-default-400", + ], + value: "group-data-[has-value=true]:text-default-foreground", + }, + bordered: { + inputWrapper: [ + "border-medium", + "border-default-200", + "data-[hover=true]:border-default-400", + "group-data-[focus=true]:border-default-foreground", + ], + }, + underlined: { + inputWrapper: [ + "!px-1", + "!pb-0", + "!gap-0", + "relative", + "box-border", + "border-b-medium", + "shadow-[0_1px_0px_0_rgba(0,0,0,0.05)]", + "border-default-200", + "!rounded-none", + "hover:border-default-300", + "after:content-['']", + "after:w-0", + "after:origin-center", + "after:bg-default-foreground", + "after:absolute", + "after:left-1/2", + "after:-translate-x-1/2", + "after:-bottom-[2px]", + "after:h-[2px]", + "group-data-[focus=true]:after:w-full", + ], + innerWrapper: "pb-1", + label: "group-data-[filled-within=true]:text-foreground", + }, + }, + color: { + default: {}, + primary: {}, + secondary: {}, + success: {}, + warning: {}, + danger: {}, + }, + size: { + sm: { + label: "text-tiny", + inputWrapper: "h-8 min-h-8 px-2 rounded-small", + input: "text-small", + clearButton: "text-medium", + }, + md: { + inputWrapper: "h-10 min-h-10 rounded-medium", + input: "text-small", + clearButton: "text-large", + }, + lg: { + label: "text-medium", + inputWrapper: "h-12 min-h-12 rounded-large", + input: "text-medium", + clearButton: "text-large", + }, + }, + radius: { + none: { + inputWrapper: "rounded-none", + }, + sm: { + inputWrapper: "rounded-small", + }, + md: { + inputWrapper: "rounded-medium", + }, + lg: { + inputWrapper: "rounded-large", + }, + full: { + inputWrapper: "rounded-full", + }, + }, + labelPlacement: { + outside: { + mainWrapper: "flex flex-col", + }, + "outside-left": { + base: "flex-row items-center flex-nowrap data-[has-helper=true]:items-start", + inputWrapper: "flex-1", + mainWrapper: "flex flex-col", + label: "relative text-foreground pe-2 ps-2 pointer-events-auto", + }, + }, + fullWidth: { + true: { + base: "w-full", + }, + false: {}, + }, + isClearable: { + true: { + input: "peer pe-6 input-search-cancel-button-none", + clearButton: [ + "peer-data-[filled=true]:pointer-events-auto", + "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block", + "peer-data-[filled=true]:scale-100", + ], + }, + }, + isDisabled: { + true: { + base: "opacity-disabled pointer-events-none", + inputWrapper: "pointer-events-none", + label: "pointer-events-none", + }, + }, + isInvalid: { + true: { + label: "!text-danger", + input: "!placeholder:text-danger !text-danger", + }, + }, + isRequired: { + true: { + label: "after:content-['*'] after:text-danger after:ms-0.5", + }, + }, + disableAnimation: { + true: { + input: "transition-none", + inputWrapper: "transition-none", + label: "transition-none", + }, + false: { + inputWrapper: "transition-background motion-reduce:transition-none !duration-150", + label: [ + "will-change-auto", + "!duration-200", + "!ease-out", + "motion-reduce:transition-none", + "transition-[transform,color,left,opacity]", + ], + clearButton: [ + "scale-90", + "ease-out", + "duration-150", + "transition-[opacity,transform]", + "motion-reduce:transition-none", + "motion-reduce:scale-100", + ], + }, + }, + }, + defaultVariants: { + variant: "flat", + color: "default", + size: "md", + fullWidth: true, + labelPlacement: "outside", + isDisabled: false, + }, + compoundVariants: [ + // flat & color + { + variant: "flat", + color: "default", + class: { + input: "group-data-[has-value=true]:text-default-foreground", + }, + }, + { + variant: "flat", + color: "primary", + class: { + inputWrapper: [ + "bg-primary-100", + "data-[hover=true]:bg-primary-50", + "text-primary", + "group-data-[focus=true]:bg-primary-50", + "placeholder:text-primary", + ], + input: "placeholder:text-primary", + label: "text-primary", + }, + }, + { + variant: "flat", + color: "secondary", + class: { + inputWrapper: [ + "bg-secondary-100", + "text-secondary", + "data-[hover=true]:bg-secondary-50", + "group-data-[focus=true]:bg-secondary-50", + "placeholder:text-secondary", + ], + input: "placeholder:text-secondary", + label: "text-secondary", + }, + }, + { + variant: "flat", + color: "success", + class: { + inputWrapper: [ + "bg-success-100", + "text-success-600", + "dark:text-success", + "placeholder:text-success-600", + "dark:placeholder:text-success", + "data-[hover=true]:bg-success-50", + "group-data-[focus=true]:bg-success-50", + ], + input: "placeholder:text-success-600 dark:placeholder:text-success", + label: "text-success-600 dark:text-success", + }, + }, + { + variant: "flat", + color: "warning", + class: { + inputWrapper: [ + "bg-warning-100", + "text-warning-600", + "dark:text-warning", + "placeholder:text-warning-600", + "dark:placeholder:text-warning", + "data-[hover=true]:bg-warning-50", + "group-data-[focus=true]:bg-warning-50", + ], + input: "placeholder:text-warning-600 dark:placeholder:text-warning", + label: "text-warning-600 dark:text-warning", + }, + }, + { + variant: "flat", + color: "danger", + class: { + inputWrapper: [ + "bg-danger-100", + "text-danger", + "dark:text-danger-500", + "placeholder:text-danger", + "dark:placeholder:text-danger-500", + "data-[hover=true]:bg-danger-50", + "group-data-[focus=true]:bg-danger-50", + ], + input: "placeholder:text-danger dark:placeholder:text-danger-500", + label: "text-danger dark:text-danger-500", + }, + }, + // faded & color + { + variant: "faded", + color: "primary", + class: { + label: "text-primary", + inputWrapper: "data-[hover=true]:border-primary focus-within:border-primary", + }, + }, + { + variant: "faded", + color: "secondary", + class: { + label: "text-secondary", + inputWrapper: "data-[hover=true]:border-secondary focus-within:border-secondary", + }, + }, + { + variant: "faded", + color: "success", + class: { + label: "text-success", + inputWrapper: "data-[hover=true]:border-success focus-within:border-success", + }, + }, + { + variant: "faded", + color: "warning", + class: { + label: "text-warning", + inputWrapper: "data-[hover=true]:border-warning focus-within:border-warning", + }, + }, + { + variant: "faded", + color: "danger", + class: { + label: "text-danger", + inputWrapper: "data-[hover=true]:border-danger focus-within:border-danger", + }, + }, + // underlined & color + { + variant: "underlined", + color: "default", + class: { + input: "group-data-[has-value=true]:text-foreground", + }, + }, + { + variant: "underlined", + color: "primary", + class: { + inputWrapper: "after:bg-primary", + label: "text-primary", + }, + }, + { + variant: "underlined", + color: "secondary", + class: { + inputWrapper: "after:bg-secondary", + label: "text-secondary", + }, + }, + { + variant: "underlined", + color: "success", + class: { + inputWrapper: "after:bg-success", + label: "text-success", + }, + }, + { + variant: "underlined", + color: "warning", + class: { + inputWrapper: "after:bg-warning", + label: "text-warning", + }, + }, + { + variant: "underlined", + color: "danger", + class: { + inputWrapper: "after:bg-danger", + label: "text-danger", + }, + }, + // bordered & color + { + variant: "bordered", + color: "primary", + class: { + inputWrapper: "group-data-[focus=true]:border-primary", + label: "text-primary", + }, + }, + { + variant: "bordered", + color: "secondary", + class: { + inputWrapper: "group-data-[focus=true]:border-secondary", + label: "text-secondary", + }, + }, + { + variant: "bordered", + color: "success", + class: { + inputWrapper: "group-data-[focus=true]:border-success", + label: "text-success", + }, + }, + { + variant: "bordered", + color: "warning", + class: { + inputWrapper: "group-data-[focus=true]:border-warning", + label: "text-warning", + }, + }, + { + variant: "bordered", + color: "danger", + class: { + inputWrapper: "group-data-[focus=true]:border-danger", + label: "text-danger", + }, + }, + // labelPlacement=outside & default + { + labelPlacement: "outside", + color: "default", + class: { + label: "group-data-[filled-within=true]:text-foreground", + }, + }, + // radius-full & size + { + radius: "full", + size: ["sm"], + class: { + inputWrapper: "px-3", + }, + }, + { + radius: "full", + size: "md", + class: { + inputWrapper: "px-4", + }, + }, + { + radius: "full", + size: "lg", + class: { + inputWrapper: "px-5", + }, + }, + // !disableAnimation & variant + { + disableAnimation: false, + variant: ["faded", "bordered"], + class: { + inputWrapper: "transition-colors motion-reduce:transition-none", + }, + }, + { + disableAnimation: false, + variant: "underlined", + class: { + inputWrapper: "after:transition-width motion-reduce:after:transition-none", + }, + }, + // flat & faded + { + variant: ["flat", "faded"], + class: { + inputWrapper: [ + // focus ring + ...groupDataFocusVisibleClasses, + ], + }, + }, + // isInvalid & variant + { + isInvalid: true, + variant: "flat", + class: { + inputWrapper: [ + "!bg-danger-50", + "data-[hover=true]:!bg-danger-100", + "group-data-[focus=true]:!bg-danger-50", + ], + }, + }, + { + isInvalid: true, + variant: "bordered", + class: { + inputWrapper: "!border-danger group-data-[focus=true]:!border-danger", + }, + }, + { + isInvalid: true, + variant: "underlined", + class: { + inputWrapper: "after:!bg-danger", + }, + }, + // labelPlacement=[inside,outside] + { + labelPlacement: ["outside"], + class: { + label: ["group-data-[filled-within=true]:pointer-events-auto"], + }, + }, + // variant=underlined & size + { + variant: "underlined", + size: "sm", + class: { + innerWrapper: "pb-1", + }, + }, + { + variant: "underlined", + size: ["md", "lg"], + class: { + innerWrapper: "pb-1.5", + }, + }, + // outside-left & size & hasHelper + { + labelPlacement: "outside-left", + size: "sm", + class: { + label: "group-data-[has-helper=true]:pt-2", + }, + }, + { + labelPlacement: "outside-left", + size: "md", + class: { + label: "group-data-[has-helper=true]:pt-3", + }, + }, + { + labelPlacement: "outside-left", + size: "lg", + class: { + label: "group-data-[has-helper=true]:pt-4", + }, + }, + ], +}); + +export type NumberFieldVariantProps = VariantProps; +export type NumberFieldSlots = keyof ReturnType; + +export {numberField}; From a8a7a771db9a9783f707d48c2f3747d9f9040999 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 23:12:53 +0800 Subject: [PATCH 008/131] feat(number-field): number-field draft --- .../number-field/src/number-field.tsx | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 packages/components/number-field/src/number-field.tsx diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx new file mode 100644 index 0000000000..9d089e9f9f --- /dev/null +++ b/packages/components/number-field/src/number-field.tsx @@ -0,0 +1,146 @@ +import {CloseFilledIcon} from "@nextui-org/shared-icons"; +import {useMemo} from "react"; +import {forwardRef} from "@nextui-org/system"; + +import {UseNumberFieldProps, useNumberField} from "./use-number-field"; +import NumberFieldVerticalStepper from "./number-field-vertical-stepper"; +import NumberFieldHorizontalStepper from "./number-field-horiztonal-stepper"; + +export interface NumberFieldProps extends UseNumberFieldProps {} + +const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { + const { + Component, + label, + description, + isClearable, + startContent, + endContent, + labelPlacement, + hasHelper, + isOutsideLeft, + shouldLabelBeOutside, + errorMessage, + isInvalid, + hideStepper, + steps, + getBaseProps, + getLabelProps, + getNumberFieldProps, + getInnerWrapperProps, + getNumberFieldWrapperProps, + getMainWrapperProps, + getHelperWrapperProps, + getDescriptionProps, + getErrorMessageProps, + getClearButtonProps, + getStepperIncreaseButtonProps, + getStepperDecreaseButtonProps, + } = useNumberField({...props, ref}); + + const labelContent = label ? : null; + + const end = useMemo(() => { + if (isClearable) { + return ; + } + + return endContent; + }, [isClearable, getClearButtonProps]); + + const helperWrapper = useMemo(() => { + const shouldShowError = isInvalid && errorMessage; + const hasContent = shouldShowError || description; + + if (!hasHelper || !hasContent) return null; + + return ( +
+ {shouldShowError ? ( +
{errorMessage}
+ ) : ( +
{description}
+ )} +
+ ); + }, [ + hasHelper, + isInvalid, + errorMessage, + description, + getHelperWrapperProps, + getErrorMessageProps, + getDescriptionProps, + ]); + + const innerWrapper = useMemo(() => { + if (hideStepper) { + return ( +
+ {startContent} + + {end} +
+ ); + } + + if (steps === "horizontal") { + return ( +
+ + {startContent} + + {end} + +
+ ); + } + + return ( +
+ {startContent} + + {end} + {!hideStepper && ( +
+ + +
+ )} +
+ ); + }, [startContent, end, getNumberFieldProps, getInnerWrapperProps]); + + const mainWrapper = useMemo(() => { + return ( +
+ {!isOutsideLeft ? labelContent : null} +
{innerWrapper}
+ {helperWrapper} +
+ ); + }, [ + labelPlacement, + helperWrapper, + shouldLabelBeOutside, + labelContent, + innerWrapper, + errorMessage, + description, + getMainWrapperProps, + getNumberFieldWrapperProps, + getErrorMessageProps, + getDescriptionProps, + ]); + + return ( + + {isOutsideLeft ? labelContent : null} + {mainWrapper} + + ); +}); + +NumberField.displayName = "NextUI.NumberField"; + +export default NumberField; From 9a9cb4651a6e751a2032b2b13a57e5c1e0f0c324 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 23:29:53 +0800 Subject: [PATCH 009/131] refactor(number-field): revise stepper icons --- .../src/number-field-horiztonal-stepper.tsx | 29 ++----------------- .../src/number-field-vertical-stepper.tsx | 29 ++----------------- 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/packages/components/number-field/src/number-field-horiztonal-stepper.tsx b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx index c868a79754..15c1da421e 100644 --- a/packages/components/number-field/src/number-field-horiztonal-stepper.tsx +++ b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx @@ -2,6 +2,7 @@ import type {AriaButtonProps} from "@react-types/button"; import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; import {Button} from "@nextui-org/button"; +import {ChevronLeftIcon, ChevronRightIcon} from "@nextui-org/shared-icons"; export interface Props extends Omit, keyof AriaButtonProps> { direction: "left" | "right"; @@ -9,39 +10,13 @@ export interface Props extends Omit, keyof AriaButtonP export type NumberFieldHorizontalStepperProps = Props & AriaButtonProps; -const StepperLeftIcon = () => ( - - - -); - -const StepperRightIcon = () => ( - - - -); - const NumberFieldHorizontalStepper = forwardRef<"button", NumberFieldHorizontalStepperProps>( (props) => { const {direction, ...otherProps} = props; return ( ); }, diff --git a/packages/components/number-field/src/number-field-vertical-stepper.tsx b/packages/components/number-field/src/number-field-vertical-stepper.tsx index f2eb92601c..b343b88c6c 100644 --- a/packages/components/number-field/src/number-field-vertical-stepper.tsx +++ b/packages/components/number-field/src/number-field-vertical-stepper.tsx @@ -2,6 +2,7 @@ import type {AriaButtonProps} from "@react-types/button"; import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; import {Button} from "@nextui-org/button"; +import {ChevronUpIcon, ChevronDownIcon} from "@nextui-org/shared-icons"; export interface Props extends Omit, keyof AriaButtonProps> { direction: "up" | "down"; @@ -9,39 +10,13 @@ export interface Props extends Omit, keyof AriaButtonP export type NumberFieldVerticalStepperProps = Props & AriaButtonProps; -const StepperUpIcon = () => ( - - - -); - -const StepperDownIcon = () => ( - - - -); - const NumberFieldVerticalStepper = forwardRef<"button", NumberFieldVerticalStepperProps>( (props) => { const {direction, ...otherProps} = props; return ( ); }, From 5b5599127fd31b521b226a2a00f5f29961a6bc29 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 23:30:10 +0800 Subject: [PATCH 010/131] feat(shared-icons): add ChevronLeftIcon --- .../shared-icons/src/chevron-left.tsx | 20 +++++++++++++++++++ packages/utilities/shared-icons/src/index.ts | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 packages/utilities/shared-icons/src/chevron-left.tsx diff --git a/packages/utilities/shared-icons/src/chevron-left.tsx b/packages/utilities/shared-icons/src/chevron-left.tsx new file mode 100644 index 0000000000..04f76f895e --- /dev/null +++ b/packages/utilities/shared-icons/src/chevron-left.tsx @@ -0,0 +1,20 @@ +import {IconSvgProps} from "./types"; + +export const ChevronLeftIcon = (props: IconSvgProps) => ( + +); diff --git a/packages/utilities/shared-icons/src/index.ts b/packages/utilities/shared-icons/src/index.ts index a2f5a0286d..577c78e128 100644 --- a/packages/utilities/shared-icons/src/index.ts +++ b/packages/utilities/shared-icons/src/index.ts @@ -5,9 +5,10 @@ export * from "./avatar"; export * from "./close"; export * from "./close-filled"; export * from "./chevron"; +export * from "./chevron-up"; export * from "./chevron-down"; +export * from "./chevron-left"; export * from "./chevron-right"; -export * from "./chevron-up"; export * from "./ellipsis"; export * from "./forward"; export * from "./sun"; From 52d3673b9f519bf421c401b0d1a2eb271c5842f0 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 21 Dec 2024 23:30:25 +0800 Subject: [PATCH 011/131] feat(theme): stepperButton styles --- packages/core/theme/src/components/number-field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index a500e85054..871ce58199 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -66,6 +66,7 @@ const numberField = tv({ // focus ring ...dataFocusVisibleClasses, ], + stepperButton: ["bg-transparent min-w-4 w-4"], helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", From b67320d67ac98da8e128a7708214358f48d0b1f1 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 22 Dec 2024 20:32:16 +0800 Subject: [PATCH 012/131] feat(theme): number-field styles --- .../core/theme/src/components/number-field.ts | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index 871ce58199..ad19afb23d 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -76,7 +76,7 @@ const numberField = tv({ flat: { inputWrapper: [ "bg-default-100", - "data-[hover=true]:bg-default-50", + "data-[hover=true]:bg-default-200", "group-data-[focus=true]:bg-default-100", ], }, @@ -548,11 +548,19 @@ const numberField = tv({ inputWrapper: "after:!bg-danger", }, }, - // labelPlacement=[inside,outside] + // labelPlacement=[outside] { - labelPlacement: ["outside"], + labelPlacement: "outside", class: { - label: ["group-data-[filled-within=true]:pointer-events-auto"], + base: "relative justify-end", + label: [ + "pb-0", + "z-20", + "top-1/2", + "-translate-y-1/2", + "group-data-[filled-within=true]:pointer-events-auto", + "group-data-[filled-within=true]:start-0", + ], }, }, // variant=underlined & size @@ -570,26 +578,50 @@ const numberField = tv({ innerWrapper: "pb-1.5", }, }, - // outside-left & size & hasHelper + // outside & size { - labelPlacement: "outside-left", + labelPlacement: "outside", size: "sm", class: { - label: "group-data-[has-helper=true]:pt-2", + label: [ + "start-2", + "text-tiny", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", }, }, { - labelPlacement: "outside-left", + labelPlacement: "outside", size: "md", class: { - label: "group-data-[has-helper=true]:pt-3", + label: [ + "start-3", + "end-auto", + "text-small", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", }, }, { - labelPlacement: "outside-left", + labelPlacement: "outside", size: "lg", class: { - label: "group-data-[has-helper=true]:pt-4", + label: [ + "start-3", + "end-auto", + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", + }, + }, + // text truncate labelPlacement=[outside] + { + labelPlacement: ["outside"], + class: { + label: ["pe-2", "max-w-full", "text-ellipsis", "overflow-hidden"], }, }, ], From 37b06be964d7a56946d4b47815c44dc3b8510eb4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 22 Dec 2024 20:32:54 +0800 Subject: [PATCH 013/131] fix(number-field): label layout --- packages/components/number-field/src/number-field.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index 9d089e9f9f..5cc7fa73cd 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -28,7 +28,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { getLabelProps, getNumberFieldProps, getInnerWrapperProps, - getNumberFieldWrapperProps, + getInputWrapperProps, getMainWrapperProps, getHelperWrapperProps, getDescriptionProps, @@ -114,8 +114,10 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { const mainWrapper = useMemo(() => { return (
- {!isOutsideLeft ? labelContent : null} -
{innerWrapper}
+
+ {!isOutsideLeft ? labelContent : null} + {innerWrapper} +
{helperWrapper}
); @@ -128,7 +130,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { errorMessage, description, getMainWrapperProps, - getNumberFieldWrapperProps, + getInputWrapperProps, getErrorMessageProps, getDescriptionProps, ]); From 3adda561f96d039e705e4e4d8c38290ccbc6ee0c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 16:52:00 +0800 Subject: [PATCH 014/131] feat(number-field): vertical stepper wrapper --- packages/components/number-field/src/number-field.tsx | 3 ++- packages/core/theme/src/components/number-field.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index 5cc7fa73cd..1bafcfef9d 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -36,6 +36,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { getClearButtonProps, getStepperIncreaseButtonProps, getStepperDecreaseButtonProps, + getVerticalStepperWrapperProps, } = useNumberField({...props, ref}); const labelContent = label ? : null; @@ -102,7 +103,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { {end} {!hideStepper && ( -
+
diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index ad19afb23d..902926cf3f 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -67,6 +67,7 @@ const numberField = tv({ ...dataFocusVisibleClasses, ], stepperButton: ["bg-transparent min-w-4 w-4"], + verticalStepperWrapper: ["flex flex-col h-full"], helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", From 734af76a467f7f280af03881934557d1cea6e424 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 16:52:32 +0800 Subject: [PATCH 015/131] feat(number-field): use-number-field (wip) --- .../number-field/src/use-number-field.ts | 574 ++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 packages/components/number-field/src/use-number-field.ts diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts new file mode 100644 index 0000000000..e712600630 --- /dev/null +++ b/packages/components/number-field/src/use-number-field.ts @@ -0,0 +1,574 @@ +import type {NumberFieldVariantProps, SlotsToClasses, NumberFieldSlots} from "@nextui-org/theme"; +import type {AriaNumberFieldProps} from "@react-types/numberfield"; +import type {NumberFieldStateOptions} from "@react-stately/numberfield"; + +import { + HTMLNextUIProps, + mapPropsVariants, + PropGetter, + useProviderContext, +} from "@nextui-org/system"; +import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; +import {useFocusRing} from "@react-aria/focus"; +import {numberField} from "@nextui-org/theme"; +import {useDOMRef, filterDOMProps} from "@nextui-org/react-utils"; +import {useFocusWithin, useHover, usePress} from "@react-aria/interactions"; +import {clsx, dataAttr, isEmpty, objectToDeps} from "@nextui-org/shared-utils"; +import {useNumberFieldState} from "@react-stately/numberfield"; +import {useNumberField as useAriaNumberField} from "@react-aria/numberfield"; +import {useMemo, Ref, useCallback, useState} from "react"; +import {chain, mergeProps} from "@react-aria/utils"; +import {FormContext, useSlottedContext} from "@nextui-org/form"; + +export interface Props + extends Omit, keyof NumberFieldVariantProps> { + /** + * Ref to the DOM node. + */ + ref?: Ref; + /** + * Ref to the container DOM node. + */ + baseRef?: Ref; + /** + * Ref to the input wrapper DOM node. + * This is the element that wraps the input label and the innerWrapper when the labelPlacement="inside" + * and the input has start/end content. + */ + wrapperRef?: Ref; + /** + * Ref to the input inner wrapper DOM node. + * This is the element that wraps the input and the start/end content when passed. + */ + innerWrapperRef?: Ref; + /** + * Element to be rendered in the left side of the input. + */ + startContent?: React.ReactNode; + /** + * Element to be rendered in the right side of the input. + * if you pass this prop and the `onClear` prop, the passed element + * will have the clear button props and it will be rendered instead of the + * default clear button. + */ + endContent?: React.ReactNode; + /** + * Classname or List of classes to change the classNames of the element. + * if `className` is passed, it will be added to the base slot. + * + * @example + * ```ts + * + * ``` + */ + classNames?: SlotsToClasses; + /** + * Whether the stepper is placed horizontally or vertically + */ + steps?: "horizontal" | "vertical"; + /** + * Whether to hide the increment and decrement buttons. + */ + hideStepper?: boolean; + /** + * Callback fired when the value is cleared. + * if you pass this prop, the clear button will be shown. + */ + onClear?: () => void; + /** + * React aria onChange event. + */ + onValueChange?: AriaNumberFieldProps["onChange"]; +} + +export type UseNumberFieldProps = Props & + Omit & + Omit & + NumberFieldVariantProps; + +export function useNumberField(originalProps: UseNumberFieldProps) { + const globalContext = useProviderContext(); + const {validationBehavior: formValidationBehavior} = useSlottedContext(FormContext) || {}; + + const [props, variantProps] = mapPropsVariants(originalProps, numberField.variantKeys); + + const { + ref, + as, + type, + label, + baseRef, + wrapperRef, + description, + className, + classNames, + autoFocus, + startContent, + endContent, + onClear, + onChange, + validationState, + validationBehavior = formValidationBehavior ?? globalContext?.validationBehavior ?? "aria", + innerWrapperRef: innerWrapperRefProp, + onValueChange, + hideStepper, + steps = "vertical", + ...otherProps + } = props; + + const [isFocusWithin, setFocusWithin] = useState(false); + + const Component = as || "div"; + + const disableAnimation = + originalProps.disableAnimation ?? globalContext?.disableAnimation ?? false; + + const domRef = useDOMRef(ref); + + const baseDomRef = useDOMRef(baseRef); + const inputWrapperRef = useDOMRef(wrapperRef); + const innerWrapperRef = useDOMRef(innerWrapperRefProp); + + const state = useNumberFieldState({ + ...props, + validationBehavior, + locale: "en-US", + onChange: onValueChange, + }); + + let { + // groupProps, + labelProps, + inputProps, + incrementButtonProps, + decrementButtonProps, + descriptionProps, + errorMessageProps, + isInvalid: isAriaInvalid, + validationErrors, + validationDetails, + } = useAriaNumberField(props, state, domRef); + + const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; + const isFilled = !isEmpty(inputValue); + const isFilledWithin = isFilled || isFocusWithin; + const isHiddenType = type === "hidden"; + + const baseStyles = clsx(classNames?.base, className, isFilled ? "is-filled" : ""); + + const handleClear = useCallback(() => { + state.setInputValue(""); + + onClear?.(); + domRef.current?.focus(); + }, [state.setInputValue, onClear]); + + // if we use `react-hook-form`, it will set the input value using the ref in register + // i.e. setting ref.current.value to something which is uncontrolled + // hence, sync the state with `ref.current.value` + useSafeLayoutEffect(() => { + if (!domRef.current) return; + + state.setInputValue(domRef.current.value); + }, [domRef.current]); + + const {isFocusVisible, isFocused, focusProps} = useFocusRing({ + autoFocus, + isTextInput: true, + }); + + const {isHovered, hoverProps} = useHover({isDisabled: !!originalProps?.isDisabled}); + + const {isHovered: isLabelHovered, hoverProps: labelHoverProps} = useHover({ + isDisabled: !!originalProps?.isDisabled, + }); + + const {focusProps: clearFocusProps, isFocusVisible: isClearButtonFocusVisible} = useFocusRing(); + + const {focusWithinProps} = useFocusWithin({ + onFocusWithinChange: setFocusWithin, + }); + + const {pressProps: clearPressProps} = usePress({ + isDisabled: !!originalProps?.isDisabled || !!originalProps?.isReadOnly, + onPress: handleClear, + }); + + const isInvalid = validationState === "invalid" || isAriaInvalid; + + const labelPlacement = originalProps.labelPlacement ?? "outside"; + + const errorMessage = + typeof props.errorMessage === "function" + ? props.errorMessage({isInvalid, validationErrors, validationDetails}) + : props.errorMessage || validationErrors?.join(" "); + const isClearable = !!onClear || originalProps.isClearable; + const hasElements = !!label || !!description || !!errorMessage; + const hasPlaceholder = !!props.placeholder; + const hasLabel = !!label; + const hasHelper = !!description || !!errorMessage; + const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; + const isPlaceholderShown = domRef.current + ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder + : false; + const isOutsideLeft = labelPlacement === "outside-left"; + + const hasStartContent = !!startContent; + const isLabelOutside = shouldLabelBeOutside + ? labelPlacement === "outside-left" || + hasPlaceholder || + (labelPlacement === "outside" && hasStartContent) + : false; + const isLabelOutsideAsPlaceholder = + labelPlacement === "outside" && !hasPlaceholder && !hasStartContent; + + const slots = useMemo( + () => + numberField({ + ...variantProps, + isInvalid, + labelPlacement, + isClearable, + disableAnimation, + }), + [ + objectToDeps(variantProps), + isInvalid, + labelPlacement, + isClearable, + hasStartContent, + disableAnimation, + ], + ); + + const getBaseProps: PropGetter = useCallback( + (props = {}) => { + return { + ref: baseDomRef, + className: slots.base({class: baseStyles}), + "data-slot": "base", + "data-filled": dataAttr( + isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown, + ), + "data-filled-within": dataAttr( + isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown, + ), + "data-focus-within": dataAttr(isFocusWithin), + "data-focus-visible": dataAttr(isFocusVisible), + "data-readonly": dataAttr(originalProps.isReadOnly), + "data-focus": dataAttr(isFocused), + "data-hover": dataAttr(isHovered || isLabelHovered), + "data-required": dataAttr(originalProps.isRequired), + "data-invalid": dataAttr(isInvalid), + "data-disabled": dataAttr(originalProps.isDisabled), + "data-has-elements": dataAttr(hasElements), + "data-has-helper": dataAttr(hasHelper), + "data-has-label": dataAttr(hasLabel), + "data-has-value": dataAttr(!isPlaceholderShown), + "data-hidden": dataAttr(isHiddenType), + ...focusWithinProps, + ...props, + }; + }, + [ + slots, + baseStyles, + isFilled, + isFocused, + isHovered, + isLabelHovered, + isInvalid, + hasHelper, + hasLabel, + hasElements, + isPlaceholderShown, + hasStartContent, + isFocusWithin, + isFocusVisible, + isFilledWithin, + hasPlaceholder, + focusWithinProps, + isHiddenType, + originalProps.isReadOnly, + originalProps.isRequired, + originalProps.isDisabled, + ], + ); + + const getLabelProps: PropGetter = useCallback( + (props = {}) => { + return { + "data-slot": "label", + className: slots.label({class: classNames?.label}), + ...mergeProps(labelProps, labelHoverProps, props), + }; + }, + [slots, isLabelHovered, labelProps, classNames?.label], + ); + + const getNumberFieldProps: PropGetter = useCallback( + (props = {}) => { + return { + "data-slot": "input", + "data-filled": dataAttr(isFilled), + "data-filled-within": dataAttr(isFilledWithin), + "data-has-start-content": dataAttr(hasStartContent), + "data-has-end-content": dataAttr(!!endContent), + className: slots.input({ + class: clsx(classNames?.input, isFilled ? "is-filled" : ""), + }), + ...mergeProps( + focusProps, + inputProps, + filterDOMProps(otherProps, { + enabled: true, + labelable: true, + omitEventNames: new Set(Object.keys(inputProps)), + }), + props, + ), + "aria-readonly": dataAttr(originalProps.isReadOnly), + onChange: chain(inputProps.onChange, onChange), + value: inputValue, + ref: domRef, + }; + }, + [ + slots, + inputValue, + focusProps, + inputProps, + otherProps, + isFilled, + isFilledWithin, + hasStartContent, + endContent, + classNames?.input, + originalProps.isReadOnly, + originalProps.isRequired, + onChange, + ], + ); + + const getInputWrapperProps: PropGetter = useCallback( + (props = {}) => { + return { + ref: inputWrapperRef, + "data-slot": "input-wrapper", + "data-hover": dataAttr(isHovered || isLabelHovered), + "data-focus-visible": dataAttr(isFocusVisible), + "data-focus": dataAttr(isFocused), + className: slots.inputWrapper({ + class: clsx(classNames?.inputWrapper, isFilled ? "is-filled" : ""), + }), + ...mergeProps(props, hoverProps), + onClick: (e) => { + if (domRef.current && e.currentTarget === e.target) { + domRef.current.focus(); + } + }, + style: { + cursor: "text", + ...props.style, + }, + }; + }, + [ + slots, + isHovered, + isLabelHovered, + isFocusVisible, + isFocused, + inputValue, + classNames?.inputWrapper, + ], + ); + + const getInnerWrapperProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + ref: innerWrapperRef, + "data-slot": "inner-wrapper", + onClick: (e) => { + if (domRef.current && e.currentTarget === e.target) { + domRef.current.focus(); + } + }, + className: slots.innerWrapper({ + class: clsx(classNames?.innerWrapper, props?.className), + }), + }; + }, + [slots, classNames?.innerWrapper], + ); + + const getMainWrapperProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + "data-slot": "main-wrapper", + className: slots.mainWrapper({ + class: clsx(classNames?.mainWrapper, props?.className), + }), + }; + }, + [slots, classNames?.mainWrapper], + ); + + const getHelperWrapperProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + "data-slot": "helper-wrapper", + className: slots.helperWrapper({ + class: clsx(classNames?.helperWrapper, props?.className), + }), + }; + }, + [slots, classNames?.helperWrapper], + ); + + const getDescriptionProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + ...descriptionProps, + "data-slot": "description", + className: slots.description({class: clsx(classNames?.description, props?.className)}), + }; + }, + [slots, classNames?.description], + ); + + const getErrorMessageProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + ...errorMessageProps, + "data-slot": "error-message", + className: slots.errorMessage({class: clsx(classNames?.errorMessage, props?.className)}), + }; + }, + [slots, errorMessageProps, classNames?.errorMessage], + ); + + const getClearButtonProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + type: "button", + tabIndex: -1, + disabled: originalProps.isDisabled, + "aria-label": "clear input", + "data-slot": "clear-button", + "data-focus-visible": dataAttr(isClearButtonFocusVisible), + className: slots.clearButton({class: clsx(classNames?.clearButton, props?.className)}), + ...mergeProps(clearPressProps, clearFocusProps), + }; + }, + [slots, isClearButtonFocusVisible, clearPressProps, clearFocusProps, classNames?.clearButton], + ); + + const getVerticalStepperWrapperProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + className: slots.verticalStepperWrapper({ + class: clsx(classNames?.verticalStepperWrapper, props?.className), + }), + // TODO: check press props & focus props + ...incrementButtonProps, + }; + }, + [slots], + ); + + const getStepperIncreaseButtonProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + type: "button", + disabled: originalProps.isDisabled, + "aria-label": "increase value", + "data-slot": "increase-button", + className: slots.stepperButton({ + // class: clsx(classNames?.stepperIncreaseButton, props?.className), + }), + // TODO: check press props & focus props + ...incrementButtonProps, + }; + }, + [slots], + ); + + const getStepperDecreaseButtonProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + type: "button", + disabled: originalProps.isDisabled, + "aria-label": "decrease value", + "data-slot": "decrease-button", + className: slots.stepperButton({ + // class: clsx(classNames?.stepperDereaseButton, props?.className), + }), + // TODO: check press props & focus props + ...decrementButtonProps, + }; + }, + [slots], + ); + + return { + Component, + classNames, + domRef, + label, + description, + startContent, + endContent, + labelPlacement, + isClearable, + hasHelper, + hasStartContent, + isLabelOutside, + isOutsideLeft, + isLabelOutsideAsPlaceholder, + shouldLabelBeOutside, + hasPlaceholder, + isInvalid, + errorMessage, + hideStepper, + steps, + incrementButtonProps, + decrementButtonProps, + getBaseProps, + getLabelProps, + getNumberFieldProps, + getMainWrapperProps, + getInputWrapperProps, + getInnerWrapperProps, + getHelperWrapperProps, + getDescriptionProps, + getErrorMessageProps, + getClearButtonProps, + getStepperIncreaseButtonProps, + getStepperDecreaseButtonProps, + getVerticalStepperWrapperProps, + }; +} + +export type UseNumberFieldReturn = ReturnType; From 7f49f0086c6c24000549668e5703a10e31f9238d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 17:33:32 +0800 Subject: [PATCH 016/131] feat(number-field): add data-direction --- packages/components/number-field/src/use-number-field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index e712600630..d7062731cc 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -326,6 +326,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { "data-filled-within": dataAttr(isFilledWithin), "data-has-start-content": dataAttr(hasStartContent), "data-has-end-content": dataAttr(!!endContent), + "data-direction": steps, className: slots.input({ class: clsx(classNames?.input, isFilled ? "is-filled" : ""), }), From f3f80575a2936069ad95cc2f10ca2abebadad4f2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 17:33:53 +0800 Subject: [PATCH 017/131] feat(theme): center the text if it is horizontal stepper --- packages/core/theme/src/components/number-field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index 902926cf3f..cd885bfbf2 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -45,6 +45,7 @@ const numberField = tv({ "w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none", "data-[has-start-content=true]:ps-1.5", "data-[has-end-content=true]:pe-1.5", + "data-[direction=horizontal]:text-center", "autofill:bg-transparent bg-clip-text", ], clearButton: [ From 0ddddd663d9b26c716ebaf6dae54f46d487ce13c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 19:12:16 +0800 Subject: [PATCH 018/131] feat(number-field): add HorizontalStepper --- .../number-field/stories/number-field.stories.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 78f3b226dc..053f7da2b6 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -386,6 +386,15 @@ export const Default = { }, }; +export const HorizontalStepper = { + render: Template, + + args: { + ...defaultProps, + steps: "horizontal", + }, +}; + export const Required = { render: FormTemplate, From 2cc8470a1b053d75390c4f94bcd3789842f15a1a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 19:14:47 +0800 Subject: [PATCH 019/131] feat(number-field): add HideStepper --- .../number-field/stories/number-field.stories.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 053f7da2b6..6ecaff1afe 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -395,6 +395,15 @@ export const HorizontalStepper = { }, }; +export const HideStepper = { + render: Template, + + args: { + ...defaultProps, + hideStepper: true, + }, +}; + export const Required = { render: FormTemplate, From 79d2cbcc0a3689b98b3e8c257eec574ebae65ce9 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 19:18:49 +0800 Subject: [PATCH 020/131] chore(number-field): revise minValue & defaultValue --- .../components/number-field/stories/number-field.stories.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 6ecaff1afe..2314296673 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -601,8 +601,9 @@ export const MinValue = { args: { ...defaultProps, - label: "Enter a number (min value: 0)", - minValue: 0, + label: "Enter a number (min value: 60)", + minValue: 60, + defaultValue: 64, }, }; From c82faa66748216fbcd66d8be95e264474330f39e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 24 Dec 2024 19:25:03 +0800 Subject: [PATCH 021/131] feat(docs): init number field structure --- apps/docs/config/routes.json | 7 +++++ apps/docs/content/components/index.ts | 1 + .../content/components/number-field/index.ts | 5 ++++ .../components/number-field/usage.raw.jsx | 5 ++++ .../content/components/number-field/usage.ts | 9 ++++++ .../content/docs/components/number-field.mdx | 29 +++++++++++++++++++ packages/components/number-field/package.json | 2 +- 7 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/components/number-field/index.ts create mode 100644 apps/docs/content/components/number-field/usage.raw.jsx create mode 100644 apps/docs/content/components/number-field/usage.ts create mode 100644 apps/docs/content/docs/components/number-field.mdx diff --git a/apps/docs/config/routes.json b/apps/docs/config/routes.json index 5f080986d7..906040c73d 100644 --- a/apps/docs/config/routes.json +++ b/apps/docs/config/routes.json @@ -325,6 +325,13 @@ "keywords": "navbar, navigation, top menu, website header", "path": "/docs/components/navbar.mdx" }, + { + "key": "number-field", + "title": "NumberField", + "keywords": "input, numeric input, number field", + "path": "/docs/components/number-field.mdx", + "newPost": true + }, { "key": "pagination", "title": "Pagination", diff --git a/apps/docs/content/components/index.ts b/apps/docs/content/components/index.ts index ced4352a6f..b77c0c4df9 100644 --- a/apps/docs/content/components/index.ts +++ b/apps/docs/content/components/index.ts @@ -34,3 +34,4 @@ export * from "./table"; export * from "./autocomplete"; export * from "./alert"; export * from "./drawer"; +export * from "./number-field"; diff --git a/apps/docs/content/components/number-field/index.ts b/apps/docs/content/components/number-field/index.ts new file mode 100644 index 0000000000..e68c5e8301 --- /dev/null +++ b/apps/docs/content/components/number-field/index.ts @@ -0,0 +1,5 @@ +import usage from "./usage"; + +export const numberFieldContent = { + usage, +}; diff --git a/apps/docs/content/components/number-field/usage.raw.jsx b/apps/docs/content/components/number-field/usage.raw.jsx new file mode 100644 index 0000000000..8facf1e6c1 --- /dev/null +++ b/apps/docs/content/components/number-field/usage.raw.jsx @@ -0,0 +1,5 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ; +} diff --git a/apps/docs/content/components/number-field/usage.ts b/apps/docs/content/components/number-field/usage.ts new file mode 100644 index 0000000000..1118304c37 --- /dev/null +++ b/apps/docs/content/components/number-field/usage.ts @@ -0,0 +1,9 @@ +import App from "./usage.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-field.mdx new file mode 100644 index 0000000000..36350d513c --- /dev/null +++ b/apps/docs/content/docs/components/number-field.mdx @@ -0,0 +1,29 @@ +--- +title: "Number Field" +description: "The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons" +--- + +import {numberFieldContent} from "@/content/components/number-field"; + +# Number Field + +The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons + + + +--- + + + +## Installation + + diff --git a/packages/components/number-field/package.json b/packages/components/number-field/package.json index 1ab6800d78..0931885260 100644 --- a/packages/components/number-field/package.json +++ b/packages/components/number-field/package.json @@ -1,7 +1,7 @@ { "name": "@nextui-org/number-field", "version": "2.0.0", - "description": "The input component is designed for capturing user input within a text field.", + "description": "The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons", "keywords": [ "input", "number", From 823bc9998059ffd38e3476993bbf44f71a3ebc06 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 25 Dec 2024 00:02:26 +0800 Subject: [PATCH 022/131] fix(theme): outside-left styles --- .../core/theme/src/components/number-field.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index cd885bfbf2..c4985498e0 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -176,7 +176,6 @@ const numberField = tv({ }, "outside-left": { base: "flex-row items-center flex-nowrap data-[has-helper=true]:items-start", - inputWrapper: "flex-1", mainWrapper: "flex flex-col", label: "relative text-foreground pe-2 ps-2 pointer-events-auto", }, @@ -619,6 +618,28 @@ const numberField = tv({ base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", }, }, + // outside-left & size & hasHelper + { + labelPlacement: "outside-left", + size: "sm", + class: { + label: "group-data-[has-helper=true]:pt-2", + }, + }, + { + labelPlacement: "outside-left", + size: "md", + class: { + label: "group-data-[has-helper=true]:pt-3", + }, + }, + { + labelPlacement: "outside-left", + size: "lg", + class: { + label: "group-data-[has-helper=true]:pt-4", + }, + }, // text truncate labelPlacement=[outside] { labelPlacement: ["outside"], From 8d54dab1e5752e509f8c1cbf609e273ac63aa4bb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 27 Dec 2024 21:33:24 +0800 Subject: [PATCH 023/131] refactor(theme): remove labelPlacement styles --- .../core/theme/src/components/number-field.ts | 139 ++++-------------- 1 file changed, 32 insertions(+), 107 deletions(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index c4985498e0..f280d66d11 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -23,10 +23,9 @@ import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; */ const numberField = tv({ slots: { - base: "group flex flex-col data-[hidden=true]:hidden", + base: "group flex flex-col data-[hidden=true]:hidden relative justify-end", label: [ "absolute", - "z-10", "pointer-events-none", "origin-top-left", "flex-shrink-0", @@ -36,8 +35,18 @@ const numberField = tv({ "block", "text-small", "text-foreground-500", + "pb-0", + "z-20", + "top-1/2", + "-translate-y-1/2", + "group-data-[filled-within=true]:pointer-events-auto", + "group-data-[filled-within=true]:start-0", + "pe-2", + "max-w-full", + "text-ellipsis", + "overflow-hidden", ], - mainWrapper: "h-full", + mainWrapper: "h-full flex flex-col", inputWrapper: "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-4 py-3 gap-3", innerWrapper: "inline-flex w-full items-center h-full box-border", @@ -136,18 +145,36 @@ const numberField = tv({ }, size: { sm: { - label: "text-tiny", + label: [ + "start-2", + "text-tiny", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", inputWrapper: "h-8 min-h-8 px-2 rounded-small", input: "text-small", clearButton: "text-medium", }, md: { + label: [ + "start-3", + "end-auto", + "text-small", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", inputWrapper: "h-10 min-h-10 rounded-medium", input: "text-small", clearButton: "text-large", }, lg: { - label: "text-medium", + label: [ + "start-3", + "end-auto", + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", inputWrapper: "h-12 min-h-12 rounded-large", input: "text-medium", clearButton: "text-large", @@ -170,16 +197,6 @@ const numberField = tv({ inputWrapper: "rounded-full", }, }, - labelPlacement: { - outside: { - mainWrapper: "flex flex-col", - }, - "outside-left": { - base: "flex-row items-center flex-nowrap data-[has-helper=true]:items-start", - mainWrapper: "flex flex-col", - label: "relative text-foreground pe-2 ps-2 pointer-events-auto", - }, - }, fullWidth: { true: { base: "w-full", @@ -245,7 +262,6 @@ const numberField = tv({ color: "default", size: "md", fullWidth: true, - labelPlacement: "outside", isDisabled: false, }, compoundVariants: [ @@ -468,14 +484,6 @@ const numberField = tv({ label: "text-danger", }, }, - // labelPlacement=outside & default - { - labelPlacement: "outside", - color: "default", - class: { - label: "group-data-[filled-within=true]:text-foreground", - }, - }, // radius-full & size { radius: "full", @@ -549,21 +557,6 @@ const numberField = tv({ inputWrapper: "after:!bg-danger", }, }, - // labelPlacement=[outside] - { - labelPlacement: "outside", - class: { - base: "relative justify-end", - label: [ - "pb-0", - "z-20", - "top-1/2", - "-translate-y-1/2", - "group-data-[filled-within=true]:pointer-events-auto", - "group-data-[filled-within=true]:start-0", - ], - }, - }, // variant=underlined & size { variant: "underlined", @@ -579,74 +572,6 @@ const numberField = tv({ innerWrapper: "pb-1.5", }, }, - // outside & size - { - labelPlacement: "outside", - size: "sm", - class: { - label: [ - "start-2", - "text-tiny", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", - }, - }, - { - labelPlacement: "outside", - size: "md", - class: { - label: [ - "start-3", - "end-auto", - "text-small", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", - }, - }, - { - labelPlacement: "outside", - size: "lg", - class: { - label: [ - "start-3", - "end-auto", - "text-medium", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", - }, - }, - // outside-left & size & hasHelper - { - labelPlacement: "outside-left", - size: "sm", - class: { - label: "group-data-[has-helper=true]:pt-2", - }, - }, - { - labelPlacement: "outside-left", - size: "md", - class: { - label: "group-data-[has-helper=true]:pt-3", - }, - }, - { - labelPlacement: "outside-left", - size: "lg", - class: { - label: "group-data-[has-helper=true]:pt-4", - }, - }, - // text truncate labelPlacement=[outside] - { - labelPlacement: ["outside"], - class: { - label: ["pe-2", "max-w-full", "text-ellipsis", "overflow-hidden"], - }, - }, ], }); From 5998dea7f17b61cc55063e5ec3999ebce41f7e2d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 27 Dec 2024 21:35:22 +0800 Subject: [PATCH 024/131] refactor(number-field): remove labelContent logic --- .../__tests__/number-field.test.tsx | 496 ++++++++++++++++++ .../number-field/src/number-field.tsx | 14 +- .../number-field/src/use-number-field.ts | 26 +- 3 files changed, 499 insertions(+), 37 deletions(-) create mode 100644 packages/components/number-field/__tests__/number-field.test.tsx diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx new file mode 100644 index 0000000000..c3b57009ba --- /dev/null +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -0,0 +1,496 @@ +import * as React from "react"; +import {render, renderHook, fireEvent, act} from "@testing-library/react"; +import userEvent, {UserEvent} from "@testing-library/user-event"; +import {useForm} from "react-hook-form"; +import {Form} from "@nextui-org/form"; + +import {Input} from "../src"; + +describe("Input", () => { + it("should render correctly", () => { + const wrapper = render(); + + expect(() => wrapper.unmount()).not.toThrow(); + }); + + it("ref should be forwarded", () => { + const ref = React.createRef(); + + render(); + expect(ref.current).not.toBeNull(); + }); + + it("should have aria-invalid when invalid", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("aria-invalid", "true"); + }); + + it("should have aria-readonly when isReadOnly", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("aria-readonly", "true"); + }); + + it("should have disabled attribute when isDisabled", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("disabled"); + }); + + it("should disable the clear button when isDisabled", () => { + const {getByRole} = render(); + + const clearButton = getByRole("button"); + + expect(clearButton).toBeDisabled(); + }); + + it("should not allow clear button to be focusable", () => { + const {getByRole} = render(); + + const clearButton = getByRole("button"); + + expect(clearButton).toHaveAttribute("tabIndex", "-1"); + }); + + it("should have required attribute when isRequired with native validationBehavior", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("required"); + expect(container.querySelector("input")).not.toHaveAttribute("aria-required"); + }); + + it("should have aria-required attribute when isRequired with aria validationBehavior", () => { + const {container} = render(); + + expect(container.querySelector("input")).not.toHaveAttribute("required"); + expect(container.querySelector("input")).toHaveAttribute("aria-required", "true"); + }); + + it("should have aria-describedby when description is provided", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); + }); + + it("should have aria-describedby when errorMessage is provided", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); + }); + + it("should have the same aria-labelledby as label id", () => { + const {container} = render(); + + const labelId = container.querySelector("label")?.id; + const labelledBy = container.querySelector("input")?.getAttribute("aria-labelledby"); + + expect(labelledBy?.includes(labelId as string)).toBeTruthy(); + }); + + it("should have the correct type attribute", () => { + const {container} = render(); + + expect(container.querySelector("input")).toHaveAttribute("type", "email"); + + const {container: container2} = render(); + + expect(container2.querySelector("input")).toHaveAttribute("type", "number"); + + const {container: container3} = render(); + + expect(container3.querySelector("input")).toHaveAttribute("type", "password"); + + const {container: container4} = render(); + + expect(container4.querySelector("input")).toHaveAttribute("type", "search"); + + const {container: container5} = render(); + + expect(container5.querySelector("input")).toHaveAttribute("type", "tel"); + + const {container: container6} = render(); + + expect(container6.querySelector("input")).toHaveAttribute("type", "text"); + }); + + it("should call dom event handlers only once", () => { + const onFocus = jest.fn(); + + const {container} = render(); + + container.querySelector("input")?.focus(); + container.querySelector("input")?.blur(); + + expect(onFocus).toHaveBeenCalledTimes(1); + }); + + it("ref should update the value", () => { + const ref = React.createRef(); + + const {container} = render(); + + if (!ref.current) { + throw new Error("ref is null"); + } + const value = "value"; + + ref.current!.value = value; + + container.querySelector("input")?.focus(); + + expect(ref.current?.value)?.toBe(value); + }); + + it("should clear the value and onClear is triggered", async () => { + const onClear = jest.fn(); + + const ref = React.createRef(); + + const {getByRole} = render( + , + ); + + const clearButton = getByRole("button")!; + + expect(clearButton).not.toBeNull(); + + const user = userEvent.setup(); + + await user.click(clearButton); + + expect(ref.current?.value)?.toBe(""); + + expect(onClear).toHaveBeenCalledTimes(1); + }); + + it("should not display input with hidden type", async () => { + const wrapper = render( + <> + + + , + ); + + const {container} = wrapper; + + const inputBaseWrappers = container.querySelectorAll("[data-slot='base']"); + + expect(inputBaseWrappers).toHaveLength(2); + + const inputs = container.querySelectorAll("input"); + + expect(inputs).toHaveLength(2); + + expect(inputBaseWrappers[0]).toHaveAttribute("data-hidden"); + + expect(inputBaseWrappers[1]).not.toHaveAttribute("data-hidden"); + + expect(inputs[0]).not.toBeVisible(); + + expect(inputs[1]).toBeVisible(); + }); + + it("should disable clear button when isReadOnly is true", async () => { + const onClear = jest.fn(); + + const ref = React.createRef(); + + const {getByRole} = render( + , + ); + + const clearButton = getByRole("button")!; + + expect(clearButton).not.toBeNull(); + + const user = userEvent.setup(); + + await user.click(clearButton); + + expect(onClear).toHaveBeenCalledTimes(0); + }); +}); + +describe("Input with React Hook Form", () => { + let input1: HTMLInputElement; + let input2: HTMLInputElement; + let input3: HTMLInputElement; + let submitButton: HTMLButtonElement; + let onSubmit: () => void; + + beforeEach(() => { + const {result} = renderHook(() => + useForm({ + defaultValues: { + withDefaultValue: "wkw", + withoutDefaultValue: "", + requiredField: "", + }, + }), + ); + + const { + handleSubmit, + register, + formState: {errors}, + } = result.current; + + onSubmit = jest.fn(); + + render( +
+ + + + {errors.requiredField && This field is required} + +
, + ); + + input1 = document.querySelector("input[name=withDefaultValue]")!; + input2 = document.querySelector("input[name=withoutDefaultValue]")!; + input3 = document.querySelector("input[name=requiredField]")!; + submitButton = document.querySelector('button[type="submit"]')!; + }); + + it("should work with defaultValues", () => { + expect(input1).toHaveValue("wkw"); + expect(input2).toHaveValue(""); + expect(input3).toHaveValue(""); + }); + + it("should not submit form when required field is empty", async () => { + const user = userEvent.setup(); + + await user.click(submitButton); + + expect(onSubmit).toHaveBeenCalledTimes(0); + }); + + it("should submit form when required field is not empty", async () => { + fireEvent.change(input3, {target: {value: "updated"}}); + + const user = userEvent.setup(); + + await user.click(submitButton); + + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + describe("validation", () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + describe("validationBehavior=native", () => { + it("supports isRequired", async () => { + const {getByTestId} = render( +
+ +
, + ); + + const input = getByTestId("input") as HTMLInputElement; + + expect(input).toHaveAttribute("required"); + expect(input).not.toHaveAttribute("aria-required"); + expect(input).not.toHaveAttribute("aria-describedby"); + expect(input.validity.valid).toBe(false); + + act(() => { + (getByTestId("form") as HTMLFormElement).checkValidity(); + }); + + expect(document.activeElement).toBe(input); + expect(input).toHaveAttribute("aria-describedby"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Constraints not satisfied", + ); + + await user.keyboard("hello"); + + expect(input).toHaveAttribute("aria-describedby"); + expect(input.validity.valid).toBe(true); + + await user.tab(); + + expect(input).not.toHaveAttribute("aria-describedby"); + }); + + it("supports validate function", async () => { + const {getByTestId} = render( +
+ (v === "Foo" ? "Invalid name" : null)} + validationBehavior="native" + /> +
, + ); + + const input = getByTestId("input") as HTMLInputElement; + + expect(input).not.toHaveAttribute("aria-describedby"); + expect(input.validity.valid).toBe(false); + + act(() => { + (getByTestId("form") as HTMLFormElement).checkValidity(); + }); + + expect(document.activeElement).toBe(input); + expect(input).toHaveAttribute("aria-describedby"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Invalid name", + ); + + await user.keyboard("hello"); + + expect(input).toHaveAttribute("aria-describedby"); + expect(input.validity.valid).toBe(true); + + await user.tab(); + + expect(input).not.toHaveAttribute("aria-describedby"); + }); + + it("supports server validation", async () => { + function Test() { + let [serverErrors, setServerErrors] = React.useState({}); + let onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setServerErrors({ + name: "Invalid name.", + }); + }; + + return ( +
+ + +
+ ); + } + + const {getByTestId} = render(); + + const input = getByTestId("input") as HTMLInputElement; + const submitButton = getByTestId("submit"); + + expect(input).not.toHaveAttribute("aria-describedby"); + + await user.click(submitButton); + act(() => { + (getByTestId("form") as HTMLFormElement).checkValidity(); + }); + + expect(input).toHaveAttribute("aria-describedby"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Invalid name.", + ); + expect(input.validity.valid).toBe(false); + + // Clicking twice doesn't clear server errors. + await user.click(submitButton); + act(() => { + (getByTestId("form") as HTMLFormElement).checkValidity(); + }); + + expect(document.activeElement).toBe(input); + expect(input).toHaveAttribute("aria-describedby"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Invalid name.", + ); + expect(input.validity.valid).toBe(false); + + await user.keyboard("hello"); + await user.tab(); + + expect(input).not.toHaveAttribute("aria-describedby"); + expect(input.validity.valid).toBe(true); + }); + }); + + describe('validationBehavior="aria"', () => { + it("supports validate function", async () => { + const {getByTestId} = render( +
+ (v === "Foo" ? "Invalid name" : null)} + /> +
, + ); + + const input = getByTestId("input") as HTMLInputElement; + + expect(input).toHaveAttribute("aria-describedby"); + expect(input).toHaveAttribute("aria-invalid", "true"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Invalid name", + ); + expect(input.validity.valid).toBe(true); + + await user.tab(); + await user.keyboard("hello"); + // TODO: fix this + // expect(input).not.toHaveAttribute("aria-describedby"); + // expect(input).not.toHaveAttribute("aria-invalid"); + }); + + it("supports server validation", async () => { + const {getByTestId} = render( +
+ +
, + ); + + const input = getByTestId("input"); + + expect(input).toHaveAttribute("aria-describedby"); + expect(input).toHaveAttribute("aria-invalid", "true"); + expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( + "Invalid name", + ); + + await user.tab(); + await user.keyboard("hello"); + await user.tab(); + + // TODO: fix this + // expect(input).not.toHaveAttribute("aria-describedby"); + // expect(input).not.toHaveAttribute("aria-invalid"); + }); + }); + }); +}); diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index 1bafcfef9d..bdd2d38b1f 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -16,10 +16,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { isClearable, startContent, endContent, - labelPlacement, hasHelper, - isOutsideLeft, - shouldLabelBeOutside, errorMessage, isInvalid, hideStepper, @@ -116,16 +113,14 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { return (
- {!isOutsideLeft ? labelContent : null} + {labelContent} {innerWrapper}
{helperWrapper}
); }, [ - labelPlacement, helperWrapper, - shouldLabelBeOutside, labelContent, innerWrapper, errorMessage, @@ -136,12 +131,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { getDescriptionProps, ]); - return ( - - {isOutsideLeft ? labelContent : null} - {mainWrapper} - - ); + return {mainWrapper}; }); NumberField.displayName = "NextUI.NumberField"; diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index d7062731cc..0bb244c524 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -208,8 +208,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const isInvalid = validationState === "invalid" || isAriaInvalid; - const labelPlacement = originalProps.labelPlacement ?? "outside"; - const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({isInvalid, validationErrors, validationDetails}) @@ -219,38 +217,21 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; const hasHelper = !!description || !!errorMessage; - const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; - const isOutsideLeft = labelPlacement === "outside-left"; const hasStartContent = !!startContent; - const isLabelOutside = shouldLabelBeOutside - ? labelPlacement === "outside-left" || - hasPlaceholder || - (labelPlacement === "outside" && hasStartContent) - : false; - const isLabelOutsideAsPlaceholder = - labelPlacement === "outside" && !hasPlaceholder && !hasStartContent; const slots = useMemo( () => numberField({ ...variantProps, isInvalid, - labelPlacement, isClearable, disableAnimation, }), - [ - objectToDeps(variantProps), - isInvalid, - labelPlacement, - isClearable, - hasStartContent, - disableAnimation, - ], + [objectToDeps(variantProps), isInvalid, isClearable, hasStartContent, disableAnimation], ); const getBaseProps: PropGetter = useCallback( @@ -541,14 +522,9 @@ export function useNumberField(originalProps: UseNumberFieldProps) { description, startContent, endContent, - labelPlacement, isClearable, hasHelper, hasStartContent, - isLabelOutside, - isOutsideLeft, - isLabelOutsideAsPlaceholder, - shouldLabelBeOutside, hasPlaceholder, isInvalid, errorMessage, From 7d17aef4221772191cc9ff7b8336f6352d63c728 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 27 Dec 2024 21:35:55 +0800 Subject: [PATCH 025/131] refactor(number-field): remove labelPlacement args --- .../stories/number-field.stories.tsx | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 2314296673..6adb74c393 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -38,12 +38,6 @@ export default { }, options: ["sm", "md", "lg"], }, - labelPlacement: { - control: { - type: "select", - }, - options: ["inside", "outside", "outside-left"], - }, isDisabled: { control: { type: "boolean", @@ -103,37 +97,6 @@ const ControlledTemplate = (args) => { ); }; -const LabelPlacementTemplate = (args) => ( -
-
-

Without placeholder

-
- - - -
-
-
-

With placeholder

-
- - - -
-
-
-); - const StartContentTemplate = (args) => (
( ], }} endContent={
} - labelPlacement="outside" placeholder="Enter the amount" />
@@ -466,14 +428,6 @@ export const WithFormatOptions = { }, }; -export const LabelPlacement = { - render: LabelPlacementTemplate, - - args: { - ...defaultProps, - }, -}; - export const Clearable = { render: Template, @@ -492,7 +446,6 @@ export const StartContent = { args: { ...defaultProps, variant: "bordered", - labelPlacement: "outside", }, }; @@ -502,7 +455,6 @@ export const EndContent = { args: { ...defaultProps, variant: "bordered", - labelPlacement: "outside", }, }; @@ -512,7 +464,6 @@ export const StartAndEndContent = { args: { ...defaultProps, variant: "bordered", - labelPlacement: "outside", }, }; From 0ad45c815ba6563030ad90cbac56fc5f159d591f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 13:28:42 +0800 Subject: [PATCH 026/131] feat(number-field): helper text --- .../number-field/src/number-field.tsx | 5 +++- .../number-field/src/use-number-field.ts | 24 +++++++++++++++++-- .../core/theme/src/components/number-field.ts | 6 +++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index bdd2d38b1f..c228878b03 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -13,6 +13,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { Component, label, description, + helperText, isClearable, startContent, endContent, @@ -29,6 +30,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { getMainWrapperProps, getHelperWrapperProps, getDescriptionProps, + getHelperTextProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, @@ -48,7 +50,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { const helperWrapper = useMemo(() => { const shouldShowError = isInvalid && errorMessage; - const hasContent = shouldShowError || description; + const hasContent = shouldShowError || description || helperText; if (!hasHelper || !hasContent) return null; @@ -59,6 +61,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { ) : (
{description}
)} + {helperText &&
{helperText}
}
); }, [ diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 0bb244c524..8a3e9be7ab 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -52,6 +52,10 @@ export interface Props * ``` @@ -112,6 +117,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { baseRef, wrapperRef, description, + helperText, className, classNames, autoFocus, @@ -213,10 +219,10 @@ export function useNumberField(originalProps: UseNumberFieldProps) { ? props.errorMessage({isInvalid, validationErrors, validationDetails}) : props.errorMessage || validationErrors?.join(" "); const isClearable = !!onClear || originalProps.isClearable; - const hasElements = !!label || !!description || !!errorMessage; + const hasElements = !!label || !!description || !!errorMessage || !!helperText; const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; - const hasHelper = !!description || !!errorMessage; + const hasHelper = !!helperText || !!errorMessage; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; @@ -435,6 +441,18 @@ export function useNumberField(originalProps: UseNumberFieldProps) { [slots, classNames?.description], ); + const getHelperTextProps: PropGetter = useCallback( + (props = {}) => { + return { + ...props, + ...descriptionProps, // apply description props + "data-slot": "helper-text", + className: slots.helperText({class: clsx(classNames?.helperText, props?.className)}), + }; + }, + [slots, classNames?.helperText], + ); + const getErrorMessageProps: PropGetter = useCallback( (props = {}) => { return { @@ -520,6 +538,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { domRef, label, description, + helperText, startContent, endContent, isClearable, @@ -540,6 +559,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { getInnerWrapperProps, getHelperWrapperProps, getDescriptionProps, + getHelperTextProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index f280d66d11..a7b372e063 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -8,15 +8,16 @@ import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; * * @example * ```js - * const {base, label, inputWrapper, input, clearButton, description, errorMessage} = numberField({...}) + * const {base, label, inputWrapper, input, clearButton, description, helperText, errorMessage} = numberField({...}) * *
* + * Description *
* * *
- * Description + * Helper text * Invalid input *
* ``` @@ -80,6 +81,7 @@ const numberField = tv({ verticalStepperWrapper: ["flex flex-col h-full"], helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", + helperText: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", }, variants: { From 6f4234282d93847755a982e6613b7a5ad2828c96 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 13:29:12 +0800 Subject: [PATCH 027/131] feat(number-field): revise number field stories --- .../stories/number-field.stories.tsx | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 6adb74c393..29c954e4b9 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -62,7 +62,6 @@ export default { const defaultProps = { ...numberField.defaultVariants, defaultValue: 1024, - label: "Width", }; const Template = (args) => ( @@ -348,12 +347,41 @@ export const Default = { }, }; +export const WithLabel = { + render: Template, + + args: { + ...defaultProps, + label: "Width", + }, +}; + +export const WithDescription = { + render: Template, + + args: { + ...defaultProps, + description: "Specify the width in meters.", + }, +}; + +export const WithHelperText = { + render: Template, + + args: { + ...defaultProps, + helperText: "Width should be between 5 and 50 inches", + }, +}; + export const HorizontalStepper = { render: Template, args: { ...defaultProps, steps: "horizontal", + label: "Horizontal Stepper", + helperText: "Set `steps` to `horizontal` to show the stepper horizontally.", }, }; @@ -363,6 +391,8 @@ export const HideStepper = { args: { ...defaultProps, hideStepper: true, + label: "Hide Stepper", + helperText: "Set `hideStepper` to `true` to hide the stepper.", }, }; @@ -406,15 +436,6 @@ export const WithoutLabel = { }, }; -export const WithDescription = { - render: Template, - - args: { - ...defaultProps, - description: "Enter a number", - }, -}; - export const WithFormatOptions = { render: Template, From 68fd5bed6138297c0eb7929f2f3a24ec7ff42998 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 14:33:09 +0800 Subject: [PATCH 028/131] feat(number-field): description --- packages/components/number-field/src/number-field.tsx | 11 ++++++----- .../components/number-field/src/use-number-field.ts | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index c228878b03..b8e293d39c 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -40,6 +40,10 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { const labelContent = label ? : null; + const descriptionContent = description ? ( +
{description}
+ ) : null; + const end = useMemo(() => { if (isClearable) { return ; @@ -56,11 +60,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { return (
- {shouldShowError ? ( -
{errorMessage}
- ) : ( -
{description}
- )} + {shouldShowError &&
{errorMessage}
} {helperText &&
{helperText}
}
); @@ -117,6 +117,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => {
{labelContent} + {descriptionContent} {innerWrapper}
{helperWrapper} diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 8a3e9be7ab..60496fb47d 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -223,6 +223,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; const hasHelper = !!helperText || !!errorMessage; + const hasDescription = !!description; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; @@ -262,6 +263,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { "data-disabled": dataAttr(originalProps.isDisabled), "data-has-elements": dataAttr(hasElements), "data-has-helper": dataAttr(hasHelper), + "data-has-description": dataAttr(hasDescription), "data-has-label": dataAttr(hasLabel), "data-has-value": dataAttr(!isPlaceholderShown), "data-hidden": dataAttr(isHiddenType), @@ -280,6 +282,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { hasHelper, hasLabel, hasElements, + hasDescription, isPlaceholderShown, hasStartContent, isFocusWithin, @@ -435,7 +438,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { ...props, ...descriptionProps, "data-slot": "description", - className: slots.description({class: clsx(classNames?.description, props?.className)}), + className: slots.description({class: clsx(classNames?.label, props?.className)}), }; }, [slots, classNames?.description], From 8fca2bcc0e01f9bc36bba0d4b793e63a35c73f4e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 14:35:37 +0800 Subject: [PATCH 029/131] refactor(number-field): revise number field stories --- .../stories/number-field.stories.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 29c954e4b9..17b96616c9 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -61,7 +61,7 @@ export default { const defaultProps = { ...numberField.defaultVariants, - defaultValue: 1024, + defaultValue: 24, }; const Template = (args) => ( @@ -286,7 +286,7 @@ const CustomWithClassNamesTemplate = (args) => ( // handleSubmit, // } = useForm({ // defaultValues: { -// withDefaultValue: 1024, +// withDefaultValue: 24, // withoutDefaultValue: "", // requiredField: "", // }, @@ -361,6 +361,7 @@ export const WithDescription = { args: { ...defaultProps, + label: "Width", description: "Specify the width in meters.", }, }; @@ -370,7 +371,11 @@ export const WithHelperText = { args: { ...defaultProps, - helperText: "Width should be between 5 and 50 inches", + label: "Width", + description: "Specify the width in meters.", + helperText: "Width should be between 5 and 50 meters", + minValue: 5, + maxValue: 50, }, }; @@ -425,17 +430,6 @@ export const ReadOnly = { }, }; -export const WithoutLabel = { - render: Template, - - args: { - ...defaultProps, - label: null, - "aria-label": "Email", - placeholder: "Enter a number", - }, -}; - export const WithFormatOptions = { render: Template, From 9897a56b0c67e46855e8bb6646fed0bec6726390 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 19:47:33 +0800 Subject: [PATCH 030/131] feat(theme): numberFieldLabelClasses --- .../core/theme/src/components/number-field.ts | 52 +++++++++---------- packages/core/theme/src/utils/classes.ts | 23 ++++++++ packages/core/theme/src/utils/index.ts | 1 + 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index a7b372e063..6e621d78d6 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -1,7 +1,11 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; +import { + numberFieldLabelClasses, + dataFocusVisibleClasses, + groupDataFocusVisibleClasses, +} from "../utils"; /** * NumberField wrapper **Tailwind Variants** component @@ -25,28 +29,7 @@ import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; const numberField = tv({ slots: { base: "group flex flex-col data-[hidden=true]:hidden relative justify-end", - label: [ - "absolute", - "pointer-events-none", - "origin-top-left", - "flex-shrink-0", - // Using RTL here as Tailwind CSS doesn't support `start` and `end` logical properties for transforms yet. - "rtl:origin-top-right", - "subpixel-antialiased", - "block", - "text-small", - "text-foreground-500", - "pb-0", - "z-20", - "top-1/2", - "-translate-y-1/2", - "group-data-[filled-within=true]:pointer-events-auto", - "group-data-[filled-within=true]:start-0", - "pe-2", - "max-w-full", - "text-ellipsis", - "overflow-hidden", - ], + label: numberFieldLabelClasses, mainWrapper: "h-full flex flex-col", inputWrapper: "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-4 py-3 gap-3", @@ -80,7 +63,7 @@ const numberField = tv({ stepperButton: ["bg-transparent min-w-4 w-4"], verticalStepperWrapper: ["flex flex-col h-full"], helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", - description: "text-tiny text-foreground-400", + description: [...numberFieldLabelClasses, "text-tiny", "text-default-400"], helperText: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", }, @@ -150,24 +133,35 @@ const numberField = tv({ label: [ "start-2", "text-tiny", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_16px)]", + "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_32px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", inputWrapper: "h-8 min-h-8 px-2 rounded-small", input: "text-small", clearButton: "text-medium", + description: [ + "start-1", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + ], }, md: { label: [ "start-3", "end-auto", - "text-small", "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", + "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_36px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", inputWrapper: "h-10 min-h-10 rounded-medium", input: "text-small", clearButton: "text-large", + description: [ + "start-2", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", + "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", + ], }, lg: { label: [ @@ -175,11 +169,17 @@ const numberField = tv({ "end-auto", "text-medium", "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", + "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_40px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", inputWrapper: "h-12 min-h-12 rounded-large", input: "text-medium", clearButton: "text-large", + description: [ + "start-2", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", + "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", + ], }, }, radius: { diff --git a/packages/core/theme/src/utils/classes.ts b/packages/core/theme/src/utils/classes.ts index 2711a48a3b..a3a580dfe4 100644 --- a/packages/core/theme/src/utils/classes.ts +++ b/packages/core/theme/src/utils/classes.ts @@ -102,3 +102,26 @@ export const hiddenInputClasses = [ // Disabled state "disabled:cursor-default", ]; + +export const numberFieldLabelClasses = [ + "absolute", + "pointer-events-none", + "origin-top-left", + "flex-shrink-0", + // Using RTL here as Tailwind CSS doesn't support `start` and `end` logical properties for transforms yet. + "rtl:origin-top-right", + "subpixel-antialiased", + "block", + "text-small", + "text-default-foreground", + "pb-0", + "z-20", + "top-1/2", + "-translate-y-1/2", + "group-data-[filled-within=true]:pointer-events-auto", + "group-data-[filled-within=true]:start-0", + "pe-2", + "max-w-full", + "text-ellipsis", + "overflow-hidden", +]; diff --git a/packages/core/theme/src/utils/index.ts b/packages/core/theme/src/utils/index.ts index f80e69ec6b..0b2b3cf528 100644 --- a/packages/core/theme/src/utils/index.ts +++ b/packages/core/theme/src/utils/index.ts @@ -8,6 +8,7 @@ export { absoluteFullClasses, collapseAdjacentVariantBorders, hiddenInputClasses, + numberFieldLabelClasses, } from "./classes"; export type {SlotsToClasses} from "./types"; export {colorVariants} from "./variants"; From d085088bb0a52327719c19e9f169b39d30ce167d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 20:13:22 +0800 Subject: [PATCH 031/131] fix(number-field): incorrect button props --- packages/components/number-field/src/number-field.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index b8e293d39c..97754e04c0 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -88,7 +88,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { if (steps === "horizontal") { return (
- + {startContent} {end} From 61c991ef244fc9843862401ad95d5b83a448623e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 28 Dec 2024 20:47:18 +0800 Subject: [PATCH 032/131] fix(number-field): typing issue on stepper buttons --- .../src/number-field-horiztonal-stepper.tsx | 27 +++++++++---------- .../src/number-field-vertical-stepper.tsx | 27 +++++++++---------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/components/number-field/src/number-field-horiztonal-stepper.tsx b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx index 15c1da421e..4be9828a2a 100644 --- a/packages/components/number-field/src/number-field-horiztonal-stepper.tsx +++ b/packages/components/number-field/src/number-field-horiztonal-stepper.tsx @@ -1,26 +1,23 @@ import type {AriaButtonProps} from "@react-types/button"; +import type {ButtonProps} from "@nextui-org/button"; -import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; import {Button} from "@nextui-org/button"; import {ChevronLeftIcon, ChevronRightIcon} from "@nextui-org/shared-icons"; -export interface Props extends Omit, keyof AriaButtonProps> { +interface NumberFieldHorizontalStepperProps extends Omit { direction: "left" | "right"; } -export type NumberFieldHorizontalStepperProps = Props & AriaButtonProps; - -const NumberFieldHorizontalStepper = forwardRef<"button", NumberFieldHorizontalStepperProps>( - (props) => { - const {direction, ...otherProps} = props; - - return ( - - ); - }, -); +const NumberFieldHorizontalStepper = ({ + direction, + ...otherProps +}: NumberFieldHorizontalStepperProps) => { + return ( + + ); +}; NumberFieldHorizontalStepper.displayName = "NextUI.NumberFieldHorizontalStepper"; diff --git a/packages/components/number-field/src/number-field-vertical-stepper.tsx b/packages/components/number-field/src/number-field-vertical-stepper.tsx index b343b88c6c..4227bf3093 100644 --- a/packages/components/number-field/src/number-field-vertical-stepper.tsx +++ b/packages/components/number-field/src/number-field-vertical-stepper.tsx @@ -1,26 +1,23 @@ import type {AriaButtonProps} from "@react-types/button"; +import type {ButtonProps} from "@nextui-org/button"; -import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; import {Button} from "@nextui-org/button"; import {ChevronUpIcon, ChevronDownIcon} from "@nextui-org/shared-icons"; -export interface Props extends Omit, keyof AriaButtonProps> { +export interface NumberFieldVerticalStepperProps extends Omit { direction: "up" | "down"; } -export type NumberFieldVerticalStepperProps = Props & AriaButtonProps; - -const NumberFieldVerticalStepper = forwardRef<"button", NumberFieldVerticalStepperProps>( - (props) => { - const {direction, ...otherProps} = props; - - return ( - - ); - }, -); +const NumberFieldVerticalStepper = ({ + direction, + ...otherProps +}: NumberFieldVerticalStepperProps) => { + return ( + + ); +}; NumberFieldVerticalStepper.displayName = "NextUI.NumberFieldVerticalStepper"; From 2f78e9c9a89cd5e6a314cca09118d04c82df99c6 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 31 Dec 2024 21:25:51 +0800 Subject: [PATCH 033/131] chore(number-field): add aria-label --- .../components/number-field/stories/number-field.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 17b96616c9..d922b9f67d 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -344,6 +344,7 @@ export const Default = { args: { ...defaultProps, + "aria-label": "Width", }, }; From 04e6bc9a5abb4f003e3de5f2880375c8aabe3f32 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 31 Dec 2024 21:28:05 +0800 Subject: [PATCH 034/131] refactor(number-field): merge props --- .../components/number-field/src/use-number-field.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 60496fb47d..34297f9926 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -154,8 +154,8 @@ export function useNumberField(originalProps: UseNumberFieldProps) { onChange: onValueChange, }); - let { - // groupProps, + const { + groupProps, labelProps, inputProps, incrementButtonProps, @@ -390,7 +390,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const getInnerWrapperProps: PropGetter = useCallback( (props = {}) => { return { - ...props, ref: innerWrapperRef, "data-slot": "inner-wrapper", onClick: (e) => { @@ -401,6 +400,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { className: slots.innerWrapper({ class: clsx(classNames?.innerWrapper, props?.className), }), + ...mergeProps(groupProps, props), }; }, [slots, classNames?.innerWrapper], @@ -492,8 +492,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { className: slots.verticalStepperWrapper({ class: clsx(classNames?.verticalStepperWrapper, props?.className), }), - // TODO: check press props & focus props - ...incrementButtonProps, }; }, [slots], @@ -511,7 +509,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { // class: clsx(classNames?.stepperIncreaseButton, props?.className), }), // TODO: check press props & focus props - ...incrementButtonProps, + ...mergeProps(incrementButtonProps, props), }; }, [slots], @@ -520,7 +518,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const getStepperDecreaseButtonProps: PropGetter = useCallback( (props = {}) => { return { - ...props, type: "button", disabled: originalProps.isDisabled, "aria-label": "decrease value", @@ -529,7 +526,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { // class: clsx(classNames?.stepperDereaseButton, props?.className), }), // TODO: check press props & focus props - ...decrementButtonProps, + ...mergeProps(decrementButtonProps, props), }; }, [slots], From 29c32bfa241e2ffa738fdb5c8a2b897b91477947 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 31 Dec 2024 23:28:23 +0800 Subject: [PATCH 035/131] fix(number-field): pass originalProps instead --- packages/components/number-field/src/use-number-field.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 34297f9926..696898964d 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -148,7 +148,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const innerWrapperRef = useDOMRef(innerWrapperRefProp); const state = useNumberFieldState({ - ...props, + ...originalProps, validationBehavior, locale: "en-US", onChange: onValueChange, @@ -165,7 +165,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { isInvalid: isAriaInvalid, validationErrors, validationDetails, - } = useAriaNumberField(props, state, domRef); + } = useAriaNumberField(originalProps, state, domRef); const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; const isFilled = !isEmpty(inputValue); From 2da4d9ce7d03ecba43b8bc37129490cf55f26960 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 31 Dec 2024 23:29:29 +0800 Subject: [PATCH 036/131] chore(number-field): revise Required story args --- .../components/number-field/stories/number-field.stories.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index d922b9f67d..1938e6f3b9 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -408,6 +408,8 @@ export const Required = { args: { ...defaultProps, isRequired: true, + defaultValue: undefined, + placeholder: "Enter a number", }, }; From 1c3d4166b4ee20bb414822377412f3126158cee8 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 11:27:04 +0800 Subject: [PATCH 037/131] feat(number-field): add WithStepValue & WithWheelDisabled & revise stories --- .../stories/number-field.stories.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 1938e6f3b9..9ee8fad4f5 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -380,6 +380,29 @@ export const WithHelperText = { }, }; +export const WithStepValue = { + render: Template, + + args: { + ...defaultProps, + label: "Width", + step: 10, + helperText: "Set `step` to `10` to increment / decrement the value by 10.", + }, +}; + +export const WithWheelDisabled = { + render: Template, + + args: { + ...defaultProps, + label: "Width", + step: 10, + helperText: "Set `isWheelDisabled` to `true` to disable the wheel.", + isWheelDisabled: true, + }, +}; + export const HorizontalStepper = { render: Template, @@ -441,7 +464,9 @@ export const WithFormatOptions = { label: "Transaction amount", formatOptions: { style: "currency", - currency: "USD", + currency: "EUR", + currencyDisplay: "code", + currencySign: "accounting", }, }, }; @@ -505,6 +530,7 @@ export const WithErrorMessageFunction = { type: "number", isRequired: true, label: "Number", + validationBehavior: "native", placeholder: "Enter a number(0-100)", errorMessage: (value: ValidationResult) => { if (value.validationDetails.rangeOverflow) { From 9af3f4bfef261839a39c4af4387cae95ef874a73 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 11:28:38 +0800 Subject: [PATCH 038/131] chore(number-field): add label to Required --- .../components/number-field/stories/number-field.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 9ee8fad4f5..391fde91c6 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -430,6 +430,7 @@ export const Required = { args: { ...defaultProps, + label: "Width", isRequired: true, defaultValue: undefined, placeholder: "Enter a number", From 1c3858a84a42308eeab2c8bde2e31f6e38863a2c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 12:41:22 +0800 Subject: [PATCH 039/131] feat(docs): number-field doc page --- .../content/docs/components/number-field.mdx | 398 ++++++++++++++++++ 1 file changed, 398 insertions(+) diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-field.mdx index 36350d513c..4c201240dd 100644 --- a/apps/docs/content/docs/components/number-field.mdx +++ b/apps/docs/content/docs/components/number-field.mdx @@ -27,3 +27,401 @@ The numeric input component is designed for users to enter a number, and increas bun: "bun add @nextui-org/number-field" }} /> + +## Usage + + + +### Disabled + + + +### Read Only + + + +### Required + +If you pass the `isRequired` property to the input, it will have a `danger` asterisk at +the end of the label and the input will be required. + + + +### Sizes + + + +### Colors + + + +### Variants + + + +### Radius + + + +### Clear Button + +If you pass the `isClearable` property to NumberField, it will have a clear button at the +end of NumberField, it will be visible when NumberField has a value. + + + +### Hide Stepper + +You can hide the stepper buttons by passing the `hideStepper` property. + + + +### Start & End Content + +You can use the `startContent` and `endContent` properties to add content to the start and end of NumberField. + + + +### With Label + +You can add a label to NumberField by passing the `label` property. + + + +### With Description + +You can add a description to NumberField by passing the `description` property. + + + +### With Helper Text + +You can add a helper text to NumberField by passing the `helperText` property. + + + +### With Min Value + +You can set the minimum value of the input by passing the `minValue` property. + + + +### With Max Value + +You can set the maximum value of the input by passing the `maxValue` property. + + + +### With Wheel Disabled + +You can disablechanging the vaule with scroll in NumberField by passing the `isWheelDisabled` property. + + + +### With Helper Text + +You can add a helper text to NumberField by passing the `helperText` property. + + + +### With Error Message + +You can combine the `isInvalid` and `errorMessage` properties to show an invalid input. `errorMessage` is only shown when `isInvalid` is set to `true`. + + + +### Controlled + +You can use the `value` and `onValueChange` properties to control NumberField value. + + + +> **Note**: NextUI `NumberField` also supports native events like `onChange`, useful for form libraries +> such as [Formik](https://formik.org/) and [React Hook Form](https://react-hook-form.com/). + +### With Form + +`NumberField` can be used with a `Form` component to leverage form state management. By default, `Form` components use `validationBehavior="aria"`, which will not block form submission if any inputs are invalid. For more on form and validation behaviors, see the [Forms](/docs/guide/forms) guide. + +#### Custom Validation + +In addition to built-in constraints, you can provide a function to the `validate` property for custom validation. + + + +#### Realtime Validation + +If you want to display validation errors while the user is typing, you can control the field value and use the `isInvalid` prop along with the `errorMessage` prop. + + + +#### Server Validation + +Client-side validation provides immediate feedback, but you should also validate data on the server to ensure accuracy and security. +NextUI allows you to display server-side validation errors by using the `validationErrors` prop in the `Form` component. +This prop should be an object where each key is the field `name` and the value is the error message. + + + +## Slots + +- **base**: Input wrapper, it handles alignment, placement, and general appearance. +- **label**: Label of the input, it is the one that is displayed above, inside or left of the input. +- **mainWrapper**: Wraps the `inputWrapper` +- **inputWrapper**: Wraps the `label` (when it is inside) and the `innerWrapper`. +- **innerWrapper**: Wraps the `input`, the `startContent` and the `endContent`. +- **input**: The input element. +- **clearButton**: The clear button, it is at the end of the input. +- **helperWrapper**: Wraps the `helperText` and the `errorMessage`. +- **description**: The description of NumberField. +- **helperText**: The helper text of NumberField. +- **errorMessage**: The error message of NumberField. + +### Custom Styles + +You can customize the `NumberField` component by passing custom Tailwind CSS classes to the component slots. + + + +### Custom Implementation + +In case you need to customize the input even further, you can use the `useInput` hook to create your own implementation. + + + + + +## Data Attributes + +`NumberField` has the following attributes on the `base` element: + +- **data-invalid**: + When the input is invalid. Based on `isInvalid` prop. +- **data-required**: + When the input is required. Based on `isRequired` prop. +- **data-readonly**: + When the input is readonly. Based on `isReadOnly` prop. +- **data-hover**: + When the input is being hovered. Based on [useHover](https://react-spectrum.adobe.com/react-aria/useHover.html) +- **data-focus**: + When the input is being focused. Based on [useFocusRing](https://react-spectrum.adobe.com/react-aria/useFocusRing.html). +- **data-focus-within**: + When the input is being focused or any of its children. Based on [useFocusWithin](https://react-spectrum.adobe.com/react-aria/useFocusWithin.html). +- **data-focus-visible**: + When the input is being focused with the keyboard. Based on [useFocusRing](https://react-spectrum.adobe.com/react-aria/useFocusRing.html). +- **data-disabled**: + When the input is disabled. Based on `isDisabled` prop. + + + +## Accessibility + +- Built with a native `` element with `type="number"`. +- Visual and ARIA labeling support. +- Change, clipboard, composition, selection, and input event support. +- Required and invalid states exposed to assistive technology via ARIA. +- Support for description, helper text, and error message linked to the input via ARIA. + + + +## API + +### NumberField Props + + ReactNode)", + description: "An error message for the input. It is only shown when isInvalid is set to true", + default: "-" + }, + { + attribute: "validate", + type: "(value: string) => ValidationError | true | null | undefined", + description: "Validate input values when committing (e.g. on blur), returning error messages for invalid values.", + default: "-" + }, + { + attribute: "validationBehavior", + type: "native | aria", + description: "Whether to use native HTML form validation or ARIA validation. When wrapped in a Form component, the default is `aria`. Otherwise, the default is `native`.", + default: "native" + }, + { + attribute: "minValue", + type: "number", + description: "The minimum value of the input.", + default: "-" + }, + { + attribute: "maxValue", + type: "number", + description: "The maximum value of the input.", + default: "-" + }, + { + attribute: "startContent", + type: "ReactNode", + description: "Element to be rendered in the left side of the input.", + default: "-" + }, + { + attribute: "endContent", + type: "ReactNode", + description: "Element to be rendered in the right side of the input.", + default: "-" + }, + { + attribute: "fullWidth", + type: "boolean", + description: "Whether the input should take up the width of its parent.", + default: "true" + }, + { + attribute: "isClearable", + type: "boolean", + description: "Whether the input should have a clear button.", + default: "false" + }, + { + attribute: "isRequired", + type: "boolean", + description: "Whether user input is required on the input before form submission.", + default: "false" + }, + { + attribute: "isReadOnly", + type: "boolean", + description: "Whether the input can be selected but not changed by the user.", + default: "false" + }, + { + attribute: "isDisabled", + type: "boolean", + description: "Whether the input is disabled.", + default: "false" + }, + { + attribute: "isInvalid", + type: "boolean", + description: "Whether the input is invalid.", + default: "false" + }, + { + attribute: "baseRef", + type: "RefObject", + description: "The ref to the base element.", + default: "-" + }, + { + attribute: "disableAnimation", + type: "boolean", + description: "Whether the input should be animated.", + default: "false" + }, + { + attribute: "classNames", + type: "Partial>", + description: "Allows to set custom class names for the Input slots.", + default: "-" + } + ]} +/> + +### NumberField Events + +", + description: "Handler that is called when the element's value changes. You can pull out the new value by accessing event.target.value (string).", + default: "-" + }, + { + attribute: "onValueChange", + type: "(value: number) => void", + description: "Handler that is called when the element's value changes.", + default: "-" + }, + { + attribute: "onClear", + type: "() => void", + description: "Handler that is called when the clear button is clicked.", + default: "-" + } + ]} +/> + From 54488787dc0205a7c43cbf3b06b3580d71a1c210 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 14:20:54 +0800 Subject: [PATCH 040/131] fix(number-field): typing issue --- packages/components/number-field/src/use-number-field.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 696898964d..92cde9d797 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -20,12 +20,11 @@ import {useMemo, Ref, useCallback, useState} from "react"; import {chain, mergeProps} from "@react-aria/utils"; import {FormContext, useSlottedContext} from "@nextui-org/form"; -export interface Props - extends Omit, keyof NumberFieldVariantProps> { +export interface Props extends Omit, keyof NumberFieldVariantProps> { /** * Ref to the DOM node. */ - ref?: Ref; + ref?: Ref; /** * Ref to the container DOM node. */ From b7028a68c95543c5f4bb6f9a35d21e5d5e333862 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 14:21:10 +0800 Subject: [PATCH 041/131] fix(number-field): test cases --- .../__tests__/number-field.test.tsx | 168 +++++++----------- 1 file changed, 65 insertions(+), 103 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index c3b57009ba..9c1eb07d9f 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -4,11 +4,11 @@ import userEvent, {UserEvent} from "@testing-library/user-event"; import {useForm} from "react-hook-form"; import {Form} from "@nextui-org/form"; -import {Input} from "../src"; +import {NumberField} from "../src"; describe("Input", () => { it("should render correctly", () => { - const wrapper = render(); + const wrapper = render(); expect(() => wrapper.unmount()).not.toThrow(); }); @@ -16,30 +16,32 @@ describe("Input", () => { it("ref should be forwarded", () => { const ref = React.createRef(); - render(); + render(); expect(ref.current).not.toBeNull(); }); it("should have aria-invalid when invalid", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-invalid", "true"); }); it("should have aria-readonly when isReadOnly", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-readonly", "true"); }); it("should have disabled attribute when isDisabled", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("disabled"); }); it("should disable the clear button when isDisabled", () => { - const {getByRole} = render(); + const {getByRole} = render( + , + ); const clearButton = getByRole("button"); @@ -47,7 +49,7 @@ describe("Input", () => { }); it("should not allow clear button to be focusable", () => { - const {getByRole} = render(); + const {getByRole} = render(); const clearButton = getByRole("button"); @@ -55,33 +57,39 @@ describe("Input", () => { }); it("should have required attribute when isRequired with native validationBehavior", () => { - const {container} = render(); + const {container} = render( + , + ); expect(container.querySelector("input")).toHaveAttribute("required"); expect(container.querySelector("input")).not.toHaveAttribute("aria-required"); }); it("should have aria-required attribute when isRequired with aria validationBehavior", () => { - const {container} = render(); + const {container} = render( + , + ); expect(container.querySelector("input")).not.toHaveAttribute("required"); expect(container.querySelector("input")).toHaveAttribute("aria-required", "true"); }); it("should have aria-describedby when description is provided", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); }); it("should have aria-describedby when errorMessage is provided", () => { - const {container} = render(); + const {container} = render( + , + ); expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); }); it("should have the same aria-labelledby as label id", () => { - const {container} = render(); + const {container} = render(); const labelId = container.querySelector("label")?.id; const labelledBy = container.querySelector("input")?.getAttribute("aria-labelledby"); @@ -89,36 +97,10 @@ describe("Input", () => { expect(labelledBy?.includes(labelId as string)).toBeTruthy(); }); - it("should have the correct type attribute", () => { - const {container} = render(); - - expect(container.querySelector("input")).toHaveAttribute("type", "email"); - - const {container: container2} = render(); - - expect(container2.querySelector("input")).toHaveAttribute("type", "number"); - - const {container: container3} = render(); - - expect(container3.querySelector("input")).toHaveAttribute("type", "password"); - - const {container: container4} = render(); - - expect(container4.querySelector("input")).toHaveAttribute("type", "search"); - - const {container: container5} = render(); - - expect(container5.querySelector("input")).toHaveAttribute("type", "tel"); - - const {container: container6} = render(); - - expect(container6.querySelector("input")).toHaveAttribute("type", "text"); - }); - it("should call dom event handlers only once", () => { const onFocus = jest.fn(); - const {container} = render(); + const {container} = render(); container.querySelector("input")?.focus(); container.querySelector("input")?.blur(); @@ -129,12 +111,12 @@ describe("Input", () => { it("ref should update the value", () => { const ref = React.createRef(); - const {container} = render(); + const {container} = render(); if (!ref.current) { throw new Error("ref is null"); } - const value = "value"; + const value = "1234"; ref.current!.value = value; @@ -149,11 +131,12 @@ describe("Input", () => { const ref = React.createRef(); const {getByRole} = render( - , ); @@ -171,45 +154,19 @@ describe("Input", () => { expect(onClear).toHaveBeenCalledTimes(1); }); - it("should not display input with hidden type", async () => { - const wrapper = render( - <> - - - , - ); - - const {container} = wrapper; - - const inputBaseWrappers = container.querySelectorAll("[data-slot='base']"); - - expect(inputBaseWrappers).toHaveLength(2); - - const inputs = container.querySelectorAll("input"); - - expect(inputs).toHaveLength(2); - - expect(inputBaseWrappers[0]).toHaveAttribute("data-hidden"); - - expect(inputBaseWrappers[1]).not.toHaveAttribute("data-hidden"); - - expect(inputs[0]).not.toBeVisible(); - - expect(inputs[1]).toBeVisible(); - }); - it("should disable clear button when isReadOnly is true", async () => { const onClear = jest.fn(); const ref = React.createRef(); const {getByRole} = render( - , ); @@ -226,7 +183,7 @@ describe("Input", () => { }); }); -describe("Input with React Hook Form", () => { +describe("NumberField with React Hook Form", () => { let input1: HTMLInputElement; let input2: HTMLInputElement; let input3: HTMLInputElement; @@ -237,9 +194,9 @@ describe("Input with React Hook Form", () => { const {result} = renderHook(() => useForm({ defaultValues: { - withDefaultValue: "wkw", - withoutDefaultValue: "", - requiredField: "", + withDefaultValue: 1234, + withoutDefaultValue: undefined, + requiredField: undefined, }, }), ); @@ -254,13 +211,13 @@ describe("Input with React Hook Form", () => { render(
- - + - { }); it("should work with defaultValues", () => { - expect(input1).toHaveValue("wkw"); - expect(input2).toHaveValue(""); - expect(input3).toHaveValue(""); + expect(input1).toHaveValue(1234); + expect(input2).toHaveValue(undefined); + expect(input3).toHaveValue(undefined); }); it("should not submit form when required field is empty", async () => { @@ -291,7 +248,7 @@ describe("Input with React Hook Form", () => { }); it("should submit form when required field is not empty", async () => { - fireEvent.change(input3, {target: {value: "updated"}}); + fireEvent.change(input3, {target: {value: 123}}); const user = userEvent.setup(); @@ -311,7 +268,7 @@ describe("Input with React Hook Form", () => { it("supports isRequired", async () => { const {getByTestId} = render( - + , ); @@ -332,7 +289,7 @@ describe("Input with React Hook Form", () => { "Constraints not satisfied", ); - await user.keyboard("hello"); + await user.keyboard("234"); expect(input).toHaveAttribute("aria-describedby"); expect(input.validity.valid).toBe(true); @@ -345,11 +302,11 @@ describe("Input with React Hook Form", () => { it("supports validate function", async () => { const {getByTestId} = render(
- (v === "Foo" ? "Invalid name" : null)} + validate={(v) => (v === "Foo" ? "Invalid width" : null)} validationBehavior="native" />
, @@ -367,7 +324,7 @@ describe("Input with React Hook Form", () => { expect(document.activeElement).toBe(input); expect(input).toHaveAttribute("aria-describedby"); expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( - "Invalid name", + "Invalid width", ); await user.keyboard("hello"); @@ -386,13 +343,18 @@ describe("Input with React Hook Form", () => { let onSubmit = (e: React.FormEvent) => { e.preventDefault(); setServerErrors({ - name: "Invalid name.", + name: "Invalid width.", }); }; return (
- + @@ -414,7 +376,7 @@ describe("Input with React Hook Form", () => { expect(input).toHaveAttribute("aria-describedby"); expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( - "Invalid name.", + "Invalid width.", ); expect(input.validity.valid).toBe(false); @@ -427,7 +389,7 @@ describe("Input with React Hook Form", () => { expect(document.activeElement).toBe(input); expect(input).toHaveAttribute("aria-describedby"); expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( - "Invalid name.", + "Invalid width.", ); expect(input.validity.valid).toBe(false); @@ -443,11 +405,11 @@ describe("Input with React Hook Form", () => { it("supports validate function", async () => { const {getByTestId} = render( - (v === "Foo" ? "Invalid name" : null)} + defaultValue={1234} + label="Width" + validate={(v) => (v === 1234 ? "Invalid width" : null)} /> , ); @@ -457,7 +419,7 @@ describe("Input with React Hook Form", () => { expect(input).toHaveAttribute("aria-describedby"); expect(input).toHaveAttribute("aria-invalid", "true"); expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( - "Invalid name", + "Invalid width", ); expect(input.validity.valid).toBe(true); @@ -470,8 +432,8 @@ describe("Input with React Hook Form", () => { it("supports server validation", async () => { const {getByTestId} = render( -
- + + , ); @@ -480,7 +442,7 @@ describe("Input with React Hook Form", () => { expect(input).toHaveAttribute("aria-describedby"); expect(input).toHaveAttribute("aria-invalid", "true"); expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent( - "Invalid name", + "Invalid width", ); await user.tab(); From ed783f3eae51b2bfa47fb3203a54639e660f60a4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:19:02 +0800 Subject: [PATCH 042/131] fix(number-field): user.keyboard & defaultValue --- .../number-field/__tests__/number-field.test.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index 9c1eb07d9f..9625ebf3d5 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -289,7 +289,7 @@ describe("NumberField with React Hook Form", () => { "Constraints not satisfied", ); - await user.keyboard("234"); + await user.keyboard("1234"); expect(input).toHaveAttribute("aria-describedby"); expect(input.validity.valid).toBe(true); @@ -304,9 +304,9 @@ describe("NumberField with React Hook Form", () => {
(v === "Foo" ? "Invalid width" : null)} + validate={(v) => (v === 1234 ? "Invalid width" : null)} validationBehavior="native" /> , @@ -327,7 +327,7 @@ describe("NumberField with React Hook Form", () => { "Invalid width", ); - await user.keyboard("hello"); + await user.keyboard("1234"); expect(input).toHaveAttribute("aria-describedby"); expect(input.validity.valid).toBe(true); @@ -393,7 +393,7 @@ describe("NumberField with React Hook Form", () => { ); expect(input.validity.valid).toBe(false); - await user.keyboard("hello"); + await user.keyboard("1234"); await user.tab(); expect(input).not.toHaveAttribute("aria-describedby"); @@ -424,7 +424,7 @@ describe("NumberField with React Hook Form", () => { expect(input.validity.valid).toBe(true); await user.tab(); - await user.keyboard("hello"); + await user.keyboard("1234"); // TODO: fix this // expect(input).not.toHaveAttribute("aria-describedby"); // expect(input).not.toHaveAttribute("aria-invalid"); @@ -446,7 +446,7 @@ describe("NumberField with React Hook Form", () => { ); await user.tab(); - await user.keyboard("hello"); + await user.keyboard("1234"); await user.tab(); // TODO: fix this From 02ff3898f17dae133386fc364d57f162dd223d99 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:22:34 +0800 Subject: [PATCH 043/131] fix(number-field): should work with defaultValues --- .../components/number-field/__tests__/number-field.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index 9625ebf3d5..a1f7efe983 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -234,9 +234,9 @@ describe("NumberField with React Hook Form", () => { }); it("should work with defaultValues", () => { - expect(input1).toHaveValue(1234); - expect(input2).toHaveValue(undefined); - expect(input3).toHaveValue(undefined); + expect(input1).toHaveValue("1234"); + expect(input2).not.toHaveValue(); + expect(input3).not.toHaveValue(); }); it("should not submit form when required field is empty", async () => { From c628b0cef7cd0a53cdc3252a0978658cc635f7e1 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:27:19 +0800 Subject: [PATCH 044/131] chore(number-field): add type: number --- packages/components/number-field/src/use-number-field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 92cde9d797..f4cfea5547 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -333,6 +333,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { onChange: chain(inputProps.onChange, onChange), value: inputValue, ref: domRef, + type: "number", }; }, [ From fb82263a098d00e42afef10bd4c6347a266b93d5 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:28:24 +0800 Subject: [PATCH 045/131] chore(number-field): remove hidden related code --- packages/components/number-field/src/use-number-field.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index f4cfea5547..a618252398 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -111,7 +111,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const { ref, as, - type, label, baseRef, wrapperRef, @@ -169,7 +168,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; const isFilled = !isEmpty(inputValue); const isFilledWithin = isFilled || isFocusWithin; - const isHiddenType = type === "hidden"; const baseStyles = clsx(classNames?.base, className, isFilled ? "is-filled" : ""); @@ -265,7 +263,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { "data-has-description": dataAttr(hasDescription), "data-has-label": dataAttr(hasLabel), "data-has-value": dataAttr(!isPlaceholderShown), - "data-hidden": dataAttr(isHiddenType), ...focusWithinProps, ...props, }; @@ -289,7 +286,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { isFilledWithin, hasPlaceholder, focusWithinProps, - isHiddenType, originalProps.isReadOnly, originalProps.isRequired, originalProps.isDisabled, From 59433b0399b15832b6f42645f957eadd8a972fdd Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:37:15 +0800 Subject: [PATCH 046/131] fix(number-field): numeric value --- .../components/number-field/__tests__/number-field.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index a1f7efe983..12b5755e99 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -234,7 +234,7 @@ describe("NumberField with React Hook Form", () => { }); it("should work with defaultValues", () => { - expect(input1).toHaveValue("1234"); + expect(input1).toHaveValue(1234); expect(input2).not.toHaveValue(); expect(input3).not.toHaveValue(); }); @@ -327,7 +327,7 @@ describe("NumberField with React Hook Form", () => { "Invalid width", ); - await user.keyboard("1234"); + await user.keyboard("4321"); expect(input).toHaveAttribute("aria-describedby"); expect(input.validity.valid).toBe(true); From 498e02df8fbd6eb26eb73e27c4aee60839c9537b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 16:38:04 +0800 Subject: [PATCH 047/131] chore(changeset): add changeset --- .changeset/witty-flies-reflect.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/witty-flies-reflect.md diff --git a/.changeset/witty-flies-reflect.md b/.changeset/witty-flies-reflect.md new file mode 100644 index 0000000000..47907bceb7 --- /dev/null +++ b/.changeset/witty-flies-reflect.md @@ -0,0 +1,7 @@ +--- +"@nextui-org/number-field": patch +"@nextui-org/shared-icons": patch +"@nextui-org/theme": patch +--- + +introduce NumberField From 439eee5f235212ad155e40a2813109e76b2eab41 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 17:16:42 +0800 Subject: [PATCH 048/131] feat(deps): add "@nextui-org/number-field" to docs --- apps/docs/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/docs/package.json b/apps/docs/package.json index 4c6e6b7a16..a1db1ff63c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -39,6 +39,7 @@ "@nextui-org/use-clipboard": "workspace:*", "@nextui-org/use-infinite-scroll": "workspace:*", "@nextui-org/use-is-mobile": "workspace:*", + "@nextui-org/number-field": "workspace:*", "clsx": "^1.2.1", "@radix-ui/react-scroll-area": "^1.0.5", "@react-aria/focus": "3.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5c8dc5a3d..6932f6e501 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -307,6 +307,9 @@ importers: '@nextui-org/listbox': specifier: workspace:* version: link:../../packages/components/listbox + '@nextui-org/number-field': + specifier: workspace:* + version: link:../../packages/components/number-field '@nextui-org/react': specifier: workspace:* version: link:../../packages/core/react From 5b269727e496453118b40ebef92d3430aaa2e715 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 17:30:33 +0800 Subject: [PATCH 049/131] feat(react): export `@nextui-org/number-field` --- packages/core/react/package.json | 1 + packages/core/react/src/index.ts | 1 + pnpm-lock.yaml | 3 +++ 3 files changed, 5 insertions(+) diff --git a/packages/core/react/package.json b/packages/core/react/package.json index a1ab0d2f71..10275fbf39 100644 --- a/packages/core/react/package.json +++ b/packages/core/react/package.json @@ -88,6 +88,7 @@ "@nextui-org/drawer": "workspace:*", "@nextui-org/form": "workspace:*", "@nextui-org/alert": "workspace:*", + "@nextui-org/number-field": "workspace:*", "@react-aria/visually-hidden": "3.8.18" }, "peerDependencies": { diff --git a/packages/core/react/src/index.ts b/packages/core/react/src/index.ts index ff125e4378..2f487e1751 100644 --- a/packages/core/react/src/index.ts +++ b/packages/core/react/src/index.ts @@ -47,6 +47,7 @@ export * from "@nextui-org/form"; export * from "@nextui-org/alert"; export * from "@nextui-org/drawer"; export * from "@nextui-org/input-otp"; +export * from "@nextui-org/number-field"; /** * React Aria - Exports diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6932f6e501..a2ad822e55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3254,6 +3254,9 @@ importers: '@nextui-org/navbar': specifier: workspace:* version: link:../../components/navbar + '@nextui-org/number-field': + specifier: workspace:* + version: link:../../components/number-field '@nextui-org/pagination': specifier: workspace:* version: link:../../components/pagination From cf7e2e3599be76f463b1efc6d5e90656cf902de7 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 17:31:10 +0800 Subject: [PATCH 050/131] feat(changeset): add @nextui-org/react --- .changeset/witty-flies-reflect.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/witty-flies-reflect.md b/.changeset/witty-flies-reflect.md index 47907bceb7..be9919ee5a 100644 --- a/.changeset/witty-flies-reflect.md +++ b/.changeset/witty-flies-reflect.md @@ -2,6 +2,7 @@ "@nextui-org/number-field": patch "@nextui-org/shared-icons": patch "@nextui-org/theme": patch +"@nextui-org/react": minor --- introduce NumberField From 7cf4fd4b00ff01fdb70f53665e632d3b82197b2f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 18:32:49 +0800 Subject: [PATCH 051/131] feat(docs): number-field examples --- .../number-field/clear-button.raw.jsx | 16 ++ .../components/number-field/clear-button.ts | 9 + .../components/number-field/colors.raw.jsx | 20 +++ .../content/components/number-field/colors.ts | 9 + .../number-field/controlled.raw.jsx | 17 ++ .../components/number-field/controlled.ts | 9 + .../number-field/custom-impl.raw.jsx | 162 ++++++++++++++++++ .../components/number-field/custom-impl.ts | 9 + .../number-field/custom-styles.raw.jsx | 68 ++++++++ .../components/number-field/custom-styles.ts | 9 + .../number-field/custom-validation.raw.jsx | 42 +++++ .../number-field/custom-validation.ts | 9 + .../number-field/description.raw.jsx | 12 ++ .../components/number-field/description.ts | 9 + .../components/number-field/disabled.raw.jsx | 12 ++ .../components/number-field/disabled.ts | 9 + .../number-field/error-message.raw.jsx | 15 ++ .../components/number-field/error-message.ts | 9 + .../number-field/helper-text.raw.jsx | 12 ++ .../components/number-field/helper-text.ts | 9 + .../number-field/hide-stepper.raw.jsx | 12 ++ .../components/number-field/hide-stepper.ts | 9 + .../content/components/number-field/index.ts | 46 +++++ .../number-field/is-wheel-disabled.raw.jsx | 12 ++ .../number-field/is-wheel-disabled.ts | 9 + .../components/number-field/label.raw.jsx | 5 + .../content/components/number-field/label.ts | 9 + .../components/number-field/max-value.raw.jsx | 13 ++ .../components/number-field/max-value.ts | 9 + .../components/number-field/min-value.raw.jsx | 13 ++ .../components/number-field/min-value.ts | 9 + .../components/number-field/radius.raw.jsx | 19 ++ .../content/components/number-field/radius.ts | 9 + .../components/number-field/readonly.raw.jsx | 13 ++ .../components/number-field/readonly.ts | 9 + .../number-field/real-time-validation.raw.jsx | 50 ++++++ .../number-field/real-time-validation.ts | 9 + .../components/number-field/required.raw.jsx | 13 ++ .../components/number-field/required.ts | 9 + .../number-field/server-validation.raw.jsx | 49 ++++++ .../number-field/server-validation.ts | 9 + .../components/number-field/sizes.raw.jsx | 15 ++ .../content/components/number-field/sizes.ts | 9 + .../number-field/start-end-content.raw.jsx | 67 ++++++++ .../number-field/start-end-content.ts | 9 + .../components/number-field/usage.raw.jsx | 2 +- .../components/number-field/variants.raw.jsx | 16 ++ .../components/number-field/variants.ts | 9 + 48 files changed, 927 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/components/number-field/clear-button.raw.jsx create mode 100644 apps/docs/content/components/number-field/clear-button.ts create mode 100644 apps/docs/content/components/number-field/colors.raw.jsx create mode 100644 apps/docs/content/components/number-field/colors.ts create mode 100644 apps/docs/content/components/number-field/controlled.raw.jsx create mode 100644 apps/docs/content/components/number-field/controlled.ts create mode 100644 apps/docs/content/components/number-field/custom-impl.raw.jsx create mode 100644 apps/docs/content/components/number-field/custom-impl.ts create mode 100644 apps/docs/content/components/number-field/custom-styles.raw.jsx create mode 100644 apps/docs/content/components/number-field/custom-styles.ts create mode 100644 apps/docs/content/components/number-field/custom-validation.raw.jsx create mode 100644 apps/docs/content/components/number-field/custom-validation.ts create mode 100644 apps/docs/content/components/number-field/description.raw.jsx create mode 100644 apps/docs/content/components/number-field/description.ts create mode 100644 apps/docs/content/components/number-field/disabled.raw.jsx create mode 100644 apps/docs/content/components/number-field/disabled.ts create mode 100644 apps/docs/content/components/number-field/error-message.raw.jsx create mode 100644 apps/docs/content/components/number-field/error-message.ts create mode 100644 apps/docs/content/components/number-field/helper-text.raw.jsx create mode 100644 apps/docs/content/components/number-field/helper-text.ts create mode 100644 apps/docs/content/components/number-field/hide-stepper.raw.jsx create mode 100644 apps/docs/content/components/number-field/hide-stepper.ts create mode 100644 apps/docs/content/components/number-field/is-wheel-disabled.raw.jsx create mode 100644 apps/docs/content/components/number-field/is-wheel-disabled.ts create mode 100644 apps/docs/content/components/number-field/label.raw.jsx create mode 100644 apps/docs/content/components/number-field/label.ts create mode 100644 apps/docs/content/components/number-field/max-value.raw.jsx create mode 100644 apps/docs/content/components/number-field/max-value.ts create mode 100644 apps/docs/content/components/number-field/min-value.raw.jsx create mode 100644 apps/docs/content/components/number-field/min-value.ts create mode 100644 apps/docs/content/components/number-field/radius.raw.jsx create mode 100644 apps/docs/content/components/number-field/radius.ts create mode 100644 apps/docs/content/components/number-field/readonly.raw.jsx create mode 100644 apps/docs/content/components/number-field/readonly.ts create mode 100644 apps/docs/content/components/number-field/real-time-validation.raw.jsx create mode 100644 apps/docs/content/components/number-field/real-time-validation.ts create mode 100644 apps/docs/content/components/number-field/required.raw.jsx create mode 100644 apps/docs/content/components/number-field/required.ts create mode 100644 apps/docs/content/components/number-field/server-validation.raw.jsx create mode 100644 apps/docs/content/components/number-field/server-validation.ts create mode 100644 apps/docs/content/components/number-field/sizes.raw.jsx create mode 100644 apps/docs/content/components/number-field/sizes.ts create mode 100644 apps/docs/content/components/number-field/start-end-content.raw.jsx create mode 100644 apps/docs/content/components/number-field/start-end-content.ts create mode 100644 apps/docs/content/components/number-field/variants.raw.jsx create mode 100644 apps/docs/content/components/number-field/variants.ts diff --git a/apps/docs/content/components/number-field/clear-button.raw.jsx b/apps/docs/content/components/number-field/clear-button.raw.jsx new file mode 100644 index 0000000000..00086d359e --- /dev/null +++ b/apps/docs/content/components/number-field/clear-button.raw.jsx @@ -0,0 +1,16 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + console.log("number field cleared")} + /> + ); +} diff --git a/apps/docs/content/components/number-field/clear-button.ts b/apps/docs/content/components/number-field/clear-button.ts new file mode 100644 index 0000000000..f84fe2f74e --- /dev/null +++ b/apps/docs/content/components/number-field/clear-button.ts @@ -0,0 +1,9 @@ +import App from "./clear-button.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/colors.raw.jsx b/apps/docs/content/components/number-field/colors.raw.jsx new file mode 100644 index 0000000000..c037d46c15 --- /dev/null +++ b/apps/docs/content/components/number-field/colors.raw.jsx @@ -0,0 +1,20 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; + + return ( +
+ {colors.map((color) => ( + + ))} +
+ ); +} diff --git a/apps/docs/content/components/number-field/colors.ts b/apps/docs/content/components/number-field/colors.ts new file mode 100644 index 0000000000..d5bef810aa --- /dev/null +++ b/apps/docs/content/components/number-field/colors.ts @@ -0,0 +1,9 @@ +import App from "./colors.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/controlled.raw.jsx b/apps/docs/content/components/number-field/controlled.raw.jsx new file mode 100644 index 0000000000..b3cb620d5e --- /dev/null +++ b/apps/docs/content/components/number-field/controlled.raw.jsx @@ -0,0 +1,17 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + const [value, setValue] = React.useState(); + + return ( +
+ +

NumberField value: {value}

+
+ ); +} diff --git a/apps/docs/content/components/number-field/controlled.ts b/apps/docs/content/components/number-field/controlled.ts new file mode 100644 index 0000000000..2c3f0cacb4 --- /dev/null +++ b/apps/docs/content/components/number-field/controlled.ts @@ -0,0 +1,9 @@ +import App from "./controlled.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/custom-impl.raw.jsx b/apps/docs/content/components/number-field/custom-impl.raw.jsx new file mode 100644 index 0000000000..9a75192076 --- /dev/null +++ b/apps/docs/content/components/number-field/custom-impl.raw.jsx @@ -0,0 +1,162 @@ +import React, {forwardRef} from "react"; +import {useInput} from "@nextui-org/react"; + +export const SearchIcon = (props) => { + return ( + + ); +}; + +export const CloseFilledIcon = (props) => { + return ( + + ); +}; + +const styles = { + label: "text-black/50 dark:text-white/90", + input: [ + "bg-transparent", + "text-black/90 dark:text-white/90", + "placeholder:text-default-700/50 dark:placeholder:text-white/60", + ], + innerWrapper: "bg-transparent", + inputWrapper: [ + "shadow-xl", + "bg-default-200/50", + "dark:bg-default/60", + "backdrop-blur-xl", + "backdrop-saturate-200", + "hover:bg-default-200/70", + "focus-within:!bg-default-200/50", + "dark:hover:bg-default/70", + "dark:focus-within:!bg-default/60", + "!cursor-text", + ], +}; + +const MyInput = forwardRef((props, ref) => { + const { + Component, + label, + domRef, + description, + isClearable, + startContent, + endContent, + shouldLabelBeOutside, + shouldLabelBeInside, + errorMessage, + getBaseProps, + getLabelProps, + getInputProps, + getInnerWrapperProps, + getInputWrapperProps, + getDescriptionProps, + getErrorMessageProps, + getClearButtonProps, + } = useInput({ + ...props, + ref, + // this is just for the example, the props bellow should be passed by the parent component + label: "Search", + type: "search", + placeholder: "Type to search...", + startContent: ( + + ), + // custom styles + classNames: { + ...styles, + }, + }); + + const labelContent = ; + + const end = React.useMemo(() => { + if (isClearable) { + return {endContent || }; + } + + return endContent; + }, [isClearable, getClearButtonProps]); + + const innerWrapper = React.useMemo(() => { + if (startContent || end) { + return ( +
+ {startContent} + + {end} +
+ ); + } + + return ; + }, [startContent, end, getInputProps, getInnerWrapperProps]); + + return ( +
+ + {shouldLabelBeOutside ? labelContent : null} +
{ + domRef.current?.focus(); + }} + onKeyDown={() => { + domRef.current?.focus(); + }} + > + {shouldLabelBeInside ? labelContent : null} + {innerWrapper} +
+ {description &&
{description}
} + {errorMessage &&
{errorMessage}
} +
+
+ ); +}); + +MyInput.displayName = "MyInput"; + +export default MyInput; diff --git a/apps/docs/content/components/number-field/custom-impl.ts b/apps/docs/content/components/number-field/custom-impl.ts new file mode 100644 index 0000000000..ab37512cec --- /dev/null +++ b/apps/docs/content/components/number-field/custom-impl.ts @@ -0,0 +1,9 @@ +import App from "./custom-impl.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/custom-styles.raw.jsx b/apps/docs/content/components/number-field/custom-styles.raw.jsx new file mode 100644 index 0000000000..87437ddfe8 --- /dev/null +++ b/apps/docs/content/components/number-field/custom-styles.raw.jsx @@ -0,0 +1,68 @@ +import {NumberField} from "@nextui-org/react"; + +export const SearchIcon = (props) => { + return ( + + ); +}; + +export default function App() { + return ( +
+ + } + /> +
+ ); +} diff --git a/apps/docs/content/components/number-field/custom-styles.ts b/apps/docs/content/components/number-field/custom-styles.ts new file mode 100644 index 0000000000..da3ea9093a --- /dev/null +++ b/apps/docs/content/components/number-field/custom-styles.ts @@ -0,0 +1,9 @@ +import App from "./custom-styles.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/custom-validation.raw.jsx b/apps/docs/content/components/number-field/custom-validation.raw.jsx new file mode 100644 index 0000000000..14616bd229 --- /dev/null +++ b/apps/docs/content/components/number-field/custom-validation.raw.jsx @@ -0,0 +1,42 @@ +import {Button, Form, NumberField} from "@nextui-org/react"; + +export default function App() { + const [submitted, setSubmitted] = React.useState(null); + + const onSubmit = (e) => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.currentTarget)); + + setSubmitted(data); + }; + + return ( +
+ { + if (value < 100) { + return "Number must be greater than 100"; + } + + if (value > 1000) { + return "Number must be less than 1000"; + } + + return value === 777 ? "Nice try!" : null; + }} + /> + + {submitted && ( +
+ You submitted: {JSON.stringify(submitted)} +
+ )} + + ); +} diff --git a/apps/docs/content/components/number-field/custom-validation.ts b/apps/docs/content/components/number-field/custom-validation.ts new file mode 100644 index 0000000000..b0bf5b8588 --- /dev/null +++ b/apps/docs/content/components/number-field/custom-validation.ts @@ -0,0 +1,9 @@ +import App from "./custom-validation.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/description.raw.jsx b/apps/docs/content/components/number-field/description.raw.jsx new file mode 100644 index 0000000000..87da165a5d --- /dev/null +++ b/apps/docs/content/components/number-field/description.raw.jsx @@ -0,0 +1,12 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/description.ts b/apps/docs/content/components/number-field/description.ts new file mode 100644 index 0000000000..aeb6340b6b --- /dev/null +++ b/apps/docs/content/components/number-field/description.ts @@ -0,0 +1,9 @@ +import App from "./description.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/disabled.raw.jsx b/apps/docs/content/components/number-field/disabled.raw.jsx new file mode 100644 index 0000000000..79f5d22cc0 --- /dev/null +++ b/apps/docs/content/components/number-field/disabled.raw.jsx @@ -0,0 +1,12 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/disabled.ts b/apps/docs/content/components/number-field/disabled.ts new file mode 100644 index 0000000000..1a215cc91f --- /dev/null +++ b/apps/docs/content/components/number-field/disabled.ts @@ -0,0 +1,9 @@ +import App from "./disabled.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/error-message.raw.jsx b/apps/docs/content/components/number-field/error-message.raw.jsx new file mode 100644 index 0000000000..a33cc4da66 --- /dev/null +++ b/apps/docs/content/components/number-field/error-message.raw.jsx @@ -0,0 +1,15 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/error-message.ts b/apps/docs/content/components/number-field/error-message.ts new file mode 100644 index 0000000000..fb8101b132 --- /dev/null +++ b/apps/docs/content/components/number-field/error-message.ts @@ -0,0 +1,9 @@ +import App from "./error-message.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/helper-text.raw.jsx b/apps/docs/content/components/number-field/helper-text.raw.jsx new file mode 100644 index 0000000000..8224a258d0 --- /dev/null +++ b/apps/docs/content/components/number-field/helper-text.raw.jsx @@ -0,0 +1,12 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/helper-text.ts b/apps/docs/content/components/number-field/helper-text.ts new file mode 100644 index 0000000000..121795e835 --- /dev/null +++ b/apps/docs/content/components/number-field/helper-text.ts @@ -0,0 +1,9 @@ +import App from "./helper-text.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/hide-stepper.raw.jsx b/apps/docs/content/components/number-field/hide-stepper.raw.jsx new file mode 100644 index 0000000000..ba26df8655 --- /dev/null +++ b/apps/docs/content/components/number-field/hide-stepper.raw.jsx @@ -0,0 +1,12 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/hide-stepper.ts b/apps/docs/content/components/number-field/hide-stepper.ts new file mode 100644 index 0000000000..4c2ca38524 --- /dev/null +++ b/apps/docs/content/components/number-field/hide-stepper.ts @@ -0,0 +1,9 @@ +import App from "./hide-stepper.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/index.ts b/apps/docs/content/components/number-field/index.ts index e68c5e8301..289a4ea4c9 100644 --- a/apps/docs/content/components/number-field/index.ts +++ b/apps/docs/content/components/number-field/index.ts @@ -1,5 +1,51 @@ import usage from "./usage"; +import disabled from "./disabled"; +import readOnly from "./readonly"; +import required from "./required"; +import sizes from "./sizes"; +import colors from "./colors"; +import variants from "./variants"; +import radius from "./radius"; +import description from "./description"; +import helperText from "./helper-text"; +import isWheelDisabled from "./is-wheel-disabled"; +import label from "./label"; +import minValue from "./min-value"; +import maxValue from "./max-value"; +import hideStepper from "./hide-stepper"; +import clearButton from "./clear-button"; +import startEndContent from "./start-end-content"; +import errorMessage from "./error-message"; +import controlled from "./controlled"; +import customValidation from "./custom-validation"; +import realTimeValidation from "./real-time-validation"; +import serverValidation from "./server-validation"; +import customStyles from "./custom-styles"; +import customImpl from "./custom-impl"; export const numberFieldContent = { usage, + disabled, + readOnly, + required, + sizes, + colors, + variants, + radius, + description, + helperText, + label, + isWheelDisabled, + minValue, + maxValue, + clearButton, + hideStepper, + startEndContent, + errorMessage, + controlled, + customValidation, + realTimeValidation, + serverValidation, + customStyles, + customImpl, }; diff --git a/apps/docs/content/components/number-field/is-wheel-disabled.raw.jsx b/apps/docs/content/components/number-field/is-wheel-disabled.raw.jsx new file mode 100644 index 0000000000..2e33586f36 --- /dev/null +++ b/apps/docs/content/components/number-field/is-wheel-disabled.raw.jsx @@ -0,0 +1,12 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/is-wheel-disabled.ts b/apps/docs/content/components/number-field/is-wheel-disabled.ts new file mode 100644 index 0000000000..a44b3147fa --- /dev/null +++ b/apps/docs/content/components/number-field/is-wheel-disabled.ts @@ -0,0 +1,9 @@ +import App from "./is-wheel-disabled.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/label.raw.jsx b/apps/docs/content/components/number-field/label.raw.jsx new file mode 100644 index 0000000000..046fa411fa --- /dev/null +++ b/apps/docs/content/components/number-field/label.raw.jsx @@ -0,0 +1,5 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ; +} diff --git a/apps/docs/content/components/number-field/label.ts b/apps/docs/content/components/number-field/label.ts new file mode 100644 index 0000000000..254b95afb8 --- /dev/null +++ b/apps/docs/content/components/number-field/label.ts @@ -0,0 +1,9 @@ +import App from "./label.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/max-value.raw.jsx b/apps/docs/content/components/number-field/max-value.raw.jsx new file mode 100644 index 0000000000..7b7d2dd65c --- /dev/null +++ b/apps/docs/content/components/number-field/max-value.raw.jsx @@ -0,0 +1,13 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/max-value.ts b/apps/docs/content/components/number-field/max-value.ts new file mode 100644 index 0000000000..80f848912b --- /dev/null +++ b/apps/docs/content/components/number-field/max-value.ts @@ -0,0 +1,9 @@ +import App from "./max-value.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/min-value.raw.jsx b/apps/docs/content/components/number-field/min-value.raw.jsx new file mode 100644 index 0000000000..c76e236b67 --- /dev/null +++ b/apps/docs/content/components/number-field/min-value.raw.jsx @@ -0,0 +1,13 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/min-value.ts b/apps/docs/content/components/number-field/min-value.ts new file mode 100644 index 0000000000..73ea3be003 --- /dev/null +++ b/apps/docs/content/components/number-field/min-value.ts @@ -0,0 +1,9 @@ +import App from "./min-value.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/radius.raw.jsx b/apps/docs/content/components/number-field/radius.raw.jsx new file mode 100644 index 0000000000..8a60153421 --- /dev/null +++ b/apps/docs/content/components/number-field/radius.raw.jsx @@ -0,0 +1,19 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + const radius = ["full", "lg", "md", "sm", "none"]; + + return ( +
+ {radius.map((r) => ( + + ))} +
+ ); +} diff --git a/apps/docs/content/components/number-field/radius.ts b/apps/docs/content/components/number-field/radius.ts new file mode 100644 index 0000000000..7b78db1ce0 --- /dev/null +++ b/apps/docs/content/components/number-field/radius.ts @@ -0,0 +1,9 @@ +import App from "./radius.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/readonly.raw.jsx b/apps/docs/content/components/number-field/readonly.raw.jsx new file mode 100644 index 0000000000..8642f376be --- /dev/null +++ b/apps/docs/content/components/number-field/readonly.raw.jsx @@ -0,0 +1,13 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/readonly.ts b/apps/docs/content/components/number-field/readonly.ts new file mode 100644 index 0000000000..fabd05ba36 --- /dev/null +++ b/apps/docs/content/components/number-field/readonly.ts @@ -0,0 +1,9 @@ +import App from "./readonly.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/real-time-validation.raw.jsx b/apps/docs/content/components/number-field/real-time-validation.raw.jsx new file mode 100644 index 0000000000..9a7083b2d0 --- /dev/null +++ b/apps/docs/content/components/number-field/real-time-validation.raw.jsx @@ -0,0 +1,50 @@ +import {Button, Form, NumberField} from "@nextui-org/react"; + +export default function App() { + const [submitted, setSubmitted] = React.useState(null); + const [width, setWidth] = React.useState(null); + const errors = []; + + const onSubmit = (e) => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.currentTarget)); + + setSubmitted(data); + }; + + if (width < 100) { + errors.push("The value must be greater than 100"); + } + + if (width > 1000) { + errors.push("The value must be less than 1000"); + } + + return ( +
+ ( +
    + {errors.map((error, i) => ( +
  • {error}
  • + ))} +
+ )} + isInvalid={errors.length > 0} + label="Width" + name="width" + placeholder="Enter a number" + value={width} + onValueChange={setWidth} + /> + + {submitted && ( +
+ You submitted: {JSON.stringify(submitted)} +
+ )} + + ); +} diff --git a/apps/docs/content/components/number-field/real-time-validation.ts b/apps/docs/content/components/number-field/real-time-validation.ts new file mode 100644 index 0000000000..6f8034a877 --- /dev/null +++ b/apps/docs/content/components/number-field/real-time-validation.ts @@ -0,0 +1,9 @@ +import App from "./real-time-validation.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/required.raw.jsx b/apps/docs/content/components/number-field/required.raw.jsx new file mode 100644 index 0000000000..d92690d04d --- /dev/null +++ b/apps/docs/content/components/number-field/required.raw.jsx @@ -0,0 +1,13 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( + + ); +} diff --git a/apps/docs/content/components/number-field/required.ts b/apps/docs/content/components/number-field/required.ts new file mode 100644 index 0000000000..b50b781e6f --- /dev/null +++ b/apps/docs/content/components/number-field/required.ts @@ -0,0 +1,9 @@ +import App from "./required.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/server-validation.raw.jsx b/apps/docs/content/components/number-field/server-validation.raw.jsx new file mode 100644 index 0000000000..82bffa2c17 --- /dev/null +++ b/apps/docs/content/components/number-field/server-validation.raw.jsx @@ -0,0 +1,49 @@ +import {Button, Form, NumberField} from "@nextui-org/react"; + +export default function App() { + const [isLoading, setIsLoading] = React.useState(false); + const [errors, setErrors] = React.useState({}); + + const onSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + + const data = Object.fromEntries(new FormData(e.currentTarget)); + const result = await callServer(data); + + setErrors(result.errors); + setIsLoading(false); + }; + + return ( +
+ + + + ); +} + +// Fake server used in this example. +async function callServer(_) { + await new Promise((resolve) => setTimeout(resolve, 500)); + + return { + errors: { + username: "Sorry, this username is taken.", + }, + }; +} diff --git a/apps/docs/content/components/number-field/server-validation.ts b/apps/docs/content/components/number-field/server-validation.ts new file mode 100644 index 0000000000..84a2823b6a --- /dev/null +++ b/apps/docs/content/components/number-field/server-validation.ts @@ -0,0 +1,9 @@ +import App from "./server-validation.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/sizes.raw.jsx b/apps/docs/content/components/number-field/sizes.raw.jsx new file mode 100644 index 0000000000..b4f54a2666 --- /dev/null +++ b/apps/docs/content/components/number-field/sizes.raw.jsx @@ -0,0 +1,15 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + const sizes = ["sm", "md", "lg"]; + + return ( +
+ {sizes.map((size) => ( +
+ +
+ ))} +
+ ); +} diff --git a/apps/docs/content/components/number-field/sizes.ts b/apps/docs/content/components/number-field/sizes.ts new file mode 100644 index 0000000000..85a2f5b30b --- /dev/null +++ b/apps/docs/content/components/number-field/sizes.ts @@ -0,0 +1,9 @@ +import App from "./sizes.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/start-end-content.raw.jsx b/apps/docs/content/components/number-field/start-end-content.raw.jsx new file mode 100644 index 0000000000..b3f801e0cf --- /dev/null +++ b/apps/docs/content/components/number-field/start-end-content.raw.jsx @@ -0,0 +1,67 @@ +import {NumberField} from "@nextui-org/react"; + +export const MailIcon = (props) => { + return ( + + ); +}; + +export default function App() { + return ( +
+
+ + $ +
+ } + type="number" + /> + + + +
+ } + label="Price" + labelPlacement="outside" + placeholder="0.00" + startContent={ +
+ $ +
+ } + type="number" + /> +
+
+ ); +} diff --git a/apps/docs/content/components/number-field/start-end-content.ts b/apps/docs/content/components/number-field/start-end-content.ts new file mode 100644 index 0000000000..e99c7e5997 --- /dev/null +++ b/apps/docs/content/components/number-field/start-end-content.ts @@ -0,0 +1,9 @@ +import App from "./start-end-content.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/usage.raw.jsx b/apps/docs/content/components/number-field/usage.raw.jsx index 8facf1e6c1..12c767d2fe 100644 --- a/apps/docs/content/components/number-field/usage.raw.jsx +++ b/apps/docs/content/components/number-field/usage.raw.jsx @@ -1,5 +1,5 @@ import {NumberField} from "@nextui-org/react"; export default function App() { - return ; + return ; } diff --git a/apps/docs/content/components/number-field/variants.raw.jsx b/apps/docs/content/components/number-field/variants.raw.jsx new file mode 100644 index 0000000000..cb2d57ec90 --- /dev/null +++ b/apps/docs/content/components/number-field/variants.raw.jsx @@ -0,0 +1,16 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + const variants = ["flat", "bordered", "underlined", "faded"]; + + return ( +
+ {variants.map((variant) => ( +
+ + +
+ ))} +
+ ); +} diff --git a/apps/docs/content/components/number-field/variants.ts b/apps/docs/content/components/number-field/variants.ts new file mode 100644 index 0000000000..ddea95fb2e --- /dev/null +++ b/apps/docs/content/components/number-field/variants.ts @@ -0,0 +1,9 @@ +import App from "./variants.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; From 10f80f4868a062477726407a9b3758c3512c8635 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 18:34:07 +0800 Subject: [PATCH 052/131] chore(number-field): use text instead --- .../components/number-field/__tests__/number-field.test.tsx | 2 +- packages/components/number-field/src/use-number-field.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index 12b5755e99..2e0ee7c256 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -234,7 +234,7 @@ describe("NumberField with React Hook Form", () => { }); it("should work with defaultValues", () => { - expect(input1).toHaveValue(1234); + expect(input1).toHaveValue("1234"); expect(input2).not.toHaveValue(); expect(input3).not.toHaveValue(); }); diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index a618252398..40842e20b6 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -329,7 +329,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { onChange: chain(inputProps.onChange, onChange), value: inputValue, ref: domRef, - type: "number", }; }, [ From eb9ba4c1902f76bf3b74417d9c99e4d75b779fcf Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 1 Jan 2025 20:48:28 +0800 Subject: [PATCH 053/131] refactor(number-field): remove unnecessary filled-within --- .../number-field/src/use-number-field.ts | 7 ---- .../core/theme/src/components/number-field.ts | 37 +++++++++---------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 40842e20b6..e0ffe57c4e 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -167,7 +167,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; const isFilled = !isEmpty(inputValue); - const isFilledWithin = isFilled || isFocusWithin; const baseStyles = clsx(classNames?.base, className, isFilled ? "is-filled" : ""); @@ -247,9 +246,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { "data-filled": dataAttr( isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown, ), - "data-filled-within": dataAttr( - isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown, - ), "data-focus-within": dataAttr(isFocusWithin), "data-focus-visible": dataAttr(isFocusVisible), "data-readonly": dataAttr(originalProps.isReadOnly), @@ -283,7 +279,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { hasStartContent, isFocusWithin, isFocusVisible, - isFilledWithin, hasPlaceholder, focusWithinProps, originalProps.isReadOnly, @@ -308,7 +303,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { return { "data-slot": "input", "data-filled": dataAttr(isFilled), - "data-filled-within": dataAttr(isFilledWithin), "data-has-start-content": dataAttr(hasStartContent), "data-has-end-content": dataAttr(!!endContent), "data-direction": steps, @@ -338,7 +332,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { inputProps, otherProps, isFilled, - isFilledWithin, hasStartContent, endContent, classNames?.input, diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index 6e621d78d6..7524c929fa 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -117,7 +117,6 @@ const numberField = tv({ "group-data-[focus=true]:after:w-full", ], innerWrapper: "pb-1", - label: "group-data-[filled-within=true]:text-foreground", }, }, color: { @@ -131,54 +130,54 @@ const numberField = tv({ size: { sm: { label: [ - "start-2", + "start-0", "text-tiny", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_16px)]", - "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_32px)]", + "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_16px)]", + "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_32px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", inputWrapper: "h-8 min-h-8 px-2 rounded-small", input: "text-small", clearButton: "text-medium", description: [ - "start-1", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", - "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + "start-0", + "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", ], }, md: { label: [ - "start-3", + "start-0", "end-auto", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", - "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_36px)]", + "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", + "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_36px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", inputWrapper: "h-10 min-h-10 rounded-medium", input: "text-small", clearButton: "text-large", description: [ - "start-2", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", - "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", + "start-0", + "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", + "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", ], }, lg: { label: [ - "start-3", + "start-0", "end-auto", "text-medium", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", - "group-data-[filled-within=true]:group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_40px)]", + "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", + "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_40px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", inputWrapper: "h-12 min-h-12 rounded-large", input: "text-medium", clearButton: "text-large", description: [ - "start-2", - "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", - "group-data-[filled-within=true]:group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", + "start-0", + "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", + "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", ], }, }, From 6ba9b27eab39c26ff5490beb49cc43b82365c62d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 2 Jan 2025 19:26:29 +0800 Subject: [PATCH 054/131] fix(number-field): test case --- .../number-field/__tests__/number-field.test.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/components/number-field/__tests__/number-field.test.tsx b/packages/components/number-field/__tests__/number-field.test.tsx index 2e0ee7c256..8f05276e6c 100644 --- a/packages/components/number-field/__tests__/number-field.test.tsx +++ b/packages/components/number-field/__tests__/number-field.test.tsx @@ -328,12 +328,13 @@ describe("NumberField with React Hook Form", () => { ); await user.keyboard("4321"); - - expect(input).toHaveAttribute("aria-describedby"); - expect(input.validity.valid).toBe(true); - await user.tab(); + act(() => { + (getByTestId("form") as HTMLFormElement).checkValidity(); + }); + + expect(input.validity.valid).toBe(true); expect(input).not.toHaveAttribute("aria-describedby"); }); From 500a161ee3719969f3ed5fbbe5fdd3a1f1eaded5 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 11:36:41 +0800 Subject: [PATCH 055/131] chore(number-field): remove aria-label for stepper buttons --- packages/components/number-field/src/use-number-field.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index e0ffe57c4e..1b7d950134 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -491,7 +491,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { ...props, type: "button", disabled: originalProps.isDisabled, - "aria-label": "increase value", "data-slot": "increase-button", className: slots.stepperButton({ // class: clsx(classNames?.stepperIncreaseButton, props?.className), @@ -508,7 +507,6 @@ export function useNumberField(originalProps: UseNumberFieldProps) { return { type: "button", disabled: originalProps.isDisabled, - "aria-label": "decrease value", "data-slot": "decrease-button", className: slots.stepperButton({ // class: clsx(classNames?.stepperDereaseButton, props?.className), From 5be13df2ae066826f71cbf85ee46c9077874ad05 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 11:37:04 +0800 Subject: [PATCH 056/131] feat(docs): add incrementAriaLabel & decrementAriaLabel to NumberField --- apps/docs/content/docs/components/number-field.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-field.mdx index 4c201240dd..49ad93d6b2 100644 --- a/apps/docs/content/docs/components/number-field.mdx +++ b/apps/docs/content/docs/components/number-field.mdx @@ -379,6 +379,18 @@ In case you need to customize the input even further, you can use the `useInput` description: "Whether the input is invalid.", default: "false" }, + { + attribute: "incrementAriaLabel", + type: "string", + description: "A custom aria-label for the increment button. If not provided, the localized string "Increment" is used.", + default: "-" + }, + { + attribute: "decrementAriaLabel", + type: "string", + description: "A custom aria-label for the increment button. If not provided, the localized string "Decrement" is used.", + default: "-" + }, { attribute: "baseRef", type: "RefObject", From 9d7ba10c08787a61f83be6efc6e9077eba40b27b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 11:38:53 +0800 Subject: [PATCH 057/131] chore(number-field): reorder WithFormatOptions --- .../stories/number-field.stories.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index 391fde91c6..db1acfcc1f 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -414,6 +414,21 @@ export const HorizontalStepper = { }, }; +export const WithFormatOptions = { + render: Template, + + args: { + ...defaultProps, + label: "Transaction amount", + formatOptions: { + style: "currency", + currency: "EUR", + currencyDisplay: "code", + currencySign: "accounting", + }, + }, +}; + export const HideStepper = { render: Template, @@ -457,21 +472,6 @@ export const ReadOnly = { }, }; -export const WithFormatOptions = { - render: Template, - - args: { - ...defaultProps, - label: "Transaction amount", - formatOptions: { - style: "currency", - currency: "EUR", - currencyDisplay: "code", - currencySign: "accounting", - }, - }, -}; - export const Clearable = { render: Template, From c165bef3439a02282c2acdf2da63adf26e894725 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 11:51:46 +0800 Subject: [PATCH 058/131] fix(deps): update number-field's peerDependencies & dependencies --- packages/components/number-field/package.json | 19 ++-- pnpm-lock.yaml | 97 +++---------------- 2 files changed, 21 insertions(+), 95 deletions(-) diff --git a/packages/components/number-field/package.json b/packages/components/number-field/package.json index 0931885260..b64d5ba236 100644 --- a/packages/components/number-field/package.json +++ b/packages/components/number-field/package.json @@ -38,8 +38,8 @@ "peerDependencies": { "react": ">=18 || >=19.0.0-rc.0", "react-dom": ">=18 || >=19.0.0-rc.0", - "@nextui-org/theme": ">=2.3.0-beta.0", - "@nextui-org/system": ">=2.3.0-beta.0" + "@nextui-org/theme": ">=2.4.5", + "@nextui-org/system": ">=2.4.5" }, "dependencies": { "@nextui-org/form": "workspace:*", @@ -48,16 +48,15 @@ "@nextui-org/shared-icons": "workspace:*", "@nextui-org/shared-utils": "workspace:*", "@nextui-org/use-safe-layout-effect": "workspace:*", - "@react-aria/focus": "3.18.4", - "@react-aria/interactions": "3.22.4", + "@react-aria/focus": "3.19.0", + "@react-aria/interactions": "3.22.5", "@react-aria/numberfield": "3.11.9", - "@react-aria/utils": "3.25.3", - "@react-stately/utils": "3.10.4", + "@react-aria/utils": "3.26.0", + "@react-stately/utils": "3.10.5", "@react-stately/numberfield": "3.9.8", - "@react-types/shared": "3.25.0", - "@react-types/numberfield": "3.8.6", - "@react-types/button": "3.10.1", - "react-textarea-autosize": "^8.5.3" + "@react-types/shared": "3.26.0", + "@react-types/numberfield": "3.8.7", + "@react-types/button": "3.10.1" }, "devDependencies": { "@nextui-org/system": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9412181e19..f11033dcc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2217,35 +2217,32 @@ importers: specifier: workspace:* version: link:../../hooks/use-safe-layout-effect '@react-aria/focus': - specifier: 3.18.4 - version: 3.18.4(react@18.2.0) + specifier: 3.19.0 + version: 3.19.0(react@18.2.0) '@react-aria/interactions': - specifier: 3.22.4 - version: 3.22.4(react@18.2.0) + specifier: 3.22.5 + version: 3.22.5(react@18.2.0) '@react-aria/numberfield': specifier: 3.11.9 version: 3.11.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@react-aria/utils': - specifier: 3.25.3 - version: 3.25.3(react@18.2.0) + specifier: 3.26.0 + version: 3.26.0(react@18.2.0) '@react-stately/numberfield': specifier: 3.9.8 version: 3.9.8(react@18.2.0) '@react-stately/utils': - specifier: 3.10.4 - version: 3.10.4(react@18.2.0) + specifier: 3.10.5 + version: 3.10.5(react@18.2.0) '@react-types/button': specifier: 3.10.1 version: 3.10.1(react@18.2.0) '@react-types/numberfield': - specifier: 3.8.6 - version: 3.8.6(react@18.2.0) + specifier: 3.8.7 + version: 3.8.7(react@18.2.0) '@react-types/shared': - specifier: 3.25.0 - version: 3.25.0(react@18.2.0) - react-textarea-autosize: - specifier: ^8.5.3 - version: 8.5.5(@types/react@18.2.8)(react@18.2.0) + specifier: 3.26.0 + version: 3.26.0(react@18.2.0) devDependencies: '@nextui-org/system': specifier: workspace:* @@ -6647,11 +6644,6 @@ packages: react: 18.2.0 react-dom: 18.2.0 - '@react-aria/focus@3.18.4': - resolution: {integrity: sha512-91J35077w9UNaMK1cpMUEFRkNNz0uZjnSwiyBCFuRdaVuivO53wNC9XtWSDNDdcO5cGy87vfJRVAiyoCn/mjqA==} - peerDependencies: - react: 18.2.0 - '@react-aria/focus@3.19.0': resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==} peerDependencies: @@ -6673,11 +6665,6 @@ packages: peerDependencies: react: 18.2.0 - '@react-aria/interactions@3.22.4': - resolution: {integrity: sha512-E0vsgtpItmknq/MJELqYJwib+YN18Qag8nroqwjk1qOnBa9ROIkUhWJerLi1qs5diXq9LHKehZDXRlwPvdEFww==} - peerDependencies: - react: 18.2.0 - '@react-aria/interactions@3.22.5': resolution: {integrity: sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==} peerDependencies: @@ -6790,11 +6777,6 @@ packages: peerDependencies: react: 18.2.0 - '@react-aria/utils@3.25.3': - resolution: {integrity: sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA==} - peerDependencies: - react: 18.2.0 - '@react-aria/utils@3.26.0': resolution: {integrity: sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==} peerDependencies: @@ -6937,11 +6919,6 @@ packages: peerDependencies: react: 18.2.0 - '@react-stately/utils@3.10.4': - resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==} - peerDependencies: - react: 18.2.0 - '@react-stately/utils@3.10.5': resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==} peerDependencies: @@ -7017,11 +6994,6 @@ packages: peerDependencies: react: 18.2.0 - '@react-types/numberfield@3.8.6': - resolution: {integrity: sha512-VtWEMAXUO1S9EEZI8whc7xv6DVccxhbWsRthMCg/LxiwU3U5KAveadNc2c5rtXkRpd3cnD5xFzz3dExXdmHkAg==} - peerDependencies: - react: 18.2.0 - '@react-types/numberfield@3.8.7': resolution: {integrity: sha512-KccMPi39cLoVkB2T0V7HW6nsxQVAwt89WWCltPZJVGzsebv/k0xTQlPVAgrUake4kDLoE687e3Fr/Oe3+1bDhw==} peerDependencies: @@ -7047,11 +7019,6 @@ packages: peerDependencies: react: 18.2.0 - '@react-types/shared@3.25.0': - resolution: {integrity: sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ==} - peerDependencies: - react: 18.2.0 - '@react-types/shared@3.26.0': resolution: {integrity: sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==} peerDependencies: @@ -18471,15 +18438,6 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@react-aria/focus@3.18.4(react@18.2.0)': - dependencies: - '@react-aria/interactions': 3.22.5(react@18.2.0) - '@react-aria/utils': 3.26.0(react@18.2.0) - '@react-types/shared': 3.26.0(react@18.2.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 18.2.0 - '@react-aria/focus@3.19.0(react@18.2.0)': dependencies: '@react-aria/interactions': 3.22.5(react@18.2.0) @@ -18528,14 +18486,6 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 - '@react-aria/interactions@3.22.4(react@18.2.0)': - dependencies: - '@react-aria/ssr': 3.9.7(react@18.2.0) - '@react-aria/utils': 3.26.0(react@18.2.0) - '@react-types/shared': 3.26.0(react@18.2.0) - '@swc/helpers': 0.5.15 - react: 18.2.0 - '@react-aria/interactions@3.22.5(react@18.2.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.2.0) @@ -18793,15 +18743,6 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 - '@react-aria/utils@3.25.3(react@18.2.0)': - dependencies: - '@react-aria/ssr': 3.9.7(react@18.2.0) - '@react-stately/utils': 3.10.5(react@18.2.0) - '@react-types/shared': 3.26.0(react@18.2.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 18.2.0 - '@react-aria/utils@3.26.0(react@18.2.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.2.0) @@ -19056,11 +18997,6 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.2.0 - '@react-stately/utils@3.10.4(react@18.2.0)': - dependencies: - '@swc/helpers': 0.5.15 - react: 18.2.0 - '@react-stately/utils@3.10.5(react@18.2.0)': dependencies: '@swc/helpers': 0.5.15 @@ -19145,11 +19081,6 @@ snapshots: '@react-types/shared': 3.26.0(react@18.2.0) react: 18.2.0 - '@react-types/numberfield@3.8.6(react@18.2.0)': - dependencies: - '@react-types/shared': 3.26.0(react@18.2.0) - react: 18.2.0 - '@react-types/numberfield@3.8.7(react@18.2.0)': dependencies: '@react-types/shared': 3.26.0(react@18.2.0) @@ -19175,10 +19106,6 @@ snapshots: '@react-types/shared': 3.26.0(react@18.2.0) react: 18.2.0 - '@react-types/shared@3.25.0(react@18.2.0)': - dependencies: - react: 18.2.0 - '@react-types/shared@3.26.0(react@18.2.0)': dependencies: react: 18.2.0 From 231bc83d9116ca6862b9c84e5d53b49c7bdee81b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 12:19:48 +0800 Subject: [PATCH 059/131] feat(number-field): hidden input for holding numeric vaule --- .../components/number-field/src/number-field.tsx | 4 ++++ .../number-field/src/use-number-field.ts | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index 97754e04c0..54da746933 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -25,6 +25,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { getBaseProps, getLabelProps, getNumberFieldProps, + getHiddenNumberFieldProps, getInnerWrapperProps, getInputWrapperProps, getMainWrapperProps, @@ -80,6 +81,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => {
{startContent} + {end}
); @@ -91,6 +93,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { {startContent} + {end} @@ -101,6 +104,7 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => {
{startContent} + {end} {!hideStepper && (
diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 1b7d950134..75609a64dd 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -321,13 +321,11 @@ export function useNumberField(originalProps: UseNumberFieldProps) { ), "aria-readonly": dataAttr(originalProps.isReadOnly), onChange: chain(inputProps.onChange, onChange), - value: inputValue, ref: domRef, }; }, [ slots, - inputValue, focusProps, inputProps, otherProps, @@ -341,6 +339,19 @@ export function useNumberField(originalProps: UseNumberFieldProps) { ], ); + const getHiddenNumberFieldProps: PropGetter = useCallback( + (props = {}) => { + return { + name: originalProps.name, + value: inputValue, + "data-slot": "hidden-input", + type: "hidden", + ...props, + }; + }, + [inputValue, originalProps.name], + ); + const getInputWrapperProps: PropGetter = useCallback( (props = {}) => { return { @@ -540,6 +551,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { getBaseProps, getLabelProps, getNumberFieldProps, + getHiddenNumberFieldProps, getMainWrapperProps, getInputWrapperProps, getInnerWrapperProps, From 5d2cd061ccedafc45c41028d22e4e7e30f5d3715 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 14:18:36 +0800 Subject: [PATCH 060/131] fix(docs): number field title --- apps/docs/config/routes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/config/routes.json b/apps/docs/config/routes.json index 05e80913a4..e524b69f36 100644 --- a/apps/docs/config/routes.json +++ b/apps/docs/config/routes.json @@ -334,7 +334,7 @@ }, { "key": "number-field", - "title": "NumberField", + "title": "Number Field", "keywords": "input, numeric input, number field", "path": "/docs/components/number-field.mdx", "newPost": true From 939eda82b81f86294075cdcfcf7130177a6a2570 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 14:23:04 +0800 Subject: [PATCH 061/131] feat(docs): add format options to number field --- .../number-field/format-options.raw.jsx | 60 +++++++++++++++++++ .../components/number-field/format-options.ts | 9 +++ .../content/components/number-field/index.ts | 2 + 3 files changed, 71 insertions(+) create mode 100644 apps/docs/content/components/number-field/format-options.raw.jsx create mode 100644 apps/docs/content/components/number-field/format-options.ts diff --git a/apps/docs/content/components/number-field/format-options.raw.jsx b/apps/docs/content/components/number-field/format-options.raw.jsx new file mode 100644 index 0000000000..cd8f1d431b --- /dev/null +++ b/apps/docs/content/components/number-field/format-options.raw.jsx @@ -0,0 +1,60 @@ +import {NumberField} from "@nextui-org/react"; + +export default function App() { + return ( +
+
+ + + +
+
+ + +
+
+ ); +} diff --git a/apps/docs/content/components/number-field/format-options.ts b/apps/docs/content/components/number-field/format-options.ts new file mode 100644 index 0000000000..1b5e16a4c6 --- /dev/null +++ b/apps/docs/content/components/number-field/format-options.ts @@ -0,0 +1,9 @@ +import App from "./format-options.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/number-field/index.ts b/apps/docs/content/components/number-field/index.ts index 289a4ea4c9..c1cf547c29 100644 --- a/apps/docs/content/components/number-field/index.ts +++ b/apps/docs/content/components/number-field/index.ts @@ -22,6 +22,7 @@ import realTimeValidation from "./real-time-validation"; import serverValidation from "./server-validation"; import customStyles from "./custom-styles"; import customImpl from "./custom-impl"; +import formatOptions from "./format-options"; export const numberFieldContent = { usage, @@ -48,4 +49,5 @@ export const numberFieldContent = { serverValidation, customStyles, customImpl, + formatOptions, }; From db3a825eaf9f1d5d406a13885f3ccda3877167fe Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 14:27:39 +0800 Subject: [PATCH 062/131] chore(docs): revise number field content --- .../number-field/clear-button.raw.jsx | 2 +- .../number-field/helper-text.raw.jsx | 3 + .../components/number-field/usage.raw.jsx | 2 +- .../content/docs/components/number-field.mdx | 76 +++++++++++++++---- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/apps/docs/content/components/number-field/clear-button.raw.jsx b/apps/docs/content/components/number-field/clear-button.raw.jsx index 00086d359e..4728f57ab6 100644 --- a/apps/docs/content/components/number-field/clear-button.raw.jsx +++ b/apps/docs/content/components/number-field/clear-button.raw.jsx @@ -7,7 +7,7 @@ export default function App() { className="max-w-xs" defaultValue={1024} label="Width" - placeholder="Enter your width" + placeholder="Enter the width" variant="bordered" // eslint-disable-next-line no-console onClear={() => console.log("number field cleared")} diff --git a/apps/docs/content/components/number-field/helper-text.raw.jsx b/apps/docs/content/components/number-field/helper-text.raw.jsx index 8224a258d0..b2bceda24f 100644 --- a/apps/docs/content/components/number-field/helper-text.raw.jsx +++ b/apps/docs/content/components/number-field/helper-text.raw.jsx @@ -7,6 +7,9 @@ export default function App() { description="Enter the width of the element" helperText="The value of the element should be between 100 and 1000" label="Width" + maxValue={1000} + minValue={100} + placeholder="Enter the width" /> ); } diff --git a/apps/docs/content/components/number-field/usage.raw.jsx b/apps/docs/content/components/number-field/usage.raw.jsx index 12c767d2fe..9d1439d101 100644 --- a/apps/docs/content/components/number-field/usage.raw.jsx +++ b/apps/docs/content/components/number-field/usage.raw.jsx @@ -1,5 +1,5 @@ import {NumberField} from "@nextui-org/react"; export default function App() { - return ; + return ; } diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-field.mdx index 49ad93d6b2..1eb8408027 100644 --- a/apps/docs/content/docs/components/number-field.mdx +++ b/apps/docs/content/docs/components/number-field.mdx @@ -114,10 +114,16 @@ You can set the maximum value of the input by passing the `maxValue` property. ### With Wheel Disabled -You can disablechanging the vaule with scroll in NumberField by passing the `isWheelDisabled` property. +You can disable changing the vaule with scroll in NumberField by passing the `isWheelDisabled` property. +### With Format Options + +You can format the value of the input by passing the `formatOptions` property. + + + ### With Helper Text You can add a helper text to NumberField by passing the `helperText` property. @@ -265,12 +271,24 @@ In case you need to customize the input even further, you can use the `useInput` description: "The radius of the input.", default: "-" }, + { + attribute: "name", + type: "string", + description: "The name of the input element, used when submitting an HTML form.", + default: "-" + }, { attribute: "label", type: "ReactNode", description: "The content to display as the label.", default: "-" }, + { + attribute: "description", + type: "ReactNode", + description: "A description for the input. Provides a description for the input.", + default: "-" + }, { attribute: "value", type: "string", @@ -284,21 +302,15 @@ In case you need to customize the input even further, you can use the `useInput` default: "-" }, { - attribute: "placeholder", - type: "string", - description: "The placeholder of the input.", - default: "-" - }, - { - attribute: "description", + attribute: "helperText", type: "ReactNode", - description: "A description for the input. Provides a description for the input.", + description: "A helper text for the input. Provides a hint such as specific requirements for what to choose.", default: "-" }, { - attribute: "helperText", - type: "ReactNode", - description: "A helper text for the input. Provides a hint such as specific requirements for what to choose.", + attribute: "placeholder", + type: "string", + description: "The placeholder of the input.", default: "-" }, { @@ -313,6 +325,12 @@ In case you need to customize the input even further, you can use the `useInput` description: "Validate input values when committing (e.g. on blur), returning error messages for invalid values.", default: "-" }, + { + attribute: "validationState", + type: "valid | invalid", + description: "Whether the inputs should display its \"valid\" or \"invalid\" visual styling. (Deprecated) use isInvalid instead.", + default: "-" + }, { attribute: "validationBehavior", type: "native | aria", @@ -331,6 +349,36 @@ In case you need to customize the input even further, you can use the `useInput` description: "The maximum value of the input.", default: "-" }, + { + attribute: "formatOptions", + type: "Intl.NumberFormatOptions", + description: "The format options for the input.", + default: "-" + }, + { + attribute: "step", + type: "number", + description: "The amount that the input value changes with each increment or decrement tick.", + default: "1" + }, + { + attribute: "steps", + type: "vertical | horizontal", + description: "The steps of the input.", + default: "vertical" + }, + { + attribute: "hideStepper", + type: "boolean", + description: "Whether the stepper buttons should be hidden.", + default: "-" + }, + { + attribute: "isWheelDisabled", + type: "boolean", + description: "Whether the wheel should be disabled.", + default: "-" + }, { attribute: "startContent", type: "ReactNode", @@ -382,13 +430,13 @@ In case you need to customize the input even further, you can use the `useInput` { attribute: "incrementAriaLabel", type: "string", - description: "A custom aria-label for the increment button. If not provided, the localized string "Increment" is used.", + description: "A custom aria-label for the increment button. If not provided, the localized string `Increment` is used.", default: "-" }, { attribute: "decrementAriaLabel", type: "string", - description: "A custom aria-label for the increment button. If not provided, the localized string "Decrement" is used.", + description: "A custom aria-label for the increment button. If not provided, the localized string `Decrement` is used.", default: "-" }, { From 85ce591a7340a1db04680445550de701f2aa8361 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 14:33:37 +0800 Subject: [PATCH 063/131] chore(number-field): add type to useDOMRef --- packages/components/number-field/src/use-number-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 75609a64dd..bb057f3023 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -139,7 +139,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const disableAnimation = originalProps.disableAnimation ?? globalContext?.disableAnimation ?? false; - const domRef = useDOMRef(ref); + const domRef = useDOMRef(ref); const baseDomRef = useDOMRef(baseRef); const inputWrapperRef = useDOMRef(wrapperRef); From b39f6645ccaa48189d13f0766ae146fece4a6799 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 15:19:56 +0800 Subject: [PATCH 064/131] fix(number-field): clear button --- packages/components/number-field/src/number-field.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/components/number-field/src/number-field.tsx b/packages/components/number-field/src/number-field.tsx index 54da746933..16150489ec 100644 --- a/packages/components/number-field/src/number-field.tsx +++ b/packages/components/number-field/src/number-field.tsx @@ -47,7 +47,14 @@ const NumberField = forwardRef<"input", NumberFieldProps>((props, ref) => { const end = useMemo(() => { if (isClearable) { - return ; + return ( + <> + + {endContent} + + ); } return endContent; From a00a3165a25759512c770fe6e7fbcc3f6af6e8a3 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 15:20:23 +0800 Subject: [PATCH 065/131] fix(theme): clear button styles --- .../core/theme/src/components/number-field.ts | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index 7524c929fa..d0eed9d782 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -1,11 +1,7 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import { - numberFieldLabelClasses, - dataFocusVisibleClasses, - groupDataFocusVisibleClasses, -} from "../utils"; +import {numberFieldLabelClasses, groupDataFocusVisibleClasses} from "../utils"; /** * NumberField wrapper **Tailwind Variants** component @@ -42,23 +38,16 @@ const numberField = tv({ "autofill:bg-transparent bg-clip-text", ], clearButton: [ - "p-2", - "-m-2", - "z-10", - "absolute", - "end-3", - "start-auto", - "pointer-events-none", - "appearance-none", - "outline-none", - "select-none", "opacity-0", - "hover:!opacity-100", - "cursor-pointer", - "active:!opacity-70", - "rounded-full", - // focus ring - ...dataFocusVisibleClasses, + "pointer-events-none", + "group-data-[invalid=true]:text-danger", + "peer-data-[filled=true]:opacity-100", // on mobile is always visible when there is a value + "peer-data-[filled=true]:pointer-events-auto", + "peer-data-[filled=true]:cursor-pointer", + "sm:peer-data-[filled=true]:opacity-0", // only visible on hover + "sm:peer-data-[filled=true]:pointer-events-none", + "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", + "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], stepperButton: ["bg-transparent min-w-4 w-4"], verticalStepperWrapper: ["flex flex-col h-full"], @@ -207,11 +196,9 @@ const numberField = tv({ isClearable: { true: { input: "peer pe-6 input-search-cancel-button-none", - clearButton: [ - "peer-data-[filled=true]:pointer-events-auto", - "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block", - "peer-data-[filled=true]:scale-100", - ], + }, + false: { + clearButton: "hidden", }, }, isDisabled: { From f7cb758637bde506806fb2ceaf83a3ea7860b3e2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 21:31:25 +0800 Subject: [PATCH 066/131] refactor(theme): stepper button styles --- packages/core/theme/src/components/number-field.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index d0eed9d782..0a21290c6d 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -28,7 +28,7 @@ const numberField = tv({ label: numberFieldLabelClasses, mainWrapper: "h-full flex flex-col", inputWrapper: - "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-4 py-3 gap-3", + "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-3 py-2 h-8 gap-3", innerWrapper: "inline-flex w-full items-center h-full box-border", input: [ "w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none", @@ -49,8 +49,8 @@ const numberField = tv({ "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], - stepperButton: ["bg-transparent min-w-4 w-4"], - verticalStepperWrapper: ["flex flex-col h-full"], + stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], + verticalStepperWrapper: ["flex flex-col"], helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", description: [...numberFieldLabelClasses, "text-tiny", "text-default-400"], helperText: "text-tiny text-foreground-400", From 843849b05441464881f85df7b1e4505df225b0b8 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 4 Jan 2025 21:31:44 +0800 Subject: [PATCH 067/131] chore(number-field): accept stepperButton class --- packages/components/number-field/src/use-number-field.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index bb057f3023..64b92786fa 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -504,7 +504,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { disabled: originalProps.isDisabled, "data-slot": "increase-button", className: slots.stepperButton({ - // class: clsx(classNames?.stepperIncreaseButton, props?.className), + class: clsx(classNames?.stepperButton, props?.className), }), // TODO: check press props & focus props ...mergeProps(incrementButtonProps, props), @@ -520,7 +520,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { disabled: originalProps.isDisabled, "data-slot": "decrease-button", className: slots.stepperButton({ - // class: clsx(classNames?.stepperDereaseButton, props?.className), + class: clsx(classNames?.stepperButton, props?.className), }), // TODO: check press props & focus props ...mergeProps(decrementButtonProps, props), From ac0c55974ca71df1c68c072c9229321a78cf9558 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 5 Jan 2025 20:26:07 +0800 Subject: [PATCH 068/131] fix(theme): helper wrapper padding --- packages/core/theme/src/components/number-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index 0a21290c6d..f9b0186626 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -51,7 +51,7 @@ const numberField = tv({ ], stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], verticalStepperWrapper: ["flex flex-col"], - helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5", + helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", description: [...numberFieldLabelClasses, "text-tiny", "text-default-400"], helperText: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", From 25ad5cbb8b72141db6d865246d052ac9127f452a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 5 Jan 2025 20:31:04 +0800 Subject: [PATCH 069/131] feat(deps): add `@react-aria/i18n` --- packages/components/number-field/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/components/number-field/package.json b/packages/components/number-field/package.json index b64d5ba236..61be538df5 100644 --- a/packages/components/number-field/package.json +++ b/packages/components/number-field/package.json @@ -49,6 +49,7 @@ "@nextui-org/shared-utils": "workspace:*", "@nextui-org/use-safe-layout-effect": "workspace:*", "@react-aria/focus": "3.19.0", + "@react-aria/i18n": "3.12.4", "@react-aria/interactions": "3.22.5", "@react-aria/numberfield": "3.11.9", "@react-aria/utils": "3.26.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f11033dcc9..12004583a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2219,6 +2219,9 @@ importers: '@react-aria/focus': specifier: 3.19.0 version: 3.19.0(react@18.2.0) + '@react-aria/i18n': + specifier: 3.12.4 + version: 3.12.4(react@18.2.0) '@react-aria/interactions': specifier: 3.22.5 version: 3.22.5(react@18.2.0) From b8de20c8fd53c782c26cca0a26b9becc3c84940f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 5 Jan 2025 20:31:41 +0800 Subject: [PATCH 070/131] fix(number-field): use locale from `@react-aria/i18n` --- packages/components/number-field/src/use-number-field.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 64b92786fa..0187baa15b 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -13,6 +13,7 @@ import {useFocusRing} from "@react-aria/focus"; import {numberField} from "@nextui-org/theme"; import {useDOMRef, filterDOMProps} from "@nextui-org/react-utils"; import {useFocusWithin, useHover, usePress} from "@react-aria/interactions"; +import {useLocale} from "@react-aria/i18n"; import {clsx, dataAttr, isEmpty, objectToDeps} from "@nextui-org/shared-utils"; import {useNumberFieldState} from "@react-stately/numberfield"; import {useNumberField as useAriaNumberField} from "@react-aria/numberfield"; @@ -145,10 +146,12 @@ export function useNumberField(originalProps: UseNumberFieldProps) { const inputWrapperRef = useDOMRef(wrapperRef); const innerWrapperRef = useDOMRef(innerWrapperRefProp); + const {locale} = useLocale(); + const state = useNumberFieldState({ ...originalProps, validationBehavior, - locale: "en-US", + locale, onChange: onValueChange, }); From 01bcd26da3e98100f0397d47852888c1d6e5cdfd Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 5 Jan 2025 20:39:06 +0800 Subject: [PATCH 071/131] fix(deps): dependency order --- apps/docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index a1db1ff63c..09cd4cd28f 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -40,7 +40,6 @@ "@nextui-org/use-infinite-scroll": "workspace:*", "@nextui-org/use-is-mobile": "workspace:*", "@nextui-org/number-field": "workspace:*", - "clsx": "^1.2.1", "@radix-ui/react-scroll-area": "^1.0.5", "@react-aria/focus": "3.19.0", "@react-aria/i18n": "3.12.4", @@ -56,6 +55,7 @@ "@rehooks/local-storage": "^2.4.5", "@tanstack/react-virtual": "3.11.2", "canvas-confetti": "^1.9.2", + "clsx": "^1.2.1", "cmdk": "^0.2.0", "color2k": "2.0.3", "contentlayer2": "0.5.3", From 51a88d1c4acbb755bf2e87f5ccf3c5ca2a0c36ed Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 6 Jan 2025 18:49:45 +0800 Subject: [PATCH 072/131] fix(docs): incorrect command --- apps/docs/content/docs/components/number-field.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-field.mdx index 1eb8408027..b5ef328e7e 100644 --- a/apps/docs/content/docs/components/number-field.mdx +++ b/apps/docs/content/docs/components/number-field.mdx @@ -20,7 +20,7 @@ The numeric input component is designed for users to enter a number, and increas Date: Tue, 7 Jan 2025 12:16:59 +0800 Subject: [PATCH 073/131] chore(docs): remove type=number --- .../content/components/number-field/start-end-content.raw.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/docs/content/components/number-field/start-end-content.raw.jsx b/apps/docs/content/components/number-field/start-end-content.raw.jsx index b3f801e0cf..003190c8ce 100644 --- a/apps/docs/content/components/number-field/start-end-content.raw.jsx +++ b/apps/docs/content/components/number-field/start-end-content.raw.jsx @@ -32,7 +32,6 @@ export default function App() { $
} - type="number" /> $
} - type="number" /> From 8bf0ee1e9a6101b6e3b0a361f513dc26bc910818 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 8 Jan 2025 21:41:25 +0800 Subject: [PATCH 074/131] chore(theme): add padding to stepper wrapper --- packages/core/theme/src/components/number-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-field.ts index f9b0186626..73a970d27e 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-field.ts @@ -50,7 +50,7 @@ const numberField = tv({ "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], - verticalStepperWrapper: ["flex flex-col"], + verticalStepperWrapper: ["flex", "flex-col", "ps-1"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", description: [...numberFieldLabelClasses, "text-tiny", "text-default-400"], helperText: "text-tiny text-foreground-400", From 26dd43cc67655c811b3a7c7b173a4e7592c4f98c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 12 Jan 2025 21:48:52 +0800 Subject: [PATCH 075/131] fix(number-field): avoid resetting value --- packages/components/number-field/src/use-number-field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-field/src/use-number-field.ts b/packages/components/number-field/src/use-number-field.ts index 0187baa15b..107bdc2181 100644 --- a/packages/components/number-field/src/use-number-field.ts +++ b/packages/components/number-field/src/use-number-field.ts @@ -319,6 +319,7 @@ export function useNumberField(originalProps: UseNumberFieldProps) { enabled: true, labelable: true, omitEventNames: new Set(Object.keys(inputProps)), + omitPropNames: new Set(["value"]), }), props, ), From db74c6498fb8c8451f82f2ecdf23a282c51b3f1b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 12 Jan 2025 22:12:29 +0800 Subject: [PATCH 076/131] fix(number-field): storybook --- .../stories/number-field.stories.tsx | 268 +++++------------- 1 file changed, 78 insertions(+), 190 deletions(-) diff --git a/packages/components/number-field/stories/number-field.stories.tsx b/packages/components/number-field/stories/number-field.stories.tsx index db1acfcc1f..7af9fd3bc8 100644 --- a/packages/components/number-field/stories/number-field.stories.tsx +++ b/packages/components/number-field/stories/number-field.stories.tsx @@ -90,195 +90,12 @@ const ControlledTemplate = (args) => { return (
- +

NumberField value: {value}

); }; -const StartContentTemplate = (args) => ( -
- - $ -
- } - /> - -); - -const EndContentTemplate = (args) => ( -
- - -
- } - label="Price" - placeholder="0.00" - /> - -); - -const StartAndEndContentTemplate = (args) => ( -
- - - -
- } - label="Price" - placeholder="0.00" - startContent={ -
- $ -
- } - /> - -); - -const CustomWithClassNamesTemplate = (args) => ( -
-
} - placeholder="Enter the amount" - /> - -); - -// const CustomWithHooksTemplate = (args: NumberFieldProps) => { -// const { -// Component, -// label, -// domRef, -// description, -// isClearable, -// startContent, -// endContent, -// shouldLabelBeOutside, -// shouldLabelBeInside, -// errorMessage, -// getBaseProps, -// getLabelProps, -// getNumberFieldProps, -// getInnerWrapperProps, -// getNumberFieldWrapperProps, -// getDescriptionProps, -// getErrorMessageProps, -// getClearButtonProps, -// } = useNumberField({ -// ...args, -// classNames: { -// label: "text-black/50 dark:text-white/90", -// input: [ -// "bg-transparent", -// "text-black/90 dark:text-white/90", -// "placeholder:text-default-700/50 dark:placeholder:text-white/60", -// ], -// innerWrapper: "bg-transparent", -// inputWrapper: [ -// "shadow-xl", -// "bg-default-200/50", -// "dark:bg-default/60", -// "backdrop-blur-xl", -// "backdrop-saturate-200", -// "hover:bg-default-200/70", -// "focus-within:!bg-default-200/50", -// "dark:hover:bg-default/70", -// "dark:focus-within:!bg-default/60", -// "!cursor-text", -// ], -// }, -// }); - -// const labelContent = ; - -// const end = React.useMemo(() => { -// if (isClearable) { -// return {endContent || }; -// } - -// return endContent; -// }, [isClearable, getClearButtonProps]); - -// const innerWrapper = React.useMemo(() => { -// if (startContent || end) { -// return ( -//
-// {startContent} -// -// {end} -//
-// ); -// } - -// return ; -// }, [startContent, end, getNumberFieldProps, getInnerWrapperProps]); - -// return ( -//
-// -// {shouldLabelBeOutside ? labelContent : null} -//
{ -// domRef.current?.focus(); -// }} -// > -// {shouldLabelBeInside ? labelContent : null} -// {innerWrapper} -//
-// {description &&
{description}
} -// {errorMessage &&
{errorMessage}
} -//
-//
-// ); -// }; - // const WithReactHookFormTemplate = (args: NumberFieldProps) => { // const { // register, @@ -331,7 +148,7 @@ const ServerValidationTemplate = (args: NumberFieldProps) => { validationErrors={serverErrors} onSubmit={onSubmit} > - + @@ -459,6 +276,7 @@ export const Disabled = { ...defaultProps, variant: "faded", isDisabled: true, + "aria-label": "width", }, }; @@ -469,6 +287,7 @@ export const ReadOnly = { ...defaultProps, variant: "bordered", isReadOnly: true, + "aria-label": "width", }, }; @@ -480,34 +299,72 @@ export const Clearable = { variant: "bordered", placeholder: "Enter a number", // eslint-disable-next-line no-console - onClear: () => console.log("input cleared"), + onClear: () => console.log("number field cleared"), + "aria-label": "width", }, }; export const StartContent = { - render: StartContentTemplate, + render: Template, args: { ...defaultProps, variant: "bordered", + label: "Price", + placeholder: "0.00", + startContent: ( +
+ $ +
+ ), }, }; export const EndContent = { - render: EndContentTemplate, + render: Template, args: { ...defaultProps, variant: "bordered", + label: "Price", + placeholde: "0.00", + endContent: ( +
+ +
+ ), }, }; export const StartAndEndContent = { - render: StartAndEndContentTemplate, + render: Template, args: { ...defaultProps, variant: "bordered", + label: "Price", + placeholder: "0.00", + endContent: ( +
+ + +
+ ), + startContent: ( +
+ $ +
+ ), }, }; @@ -518,6 +375,7 @@ export const WithErrorMessage = { ...defaultProps, isInvalid: true, errorMessage: "Please enter a valid number", + "aria-label": "width", }, }; @@ -568,6 +426,8 @@ export const WithServerValidation = { args: { ...defaultProps, + label: "width", + name: "width", }, }; @@ -580,6 +440,7 @@ export const IsInvalid = { isInvalid: true, placeholder: "Enter a number", errorMessage: "Please enter a valid range of numbers", + "aria-label": "width", }, }; @@ -589,6 +450,8 @@ export const Controlled = { args: { ...defaultProps, variant: "bordered", + placeholder: "Enter a number", + "aria-label": "width", }, }; @@ -615,10 +478,35 @@ export const MaxValue = { }; export const CustomWithClassNames = { - render: CustomWithClassNamesTemplate, + render: Template, args: { ...defaultProps, + classNames: { + label: "hidden", + inputWrapper: [ + "bg-slate-100", + "border", + "shadow", + "hover:bg-slate-200", + "focus-within:!bg-slate-100", + "dark:bg-slate-900", + "dark:hover:bg-slate-800", + "dark:border-slate-800", + "dark:focus-within:!bg-slate-900", + ], + innerWrapper: "gap-3", + input: [ + "text-base", + "text-slate-500", + "placeholder:text-slate-500", + "dark:text-slate-400", + "dark:placeholder:text-slate-400", + ], + }, + endContent:
, + placeholder: "Enter the amount", + "aria-label": "amount", }, }; From 64813cc027b7b9c1ac2abcae981a99f6a2cb4c4c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 12 Jan 2025 22:12:51 +0800 Subject: [PATCH 077/131] chore(docs): remove custom impl --- .../number-field/custom-impl.raw.jsx | 162 ------------------ .../components/number-field/custom-impl.ts | 9 - .../content/components/number-field/index.ts | 2 - 3 files changed, 173 deletions(-) delete mode 100644 apps/docs/content/components/number-field/custom-impl.raw.jsx delete mode 100644 apps/docs/content/components/number-field/custom-impl.ts diff --git a/apps/docs/content/components/number-field/custom-impl.raw.jsx b/apps/docs/content/components/number-field/custom-impl.raw.jsx deleted file mode 100644 index 9a75192076..0000000000 --- a/apps/docs/content/components/number-field/custom-impl.raw.jsx +++ /dev/null @@ -1,162 +0,0 @@ -import React, {forwardRef} from "react"; -import {useInput} from "@nextui-org/react"; - -export const SearchIcon = (props) => { - return ( - - ); -}; - -export const CloseFilledIcon = (props) => { - return ( - - ); -}; - -const styles = { - label: "text-black/50 dark:text-white/90", - input: [ - "bg-transparent", - "text-black/90 dark:text-white/90", - "placeholder:text-default-700/50 dark:placeholder:text-white/60", - ], - innerWrapper: "bg-transparent", - inputWrapper: [ - "shadow-xl", - "bg-default-200/50", - "dark:bg-default/60", - "backdrop-blur-xl", - "backdrop-saturate-200", - "hover:bg-default-200/70", - "focus-within:!bg-default-200/50", - "dark:hover:bg-default/70", - "dark:focus-within:!bg-default/60", - "!cursor-text", - ], -}; - -const MyInput = forwardRef((props, ref) => { - const { - Component, - label, - domRef, - description, - isClearable, - startContent, - endContent, - shouldLabelBeOutside, - shouldLabelBeInside, - errorMessage, - getBaseProps, - getLabelProps, - getInputProps, - getInnerWrapperProps, - getInputWrapperProps, - getDescriptionProps, - getErrorMessageProps, - getClearButtonProps, - } = useInput({ - ...props, - ref, - // this is just for the example, the props bellow should be passed by the parent component - label: "Search", - type: "search", - placeholder: "Type to search...", - startContent: ( - - ), - // custom styles - classNames: { - ...styles, - }, - }); - - const labelContent = ; - - const end = React.useMemo(() => { - if (isClearable) { - return {endContent || }; - } - - return endContent; - }, [isClearable, getClearButtonProps]); - - const innerWrapper = React.useMemo(() => { - if (startContent || end) { - return ( -
- {startContent} - - {end} -
- ); - } - - return ; - }, [startContent, end, getInputProps, getInnerWrapperProps]); - - return ( -
- - {shouldLabelBeOutside ? labelContent : null} -
{ - domRef.current?.focus(); - }} - onKeyDown={() => { - domRef.current?.focus(); - }} - > - {shouldLabelBeInside ? labelContent : null} - {innerWrapper} -
- {description &&
{description}
} - {errorMessage &&
{errorMessage}
} -
-
- ); -}); - -MyInput.displayName = "MyInput"; - -export default MyInput; diff --git a/apps/docs/content/components/number-field/custom-impl.ts b/apps/docs/content/components/number-field/custom-impl.ts deleted file mode 100644 index ab37512cec..0000000000 --- a/apps/docs/content/components/number-field/custom-impl.ts +++ /dev/null @@ -1,9 +0,0 @@ -import App from "./custom-impl.raw.jsx?raw"; - -const react = { - "/App.jsx": App, -}; - -export default { - ...react, -}; diff --git a/apps/docs/content/components/number-field/index.ts b/apps/docs/content/components/number-field/index.ts index c1cf547c29..f72d598e82 100644 --- a/apps/docs/content/components/number-field/index.ts +++ b/apps/docs/content/components/number-field/index.ts @@ -21,7 +21,6 @@ import customValidation from "./custom-validation"; import realTimeValidation from "./real-time-validation"; import serverValidation from "./server-validation"; import customStyles from "./custom-styles"; -import customImpl from "./custom-impl"; import formatOptions from "./format-options"; export const numberFieldContent = { @@ -48,6 +47,5 @@ export const numberFieldContent = { realTimeValidation, serverValidation, customStyles, - customImpl, formatOptions, }; From 4ee3dfee6ea1929fffb769fc798ebfaa859421f2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 12 Jan 2025 22:13:37 +0800 Subject: [PATCH 078/131] chore(docs): update docs code & content --- .../number-field/custom-styles.raw.jsx | 40 +++---------------- .../number-field/real-time-validation.raw.jsx | 4 ++ .../number-field/server-validation.raw.jsx | 9 ++--- .../content/docs/components/number-field.mdx | 27 +++++++------ 4 files changed, 28 insertions(+), 52 deletions(-) diff --git a/apps/docs/content/components/number-field/custom-styles.raw.jsx b/apps/docs/content/components/number-field/custom-styles.raw.jsx index 87437ddfe8..3274e22316 100644 --- a/apps/docs/content/components/number-field/custom-styles.raw.jsx +++ b/apps/docs/content/components/number-field/custom-styles.raw.jsx @@ -1,35 +1,5 @@ import {NumberField} from "@nextui-org/react"; -export const SearchIcon = (props) => { - return ( - - ); -}; - export default function App() { return (
@@ -56,12 +26,12 @@ export default function App() { "!cursor-text", ], }} - label="Search" - placeholder="Type to search..." + description="The number of apples that Marcus bought" + helperText="Must be equal or greater than 1" + label="Number of apples" + minValue={1} + placeholder="Enter a number..." radius="lg" - startContent={ - - } />
); diff --git a/apps/docs/content/components/number-field/real-time-validation.raw.jsx b/apps/docs/content/components/number-field/real-time-validation.raw.jsx index 9a7083b2d0..bc0106da1c 100644 --- a/apps/docs/content/components/number-field/real-time-validation.raw.jsx +++ b/apps/docs/content/components/number-field/real-time-validation.raw.jsx @@ -12,6 +12,10 @@ export default function App() { setSubmitted(data); }; + if (!width) { + errors.push("The value must not be empty"); + } + if (width < 100) { errors.push("The value must be greater than 100"); } diff --git a/apps/docs/content/components/number-field/server-validation.raw.jsx b/apps/docs/content/components/number-field/server-validation.raw.jsx index 82bffa2c17..7455a3cd50 100644 --- a/apps/docs/content/components/number-field/server-validation.raw.jsx +++ b/apps/docs/content/components/number-field/server-validation.raw.jsx @@ -25,10 +25,9 @@ export default function App() { @@ -90,13 +90,13 @@ const ControlledTemplate = (args) => { return (
- -

NumberField value: {value}

+ +

NumberInput value: {value}

); }; -// const WithReactHookFormTemplate = (args: NumberFieldProps) => { +// const WithReactHookFormTemplate = (args: NumberInputProps) => { // const { // register, // formState: {errors}, @@ -117,14 +117,14 @@ const ControlledTemplate = (args) => { // return ( //
-// -// -// +// +// // {errors.requiredField && This field is required} // @@ -299,7 +299,7 @@ export const Clearable = { variant: "bordered", placeholder: "Enter a number", // eslint-disable-next-line no-console - onClear: () => console.log("number field cleared"), + onClear: () => console.log("number input cleared"), "aria-label": "width", }, }; diff --git a/packages/components/number-field/tsconfig.json b/packages/components/number-input/tsconfig.json similarity index 100% rename from packages/components/number-field/tsconfig.json rename to packages/components/number-input/tsconfig.json diff --git a/packages/components/number-field/tsup.config.ts b/packages/components/number-input/tsup.config.ts similarity index 100% rename from packages/components/number-field/tsup.config.ts rename to packages/components/number-input/tsup.config.ts diff --git a/packages/core/react/package.json b/packages/core/react/package.json index d81ef44814..ed8488ce3d 100644 --- a/packages/core/react/package.json +++ b/packages/core/react/package.json @@ -88,7 +88,7 @@ "@heroui/drawer": "workspace:*", "@heroui/form": "workspace:*", "@heroui/alert": "workspace:*", - "@heroui/number-field": "workspace:*", + "@heroui/number-input": "workspace:*", "@react-aria/visually-hidden": "3.8.18" }, "peerDependencies": { diff --git a/packages/core/react/src/index.ts b/packages/core/react/src/index.ts index e79278ec5a..4a1cd11b20 100644 --- a/packages/core/react/src/index.ts +++ b/packages/core/react/src/index.ts @@ -47,7 +47,7 @@ export * from "@heroui/form"; export * from "@heroui/alert"; export * from "@heroui/drawer"; export * from "@heroui/input-otp"; -export * from "@heroui/number-field"; +export * from "@heroui/number-input"; /** * React Aria - Exports diff --git a/packages/core/theme/src/components/index.ts b/packages/core/theme/src/components/index.ts index d039745fbb..6aa6bdd29b 100644 --- a/packages/core/theme/src/components/index.ts +++ b/packages/core/theme/src/components/index.ts @@ -41,4 +41,4 @@ export * from "./date-picker"; export * from "./alert"; export * from "./drawer"; export * from "./form"; -export * from "./number-field"; +export * from "./number-input"; diff --git a/packages/core/theme/src/components/number-field.ts b/packages/core/theme/src/components/number-input.ts similarity index 97% rename from packages/core/theme/src/components/number-field.ts rename to packages/core/theme/src/components/number-input.ts index 73a970d27e..b0383de005 100644 --- a/packages/core/theme/src/components/number-field.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -1,14 +1,14 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {numberFieldLabelClasses, groupDataFocusVisibleClasses} from "../utils"; +import {numberInputLabelClasses, groupDataFocusVisibleClasses} from "../utils"; /** - * NumberField wrapper **Tailwind Variants** component + * NumberInput wrapper **Tailwind Variants** component * * @example * ```js - * const {base, label, inputWrapper, input, clearButton, description, helperText, errorMessage} = numberField({...}) + * const {base, label, inputWrapper, input, clearButton, description, helperText, errorMessage} = numberInput({...}) * *
* @@ -22,10 +22,10 @@ import {numberFieldLabelClasses, groupDataFocusVisibleClasses} from "../utils"; *
* ``` */ -const numberField = tv({ +const numberInput = tv({ slots: { base: "group flex flex-col data-[hidden=true]:hidden relative justify-end", - label: numberFieldLabelClasses, + label: numberInputLabelClasses, mainWrapper: "h-full flex flex-col", inputWrapper: "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-3 py-2 h-8 gap-3", @@ -52,7 +52,7 @@ const numberField = tv({ stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], verticalStepperWrapper: ["flex", "flex-col", "ps-1"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", - description: [...numberFieldLabelClasses, "text-tiny", "text-default-400"], + description: [...numberInputLabelClasses, "text-tiny", "text-default-400"], helperText: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", }, @@ -563,7 +563,7 @@ const numberField = tv({ ], }); -export type NumberFieldVariantProps = VariantProps; -export type NumberFieldSlots = keyof ReturnType; +export type NumberInputVariantProps = VariantProps; +export type NumberInputSlots = keyof ReturnType; -export {numberField}; +export {numberInput}; diff --git a/packages/core/theme/src/utils/classes.ts b/packages/core/theme/src/utils/classes.ts index a3a580dfe4..619eee6833 100644 --- a/packages/core/theme/src/utils/classes.ts +++ b/packages/core/theme/src/utils/classes.ts @@ -103,7 +103,7 @@ export const hiddenInputClasses = [ "disabled:cursor-default", ]; -export const numberFieldLabelClasses = [ +export const numberInputLabelClasses = [ "absolute", "pointer-events-none", "origin-top-left", diff --git a/packages/core/theme/src/utils/index.ts b/packages/core/theme/src/utils/index.ts index 0b2b3cf528..5f7e304717 100644 --- a/packages/core/theme/src/utils/index.ts +++ b/packages/core/theme/src/utils/index.ts @@ -8,7 +8,7 @@ export { absoluteFullClasses, collapseAdjacentVariantBorders, hiddenInputClasses, - numberFieldLabelClasses, + numberInputLabelClasses, } from "./classes"; export type {SlotsToClasses} from "./types"; export {colorVariants} from "./variants"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f50510dbdc..6869221b9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,9 +295,9 @@ importers: '@heroui/listbox': specifier: workspace:* version: link:../../packages/components/listbox - '@heroui/number-field': + '@heroui/number-input': specifier: workspace:* - version: link:../../packages/components/number-field + version: link:../../packages/components/number-input '@heroui/react': specifier: workspace:* version: link:../../packages/core/react @@ -2199,7 +2199,7 @@ importers: specifier: 0.13.0 version: 0.13.0(react@18.2.0) - packages/components/number-field: + packages/components/number-input: dependencies: '@heroui/button': specifier: workspace:* @@ -3257,9 +3257,9 @@ importers: '@heroui/navbar': specifier: workspace:* version: link:../../components/navbar - '@heroui/number-field': + '@heroui/number-input': specifier: workspace:* - version: link:../../components/number-field + version: link:../../components/number-input '@heroui/pagination': specifier: workspace:* version: link:../../components/pagination From 18d4cb1d52e3d343b555134b80bc08ddab52ea39 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 18 Jan 2025 00:26:00 +0800 Subject: [PATCH 083/131] fix(number-input): incorrect import --- .../components/number-input/stories/number-input.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index 65017da70d..701576377f 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -6,7 +6,7 @@ import React from "react"; import {Meta} from "@storybook/react"; import {button} from "@heroui/theme"; import {Form} from "@heroui/form"; -import {numberField} from "@heroui/theme"; +import {numberInput} from "@heroui/theme"; import {NumberInput, NumberInputProps} from "../src"; @@ -60,7 +60,7 @@ export default { } as Meta; const defaultProps = { - ...numberField.defaultVariants, + ...numberInput.defaultVariants, defaultValue: 24, }; From bfcafcc94e068d1d38aebd668d72e6c9e676417a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 18 Jan 2025 00:30:19 +0800 Subject: [PATCH 084/131] chore(docs): rename to number input --- apps/docs/config/routes.json | 4 +- .../number-input/clear-button.raw.jsx | 2 +- .../content/components/number-input/index.ts | 2 +- .../{number-field.mdx => number-input.mdx} | 56 +++++++++---------- apps/docs/content/docs/guide/forms.mdx | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) rename apps/docs/content/docs/components/{number-field.mdx => number-input.mdx} (89%) diff --git a/apps/docs/config/routes.json b/apps/docs/config/routes.json index d2dddd1ab1..ea749f7109 100644 --- a/apps/docs/config/routes.json +++ b/apps/docs/config/routes.json @@ -340,8 +340,8 @@ }, { "key": "number-input", - "title": "Number Field", - "keywords": "input, numeric input, number field", + "title": "Number Input", + "keywords": "input, numeric input, number input", "path": "/docs/components/number-input.mdx", "newPost": true }, diff --git a/apps/docs/content/components/number-input/clear-button.raw.jsx b/apps/docs/content/components/number-input/clear-button.raw.jsx index 4728f57ab6..8bb63b8c7b 100644 --- a/apps/docs/content/components/number-input/clear-button.raw.jsx +++ b/apps/docs/content/components/number-input/clear-button.raw.jsx @@ -10,7 +10,7 @@ export default function App() { placeholder="Enter the width" variant="bordered" // eslint-disable-next-line no-console - onClear={() => console.log("number field cleared")} + onClear={() => console.log("number input cleared")} /> ); } diff --git a/apps/docs/content/components/number-input/index.ts b/apps/docs/content/components/number-input/index.ts index f72d598e82..72bbcedba4 100644 --- a/apps/docs/content/components/number-input/index.ts +++ b/apps/docs/content/components/number-input/index.ts @@ -23,7 +23,7 @@ import serverValidation from "./server-validation"; import customStyles from "./custom-styles"; import formatOptions from "./format-options"; -export const numberFieldContent = { +export const numberInputContent = { usage, disabled, readOnly, diff --git a/apps/docs/content/docs/components/number-field.mdx b/apps/docs/content/docs/components/number-input.mdx similarity index 89% rename from apps/docs/content/docs/components/number-field.mdx rename to apps/docs/content/docs/components/number-input.mdx index 13c98ffe3b..872ff7bb48 100644 --- a/apps/docs/content/docs/components/number-field.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -1,11 +1,11 @@ --- -title: "Number Field" +title: "Number Input" description: "The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons" --- -import {numberFieldContent} from "@/content/components/number-input"; +import {numberInputContent} from "@/content/components/number-input"; -# Number Field +# Number Input The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons @@ -30,117 +30,117 @@ The numeric input component is designed for users to enter a number, and increas ## Usage - + ### Disabled - + ### Read Only - + ### Required If you pass the `isRequired` property to the input, it will have a `danger` asterisk at the end of the label and the input will be required. - + ### Sizes - + ### Colors - + ### Variants - + ### Radius - + ### Clear Button If you pass the `isClearable` property to NumberField, it will have a clear button at the end of NumberField, it will be visible when NumberField has a value. - + ### Hide Stepper You can hide the stepper buttons by passing the `hideStepper` property. - + ### Start & End Content You can use the `startContent` and `endContent` properties to add content to the start and end of NumberField. - + ### With Label You can add a label to NumberField by passing the `label` property. - + ### With Description You can add a description to NumberField by passing the `description` property. - + ### With Helper Text You can add a helper text to NumberField by passing the `helperText` property. - + ### With Min Value You can set the minimum value of the input by passing the `minValue` property. - + ### With Max Value You can set the maximum value of the input by passing the `maxValue` property. - + ### With Wheel Disabled You can disable changing the vaule with scroll in NumberField by passing the `isWheelDisabled` property. - + ### With Format Options You can format the value of the input by passing the `formatOptions` property. - + ### With Helper Text You can add a helper text to NumberField by passing the `helperText` property. - + ### With Error Message You can combine the `isInvalid` and `errorMessage` properties to show an invalid input. `errorMessage` is only shown when `isInvalid` is set to `true`. - + ### Controlled You can use the `value` and `onValueChange` properties to control NumberField value. - + > **Note**: NextUI `NumberField` also supports native events like `onChange`, useful for form libraries > such as [Formik](https://formik.org/) and [React Hook Form](https://react-hook-form.com/). @@ -153,13 +153,13 @@ You can use the `value` and `onValueChange` properties to control NumberField va In addition to built-in constraints, you can provide a function to the `validate` property for custom validation. - + #### Realtime Validation If you want to display validation errors while the user is typing, you can control the field value and use the `isInvalid` prop along with the `errorMessage` prop. - + #### Server Validation @@ -167,7 +167,7 @@ Client-side validation provides immediate feedback, but you should also validate NextUI allows you to display server-side validation errors by using the `validationErrors` prop in the `Form` component. This prop should be an object where each key is the field `name` and the value is the error message. - + ## Slots @@ -188,7 +188,7 @@ This prop should be an object where each key is the field `name` and the value i You can customize the `NumberField` component by passing custom Tailwind CSS classes to the component slots. - + diff --git a/apps/docs/content/docs/guide/forms.mdx b/apps/docs/content/docs/guide/forms.mdx index 49b3809638..55b6beebf5 100644 --- a/apps/docs/content/docs/guide/forms.mdx +++ b/apps/docs/content/docs/guide/forms.mdx @@ -232,7 +232,7 @@ You can change the form defaults for your entire app using [HeroUI Provider](/do Supported constraints include: - `isRequired` indicates that a field must have a value before the form can be submitted. -- `minValue` and `maxValue` specify the minimum and maximum value in a date picker or number field. +- `minValue` and `maxValue` specify the minimum and maximum value in a date picker or number input. - `minLength` and `maxLength` specify the minimum and length of text input. - `pattern` provides a custom regular expression that a text input must conform to. - `type="email"` and `type="url"` provide built-in validation for email addresses and URLs. From 569e224009dc3621c7df710c332aeb52ab2eeefd Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 18 Jan 2025 00:40:37 +0800 Subject: [PATCH 085/131] chore: change to number input --- .changeset/witty-flies-reflect.md | 4 +-- .../number-input/clear-button.raw.jsx | 4 +-- .../components/number-input/colors.raw.jsx | 4 +-- .../number-input/controlled.raw.jsx | 6 ++-- .../number-input/custom-styles.raw.jsx | 4 +-- .../number-input/custom-validation.raw.jsx | 4 +-- .../number-input/description.raw.jsx | 4 +-- .../components/number-input/disabled.raw.jsx | 4 +-- .../number-input/error-message.raw.jsx | 4 +-- .../number-input/format-options.raw.jsx | 12 +++---- .../number-input/helper-text.raw.jsx | 4 +-- .../number-input/hide-stepper.raw.jsx | 4 +-- .../number-input/is-wheel-disabled.raw.jsx | 4 +-- .../components/number-input/label.raw.jsx | 4 +-- .../components/number-input/max-value.raw.jsx | 4 +-- .../components/number-input/min-value.raw.jsx | 4 +-- .../components/number-input/radius.raw.jsx | 4 +-- .../components/number-input/readonly.raw.jsx | 4 +-- .../number-input/real-time-validation.raw.jsx | 4 +-- .../components/number-input/required.raw.jsx | 4 +-- .../number-input/server-validation.raw.jsx | 4 +-- .../components/number-input/sizes.raw.jsx | 4 +-- .../number-input/start-end-content.raw.jsx | 6 ++-- .../components/number-input/usage.raw.jsx | 4 +-- .../components/number-input/variants.raw.jsx | 6 ++-- .../content/docs/components/number-input.mdx | 36 +++++++++---------- .../content/docs/guide/nextui-to-heroui.mdx | 8 ++--- 27 files changed, 79 insertions(+), 79 deletions(-) diff --git a/.changeset/witty-flies-reflect.md b/.changeset/witty-flies-reflect.md index dcbdf96394..aeac481e55 100644 --- a/.changeset/witty-flies-reflect.md +++ b/.changeset/witty-flies-reflect.md @@ -2,7 +2,7 @@ "@nextui-org/number-input": patch "@nextui-org/shared-icons": patch "@nextui-org/theme": patch -"@nextui-org/react": minor +"@heroui/react": minor --- -introduce NumberField +introduce NumberInput diff --git a/apps/docs/content/components/number-input/clear-button.raw.jsx b/apps/docs/content/components/number-input/clear-button.raw.jsx index 8bb63b8c7b..4a21555536 100644 --- a/apps/docs/content/components/number-input/clear-button.raw.jsx +++ b/apps/docs/content/components/number-input/clear-button.raw.jsx @@ -1,8 +1,8 @@ -import {NumberField} from "@nextui-org/react"; +import {NumberInput} from "@heroui/react"; export default function App() { return ( - {colors.map((color) => ( - - -

NumberField value: {value}

+

NumberInput value: {value}

); } diff --git a/apps/docs/content/components/number-input/custom-styles.raw.jsx b/apps/docs/content/components/number-input/custom-styles.raw.jsx index 3274e22316..e78b8fa13e 100644 --- a/apps/docs/content/components/number-input/custom-styles.raw.jsx +++ b/apps/docs/content/components/number-input/custom-styles.raw.jsx @@ -1,9 +1,9 @@ -import {NumberField} from "@nextui-org/react"; +import {NumberInput} from "@heroui/react"; export default function App() { return (
- -
- - -
- - ; + return ; } diff --git a/apps/docs/content/components/number-input/max-value.raw.jsx b/apps/docs/content/components/number-input/max-value.raw.jsx index 7b7d2dd65c..933d7e842f 100644 --- a/apps/docs/content/components/number-input/max-value.raw.jsx +++ b/apps/docs/content/components/number-input/max-value.raw.jsx @@ -1,8 +1,8 @@ -import {NumberField} from "@nextui-org/react"; +import {NumberInput} from "@heroui/react"; export default function App() { return ( - {radius.map((r) => ( - - (
    {errors.map((error, i) => ( diff --git a/apps/docs/content/components/number-input/required.raw.jsx b/apps/docs/content/components/number-input/required.raw.jsx index d92690d04d..af0c372dba 100644 --- a/apps/docs/content/components/number-input/required.raw.jsx +++ b/apps/docs/content/components/number-input/required.raw.jsx @@ -1,8 +1,8 @@ -import {NumberField} from "@nextui-org/react"; +import {NumberInput} from "@heroui/react"; export default function App() { return ( - - {sizes.map((size) => (
    - +
    ))}
diff --git a/apps/docs/content/components/number-input/start-end-content.raw.jsx b/apps/docs/content/components/number-input/start-end-content.raw.jsx index 003190c8ce..879253adc0 100644 --- a/apps/docs/content/components/number-input/start-end-content.raw.jsx +++ b/apps/docs/content/components/number-input/start-end-content.raw.jsx @@ -1,4 +1,4 @@ -import {NumberField} from "@nextui-org/react"; +import {NumberInput} from "@heroui/react"; export const MailIcon = (props) => { return ( @@ -24,7 +24,7 @@ export default function App() { return (
- } /> -
diff --git a/apps/docs/content/components/number-input/controlled.raw.jsx b/apps/docs/content/components/number-input/controlled.raw.jsx index 8bb37c21d4..cca5915235 100644 --- a/apps/docs/content/components/number-input/controlled.raw.jsx +++ b/apps/docs/content/components/number-input/controlled.raw.jsx @@ -6,8 +6,8 @@ export default function App() { return (
diff --git a/apps/docs/content/components/number-input/custom-styles.raw.jsx b/apps/docs/content/components/number-input/custom-styles.raw.jsx index e78b8fa13e..4d602d7563 100644 --- a/apps/docs/content/components/number-input/custom-styles.raw.jsx +++ b/apps/docs/content/components/number-input/custom-styles.raw.jsx @@ -25,6 +25,7 @@ export default function App() { "dark:group-data-[focus=true]:bg-default/60", "!cursor-text", ], + helperText: "text-black/50 dark:text-white/90", }} description="The number of apples that Marcus bought" helperText="Must be equal or greater than 1" diff --git a/apps/docs/content/components/number-input/custom-validation.raw.jsx b/apps/docs/content/components/number-input/custom-validation.raw.jsx index 8276362171..1ce394601b 100644 --- a/apps/docs/content/components/number-input/custom-validation.raw.jsx +++ b/apps/docs/content/components/number-input/custom-validation.raw.jsx @@ -14,8 +14,8 @@ export default function App() { { if (value < 100) { diff --git a/apps/docs/content/components/number-input/description.raw.jsx b/apps/docs/content/components/number-input/description.raw.jsx index 3412f001b1..7df4ed5eae 100644 --- a/apps/docs/content/components/number-input/description.raw.jsx +++ b/apps/docs/content/components/number-input/description.raw.jsx @@ -5,8 +5,8 @@ export default function App() { ); } diff --git a/apps/docs/content/components/number-input/disabled.raw.jsx b/apps/docs/content/components/number-input/disabled.raw.jsx index 0aec288c46..d980fe8874 100644 --- a/apps/docs/content/components/number-input/disabled.raw.jsx +++ b/apps/docs/content/components/number-input/disabled.raw.jsx @@ -6,7 +6,7 @@ export default function App() { isDisabled className="max-w-xs" defaultValue={1024} - placeholder="Enter the width" + placeholder="Enter the amount" /> ); } diff --git a/apps/docs/content/components/number-input/error-message.raw.jsx b/apps/docs/content/components/number-input/error-message.raw.jsx index bc9f383150..05cb705833 100644 --- a/apps/docs/content/components/number-input/error-message.raw.jsx +++ b/apps/docs/content/components/number-input/error-message.raw.jsx @@ -7,8 +7,8 @@ export default function App() { defaultValue={1024} errorMessage="Please enter a valid number" isInvalid={true} - label="Width" - placeholder="Enter the width" + label="Amount" + placeholder="Enter the amount" variant="bordered" /> ); diff --git a/apps/docs/content/components/number-input/helper-text.raw.jsx b/apps/docs/content/components/number-input/helper-text.raw.jsx index a8471d2ec0..f6877f4103 100644 --- a/apps/docs/content/components/number-input/helper-text.raw.jsx +++ b/apps/docs/content/components/number-input/helper-text.raw.jsx @@ -4,12 +4,12 @@ export default function App() { return ( ); } diff --git a/apps/docs/content/components/number-input/hide-stepper.raw.jsx b/apps/docs/content/components/number-input/hide-stepper.raw.jsx index 2cbfa004b6..ce72f05dea 100644 --- a/apps/docs/content/components/number-input/hide-stepper.raw.jsx +++ b/apps/docs/content/components/number-input/hide-stepper.raw.jsx @@ -6,7 +6,7 @@ export default function App() { hideStepper className="max-w-xs" defaultValue={1024} - placeholder="Enter the width" + placeholder="Enter the amount" /> ); } diff --git a/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx b/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx index 184b975308..ad17d039fa 100644 --- a/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx +++ b/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx @@ -6,7 +6,7 @@ export default function App() { isWheelDisabled className="max-w-xs" defaultValue={1024} - placeholder="Enter the width" + placeholder="Enter the amount" /> ); } diff --git a/apps/docs/content/components/number-input/label.raw.jsx b/apps/docs/content/components/number-input/label.raw.jsx index 2175e50544..8b7b6e368a 100644 --- a/apps/docs/content/components/number-input/label.raw.jsx +++ b/apps/docs/content/components/number-input/label.raw.jsx @@ -1,5 +1,5 @@ import {NumberInput} from "@heroui/react"; export default function App() { - return ; + return ; } diff --git a/apps/docs/content/components/number-input/max-value.raw.jsx b/apps/docs/content/components/number-input/max-value.raw.jsx index 933d7e842f..b392ce629f 100644 --- a/apps/docs/content/components/number-input/max-value.raw.jsx +++ b/apps/docs/content/components/number-input/max-value.raw.jsx @@ -7,7 +7,7 @@ export default function App() { className="max-w-xs" helperText="The value of the element should be less than 100" maxValue={100} - placeholder="Enter the width" + placeholder="Enter the amount" /> ); } diff --git a/apps/docs/content/components/number-input/min-value.raw.jsx b/apps/docs/content/components/number-input/min-value.raw.jsx index f71fb1b691..dac788d63e 100644 --- a/apps/docs/content/components/number-input/min-value.raw.jsx +++ b/apps/docs/content/components/number-input/min-value.raw.jsx @@ -7,7 +7,7 @@ export default function App() { className="max-w-xs" helperText="The value of the element should be greater than 100" minValue={100} - placeholder="Enter the width" + placeholder="Enter the amount" /> ); } diff --git a/apps/docs/content/components/number-input/radius.raw.jsx b/apps/docs/content/components/number-input/radius.raw.jsx index e5d07e190b..f734457198 100644 --- a/apps/docs/content/components/number-input/radius.raw.jsx +++ b/apps/docs/content/components/number-input/radius.raw.jsx @@ -10,7 +10,7 @@ export default function App() { key={r} className="max-w-[220px]" defaultValue={1024} - placeholder="Enter the width" + placeholder="Enter the amount" radius={r} /> ))} diff --git a/apps/docs/content/components/number-input/readonly.raw.jsx b/apps/docs/content/components/number-input/readonly.raw.jsx index 4b71652d32..42308e5c98 100644 --- a/apps/docs/content/components/number-input/readonly.raw.jsx +++ b/apps/docs/content/components/number-input/readonly.raw.jsx @@ -6,7 +6,7 @@ export default function App() { isReadOnly className="max-w-xs" defaultValue={1024} - placeholder="Enter the width" + placeholder="Enter the amount" variant="bordered" /> ); diff --git a/apps/docs/content/components/number-input/real-time-validation.raw.jsx b/apps/docs/content/components/number-input/real-time-validation.raw.jsx index 9a61abbb38..6d1e27e67d 100644 --- a/apps/docs/content/components/number-input/real-time-validation.raw.jsx +++ b/apps/docs/content/components/number-input/real-time-validation.raw.jsx @@ -2,7 +2,7 @@ import {Button, Form, NumberInput} from "@heroui/react"; export default function App() { const [submitted, setSubmitted] = React.useState(null); - const [width, setWidth] = React.useState(null); + const [amount, setAmount] = React.useState(null); const errors = []; const onSubmit = (e) => { @@ -12,15 +12,15 @@ export default function App() { setSubmitted(data); }; - if (!width) { + if (!amount) { errors.push("The value must not be empty"); } - if (width < 100) { + if (amount < 100) { errors.push("The value must be greater than 100"); } - if (width > 1000) { + if (amount > 1000) { errors.push("The value must be less than 1000"); } @@ -35,11 +35,11 @@ export default function App() { )} isInvalid={errors.length > 0} - label="Width" - name="width" + label="Amount" + name="amount" placeholder="Enter a number" - value={width} - onValueChange={setWidth} + value={amount} + onValueChange={setAmount} /> - ); -}; - -NumberInputHorizontalStepper.displayName = "HeroUI.NumberInputHorizontalStepper"; - -export default NumberInputHorizontalStepper; diff --git a/packages/components/number-input/src/number-input.tsx b/packages/components/number-input/src/number-input.tsx index b352ecbe10..09896efe33 100644 --- a/packages/components/number-input/src/number-input.tsx +++ b/packages/components/number-input/src/number-input.tsx @@ -4,7 +4,6 @@ import {forwardRef} from "@heroui/system"; import {UseNumberInputProps, useNumberInput} from "./use-number-input"; import NumberInputVerticalStepper from "./number-input-vertical-stepper"; -import NumberInputHorizontalStepper from "./number-input-horiztonal-stepper"; export interface NumberInputProps extends UseNumberInputProps {} @@ -21,7 +20,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { errorMessage, isInvalid, hideStepper, - steps, getBaseProps, getLabelProps, getNumberInputProps, @@ -83,30 +81,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { ]); const innerWrapper = useMemo(() => { - if (hideStepper) { - return ( -
- {startContent} - - - {end} -
- ); - } - - if (steps === "horizontal") { - return ( -
- - {startContent} - - - {end} - -
- ); - } - return (
{startContent} diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index e6e96400eb..ae4ee70cce 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -74,10 +74,6 @@ export interface Props extends Omit, keyof NumberInputV * ``` */ classNames?: SlotsToClasses; - /** - * Whether the stepper is placed horizontally or vertically - */ - steps?: "horizontal" | "vertical"; /** * Whether to hide the increment and decrement buttons. */ @@ -124,7 +120,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { innerWrapperRef: innerWrapperRefProp, onValueChange, hideStepper, - steps = "vertical", ...otherProps } = props; @@ -303,7 +298,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { "data-filled": dataAttr(isFilled), "data-has-start-content": dataAttr(hasStartContent), "data-has-end-content": dataAttr(!!endContent), - "data-direction": steps, className: slots.input({ class: clsx(classNames?.input, isFilled ? "is-filled" : ""), }), @@ -544,7 +538,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { isInvalid, errorMessage, hideStepper, - steps, incrementButtonProps, decrementButtonProps, getBaseProps, diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index b0383de005..7a6958452a 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -34,7 +34,6 @@ const numberInput = tv({ "w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none", "data-[has-start-content=true]:ps-1.5", "data-[has-end-content=true]:pe-1.5", - "data-[direction=horizontal]:text-center", "autofill:bg-transparent bg-clip-text", ], clearButton: [ From a6a80a29a18cc6390a6ef50466e77e0c01675ee7 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 20:43:17 +0800 Subject: [PATCH 090/131] refactor: remove helper text --- .../content/docs/components/number-input.mdx | 21 +-------------- .../number-input/src/number-input.tsx | 16 +++++------ .../number-input/src/use-number-input.ts | 27 ++----------------- .../stories/number-input.stories.tsx | 21 +++------------ .../core/theme/src/components/number-input.ts | 24 ++++++++++++----- packages/core/theme/src/utils/classes.ts | 23 ---------------- packages/core/theme/src/utils/index.ts | 1 - 7 files changed, 30 insertions(+), 103 deletions(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index fe83164dd8..eaf89f1718 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -94,12 +94,6 @@ You can add a description to NumberInput by passing the `description` property. -### With Helper Text - -You can add a helper text to NumberInput by passing the `helperText` property. - - - ### With Min Value You can set the minimum value of the input by passing the `minValue` property. @@ -124,12 +118,6 @@ You can format the value of the input by passing the `formatOptions` property. -### With Helper Text - -You can add a helper text to NumberInput by passing the `helperText` property. - - - ### With Error Message You can combine the `isInvalid` and `errorMessage` properties to show an invalid input. `errorMessage` is only shown when `isInvalid` is set to `true`. @@ -181,7 +169,6 @@ This prop should be an object where each key is the field `name` and the value i - **stepperButton**: The stepper button to increase or decrease the value. - **verticalStepperWrapper**: The wrapper for the vertical stepper. - **description**: The description of NumberInput. -- **helperText**: The helper text of NumberInput. - **errorMessage**: The error message of NumberInput. ### Custom Styles @@ -304,12 +291,6 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla description: "The default value of the input (uncontrolled).", default: "-" }, - { - attribute: "helperText", - type: "ReactNode", - description: "A helper text for the input. Provides a hint such as specific requirements for what to choose.", - default: "-" - }, { attribute: "placeholder", type: "string", @@ -450,7 +431,7 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla }, { attribute: "classNames", - type: "Partial>", + type: "Partial>", description: "Allows to set custom class names for the Input slots.", default: "-" } diff --git a/packages/components/number-input/src/number-input.tsx b/packages/components/number-input/src/number-input.tsx index 09896efe33..7667d5f988 100644 --- a/packages/components/number-input/src/number-input.tsx +++ b/packages/components/number-input/src/number-input.tsx @@ -12,7 +12,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { Component, label, description, - helperText, isClearable, startContent, endContent, @@ -29,7 +28,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { getMainWrapperProps, getHelperWrapperProps, getDescriptionProps, - getHelperTextProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, @@ -39,10 +37,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { const labelContent = label ? : null; - const descriptionContent = description ? ( -
{description}
- ) : null; - const end = useMemo(() => { if (isClearable) { return ( @@ -60,14 +54,17 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { const helperWrapper = useMemo(() => { const shouldShowError = isInvalid && errorMessage; - const hasContent = shouldShowError || description || helperText; + const hasContent = shouldShowError || description; if (!hasHelper || !hasContent) return null; return (
- {shouldShowError &&
{errorMessage}
} - {helperText &&
{helperText}
} + {shouldShowError ? ( +
{errorMessage}
+ ) : ( +
{description}
+ )}
); }, [ @@ -102,7 +99,6 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => {
{labelContent} - {descriptionContent} {innerWrapper}
{helperWrapper} diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index ae4ee70cce..0d18b721c8 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -47,10 +47,6 @@ export interface Props extends Omit, keyof NumberInputV * default clear button. */ endContent?: React.ReactNode; - /** - * A helper text for the field. Provides a hint such as specific requirements for what to choose. - */ - helperText?: React.ReactNode; /** * Classname or List of classes to change the classNames of the element. * if `className` is passed, it will be added to the base slot. @@ -68,7 +64,6 @@ export interface Props extends Omit, keyof NumberInputV * helperWrapper: "helper-wrapper-classes", * verticalStepperWrapper: "vertical-stepper-wrapper-classes", * description: "description-classes", - * helperText: "helper-text-classes", * errorMessage: "error-message-classes", * }} /> * ``` @@ -107,7 +102,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { baseRef, wrapperRef, description, - helperText, className, classNames, autoFocus, @@ -208,11 +202,10 @@ export function useNumberInput(originalProps: UseNumberInputProps) { ? props.errorMessage({isInvalid, validationErrors, validationDetails}) : props.errorMessage || validationErrors?.join(" "); const isClearable = !!onClear || originalProps.isClearable; - const hasElements = !!label || !!description || !!errorMessage || !!helperText; + const hasElements = !!label || !!description || !!errorMessage; const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; - const hasHelper = !!helperText || !!errorMessage; - const hasDescription = !!description; + const hasHelper = !!description || !!errorMessage; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; @@ -249,7 +242,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { "data-disabled": dataAttr(originalProps.isDisabled), "data-has-elements": dataAttr(hasElements), "data-has-helper": dataAttr(hasHelper), - "data-has-description": dataAttr(hasDescription), "data-has-label": dataAttr(hasLabel), "data-has-value": dataAttr(!isPlaceholderShown), ...focusWithinProps, @@ -267,7 +259,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { hasHelper, hasLabel, hasElements, - hasDescription, isPlaceholderShown, hasStartContent, isFocusWithin, @@ -436,18 +427,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { [slots, classNames?.description], ); - const getHelperTextProps: PropGetter = useCallback( - (props = {}) => { - return { - ...props, - ...descriptionProps, // apply description props - "data-slot": "helper-text", - className: slots.helperText({class: clsx(classNames?.helperText, props?.className)}), - }; - }, - [slots, classNames?.helperText], - ); - const getErrorMessageProps: PropGetter = useCallback( (props = {}) => { return { @@ -528,7 +507,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { domRef, label, description, - helperText, startContent, endContent, isClearable, @@ -549,7 +527,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { getInnerWrapperProps, getHelperWrapperProps, getDescriptionProps, - getHelperTextProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index 8d19220c78..ba7e32e4cd 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -184,19 +184,6 @@ export const WithDescription = { }, }; -export const WithHelperText = { - render: Template, - - args: { - ...defaultProps, - label: "Amount", - description: "Specify the amount", - helperText: "Amount should be between 5 and 50", - minValue: 5, - maxValue: 50, - }, -}; - export const WithStepValue = { render: Template, @@ -204,7 +191,7 @@ export const WithStepValue = { ...defaultProps, label: "Amount", step: 10, - helperText: "Set `step` to `10` to increment / decrement the value by 10.", + description: "Set `step` to `10` to increment / decrement the value by 10.", }, }; @@ -215,7 +202,7 @@ export const WithWheelDisabled = { ...defaultProps, label: "Amount", step: 10, - helperText: "Set `isWheelDisabled` to `true` to disable the wheel.", + description: "Set `isWheelDisabled` to `true` to disable the wheel.", isWheelDisabled: true, }, }; @@ -227,7 +214,7 @@ export const HorizontalStepper = { ...defaultProps, steps: "horizontal", label: "Horizontal Stepper", - helperText: "Set `steps` to `horizontal` to show the stepper horizontally.", + description: "Set `steps` to `horizontal` to show the stepper horizontally.", }, }; @@ -253,7 +240,7 @@ export const HideStepper = { ...defaultProps, hideStepper: true, label: "Hide Stepper", - helperText: "Set `hideStepper` to `true` to hide the stepper.", + description: "Set `hideStepper` to `true` to hide the stepper.", }, }; diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index 7a6958452a..351c9d1162 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -1,23 +1,22 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {numberInputLabelClasses, groupDataFocusVisibleClasses} from "../utils"; +import {groupDataFocusVisibleClasses} from "../utils"; /** * NumberInput wrapper **Tailwind Variants** component * * @example * ```js - * const {base, label, inputWrapper, input, clearButton, description, helperText, errorMessage} = numberInput({...}) + * const {base, label, inputWrapper, input, clearButton, description, errorMessage} = numberInput({...}) * *
* - * Description *
* * *
- * Helper text + * Description * Invalid input *
* ``` @@ -25,7 +24,19 @@ import {numberInputLabelClasses, groupDataFocusVisibleClasses} from "../utils"; const numberInput = tv({ slots: { base: "group flex flex-col data-[hidden=true]:hidden relative justify-end", - label: numberInputLabelClasses, + label: [ + "absolute", + "z-10", + "pointer-events-none", + "origin-top-left", + "flex-shrink-0", + // Using RTL here as Tailwind CSS doesn't support `start` and `end` logical properties for transforms yet. + "rtl:origin-top-right", + "subpixel-antialiased", + "block", + "text-small", + "text-foreground-500", + ], mainWrapper: "h-full flex flex-col", inputWrapper: "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-3 py-2 h-8 gap-3", @@ -51,8 +62,7 @@ const numberInput = tv({ stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], verticalStepperWrapper: ["flex", "flex-col", "ps-1"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", - description: [...numberInputLabelClasses, "text-tiny", "text-default-400"], - helperText: "text-tiny text-foreground-400", + description: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", }, variants: { diff --git a/packages/core/theme/src/utils/classes.ts b/packages/core/theme/src/utils/classes.ts index 619eee6833..2711a48a3b 100644 --- a/packages/core/theme/src/utils/classes.ts +++ b/packages/core/theme/src/utils/classes.ts @@ -102,26 +102,3 @@ export const hiddenInputClasses = [ // Disabled state "disabled:cursor-default", ]; - -export const numberInputLabelClasses = [ - "absolute", - "pointer-events-none", - "origin-top-left", - "flex-shrink-0", - // Using RTL here as Tailwind CSS doesn't support `start` and `end` logical properties for transforms yet. - "rtl:origin-top-right", - "subpixel-antialiased", - "block", - "text-small", - "text-default-foreground", - "pb-0", - "z-20", - "top-1/2", - "-translate-y-1/2", - "group-data-[filled-within=true]:pointer-events-auto", - "group-data-[filled-within=true]:start-0", - "pe-2", - "max-w-full", - "text-ellipsis", - "overflow-hidden", -]; diff --git a/packages/core/theme/src/utils/index.ts b/packages/core/theme/src/utils/index.ts index 5f7e304717..f80e69ec6b 100644 --- a/packages/core/theme/src/utils/index.ts +++ b/packages/core/theme/src/utils/index.ts @@ -8,7 +8,6 @@ export { absoluteFullClasses, collapseAdjacentVariantBorders, hiddenInputClasses, - numberInputLabelClasses, } from "./classes"; export type {SlotsToClasses} from "./types"; export {colorVariants} from "./variants"; From 05273f44e374a64ca1d6d291ca41241adaab0307 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 21:20:03 +0800 Subject: [PATCH 091/131] feat(number-input): label placement --- .../number-input/src/number-input.tsx | 32 +- .../number-input/src/use-number-input.ts | 40 +- .../stories/number-input.stories.tsx | 47 +++ .../core/theme/src/components/number-input.ts | 352 +++++++++++++++--- 4 files changed, 407 insertions(+), 64 deletions(-) diff --git a/packages/components/number-input/src/number-input.tsx b/packages/components/number-input/src/number-input.tsx index 7667d5f988..1548373c5d 100644 --- a/packages/components/number-input/src/number-input.tsx +++ b/packages/components/number-input/src/number-input.tsx @@ -15,7 +15,10 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { isClearable, startContent, endContent, + labelPlacement, hasHelper, + isOutsideLeft, + shouldLabelBeOutside, errorMessage, isInvalid, hideStepper, @@ -32,7 +35,7 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { getClearButtonProps, getStepperIncreaseButtonProps, getStepperDecreaseButtonProps, - getVerticalStepperWrapperProps, + getStepperWrapperProps, } = useNumberInput({...props, ref}); const labelContent = label ? : null; @@ -85,7 +88,7 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { {end} {!hideStepper && ( -
+
@@ -95,17 +98,31 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { }, [startContent, end, getNumberInputProps, getInnerWrapperProps]); const mainWrapper = useMemo(() => { + if (shouldLabelBeOutside) { + return ( +
+
+ {!isOutsideLeft ? labelContent : null} + {innerWrapper} +
+ {helperWrapper} +
+ ); + } + return ( -
+ <>
{labelContent} {innerWrapper}
{helperWrapper} -
+ ); }, [ + labelPlacement, helperWrapper, + shouldLabelBeOutside, labelContent, innerWrapper, errorMessage, @@ -116,7 +133,12 @@ const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => { getDescriptionProps, ]); - return {mainWrapper}; + return ( + + {isOutsideLeft ? labelContent : null} + {mainWrapper} + + ); }); NumberInput.displayName = "HeroUI.NumberInput"; diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index 0d18b721c8..3521bb3295 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -62,7 +62,7 @@ export interface Props extends Omit, keyof NumberInputV * input: "input-classes", * clearButton: "clear-button-classes", * helperWrapper: "helper-wrapper-classes", - * verticalStepperWrapper: "vertical-stepper-wrapper-classes", + * stepperWrapper: "stepper-wrapper-classes", * description: "description-classes", * errorMessage: "error-message-classes", * }} /> @@ -153,8 +153,11 @@ export function useNumberInput(originalProps: UseNumberInputProps) { } = useAriaNumberInput(originalProps, state, domRef); const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; + const isFilled = !isEmpty(inputValue); + const isFilledWithin = isFilled || isFocusWithin; + const baseStyles = clsx(classNames?.base, className, isFilled ? "is-filled" : ""); const handleClear = useCallback(() => { @@ -197,6 +200,14 @@ export function useNumberInput(originalProps: UseNumberInputProps) { const isInvalid = validationState === "invalid" || isAriaInvalid; + const labelPlacement = useMemo(() => { + if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) { + return "outside"; + } + + return originalProps.labelPlacement ?? "inside"; + }, [originalProps.labelPlacement, label]); + const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({isInvalid, validationErrors, validationDetails}) @@ -206,11 +217,21 @@ export function useNumberInput(originalProps: UseNumberInputProps) { const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; const hasHelper = !!description || !!errorMessage; + const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; + const shouldLabelBeInside = labelPlacement === "inside"; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; + const isOutsideLeft = labelPlacement === "outside-left"; const hasStartContent = !!startContent; + const isLabelOutside = shouldLabelBeOutside + ? labelPlacement === "outside-left" || + hasPlaceholder || + (labelPlacement === "outside" && hasStartContent) + : false; + const isLabelOutsideAsPlaceholder = + labelPlacement === "outside" && !hasPlaceholder && !hasStartContent; const slots = useMemo( () => @@ -232,6 +253,9 @@ export function useNumberInput(originalProps: UseNumberInputProps) { "data-filled": dataAttr( isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown, ), + "data-filled-within": dataAttr( + isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown, + ), "data-focus-within": dataAttr(isFocusWithin), "data-focus-visible": dataAttr(isFocusVisible), "data-readonly": dataAttr(originalProps.isReadOnly), @@ -456,12 +480,12 @@ export function useNumberInput(originalProps: UseNumberInputProps) { [slots, isClearButtonFocusVisible, clearPressProps, clearFocusProps, classNames?.clearButton], ); - const getVerticalStepperWrapperProps: PropGetter = useCallback( + const getStepperWrapperProps: PropGetter = useCallback( (props = {}) => { return { ...props, - className: slots.verticalStepperWrapper({ - class: clsx(classNames?.verticalStepperWrapper, props?.className), + className: slots.stepperWrapper({ + class: clsx(classNames?.stepperWrapper, props?.className), }), }; }, @@ -509,9 +533,15 @@ export function useNumberInput(originalProps: UseNumberInputProps) { description, startContent, endContent, + labelPlacement, isClearable, hasHelper, hasStartContent, + isLabelOutside, + isOutsideLeft, + isLabelOutsideAsPlaceholder, + shouldLabelBeOutside, + shouldLabelBeInside, hasPlaceholder, isInvalid, errorMessage, @@ -531,7 +561,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) { getClearButtonProps, getStepperIncreaseButtonProps, getStepperDecreaseButtonProps, - getVerticalStepperWrapperProps, + getStepperWrapperProps, }; } diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index ba7e32e4cd..aca5d148e8 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -38,6 +38,12 @@ export default { }, options: ["sm", "md", "lg"], }, + labelPlacement: { + control: { + type: "select", + }, + options: ["inside", "outside", "outside-left"], + }, isDisabled: { control: { type: "boolean", @@ -96,6 +102,37 @@ const ControlledTemplate = (args) => { ); }; +const LabelPlacementTemplate = (args) => ( +
+
+

Without placeholder

+
+ + + +
+
+
+

With placeholder

+
+ + + +
+
+
+); + // const WithReactHookFormTemplate = (args: NumberInputProps) => { // const { // register, @@ -278,6 +315,16 @@ export const ReadOnly = { }, }; +export const LabelPlacement = { + render: LabelPlacementTemplate, + + args: { + ...defaultProps, + label: "Amount", + defaultValue: undefined, + }, +}; + export const Clearable = { render: Template, diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index 351c9d1162..b26e66d991 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -1,7 +1,7 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {groupDataFocusVisibleClasses} from "../utils"; +import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; /** * NumberInput wrapper **Tailwind Variants** component @@ -23,7 +23,7 @@ import {groupDataFocusVisibleClasses} from "../utils"; */ const numberInput = tv({ slots: { - base: "group flex flex-col data-[hidden=true]:hidden relative justify-end", + base: "group flex flex-col data-[hidden=true]:hidden", label: [ "absolute", "z-10", @@ -37,9 +37,9 @@ const numberInput = tv({ "text-small", "text-foreground-500", ], - mainWrapper: "h-full flex flex-col", + mainWrapper: "h-full", inputWrapper: - "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-3 py-2 h-8 gap-3", + "relative w-full inline-flex tap-highlight-transparent flex-row items-center shadow-sm px-3 gap-3", innerWrapper: "inline-flex w-full items-center h-full box-border", input: [ "w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none", @@ -48,19 +48,26 @@ const numberInput = tv({ "autofill:bg-transparent bg-clip-text", ], clearButton: [ - "opacity-0", + "p-2", + "-m-2", + "z-10", + "absolute", + "end-3", + "start-auto", "pointer-events-none", - "group-data-[invalid=true]:text-danger", - "peer-data-[filled=true]:opacity-100", // on mobile is always visible when there is a value - "peer-data-[filled=true]:pointer-events-auto", - "peer-data-[filled=true]:cursor-pointer", - "sm:peer-data-[filled=true]:opacity-0", // only visible on hover - "sm:peer-data-[filled=true]:pointer-events-none", - "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", - "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", + "appearance-none", + "outline-none", + "select-none", + "opacity-0", + "hover:!opacity-100", + "cursor-pointer", + "active:!opacity-70", + "rounded-full", + // focus ring + ...dataFocusVisibleClasses, ], stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], - verticalStepperWrapper: ["flex", "flex-col", "ps-1"], + stepperWrapper: ["flex", "flex-col", "ps-1"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", @@ -115,6 +122,7 @@ const numberInput = tv({ "group-data-[focus=true]:after:w-full", ], innerWrapper: "pb-1", + label: "group-data-[filled-within=true]:text-foreground", }, }, color: { @@ -127,56 +135,21 @@ const numberInput = tv({ }, size: { sm: { - label: [ - "start-0", - "text-tiny", - "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_16px)]", - "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_32px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", + label: "text-tiny", inputWrapper: "h-8 min-h-8 px-2 rounded-small", input: "text-small", clearButton: "text-medium", - description: [ - "start-0", - "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", - "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", - ], }, md: { - label: [ - "start-0", - "end-auto", - "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", - "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_36px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", inputWrapper: "h-10 min-h-10 rounded-medium", input: "text-small", clearButton: "text-large", - description: [ - "start-0", - "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", - "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_20px)]", - ], }, lg: { - label: [ - "start-0", - "end-auto", - "text-medium", - "-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", - "group-data-[has-description=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_40px)]", - ], - base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", + label: "text-medium", inputWrapper: "h-12 min-h-12 rounded-large", input: "text-medium", clearButton: "text-large", - description: [ - "start-0", - "-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", - "group-data-[has-label=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_24px)]", - ], }, }, radius: { @@ -196,6 +169,22 @@ const numberInput = tv({ inputWrapper: "rounded-full", }, }, + labelPlacement: { + outside: { + mainWrapper: "flex flex-col", + }, + "outside-left": { + base: "flex-row items-center flex-nowrap data-[has-helper=true]:items-start", + inputWrapper: "flex-1", + mainWrapper: "flex flex-col", + label: "relative text-foreground pe-2 ps-2 pointer-events-auto", + }, + inside: { + label: "cursor-text", + inputWrapper: "flex-col items-start justify-center gap-0", + innerWrapper: "group-data-[has-label=true]:items-end", + }, + }, fullWidth: { true: { base: "w-full", @@ -205,9 +194,11 @@ const numberInput = tv({ isClearable: { true: { input: "peer pe-6 input-search-cancel-button-none", - }, - false: { - clearButton: "hidden", + clearButton: [ + "peer-data-[filled=true]:pointer-events-auto", + "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block", + "peer-data-[filled=true]:scale-100", + ], }, }, isDisabled: { @@ -259,6 +250,7 @@ const numberInput = tv({ color: "default", size: "md", fullWidth: true, + labelPlacement: "inside", isDisabled: false, }, compoundVariants: [ @@ -481,6 +473,22 @@ const numberInput = tv({ label: "text-danger", }, }, + // labelPlacement=inside & default + { + labelPlacement: "inside", + color: "default", + class: { + label: "group-data-[filled-within=true]:text-default-600", + }, + }, + // labelPlacement=outside & default + { + labelPlacement: "outside", + color: "default", + class: { + label: "group-data-[filled-within=true]:text-foreground", + }, + }, // radius-full & size { radius: "full", @@ -554,6 +562,101 @@ const numberInput = tv({ inputWrapper: "after:!bg-danger", }, }, + // size & labelPlacement + { + labelPlacement: "inside", + size: "sm", + class: { + inputWrapper: "h-12 py-1.5 px-3", + }, + }, + { + labelPlacement: "inside", + size: "md", + class: { + inputWrapper: "h-14 py-2", + }, + }, + { + labelPlacement: "inside", + size: "lg", + class: { + inputWrapper: "h-16 py-2.5 gap-0", + }, + }, + // size & labelPlacement & variant=[faded, bordered] + { + labelPlacement: "inside", + size: "sm", + variant: ["bordered", "faded"], + class: { + inputWrapper: "py-1", + }, + }, + // labelPlacement=[inside,outside] + { + labelPlacement: ["inside", "outside"], + class: { + label: ["group-data-[filled-within=true]:pointer-events-auto"], + }, + }, + // labelPlacement=[outside] + { + labelPlacement: "outside", + class: { + base: "relative justify-end", + label: [ + "pb-0", + "z-20", + "top-1/2", + "-translate-y-1/2", + "group-data-[filled-within=true]:start-0", + ], + }, + }, + // labelPlacement=[inside] + { + labelPlacement: ["inside"], + class: { + label: ["group-data-[filled-within=true]:scale-85"], + }, + }, + { + labelPlacement: "inside", + size: "sm", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.tiny)/2_-_8px)]", + ], + }, + }, + { + labelPlacement: "inside", + size: "md", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_6px)]", + ], + }, + }, + { + labelPlacement: "inside", + size: "lg", + class: { + label: [ + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_8px)]", + ], + }, + }, + // labelPlacement=[inside] & variant=flat + { + labelPlacement: ["inside"], + variant: "flat", + class: { + innerWrapper: "pb-0.5", + }, + }, // variant=underlined & size { variant: "underlined", @@ -569,6 +672,147 @@ const numberInput = tv({ innerWrapper: "pb-1.5", }, }, + // inside & size + { + labelPlacement: "inside", + size: ["sm", "md"], + class: { + label: "text-small", + }, + }, + // inside & size & [faded, bordered] + { + labelPlacement: "inside", + variant: ["faded", "bordered"], + size: "sm", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.tiny)/2_-_8px_-_theme(borderWidth.medium))]", + ], + }, + }, + { + labelPlacement: "inside", + variant: ["faded", "bordered"], + isMultiline: false, + size: "md", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_6px_-_theme(borderWidth.medium))]", + ], + }, + }, + { + labelPlacement: "inside", + variant: ["faded", "bordered"], + size: "lg", + class: { + label: [ + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_8px_-_theme(borderWidth.medium))]", + ], + }, + }, + // inside & size & underlined + { + labelPlacement: "inside", + variant: "underlined", + size: "sm", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.tiny)/2_-_5px)]", + ], + }, + }, + { + labelPlacement: "inside", + variant: "underlined", + size: "md", + class: { + label: [ + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_3.5px)]", + ], + }, + }, + { + labelPlacement: "inside", + variant: "underlined", + size: "lg", + class: { + label: [ + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(50%_+_theme(fontSize.small)/2_-_4px)]", + ], + }, + }, + // outside & size + { + labelPlacement: "outside", + size: "sm", + class: { + label: [ + "start-2", + "text-tiny", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.tiny)/2_+_16px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_8px)]", + }, + }, + { + labelPlacement: "outside", + size: "md", + class: { + label: [ + "start-3", + "end-auto", + "text-small", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_20px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_10px)]", + }, + }, + { + labelPlacement: "outside", + size: "lg", + class: { + label: [ + "start-3", + "end-auto", + "text-medium", + "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", + ], + base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", + }, + }, + // outside-left & size & hasHelper + { + labelPlacement: "outside-left", + size: "sm", + class: { + label: "group-data-[has-helper=true]:pt-2", + }, + }, + { + labelPlacement: "outside-left", + size: "md", + class: { + label: "group-data-[has-helper=true]:pt-3", + }, + }, + { + labelPlacement: "outside-left", + size: "lg", + class: { + label: "group-data-[has-helper=true]:pt-4", + }, + }, + // text truncate labelPlacement=[inside,outside] + { + labelPlacement: ["inside", "outside"], + class: { + label: ["pe-2", "max-w-full", "text-ellipsis", "overflow-hidden"], + }, + }, ], }); From 1f5a6f8ec7a68452913ebe2a30ea6ede5aee0db4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 21:21:32 +0800 Subject: [PATCH 092/131] refactor(number-input): rename stepper --- apps/docs/content/docs/components/number-input.mdx | 4 ++-- ...-vertical-stepper.tsx => number-input-stepper.tsx} | 11 ++++------- packages/components/number-input/src/number-input.tsx | 6 +++--- 3 files changed, 9 insertions(+), 12 deletions(-) rename packages/components/number-input/src/{number-input-vertical-stepper.tsx => number-input-stepper.tsx} (54%) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index eaf89f1718..8ec2ac69c4 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -167,7 +167,7 @@ This prop should be an object where each key is the field `name` and the value i - **input**: The input element. - **clearButton**: The clear button, it is at the end of the input. - **stepperButton**: The stepper button to increase or decrease the value. -- **verticalStepperWrapper**: The wrapper for the vertical stepper. +- **stepperWrapper**: The wrapper for the stepper. - **description**: The description of NumberInput. - **errorMessage**: The error message of NumberInput. @@ -431,7 +431,7 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla }, { attribute: "classNames", - type: "Partial>", + type: "Partial>", description: "Allows to set custom class names for the Input slots.", default: "-" } diff --git a/packages/components/number-input/src/number-input-vertical-stepper.tsx b/packages/components/number-input/src/number-input-stepper.tsx similarity index 54% rename from packages/components/number-input/src/number-input-vertical-stepper.tsx rename to packages/components/number-input/src/number-input-stepper.tsx index b035673638..b989821752 100644 --- a/packages/components/number-input/src/number-input-vertical-stepper.tsx +++ b/packages/components/number-input/src/number-input-stepper.tsx @@ -4,14 +4,11 @@ import type {ButtonProps} from "@heroui/button"; import {Button} from "@heroui/button"; import {ChevronUpIcon, ChevronDownIcon} from "@heroui/shared-icons"; -export interface NumberInputVerticalStepperProps extends Omit { +export interface NumberInputStepperProps extends Omit { direction: "up" | "down"; } -const NumberInputVerticalStepper = ({ - direction, - ...otherProps -}: NumberInputVerticalStepperProps) => { +const NumberInputStepper = ({direction, ...otherProps}: NumberInputStepperProps) => { return (
From 65f65c0c88c5d9e4511384b41f2232c73c36d2b3 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 21:31:06 +0800 Subject: [PATCH 093/131] fix(theme): isClearable --- .../core/theme/src/components/number-input.ts | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index b26e66d991..a83a3b4774 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -1,7 +1,7 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; +import {groupDataFocusVisibleClasses} from "../utils"; /** * NumberInput wrapper **Tailwind Variants** component @@ -48,23 +48,16 @@ const numberInput = tv({ "autofill:bg-transparent bg-clip-text", ], clearButton: [ - "p-2", - "-m-2", - "z-10", - "absolute", - "end-3", - "start-auto", - "pointer-events-none", - "appearance-none", - "outline-none", - "select-none", "opacity-0", - "hover:!opacity-100", - "cursor-pointer", - "active:!opacity-70", - "rounded-full", - // focus ring - ...dataFocusVisibleClasses, + "pointer-events-none", + "group-data-[invalid=true]:text-danger", + "peer-data-[filled=true]:opacity-100", // on mobile is always visible when there is a value + "peer-data-[filled=true]:pointer-events-auto", + "peer-data-[filled=true]:cursor-pointer", + "sm:peer-data-[filled=true]:opacity-0", // only visible on hover + "sm:peer-data-[filled=true]:pointer-events-none", + "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", + "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], stepperWrapper: ["flex", "flex-col", "ps-1"], @@ -194,11 +187,9 @@ const numberInput = tv({ isClearable: { true: { input: "peer pe-6 input-search-cancel-button-none", - clearButton: [ - "peer-data-[filled=true]:pointer-events-auto", - "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block", - "peer-data-[filled=true]:scale-100", - ], + }, + false: { + clearButton: "hidden", }, }, isDisabled: { From 0a92df1c3b0de549abd9c7eb7e8badc285dcf421 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 21:34:02 +0800 Subject: [PATCH 094/131] feat(docs): add label placements --- .../content/components/number-input/index.ts | 2 + .../number-input/label-placements.raw.jsx | 37 +++++++++++++++++++ .../number-input/label-placements.ts | 9 +++++ .../content/docs/components/number-input.mdx | 8 ++++ 4 files changed, 56 insertions(+) create mode 100644 apps/docs/content/components/number-input/label-placements.raw.jsx create mode 100644 apps/docs/content/components/number-input/label-placements.ts diff --git a/apps/docs/content/components/number-input/index.ts b/apps/docs/content/components/number-input/index.ts index 72bbcedba4..1ef95765c7 100644 --- a/apps/docs/content/components/number-input/index.ts +++ b/apps/docs/content/components/number-input/index.ts @@ -22,6 +22,7 @@ import realTimeValidation from "./real-time-validation"; import serverValidation from "./server-validation"; import customStyles from "./custom-styles"; import formatOptions from "./format-options"; +import labelPlacements from "./label-placements"; export const numberInputContent = { usage, @@ -48,4 +49,5 @@ export const numberInputContent = { serverValidation, customStyles, formatOptions, + labelPlacements, }; diff --git a/apps/docs/content/components/number-input/label-placements.raw.jsx b/apps/docs/content/components/number-input/label-placements.raw.jsx new file mode 100644 index 0000000000..b1eabd4564 --- /dev/null +++ b/apps/docs/content/components/number-input/label-placements.raw.jsx @@ -0,0 +1,37 @@ +import {NumberInput} from "@heroui/react"; + +export default function App() { + const placements = ["inside", "outside", "outside-left"]; + + return ( +
+
+

Without placeholder

+
+ {placements.map((placement) => ( + + ))} +
+
+
+

With placeholder

+
+ {placements.map((placement) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/docs/content/components/number-input/label-placements.ts b/apps/docs/content/components/number-input/label-placements.ts new file mode 100644 index 0000000000..cd2a65d352 --- /dev/null +++ b/apps/docs/content/components/number-input/label-placements.ts @@ -0,0 +1,9 @@ +import App from "./label-placements.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 8ec2ac69c4..1d9171858c 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -63,6 +63,14 @@ the end of the label and the input will be required. +### Label Placements + +You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`. + + + +> **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default. + ### Clear Button If you pass the `isClearable` property to NumberInput, it will have a clear button at the From 5fc9aa0307ba01c798cfe4e7067e61cadbc91516 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 21 Jan 2025 21:35:59 +0800 Subject: [PATCH 095/131] refactor(docs): update number-input content --- apps/docs/content/docs/components/number-input.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 1d9171858c..74b6b56e75 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -73,7 +73,7 @@ You can change the position of the label by setting the `labelPlacement` propert ### Clear Button -If you pass the `isClearable` property to NumberInput, it will have a clear button at the +If you pass the `isClearable` property to the input, it will have a clear button at the end of input, it will be visible when input has a value. @@ -92,13 +92,13 @@ You can use the `startContent` and `endContent` properties to add content to the ### With Label -You can add a label to NumberInput by passing the `label` property. +You can add a label to the input by passing the `label` property. ### With Description -You can add a description to NumberInput by passing the `description` property. +You can add a description to the input by passing the `description` property. @@ -134,11 +134,11 @@ You can combine the `isInvalid` and `errorMessage` properties to show an invalid ### Controlled -You can use the `value` and `onValueChange` properties to control NumberInput value. +You can use the `value` and `onValueChange` properties to control the input value. -> **Note**: NextUI `NumberInput` also supports native events like `onChange`, useful for form libraries +> **Note**: HeroUI `NumberInput` also supports native events like `onChange`, useful for form libraries > such as [Formik](https://formik.org/) and [React Hook Form](https://react-hook-form.com/). ### With Form @@ -160,7 +160,7 @@ If you want to display validation errors while the user is typing, you can contr #### Server Validation Client-side validation provides immediate feedback, but you should also validate data on the server to ensure accuracy and security. -NextUI allows you to display server-side validation errors by using the `validationErrors` prop in the `Form` component. +HeroUI allows you to display server-side validation errors by using the `validationErrors` prop in the `Form` component. This prop should be an object where each key is the field `name` and the value is the error message. From d69ba34f1cc22161d5854d4e2cbcec07aa1adffc Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:24:48 +0800 Subject: [PATCH 096/131] fix(docs): incorrect file --- apps/docs/content/docs/components/number-input.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 74b6b56e75..2f7d2500b2 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -67,7 +67,7 @@ the end of the label and the input will be required. You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`. - + > **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default. From 3370a70c3703330f45777696b4a5541b52eb4b17 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:25:38 +0800 Subject: [PATCH 097/131] feat(docs): add lablePlacement --- apps/docs/content/docs/components/number-input.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 2f7d2500b2..8ed7cef1bd 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -377,6 +377,12 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla description: "Element to be rendered in the right side of the input.", default: "-" }, + { + attribute: "labelPlacement", + type: "inside | outside | outside-left", + description: "The position of the label.", + default: "inside" + }, { attribute: "fullWidth", type: "boolean", From 520a0ab847c99af511379fa02986f37257707cf3 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:27:35 +0800 Subject: [PATCH 098/131] refactor(docs): remove labelPlacement & startContent --- .../components/number-input/start-end-content.raw.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/docs/content/components/number-input/start-end-content.raw.jsx b/apps/docs/content/components/number-input/start-end-content.raw.jsx index cb3c59624e..af07291726 100644 --- a/apps/docs/content/components/number-input/start-end-content.raw.jsx +++ b/apps/docs/content/components/number-input/start-end-content.raw.jsx @@ -31,13 +31,7 @@ export default function App() {
} label="Price" - labelPlacement="outside" placeholder="0.00" - startContent={ -
- $ -
- } />
From 106e4c4688994fd723ab061853c0b4fb41043f86 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:28:23 +0800 Subject: [PATCH 099/131] refactor(docs): remove helperText --- .../components/number-input/helper-text.raw.jsx | 15 --------------- .../components/number-input/helper-text.ts | 9 --------- 2 files changed, 24 deletions(-) delete mode 100644 apps/docs/content/components/number-input/helper-text.raw.jsx delete mode 100644 apps/docs/content/components/number-input/helper-text.ts diff --git a/apps/docs/content/components/number-input/helper-text.raw.jsx b/apps/docs/content/components/number-input/helper-text.raw.jsx deleted file mode 100644 index f6877f4103..0000000000 --- a/apps/docs/content/components/number-input/helper-text.raw.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import {NumberInput} from "@heroui/react"; - -export default function App() { - return ( - - ); -} diff --git a/apps/docs/content/components/number-input/helper-text.ts b/apps/docs/content/components/number-input/helper-text.ts deleted file mode 100644 index 121795e835..0000000000 --- a/apps/docs/content/components/number-input/helper-text.ts +++ /dev/null @@ -1,9 +0,0 @@ -import App from "./helper-text.raw.jsx?raw"; - -const react = { - "/App.jsx": App, -}; - -export default { - ...react, -}; From d079509485dde139241401ddee38ef81291f01be Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:45:04 +0800 Subject: [PATCH 100/131] refactor(docs): remove helperText --- apps/docs/content/components/number-input/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/docs/content/components/number-input/index.ts b/apps/docs/content/components/number-input/index.ts index 1ef95765c7..779bbb8385 100644 --- a/apps/docs/content/components/number-input/index.ts +++ b/apps/docs/content/components/number-input/index.ts @@ -7,7 +7,6 @@ import colors from "./colors"; import variants from "./variants"; import radius from "./radius"; import description from "./description"; -import helperText from "./helper-text"; import isWheelDisabled from "./is-wheel-disabled"; import label from "./label"; import minValue from "./min-value"; @@ -34,7 +33,6 @@ export const numberInputContent = { variants, radius, description, - helperText, label, isWheelDisabled, minValue, From ab5766e9abcb8c0c331f9497d5ecb5f17ea940a7 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 25 Jan 2025 23:46:03 +0800 Subject: [PATCH 101/131] refactor(docs): revise description --- apps/docs/content/components/number-input/description.raw.jsx | 2 +- apps/docs/content/components/number-input/max-value.raw.jsx | 2 +- apps/docs/content/components/number-input/min-value.raw.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/components/number-input/description.raw.jsx b/apps/docs/content/components/number-input/description.raw.jsx index 7df4ed5eae..6afae214e0 100644 --- a/apps/docs/content/components/number-input/description.raw.jsx +++ b/apps/docs/content/components/number-input/description.raw.jsx @@ -5,7 +5,7 @@ export default function App() { ); diff --git a/apps/docs/content/components/number-input/max-value.raw.jsx b/apps/docs/content/components/number-input/max-value.raw.jsx index b392ce629f..8c4f40b86e 100644 --- a/apps/docs/content/components/number-input/max-value.raw.jsx +++ b/apps/docs/content/components/number-input/max-value.raw.jsx @@ -5,7 +5,7 @@ export default function App() { diff --git a/apps/docs/content/components/number-input/min-value.raw.jsx b/apps/docs/content/components/number-input/min-value.raw.jsx index dac788d63e..f0a4911b8d 100644 --- a/apps/docs/content/components/number-input/min-value.raw.jsx +++ b/apps/docs/content/components/number-input/min-value.raw.jsx @@ -5,7 +5,7 @@ export default function App() { From 4866e09f694736ea337a1ccfeb9fdc2ffe5fcd28 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 26 Jan 2025 00:03:24 +0800 Subject: [PATCH 102/131] feat(number-input): add data-slot for stepper-wrapper --- packages/components/number-input/src/use-number-input.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index 3521bb3295..291dcb32dc 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -484,6 +484,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) { (props = {}) => { return { ...props, + "data-slot": "stepper-wrapper", className: slots.stepperWrapper({ class: clsx(classNames?.stepperWrapper, props?.className), }), From 87483c0e0a66366d39bc9d3c8aaedac0bc4f46be Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 26 Jan 2025 00:03:39 +0800 Subject: [PATCH 103/131] fix(number-input): test cases --- .../__tests__/number-input.test.tsx | 106 +++++++++++++----- 1 file changed, 79 insertions(+), 27 deletions(-) diff --git a/packages/components/number-input/__tests__/number-input.test.tsx b/packages/components/number-input/__tests__/number-input.test.tsx index 2b2e76be79..55cb03184b 100644 --- a/packages/components/number-input/__tests__/number-input.test.tsx +++ b/packages/components/number-input/__tests__/number-input.test.tsx @@ -6,9 +6,15 @@ import {Form} from "@heroui/form"; import {NumberInput} from "../src"; -describe("Input", () => { +describe("NumberInput", () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + it("should render correctly", () => { - const wrapper = render(); + const wrapper = render(); expect(() => wrapper.unmount()).not.toThrow(); }); @@ -16,31 +22,32 @@ describe("Input", () => { it("ref should be forwarded", () => { const ref = React.createRef(); - render(); + render(); + expect(ref.current).not.toBeNull(); }); it("should have aria-invalid when invalid", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-invalid", "true"); }); it("should have aria-readonly when isReadOnly", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-readonly", "true"); }); it("should have disabled attribute when isDisabled", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("disabled"); }); it("should disable the clear button when isDisabled", () => { const {getByRole} = render( - , + , ); const clearButton = getByRole("button"); @@ -49,7 +56,7 @@ describe("Input", () => { }); it("should not allow clear button to be focusable", () => { - const {getByRole} = render(); + const {getByRole} = render(); const clearButton = getByRole("button"); @@ -58,7 +65,7 @@ describe("Input", () => { it("should have required attribute when isRequired with native validationBehavior", () => { const {container} = render( - , + , ); expect(container.querySelector("input")).toHaveAttribute("required"); @@ -67,7 +74,7 @@ describe("Input", () => { it("should have aria-required attribute when isRequired with aria validationBehavior", () => { const {container} = render( - , + , ); expect(container.querySelector("input")).not.toHaveAttribute("required"); @@ -75,23 +82,24 @@ describe("Input", () => { }); it("should have aria-describedby when description is provided", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); }); it("should have aria-describedby when errorMessage is provided", () => { const {container} = render( - , + , ); expect(container.querySelector("input")).toHaveAttribute("aria-describedby"); }); it("should have the same aria-labelledby as label id", () => { - const {container} = render(); + const {container} = render(); const labelId = container.querySelector("label")?.id; + const labelledBy = container.querySelector("input")?.getAttribute("aria-labelledby"); expect(labelledBy?.includes(labelId as string)).toBeTruthy(); @@ -100,18 +108,21 @@ describe("Input", () => { it("should call dom event handlers only once", () => { const onFocus = jest.fn(); - const {container} = render(); + const {container} = render(); + + act(() => { + container.querySelector("input")?.focus(); - container.querySelector("input")?.focus(); - container.querySelector("input")?.blur(); + container.querySelector("input")?.blur(); - expect(onFocus).toHaveBeenCalledTimes(1); + expect(onFocus).toHaveBeenCalledTimes(1); + }); }); it("ref should update the value", () => { const ref = React.createRef(); - const {container} = render(); + const {container} = render(); if (!ref.current) { throw new Error("ref is null"); @@ -120,9 +131,11 @@ describe("Input", () => { ref.current!.value = value; - container.querySelector("input")?.focus(); + act(() => { + container.querySelector("input")?.focus(); - expect(ref.current?.value)?.toBe(value); + expect(ref.current?.value)?.toBe(value); + }); }); it("should clear the value and onClear is triggered", async () => { @@ -181,6 +194,52 @@ describe("Input", () => { expect(onClear).toHaveBeenCalledTimes(0); }); + + it("should reset to max value if the value exceeds", async () => { + const {container} = render( + , + ); + + const input = container.querySelector("input") as HTMLInputElement; + + await user.click(input); + await user.keyboard("1024"); + await user.tab(); + + expect(input).toHaveValue("100"); + }); + + it("should reset to min value if the value subceed", async () => { + const {container} = render( + , + ); + + const input = container.querySelector("input") as HTMLInputElement; + + await user.click(input); + await user.keyboard("50"); + await user.tab(); + + expect(input).toHaveValue("100"); + }); + + it("should render stepper", async () => { + const {container} = render(); + + const stepperButton = container.querySelector("[data-slot='stepper-wrapper'] button")!; + + expect(stepperButton).not.toBeNull(); + }); + + it("should hide stepper", async () => { + const {container} = render( + , + ); + + const stepperButton = container.querySelector("[data-slot='stepper-wrapper'] button")!; + + expect(stepperButton).toBeNull(); + }); }); describe("NumberInput with React Hook Form", () => { @@ -426,9 +485,6 @@ describe("NumberInput with React Hook Form", () => { await user.tab(); await user.keyboard("1234"); - // TODO: fix this - // expect(input).not.toHaveAttribute("aria-describedby"); - // expect(input).not.toHaveAttribute("aria-invalid"); }); it("supports server validation", async () => { @@ -449,10 +505,6 @@ describe("NumberInput with React Hook Form", () => { await user.tab(); await user.keyboard("1234"); await user.tab(); - - // TODO: fix this - // expect(input).not.toHaveAttribute("aria-describedby"); - // expect(input).not.toHaveAttribute("aria-invalid"); }); }); }); From 34c6a606d831bfc247e6eba2032b4e5830f36c02 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 27 Jan 2025 22:15:34 +0800 Subject: [PATCH 104/131] fix(docs): unexpected change --- apps/docs/content/docs/guide/nextui-to-heroui.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/docs/guide/nextui-to-heroui.mdx b/apps/docs/content/docs/guide/nextui-to-heroui.mdx index 6d92426890..da5c88e8d6 100644 --- a/apps/docs/content/docs/guide/nextui-to-heroui.mdx +++ b/apps/docs/content/docs/guide/nextui-to-heroui.mdx @@ -81,7 +81,7 @@ Update your `tailwind.config.js`: ```js // Before -const {nextui} = require("@heroui/react"); +const {nextui} = require("@nextui-org/react"); /** @type {import('tailwindcss').Config} */ module.exports = { @@ -109,7 +109,7 @@ Replace the NextUI provider with HeroUI's provider: ```jsx // Before -import {NextUIProvider} from "@heroui/react"; +import {NextUIProvider} from "@nextui-org/react"; // After import {HeroUIProvider} from "@heroui/react"; @@ -121,7 +121,7 @@ Update all component imports to use the new package name: ```jsx // Before -import {Button, Input} from "@heroui/react"; +import {Button, Input} from "@nextui-org/react"; // After import {Button, Input} from "@heroui/react"; @@ -136,7 +136,7 @@ If you're using individual packages, update each package name: import {Button} from "@nextui-org/button"; import {Input} from "@nextui-org/input"; import {nextui} from "@nextui-org/theme"; -import {NextUIProvider} from "@heroui/react"; +import {NextUIProvider} from "@nextui-org/react"; // After import {Button} from "@heroui/button"; From 53032f206c7a42646cd168767c3a503ea27c61ce Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 27 Jan 2025 22:20:21 +0800 Subject: [PATCH 105/131] refactor(number-input): update outdated info --- packages/components/number-input/README.md | 6 +++--- packages/components/number-input/package.json | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/number-input/README.md b/packages/components/number-input/README.md index 9a1ff8a470..3860d86969 100644 --- a/packages/components/number-input/README.md +++ b/packages/components/number-input/README.md @@ -2,7 +2,7 @@ NumberInput is a component that allows users to enter number. It can be used to get user inputs in forms, search fields, and more. -Please refer to the [documentation](https://nextui.org/docs/components/number-input) for more information. +Please refer to the [documentation](https://heroui.com/docs/components/number-input) for more information. ## Installation @@ -15,10 +15,10 @@ npm i @heroui/number-input ## Contribution Yes please! See the -[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md) +[contributing guidelines](https://github.com/heroui-inc/heroui/blob/master/CONTRIBUTING.md) for details. ## License This project is licensed under the terms of the -[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE). +[MIT license](https://github.com/heroui-inc/heroui/blob/master/LICENSE). diff --git a/packages/components/number-input/package.json b/packages/components/number-input/package.json index b9f1d02559..948d696936 100644 --- a/packages/components/number-input/package.json +++ b/packages/components/number-input/package.json @@ -20,11 +20,11 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/frontio-ai/heroui.git", - "directory": "packages/components/input" + "url": "git+https://github.com/heroui-inc/heroui.git", + "directory": "packages/components/number-input" }, "bugs": { - "url": "https://github.com/frontio-ai/heroui/issues" + "url": "https://github.com/heroui-inc/heroui/issues" }, "scripts": { "build": "tsup src --dts", @@ -38,8 +38,8 @@ "peerDependencies": { "react": ">=18 || >=19.0.0-rc.0", "react-dom": ">=18 || >=19.0.0-rc.0", - "@heroui/theme": ">=2.4.5", - "@heroui/system": ">=2.4.5" + "@heroui/theme": ">=2.4.7", + "@heroui/system": ">=2.4.8" }, "dependencies": { "@heroui/form": "workspace:*", From 73e0af47ce44eacd85d2896960033ec6be3df3b2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 28 Jan 2025 00:14:31 +0800 Subject: [PATCH 106/131] fix(docs): coderabbitai comments --- .../components/number-input/controlled.raw.jsx | 1 + .../components/number-input/disabled.raw.jsx | 1 + .../components/number-input/hide-stepper.raw.jsx | 1 + .../number-input/is-wheel-disabled.raw.jsx | 1 + .../components/number-input/max-value.raw.jsx | 3 ++- .../components/number-input/min-value.raw.jsx | 3 ++- .../content/components/number-input/radius.raw.jsx | 2 ++ .../components/number-input/readonly.raw.jsx | 1 + .../number-input/start-end-content.raw.jsx | 14 +++++++++++--- 9 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/docs/content/components/number-input/controlled.raw.jsx b/apps/docs/content/components/number-input/controlled.raw.jsx index cca5915235..1f11a3cc9e 100644 --- a/apps/docs/content/components/number-input/controlled.raw.jsx +++ b/apps/docs/content/components/number-input/controlled.raw.jsx @@ -1,3 +1,4 @@ +import React from "react"; import {NumberInput} from "@heroui/react"; export default function App() { diff --git a/apps/docs/content/components/number-input/disabled.raw.jsx b/apps/docs/content/components/number-input/disabled.raw.jsx index d980fe8874..2eaac6efe5 100644 --- a/apps/docs/content/components/number-input/disabled.raw.jsx +++ b/apps/docs/content/components/number-input/disabled.raw.jsx @@ -4,6 +4,7 @@ export default function App() { return ( ); diff --git a/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx b/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx index ad17d039fa..3cd7a7b3ae 100644 --- a/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx +++ b/apps/docs/content/components/number-input/is-wheel-disabled.raw.jsx @@ -6,6 +6,7 @@ export default function App() { isWheelDisabled className="max-w-xs" defaultValue={1024} + label="Amount" placeholder="Enter the amount" /> ); diff --git a/apps/docs/content/components/number-input/max-value.raw.jsx b/apps/docs/content/components/number-input/max-value.raw.jsx index 8c4f40b86e..6ff15aba48 100644 --- a/apps/docs/content/components/number-input/max-value.raw.jsx +++ b/apps/docs/content/components/number-input/max-value.raw.jsx @@ -4,8 +4,9 @@ export default function App() { return ( diff --git a/apps/docs/content/components/number-input/min-value.raw.jsx b/apps/docs/content/components/number-input/min-value.raw.jsx index f0a4911b8d..fd716da2c3 100644 --- a/apps/docs/content/components/number-input/min-value.raw.jsx +++ b/apps/docs/content/components/number-input/min-value.raw.jsx @@ -4,8 +4,9 @@ export default function App() { return ( diff --git a/apps/docs/content/components/number-input/radius.raw.jsx b/apps/docs/content/components/number-input/radius.raw.jsx index f734457198..0765103af4 100644 --- a/apps/docs/content/components/number-input/radius.raw.jsx +++ b/apps/docs/content/components/number-input/radius.raw.jsx @@ -8,8 +8,10 @@ export default function App() { {radius.map((r) => ( diff --git a/apps/docs/content/components/number-input/readonly.raw.jsx b/apps/docs/content/components/number-input/readonly.raw.jsx index 42308e5c98..07d9f9ad15 100644 --- a/apps/docs/content/components/number-input/readonly.raw.jsx +++ b/apps/docs/content/components/number-input/readonly.raw.jsx @@ -4,6 +4,7 @@ export default function App() { return (
} From 9198bdea6f735408273cbfa039a5dc25ee38eabb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 28 Jan 2025 00:14:43 +0800 Subject: [PATCH 107/131] refactor: remove validationState --- apps/docs/content/docs/components/number-input.mdx | 6 ------ packages/components/number-input/src/use-number-input.ts | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 8ed7cef1bd..a4a9434529 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -317,12 +317,6 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla description: "Validate input values when committing (e.g. on blur), returning error messages for invalid values.", default: "-" }, - { - attribute: "validationState", - type: "valid | invalid", - description: "Whether the inputs should display its \"valid\" or \"invalid\" visual styling. (Deprecated) use isInvalid instead.", - default: "-" - }, { attribute: "validationBehavior", type: "native | aria", diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index 291dcb32dc..ad8e964664 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -109,7 +109,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { endContent, onClear, onChange, - validationState, validationBehavior = formValidationBehavior ?? globalContext?.validationBehavior ?? "aria", innerWrapperRef: innerWrapperRefProp, onValueChange, @@ -147,7 +146,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) { decrementButtonProps, descriptionProps, errorMessageProps, - isInvalid: isAriaInvalid, + isInvalid, validationErrors, validationDetails, } = useAriaNumberInput(originalProps, state, domRef); @@ -198,8 +197,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { onPress: handleClear, }); - const isInvalid = validationState === "invalid" || isAriaInvalid; - const labelPlacement = useMemo(() => { if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) { return "outside"; From 4db884b04c0a479d47bb5f589053819782911f44 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 28 Jan 2025 00:24:33 +0800 Subject: [PATCH 108/131] fix(docs): typo --- apps/docs/content/docs/components/number-input.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index a4a9434529..c3af82de81 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -422,7 +422,7 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla { attribute: "decrementAriaLabel", type: "string", - description: "A custom aria-label for the increment button. If not provided, the localized string `Decrement` is used.", + description: "A custom aria-label for the decrement button. If not provided, the localized string `Decrement` is used.", default: "-" }, { From 0858fd8f86bba90189f8c63ffa8f3412cbbb104c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 28 Jan 2025 21:02:20 +0800 Subject: [PATCH 109/131] chore(deps): remove unnecessary dep --- apps/docs/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index d9582f0e0e..5b5bee021e 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -35,7 +35,6 @@ "@heroui/use-clipboard": "workspace:*", "@heroui/use-infinite-scroll": "workspace:*", "@heroui/use-is-mobile": "workspace:*", - "@heroui/number-input": "workspace:*", "@iconify/icons-solar": "1.2.3", "@iconify/react": "5.0.2", "@internationalized/date": "3.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6869221b9a..0699bc6c10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,9 +295,6 @@ importers: '@heroui/listbox': specifier: workspace:* version: link:../../packages/components/listbox - '@heroui/number-input': - specifier: workspace:* - version: link:../../packages/components/number-input '@heroui/react': specifier: workspace:* version: link:../../packages/core/react From a9df502455bb9da07e957aff1b6fea112cbdea88 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 30 Jan 2025 20:59:45 +0800 Subject: [PATCH 110/131] chore(deps): bump RA versions --- packages/components/number-input/package.json | 18 +- pnpm-lock.yaml | 162 ++++-------------- 2 files changed, 40 insertions(+), 140 deletions(-) diff --git a/packages/components/number-input/package.json b/packages/components/number-input/package.json index 948d696936..6ea15575ee 100644 --- a/packages/components/number-input/package.json +++ b/packages/components/number-input/package.json @@ -48,16 +48,16 @@ "@heroui/shared-icons": "workspace:*", "@heroui/shared-utils": "workspace:*", "@heroui/use-safe-layout-effect": "workspace:*", - "@react-aria/focus": "3.19.0", - "@react-aria/i18n": "3.12.4", - "@react-aria/interactions": "3.22.5", - "@react-aria/numberfield": "3.11.9", - "@react-aria/utils": "3.26.0", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/numberfield": "3.11.10", + "@react-aria/utils": "3.27.0", "@react-stately/utils": "3.10.5", - "@react-stately/numberfield": "3.9.8", - "@react-types/shared": "3.26.0", - "@react-types/numberfield": "3.8.7", - "@react-types/button": "3.10.1" + "@react-stately/numberfield": "3.9.9", + "@react-types/shared": "3.27.0", + "@react-types/numberfield": "3.8.8", + "@react-types/button": "3.10.2" }, "devDependencies": { "@heroui/system": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6515a4da09..34916990dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2232,35 +2232,35 @@ importers: specifier: workspace:* version: link:../../hooks/use-safe-layout-effect '@react-aria/focus': - specifier: 3.19.0 - version: 3.19.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + specifier: 3.19.1 + version: 3.19.1(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/i18n': - specifier: 3.12.4 - version: 3.12.4(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + specifier: 3.12.5 + version: 3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/interactions': - specifier: 3.22.5 - version: 3.22.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + specifier: 3.23.0 + version: 3.23.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/numberfield': - specifier: 3.11.9 - version: 3.11.9(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + specifier: 3.11.10 + version: 3.11.10(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/utils': - specifier: 3.26.0 - version: 3.26.0(react@18.3.0) + specifier: 3.27.0 + version: 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-stately/numberfield': - specifier: 3.9.8 - version: 3.9.8(react@18.3.0) + specifier: 3.9.9 + version: 3.9.9(react@18.3.0) '@react-stately/utils': specifier: 3.10.5 version: 3.10.5(react@18.3.0) '@react-types/button': - specifier: 3.10.1 - version: 3.10.1(react@18.3.0) + specifier: 3.10.2 + version: 3.10.2(react@18.3.0) '@react-types/numberfield': - specifier: 3.8.7 - version: 3.8.7(react@18.3.0) + specifier: 3.8.8 + version: 3.8.8(react@18.3.0) '@react-types/shared': - specifier: 3.26.0 - version: 3.26.0(react@18.3.0) + specifier: 3.27.0 + version: 3.27.0(react@18.3.0) devDependencies: '@heroui/system': specifier: workspace:* @@ -6951,11 +6951,6 @@ packages: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/focus@3.19.0': - resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==} - peerDependencies: - react: 18.3.0 - '@react-aria/focus@3.19.1': resolution: {integrity: sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==} peerDependencies: @@ -6974,22 +6969,12 @@ packages: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/i18n@3.12.4': - resolution: {integrity: sha512-j9+UL3q0Ls8MhXV9gtnKlyozq4aM95YywXqnmJtzT1rYeBx7w28hooqrWkCYLfqr4OIryv1KUnPiCSLwC2OC7w==} - peerDependencies: - react: 18.3.0 - '@react-aria/i18n@3.12.5': resolution: {integrity: sha512-ooeop2pTG94PuaHoN2OTk2hpkqVuoqgEYxRvnc1t7DVAtsskfhS/gVOTqyWGsxvwAvRi7m/CnDu6FYdeQ/bK5w==} peerDependencies: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/interactions@3.22.5': - resolution: {integrity: sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==} - peerDependencies: - react: 18.3.0 - '@react-aria/interactions@3.23.0': resolution: {integrity: sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==} peerDependencies: @@ -7023,8 +7008,8 @@ packages: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/numberfield@3.11.9': - resolution: {integrity: sha512-3tiGPx2y4zyOV7PmdBASes99ZZsFTZAJTnU45Z+p1CW4131lw7y2ZhbojBl7U6DaXAJvi1z6zY6cq2UE9w5a0Q==} + '@react-aria/numberfield@3.11.10': + resolution: {integrity: sha512-bYbTfO9NbAKMFOfEGGs+lvlxk0I9L0lU3WD2PFQZWdaoBz9TCkL+vK0fJk1zsuKaVjeGsmHP9VesBPRmaP0MiA==} peerDependencies: react: 18.3.0 react-dom: 18.3.0 @@ -7059,12 +7044,6 @@ packages: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/spinbutton@3.6.10': - resolution: {integrity: sha512-nhYEYk7xUNOZDaqiQ5w/nHH9ouqjJbabTWXH+KK7UR1oVGfo4z1wG94l8KWF3Z6SGGnBxzLJyTBguZ4g9aYTSg==} - peerDependencies: - react: 18.3.0 - react-dom: 18.3.0 - '@react-aria/spinbutton@3.6.11': resolution: {integrity: sha512-RM+gYS9tf9Wb+GegV18n4ArK3NBKgcsak7Nx1CkEgX9BjJ0yayWUHdfEjRRvxGXl+1z1n84cJVkZ6FUlWOWEZA==} peerDependencies: @@ -7118,11 +7097,6 @@ packages: react: 18.3.0 react-dom: 18.3.0 - '@react-aria/utils@3.26.0': - resolution: {integrity: sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==} - peerDependencies: - react: 18.3.0 - '@react-aria/utils@3.27.0': resolution: {integrity: sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==} peerDependencies: @@ -7212,8 +7186,8 @@ packages: peerDependencies: react: 18.3.0 - '@react-stately/numberfield@3.9.8': - resolution: {integrity: sha512-J6qGILxDNEtu7yvd3/y+FpbrxEaAeIODwlrFo6z1kvuDlLAm/KszXAc75yoDi0OtakFTCMP6/HR5VnHaQdMJ3w==} + '@react-stately/numberfield@3.9.9': + resolution: {integrity: sha512-hZsLiGGHTHmffjFymbH1qVmA633rU2GNjMFQTuSsN4lqqaP8fgxngd5pPCoTCUFEkUgWjdHenw+ZFByw8lIE+g==} peerDependencies: react: 18.3.0 @@ -7288,11 +7262,6 @@ packages: peerDependencies: react: 18.3.0 - '@react-types/button@3.10.1': - resolution: {integrity: sha512-XTtap8o04+4QjPNAshFWOOAusUTxQlBjU2ai0BTVLShQEjHhRVDBIWsI2B2FKJ4KXT6AZ25llaxhNrreWGonmA==} - peerDependencies: - react: 18.3.0 - '@react-types/button@3.10.2': resolution: {integrity: sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg==} peerDependencies: @@ -7348,8 +7317,8 @@ packages: peerDependencies: react: 18.3.0 - '@react-types/numberfield@3.8.7': - resolution: {integrity: sha512-KccMPi39cLoVkB2T0V7HW6nsxQVAwt89WWCltPZJVGzsebv/k0xTQlPVAgrUake4kDLoE687e3Fr/Oe3+1bDhw==} + '@react-types/numberfield@3.8.8': + resolution: {integrity: sha512-825JPppxDaWh0Zxb0Q+wSslgRQYOtQPCAuhszPuWEy6d2F/M+hLR+qQqvQm9+LfMbdwiTg6QK5wxdWFCp2t7jw==} peerDependencies: react: 18.3.0 @@ -7373,11 +7342,6 @@ packages: peerDependencies: react: 18.3.0 - '@react-types/shared@3.26.0': - resolution: {integrity: sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==} - peerDependencies: - react: 18.3.0 - '@react-types/shared@3.27.0': resolution: {integrity: sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==} peerDependencies: @@ -19145,17 +19109,6 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/focus@3.19.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': - dependencies: - '@react-aria/interactions': 3.23.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-aria/utils': 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-types/shared': 3.27.0(react@18.3.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 18.3.0 - transitivePeerDependencies: - - react-dom - '@react-aria/focus@3.19.1(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@react-aria/interactions': 3.23.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) @@ -19194,20 +19147,6 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/i18n@3.12.4(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': - dependencies: - '@internationalized/date': 3.7.0 - '@internationalized/message': 3.1.6 - '@internationalized/number': 3.6.0 - '@internationalized/string': 3.2.5 - '@react-aria/ssr': 3.9.7(react@18.3.0) - '@react-aria/utils': 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-types/shared': 3.27.0(react@18.3.0) - '@swc/helpers': 0.5.15 - react: 18.3.0 - transitivePeerDependencies: - - react-dom - '@react-aria/i18n@3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@internationalized/date': 3.7.0 @@ -19221,16 +19160,6 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/interactions@3.22.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': - dependencies: - '@react-aria/ssr': 3.9.7(react@18.3.0) - '@react-aria/utils': 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-types/shared': 3.27.0(react@18.3.0) - '@swc/helpers': 0.5.15 - react: 18.3.0 - transitivePeerDependencies: - - react-dom - '@react-aria/interactions@3.23.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.3.0) @@ -19296,17 +19225,17 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/numberfield@3.11.9(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': + '@react-aria/numberfield@3.11.10(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@react-aria/i18n': 3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/interactions': 3.23.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-aria/spinbutton': 3.6.10(react-dom@18.3.0(react@18.3.0))(react@18.3.0) + '@react-aria/spinbutton': 3.6.11(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/textfield': 3.16.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-aria/utils': 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) '@react-stately/form': 3.1.1(react@18.3.0) - '@react-stately/numberfield': 3.9.8(react@18.3.0) + '@react-stately/numberfield': 3.9.9(react@18.3.0) '@react-types/button': 3.10.2(react@18.3.0) - '@react-types/numberfield': 3.8.7(react@18.3.0) + '@react-types/numberfield': 3.8.8(react@18.3.0) '@react-types/shared': 3.27.0(react@18.3.0) '@swc/helpers': 0.5.15 react: 18.3.0 @@ -19380,17 +19309,6 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/spinbutton@3.6.10(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': - dependencies: - '@react-aria/i18n': 3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-aria/live-announcer': 3.4.1 - '@react-aria/utils': 3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0) - '@react-types/button': 3.10.2(react@18.3.0) - '@react-types/shared': 3.27.0(react@18.3.0) - '@swc/helpers': 0.5.15 - react: 18.3.0 - react-dom: 18.3.0(react@18.3.0) - '@react-aria/spinbutton@3.6.11(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@react-aria/i18n': 3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0) @@ -19499,15 +19417,6 @@ snapshots: react: 18.3.0 react-dom: 18.3.0(react@18.3.0) - '@react-aria/utils@3.26.0(react@18.3.0)': - dependencies: - '@react-aria/ssr': 3.9.7(react@18.3.0) - '@react-stately/utils': 3.10.5(react@18.3.0) - '@react-types/shared': 3.27.0(react@18.3.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 18.3.0 - '@react-aria/utils@3.27.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0)': dependencies: '@react-aria/ssr': 3.9.7(react@18.3.0) @@ -19670,12 +19579,12 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.3.0 - '@react-stately/numberfield@3.9.8(react@18.3.0)': + '@react-stately/numberfield@3.9.9(react@18.3.0)': dependencies: '@internationalized/number': 3.6.0 '@react-stately/form': 3.1.1(react@18.3.0) '@react-stately/utils': 3.10.5(react@18.3.0) - '@react-types/numberfield': 3.8.7(react@18.3.0) + '@react-types/numberfield': 3.8.8(react@18.3.0) '@swc/helpers': 0.5.15 react: 18.3.0 @@ -19790,11 +19699,6 @@ snapshots: '@react-types/shared': 3.27.0(react@18.3.0) react: 18.3.0 - '@react-types/button@3.10.1(react@18.3.0)': - dependencies: - '@react-types/shared': 3.27.0(react@18.3.0) - react: 18.3.0 - '@react-types/button@3.10.2(react@18.3.0)': dependencies: '@react-types/shared': 3.27.0(react@18.3.0) @@ -19856,7 +19760,7 @@ snapshots: '@react-types/shared': 3.27.0(react@18.3.0) react: 18.3.0 - '@react-types/numberfield@3.8.7(react@18.3.0)': + '@react-types/numberfield@3.8.8(react@18.3.0)': dependencies: '@react-types/shared': 3.27.0(react@18.3.0) react: 18.3.0 @@ -19881,10 +19785,6 @@ snapshots: '@react-types/shared': 3.27.0(react@18.3.0) react: 18.3.0 - '@react-types/shared@3.26.0(react@18.3.0)': - dependencies: - react: 18.3.0 - '@react-types/shared@3.27.0(react@18.3.0)': dependencies: react: 18.3.0 From fb635e5a436a25bed9df03bd2064e985344b95df Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 30 Jan 2025 21:00:01 +0800 Subject: [PATCH 111/131] chore(number-input): apply latest labelPlacement change --- .../number-input/src/use-number-input.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index ad8e964664..b3acfdac83 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -1,8 +1,9 @@ import type {NumberInputVariantProps, SlotsToClasses, NumberInputSlots} from "@heroui/theme"; import type {AriaNumberFieldProps} from "@react-types/numberfield"; import type {NumberFieldStateOptions} from "@react-stately/numberfield"; +import type {HTMLHeroUIProps, PropGetter} from "@heroui/system"; -import {HTMLHeroUIProps, mapPropsVariants, PropGetter, useProviderContext} from "@heroui/system"; +import {useLabelPlacement, mapPropsVariants, useProviderContext} from "@heroui/system"; import {useSafeLayoutEffect} from "@heroui/use-safe-layout-effect"; import {useFocusRing} from "@react-aria/focus"; import {numberInput} from "@heroui/theme"; @@ -197,13 +198,10 @@ export function useNumberInput(originalProps: UseNumberInputProps) { onPress: handleClear, }); - const labelPlacement = useMemo(() => { - if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) { - return "outside"; - } - - return originalProps.labelPlacement ?? "inside"; - }, [originalProps.labelPlacement, label]); + const labelPlacement = useLabelPlacement({ + labelPlacement: originalProps.labelPlacement, + label, + }); const errorMessage = typeof props.errorMessage === "function" From f09ed32331ced031e6f900aaf42a05c9ad3998f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=D3=84=D5=A1?= Date: Thu, 6 Feb 2025 12:34:12 +0800 Subject: [PATCH 112/131] refactor(number-input): update author --- packages/components/number-input/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/package.json b/packages/components/number-input/package.json index 6ea15575ee..fcf58c740a 100644 --- a/packages/components/number-input/package.json +++ b/packages/components/number-input/package.json @@ -7,7 +7,7 @@ "number", "numeric input" ], - "author": "WK Wong ", + "author": "HeroUI ", "homepage": "https://heroui.com", "license": "MIT", "main": "src/index.ts", From 40ca83bb869e3cb9b41df939ece5771e389e4f26 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Feb 2025 23:19:07 +0800 Subject: [PATCH 113/131] refactor(number-input): revise stepper wrapper alignment --- packages/core/theme/src/components/number-input.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index a83a3b4774..a49988c933 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -60,7 +60,7 @@ const numberInput = tv({ "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], - stepperWrapper: ["flex", "flex-col", "ps-1"], + stepperWrapper: ["flex", "flex-col", "ps-1", "h-full", "justify-center"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", errorMessage: "text-tiny text-danger", From cc3231f34f3d45ab76c7aa66b82a97847dbfa3a6 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 8 Feb 2025 19:20:21 +0800 Subject: [PATCH 114/131] refactor(number-input): stepper button styles --- .../number-input/src/use-number-input.ts | 2 -- .../core/theme/src/components/number-input.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index b3acfdac83..a952f7f2c6 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -498,7 +498,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { className: slots.stepperButton({ class: clsx(classNames?.stepperButton, props?.className), }), - // TODO: check press props & focus props ...mergeProps(incrementButtonProps, props), }; }, @@ -514,7 +513,6 @@ export function useNumberInput(originalProps: UseNumberInputProps) { className: slots.stepperButton({ class: clsx(classNames?.stepperButton, props?.className), }), - // TODO: check press props & focus props ...mergeProps(decrementButtonProps, props), }; }, diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index a49988c933..0037600c29 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -59,7 +59,20 @@ const numberInput = tv({ "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", ], - stepperButton: ["bg-transparent", "min-w-5", "w-5", "h-4", "rounded-none"], + stepperButton: [ + "bg-transparent", + "flex", + "justify-center", + "items-center", + "before:absolute", + "before:w-11", + "before:h-11", + "before:rounded-full", + "after:shadow-small", + "after:shadow-small", + "after:bg-background", + "data-[focused=true]:z-10", + ], stepperWrapper: ["flex", "flex-col", "ps-1", "h-full", "justify-center"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", description: "text-tiny text-foreground-400", From 13df8b534b646265bf1a271759957deaf394766a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 8 Feb 2025 21:39:45 +0800 Subject: [PATCH 115/131] chore(number-input): add disableRipple --- packages/components/number-input/src/number-input-stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/src/number-input-stepper.tsx b/packages/components/number-input/src/number-input-stepper.tsx index b989821752..10307e3357 100644 --- a/packages/components/number-input/src/number-input-stepper.tsx +++ b/packages/components/number-input/src/number-input-stepper.tsx @@ -10,7 +10,7 @@ export interface NumberInputStepperProps extends Omit { return ( - ); From 84a45db1fccb27f50439edd1135002c23a95904a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 8 Feb 2025 21:40:07 +0800 Subject: [PATCH 116/131] fix(theme): increase stepper button click area --- packages/core/theme/src/components/number-input.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index 0037600c29..fd2ffeecd0 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -65,13 +65,16 @@ const numberInput = tv({ "justify-center", "items-center", "before:absolute", - "before:w-11", - "before:h-11", + "before:w-8", // the max width that won't block clear button + "before:h-8", "before:rounded-full", "after:shadow-small", - "after:shadow-small", "after:bg-background", "data-[focused=true]:z-10", + "min-w-5", + "w-5", + "h-5", + "overflow-visible", ], stepperWrapper: ["flex", "flex-col", "ps-1", "h-full", "justify-center"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", From f6ff930affb62387212a6de50ca6c463016919dd Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 8 Feb 2025 22:17:41 +0800 Subject: [PATCH 117/131] fix(number-input): sync latest validationBehavior changes --- .../number-input/custom-validation.raw.jsx | 2 +- .../number-input/real-time-validation.raw.jsx | 2 +- .../number-input/server-validation.raw.jsx | 7 +----- .../content/docs/components/number-input.mdx | 2 +- .../__tests__/number-input.test.tsx | 25 +++++++++---------- .../number-input/src/use-number-input.ts | 2 +- 6 files changed, 17 insertions(+), 23 deletions(-) diff --git a/apps/docs/content/components/number-input/custom-validation.raw.jsx b/apps/docs/content/components/number-input/custom-validation.raw.jsx index 1ce394601b..712f337876 100644 --- a/apps/docs/content/components/number-input/custom-validation.raw.jsx +++ b/apps/docs/content/components/number-input/custom-validation.raw.jsx @@ -11,7 +11,7 @@ export default function App() { }; return ( -
+ + (
    diff --git a/apps/docs/content/components/number-input/server-validation.raw.jsx b/apps/docs/content/components/number-input/server-validation.raw.jsx index b8302ca876..55acf4ade4 100644 --- a/apps/docs/content/components/number-input/server-validation.raw.jsx +++ b/apps/docs/content/components/number-input/server-validation.raw.jsx @@ -16,12 +16,7 @@ export default function App() { }; return ( - + { describe("validationBehavior=native", () => { it("supports isRequired", async () => { const {getByTestId} = render( - - + + , ); @@ -360,13 +360,12 @@ describe("NumberInput with React Hook Form", () => { it("supports validate function", async () => { const {getByTestId} = render( -
    + (v === 1234 ? "Invalid amount" : null)} - validationBehavior="native" /> , ); @@ -408,13 +407,13 @@ describe("NumberInput with React Hook Form", () => { }; return ( -
    - + + @@ -464,7 +463,7 @@ describe("NumberInput with React Hook Form", () => { describe('validationBehavior="aria"', () => { it("supports validate function", async () => { const {getByTestId} = render( - + { it("supports server validation", async () => { const {getByTestId} = render( - + , ); diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index a952f7f2c6..29d572a124 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -110,7 +110,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) { endContent, onClear, onChange, - validationBehavior = formValidationBehavior ?? globalContext?.validationBehavior ?? "aria", + validationBehavior = formValidationBehavior ?? globalContext?.validationBehavior ?? "native", innerWrapperRef: innerWrapperRefProp, onValueChange, hideStepper, From 1decf883f73af8a8af0843c4f0f8b124fa4d5933 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 8 Feb 2025 22:48:06 +0800 Subject: [PATCH 118/131] fix(number-input): pass validationBehavior to useAriaNumberInput --- packages/components/number-input/src/use-number-input.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/src/use-number-input.ts b/packages/components/number-input/src/use-number-input.ts index 29d572a124..1de8811ed7 100644 --- a/packages/components/number-input/src/use-number-input.ts +++ b/packages/components/number-input/src/use-number-input.ts @@ -150,7 +150,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) { isInvalid, validationErrors, validationDetails, - } = useAriaNumberInput(originalProps, state, domRef); + } = useAriaNumberInput({...originalProps, validationBehavior}, state, domRef); const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; From 0173a36d9d4d11dfcba44a04038311256a6a4d43 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 9 Feb 2025 14:30:23 +0800 Subject: [PATCH 119/131] chore(docs): add import react --- .../content/components/number-input/custom-validation.raw.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/content/components/number-input/custom-validation.raw.jsx b/apps/docs/content/components/number-input/custom-validation.raw.jsx index 712f337876..8086f0387e 100644 --- a/apps/docs/content/components/number-input/custom-validation.raw.jsx +++ b/apps/docs/content/components/number-input/custom-validation.raw.jsx @@ -1,3 +1,4 @@ +import React from "react"; import {Button, Form, NumberInput} from "@heroui/react"; export default function App() { From 7f04255f4b645242c16c7917a833780a2ff1a071 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:23:28 +0800 Subject: [PATCH 120/131] chore(number-input): remove HorizontalStepper story --- .../number-input/stories/number-input.stories.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index aca5d148e8..53af5d94c4 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -244,17 +244,6 @@ export const WithWheelDisabled = { }, }; -export const HorizontalStepper = { - render: Template, - - args: { - ...defaultProps, - steps: "horizontal", - label: "Horizontal Stepper", - description: "Set `steps` to `horizontal` to show the stepper horizontally.", - }, -}; - export const WithFormatOptions = { render: Template, From 2021ce615b33dd58cb02734d43eec43e3cf30002 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:25:11 +0800 Subject: [PATCH 121/131] chore(number-input): enable ripple --- packages/components/number-input/src/number-input-stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/src/number-input-stepper.tsx b/packages/components/number-input/src/number-input-stepper.tsx index 10307e3357..b989821752 100644 --- a/packages/components/number-input/src/number-input-stepper.tsx +++ b/packages/components/number-input/src/number-input-stepper.tsx @@ -10,7 +10,7 @@ export interface NumberInputStepperProps extends Omit { return ( - ); From aebd00a7e9720eb89c864e77a19f6efbae2d65da Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:38:11 +0800 Subject: [PATCH 122/131] fix(number-input): remove number type --- .../components/number-input/stories/number-input.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index 53af5d94c4..92861ea95c 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -409,7 +409,6 @@ export const WithErrorMessageFunction = { ...defaultProps, min: "0", max: "100", - type: "number", isRequired: true, label: "Number", validationBehavior: "native", From aaf5fed32a0d6de578ec26b600882d6d8e819c12 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:39:25 +0800 Subject: [PATCH 123/131] refactor(theme): follow input clear button styles --- .../core/theme/src/components/number-input.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index fd2ffeecd0..ce6073b66b 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -1,7 +1,7 @@ import type {VariantProps} from "tailwind-variants"; import {tv} from "../utils/tv"; -import {groupDataFocusVisibleClasses} from "../utils"; +import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils"; /** * NumberInput wrapper **Tailwind Variants** component @@ -48,16 +48,22 @@ const numberInput = tv({ "autofill:bg-transparent bg-clip-text", ], clearButton: [ - "opacity-0", + "p-2", + "-m-2", + "z-10", + "end-3", + "start-auto", "pointer-events-none", - "group-data-[invalid=true]:text-danger", - "peer-data-[filled=true]:opacity-100", // on mobile is always visible when there is a value - "peer-data-[filled=true]:pointer-events-auto", - "peer-data-[filled=true]:cursor-pointer", - "sm:peer-data-[filled=true]:opacity-0", // only visible on hover - "sm:peer-data-[filled=true]:pointer-events-none", - "sm:group-data-[hover=true]:peer-data-[filled=true]:opacity-100", - "sm:group-data-[hover=true]:peer-data-[filled=true]:pointer-events-auto", + "appearance-none", + "outline-none", + "select-none", + "opacity-0", + "hover:!opacity-100", + "cursor-pointer", + "active:!opacity-70", + "rounded-full", + // focus ring + ...dataFocusVisibleClasses, ], stepperButton: [ "bg-transparent", @@ -203,9 +209,11 @@ const numberInput = tv({ isClearable: { true: { input: "peer pe-6 input-search-cancel-button-none", - }, - false: { - clearButton: "hidden", + clearButton: [ + "peer-data-[filled=true]:pointer-events-auto", + "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block", + "peer-data-[filled=true]:scale-100", + ], }, }, isDisabled: { From a7d93b788a6526e7de3c164bcc2f5523dc1ecd68 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:46:27 +0800 Subject: [PATCH 124/131] feat(theme): add color for stepperButton --- .../core/theme/src/components/number-input.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index ce6073b66b..a34b58a951 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -142,11 +142,21 @@ const numberInput = tv({ }, color: { default: {}, - primary: {}, - secondary: {}, - success: {}, - warning: {}, - danger: {}, + primary: { + stepperButton: "text-primary", + }, + secondary: { + stepperButton: "text-secondary", + }, + success: { + stepperButton: "text-success", + }, + warning: { + stepperButton: "text-warning", + }, + danger: { + stepperButton: "text-danger", + }, }, size: { sm: { From 9edef40bb0141fe6539de53dfe3d1d024d745e81 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:56:03 +0800 Subject: [PATCH 125/131] fix(theme): revise stepperButton size for outside & outside-left cases --- packages/core/theme/src/components/number-input.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index a34b58a951..f18fd7e334 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -197,12 +197,14 @@ const numberInput = tv({ labelPlacement: { outside: { mainWrapper: "flex flex-col", + stepperButton: "min-w-3 w-3 h-3", }, "outside-left": { base: "flex-row items-center flex-nowrap data-[has-helper=true]:items-start", inputWrapper: "flex-1", mainWrapper: "flex flex-col", label: "relative text-foreground pe-2 ps-2 pointer-events-auto", + stepperButton: "min-w-3 w-3 h-3", }, inside: { label: "cursor-text", @@ -807,6 +809,7 @@ const numberInput = tv({ "group-data-[filled-within=true]:-translate-y-[calc(100%_+_theme(fontSize.small)/2_+_24px)]", ], base: "data-[has-label=true]:mt-[calc(theme(fontSize.small)_+_12px)]", + stepperButton: "min-4 w-4 h-4", }, }, // outside-left & size & hasHelper @@ -829,6 +832,7 @@ const numberInput = tv({ size: "lg", class: { label: "group-data-[has-helper=true]:pt-4", + stepperButton: "min-4 w-4 h-4", }, }, // text truncate labelPlacement=[inside,outside] From 7cb3d6712bbd2b3d7e480fb9ba70970db05b4c8e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 21:59:12 +0800 Subject: [PATCH 126/131] fix(number-input): typo --- .../components/number-input/stories/number-input.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/stories/number-input.stories.tsx b/packages/components/number-input/stories/number-input.stories.tsx index 92861ea95c..5632dc87b9 100644 --- a/packages/components/number-input/stories/number-input.stories.tsx +++ b/packages/components/number-input/stories/number-input.stories.tsx @@ -350,7 +350,7 @@ export const EndContent = { ...defaultProps, variant: "bordered", label: "Price", - placeholde: "0.00", + placeholder: "0.00", endContent: (
    From b840ed37601230092cea46ac0817a66c378bafb0 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Feb 2025 22:10:30 +0800 Subject: [PATCH 127/131] chore(docs): update description for wheel --- apps/docs/content/docs/components/number-input.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx index 11b3039fe0..054fddf9b7 100644 --- a/apps/docs/content/docs/components/number-input.mdx +++ b/apps/docs/content/docs/components/number-input.mdx @@ -116,7 +116,7 @@ You can set the maximum value of the input by passing the `maxValue` property. ### With Wheel Disabled -You can disable changing the vaule with scroll in NumberInput by passing the `isWheelDisabled` property. +By default, you can increase or decrease the value with scroll wheel. You can disable changing the vaule with scroll in NumberInput by passing the `isWheelDisabled` property. From ccd1c80adef9ca05d80dbc0fb824513a9eb467d5 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 12 Feb 2025 21:29:59 +0800 Subject: [PATCH 128/131] chore(theme): change opacity when pressed --- packages/core/theme/src/components/number-input.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index f18fd7e334..dc8001e76c 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -81,6 +81,7 @@ const numberInput = tv({ "w-5", "h-5", "overflow-visible", + "data-[pressed=true]:opacity-70", ], stepperWrapper: ["flex", "flex-col", "ps-1", "h-full", "justify-center"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5", From 4405265d197ac70a69e8361518ca4650a8397f6b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 12 Feb 2025 21:30:18 +0800 Subject: [PATCH 129/131] chore(number-input): add disableRipple --- packages/components/number-input/src/number-input-stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/number-input/src/number-input-stepper.tsx b/packages/components/number-input/src/number-input-stepper.tsx index b989821752..10307e3357 100644 --- a/packages/components/number-input/src/number-input-stepper.tsx +++ b/packages/components/number-input/src/number-input-stepper.tsx @@ -10,7 +10,7 @@ export interface NumberInputStepperProps extends Omit { return ( - ); From a11f28e6015fbb1910636513bf91e4b70df8fb37 Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Fri, 14 Feb 2025 11:23:26 -0300 Subject: [PATCH 130/131] Update .changeset/witty-flies-reflect.md --- .changeset/witty-flies-reflect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/witty-flies-reflect.md b/.changeset/witty-flies-reflect.md index d73a10ed94..1050961106 100644 --- a/.changeset/witty-flies-reflect.md +++ b/.changeset/witty-flies-reflect.md @@ -2,7 +2,7 @@ "@heroui/number-input": patch "@heroui/shared-icons": patch "@heroui/theme": patch -"@heroui/react": minor +"@heroui/react": patch --- introduce NumberInput From 7e94eebfb514c2a4a54a6e6e525ddced272858e2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 14 Feb 2025 23:02:42 +0800 Subject: [PATCH 131/131] fix(theme): add hover opacity effect --- packages/core/theme/src/components/number-input.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/theme/src/components/number-input.ts b/packages/core/theme/src/components/number-input.ts index dc8001e76c..02d6126073 100644 --- a/packages/core/theme/src/components/number-input.ts +++ b/packages/core/theme/src/components/number-input.ts @@ -81,7 +81,9 @@ const numberInput = tv({ "w-5", "h-5", "overflow-visible", - "data-[pressed=true]:opacity-70", + "transition-opacity", + "data-[hover=true]:opacity-70", + "data-[pressed=true]:opacity-disabled", ], stepperWrapper: ["flex", "flex-col", "ps-1", "h-full", "justify-center"], helperWrapper: "hidden group-data-[has-helper=true]:flex py-2 relative flex-col gap-1.5",