mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(input): initial structure and styles created
This commit is contained in:
parent
3a7579dbf5
commit
1ee0293bb3
@ -41,9 +41,13 @@
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@react-aria/textfield": "^3.9.0"
|
||||
"@nextui-org/use-aria-field": "workspace:*",
|
||||
"@react-aria/focus": "^3.11.0",
|
||||
"@react-aria/utils": "^3.15.0",
|
||||
"@react-stately/utils": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-types/shared": "^3.15.0",
|
||||
"@react-types/textfield": "^3.7.0",
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
|
||||
@ -4,12 +4,33 @@ import {UseInputProps, useInput} from "./use-input";
|
||||
|
||||
export interface InputProps extends Omit<UseInputProps, "ref"> {}
|
||||
|
||||
const Input = forwardRef<InputProps, "div">((props, ref) => {
|
||||
const {Component, domRef, children, styles, ...otherProps} = useInput({ref, ...props});
|
||||
const Input = forwardRef<InputProps, "input">((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
label,
|
||||
description,
|
||||
shouldLabelBeOutside,
|
||||
shouldLabelBeInside,
|
||||
errorMessage,
|
||||
getBaseProps,
|
||||
getLabelProps,
|
||||
getInputProps,
|
||||
getInputWrapperProps,
|
||||
getDescriptionProps,
|
||||
getErrorMessageProps,
|
||||
} = useInput({ref, ...props});
|
||||
|
||||
const labelContent = <label {...getLabelProps()}>{label}</label>;
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={styles} {...otherProps}>
|
||||
{children}
|
||||
<Component {...getBaseProps()}>
|
||||
{shouldLabelBeOutside ? labelContent : null}
|
||||
<div {...getInputWrapperProps()}>
|
||||
{shouldLabelBeInside ? labelContent : null}
|
||||
<input {...getInputProps()} />
|
||||
</div>
|
||||
{description && <div {...getDescriptionProps()}>{description}</div>}
|
||||
{errorMessage && <div {...getErrorMessageProps()}>{errorMessage}</div>}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
166
packages/components/input/src/use-aria-text-field.ts
Normal file
166
packages/components/input/src/use-aria-text-field.ts
Normal file
@ -0,0 +1,166 @@
|
||||
// based on @react-aria/use-textfield hook, but with useId from react 18
|
||||
// thanks to @adobe/react-spectrum for the great work ❤️
|
||||
|
||||
import {AriaTextFieldProps} from "@react-types/textfield";
|
||||
import {
|
||||
ChangeEvent,
|
||||
DOMFactory,
|
||||
HTMLAttributes,
|
||||
LabelHTMLAttributes,
|
||||
ReactDOM,
|
||||
RefObject,
|
||||
} from "react";
|
||||
import {DOMAttributes} from "@react-types/shared";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useAriaField} from "@nextui-org/use-aria-field";
|
||||
import {useFocusable} from "@react-aria/focus";
|
||||
|
||||
/**
|
||||
* A map of HTML element names and their interface types.
|
||||
* For example `'a'` -> `HTMLAnchorElement`.
|
||||
*/
|
||||
type IntrinsicHTMLElements = {
|
||||
[K in keyof IntrinsicHTMLAttributes]: IntrinsicHTMLAttributes[K] extends HTMLAttributes<infer T>
|
||||
? T
|
||||
: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of HTML element names and their attribute interface types.
|
||||
* For example `'a'` -> `AnchorHTMLAttributes<HTMLAnchorElement>`.
|
||||
*/
|
||||
type IntrinsicHTMLAttributes = {
|
||||
[K in keyof ReactDOM]: ReactDOM[K] extends DOMFactory<infer T, any> ? T : never;
|
||||
};
|
||||
|
||||
type DefaultElementType = "input";
|
||||
|
||||
/**
|
||||
* The intrinsic HTML element names that `useTextField` supports; e.g. `input`,
|
||||
* `textarea`.
|
||||
*/
|
||||
type TextFieldIntrinsicElements = keyof Pick<IntrinsicHTMLElements, "input" | "textarea">;
|
||||
|
||||
/**
|
||||
* The HTML element interfaces that `useTextField` supports based on what is
|
||||
* defined for `TextFieldIntrinsicElements`; e.g. `HTMLInputElement`,
|
||||
* `HTMLTextAreaElement`.
|
||||
*/
|
||||
type TextFieldHTMLElementType = Pick<IntrinsicHTMLElements, TextFieldIntrinsicElements>;
|
||||
|
||||
/**
|
||||
* The HTML attributes interfaces that `useTextField` supports based on what
|
||||
* is defined for `TextFieldIntrinsicElements`; e.g. `InputHTMLAttributes`,
|
||||
* `TextareaHTMLAttributes`.
|
||||
*/
|
||||
type TextFieldHTMLAttributesType = Pick<IntrinsicHTMLAttributes, TextFieldIntrinsicElements>;
|
||||
|
||||
/**
|
||||
* The type of `inputProps` returned by `useTextField`; e.g. `InputHTMLAttributes`,
|
||||
* `TextareaHTMLAttributes`.
|
||||
*/
|
||||
type TextFieldInputProps<T extends TextFieldIntrinsicElements> = TextFieldHTMLAttributesType[T];
|
||||
|
||||
export interface AriaTextFieldOptions<T extends TextFieldIntrinsicElements>
|
||||
extends AriaTextFieldProps {
|
||||
/**
|
||||
* The HTML element used to render the input, e.g. 'input', or 'textarea'.
|
||||
* It determines whether certain HTML attributes will be included in `inputProps`.
|
||||
* For example, [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type).
|
||||
* @default 'input'
|
||||
*/
|
||||
inputElementType?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of `ref` object that can be passed to `useTextField` based on the given
|
||||
* intrinsic HTML element name; e.g.`RefObject<HTMLInputElement>`,
|
||||
* `RefObject<HTMLTextAreaElement>`.
|
||||
*/
|
||||
type TextFieldRefObject<T extends TextFieldIntrinsicElements> = RefObject<
|
||||
TextFieldHTMLElementType[T]
|
||||
>;
|
||||
|
||||
export interface TextFieldAria<T extends TextFieldIntrinsicElements = DefaultElementType> {
|
||||
/** Props for the input element. */
|
||||
inputProps: TextFieldInputProps<T>;
|
||||
/** Props for the text field's visible label element, if any. */
|
||||
labelProps: DOMAttributes | LabelHTMLAttributes<HTMLLabelElement>;
|
||||
/** Props for the text field's description element, if any. */
|
||||
descriptionProps: DOMAttributes;
|
||||
/** Props for the text field's error message element, if any. */
|
||||
errorMessageProps: DOMAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the behavior and accessibility implementation for a text field.
|
||||
* @param props - Props for the text field.
|
||||
* @param ref - Ref to the HTML input or textarea element.
|
||||
*/
|
||||
export function useAriaTextField<T extends TextFieldIntrinsicElements = DefaultElementType>(
|
||||
props: AriaTextFieldOptions<T>,
|
||||
ref: TextFieldRefObject<T>,
|
||||
): TextFieldAria<T> {
|
||||
let {
|
||||
inputElementType = "input",
|
||||
isDisabled = false,
|
||||
isRequired = false,
|
||||
isReadOnly = false,
|
||||
validationState,
|
||||
type = "text",
|
||||
onChange = () => {},
|
||||
}: AriaTextFieldOptions<TextFieldIntrinsicElements> = props;
|
||||
let {focusableProps} = useFocusable(props, ref);
|
||||
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useAriaField(props);
|
||||
let domProps = filterDOMProps(props, {labelable: true});
|
||||
|
||||
const inputOnlyProps = {
|
||||
type,
|
||||
pattern: props.pattern,
|
||||
};
|
||||
|
||||
return {
|
||||
labelProps,
|
||||
// @ts-ignore
|
||||
inputProps: mergeProps(domProps, inputElementType === "input" && inputOnlyProps, {
|
||||
disabled: isDisabled,
|
||||
readOnly: isReadOnly,
|
||||
"aria-required": isRequired || undefined,
|
||||
"aria-invalid": validationState === "invalid" || undefined,
|
||||
"aria-errormessage": props["aria-errormessage"],
|
||||
"aria-activedescendant": props["aria-activedescendant"],
|
||||
"aria-autocomplete": props["aria-autocomplete"],
|
||||
"aria-haspopup": props["aria-haspopup"],
|
||||
value: props.value,
|
||||
defaultValue: props.value ? undefined : props.defaultValue,
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value),
|
||||
autoComplete: props.autoComplete,
|
||||
maxLength: props.maxLength,
|
||||
minLength: props.minLength,
|
||||
name: props.name,
|
||||
placeholder: props.placeholder,
|
||||
inputMode: props.inputMode,
|
||||
|
||||
// Clipboard events
|
||||
onCopy: props.onCopy,
|
||||
onCut: props.onCut,
|
||||
onPaste: props.onPaste,
|
||||
|
||||
// Composition events
|
||||
onCompositionEnd: props.onCompositionEnd,
|
||||
onCompositionStart: props.onCompositionStart,
|
||||
onCompositionUpdate: props.onCompositionUpdate,
|
||||
|
||||
// Selection events
|
||||
onSelect: props.onSelect,
|
||||
|
||||
// Input events
|
||||
onBeforeInput: props.onBeforeInput,
|
||||
onInput: props.onInput,
|
||||
...focusableProps,
|
||||
...fieldProps,
|
||||
}),
|
||||
descriptionProps,
|
||||
errorMessageProps,
|
||||
};
|
||||
}
|
||||
@ -1,37 +1,167 @@
|
||||
import type {InputVariantProps} from "@nextui-org/theme";
|
||||
import type {InputVariantProps, SlotsToClasses, InputSlots} from "@nextui-org/theme";
|
||||
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {AriaTextFieldProps} from "@react-types/textfield";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {input} from "@nextui-org/theme";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useControlledState} from "@react-stately/utils";
|
||||
import {useMemo, Ref} from "react";
|
||||
import {chain, mergeProps} from "@react-aria/utils";
|
||||
|
||||
export interface UseInputProps extends HTMLNextUIProps<"div", InputVariantProps> {
|
||||
import {useAriaTextField} from "./use-aria-text-field";
|
||||
|
||||
export interface Props extends HTMLNextUIProps<"input"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
ref?: Ref<HTMLInputElement>;
|
||||
/**
|
||||
* Classname or List of classes to change the styles of the element.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <Chip styles={{
|
||||
* base:"base-classes",
|
||||
* label: "label-classes",
|
||||
* inputWrapper: "input-wrapper-classes",
|
||||
* input: "input-classes",
|
||||
* description: "description-classes",
|
||||
* helperText: "helper-text-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<InputSlots>;
|
||||
}
|
||||
|
||||
export type UseInputProps = Props & AriaTextFieldProps & InputVariantProps;
|
||||
|
||||
export function useInput(originalProps: UseInputProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, input.variantKeys);
|
||||
|
||||
const {ref, as, className, ...otherProps} = props;
|
||||
const {
|
||||
ref,
|
||||
as,
|
||||
label,
|
||||
description,
|
||||
errorMessage,
|
||||
className,
|
||||
styles,
|
||||
autoFocus,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "div";
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
const domRef = useDOMRef<HTMLInputElement>(ref);
|
||||
|
||||
const styles = useMemo(
|
||||
const [inputValue, setInputValue] = useControlledState(props.value, props.defaultValue, () => {});
|
||||
|
||||
const {labelProps, inputProps, descriptionProps, errorMessageProps} = useAriaTextField(
|
||||
{
|
||||
...originalProps,
|
||||
onChange: chain(props.onChange, setInputValue),
|
||||
},
|
||||
domRef,
|
||||
);
|
||||
|
||||
const {isFocusVisible, isFocused, focusProps} = useFocusRing({
|
||||
autoFocus,
|
||||
isTextInput: true,
|
||||
});
|
||||
|
||||
const isInvalid = props.validationState === "invalid";
|
||||
const labelPosition = originalProps.labelPosition || "outside";
|
||||
const isLabelPlaceholder = !props.placeholder;
|
||||
|
||||
const shouldLabelBeOutside = labelPosition === "outside" || labelPosition === "outside-left";
|
||||
const shouldLabelBeInside = labelPosition === "inside";
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
input({
|
||||
...variantProps,
|
||||
className,
|
||||
isInvalid,
|
||||
isLabelPlaceholder,
|
||||
isFocusVisible,
|
||||
}),
|
||||
[...Object.values(variantProps), className],
|
||||
[...Object.values(variantProps), isInvalid, isLabelPlaceholder, isFocusVisible],
|
||||
);
|
||||
|
||||
return {Component, styles, domRef, ...otherProps};
|
||||
const getBaseProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
className: slots.base({class: baseStyles}),
|
||||
"data-focus-visible": dataAttr(isFocusVisible),
|
||||
"data-focused": dataAttr(isFocused),
|
||||
"data-invalid": dataAttr(isInvalid),
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
const getLabelProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
className: slots.label({class: styles?.label}),
|
||||
...labelProps,
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
const getInputProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.input({class: styles?.input}),
|
||||
"data-focus-visible": dataAttr(isFocusVisible),
|
||||
"data-focused": dataAttr(isFocused),
|
||||
"data-invalid": dataAttr(isInvalid),
|
||||
...mergeProps(focusProps, inputProps, otherProps, props),
|
||||
};
|
||||
};
|
||||
|
||||
const getInputWrapperProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
className: slots.inputWrapper({
|
||||
class: clsx(styles?.inputWrapper, !!inputValue ? "is-filled" : ""),
|
||||
}),
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
const getDescriptionProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
className: slots.description({class: styles?.description}),
|
||||
...descriptionProps,
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
const getErrorMessageProps: PropGetter = (props = {}) => {
|
||||
return {
|
||||
className: slots.errorMessage({class: styles?.errorMessage}),
|
||||
...errorMessageProps,
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
Component,
|
||||
styles,
|
||||
domRef,
|
||||
label,
|
||||
description,
|
||||
labelPosition,
|
||||
shouldLabelBeOutside,
|
||||
shouldLabelBeInside,
|
||||
errorMessage,
|
||||
getBaseProps,
|
||||
getLabelProps,
|
||||
getInputProps,
|
||||
getInputWrapperProps,
|
||||
getDescriptionProps,
|
||||
getErrorMessageProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseInputReturn = ReturnType<typeof useInput>;
|
||||
|
||||
@ -8,6 +8,12 @@ export default {
|
||||
title: "Components/Input",
|
||||
component: Input,
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["flat", "faded", "bordered", "underlined"],
|
||||
},
|
||||
},
|
||||
color: {
|
||||
control: {
|
||||
type: "select",
|
||||
@ -26,6 +32,12 @@ export default {
|
||||
options: ["xs", "sm", "md", "lg", "xl"],
|
||||
},
|
||||
},
|
||||
labelPosition: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["outside", "outside-left", "inside"],
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
@ -36,11 +48,37 @@ export default {
|
||||
|
||||
const defaultProps = {
|
||||
...input.defaultVariants,
|
||||
label: "Email",
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Input> = (args: InputProps) => <Input {...args} />;
|
||||
const Template: ComponentStory<typeof Input> = (args: InputProps) => (
|
||||
<div className="max-w-md flex flex-row gap-4">
|
||||
<Input {...args} />
|
||||
<Input {...args} placeholder="Enter your email" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const WithPlaceholder = Template.bind({});
|
||||
WithPlaceholder.args = {
|
||||
...defaultProps,
|
||||
placeholder: "Enter your email",
|
||||
};
|
||||
|
||||
export const WithErrorMessage = Template.bind({});
|
||||
WithErrorMessage.args = {
|
||||
...defaultProps,
|
||||
errorMessage: "Please enter a valid email address",
|
||||
};
|
||||
|
||||
export const InvalidValidationState = Template.bind({});
|
||||
InvalidValidationState.args = {
|
||||
...defaultProps,
|
||||
variant: "bordered",
|
||||
validationState: "invalid",
|
||||
errorMessage: "Please enter a valid email address",
|
||||
};
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isempty": "^4.4.0",
|
||||
"tailwind-variants": "^0.1.1",
|
||||
"tailwind-variants": "^0.1.2",
|
||||
"tailwindcss": "^3.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -22,3 +22,4 @@ export * from "./accordion-item";
|
||||
export * from "./accordion";
|
||||
export * from "./progress";
|
||||
export * from "./circular-progress";
|
||||
export * from "./input";
|
||||
|
||||
608
packages/core/theme/src/components/input.ts
Normal file
608
packages/core/theme/src/components/input.ts
Normal file
@ -0,0 +1,608 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
import {ringClasses} from "../utils";
|
||||
|
||||
/**
|
||||
* Input wrapper **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const {base, label, inputWrapper, input, description, helperText} = input({...})
|
||||
*
|
||||
* <div className={base())}>
|
||||
* <label className={label()}>Label</label>
|
||||
* <div className={inputWrapper()}>
|
||||
* <input className={input()}/>
|
||||
* </div>
|
||||
* <span className={description()}>Description</span>
|
||||
* <span className={helperText()}>Helper text</span>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
const input = tv({
|
||||
slots: {
|
||||
base: "flex flex-col gap-2",
|
||||
label: "block text-sm font-medium text-neutral-600",
|
||||
inputWrapper: "w-full flex flex-row items-center shadow-sm px-3 gap-3",
|
||||
input: "w-full h-full bg-transparent outline-none placeholder:text-neutral-500",
|
||||
description: "text-xs text-neutral-500",
|
||||
errorMessage: "text-xs text-danger",
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
flat: {
|
||||
inputWrapper: ["bg-neutral-100", "hover:bg-neutral-200", "focus-within:!bg-neutral-100"],
|
||||
},
|
||||
faded: {
|
||||
inputWrapper: [
|
||||
"bg-neutral-100",
|
||||
"border",
|
||||
"border-neutral-200",
|
||||
"hover:border-neutral-400",
|
||||
],
|
||||
},
|
||||
bordered: {
|
||||
inputWrapper: [
|
||||
"border-2",
|
||||
"border-neutral-200",
|
||||
"hover:border-neutral-400",
|
||||
"focus-within:!border-foreground",
|
||||
],
|
||||
},
|
||||
underlined: {
|
||||
inputWrapper: [
|
||||
"!px-1",
|
||||
"relative",
|
||||
"box-border",
|
||||
"border-b-2",
|
||||
"shadow-[0_1px_0px_0_rgba(0,0,0,0.05)]",
|
||||
"border-neutral-200",
|
||||
"!rounded-none",
|
||||
"hover:border-neutral-300",
|
||||
"after:content-['']",
|
||||
"after:w-0",
|
||||
"after:origin-center",
|
||||
"after:bg-foreground",
|
||||
"after:absolute",
|
||||
"after:left-1/2",
|
||||
"after:-translate-x-1/2",
|
||||
"after:-bottom-[2px]",
|
||||
"after:h-[2px]",
|
||||
"focus-within:after:w-full",
|
||||
],
|
||||
},
|
||||
},
|
||||
color: {
|
||||
neutral: {},
|
||||
primary: {
|
||||
label: "text-primary",
|
||||
},
|
||||
secondary: {
|
||||
label: "text-secondary",
|
||||
},
|
||||
success: {
|
||||
label: "text-success",
|
||||
},
|
||||
warning: {
|
||||
label: "text-warning",
|
||||
},
|
||||
danger: {
|
||||
label: "text-danger",
|
||||
},
|
||||
},
|
||||
size: {
|
||||
xs: {
|
||||
label: "text-xs",
|
||||
inputWrapper: "h-6 px-1",
|
||||
input: "text-xs",
|
||||
},
|
||||
sm: {
|
||||
label: "text-xs",
|
||||
inputWrapper: "h-8 px-2",
|
||||
input: "text-xs",
|
||||
},
|
||||
md: {
|
||||
inputWrapper: "h-10",
|
||||
input: "text-sm",
|
||||
},
|
||||
lg: {
|
||||
inputWrapper: "h-12",
|
||||
input: "text-base",
|
||||
},
|
||||
xl: {
|
||||
inputWrapper: "h-14",
|
||||
input: "text-md",
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
none: {
|
||||
inputWrapper: "rounded-none",
|
||||
},
|
||||
base: {
|
||||
inputWrapper: "rounded",
|
||||
},
|
||||
sm: {
|
||||
inputWrapper: "rounded-sm",
|
||||
},
|
||||
md: {
|
||||
inputWrapper: "rounded-md",
|
||||
},
|
||||
lg: {
|
||||
inputWrapper: "rounded-lg",
|
||||
},
|
||||
xl: {
|
||||
inputWrapper: "rounded-xl",
|
||||
},
|
||||
full: {
|
||||
inputWrapper: "rounded-full",
|
||||
},
|
||||
},
|
||||
labelPosition: {
|
||||
outside: {},
|
||||
"outside-left": {
|
||||
base: "flex-row items-center",
|
||||
},
|
||||
inside: {
|
||||
label: "text-xs",
|
||||
inputWrapper: "flex-col items-start justify-center gap-0",
|
||||
},
|
||||
},
|
||||
fullWidth: {
|
||||
true: {
|
||||
base: "w-full",
|
||||
},
|
||||
},
|
||||
isLabelPlaceholder: {
|
||||
true: {
|
||||
label: "absolute",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
base: "opacity-50 pointer-events-none",
|
||||
},
|
||||
},
|
||||
isFocusVisible: {
|
||||
true: {},
|
||||
},
|
||||
isInvalid: {
|
||||
true: {
|
||||
label: "text-danger",
|
||||
input: "placeholder:text-danger",
|
||||
},
|
||||
},
|
||||
disableAnimation: {
|
||||
true: {
|
||||
inputWrapper: "transition-none",
|
||||
label: "transition-none",
|
||||
},
|
||||
false: {
|
||||
inputWrapper: "transition-background motion-reduce:transition-none",
|
||||
label: [
|
||||
"will-change-auto",
|
||||
"transition-all",
|
||||
"!duration-200",
|
||||
"motion-reduce:transition-none",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "flat",
|
||||
color: "neutral",
|
||||
size: "md",
|
||||
radius: "xl",
|
||||
fullWidth: true,
|
||||
labelPosition: "outside",
|
||||
isDisabled: false,
|
||||
disableAnimation: false,
|
||||
},
|
||||
compoundVariants: [
|
||||
// flat & faded & color
|
||||
{
|
||||
variant: "flat",
|
||||
color: "primary",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-primary-50",
|
||||
"hover:bg-primary-100",
|
||||
"text-primary",
|
||||
"focus-within:!bg-primary-50",
|
||||
"placeholder:text-primary",
|
||||
],
|
||||
input: "placeholder:text-primary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "flat",
|
||||
color: "secondary",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-secondary-50",
|
||||
"hover:bg-secondary-100",
|
||||
"text-secondary",
|
||||
"focus-within:!bg-secondary-50",
|
||||
"placeholder:text-secondary",
|
||||
],
|
||||
input: "placeholder:text-secondary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "flat",
|
||||
color: "success",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-success-50",
|
||||
"hover:bg-success-100",
|
||||
"text-success",
|
||||
"focus-within:!bg-success-50",
|
||||
"placeholder:text-success",
|
||||
],
|
||||
input: "placeholder:text-success",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "flat",
|
||||
color: "warning",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-warning-50",
|
||||
"hover:bg-warning-100",
|
||||
"text-warning",
|
||||
"focus-within:!bg-warning-50",
|
||||
"placeholder:text-warning",
|
||||
],
|
||||
input: "placeholder:text-warning",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "flat",
|
||||
color: "danger",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-danger-50",
|
||||
"hover:bg-danger-100",
|
||||
"text-danger",
|
||||
"focus-within:!bg-danger-50",
|
||||
"placeholder:text-danger",
|
||||
],
|
||||
input: "placeholder:text-danger",
|
||||
},
|
||||
},
|
||||
// faded & color
|
||||
{
|
||||
variant: "faded",
|
||||
color: "primary",
|
||||
class: {
|
||||
inputWrapper: "text-primary",
|
||||
input: "placeholder:text-primary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "faded",
|
||||
color: "secondary",
|
||||
class: {
|
||||
inputWrapper: "text-secondary",
|
||||
input: "placeholder:text-secondary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "faded",
|
||||
color: "success",
|
||||
class: {
|
||||
inputWrapper: "text-success",
|
||||
input: "placeholder:text-success",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "faded",
|
||||
color: "warning",
|
||||
class: {
|
||||
inputWrapper: "text-warning",
|
||||
input: "placeholder:text-warning",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "faded",
|
||||
color: "danger",
|
||||
class: {
|
||||
inputWrapper: "text-danger",
|
||||
input: "placeholder:text-danger",
|
||||
},
|
||||
},
|
||||
// underlined & color
|
||||
{
|
||||
variant: "underlined",
|
||||
color: "primary",
|
||||
class: {
|
||||
inputWrapper: "after:bg-primary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "underlined",
|
||||
color: "secondary",
|
||||
class: {
|
||||
inputWrapper: "after:bg-secondary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "underlined",
|
||||
color: "success",
|
||||
class: {
|
||||
inputWrapper: "after:bg-success",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "underlined",
|
||||
color: "warning",
|
||||
class: {
|
||||
inputWrapper: "after:bg-warning",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "underlined",
|
||||
color: "danger",
|
||||
class: {
|
||||
inputWrapper: "after:bg-danger",
|
||||
},
|
||||
},
|
||||
// bordered & color
|
||||
{
|
||||
variant: "bordered",
|
||||
color: "primary",
|
||||
class: {
|
||||
inputWrapper: "focus-within:!border-primary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "bordered",
|
||||
color: "secondary",
|
||||
class: {
|
||||
inputWrapper: "focus-within:!border-secondary",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "bordered",
|
||||
color: "success",
|
||||
class: {
|
||||
inputWrapper: "focus-within:!border-success",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "bordered",
|
||||
color: "warning",
|
||||
class: {
|
||||
inputWrapper: "focus-within:!border-warning",
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: "bordered",
|
||||
color: "danger",
|
||||
class: {
|
||||
inputWrapper: "focus-within:!border-danger",
|
||||
},
|
||||
},
|
||||
// radius-full & size
|
||||
{
|
||||
radius: "full",
|
||||
size: ["xs", "sm"],
|
||||
class: {
|
||||
inputWrapper: "px-3",
|
||||
},
|
||||
},
|
||||
{
|
||||
radius: "full",
|
||||
size: "md",
|
||||
class: {
|
||||
inputWrapper: "px-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
radius: "full",
|
||||
size: "lg",
|
||||
class: {
|
||||
inputWrapper: "px-5",
|
||||
},
|
||||
},
|
||||
{
|
||||
radius: "full",
|
||||
size: "xl",
|
||||
class: {
|
||||
inputWrapper: "px-6",
|
||||
},
|
||||
},
|
||||
// !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",
|
||||
},
|
||||
},
|
||||
// isFocusVisible & variant
|
||||
{
|
||||
isFocusVisible: true,
|
||||
variant: ["flat", "faded"],
|
||||
class: {
|
||||
inputWrapper: [...ringClasses],
|
||||
},
|
||||
},
|
||||
// isInvalid & variant
|
||||
{
|
||||
isInvalid: true,
|
||||
variant: "flat",
|
||||
class: {
|
||||
inputWrapper: [
|
||||
"bg-danger-50",
|
||||
"hover:bg-danger-100",
|
||||
"text-danger",
|
||||
"focus-within:!bg-danger-50",
|
||||
"placeholder:text-danger",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
isInvalid: true,
|
||||
variant: "faded",
|
||||
class: {
|
||||
inputWrapper: "text-danger",
|
||||
},
|
||||
},
|
||||
{
|
||||
isInvalid: true,
|
||||
variant: "bordered",
|
||||
class: {
|
||||
inputWrapper: "!border-danger focus-within:!border-danger",
|
||||
},
|
||||
},
|
||||
{
|
||||
isInvalid: true,
|
||||
variant: "underlined",
|
||||
class: {
|
||||
inputWrapper: "after:bg-danger",
|
||||
},
|
||||
},
|
||||
// size & labelPosition
|
||||
{
|
||||
labelPosition: "inside",
|
||||
size: "xs",
|
||||
class: {
|
||||
label: "text-[0.6rem]",
|
||||
inputWrapper: "h-10 py-1 px-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
labelPosition: "inside",
|
||||
size: "sm",
|
||||
class: {
|
||||
inputWrapper: "h-12 py-1.5 px-3",
|
||||
},
|
||||
},
|
||||
{
|
||||
labelPosition: "inside",
|
||||
size: "md",
|
||||
class: {
|
||||
inputWrapper: "h-14 py-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
labelPosition: "inside",
|
||||
size: "lg",
|
||||
class: {
|
||||
label: "text-sm",
|
||||
inputWrapper: "h-16 py-2.5 gap-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
labelPosition: "inside",
|
||||
size: "xl",
|
||||
class: {
|
||||
label: "text-sm",
|
||||
inputWrapper: "h-20 p-4 gap-2",
|
||||
},
|
||||
},
|
||||
// !isLabelPlaceholder & labelPosition
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
class: {
|
||||
inputWrapper: "group",
|
||||
label: [
|
||||
"font-normal",
|
||||
"text-neutral-500",
|
||||
"group-focus-within:font-medium",
|
||||
"group-focus-within:text-neutral-600",
|
||||
"group-[.is-filled]:font-medium",
|
||||
"group-[.is-filled]:text-neutral-600",
|
||||
],
|
||||
},
|
||||
},
|
||||
// isLabelPlaceholder & labelPosition & size
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: "xs",
|
||||
class: {
|
||||
label: [
|
||||
"text-xs",
|
||||
"group-focus-within:text-[0.6rem]",
|
||||
"group-focus-within:-translate-y-2",
|
||||
"group-[.is-filled]:text-[0.6rem]",
|
||||
"group-[.is-filled]:-translate-y-2",
|
||||
],
|
||||
input: "pt-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: ["sm", "md"],
|
||||
class: {
|
||||
label: ["text-sm", "group-focus-within:text-xs", "group-[.is-filled]:text-xs"],
|
||||
input: "pt-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: "sm",
|
||||
class: {
|
||||
label: ["group-focus-within:-translate-y-2.5", "group-[.is-filled]:-translate-y-2.5"],
|
||||
input: "pt-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: "md",
|
||||
class: {
|
||||
label: ["group-focus-within:-translate-y-3", "group-[.is-filled]:-translate-y-3"],
|
||||
input: "pt-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: "lg",
|
||||
class: {
|
||||
label: [
|
||||
"text-base",
|
||||
"group-focus-within:text-sm",
|
||||
"group-focus-within:-translate-y-3",
|
||||
"group-[.is-filled]:text-sm",
|
||||
"group-[.is-filled]:-translate-y-3",
|
||||
],
|
||||
input: "pt-6",
|
||||
},
|
||||
},
|
||||
{
|
||||
isLabelPlaceholder: true,
|
||||
labelPosition: "inside",
|
||||
size: "xl",
|
||||
class: {
|
||||
label: [
|
||||
"text-md",
|
||||
"group-focus-within:text-sm",
|
||||
"group-focus-within:-translate-y-3",
|
||||
"group-[.is-filled]:text-sm",
|
||||
"group-[.is-filled]:-translate-y-3",
|
||||
],
|
||||
input: "pt-8",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export type InputVariantProps = VariantProps<typeof input>;
|
||||
export type InputSlots = keyof ReturnType<typeof input>;
|
||||
|
||||
export {input};
|
||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@ -831,10 +831,22 @@ importers:
|
||||
'@nextui-org/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/theme
|
||||
'@react-aria/textfield':
|
||||
specifier: ^3.9.0
|
||||
version: 3.9.0(react@18.2.0)
|
||||
'@nextui-org/use-aria-field':
|
||||
specifier: workspace:*
|
||||
version: link:../../hooks/use-aria-field
|
||||
'@react-aria/focus':
|
||||
specifier: ^3.11.0
|
||||
version: 3.11.0(react@18.2.0)
|
||||
'@react-aria/utils':
|
||||
specifier: ^3.15.0
|
||||
version: 3.15.0(react@18.2.0)
|
||||
'@react-stately/utils':
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@react-types/shared':
|
||||
specifier: ^3.15.0
|
||||
version: 3.17.0(react@18.2.0)
|
||||
'@react-types/textfield':
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0(react@18.2.0)
|
||||
@ -1303,8 +1315,8 @@ importers:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
tailwind-variants:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1(tailwindcss@3.2.7)
|
||||
specifier: ^0.1.2
|
||||
version: 0.1.2(tailwindcss@3.2.7)
|
||||
tailwindcss:
|
||||
specifier: ^3.2.7
|
||||
version: 3.2.7(postcss@8.4.21)(ts-node@10.9.1)
|
||||
@ -5910,20 +5922,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-aria/textfield@3.9.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-plX+/RDidTpz4kfQni2mnH10g9iARC5P7oi4XBXqwrVCIqpTUNoyGLUH952wObYOI9k7lG2QG0+b+3GyrV159g==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-aria/focus': 3.11.0(react@18.2.0)
|
||||
'@react-aria/label': 3.5.0(react@18.2.0)
|
||||
'@react-aria/utils': 3.15.0(react@18.2.0)
|
||||
'@react-types/shared': 3.17.0(react@18.2.0)
|
||||
'@react-types/textfield': 3.7.0(react@18.2.0)
|
||||
'@swc/helpers': 0.4.14
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-aria/toggle@3.5.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-K49OmHBmYW8pk0rXJU1TNRzR+PxLVvfL/ni6ifV5gcxoxV6DmFsNFj+5B/U3AMnCEQeyKQeiY6z9X7EBVX6j9Q==}
|
||||
peerDependencies:
|
||||
@ -6220,6 +6218,7 @@ packages:
|
||||
dependencies:
|
||||
'@react-types/shared': 3.17.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@react-types/tooltip@3.3.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-TMaKkjYbysZbMnY8zjI2Djh8QMHvB8LoN9EjOZX++3ZsO74CeCeOoGARh6+4c0Bu5rg4BXhNyi+y1JL3jtUEBg==}
|
||||
@ -21550,8 +21549,8 @@ packages:
|
||||
resolution: {integrity: sha512-oG3/328Y7LrfPMfkgMNxoqEk1ZQdXBxdphf9FFrreo8q0EtVIHt3bQf2IyFhQuVt8puB57lCRBNbazGhYAyz9w==}
|
||||
dev: false
|
||||
|
||||
/tailwind-variants@0.1.1(tailwindcss@3.2.7):
|
||||
resolution: {integrity: sha512-kW9kXmVv9ankGydv36pg9M7fwgQfN/DZ3JlqFfmuIDPRa1/psm3KbTcB4vgEBiku7VtGQBm6oy3EsTJUYJKsLg==}
|
||||
/tailwind-variants@0.1.2(tailwindcss@3.2.7):
|
||||
resolution: {integrity: sha512-MOTaBS5Hg7RN/M0tlMZCUD1crDGPlHe7XsFS7+I5bc4hfwQPVuTiPiRqivW9xH/0QvtuVo8AlEwvR2uxSC3phw==}
|
||||
engines: {node: '>=16.x', pnpm: '>=7.x'}
|
||||
peerDependencies:
|
||||
tailwindcss: '*'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user