mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat: introduce NumberInput (#4475)
* feat(number-field): init structure * feat(deps): add `@nextui-org/button` & `@react-types/button` * feat(theme): export number-field * feat(number-field): storybook init structure * feat(number-field): add NumberFieldHorizontalStepper * feat(number-field): add NumberFieldHorizontalStepper * feat(theme): init number field theme * feat(number-field): number-field draft * refactor(number-field): revise stepper icons * feat(shared-icons): add ChevronLeftIcon * feat(theme): stepperButton styles * feat(theme): number-field styles * fix(number-field): label layout * feat(number-field): vertical stepper wrapper * feat(number-field): use-number-field (wip) * feat(number-field): add data-direction * feat(theme): center the text if it is horizontal stepper * feat(number-field): add HorizontalStepper * feat(number-field): add HideStepper * chore(number-field): revise minValue & defaultValue * feat(docs): init number field structure * fix(theme): outside-left styles * refactor(theme): remove labelPlacement styles * refactor(number-field): remove labelContent logic * refactor(number-field): remove labelPlacement args * feat(number-field): helper text * feat(number-field): revise number field stories * feat(number-field): description * refactor(number-field): revise number field stories * feat(theme): numberFieldLabelClasses * fix(number-field): incorrect button props * fix(number-field): typing issue on stepper buttons * chore(number-field): add aria-label * refactor(number-field): merge props * fix(number-field): pass originalProps instead * chore(number-field): revise Required story args * feat(number-field): add WithStepValue & WithWheelDisabled & revise stories * chore(number-field): add label to Required * feat(docs): number-field doc page * fix(number-field): typing issue * fix(number-field): test cases * fix(number-field): user.keyboard & defaultValue * fix(number-field): should work with defaultValues * chore(number-field): add type: number * chore(number-field): remove hidden related code * fix(number-field): numeric value * chore(changeset): add changeset * feat(deps): add "@nextui-org/number-field" to docs * feat(react): export `@nextui-org/number-field` * feat(changeset): add @nextui-org/react * feat(docs): number-field examples * chore(number-field): use text instead * refactor(number-field): remove unnecessary filled-within * fix(number-field): test case * chore(number-field): remove aria-label for stepper buttons * feat(docs): add incrementAriaLabel & decrementAriaLabel to NumberField * chore(number-field): reorder WithFormatOptions * fix(deps): update number-field's peerDependencies & dependencies * feat(number-field): hidden input for holding numeric vaule * fix(docs): number field title * feat(docs): add format options to number field * chore(docs): revise number field content * chore(number-field): add type to useDOMRef * fix(number-field): clear button * fix(theme): clear button styles * refactor(theme): stepper button styles * chore(number-field): accept stepperButton class * fix(theme): helper wrapper padding * feat(deps): add `@react-aria/i18n` * fix(number-field): use locale from `@react-aria/i18n` * fix(deps): dependency order * fix(docs): incorrect command * chore(docs): remove type=number * chore(theme): add padding to stepper wrapper * fix(number-field): avoid resetting value * fix(number-field): storybook * chore(docs): remove custom impl * chore(docs): update docs code & content * chore(number-field): migrate to heroui * chore(number-field): migrate to heroui * chore(number-field): migrate to heroui * chore: rename to number input * fix(number-input): incorrect import * chore(docs): rename to number input * chore: change to number input * refactor(number-input): change label to amount * fix(docs): use heroui commands * chore(changeset): update package name * refactor(number-input): remove steps * refactor: remove helper text * feat(number-input): label placement * refactor(number-input): rename stepper * fix(theme): isClearable * feat(docs): add label placements * refactor(docs): update number-input content * fix(docs): incorrect file * feat(docs): add lablePlacement * refactor(docs): remove labelPlacement & startContent * refactor(docs): remove helperText * refactor(docs): remove helperText * refactor(docs): revise description * feat(number-input): add data-slot for stepper-wrapper * fix(number-input): test cases * fix(docs): unexpected change * refactor(number-input): update outdated info * fix(docs): coderabbitai comments * refactor: remove validationState * fix(docs): typo * chore(deps): remove unnecessary dep * chore(deps): bump RA versions * chore(number-input): apply latest labelPlacement change * refactor(number-input): update author * refactor(number-input): revise stepper wrapper alignment * refactor(number-input): stepper button styles * chore(number-input): add disableRipple * fix(theme): increase stepper button click area * fix(number-input): sync latest validationBehavior changes * fix(number-input): pass validationBehavior to useAriaNumberInput * chore(docs): add import react * chore(number-input): remove HorizontalStepper story * chore(number-input): enable ripple * fix(number-input): remove number type * refactor(theme): follow input clear button styles * feat(theme): add color for stepperButton * fix(theme): revise stepperButton size for outside & outside-left cases * fix(number-input): typo * chore(docs): update description for wheel * chore(theme): change opacity when pressed * chore(number-input): add disableRipple * Update .changeset/witty-flies-reflect.md * fix(theme): add hover opacity effect --------- Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
This commit is contained in:
parent
e99ddc45b9
commit
5f979617d5
8
.changeset/witty-flies-reflect.md
Normal file
8
.changeset/witty-flies-reflect.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
"@heroui/number-input": patch
|
||||
"@heroui/shared-icons": patch
|
||||
"@heroui/theme": patch
|
||||
"@heroui/react": patch
|
||||
---
|
||||
|
||||
introduce NumberInput
|
||||
@ -338,6 +338,13 @@
|
||||
"keywords": "navbar, navigation, top menu, website header",
|
||||
"path": "/docs/components/navbar.mdx"
|
||||
},
|
||||
{
|
||||
"key": "number-input",
|
||||
"title": "Number Input",
|
||||
"keywords": "input, numeric input, number input",
|
||||
"path": "/docs/components/number-input.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "pagination",
|
||||
"title": "Pagination",
|
||||
|
||||
@ -34,3 +34,4 @@ export * from "./table";
|
||||
export * from "./autocomplete";
|
||||
export * from "./alert";
|
||||
export * from "./drawer";
|
||||
export * from "./number-input";
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
isClearable
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
variant="bordered"
|
||||
// eslint-disable-next-line no-console
|
||||
onClear={() => console.log("number input cleared")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./clear-button.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
20
apps/docs/content/components/number-input/colors.raw.jsx
Normal file
20
apps/docs/content/components/number-input/colors.raw.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const colors = ["default", "primary", "secondary", "success", "warning", "danger"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-row flex-wrap gap-4">
|
||||
{colors.map((color) => (
|
||||
<NumberInput
|
||||
key={color}
|
||||
className="max-w-[220px]"
|
||||
color={color}
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/colors.ts
Normal file
9
apps/docs/content/components/number-input/colors.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./colors.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
18
apps/docs/content/components/number-input/controlled.raw.jsx
Normal file
18
apps/docs/content/components/number-input/controlled.raw.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState();
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 max-w-[240px]">
|
||||
<NumberInput
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
/>
|
||||
<p className="text-default-500 text-small">NumberInput value: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/controlled.ts
Normal file
9
apps/docs/content/components/number-input/controlled.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./controlled.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="w-[340px] h-[240px] px-8 rounded-2xl flex justify-center items-center bg-gradient-to-tr from-pink-500 to-yellow-500 text-white shadow-lg">
|
||||
<NumberInput
|
||||
isClearable
|
||||
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",
|
||||
"dark:hover:bg-default/70",
|
||||
"group-data-[focus=true]:bg-default-200/50",
|
||||
"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"
|
||||
label="Number of apples"
|
||||
minValue={1}
|
||||
placeholder="Enter a number..."
|
||||
radius="lg"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./custom-styles.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import {Button, Form, NumberInput} from "@heroui/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 (
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<NumberInput
|
||||
isRequired
|
||||
label="Amount"
|
||||
name="amount"
|
||||
placeholder="Enter a number"
|
||||
validate={(value) => {
|
||||
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;
|
||||
}}
|
||||
/>
|
||||
<Button color="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
{submitted && (
|
||||
<div className="text-small text-default-500">
|
||||
You submitted: <code>{JSON.stringify(submitted)}</code>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./custom-validation.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
description="Enter the amount"
|
||||
label="Amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/description.ts
Normal file
9
apps/docs/content/components/number-input/description.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./description.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
13
apps/docs/content/components/number-input/disabled.raw.jsx
Normal file
13
apps/docs/content/components/number-input/disabled.raw.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
isDisabled
|
||||
aria-label="Amount"
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/disabled.ts
Normal file
9
apps/docs/content/components/number-input/disabled.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./disabled.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
errorMessage="Please enter a valid number"
|
||||
isInvalid={true}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
variant="bordered"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./error-message.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,60 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={6}
|
||||
formatOptions={{
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}}
|
||||
label="With Currency"
|
||||
/>
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={6}
|
||||
formatOptions={{
|
||||
signDisplay: "exceptZero",
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 2,
|
||||
}}
|
||||
label="With Sign"
|
||||
/>
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={6}
|
||||
formatOptions={{
|
||||
style: "percent",
|
||||
}}
|
||||
label="With Percent"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={6}
|
||||
formatOptions={{
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
currencyDisplay: "code",
|
||||
currencySign: "accounting",
|
||||
}}
|
||||
label="With Currency Vaule"
|
||||
/>
|
||||
<NumberInput
|
||||
className="max-w-xs"
|
||||
defaultValue={6}
|
||||
formatOptions={{
|
||||
style: "unit",
|
||||
unit: "inch",
|
||||
unitDisplay: "long",
|
||||
}}
|
||||
label="With Unit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./format-options.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
hideStepper
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./hide-stepper.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
51
apps/docs/content/components/number-input/index.ts
Normal file
51
apps/docs/content/components/number-input/index.ts
Normal file
@ -0,0 +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 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 formatOptions from "./format-options";
|
||||
import labelPlacements from "./label-placements";
|
||||
|
||||
export const numberInputContent = {
|
||||
usage,
|
||||
disabled,
|
||||
readOnly,
|
||||
required,
|
||||
sizes,
|
||||
colors,
|
||||
variants,
|
||||
radius,
|
||||
description,
|
||||
label,
|
||||
isWheelDisabled,
|
||||
minValue,
|
||||
maxValue,
|
||||
clearButton,
|
||||
hideStepper,
|
||||
startEndContent,
|
||||
errorMessage,
|
||||
controlled,
|
||||
customValidation,
|
||||
realTimeValidation,
|
||||
serverValidation,
|
||||
customStyles,
|
||||
formatOptions,
|
||||
labelPlacements,
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
isWheelDisabled
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./is-wheel-disabled.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const placements = ["inside", "outside", "outside-left"];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-default-500 text-small">Without placeholder</h3>
|
||||
<div className="flex w-full flex-wrap items-end md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
{placements.map((placement) => (
|
||||
<NumberInput
|
||||
key={placement}
|
||||
description={placement}
|
||||
label="Amount"
|
||||
labelPlacement={placement}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-default-500 text-small">With placeholder</h3>
|
||||
<div className="flex w-full flex-wrap items-end md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
{placements.map((placement) => (
|
||||
<NumberInput
|
||||
key={placement}
|
||||
description={placement}
|
||||
label="Amount"
|
||||
labelPlacement={placement}
|
||||
placeholder="Enter a number"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./label-placements.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
5
apps/docs/content/components/number-input/label.raw.jsx
Normal file
5
apps/docs/content/components/number-input/label.raw.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return <NumberInput className="max-w-xs" defaultValue={1024} label="Amount" />;
|
||||
}
|
||||
9
apps/docs/content/components/number-input/label.ts
Normal file
9
apps/docs/content/components/number-input/label.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./label.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
14
apps/docs/content/components/number-input/max-value.raw.jsx
Normal file
14
apps/docs/content/components/number-input/max-value.raw.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
hideStepper
|
||||
aria-label="Amount"
|
||||
className="max-w-xs"
|
||||
description="The value should be less than or equal to 100"
|
||||
maxValue={100}
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/max-value.ts
Normal file
9
apps/docs/content/components/number-input/max-value.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./max-value.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
14
apps/docs/content/components/number-input/min-value.raw.jsx
Normal file
14
apps/docs/content/components/number-input/min-value.raw.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
hideStepper
|
||||
aria-label="Amount"
|
||||
className="max-w-xs"
|
||||
description="The value should be greater than or equal to 100"
|
||||
minValue={100}
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/min-value.ts
Normal file
9
apps/docs/content/components/number-input/min-value.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./min-value.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
21
apps/docs/content/components/number-input/radius.raw.jsx
Normal file
21
apps/docs/content/components/number-input/radius.raw.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const radius = ["full", "lg", "md", "sm", "none"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-row flex-wrap gap-4">
|
||||
{radius.map((r) => (
|
||||
<NumberInput
|
||||
key={r}
|
||||
aria-label={`${r} radius`}
|
||||
className="max-w-[220px]"
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
radius={r}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/radius.ts
Normal file
9
apps/docs/content/components/number-input/radius.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./radius.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
14
apps/docs/content/components/number-input/readonly.raw.jsx
Normal file
14
apps/docs/content/components/number-input/readonly.raw.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
isReadOnly
|
||||
aria-label="Amount"
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
placeholder="Enter the amount"
|
||||
variant="bordered"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/readonly.ts
Normal file
9
apps/docs/content/components/number-input/readonly.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./readonly.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
import {Button, Form, NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const [submitted, setSubmitted] = React.useState(null);
|
||||
const [amount, setAmount] = React.useState(null);
|
||||
const errors = [];
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.currentTarget));
|
||||
|
||||
setSubmitted(data);
|
||||
};
|
||||
|
||||
if (!amount) {
|
||||
errors.push("The value must not be empty");
|
||||
}
|
||||
|
||||
if (amount < 100) {
|
||||
errors.push("The value must be greater than 100");
|
||||
}
|
||||
|
||||
if (amount > 1000) {
|
||||
errors.push("The value must be less than 1000");
|
||||
}
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<NumberInput
|
||||
errorMessage={() => (
|
||||
<ul>
|
||||
{errors.map((error, i) => (
|
||||
<li key={i}>{error}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
isInvalid={errors.length > 0}
|
||||
label="Amount"
|
||||
name="amount"
|
||||
placeholder="Enter a number"
|
||||
value={amount}
|
||||
onValueChange={setAmount}
|
||||
/>
|
||||
<Button color="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
{submitted && (
|
||||
<div className="text-small text-default-500">
|
||||
You submitted: <code>{JSON.stringify(submitted)}</code>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./real-time-validation.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
13
apps/docs/content/components/number-input/required.raw.jsx
Normal file
13
apps/docs/content/components/number-input/required.raw.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NumberInput
|
||||
isRequired
|
||||
className="max-w-xs"
|
||||
defaultValue={1024}
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/required.ts
Normal file
9
apps/docs/content/components/number-input/required.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./required.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import {Button, Form, NumberInput} from "@heroui/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 (
|
||||
<Form className="w-full max-w-xs" validationErrors={errors} onSubmit={onSubmit}>
|
||||
<NumberInput
|
||||
isRequired
|
||||
isDisabled={isLoading}
|
||||
label="Amount"
|
||||
name="amount"
|
||||
placeholder="Enter a number"
|
||||
/>
|
||||
<Button color="primary" isLoading={isLoading} type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// Fake server used in this example.
|
||||
async function callServer(_) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return {
|
||||
errors: {
|
||||
amount: "Sorry, this amount is not valid.",
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./server-validation.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
15
apps/docs/content/components/number-input/sizes.raw.jsx
Normal file
15
apps/docs/content/components/number-input/sizes.raw.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const sizes = ["sm", "md", "lg"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<NumberInput label="Amount" placeholder="Enter the amount" size={size} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/sizes.ts
Normal file
9
apps/docs/content/components/number-input/sizes.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./sizes.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -0,0 +1,47 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<NumberInput
|
||||
label="Price"
|
||||
placeholder="0.00"
|
||||
startContent={
|
||||
<div className="pointer-events-none flex items-center">
|
||||
<span className="text-default-400 text-small">$</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
endContent={
|
||||
<div className="flex items-center">
|
||||
<label className="sr-only" htmlFor="currency">
|
||||
Currency
|
||||
</label>
|
||||
<select
|
||||
aria-label="Select currency"
|
||||
className="outline-none border-0 bg-transparent text-default-400 text-small"
|
||||
defaultValue="USD"
|
||||
id="currency"
|
||||
name="currency"
|
||||
>
|
||||
<option aria-label="US Dollar" value="USD">
|
||||
USD
|
||||
</option>
|
||||
<option aria-label="Argentine Peso" value="ARS">
|
||||
ARS
|
||||
</option>
|
||||
<option aria-label="Euro" value="EUR">
|
||||
EUR
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
label="Price"
|
||||
placeholder="0.00"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./start-end-content.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
5
apps/docs/content/components/number-input/usage.raw.jsx
Normal file
5
apps/docs/content/components/number-input/usage.raw.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return <NumberInput className="max-w-xs" placeholder="Enter the amount" />;
|
||||
}
|
||||
9
apps/docs/content/components/number-input/usage.ts
Normal file
9
apps/docs/content/components/number-input/usage.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./usage.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
16
apps/docs/content/components/number-input/variants.raw.jsx
Normal file
16
apps/docs/content/components/number-input/variants.raw.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import {NumberInput} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
const variants = ["flat", "bordered", "underlined", "faded"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<NumberInput label="Amount" variant={variant} />
|
||||
<NumberInput label="Amount" placeholder="Enter the amount" variant={variant} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/docs/content/components/number-input/variants.ts
Normal file
9
apps/docs/content/components/number-input/variants.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from "./variants.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
473
apps/docs/content/docs/components/number-input.mdx
Normal file
473
apps/docs/content/docs/components/number-input.mdx
Normal file
@ -0,0 +1,473 @@
|
||||
---
|
||||
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 {numberInputContent} from "@/content/components/number-input";
|
||||
|
||||
# Number Input
|
||||
|
||||
The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons
|
||||
|
||||
<ComponentLinks component="number-input" reactAriaHook="useNumberField" />
|
||||
|
||||
---
|
||||
|
||||
<CarbonAd/>
|
||||
|
||||
## Installation
|
||||
|
||||
<PackageManagers
|
||||
showGlobalInstallWarning
|
||||
commands={{
|
||||
cli: "npx heroui-cli@latest add number-input",
|
||||
npm: "npm install @heroui/number-input",
|
||||
yarn: "yarn add @heroui/number-input",
|
||||
pnpm: "pnpm add @heroui/number-input",
|
||||
bun: "bun add @heroui/number-input"
|
||||
}}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeDemo title="Usage" files={numberInputContent.usage} />
|
||||
|
||||
### Disabled
|
||||
|
||||
<CodeDemo title="Disabled" files={numberInputContent.disabled} />
|
||||
|
||||
### Read Only
|
||||
|
||||
<CodeDemo title="Read Only" files={numberInputContent.readOnly} />
|
||||
|
||||
### 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.
|
||||
|
||||
<CodeDemo title="Required" files={numberInputContent.required} />
|
||||
|
||||
### Sizes
|
||||
|
||||
<CodeDemo title="Sizes" files={numberInputContent.sizes} />
|
||||
|
||||
### Colors
|
||||
|
||||
<CodeDemo title="Colors" files={numberInputContent.colors} />
|
||||
|
||||
### Variants
|
||||
|
||||
<CodeDemo title="Variants" files={numberInputContent.variants} />
|
||||
|
||||
### Radius
|
||||
|
||||
<CodeDemo title="Radius" files={numberInputContent.radius} />
|
||||
|
||||
### Label Placements
|
||||
|
||||
You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
|
||||
|
||||
<CodeDemo title="Label Placements" files={numberInputContent.labelPlacements} />
|
||||
|
||||
> **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default.
|
||||
|
||||
### Clear Button
|
||||
|
||||
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.
|
||||
|
||||
<CodeDemo title="Clear Button" files={numberInputContent.clearButton} />
|
||||
|
||||
### Hide Stepper
|
||||
|
||||
You can hide the stepper buttons by passing the `hideStepper` property.
|
||||
|
||||
<CodeDemo title="Hide Stepper" files={numberInputContent.hideStepper} />
|
||||
|
||||
### Start & End Content
|
||||
|
||||
You can use the `startContent` and `endContent` properties to add content to the start and end of NumberInput.
|
||||
|
||||
<CodeDemo title="Start and End Content" files={numberInputContent.startEndContent} />
|
||||
|
||||
### With Label
|
||||
|
||||
You can add a label to the input by passing the `label` property.
|
||||
|
||||
<CodeDemo title="With Label" files={numberInputContent.label} />
|
||||
|
||||
### With Description
|
||||
|
||||
You can add a description to the input by passing the `description` property.
|
||||
|
||||
<CodeDemo title="With Description" files={numberInputContent.description} />
|
||||
|
||||
### With Min Value
|
||||
|
||||
You can set the minimum value of the input by passing the `minValue` property.
|
||||
|
||||
<CodeDemo title="With Min Value" files={numberInputContent.minValue} />
|
||||
|
||||
### With Max Value
|
||||
|
||||
You can set the maximum value of the input by passing the `maxValue` property.
|
||||
|
||||
<CodeDemo title="With Max Value" files={numberInputContent.maxValue} />
|
||||
|
||||
### With Wheel Disabled
|
||||
|
||||
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.
|
||||
|
||||
<CodeDemo title="With Wheel Disabled" files={numberInputContent.isWheelDisabled} />
|
||||
|
||||
### With Format Options
|
||||
|
||||
You can format the value of the input by passing the `formatOptions` property.
|
||||
|
||||
<CodeDemo title="With Format Options" files={numberInputContent.formatOptions} />
|
||||
|
||||
### 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`.
|
||||
|
||||
<CodeDemo title="With Error Message" files={numberInputContent.errorMessage} />
|
||||
|
||||
### Controlled
|
||||
|
||||
You can use the `value` and `onValueChange` properties to control the input value.
|
||||
|
||||
<CodeDemo title="Controlled" files={numberInputContent.controlled} />
|
||||
|
||||
> **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
|
||||
|
||||
`NumberInput` can be used with a `Form` component to leverage form state management. 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.
|
||||
|
||||
<CodeDemo title="Custom Validation" files={numberInputContent.customValidation} />
|
||||
|
||||
#### 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.
|
||||
|
||||
<CodeDemo title="Realtime Validation" files={numberInputContent.realTimeValidation} />
|
||||
|
||||
#### Server Validation
|
||||
|
||||
Client-side validation provides immediate feedback, but you should also validate data on the server to ensure accuracy and security.
|
||||
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.
|
||||
|
||||
<CodeDemo title="Server Validation" files={numberInputContent.serverValidation} />
|
||||
|
||||
## 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.
|
||||
- **stepperButton**: The stepper button to increase or decrease the value.
|
||||
- **stepperWrapper**: The wrapper for the stepper.
|
||||
- **description**: The description of NumberInput.
|
||||
- **errorMessage**: The error message of NumberInput.
|
||||
|
||||
### Custom Styles
|
||||
|
||||
You can customize the `NumberInput` component by passing custom Tailwind CSS classes to the component slots.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={numberInputContent.customStyles} />
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Data Attributes
|
||||
|
||||
`NumberInput` 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.
|
||||
- **data-filled**:
|
||||
When the input has content, placeholder, start content or the placeholder is shown.
|
||||
- **data-has-elements**:
|
||||
When the input has any element (label, helper text, description, error message).
|
||||
- **data-has-helper**:
|
||||
When the input has helper text.
|
||||
- **data-has-description**:
|
||||
When the input has a description.
|
||||
- **data-has-label**:
|
||||
When the input has a label.
|
||||
- **data-has-value**:
|
||||
When the input has a value (placeholder is not shown).
|
||||
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Built with a native `<input>` 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.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## API
|
||||
|
||||
### NumberInput Props
|
||||
|
||||
<APITable
|
||||
data={[
|
||||
{
|
||||
attribute: "children",
|
||||
type: "ReactNode",
|
||||
description: "The content of the input.",
|
||||
default: "-"
|
||||
},
|
||||
{
|
||||
attribute: "variant",
|
||||
type: "flat | bordered | faded | underlined",
|
||||
description: "The variant of the input.",
|
||||
default: "flat"
|
||||
},
|
||||
{
|
||||
attribute: "color",
|
||||
type: "default | primary | secondary | success | warning | danger",
|
||||
description: "The color of the input.",
|
||||
default: "default"
|
||||
},
|
||||
{
|
||||
attribute: "size",
|
||||
type: "sm | md | lg",
|
||||
description: "The size of the input.",
|
||||
default: "md"
|
||||
},
|
||||
{
|
||||
attribute: "radius",
|
||||
type: "none | sm | md | lg | full",
|
||||
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",
|
||||
description: "The current value of the input (controlled).",
|
||||
default: "-"
|
||||
},
|
||||
{
|
||||
attribute: "defaultValue",
|
||||
type: "string",
|
||||
description: "The default value of the input (uncontrolled).",
|
||||
default: "-"
|
||||
},
|
||||
{
|
||||
attribute: "placeholder",
|
||||
type: "string",
|
||||
description: "The placeholder of the input.",
|
||||
default: "-"
|
||||
},
|
||||
{
|
||||
attribute: "errorMessage",
|
||||
type: "ReactNode | ((v: ValidationResult) => 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: "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: "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",
|
||||
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: "labelPlacement",
|
||||
type: "inside | outside | outside-left",
|
||||
description: "The position of the label.",
|
||||
default: "inside"
|
||||
},
|
||||
{
|
||||
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: "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 decrement button. If not provided, the localized string `Decrement` is used.",
|
||||
default: "-"
|
||||
},
|
||||
{
|
||||
attribute: "baseRef",
|
||||
type: "RefObject<HTMLDivElement>",
|
||||
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<Record<'base' | 'label' | 'inputWrapper' | 'innerWrapper' | 'mainWrapper' | 'input' | 'clearButton' | 'stepperButton' | 'helperWrapper' | 'stepperWrapper' | 'description' | 'errorMessage', string>>",
|
||||
description: "Allows to set custom class names for the Input slots.",
|
||||
default: "-"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
### NumberInput Events
|
||||
|
||||
<APITable
|
||||
data={[
|
||||
{
|
||||
attribute: "onChange",
|
||||
type: "React.ChangeEvent<HTMLInputElement>",
|
||||
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: "-"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -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.
|
||||
|
||||
24
packages/components/number-input/README.md
Normal file
24
packages/components/number-input/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @heroui/number-input
|
||||
|
||||
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://heroui.com/docs/components/number-input) for more information.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @heroui/number-input
|
||||
# or
|
||||
npm i @heroui/number-input
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[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/heroui-inc/heroui/blob/master/LICENSE).
|
||||
510
packages/components/number-input/__tests__/number-input.test.tsx
Normal file
510
packages/components/number-input/__tests__/number-input.test.tsx
Normal file
@ -0,0 +1,510 @@
|
||||
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 "@heroui/form";
|
||||
|
||||
import {NumberInput} from "../src";
|
||||
|
||||
describe("NumberInput", () => {
|
||||
let user: UserEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<NumberInput label="test number input" />);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLInputElement>();
|
||||
|
||||
render(<NumberInput ref={ref} label="test number input" />);
|
||||
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should have aria-invalid when invalid", () => {
|
||||
const {container} = render(<NumberInput isInvalid={true} label="test number input" />);
|
||||
|
||||
expect(container.querySelector("input")).toHaveAttribute("aria-invalid", "true");
|
||||
});
|
||||
|
||||
it("should have aria-readonly when isReadOnly", () => {
|
||||
const {container} = render(<NumberInput isReadOnly label="test number input" />);
|
||||
|
||||
expect(container.querySelector("input")).toHaveAttribute("aria-readonly", "true");
|
||||
});
|
||||
|
||||
it("should have disabled attribute when isDisabled", () => {
|
||||
const {container} = render(<NumberInput isDisabled label="test number input" />);
|
||||
|
||||
expect(container.querySelector("input")).toHaveAttribute("disabled");
|
||||
});
|
||||
|
||||
it("should disable the clear button when isDisabled", () => {
|
||||
const {getByRole} = render(
|
||||
<NumberInput hideStepper isClearable isDisabled label="test number input" />,
|
||||
);
|
||||
|
||||
const clearButton = getByRole("button");
|
||||
|
||||
expect(clearButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should not allow clear button to be focusable", () => {
|
||||
const {getByRole} = render(<NumberInput hideStepper isClearable label="test number input" />);
|
||||
|
||||
const clearButton = getByRole("button");
|
||||
|
||||
expect(clearButton).toHaveAttribute("tabIndex", "-1");
|
||||
});
|
||||
|
||||
it("should have required attribute when isRequired with native validationBehavior", () => {
|
||||
const {container} = render(
|
||||
<NumberInput isRequired label="test number input" validationBehavior="native" />,
|
||||
);
|
||||
|
||||
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(
|
||||
<NumberInput isRequired label="test number input" validationBehavior="aria" />,
|
||||
);
|
||||
|
||||
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(<NumberInput description="description" label="test number input" />);
|
||||
|
||||
expect(container.querySelector("input")).toHaveAttribute("aria-describedby");
|
||||
});
|
||||
|
||||
it("should have aria-describedby when errorMessage is provided", () => {
|
||||
const {container} = render(
|
||||
<NumberInput isInvalid errorMessage="error text" label="test number input" />,
|
||||
);
|
||||
|
||||
expect(container.querySelector("input")).toHaveAttribute("aria-describedby");
|
||||
});
|
||||
|
||||
it("should have the same aria-labelledby as label id", () => {
|
||||
const {container} = render(<NumberInput label="test number input" />);
|
||||
|
||||
const labelId = container.querySelector("label")?.id;
|
||||
|
||||
const labelledBy = container.querySelector("input")?.getAttribute("aria-labelledby");
|
||||
|
||||
expect(labelledBy?.includes(labelId as string)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should call dom event handlers only once", () => {
|
||||
const onFocus = jest.fn();
|
||||
|
||||
const {container} = render(<NumberInput label="test number input" onFocus={onFocus} />);
|
||||
|
||||
act(() => {
|
||||
container.querySelector("input")?.focus();
|
||||
|
||||
container.querySelector("input")?.blur();
|
||||
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("ref should update the value", () => {
|
||||
const ref = React.createRef<HTMLInputElement>();
|
||||
|
||||
const {container} = render(<NumberInput ref={ref} label="test number input" />);
|
||||
|
||||
if (!ref.current) {
|
||||
throw new Error("ref is null");
|
||||
}
|
||||
const value = "1234";
|
||||
|
||||
ref.current!.value = value;
|
||||
|
||||
act(() => {
|
||||
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<HTMLInputElement>();
|
||||
|
||||
const {getByRole} = render(
|
||||
<NumberInput
|
||||
ref={ref}
|
||||
hideStepper
|
||||
isClearable
|
||||
defaultValue={1234}
|
||||
label="test number-input"
|
||||
onClear={onClear}
|
||||
/>,
|
||||
);
|
||||
|
||||
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 disable clear button when isReadOnly is true", async () => {
|
||||
const onClear = jest.fn();
|
||||
|
||||
const ref = React.createRef<HTMLInputElement>();
|
||||
|
||||
const {getByRole} = render(
|
||||
<NumberInput
|
||||
ref={ref}
|
||||
hideStepper
|
||||
isClearable
|
||||
isReadOnly
|
||||
defaultValue={1234}
|
||||
label="test number-input"
|
||||
onClear={onClear}
|
||||
/>,
|
||||
);
|
||||
|
||||
const clearButton = getByRole("button")!;
|
||||
|
||||
expect(clearButton).not.toBeNull();
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
await user.click(clearButton);
|
||||
|
||||
expect(onClear).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("should reset to max value if the value exceeds", async () => {
|
||||
const {container} = render(
|
||||
<NumberInput isInvalid={true} label="test number input" maxValue={100} />,
|
||||
);
|
||||
|
||||
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(
|
||||
<NumberInput isInvalid={true} label="test number input" minValue={100} />,
|
||||
);
|
||||
|
||||
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(<NumberInput isInvalid={true} label="test number input" />);
|
||||
|
||||
const stepperButton = container.querySelector("[data-slot='stepper-wrapper'] button")!;
|
||||
|
||||
expect(stepperButton).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should hide stepper", async () => {
|
||||
const {container} = render(
|
||||
<NumberInput hideStepper isInvalid={true} label="test number input" />,
|
||||
);
|
||||
|
||||
const stepperButton = container.querySelector("[data-slot='stepper-wrapper'] button")!;
|
||||
|
||||
expect(stepperButton).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("NumberInput 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: 1234,
|
||||
withoutDefaultValue: undefined,
|
||||
requiredField: undefined,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: {errors},
|
||||
} = result.current;
|
||||
|
||||
onSubmit = jest.fn();
|
||||
|
||||
render(
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<NumberInput isClearable label="With default value" {...register("withDefaultValue")} />
|
||||
<NumberInput
|
||||
data-testid="input-2"
|
||||
label="Without default value"
|
||||
{...register("withoutDefaultValue")}
|
||||
/>
|
||||
<NumberInput
|
||||
data-testid="input-3"
|
||||
label="Required"
|
||||
{...register("requiredField", {required: true})}
|
||||
/>
|
||||
{errors.requiredField && <span className="text-danger">This field is required</span>}
|
||||
<button type="submit">Submit</button>
|
||||
</form>,
|
||||
);
|
||||
|
||||
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("1234");
|
||||
expect(input2).not.toHaveValue();
|
||||
expect(input3).not.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: 123}});
|
||||
|
||||
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(
|
||||
<Form data-testid="form" validationBehavior="native">
|
||||
<NumberInput isRequired data-testid="input" label="Name" />
|
||||
</Form>,
|
||||
);
|
||||
|
||||
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("1234");
|
||||
|
||||
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(
|
||||
<Form data-testid="form" validationBehavior="native">
|
||||
<NumberInput
|
||||
data-testid="input"
|
||||
defaultValue={1234}
|
||||
label="Name"
|
||||
validate={(v) => (v === 1234 ? "Invalid amount" : null)}
|
||||
/>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
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 amount",
|
||||
);
|
||||
|
||||
await user.keyboard("4321");
|
||||
await user.tab();
|
||||
|
||||
act(() => {
|
||||
(getByTestId("form") as HTMLFormElement).checkValidity();
|
||||
});
|
||||
|
||||
expect(input.validity.valid).toBe(true);
|
||||
expect(input).not.toHaveAttribute("aria-describedby");
|
||||
});
|
||||
|
||||
it("supports server validation", async () => {
|
||||
function Test() {
|
||||
let [serverErrors, setServerErrors] = React.useState({});
|
||||
let onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setServerErrors({
|
||||
name: "Invalid amount.",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
data-testid="form"
|
||||
validationBehavior="native"
|
||||
validationErrors={serverErrors}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<NumberInput data-testid="input" label="Name" name="name" />
|
||||
<button data-testid="submit" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
const {getByTestId} = render(<Test />);
|
||||
|
||||
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 amount.",
|
||||
);
|
||||
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 amount.",
|
||||
);
|
||||
expect(input.validity.valid).toBe(false);
|
||||
|
||||
await user.keyboard("1234");
|
||||
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(
|
||||
<Form data-testid="form" validationBehavior="aria">
|
||||
<NumberInput
|
||||
data-testid="input"
|
||||
defaultValue={1234}
|
||||
label="Amount"
|
||||
validate={(v) => (v === 1234 ? "Invalid amount" : null)}
|
||||
/>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
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 amount",
|
||||
);
|
||||
expect(input.validity.valid).toBe(true);
|
||||
|
||||
await user.tab();
|
||||
await user.keyboard("1234");
|
||||
});
|
||||
|
||||
it("supports server validation", async () => {
|
||||
const {getByTestId} = render(
|
||||
<Form validationBehavior="aria" validationErrors={{name: "Invalid amount"}}>
|
||||
<NumberInput data-testid="input" label="Name" name="name" />
|
||||
</Form>,
|
||||
);
|
||||
|
||||
const input = getByTestId("input");
|
||||
|
||||
expect(input).toHaveAttribute("aria-describedby");
|
||||
expect(input).toHaveAttribute("aria-invalid", "true");
|
||||
expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent(
|
||||
"Invalid amount",
|
||||
);
|
||||
|
||||
await user.tab();
|
||||
await user.keyboard("1234");
|
||||
await user.tab();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
71
packages/components/number-input/package.json
Normal file
71
packages/components/number-input/package.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "@heroui/number-input",
|
||||
"version": "2.0.0",
|
||||
"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",
|
||||
"numeric input"
|
||||
],
|
||||
"author": "HeroUI <support@heroui.com>",
|
||||
"homepage": "https://heroui.com",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/heroui-inc/heroui.git",
|
||||
"directory": "packages/components/number-input"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/heroui-inc/heroui/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",
|
||||
"@heroui/theme": ">=2.4.7",
|
||||
"@heroui/system": ">=2.4.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/form": "workspace:*",
|
||||
"@heroui/button": "workspace:*",
|
||||
"@heroui/react-utils": "workspace:*",
|
||||
"@heroui/shared-icons": "workspace:*",
|
||||
"@heroui/shared-utils": "workspace:*",
|
||||
"@heroui/use-safe-layout-effect": "workspace:*",
|
||||
"@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.9",
|
||||
"@react-types/shared": "3.27.0",
|
||||
"@react-types/numberfield": "3.8.8",
|
||||
"@react-types/button": "3.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@heroui/system": "workspace:*",
|
||||
"@heroui/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"
|
||||
}
|
||||
10
packages/components/number-input/src/index.ts
Normal file
10
packages/components/number-input/src/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import NumberInput from "./number-input";
|
||||
|
||||
// export types
|
||||
export type {NumberInputProps} from "./number-input";
|
||||
|
||||
// export hooks
|
||||
export {useNumberInput} from "./use-number-input";
|
||||
|
||||
// export component
|
||||
export {NumberInput};
|
||||
@ -0,0 +1,21 @@
|
||||
import type {AriaButtonProps} from "@react-types/button";
|
||||
import type {ButtonProps} from "@heroui/button";
|
||||
|
||||
import {Button} from "@heroui/button";
|
||||
import {ChevronUpIcon, ChevronDownIcon} from "@heroui/shared-icons";
|
||||
|
||||
export interface NumberInputStepperProps extends Omit<ButtonProps, keyof AriaButtonProps> {
|
||||
direction: "up" | "down";
|
||||
}
|
||||
|
||||
const NumberInputStepper = ({direction, ...otherProps}: NumberInputStepperProps) => {
|
||||
return (
|
||||
<Button disableRipple isIconOnly {...otherProps}>
|
||||
{direction == "up" ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
NumberInputStepper.displayName = "HeroUI.NumberInputStepper";
|
||||
|
||||
export default NumberInputStepper;
|
||||
146
packages/components/number-input/src/number-input.tsx
Normal file
146
packages/components/number-input/src/number-input.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import {CloseFilledIcon} from "@heroui/shared-icons";
|
||||
import {useMemo} from "react";
|
||||
import {forwardRef} from "@heroui/system";
|
||||
|
||||
import {UseNumberInputProps, useNumberInput} from "./use-number-input";
|
||||
import NumberInputStepper from "./number-input-stepper";
|
||||
|
||||
export interface NumberInputProps extends UseNumberInputProps {}
|
||||
|
||||
const NumberInput = forwardRef<"input", NumberInputProps>((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
label,
|
||||
description,
|
||||
isClearable,
|
||||
startContent,
|
||||
endContent,
|
||||
labelPlacement,
|
||||
hasHelper,
|
||||
isOutsideLeft,
|
||||
shouldLabelBeOutside,
|
||||
errorMessage,
|
||||
isInvalid,
|
||||
hideStepper,
|
||||
getBaseProps,
|
||||
getLabelProps,
|
||||
getNumberInputProps,
|
||||
getHiddenNumberInputProps,
|
||||
getInnerWrapperProps,
|
||||
getInputWrapperProps,
|
||||
getMainWrapperProps,
|
||||
getHelperWrapperProps,
|
||||
getDescriptionProps,
|
||||
getErrorMessageProps,
|
||||
getClearButtonProps,
|
||||
getStepperIncreaseButtonProps,
|
||||
getStepperDecreaseButtonProps,
|
||||
getStepperWrapperProps,
|
||||
} = useNumberInput({...props, ref});
|
||||
|
||||
const labelContent = label ? <label {...getLabelProps()}>{label}</label> : null;
|
||||
|
||||
const end = useMemo(() => {
|
||||
if (isClearable) {
|
||||
return (
|
||||
<>
|
||||
<button {...getClearButtonProps()}>
|
||||
<CloseFilledIcon />
|
||||
</button>
|
||||
{endContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return endContent;
|
||||
}, [isClearable, getClearButtonProps]);
|
||||
|
||||
const helperWrapper = useMemo(() => {
|
||||
const shouldShowError = isInvalid && errorMessage;
|
||||
const hasContent = shouldShowError || description;
|
||||
|
||||
if (!hasHelper || !hasContent) return null;
|
||||
|
||||
return (
|
||||
<div {...getHelperWrapperProps()}>
|
||||
{shouldShowError ? (
|
||||
<div {...getErrorMessageProps()}>{errorMessage}</div>
|
||||
) : (
|
||||
<div {...getDescriptionProps()}>{description}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
hasHelper,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
description,
|
||||
getHelperWrapperProps,
|
||||
getErrorMessageProps,
|
||||
getDescriptionProps,
|
||||
]);
|
||||
|
||||
const innerWrapper = useMemo(() => {
|
||||
return (
|
||||
<div {...getInnerWrapperProps()}>
|
||||
{startContent}
|
||||
<input {...getNumberInputProps()} />
|
||||
<input {...getHiddenNumberInputProps()} />
|
||||
{end}
|
||||
{!hideStepper && (
|
||||
<div {...getStepperWrapperProps()}>
|
||||
<NumberInputStepper {...getStepperIncreaseButtonProps()} direction="up" />
|
||||
<NumberInputStepper {...getStepperDecreaseButtonProps()} direction="down" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [startContent, end, getNumberInputProps, getInnerWrapperProps]);
|
||||
|
||||
const mainWrapper = useMemo(() => {
|
||||
if (shouldLabelBeOutside) {
|
||||
return (
|
||||
<div {...getMainWrapperProps()}>
|
||||
<div {...getInputWrapperProps()}>
|
||||
{!isOutsideLeft ? labelContent : null}
|
||||
{innerWrapper}
|
||||
</div>
|
||||
{helperWrapper}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div {...getInputWrapperProps()}>
|
||||
{labelContent}
|
||||
{innerWrapper}
|
||||
</div>
|
||||
{helperWrapper}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
labelPlacement,
|
||||
helperWrapper,
|
||||
shouldLabelBeOutside,
|
||||
labelContent,
|
||||
innerWrapper,
|
||||
errorMessage,
|
||||
description,
|
||||
getMainWrapperProps,
|
||||
getInputWrapperProps,
|
||||
getErrorMessageProps,
|
||||
getDescriptionProps,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Component {...getBaseProps()}>
|
||||
{isOutsideLeft ? labelContent : null}
|
||||
{mainWrapper}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
NumberInput.displayName = "HeroUI.NumberInput";
|
||||
|
||||
export default NumberInput;
|
||||
562
packages/components/number-input/src/use-number-input.ts
Normal file
562
packages/components/number-input/src/use-number-input.ts
Normal file
@ -0,0 +1,562 @@
|
||||
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 {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";
|
||||
import {useDOMRef, filterDOMProps} from "@heroui/react-utils";
|
||||
import {useFocusWithin, useHover, usePress} from "@react-aria/interactions";
|
||||
import {useLocale} from "@react-aria/i18n";
|
||||
import {clsx, dataAttr, isEmpty, objectToDeps} from "@heroui/shared-utils";
|
||||
import {useNumberFieldState} from "@react-stately/numberfield";
|
||||
import {useNumberField as useAriaNumberInput} from "@react-aria/numberfield";
|
||||
import {useMemo, Ref, useCallback, useState} from "react";
|
||||
import {chain, mergeProps} from "@react-aria/utils";
|
||||
import {FormContext, useSlottedContext} from "@heroui/form";
|
||||
|
||||
export interface Props extends Omit<HTMLHeroUIProps<"input">, keyof NumberInputVariantProps> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: Ref<HTMLInputElement>;
|
||||
/**
|
||||
* Ref to the container DOM node.
|
||||
*/
|
||||
baseRef?: Ref<HTMLDivElement>;
|
||||
/**
|
||||
* 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<HTMLDivElement>;
|
||||
/**
|
||||
* 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<HTMLDivElement>;
|
||||
/**
|
||||
* 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
|
||||
* <Input classNames={{
|
||||
* base:"base-classes",
|
||||
* label: "label-classes",
|
||||
* mainWrapper: "main-wrapper-classes",
|
||||
* inputWrapper: "input-wrapper-classes",
|
||||
* innerWrapper: "inner-wrapper-classes",
|
||||
* input: "input-classes",
|
||||
* clearButton: "clear-button-classes",
|
||||
* helperWrapper: "helper-wrapper-classes",
|
||||
* stepperWrapper: "stepper-wrapper-classes",
|
||||
* description: "description-classes",
|
||||
* errorMessage: "error-message-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
classNames?: SlotsToClasses<NumberInputSlots>;
|
||||
/**
|
||||
* 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 UseNumberInputProps = Props &
|
||||
Omit<NumberFieldStateOptions, "locale"> &
|
||||
Omit<AriaNumberFieldProps, "onChange"> &
|
||||
NumberInputVariantProps;
|
||||
|
||||
export function useNumberInput(originalProps: UseNumberInputProps) {
|
||||
const globalContext = useProviderContext();
|
||||
const {validationBehavior: formValidationBehavior} = useSlottedContext(FormContext) || {};
|
||||
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, numberInput.variantKeys);
|
||||
|
||||
const {
|
||||
ref,
|
||||
as,
|
||||
label,
|
||||
baseRef,
|
||||
wrapperRef,
|
||||
description,
|
||||
className,
|
||||
classNames,
|
||||
autoFocus,
|
||||
startContent,
|
||||
endContent,
|
||||
onClear,
|
||||
onChange,
|
||||
validationBehavior = formValidationBehavior ?? globalContext?.validationBehavior ?? "native",
|
||||
innerWrapperRef: innerWrapperRefProp,
|
||||
onValueChange,
|
||||
hideStepper,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const [isFocusWithin, setFocusWithin] = useState(false);
|
||||
|
||||
const Component = as || "div";
|
||||
|
||||
const disableAnimation =
|
||||
originalProps.disableAnimation ?? globalContext?.disableAnimation ?? false;
|
||||
|
||||
const domRef = useDOMRef<HTMLInputElement>(ref);
|
||||
|
||||
const baseDomRef = useDOMRef<HTMLDivElement>(baseRef);
|
||||
const inputWrapperRef = useDOMRef<HTMLDivElement>(wrapperRef);
|
||||
const innerWrapperRef = useDOMRef<HTMLDivElement>(innerWrapperRefProp);
|
||||
|
||||
const {locale} = useLocale();
|
||||
|
||||
const state = useNumberFieldState({
|
||||
...originalProps,
|
||||
validationBehavior,
|
||||
locale,
|
||||
onChange: onValueChange,
|
||||
});
|
||||
|
||||
const {
|
||||
groupProps,
|
||||
labelProps,
|
||||
inputProps,
|
||||
incrementButtonProps,
|
||||
decrementButtonProps,
|
||||
descriptionProps,
|
||||
errorMessageProps,
|
||||
isInvalid,
|
||||
validationErrors,
|
||||
validationDetails,
|
||||
} = useAriaNumberInput({...originalProps, validationBehavior}, 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(() => {
|
||||
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 labelPlacement = useLabelPlacement({
|
||||
labelPlacement: originalProps.labelPlacement,
|
||||
label,
|
||||
});
|
||||
|
||||
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 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(
|
||||
() =>
|
||||
numberInput({
|
||||
...variantProps,
|
||||
isInvalid,
|
||||
isClearable,
|
||||
disableAnimation,
|
||||
}),
|
||||
[objectToDeps(variantProps), isInvalid, 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),
|
||||
...focusWithinProps,
|
||||
...props,
|
||||
};
|
||||
},
|
||||
[
|
||||
slots,
|
||||
baseStyles,
|
||||
isFilled,
|
||||
isFocused,
|
||||
isHovered,
|
||||
isLabelHovered,
|
||||
isInvalid,
|
||||
hasHelper,
|
||||
hasLabel,
|
||||
hasElements,
|
||||
isPlaceholderShown,
|
||||
hasStartContent,
|
||||
isFocusWithin,
|
||||
isFocusVisible,
|
||||
hasPlaceholder,
|
||||
focusWithinProps,
|
||||
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 getNumberInputProps: PropGetter = useCallback(
|
||||
(props = {}) => {
|
||||
return {
|
||||
"data-slot": "input",
|
||||
"data-filled": dataAttr(isFilled),
|
||||
"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)),
|
||||
omitPropNames: new Set(["value"]),
|
||||
}),
|
||||
props,
|
||||
),
|
||||
"aria-readonly": dataAttr(originalProps.isReadOnly),
|
||||
onChange: chain(inputProps.onChange, onChange),
|
||||
ref: domRef,
|
||||
};
|
||||
},
|
||||
[
|
||||
slots,
|
||||
focusProps,
|
||||
inputProps,
|
||||
otherProps,
|
||||
isFilled,
|
||||
hasStartContent,
|
||||
endContent,
|
||||
classNames?.input,
|
||||
originalProps.isReadOnly,
|
||||
originalProps.isRequired,
|
||||
onChange,
|
||||
],
|
||||
);
|
||||
|
||||
const getHiddenNumberInputProps: 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 {
|
||||
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 {
|
||||
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),
|
||||
}),
|
||||
...mergeProps(groupProps, props),
|
||||
};
|
||||
},
|
||||
[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?.label, 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 getStepperWrapperProps: PropGetter = useCallback(
|
||||
(props = {}) => {
|
||||
return {
|
||||
...props,
|
||||
"data-slot": "stepper-wrapper",
|
||||
className: slots.stepperWrapper({
|
||||
class: clsx(classNames?.stepperWrapper, props?.className),
|
||||
}),
|
||||
};
|
||||
},
|
||||
[slots],
|
||||
);
|
||||
|
||||
const getStepperIncreaseButtonProps: PropGetter = useCallback(
|
||||
(props = {}) => {
|
||||
return {
|
||||
...props,
|
||||
type: "button",
|
||||
disabled: originalProps.isDisabled,
|
||||
"data-slot": "increase-button",
|
||||
className: slots.stepperButton({
|
||||
class: clsx(classNames?.stepperButton, props?.className),
|
||||
}),
|
||||
...mergeProps(incrementButtonProps, props),
|
||||
};
|
||||
},
|
||||
[slots],
|
||||
);
|
||||
|
||||
const getStepperDecreaseButtonProps: PropGetter = useCallback(
|
||||
(props = {}) => {
|
||||
return {
|
||||
type: "button",
|
||||
disabled: originalProps.isDisabled,
|
||||
"data-slot": "decrease-button",
|
||||
className: slots.stepperButton({
|
||||
class: clsx(classNames?.stepperButton, props?.className),
|
||||
}),
|
||||
...mergeProps(decrementButtonProps, props),
|
||||
};
|
||||
},
|
||||
[slots],
|
||||
);
|
||||
|
||||
return {
|
||||
Component,
|
||||
classNames,
|
||||
domRef,
|
||||
label,
|
||||
description,
|
||||
startContent,
|
||||
endContent,
|
||||
labelPlacement,
|
||||
isClearable,
|
||||
hasHelper,
|
||||
hasStartContent,
|
||||
isLabelOutside,
|
||||
isOutsideLeft,
|
||||
isLabelOutsideAsPlaceholder,
|
||||
shouldLabelBeOutside,
|
||||
shouldLabelBeInside,
|
||||
hasPlaceholder,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
hideStepper,
|
||||
incrementButtonProps,
|
||||
decrementButtonProps,
|
||||
getBaseProps,
|
||||
getLabelProps,
|
||||
getNumberInputProps,
|
||||
getHiddenNumberInputProps,
|
||||
getMainWrapperProps,
|
||||
getInputWrapperProps,
|
||||
getInnerWrapperProps,
|
||||
getHelperWrapperProps,
|
||||
getDescriptionProps,
|
||||
getErrorMessageProps,
|
||||
getClearButtonProps,
|
||||
getStepperIncreaseButtonProps,
|
||||
getStepperDecreaseButtonProps,
|
||||
getStepperWrapperProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseNumberInputReturn = ReturnType<typeof useNumberInput>;
|
||||
@ -0,0 +1,555 @@
|
||||
/* 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 "@heroui/theme";
|
||||
import {Form} from "@heroui/form";
|
||||
import {numberInput} from "@heroui/theme";
|
||||
|
||||
import {NumberInput, NumberInputProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/NumberInput",
|
||||
component: NumberInput,
|
||||
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) => (
|
||||
<div className="flex items-center justify-center w-screen h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} as Meta<typeof NumberInput>;
|
||||
|
||||
const defaultProps = {
|
||||
...numberInput.defaultVariants,
|
||||
defaultValue: 24,
|
||||
};
|
||||
|
||||
const Template = (args) => (
|
||||
<div className="w-full max-w-[240px]">
|
||||
<NumberInput {...args} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const FormTemplate = (args) => (
|
||||
<form
|
||||
className="w-full max-w-xl flex flex-row items-end gap-4"
|
||||
onSubmit={(e) => {
|
||||
alert(`Submitted value: ${e.target["example"].value}`);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<NumberInput {...args} name="example" />
|
||||
<button className={button({color: "primary"})} type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
|
||||
const ControlledTemplate = (args) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 max-w-[240px]">
|
||||
<NumberInput {...args} value={value} onValueChange={setValue} />
|
||||
<p className="text-default-500 text-sm">NumberInput value: {value}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LabelPlacementTemplate = (args) => (
|
||||
<div className="w-full flex flex-col items-center gap-12">
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3>Without placeholder</h3>
|
||||
<div className="w-full max-w-3xl flex flex-row items-end gap-4">
|
||||
<NumberInput {...args} description="inside" />
|
||||
<NumberInput {...args} description="outside" labelPlacement="outside" />
|
||||
<NumberInput {...args} description="outside-left" labelPlacement="outside-left" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3>With placeholder</h3>
|
||||
<div className="w-full max-w-3xl flex flex-row items-end gap-4">
|
||||
<NumberInput {...args} description="inside" placeholder="Enter a number" />
|
||||
<NumberInput
|
||||
{...args}
|
||||
description="outside"
|
||||
labelPlacement="outside"
|
||||
placeholder="Enter a number"
|
||||
/>
|
||||
<NumberInput
|
||||
{...args}
|
||||
description="outside-left"
|
||||
labelPlacement="outside-left"
|
||||
placeholder="Enter a number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// const WithReactHookFormTemplate = (args: NumberInputProps) => {
|
||||
// const {
|
||||
// register,
|
||||
// formState: {errors},
|
||||
// handleSubmit,
|
||||
// } = useForm({
|
||||
// defaultValues: {
|
||||
// withDefaultValue: 24,
|
||||
// withoutDefaultValue: "",
|
||||
// requiredField: "",
|
||||
// },
|
||||
// });
|
||||
|
||||
// const onSubmit = (data: any) => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log(data);
|
||||
// alert("Submitted value: " + JSON.stringify(data));
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
// <NumberInput
|
||||
// {...args}
|
||||
// isClearable
|
||||
// label="With default value"
|
||||
// {...register("withDefaultValue")}
|
||||
// />
|
||||
// <NumberInput {...args} label="Without default value" {...register("withoutDefaultValue")} />
|
||||
// <NumberInput {...args} label="Required" {...register("requiredField", {required: true})} />
|
||||
// {errors.requiredField && <span className="text-danger">This field is required</span>}
|
||||
// <button className={button({class: "w-fit"})} type="submit">
|
||||
// Submit
|
||||
// </button>
|
||||
// </form>
|
||||
// );
|
||||
// };
|
||||
|
||||
const ServerValidationTemplate = (args: NumberInputProps) => {
|
||||
const [serverErrors, setServerErrors] = React.useState({});
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setServerErrors({
|
||||
amount: "Please provide a valid number.",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="flex flex-col items-start gap-2"
|
||||
validationErrors={serverErrors}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<NumberInput {...args} />
|
||||
<button className={button({color: "primary"})} type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
"aria-label": "Amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLabel = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
description: "Specify the amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithStepValue = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
step: 10,
|
||||
description: "Set `step` to `10` to increment / decrement the value by 10.",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithWheelDisabled = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
step: 10,
|
||||
description: "Set `isWheelDisabled` to `true` to disable the wheel.",
|
||||
isWheelDisabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithFormatOptions = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Transaction amount",
|
||||
formatOptions: {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
currencyDisplay: "code",
|
||||
currencySign: "accounting",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const HideStepper = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
hideStepper: true,
|
||||
label: "Hide Stepper",
|
||||
description: "Set `hideStepper` to `true` to hide the stepper.",
|
||||
},
|
||||
};
|
||||
|
||||
export const Required = {
|
||||
render: FormTemplate,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
isRequired: true,
|
||||
defaultValue: undefined,
|
||||
placeholder: "Enter a number",
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "faded",
|
||||
isDisabled: true,
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnly = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
isReadOnly: true,
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const LabelPlacement = {
|
||||
render: LabelPlacementTemplate,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Amount",
|
||||
defaultValue: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const Clearable = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
placeholder: "Enter a number",
|
||||
// eslint-disable-next-line no-console
|
||||
onClear: () => console.log("number input cleared"),
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const StartContent = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
label: "Price",
|
||||
placeholder: "0.00",
|
||||
startContent: (
|
||||
<div className="pointer-events-none flex items-center">
|
||||
<span className="text-default-400 text-sm">$</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const EndContent = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
label: "Price",
|
||||
placeholder: "0.00",
|
||||
endContent: (
|
||||
<div className="pointer-events-none flex items-center">
|
||||
<span className="text-default-400 text-sm">€</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const StartAndEndContent = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
label: "Price",
|
||||
placeholder: "0.00",
|
||||
endContent: (
|
||||
<div className="flex items-center">
|
||||
<label className="sr-only" htmlFor="currency">
|
||||
Currency
|
||||
</label>
|
||||
<select
|
||||
className="outline-none border-0 bg-transparent text-default-400 text-sm"
|
||||
id="currency"
|
||||
name="currency"
|
||||
>
|
||||
<option>USD</option>
|
||||
<option>ARS</option>
|
||||
<option>EUR</option>
|
||||
</select>
|
||||
</div>
|
||||
),
|
||||
startContent: (
|
||||
<div className="pointer-events-none flex items-center">
|
||||
<span className="text-default-400 text-sm">$</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const WithErrorMessage = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
isInvalid: true,
|
||||
errorMessage: "Please enter a valid number",
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithErrorMessageFunction = {
|
||||
render: FormTemplate,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
min: "0",
|
||||
max: "100",
|
||||
isRequired: true,
|
||||
label: "Number",
|
||||
validationBehavior: "native",
|
||||
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,
|
||||
label: "amount",
|
||||
name: "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const IsInvalid = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
isInvalid: true,
|
||||
placeholder: "Enter a number",
|
||||
errorMessage: "Please enter a valid range of numbers",
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const Controlled = {
|
||||
render: ControlledTemplate,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
placeholder: "Enter a number",
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
export const MinValue = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Enter a number (min value: 60)",
|
||||
minValue: 60,
|
||||
defaultValue: 64,
|
||||
},
|
||||
};
|
||||
|
||||
export const MaxValue = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
...defaultProps,
|
||||
label: "Enter a number (max value: 100)",
|
||||
defaultValue: 0,
|
||||
maxValue: 100,
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomWithClassNames = {
|
||||
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: <div className="pointer-events-none flex items-center">€</div>,
|
||||
placeholder: "Enter the amount",
|
||||
"aria-label": "amount",
|
||||
},
|
||||
};
|
||||
|
||||
// export const CustomWithHooks = {
|
||||
// render: CustomWithHooksTemplate,
|
||||
|
||||
// args: {
|
||||
// ...defaultProps,
|
||||
// label: "Search",
|
||||
// type: "search",
|
||||
// placeholder: "Type to search...",
|
||||
// startContent: (
|
||||
// <SearchIcon className="text-black/50 mb-0.5 dark:text-white/90 text-slate-400 pointer-events-none flex-shrink-0" />
|
||||
// ),
|
||||
// },
|
||||
// };
|
||||
|
||||
// export const WithReactHookForm = {
|
||||
// render: WithReactHookFormTemplate,
|
||||
|
||||
// args: {
|
||||
// ...defaultProps,
|
||||
// },
|
||||
// };
|
||||
10
packages/components/number-input/tsconfig.json
Normal file
10
packages/components/number-input/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"tailwind-variants": ["../../../node_modules/tailwind-variants"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
8
packages/components/number-input/tsup.config.ts
Normal file
8
packages/components/number-input/tsup.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import {defineConfig} from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
target: "es2019",
|
||||
format: ["cjs", "esm"],
|
||||
banner: {js: '"use client";'},
|
||||
});
|
||||
@ -89,6 +89,7 @@
|
||||
"@heroui/drawer": "workspace:*",
|
||||
"@heroui/form": "workspace:*",
|
||||
"@heroui/alert": "workspace:*",
|
||||
"@heroui/number-input": "workspace:*",
|
||||
"@heroui/toast": "workspace:*",
|
||||
"@react-aria/visually-hidden": "3.8.19"
|
||||
},
|
||||
|
||||
@ -47,6 +47,7 @@ export * from "@heroui/form";
|
||||
export * from "@heroui/alert";
|
||||
export * from "@heroui/drawer";
|
||||
export * from "@heroui/input-otp";
|
||||
export * from "@heroui/number-input";
|
||||
export * from "@heroui/toast";
|
||||
|
||||
/**
|
||||
|
||||
@ -41,4 +41,5 @@ export * from "./date-picker";
|
||||
export * from "./alert";
|
||||
export * from "./drawer";
|
||||
export * from "./form";
|
||||
export * from "./number-input";
|
||||
export * from "./toast";
|
||||
|
||||
854
packages/core/theme/src/components/number-input.ts
Normal file
854
packages/core/theme/src/components/number-input.ts
Normal file
@ -0,0 +1,854 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "../utils/tv";
|
||||
import {dataFocusVisibleClasses, groupDataFocusVisibleClasses} from "../utils";
|
||||
|
||||
/**
|
||||
* NumberInput wrapper **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const {base, label, inputWrapper, input, clearButton, description, errorMessage} = numberInput({...})
|
||||
*
|
||||
* <div className={base())}>
|
||||
* <label className={label()}>Label</label>
|
||||
* <div className={inputWrapper()}>
|
||||
* <input className={input()}/>
|
||||
* <button className={clearButton()}>Clear</button>
|
||||
* </div>
|
||||
* <span className={description()}>Description</span>
|
||||
* <span className={errorMessage()}>Invalid input</span>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
const numberInput = 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-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",
|
||||
"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,
|
||||
],
|
||||
stepperButton: [
|
||||
"bg-transparent",
|
||||
"flex",
|
||||
"justify-center",
|
||||
"items-center",
|
||||
"before:absolute",
|
||||
"before:w-8", // the max width that won't block clear button
|
||||
"before:h-8",
|
||||
"before:rounded-full",
|
||||
"after:shadow-small",
|
||||
"after:bg-background",
|
||||
"data-[focused=true]:z-10",
|
||||
"min-w-5",
|
||||
"w-5",
|
||||
"h-5",
|
||||
"overflow-visible",
|
||||
"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",
|
||||
description: "text-tiny text-foreground-400",
|
||||
errorMessage: "text-tiny text-danger",
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
flat: {
|
||||
inputWrapper: [
|
||||
"bg-default-100",
|
||||
"data-[hover=true]:bg-default-200",
|
||||
"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: {
|
||||
stepperButton: "text-primary",
|
||||
},
|
||||
secondary: {
|
||||
stepperButton: "text-secondary",
|
||||
},
|
||||
success: {
|
||||
stepperButton: "text-success",
|
||||
},
|
||||
warning: {
|
||||
stepperButton: "text-warning",
|
||||
},
|
||||
danger: {
|
||||
stepperButton: "text-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",
|
||||
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",
|
||||
inputWrapper: "flex-col items-start justify-center gap-0",
|
||||
innerWrapper: "group-data-[has-label=true]:items-end",
|
||||
},
|
||||
},
|
||||
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: "inside",
|
||||
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=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",
|
||||
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",
|
||||
},
|
||||
},
|
||||
// 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",
|
||||
size: "sm",
|
||||
class: {
|
||||
innerWrapper: "pb-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "underlined",
|
||||
size: ["md", "lg"],
|
||||
class: {
|
||||
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)]",
|
||||
stepperButton: "min-4 w-4 h-4",
|
||||
},
|
||||
},
|
||||
// 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",
|
||||
stepperButton: "min-4 w-4 h-4",
|
||||
},
|
||||
},
|
||||
// text truncate labelPlacement=[inside,outside]
|
||||
{
|
||||
labelPlacement: ["inside", "outside"],
|
||||
class: {
|
||||
label: ["pe-2", "max-w-full", "text-ellipsis", "overflow-hidden"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export type NumberInputVariantProps = VariantProps<typeof numberInput>;
|
||||
export type NumberInputSlots = keyof ReturnType<typeof numberInput>;
|
||||
|
||||
export {numberInput};
|
||||
20
packages/utilities/shared-icons/src/chevron-left.tsx
Normal file
20
packages/utilities/shared-icons/src/chevron-left.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const ChevronLeftIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="m15 18-6-6 6-6" />
|
||||
</svg>
|
||||
);
|
||||
@ -5,9 +5,10 @@ export * from "./avatar";
|
||||
export * from "./close";
|
||||
export * from "./close-filled";
|
||||
export * from "./chevron";
|
||||
export * from "./chevron-down";
|
||||
export * from "./chevron-right";
|
||||
export * from "./chevron-up";
|
||||
export * from "./chevron-down";
|
||||
export * from "./chevron-left";
|
||||
export * from "./chevron-right";
|
||||
export * from "./ellipsis";
|
||||
export * from "./forward";
|
||||
export * from "./sun";
|
||||
|
||||
282
pnpm-lock.yaml
generated
282
pnpm-lock.yaml
generated
@ -123,16 +123,16 @@ importers:
|
||||
version: 7.32.0
|
||||
eslint-config-airbnb:
|
||||
specifier: ^18.2.1
|
||||
version: 18.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)
|
||||
version: 18.2.1(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-config-airbnb-typescript:
|
||||
specifier: ^12.3.1
|
||||
version: 12.3.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3)
|
||||
version: 12.3.1(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3)
|
||||
eslint-config-prettier:
|
||||
specifier: ^8.2.0
|
||||
version: 8.10.0(eslint@7.32.0)
|
||||
eslint-config-react-app:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(babel-eslint@10.1.0(eslint@7.32.0))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3)
|
||||
version: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(babel-eslint@10.1.0(eslint@7.32.0))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3)
|
||||
eslint-config-ts-lambdas:
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3)
|
||||
@ -141,7 +141,7 @@ importers:
|
||||
version: 2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0)
|
||||
eslint-loader:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(eslint@7.32.0)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12))
|
||||
version: 4.0.2(eslint@7.32.0)(webpack@5.97.1)
|
||||
eslint-plugin-import:
|
||||
specifier: ^2.26.0
|
||||
version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
|
||||
@ -195,13 +195,13 @@ importers:
|
||||
version: 10.7.11
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
version: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
jest-environment-jsdom:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
jest-watch-typeahead:
|
||||
specifier: 2.2.2
|
||||
version: 2.2.2(jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)))
|
||||
version: 2.2.2(jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)))
|
||||
lint-staged:
|
||||
specifier: ^13.0.3
|
||||
version: 13.3.0(enquirer@2.4.1)
|
||||
@ -252,7 +252,7 @@ importers:
|
||||
version: 5.7.3
|
||||
webpack:
|
||||
specifier: ^5.53.0
|
||||
version: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)
|
||||
version: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12(webpack@5.97.1))
|
||||
webpack-bundle-analyzer:
|
||||
specifier: ^4.4.2
|
||||
version: 4.10.2
|
||||
@ -2217,6 +2217,76 @@ importers:
|
||||
specifier: 0.13.0
|
||||
version: 0.13.0(react@18.3.0)
|
||||
|
||||
packages/components/number-input:
|
||||
dependencies:
|
||||
'@heroui/button':
|
||||
specifier: workspace:*
|
||||
version: link:../button
|
||||
'@heroui/form':
|
||||
specifier: workspace:*
|
||||
version: link:../form
|
||||
'@heroui/react-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/react-utils
|
||||
'@heroui/shared-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/shared-icons
|
||||
'@heroui/shared-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/shared-utils
|
||||
'@heroui/use-safe-layout-effect':
|
||||
specifier: workspace:*
|
||||
version: link:../../hooks/use-safe-layout-effect
|
||||
'@react-aria/focus':
|
||||
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.5
|
||||
version: 3.12.5(react-dom@18.3.0(react@18.3.0))(react@18.3.0)
|
||||
'@react-aria/interactions':
|
||||
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.10
|
||||
version: 3.11.10(react-dom@18.3.0(react@18.3.0))(react@18.3.0)
|
||||
'@react-aria/utils':
|
||||
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.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.2
|
||||
version: 3.10.2(react@18.3.0)
|
||||
'@react-types/numberfield':
|
||||
specifier: 3.8.8
|
||||
version: 3.8.8(react@18.3.0)
|
||||
'@react-types/shared':
|
||||
specifier: 3.27.0
|
||||
version: 3.27.0(react@18.3.0)
|
||||
devDependencies:
|
||||
'@heroui/system':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/system
|
||||
'@heroui/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/theme
|
||||
clean-package:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0
|
||||
react:
|
||||
specifier: 18.3.0
|
||||
version: 18.3.0
|
||||
react-dom:
|
||||
specifier: 18.3.0
|
||||
version: 18.3.0(react@18.3.0)
|
||||
react-hook-form:
|
||||
specifier: ^7.51.3
|
||||
version: 7.54.2(react@18.3.0)
|
||||
|
||||
packages/components/pagination:
|
||||
dependencies:
|
||||
'@heroui/react-utils':
|
||||
@ -3284,6 +3354,9 @@ importers:
|
||||
'@heroui/navbar':
|
||||
specifier: workspace:*
|
||||
version: link:../../components/navbar
|
||||
'@heroui/number-input':
|
||||
specifier: workspace:*
|
||||
version: link:../../components/number-input
|
||||
'@heroui/pagination':
|
||||
specifier: workspace:*
|
||||
version: link:../../components/pagination
|
||||
@ -3427,7 +3500,7 @@ importers:
|
||||
version: 18.3.0
|
||||
tailwind-variants:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)))
|
||||
version: 0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)))
|
||||
|
||||
packages/core/theme:
|
||||
dependencies:
|
||||
@ -3454,7 +3527,7 @@ importers:
|
||||
version: 2.5.4
|
||||
tailwind-variants:
|
||||
specifier: 0.3.0
|
||||
version: 0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)))
|
||||
version: 0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)))
|
||||
devDependencies:
|
||||
'@types/color':
|
||||
specifier: ^4.2.0
|
||||
@ -3467,7 +3540,7 @@ importers:
|
||||
version: 2.2.0
|
||||
tailwindcss:
|
||||
specifier: ^3.4.16
|
||||
version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
|
||||
packages/hooks/use-aria-accordion:
|
||||
dependencies:
|
||||
@ -7011,6 +7084,12 @@ packages:
|
||||
react: 18.3.0
|
||||
react-dom: 18.3.0
|
||||
|
||||
'@react-aria/numberfield@3.11.10':
|
||||
resolution: {integrity: sha512-bYbTfO9NbAKMFOfEGGs+lvlxk0I9L0lU3WD2PFQZWdaoBz9TCkL+vK0fJk1zsuKaVjeGsmHP9VesBPRmaP0MiA==}
|
||||
peerDependencies:
|
||||
react: 18.3.0
|
||||
react-dom: 18.3.0
|
||||
|
||||
'@react-aria/overlays@3.25.0':
|
||||
resolution: {integrity: sha512-UEqJJ4duowrD1JvwXpPZreBuK79pbyNjNxFUVpFSskpGEJe3oCWwsSDKz7P1O7xbx5OYp+rDiY8fk/sE5rkaKw==}
|
||||
peerDependencies:
|
||||
@ -7189,6 +7268,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: 18.3.0
|
||||
|
||||
'@react-stately/numberfield@3.9.9':
|
||||
resolution: {integrity: sha512-hZsLiGGHTHmffjFymbH1qVmA633rU2GNjMFQTuSsN4lqqaP8fgxngd5pPCoTCUFEkUgWjdHenw+ZFByw8lIE+g==}
|
||||
peerDependencies:
|
||||
react: 18.3.0
|
||||
|
||||
'@react-stately/overlays@3.6.13':
|
||||
resolution: {integrity: sha512-WsU85Gf/b+HbWsnnYw7P/Ila3wD+C37Uk/WbU4/fHgJ26IEOWsPE6wlul8j54NZ1PnLNhV9Fn+Kffi+PaJMQXQ==}
|
||||
peerDependencies:
|
||||
@ -7320,6 +7404,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: 18.3.0
|
||||
|
||||
'@react-types/numberfield@3.8.8':
|
||||
resolution: {integrity: sha512-825JPppxDaWh0Zxb0Q+wSslgRQYOtQPCAuhszPuWEy6d2F/M+hLR+qQqvQm9+LfMbdwiTg6QK5wxdWFCp2t7jw==}
|
||||
peerDependencies:
|
||||
react: 18.3.0
|
||||
|
||||
'@react-types/overlays@3.8.12':
|
||||
resolution: {integrity: sha512-ZvR1t0YV7/6j+6OD8VozKYjvsXT92+C/2LOIKozy7YUNS5KI4MkXbRZzJvkuRECVZOmx8JXKTUzhghWJM/3QuQ==}
|
||||
peerDependencies:
|
||||
@ -16896,7 +16985,7 @@ snapshots:
|
||||
lodash.merge: 4.6.2
|
||||
lodash.uniq: 4.5.0
|
||||
resolve-from: 5.0.0
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
@ -17615,7 +17704,7 @@ snapshots:
|
||||
jest-util: 29.7.0
|
||||
slash: 3.0.0
|
||||
|
||||
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))':
|
||||
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))':
|
||||
dependencies:
|
||||
'@jest/console': 29.7.0
|
||||
'@jest/reporters': 29.7.0
|
||||
@ -17629,7 +17718,7 @@ snapshots:
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 29.7.0
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
jest-haste-map: 29.7.0
|
||||
jest-message-util: 29.7.0
|
||||
jest-regex-util: 29.6.3
|
||||
@ -19257,6 +19346,22 @@ snapshots:
|
||||
react: 18.3.0
|
||||
react-dom: 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.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.9(react@18.3.0)
|
||||
'@react-types/button': 3.10.2(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
|
||||
react-dom: 18.3.0(react@18.3.0)
|
||||
|
||||
'@react-aria/overlays@3.25.0(react-dom@18.3.0(react@18.3.0))(react@18.3.0)':
|
||||
dependencies:
|
||||
'@react-aria/focus': 3.19.1(react-dom@18.3.0(react@18.3.0))(react@18.3.0)
|
||||
@ -19608,6 +19713,15 @@ snapshots:
|
||||
'@swc/helpers': 0.5.15
|
||||
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.8(react@18.3.0)
|
||||
'@swc/helpers': 0.5.15
|
||||
react: 18.3.0
|
||||
|
||||
'@react-stately/overlays@3.6.13(react@18.3.0)':
|
||||
dependencies:
|
||||
'@react-stately/utils': 3.10.5(react@18.3.0)
|
||||
@ -19786,6 +19900,11 @@ snapshots:
|
||||
'@react-types/shared': 3.27.0(react@18.3.0)
|
||||
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
|
||||
|
||||
'@react-types/overlays@3.8.12(react@18.3.0)':
|
||||
dependencies:
|
||||
'@react-types/shared': 3.27.0(react@18.3.0)
|
||||
@ -22158,7 +22277,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 20.5.1
|
||||
cosmiconfig: 8.3.6(typescript@5.7.3)
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)
|
||||
typescript: 5.7.3
|
||||
|
||||
cosmiconfig@8.3.6(typescript@5.7.3):
|
||||
@ -22179,13 +22298,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
create-jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)):
|
||||
create-jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
jest-util: 29.7.0
|
||||
prompts: 2.4.2
|
||||
transitivePeerDependencies:
|
||||
@ -22848,7 +22967,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
eslint-config-airbnb-base@14.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint@7.32.0):
|
||||
eslint-config-airbnb-base@14.2.1(eslint-plugin-import@2.31.0)(eslint@7.32.0):
|
||||
dependencies:
|
||||
confusing-browser-globals: 1.0.11
|
||||
eslint: 7.32.0
|
||||
@ -22856,11 +22975,11 @@ snapshots:
|
||||
object.assign: 4.1.7
|
||||
object.entries: 1.1.8
|
||||
|
||||
eslint-config-airbnb-typescript@12.3.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3):
|
||||
eslint-config-airbnb-typescript@12.3.1(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.7.3)
|
||||
eslint-config-airbnb: 18.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-config-airbnb-base: 14.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-config-airbnb: 18.2.1(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-config-airbnb-base: 14.2.1(eslint-plugin-import@2.31.0)(eslint@7.32.0)
|
||||
transitivePeerDependencies:
|
||||
- eslint
|
||||
- eslint-plugin-import
|
||||
@ -22870,10 +22989,10 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
eslint-config-airbnb@18.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0):
|
||||
eslint-config-airbnb@18.2.1(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0):
|
||||
dependencies:
|
||||
eslint: 7.32.0
|
||||
eslint-config-airbnb-base: 14.2.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-config-airbnb-base: 14.2.1(eslint-plugin-import@2.31.0)(eslint@7.32.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@7.32.0)
|
||||
eslint-plugin-react: 7.37.3(eslint@7.32.0)
|
||||
@ -22905,7 +23024,7 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 7.32.0
|
||||
|
||||
eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(babel-eslint@10.1.0(eslint@7.32.0))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0))(eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3):
|
||||
eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(babel-eslint@10.1.0(eslint@7.32.0))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.2(eslint@7.32.0))(eslint-plugin-react@7.37.3(eslint@7.32.0))(eslint@7.32.0)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint@7.32.0)(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.7.3)
|
||||
@ -22964,7 +23083,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-loader@4.0.2(eslint@7.32.0)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)):
|
||||
eslint-loader@4.0.2(eslint@7.32.0)(webpack@5.97.1):
|
||||
dependencies:
|
||||
eslint: 7.32.0
|
||||
find-cache-dir: 3.3.2
|
||||
@ -22972,9 +23091,9 @@ snapshots:
|
||||
loader-utils: 2.0.4
|
||||
object-hash: 2.2.0
|
||||
schema-utils: 2.7.1
|
||||
webpack: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)
|
||||
webpack: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12(webpack@5.97.1))
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -23008,7 +23127,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 7.32.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@ -24736,16 +24855,16 @@ snapshots:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-cli@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)):
|
||||
jest-cli@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
create-jest: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
exit: 0.1.2
|
||||
import-local: 3.2.0
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
jest-config: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
yargs: 17.7.2
|
||||
@ -24755,7 +24874,7 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest-config@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)):
|
||||
jest-config@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
@ -24781,7 +24900,7 @@ snapshots:
|
||||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 15.14.9
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
@ -24998,11 +25117,11 @@ snapshots:
|
||||
leven: 3.1.0
|
||||
pretty-format: 29.7.0
|
||||
|
||||
jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))):
|
||||
jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))):
|
||||
dependencies:
|
||||
ansi-escapes: 6.2.1
|
||||
chalk: 5.4.1
|
||||
jest: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
jest: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
jest-regex-util: 29.6.3
|
||||
jest-watcher: 29.7.0
|
||||
slash: 5.1.0
|
||||
@ -25033,12 +25152,12 @@ snapshots:
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)):
|
||||
jest@29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.2.0
|
||||
jest-cli: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
jest-cli: 29.7.0(@types/node@15.14.9)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
@ -27082,14 +27201,6 @@ snapshots:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.4.49
|
||||
|
||||
postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
yaml: 2.7.0
|
||||
optionalDependencies:
|
||||
postcss: 8.4.49
|
||||
ts-node: 10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)
|
||||
|
||||
postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.2.5)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
@ -28702,10 +28813,10 @@ snapshots:
|
||||
tailwind-merge: 2.5.4
|
||||
tailwindcss: 3.4.14(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.2.5)(typescript@5.7.3))
|
||||
|
||||
tailwind-variants@0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))):
|
||||
tailwind-variants@0.3.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))):
|
||||
dependencies:
|
||||
tailwind-merge: 2.5.4
|
||||
tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
|
||||
tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.2.5)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
@ -28761,7 +28872,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3)):
|
||||
tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
arg: 5.0.2
|
||||
@ -28780,7 +28891,7 @@ snapshots:
|
||||
postcss: 8.4.49
|
||||
postcss-import: 15.1.0(postcss@8.4.49)
|
||||
postcss-js: 4.0.1(postcss@8.4.49)
|
||||
postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3))
|
||||
postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.7.3))
|
||||
postcss-nested: 6.2.0(postcss@8.4.49)
|
||||
postcss-selector-parser: 6.1.2
|
||||
resolve: 1.22.10
|
||||
@ -28836,7 +28947,7 @@ snapshots:
|
||||
|
||||
term-size@2.2.1: {}
|
||||
|
||||
terser-webpack-plugin@5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12(webpack@5.97.1))):
|
||||
terser-webpack-plugin@5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
@ -28848,18 +28959,6 @@ snapshots:
|
||||
'@swc/core': 1.10.6(@swc/helpers@0.5.15)
|
||||
esbuild: 0.24.2
|
||||
|
||||
terser-webpack-plugin@5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 4.3.0
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.37.0
|
||||
webpack: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.10.6(@swc/helpers@0.5.15)
|
||||
esbuild: 0.24.2
|
||||
|
||||
terser@5.37.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
@ -28997,26 +29096,6 @@ snapshots:
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@15.14.9)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.11
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 15.14.9
|
||||
acorn: 8.14.0
|
||||
acorn-walk: 8.3.4
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.7.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.10.6(@swc/helpers@0.5.15)
|
||||
|
||||
ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@20.2.5)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
@ -29057,7 +29136,6 @@ snapshots:
|
||||
yn: 3.1.1
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.10.6(@swc/helpers@0.5.15)
|
||||
optional: true
|
||||
|
||||
ts-pattern@5.6.0: {}
|
||||
|
||||
@ -29695,7 +29773,7 @@ snapshots:
|
||||
loader-utils: 1.4.2
|
||||
supports-color: 6.1.0
|
||||
v8-compile-cache: 2.4.0
|
||||
webpack: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12)
|
||||
webpack: 5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12(webpack@5.97.1))
|
||||
yargs: 13.3.2
|
||||
|
||||
webpack-merge@5.10.0:
|
||||
@ -29730,39 +29808,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12(webpack@5.97.1)))
|
||||
watchpack: 2.4.2
|
||||
webpack-sources: 3.2.3
|
||||
optionalDependencies:
|
||||
webpack-cli: 3.3.12(webpack@5.97.1)
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
- uglify-js
|
||||
|
||||
webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12):
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.7
|
||||
'@types/estree': 1.0.6
|
||||
'@webassemblyjs/ast': 1.14.1
|
||||
'@webassemblyjs/wasm-edit': 1.14.1
|
||||
'@webassemblyjs/wasm-parser': 1.14.1
|
||||
acorn: 8.14.0
|
||||
browserslist: 4.24.3
|
||||
chrome-trace-event: 1.0.4
|
||||
enhanced-resolve: 5.18.0
|
||||
es-module-lexer: 1.6.0
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
glob-to-regexp: 0.4.1
|
||||
graceful-fs: 4.2.11
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
loader-runner: 4.3.0
|
||||
mime-types: 2.1.35
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@3.3.12))
|
||||
terser-webpack-plugin: 5.3.11(@swc/core@1.10.6(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1)
|
||||
watchpack: 2.4.2
|
||||
webpack-sources: 3.2.3
|
||||
optionalDependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user