diff --git a/.changeset/angry-boxes-wonder.md b/.changeset/angry-boxes-wonder.md new file mode 100644 index 0000000000..87fcae53b3 --- /dev/null +++ b/.changeset/angry-boxes-wonder.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/listbox": patch +--- + +fix listbox section items prop in html element (#4277) diff --git a/.changeset/chilled-files-serve.md b/.changeset/chilled-files-serve.md new file mode 100644 index 0000000000..4c351545da --- /dev/null +++ b/.changeset/chilled-files-serve.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/calendar": patch +--- + +remove unnecessary fragment in calendar (#4358, #4068) diff --git a/.changeset/cyan-donkeys-swim.md b/.changeset/cyan-donkeys-swim.md new file mode 100644 index 0000000000..fbd47f425a --- /dev/null +++ b/.changeset/cyan-donkeys-swim.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/input-otp": patch +"@nextui-org/shared-utils": patch +--- + +Fix virtual keyboard to display the keys based on allowedKeys(#4408) diff --git a/.changeset/late-doors-rhyme.md b/.changeset/late-doors-rhyme.md new file mode 100644 index 0000000000..1588c6a78a --- /dev/null +++ b/.changeset/late-doors-rhyme.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/theme": patch +--- + +Fixing the input in card to not become invisible on hover and making time-input and date-input consistent to other inputs. diff --git a/.changeset/lemon-cheetahs-grow.md b/.changeset/lemon-cheetahs-grow.md new file mode 100644 index 0000000000..d5c11df337 --- /dev/null +++ b/.changeset/lemon-cheetahs-grow.md @@ -0,0 +1,7 @@ +--- +"@nextui-org/use-aria-multiselect": patch +"@nextui-org/select": patch +--- + +fixed validationBehavior=native showing browser ui error for select component (#3913) +fixed select not committing error message when validationBehavior=native diff --git a/.changeset/nasty-dolls-tease.md b/.changeset/nasty-dolls-tease.md new file mode 100644 index 0000000000..52e9fe1b33 --- /dev/null +++ b/.changeset/nasty-dolls-tease.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/input-otp": patch +--- + +fixed isRequired not showing error when validationBehavior=native is not explicitly set diff --git a/.changeset/smart-oranges-peel.md b/.changeset/smart-oranges-peel.md new file mode 100644 index 0000000000..90aab7f5e4 --- /dev/null +++ b/.changeset/smart-oranges-peel.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/navbar": patch +--- + + +Resolving the issue preventing the navbar from opening(#4345) diff --git a/apps/docs/app/examples/modal/placement/page.tsx b/apps/docs/app/examples/modal/placement/page.tsx index a6f2c3ac9a..ac5f472908 100644 --- a/apps/docs/app/examples/modal/placement/page.tsx +++ b/apps/docs/app/examples/modal/placement/page.tsx @@ -58,7 +58,7 @@ export default function Page() {

- diff --git a/apps/docs/content/components/input/built-in-validation.raw.jsx b/apps/docs/content/components/input/built-in-validation.raw.jsx new file mode 100644 index 0000000000..70ca6412fa --- /dev/null +++ b/apps/docs/content/components/input/built-in-validation.raw.jsx @@ -0,0 +1,40 @@ +import {Button, Form, Input} 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 (validationDetails.typeMismatch) { + return "Please enter a valid email address"; + } + + return validationErrors; + }} + label="Email" + labelPlacement="outside" + name="email" + placeholder="Enter your email" + type="email" + /> + + {submitted && ( +
+ You submitted: {JSON.stringify(submitted)} +
+ )} +
+ ); +} diff --git a/apps/docs/content/components/input/built-in-validation.ts b/apps/docs/content/components/input/built-in-validation.ts new file mode 100644 index 0000000000..407bbc6a41 --- /dev/null +++ b/apps/docs/content/components/input/built-in-validation.ts @@ -0,0 +1,9 @@ +import App from "./built-in-validation.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/input/custom-validation.raw.jsx b/apps/docs/content/components/input/custom-validation.raw.jsx new file mode 100644 index 0000000000..78525f9f58 --- /dev/null +++ b/apps/docs/content/components/input/custom-validation.raw.jsx @@ -0,0 +1,40 @@ +import {Button, Form, Input} 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.length < 3) { + return "Username must be at least 3 characters long"; + } + + return value === "admin" ? "Nice try!" : null; + }} + /> + + {submitted && ( +
+ You submitted: {JSON.stringify(submitted)} +
+ )} +
+ ); +} diff --git a/apps/docs/content/components/input/custom-validation.ts b/apps/docs/content/components/input/custom-validation.ts new file mode 100644 index 0000000000..b0bf5b8588 --- /dev/null +++ b/apps/docs/content/components/input/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/input/index.ts b/apps/docs/content/components/input/index.ts index 73664fc946..93c65cf335 100644 --- a/apps/docs/content/components/input/index.ts +++ b/apps/docs/content/components/input/index.ts @@ -14,6 +14,10 @@ import startEndContent from "./start-end-content"; import errorMessage from "./error-message"; import regexValidation from "./regex-validation"; import controlled from "./controlled"; +import builtInValidation from "./built-in-validation"; +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"; @@ -34,6 +38,10 @@ export const inputContent = { errorMessage, regexValidation, controlled, + builtInValidation, + customValidation, + realTimeValidation, + serverValidation, customStyles, customImpl, }; diff --git a/apps/docs/content/components/input/real-time-validation.raw.jsx b/apps/docs/content/components/input/real-time-validation.raw.jsx new file mode 100644 index 0000000000..cfb5d915c5 --- /dev/null +++ b/apps/docs/content/components/input/real-time-validation.raw.jsx @@ -0,0 +1,53 @@ +import {Button, Form, Input} from "@nextui-org/react"; + +export default function App() { + const [submitted, setSubmitted] = React.useState(null); + const [password, setPassword] = React.useState(""); + const errors = []; + + const onSubmit = (e) => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.currentTarget)); + + setSubmitted(data); + }; + + if (password.length < 4) { + errors.push("Password must be 4 characters or more."); + } + if ((password.match(/[A-Z]/g) || []).length < 1) { + errors.push("Password must include at least 1 upper case letter"); + } + if ((password.match(/[^a-z0-9]/gi) || []).length < 1) { + errors.push("Password must include at least 1 symbol."); + } + + return ( +
+ ( + + )} + isInvalid={errors.length > 0} + label="Password" + labelPlacement="outside" + name="password" + placeholder="Enter your password" + value={password} + onValueChange={setPassword} + /> + + {submitted && ( +
+ You submitted: {JSON.stringify(submitted)} +
+ )} +
+ ); +} diff --git a/apps/docs/content/components/input/real-time-validation.ts b/apps/docs/content/components/input/real-time-validation.ts new file mode 100644 index 0000000000..6f8034a877 --- /dev/null +++ b/apps/docs/content/components/input/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/input/server-validation.raw.jsx b/apps/docs/content/components/input/server-validation.raw.jsx new file mode 100644 index 0000000000..3169541794 --- /dev/null +++ b/apps/docs/content/components/input/server-validation.raw.jsx @@ -0,0 +1,49 @@ +import {Button, Form, Input} 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/input/server-validation.ts b/apps/docs/content/components/input/server-validation.ts new file mode 100644 index 0000000000..84a2823b6a --- /dev/null +++ b/apps/docs/content/components/input/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/switch/custom-styles.raw.jsx b/apps/docs/content/components/switch/custom-styles.raw.jsx index db7a3568c9..ee01b9a7c3 100644 --- a/apps/docs/content/components/switch/custom-styles.raw.jsx +++ b/apps/docs/content/components/switch/custom-styles.raw.jsx @@ -14,10 +14,10 @@ export default function App() { "w-6 h-6 border-2 shadow-lg", "group-data-[hover=true]:border-primary", //selected - "group-data-[selected=true]:ml-6", + "group-data-[selected=true]:ms-6", // pressed "group-data-[pressed=true]:w-7", - "group-data-[selected]:group-data-[pressed]:ml-4", + "group-data-[selected]:group-data-[pressed]:ms-4", ), }} > diff --git a/apps/docs/content/docs/components/input.mdx b/apps/docs/content/docs/components/input.mdx index a2451d3010..bd2997ce14 100644 --- a/apps/docs/content/docs/components/input.mdx +++ b/apps/docs/content/docs/components/input.mdx @@ -125,6 +125,43 @@ You can use the `value` and `onValueChange` properties to control the input valu > **Note**: NextUI `Input` 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 + +`Input` 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. + +#### Built-in Validation + +`Input` supports the following [native HTML constraints](https://developer.mozilla.org/docs/Web/HTML/Constraint_validation): + +- `isRequired` indicates that a field must have a value before the form can be submitted. +- `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. + +When using native validation, error messages can be customized by passing a function to `errorMessage` and checking the [ValidityState](https://developer.mozilla.org/docs/Web/API/ValidityState) of `validationDetails`. + + + +#### 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. @@ -274,6 +311,30 @@ In case you need to customize the input even further, you can use the `useInput` 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: "minLength", + type: "number", + description: "The minimum length of the text input.", + default: "-" + }, + { + attribute: "maxLength", + type: "number", + description: "The maximum length of the text input.", + default: "-" + }, + { + attribute: "pattern", + type: "string", + description: "A regular expression that the input value is checked against.", + default: "-" + }, + { + attribute: "type", + type: "text | email | url | password | tel | search", + description: "The type of the input.", + default: "text" + }, { attribute: "startContent", type: "ReactNode", diff --git a/apps/docs/content/docs/components/select.mdx b/apps/docs/content/docs/components/select.mdx index 7d05d3190d..165008ad0d 100644 --- a/apps/docs/content/docs/components/select.mdx +++ b/apps/docs/content/docs/components/select.mdx @@ -376,7 +376,6 @@ the popover and listbox components. - Keyboard support for opening the listbox using the arrow keys, including automatically focusing the first or last item accordingly. - Typeahead to allow selecting options by typing text, even without opening the listbox. - Browser autofill integration via a hidden native `