mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(components): radio & checkbox improved, custom examples added to the stories
This commit is contained in:
parent
2545eb7898
commit
aba2e459d6
@ -1,6 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {render} from "@testing-library/react";
|
import {act, render} from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
|
|
||||||
import {Button} from "../src";
|
import {Button} from "../src";
|
||||||
|
|
||||||
@ -22,7 +21,10 @@ describe("Button", () => {
|
|||||||
const onPress = jest.fn();
|
const onPress = jest.fn();
|
||||||
const {getByRole} = render(<Button onPress={onPress} />);
|
const {getByRole} = render(<Button onPress={onPress} />);
|
||||||
|
|
||||||
getByRole("button").click();
|
act(() => {
|
||||||
|
getByRole("button").click();
|
||||||
|
});
|
||||||
|
|
||||||
expect(onPress).toHaveBeenCalled();
|
expect(onPress).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,9 +34,9 @@ describe("Button", () => {
|
|||||||
|
|
||||||
const wrapper = render(<Button onClick={onClick} />);
|
const wrapper = render(<Button onClick={onClick} />);
|
||||||
|
|
||||||
let button = wrapper.getByRole("button");
|
act(() => {
|
||||||
|
wrapper.getByRole("button").click();
|
||||||
userEvent.click(button);
|
});
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
@ -44,7 +46,10 @@ describe("Button", () => {
|
|||||||
const onPress = jest.fn();
|
const onPress = jest.fn();
|
||||||
const {getByRole} = render(<Button disabled onPress={onPress} />);
|
const {getByRole} = render(<Button disabled onPress={onPress} />);
|
||||||
|
|
||||||
getByRole("button").click();
|
act(() => {
|
||||||
|
getByRole("button").click();
|
||||||
|
});
|
||||||
|
|
||||||
expect(onPress).not.toHaveBeenCalled();
|
expect(onPress).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,10 @@
|
|||||||
"@react-aria/utils": "^3.15.0"
|
"@react-aria/utils": "^3.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nextui-org/shared-icons": "workspace:*",
|
||||||
|
"@nextui-org/chip": "workspace:*",
|
||||||
|
"@nextui-org/user": "workspace:*",
|
||||||
|
"@nextui-org/link": "workspace:*",
|
||||||
"@react-types/checkbox": "^3.4.2",
|
"@react-types/checkbox": "^3.4.2",
|
||||||
"@react-types/shared": "^3.17.0",
|
"@react-types/shared": "^3.17.0",
|
||||||
"clean-package": "2.2.0",
|
"clean-package": "2.2.0",
|
||||||
|
|||||||
@ -23,7 +23,10 @@ const Checkbox = forwardRef<CheckboxProps, "label">((props, ref) => {
|
|||||||
...props,
|
...props,
|
||||||
});
|
});
|
||||||
|
|
||||||
const clonedIcon = cloneElement(icon as ReactElement, getIconProps());
|
const clonedIcon =
|
||||||
|
typeof icon === "function"
|
||||||
|
? icon(getIconProps())
|
||||||
|
: cloneElement(icon as ReactElement, getIconProps());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component {...getBaseProps()}>
|
<Component {...getBaseProps()}>
|
||||||
|
|||||||
@ -8,6 +8,10 @@ export {useCheckboxGroup} from "./use-checkbox-group";
|
|||||||
// export types
|
// export types
|
||||||
export type {CheckboxProps} from "./checkbox";
|
export type {CheckboxProps} from "./checkbox";
|
||||||
export type {CheckboxGroupProps} from "./checkbox-group";
|
export type {CheckboxGroupProps} from "./checkbox-group";
|
||||||
|
export type {CheckboxIconProps} from "./use-checkbox";
|
||||||
|
|
||||||
|
// export context
|
||||||
|
export * from "./checkbox-group-context";
|
||||||
|
|
||||||
// export components
|
// export components
|
||||||
export {Checkbox, CheckboxGroup};
|
export {Checkbox, CheckboxGroup};
|
||||||
|
|||||||
@ -19,6 +19,14 @@ import {FocusableRef} from "@react-types/shared";
|
|||||||
|
|
||||||
import {useCheckboxGroupContext} from "./checkbox-group-context";
|
import {useCheckboxGroupContext} from "./checkbox-group-context";
|
||||||
|
|
||||||
|
export type CheckboxIconProps = {
|
||||||
|
"data-checked": string;
|
||||||
|
isSelected: boolean;
|
||||||
|
isIndeterminate: boolean;
|
||||||
|
disableAnimation: boolean;
|
||||||
|
className: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface Props extends HTMLNextUIProps<"label"> {
|
interface Props extends HTMLNextUIProps<"label"> {
|
||||||
/**
|
/**
|
||||||
* Ref to the DOM node.
|
* Ref to the DOM node.
|
||||||
@ -36,7 +44,7 @@ interface Props extends HTMLNextUIProps<"label"> {
|
|||||||
/**
|
/**
|
||||||
* The icon to be displayed when the checkbox is checked.
|
* The icon to be displayed when the checkbox is checked.
|
||||||
*/
|
*/
|
||||||
icon?: ReactNode;
|
icon?: ReactNode | ((props: CheckboxIconProps) => ReactNode);
|
||||||
/**
|
/**
|
||||||
* Classname or List of classes to change the styles of the element.
|
* Classname or List of classes to change the styles of the element.
|
||||||
* if `className` is passed, it will be added to the base slot.
|
* if `className` is passed, it will be added to the base slot.
|
||||||
@ -54,7 +62,7 @@ interface Props extends HTMLNextUIProps<"label"> {
|
|||||||
styles?: SlotsToClasses<CheckboxSlots>;
|
styles?: SlotsToClasses<CheckboxSlots>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseCheckboxProps = Omit<Props, "defaultChecked"> &
|
export type UseCheckboxProps = Omit<Props, "defaultChecked" | "onChange"> &
|
||||||
Omit<AriaCheckboxProps, keyof CheckboxVariantProps> &
|
Omit<AriaCheckboxProps, keyof CheckboxVariantProps> &
|
||||||
Omit<CheckboxVariantProps, "isFocusVisible">;
|
Omit<CheckboxVariantProps, "isFocusVisible">;
|
||||||
|
|
||||||
@ -65,11 +73,13 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
const {
|
const {
|
||||||
as,
|
as,
|
||||||
ref,
|
ref,
|
||||||
isSelected,
|
|
||||||
value = "",
|
value = "",
|
||||||
children,
|
children,
|
||||||
icon,
|
icon,
|
||||||
|
name,
|
||||||
isRequired = false,
|
isRequired = false,
|
||||||
|
isReadOnly = false,
|
||||||
|
isSelected: isSelectedProp,
|
||||||
size = groupContext?.size ?? "md",
|
size = groupContext?.size ?? "md",
|
||||||
color = groupContext?.color ?? "primary",
|
color = groupContext?.color ?? "primary",
|
||||||
radius = groupContext?.radius ?? "md",
|
radius = groupContext?.radius ?? "md",
|
||||||
@ -77,6 +87,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
isDisabled = groupContext?.isDisabled ?? false,
|
isDisabled = groupContext?.isDisabled ?? false,
|
||||||
disableAnimation = groupContext?.disableAnimation ?? false,
|
disableAnimation = groupContext?.disableAnimation ?? false,
|
||||||
isIndeterminate = false,
|
isIndeterminate = false,
|
||||||
|
validationState,
|
||||||
defaultSelected,
|
defaultSelected,
|
||||||
styles,
|
styles,
|
||||||
onChange,
|
onChange,
|
||||||
@ -85,7 +96,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (groupContext && __DEV__) {
|
if (groupContext && __DEV__) {
|
||||||
if (isSelected) {
|
if (isSelectedProp) {
|
||||||
warn(
|
warn(
|
||||||
"The Checkbox.Group is being used, `isSelected` will be ignored. Use the `value` of the Checkbox.Group instead.",
|
"The Checkbox.Group is being used, `isSelected` will be ignored. Use the `value` of the Checkbox.Group instead.",
|
||||||
"Checkbox",
|
"Checkbox",
|
||||||
@ -105,34 +116,50 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
const domRef = useFocusableRef(ref as FocusableRef<HTMLLabelElement>, inputRef);
|
const domRef = useFocusableRef(ref as FocusableRef<HTMLLabelElement>, inputRef);
|
||||||
|
|
||||||
const ariaCheckboxProps = useMemo(() => {
|
const ariaCheckboxProps = useMemo(() => {
|
||||||
const arialabel =
|
|
||||||
otherProps["aria-label"] || typeof children === "string" ? (children as string) : undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
name,
|
||||||
value,
|
value,
|
||||||
|
children,
|
||||||
defaultSelected,
|
defaultSelected,
|
||||||
isSelected,
|
isSelected: isSelectedProp,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isIndeterminate,
|
isIndeterminate,
|
||||||
isRequired,
|
isRequired,
|
||||||
|
isReadOnly,
|
||||||
|
validationState,
|
||||||
|
"aria-label": otherProps["aria-label"],
|
||||||
|
"aria-labelledby": otherProps["aria-labelledby"],
|
||||||
onChange,
|
onChange,
|
||||||
"aria-label": arialabel,
|
|
||||||
"aria-labelledby": otherProps["aria-labelledby"] || arialabel,
|
|
||||||
};
|
};
|
||||||
}, [isIndeterminate, isDisabled]);
|
}, [
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
children,
|
||||||
|
isIndeterminate,
|
||||||
|
isDisabled,
|
||||||
|
isSelectedProp,
|
||||||
|
defaultSelected,
|
||||||
|
validationState,
|
||||||
|
otherProps["aria-label"],
|
||||||
|
otherProps["aria-labelledby"],
|
||||||
|
onChange,
|
||||||
|
]);
|
||||||
|
|
||||||
const {inputProps} = isInGroup
|
const {inputProps} = isInGroup
|
||||||
? // eslint-disable-next-line
|
? // eslint-disable-next-line
|
||||||
useReactAriaCheckboxGroupItem(
|
useReactAriaCheckboxGroupItem(
|
||||||
{
|
{
|
||||||
...ariaCheckboxProps,
|
...ariaCheckboxProps,
|
||||||
validationState: otherProps.validationState,
|
validationState,
|
||||||
},
|
},
|
||||||
groupContext.groupState,
|
groupContext.groupState,
|
||||||
inputRef,
|
inputRef,
|
||||||
)
|
)
|
||||||
: useReactAriaCheckbox(ariaCheckboxProps, useToggleState(ariaCheckboxProps), inputRef); // eslint-disable-line
|
: useReactAriaCheckbox(ariaCheckboxProps, useToggleState(ariaCheckboxProps), inputRef); // eslint-disable-line
|
||||||
|
|
||||||
|
const isSelected = inputProps.checked;
|
||||||
|
const isInvalid = useMemo(() => validationState === "invalid", [validationState]);
|
||||||
|
|
||||||
if (isRequired) {
|
if (isRequired) {
|
||||||
inputProps.required = true;
|
inputProps.required = true;
|
||||||
}
|
}
|
||||||
@ -166,8 +193,8 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
ref: domRef,
|
ref: domRef,
|
||||||
className: slots.base({class: baseStyles}),
|
className: slots.base({class: baseStyles}),
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
|
"data-invalid": dataAttr(isInvalid),
|
||||||
...mergeProps(hoverProps, otherProps),
|
...mergeProps(hoverProps, otherProps),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -175,12 +202,12 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
const getWrapperProps: PropGetter = () => {
|
const getWrapperProps: PropGetter = () => {
|
||||||
return {
|
return {
|
||||||
"data-hover": dataAttr(isHovered),
|
"data-hover": dataAttr(isHovered),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-focus": dataAttr(isFocused),
|
"data-focus": dataAttr(isFocused),
|
||||||
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
|
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
|
||||||
"data-indeterminate": dataAttr(isIndeterminate),
|
"data-indeterminate": dataAttr(isIndeterminate),
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
|
"data-invalid": dataAttr(isInvalid),
|
||||||
"data-readonly": dataAttr(inputProps.readOnly),
|
"data-readonly": dataAttr(inputProps.readOnly),
|
||||||
"aria-hidden": true,
|
"aria-hidden": true,
|
||||||
className: clsx(slots.wrapper({class: styles?.wrapper})),
|
className: clsx(slots.wrapper({class: styles?.wrapper})),
|
||||||
@ -197,28 +224,32 @@ export function useCheckbox(props: UseCheckboxProps) {
|
|||||||
const getLabelProps: PropGetter = useCallback(
|
const getLabelProps: PropGetter = useCallback(
|
||||||
() => ({
|
() => ({
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
|
"data-invalid": dataAttr(isInvalid),
|
||||||
className: slots.label({class: styles?.label}),
|
className: slots.label({class: styles?.label}),
|
||||||
}),
|
}),
|
||||||
[slots, isDisabled, inputProps.checked, otherProps.validationState],
|
[slots, styles?.label, isDisabled, isSelected, isInvalid],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getIconProps: PropGetter = useCallback(
|
const getIconProps = useCallback(
|
||||||
() => ({
|
() =>
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
({
|
||||||
isSelected: inputProps.checked,
|
"data-checked": dataAttr(isSelected),
|
||||||
isIndeterminate: !!isIndeterminate,
|
isSelected: isSelected,
|
||||||
disableAnimation: !!disableAnimation,
|
isIndeterminate: !!isIndeterminate,
|
||||||
className: slots.icon({class: styles?.icon}),
|
disableAnimation: !!disableAnimation,
|
||||||
}),
|
className: slots.icon({class: styles?.icon}),
|
||||||
[slots, inputProps.checked, isIndeterminate, disableAnimation],
|
} as CheckboxIconProps),
|
||||||
|
[slots, styles?.icon, isSelected, isIndeterminate, disableAnimation],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Component,
|
Component,
|
||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
|
isSelected,
|
||||||
|
isDisabled,
|
||||||
|
isInvalid,
|
||||||
getBaseProps,
|
getBaseProps,
|
||||||
getWrapperProps,
|
getWrapperProps,
|
||||||
getInputProps,
|
getInputProps,
|
||||||
|
|||||||
@ -4,6 +4,11 @@ import {checkbox} from "@nextui-org/theme";
|
|||||||
|
|
||||||
import {CheckboxGroup, Checkbox, CheckboxGroupProps} from "../src";
|
import {CheckboxGroup, Checkbox, CheckboxGroupProps} from "../src";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CustomWithStyles as CheckboxItemWithStyles,
|
||||||
|
CustomWithHooks as CheckboxItemWithHooks,
|
||||||
|
} from "./checkbox.stories";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Inputs/CheckboxGroup",
|
title: "Inputs/CheckboxGroup",
|
||||||
component: CheckboxGroup,
|
component: CheckboxGroup,
|
||||||
@ -121,25 +126,58 @@ export const Controlled = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Group = () => {
|
export const CustomWithStyles = () => {
|
||||||
// eslint-disable-next-line no-console
|
const [groupSelected, setGroupSelected] = React.useState<string[]>([]);
|
||||||
const handleGroupChange = (value: string[]) => console.log(value);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CheckboxGroup
|
<>
|
||||||
color="warning"
|
<CheckboxGroup label="Select employees" value={groupSelected} onChange={setGroupSelected}>
|
||||||
defaultValue={["buenos-aires"]}
|
<CheckboxItemWithStyles value="junior" />
|
||||||
label="Select cities"
|
<CheckboxItemWithStyles
|
||||||
onChange={handleGroupChange}
|
userName="John Doe"
|
||||||
>
|
userProfile={{
|
||||||
<Checkbox color="primary" value="buenos-aires">
|
avatar: "https://i.pravatar.cc/300?u=a042581f4e29026707d",
|
||||||
Buenos Aires
|
username: "johndoe",
|
||||||
</Checkbox>
|
url: "#",
|
||||||
<Checkbox value="sydney">Sydney</Checkbox>
|
}}
|
||||||
<Checkbox isDisabled value="london">
|
userRole="Product Designer"
|
||||||
London
|
value="johndoe"
|
||||||
</Checkbox>
|
/>
|
||||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
<CheckboxItemWithStyles
|
||||||
|
userName="Zoey Lang"
|
||||||
|
userProfile={{
|
||||||
|
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
|
||||||
|
username: "zoeylang",
|
||||||
|
url: "#",
|
||||||
|
}}
|
||||||
|
userRole="Technical Writer"
|
||||||
|
value="zoeylang"
|
||||||
|
/>
|
||||||
|
<CheckboxItemWithStyles
|
||||||
|
userName="William Howard"
|
||||||
|
userProfile={{
|
||||||
|
avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d",
|
||||||
|
username: "william",
|
||||||
|
url: "#",
|
||||||
|
}}
|
||||||
|
userRole="Sales Manager"
|
||||||
|
value="william"
|
||||||
|
/>
|
||||||
|
</CheckboxGroup>
|
||||||
|
<p className="mt-4 ml-1 text-neutral-500">Selected: {groupSelected.join(", ")}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomWithHooks = () => {
|
||||||
|
return (
|
||||||
|
<CheckboxGroup className="gap-1" label="Select ammenities" orientation="horizontal">
|
||||||
|
<CheckboxItemWithHooks value="wifi">Wifi</CheckboxItemWithHooks>
|
||||||
|
<CheckboxItemWithHooks value="tv">TV</CheckboxItemWithHooks>
|
||||||
|
<CheckboxItemWithHooks value="kitchen">Kitchen</CheckboxItemWithHooks>
|
||||||
|
<CheckboxItemWithHooks value="parking">Parking</CheckboxItemWithHooks>
|
||||||
|
<CheckboxItemWithHooks value="pool">Pool</CheckboxItemWithHooks>
|
||||||
|
<CheckboxItemWithHooks value="gym">Gym</CheckboxItemWithHooks>
|
||||||
</CheckboxGroup>
|
</CheckboxGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||||
import {checkbox} from "@nextui-org/theme";
|
import {checkbox, colors} from "@nextui-org/theme";
|
||||||
|
import {CheckIcon, CloseIcon} from "@nextui-org/shared-icons";
|
||||||
|
import {User} from "@nextui-org/user";
|
||||||
|
import {Link} from "@nextui-org/link";
|
||||||
|
import {Chip, ChipProps} from "@nextui-org/chip";
|
||||||
|
import {clsx} from "@nextui-org/shared-utils";
|
||||||
|
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||||
|
|
||||||
import {Checkbox, CheckboxProps} from "../src";
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxIconProps,
|
||||||
|
CheckboxProps,
|
||||||
|
useCheckbox,
|
||||||
|
useCheckboxGroupContext,
|
||||||
|
} from "../src";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Inputs/Checkbox",
|
title: "Inputs/Checkbox",
|
||||||
@ -39,7 +51,7 @@ export default {
|
|||||||
},
|
},
|
||||||
} as ComponentMeta<typeof Checkbox>;
|
} as ComponentMeta<typeof Checkbox>;
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps: CheckboxProps = {
|
||||||
...checkbox.defaultVariants,
|
...checkbox.defaultVariants,
|
||||||
children: "Option",
|
children: "Option",
|
||||||
};
|
};
|
||||||
@ -63,6 +75,18 @@ DefaultSelected.args = {
|
|||||||
defaultSelected: true,
|
defaultSelected: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CustomIconNode = Template.bind({});
|
||||||
|
CustomIconNode.args = {
|
||||||
|
...defaultProps,
|
||||||
|
icon: <CloseIcon />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomIconFunction = Template.bind({});
|
||||||
|
CustomIconFunction.args = {
|
||||||
|
...defaultProps,
|
||||||
|
icon: (props: CheckboxIconProps) => <CloseIcon {...props} />,
|
||||||
|
};
|
||||||
|
|
||||||
export const AlwaysSelected = Template.bind({});
|
export const AlwaysSelected = Template.bind({});
|
||||||
AlwaysSelected.args = {
|
AlwaysSelected.args = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
@ -103,3 +127,112 @@ export const Controlled = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CustomCheckboxProps extends CheckboxProps {
|
||||||
|
userName?: string;
|
||||||
|
userProfile?: {
|
||||||
|
username?: string;
|
||||||
|
avatar?: string;
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
userRole?: string;
|
||||||
|
status?: string;
|
||||||
|
statusColor?: ChipProps["color"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomWithStyles = (props: CustomCheckboxProps) => {
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
userName = "Junior Garcia",
|
||||||
|
userProfile = {
|
||||||
|
avatar: "https://avatars.githubusercontent.com/u/30373425?v=4",
|
||||||
|
username: "jrgarciadev",
|
||||||
|
url: "https://twitter.com/jrgarciadev",
|
||||||
|
},
|
||||||
|
userRole = "Software Developer",
|
||||||
|
status = "Active",
|
||||||
|
statusColor = "secondary",
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const groupContext = useCheckboxGroupContext();
|
||||||
|
const isInGroup = !!groupContext;
|
||||||
|
|
||||||
|
const [isSelected, setIsSelected] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const checkboxProps = !isInGroup
|
||||||
|
? {
|
||||||
|
isSelected,
|
||||||
|
onChange: setIsSelected,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const isChecked = isInGroup && value ? groupContext?.groupState.isSelected(value) : isSelected;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
{...otherProps}
|
||||||
|
aria-label={userName}
|
||||||
|
styles={{
|
||||||
|
base: clsx(
|
||||||
|
"inline-flex w-full max-w-md bg-content1 hover:bg-content2 items-center justify-start cursor-pointer rounded-lg gap-2 p-4 border-2 border-transparent",
|
||||||
|
{
|
||||||
|
"border-primary": isChecked,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
label: "w-full",
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
{...checkboxProps}
|
||||||
|
>
|
||||||
|
<div className="w-full flex justify-between gap-2">
|
||||||
|
<User
|
||||||
|
avatarProps={{size: "sm", src: userProfile.avatar}}
|
||||||
|
description={
|
||||||
|
<Link href={userProfile.url} size="xs">
|
||||||
|
@{userProfile.username}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
name={userName}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col items-end gap-1">
|
||||||
|
<span className="text-xs text-neutral-500">{userRole}</span>
|
||||||
|
<Chip color={statusColor} size="xs" variant="flat">
|
||||||
|
{status}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomWithHooks = (props: CheckboxProps) => {
|
||||||
|
const {children, isSelected, getBaseProps, getLabelProps, getInputProps} = useCheckbox({
|
||||||
|
"aria-label": props["aria-label"] || "Toggle status",
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label {...getBaseProps()}>
|
||||||
|
<VisuallyHidden>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
</VisuallyHidden>
|
||||||
|
<Chip
|
||||||
|
color="primary"
|
||||||
|
leftContent={isSelected ? <CheckIcon className="ml-1" color={colors.white} /> : null}
|
||||||
|
styles={{
|
||||||
|
base: clsx("border-neutral hover:bg-neutral-200", {
|
||||||
|
"border-primary bg-primary hover:bg-primary-600 hover:border-primary-600": isSelected,
|
||||||
|
}),
|
||||||
|
content: clsx("text-primary", {
|
||||||
|
"text-primary-contrastText pl-1": isSelected,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
variant="faded"
|
||||||
|
{...getLabelProps()}
|
||||||
|
>
|
||||||
|
{children ? children : isSelected ? "Enabled" : "Disabled"}
|
||||||
|
</Chip>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const Chip = forwardRef<ChipProps, "div">((props, ref) => {
|
|||||||
return (
|
return (
|
||||||
<Component {...getChipProps()}>
|
<Component {...getChipProps()}>
|
||||||
{left}
|
{left}
|
||||||
<label className={slots.label({class: styles?.label})}>{children}</label>
|
<span className={slots.content({class: styles?.content})}>{children}</span>
|
||||||
{right}
|
{right}
|
||||||
</Component>
|
</Component>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export interface UseChipProps extends HTMLNextUIProps<"div">, ChipVariantProps {
|
|||||||
* <Chip styles={{
|
* <Chip styles={{
|
||||||
* base:"base-classes",
|
* base:"base-classes",
|
||||||
* dot: "dot-classes",
|
* dot: "dot-classes",
|
||||||
* label: "label-classes",
|
* content: "content-classes",
|
||||||
* avatar: "avatar-classes",
|
* avatar: "avatar-classes",
|
||||||
* closeButton: "close-button-classes",
|
* closeButton: "close-button-classes",
|
||||||
* }} />
|
* }} />
|
||||||
|
|||||||
@ -9,5 +9,8 @@ export type {RadioGroupProps} from "./radio-group";
|
|||||||
export {useRadio} from "./use-radio";
|
export {useRadio} from "./use-radio";
|
||||||
export {useRadioGroup} from "./use-radio-group";
|
export {useRadioGroup} from "./use-radio-group";
|
||||||
|
|
||||||
|
// export context
|
||||||
|
export * from "./radio-group-context";
|
||||||
|
|
||||||
// export component
|
// export component
|
||||||
export {Radio, RadioGroup};
|
export {Radio, RadioGroup};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
|
|||||||
getWrapperProps,
|
getWrapperProps,
|
||||||
getInputProps,
|
getInputProps,
|
||||||
getLabelProps,
|
getLabelProps,
|
||||||
|
getLabelWrapperProps,
|
||||||
getControlProps,
|
getControlProps,
|
||||||
} = useRadio({ref, ...props});
|
} = useRadio({ref, ...props});
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
|
|||||||
<span {...getWrapperProps()}>
|
<span {...getWrapperProps()}>
|
||||||
<span {...getControlProps()} />
|
<span {...getControlProps()} />
|
||||||
</span>
|
</span>
|
||||||
<div className={slots.labelWrapper({class: styles?.labelWrapper})}>
|
<div {...getLabelWrapperProps()}>
|
||||||
{children && <span {...getLabelProps()}>{children}</span>}
|
{children && <span {...getLabelProps()}>{children}</span>}
|
||||||
{description && (
|
{description && (
|
||||||
<span className={slots.description({class: styles?.description})}>{description}</span>
|
<span className={slots.description({class: styles?.description})}>{description}</span>
|
||||||
|
|||||||
@ -120,6 +120,8 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
inputRef,
|
inputRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isSelected = useMemo(() => inputProps.checked, [inputProps.checked]);
|
||||||
|
|
||||||
const {hoverProps, isHovered} = useHover({isDisabled});
|
const {hoverProps, isHovered} = useHover({isDisabled});
|
||||||
|
|
||||||
const {focusProps, isFocused, isFocusVisible} = useFocusRing({
|
const {focusProps, isFocused, isFocusVisible} = useFocusRing({
|
||||||
@ -142,8 +144,9 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
|
|
||||||
const baseStyles = clsx(styles?.base, className);
|
const baseStyles = clsx(styles?.base, className);
|
||||||
|
|
||||||
const getBaseProps: PropGetter = () => {
|
const getBaseProps: PropGetter = (props = {}) => {
|
||||||
return {
|
return {
|
||||||
|
...props,
|
||||||
ref: domRef,
|
ref: domRef,
|
||||||
className: slots.base({class: baseStyles}),
|
className: slots.base({class: baseStyles}),
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
@ -153,12 +156,13 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWrapperProps: PropGetter = () => {
|
const getWrapperProps: PropGetter = (props = {}) => {
|
||||||
return {
|
return {
|
||||||
"data-active": dataAttr(inputProps.checked),
|
...props,
|
||||||
|
"data-active": dataAttr(isSelected),
|
||||||
"data-hover": dataAttr(isHovered),
|
"data-hover": dataAttr(isHovered),
|
||||||
"data-hover-unchecked": dataAttr(isHovered && !inputProps.checked),
|
"data-hover-unchecked": dataAttr(isHovered && !isSelected),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-focus": dataAttr(isFocused),
|
"data-focus": dataAttr(isFocused),
|
||||||
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
|
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
@ -170,8 +174,9 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInputProps: PropGetter = () => {
|
const getInputProps: PropGetter = (props = {}) => {
|
||||||
return {
|
return {
|
||||||
|
...props,
|
||||||
ref: inputRef,
|
ref: inputRef,
|
||||||
required: isRequired,
|
required: isRequired,
|
||||||
"aria-required": dataAttr(isRequired),
|
"aria-required": dataAttr(isRequired),
|
||||||
@ -182,23 +187,33 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getLabelProps: PropGetter = useCallback(
|
const getLabelProps: PropGetter = useCallback(
|
||||||
() => ({
|
(props = {}) => ({
|
||||||
|
...props,
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-invalid": dataAttr(isInvalid),
|
"data-invalid": dataAttr(isInvalid),
|
||||||
className: slots.label({class: styles?.label}),
|
className: slots.label({class: styles?.label}),
|
||||||
}),
|
}),
|
||||||
[slots, isDisabled, inputProps.checked, isInvalid],
|
[slots, styles, isDisabled, isSelected, isInvalid],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getLabelWrapperProps: PropGetter = useCallback(
|
||||||
|
(props = {}) => ({
|
||||||
|
...props,
|
||||||
|
className: slots.labelWrapper({class: styles?.labelWrapper}),
|
||||||
|
}),
|
||||||
|
[slots, styles],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getControlProps: PropGetter = useCallback(
|
const getControlProps: PropGetter = useCallback(
|
||||||
() => ({
|
(props = {}) => ({
|
||||||
|
...props,
|
||||||
"data-disabled": dataAttr(isDisabled),
|
"data-disabled": dataAttr(isDisabled),
|
||||||
"data-checked": dataAttr(inputProps.checked),
|
"data-checked": dataAttr(isSelected),
|
||||||
"data-invalid": dataAttr(isInvalid),
|
"data-invalid": dataAttr(isInvalid),
|
||||||
className: slots.control({class: styles?.control}),
|
className: slots.control({class: styles?.control}),
|
||||||
}),
|
}),
|
||||||
[slots, isDisabled, inputProps.checked, isInvalid],
|
[slots, isDisabled, isSelected, isInvalid],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -207,10 +222,15 @@ export function useRadio(props: UseRadioProps) {
|
|||||||
slots,
|
slots,
|
||||||
styles,
|
styles,
|
||||||
description,
|
description,
|
||||||
|
isSelected,
|
||||||
|
isDisabled,
|
||||||
|
isInvalid,
|
||||||
|
isFocusVisible,
|
||||||
getBaseProps,
|
getBaseProps,
|
||||||
getWrapperProps,
|
getWrapperProps,
|
||||||
getInputProps,
|
getInputProps,
|
||||||
getLabelProps,
|
getLabelProps,
|
||||||
|
getLabelWrapperProps,
|
||||||
getControlProps,
|
getControlProps,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||||
|
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||||
import {radio, button} from "@nextui-org/theme";
|
import {radio, button} from "@nextui-org/theme";
|
||||||
|
import {clsx} from "@nextui-org/shared-utils";
|
||||||
|
|
||||||
import {RadioGroup, Radio, RadioGroupProps} from "../src";
|
import {
|
||||||
|
RadioGroup,
|
||||||
|
Radio,
|
||||||
|
RadioProps,
|
||||||
|
RadioGroupProps,
|
||||||
|
useRadio,
|
||||||
|
useRadioGroupContext,
|
||||||
|
} from "../src";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Inputs/RadioGroup",
|
title: "Inputs/RadioGroup",
|
||||||
@ -141,16 +150,22 @@ Row.args = {
|
|||||||
description: "for",
|
description: "for",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DisableAnimation = Template.bind({});
|
||||||
|
DisableAnimation.args = {
|
||||||
|
...defaultProps,
|
||||||
|
disableAnimation: true,
|
||||||
|
};
|
||||||
|
|
||||||
export const Controlled = () => {
|
export const Controlled = () => {
|
||||||
const [checked, setChecked] = React.useState<string>("london");
|
const [isSelected, setIsSelected] = React.useState<string>("london");
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log("checked:", checked);
|
console.log("isSelected:", isSelected);
|
||||||
}, [checked]);
|
}, [isSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioGroup label="Select city" value={checked} onChange={setChecked}>
|
<RadioGroup label="Select city" value={isSelected} onChange={setIsSelected}>
|
||||||
<Radio value="buenos-aires">Buenos Aires</Radio>
|
<Radio value="buenos-aires">Buenos Aires</Radio>
|
||||||
<Radio value="sydney">Sydney</Radio>
|
<Radio value="sydney">Sydney</Radio>
|
||||||
<Radio value="london">London</Radio>
|
<Radio value="london">London</Radio>
|
||||||
@ -159,8 +174,98 @@ export const Controlled = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DisableAnimation = Template.bind({});
|
const CustomRadio = (props: RadioProps) => {
|
||||||
DisableAnimation.args = {
|
const {children, ...otherProps} = props;
|
||||||
...defaultProps,
|
|
||||||
disableAnimation: true,
|
const {groupState} = useRadioGroupContext();
|
||||||
|
|
||||||
|
const isSelected = groupState.selectedValue === otherProps.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Radio
|
||||||
|
{...otherProps}
|
||||||
|
styles={{
|
||||||
|
base: clsx(
|
||||||
|
"inline-flex bg-content1 hover:bg-content2 items-center justify-between flex-row-reverse max-w-[300px] cursor-pointer rounded-lg gap-4 p-4 border-2 border-transparent",
|
||||||
|
{
|
||||||
|
"border-primary": isSelected,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Radio>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomWithStyles = () => {
|
||||||
|
return (
|
||||||
|
<RadioGroup label="Plans">
|
||||||
|
<CustomRadio description="Up to 20 items" value="free">
|
||||||
|
Free
|
||||||
|
</CustomRadio>
|
||||||
|
<CustomRadio description="Unlimited items. $10 per month." value="pro">
|
||||||
|
Pro
|
||||||
|
</CustomRadio>
|
||||||
|
<CustomRadio description="24/7 support. Contact us for pricing." value="enterprise">
|
||||||
|
Enterprise
|
||||||
|
</CustomRadio>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RadioCard = (props: RadioProps) => {
|
||||||
|
const {
|
||||||
|
Component,
|
||||||
|
children,
|
||||||
|
isSelected,
|
||||||
|
description,
|
||||||
|
getBaseProps,
|
||||||
|
getWrapperProps,
|
||||||
|
getInputProps,
|
||||||
|
getLabelProps,
|
||||||
|
getLabelWrapperProps,
|
||||||
|
getControlProps,
|
||||||
|
} = useRadio(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...getBaseProps()}
|
||||||
|
className={clsx(
|
||||||
|
"inline-flex items-center justify-between hover:bg-content2 flex-row-reverse max-w-[300px] cursor-pointer border-2 border-neutral rounded-lg gap-4 p-4",
|
||||||
|
{
|
||||||
|
"border-primary": isSelected,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<VisuallyHidden>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
</VisuallyHidden>
|
||||||
|
<span {...getWrapperProps()}>
|
||||||
|
<span {...getControlProps()} />
|
||||||
|
</span>
|
||||||
|
<div {...getLabelWrapperProps()}>
|
||||||
|
{children && <span {...getLabelProps()}>{children}</span>}
|
||||||
|
{description && (
|
||||||
|
<span className={clsx("text-sm text-foreground opacity-70")}>{description}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomWithHooks = () => {
|
||||||
|
return (
|
||||||
|
<RadioGroup label="Plans">
|
||||||
|
<RadioCard description="Up to 20 items" value="free">
|
||||||
|
Free
|
||||||
|
</RadioCard>
|
||||||
|
<RadioCard description="Unlimited items. $10 per month." value="pro">
|
||||||
|
Pro
|
||||||
|
</RadioCard>
|
||||||
|
<RadioCard description="24/7 support. Contact us for pricing." value="enterprise">
|
||||||
|
Enterprise
|
||||||
|
</RadioCard>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -73,7 +73,8 @@ describe("Snippet - Clipboard", () => {
|
|||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
wrapper.getByRole("button").click();
|
wrapper.getByRole("button").click();
|
||||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,16 @@
|
|||||||
export * from "./common";
|
import {commonColors} from "./common";
|
||||||
export * from "./semantic";
|
import {semanticColorsLight, semanticColorsDark} from "./semantic";
|
||||||
|
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
...commonColors,
|
||||||
|
light: {
|
||||||
|
...semanticColorsLight,
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
...semanticColorsDark,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export {colors, commonColors, semanticColorsLight, semanticColorsDark};
|
||||||
|
|||||||
@ -50,16 +50,16 @@ const base: SemanticBaseColors = {
|
|||||||
contrastText: readableColor(twColors.zinc[900]),
|
contrastText: readableColor(twColors.zinc[900]),
|
||||||
},
|
},
|
||||||
content2: {
|
content2: {
|
||||||
|
DEFAULT: twColors.zinc[800],
|
||||||
|
contrastText: readableColor(twColors.zinc[800]),
|
||||||
|
},
|
||||||
|
content3: {
|
||||||
DEFAULT: twColors.zinc[700],
|
DEFAULT: twColors.zinc[700],
|
||||||
contrastText: readableColor(twColors.zinc[700]),
|
contrastText: readableColor(twColors.zinc[700]),
|
||||||
},
|
},
|
||||||
content3: {
|
|
||||||
DEFAULT: twColors.zinc[500],
|
|
||||||
contrastText: readableColor(twColors.zinc[500]),
|
|
||||||
},
|
|
||||||
content4: {
|
content4: {
|
||||||
DEFAULT: twColors.zinc[400],
|
DEFAULT: twColors.zinc[600],
|
||||||
contrastText: readableColor(twColors.zinc[400]),
|
contrastText: readableColor(twColors.zinc[600]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const badge = tv({
|
|||||||
solid: {},
|
solid: {},
|
||||||
flat: {},
|
flat: {},
|
||||||
faded: {
|
faded: {
|
||||||
badge: "border-2",
|
badge: "border-1.5",
|
||||||
},
|
},
|
||||||
shadow: {},
|
shadow: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const button = tv({
|
|||||||
bordered: "border-2 !bg-transparent",
|
bordered: "border-2 !bg-transparent",
|
||||||
light: "!bg-transparent",
|
light: "!bg-transparent",
|
||||||
flat: "",
|
flat: "",
|
||||||
faded: "border-2",
|
faded: "border-1.5",
|
||||||
shadow: "",
|
shadow: "",
|
||||||
ghost: "border-2 !bg-transparent",
|
ghost: "border-2 !bg-transparent",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const checkbox = tv({
|
|||||||
"data-[hover=true]:before:bg-neutral-100",
|
"data-[hover=true]:before:bg-neutral-100",
|
||||||
],
|
],
|
||||||
icon: "z-10 w-4 h-3 opacity-0 data-[checked=true]:opacity-100",
|
icon: "z-10 w-4 h-3 opacity-0 data-[checked=true]:opacity-100",
|
||||||
label: "relative ml-1 text-foreground select-none",
|
label: "relative text-foreground select-none",
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
color: {
|
color: {
|
||||||
@ -75,28 +75,28 @@ const checkbox = tv({
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: {
|
xs: {
|
||||||
wrapper: "w-3.5 h-3.5",
|
wrapper: "w-3.5 h-3.5 mr-1",
|
||||||
label: "ml-1 text-xs",
|
label: "text-xs",
|
||||||
icon: "w-3 h-2",
|
icon: "w-3 h-2",
|
||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
wrapper: "w-4 h-4",
|
wrapper: "w-4 h-4 mr-1",
|
||||||
label: "ml-1 text-sm",
|
label: "text-sm",
|
||||||
icon: "w-3 h-2",
|
icon: "w-3 h-2",
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
wrapper: "w-5 h-5",
|
wrapper: "w-5 h-5 mr-2",
|
||||||
label: "ml-2 text-base",
|
label: "text-base",
|
||||||
icon: "w-4 h-3",
|
icon: "w-4 h-3",
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
wrapper: "w-6 h-6",
|
wrapper: "w-6 h-6 mr-2",
|
||||||
label: "ml-2 text-lg",
|
label: "text-lg",
|
||||||
icon: "w-5 h-4",
|
icon: "w-5 h-4",
|
||||||
},
|
},
|
||||||
xl: {
|
xl: {
|
||||||
wrapper: "w-7 h-7",
|
wrapper: "w-7 h-7 mr-2",
|
||||||
label: "ml-2 text-xl",
|
label: "text-xl",
|
||||||
icon: "w-6 h-5",
|
icon: "w-6 h-5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,22 +5,22 @@ import {ringClasses, colorVariants} from "../utils";
|
|||||||
/**
|
/**
|
||||||
* Chip wrapper **Tailwind Variants** component
|
* Chip wrapper **Tailwind Variants** component
|
||||||
*
|
*
|
||||||
* const {base, label, dot, avatar, closeButton} = chip({...})
|
* const {base, content, dot, avatar, closeButton} = chip({...})
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* <div className={base())}>
|
* <div className={base())}>
|
||||||
* // left content
|
* // left content
|
||||||
* <span className={avatar()}/>
|
* <span className={avatar()}/>
|
||||||
* <svg className={dot()}/>
|
* <svg className={dot()}/>
|
||||||
* <label className={label()}>Default</label>
|
* <span className={content()}>Default</span>
|
||||||
* <svg className={closeButton()}>close button</svg>
|
* <svg className={closeButton()}>close button</svg>
|
||||||
* // right content
|
* // right content
|
||||||
* </div>
|
* </div>
|
||||||
*/
|
*/
|
||||||
const chip = tv({
|
const chip = tv({
|
||||||
slots: {
|
slots: {
|
||||||
base: ["relative", "inline-flex", "items-center", "justify-between", "box-border"],
|
base: ["relative", "max-w-fit", "inline-flex", "items-center", "justify-between", "box-border"],
|
||||||
label: "flex-1 text-inherit select-none font-regular",
|
content: "flex-1 text-inherit select-none font-regular",
|
||||||
dot: ["w-2", "h-2", "mx-1", "rounded-full"],
|
dot: ["w-2", "h-2", "mx-1", "rounded-full"],
|
||||||
avatar: "flex-shrink-0",
|
avatar: "flex-shrink-0",
|
||||||
closeButton: [
|
closeButton: [
|
||||||
@ -38,18 +38,18 @@ const chip = tv({
|
|||||||
variant: {
|
variant: {
|
||||||
solid: {},
|
solid: {},
|
||||||
bordered: {
|
bordered: {
|
||||||
base: "border-2 !bg-transparent",
|
base: "border-1.5 !bg-transparent",
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
base: "!bg-transparent",
|
base: "!bg-transparent",
|
||||||
},
|
},
|
||||||
flat: {},
|
flat: {},
|
||||||
faded: {
|
faded: {
|
||||||
base: "border-2",
|
base: "border-1.5",
|
||||||
},
|
},
|
||||||
shadow: {},
|
shadow: {},
|
||||||
dot: {
|
dot: {
|
||||||
base: "border-2 border-neutral text-foreground !bg-transparent",
|
base: "border-1.5 border-neutral text-foreground !bg-transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
@ -75,31 +75,31 @@ const chip = tv({
|
|||||||
size: {
|
size: {
|
||||||
xs: {
|
xs: {
|
||||||
base: "px-0.5 h-5 text-xs",
|
base: "px-0.5 h-5 text-xs",
|
||||||
label: "px-0.5",
|
content: "px-1",
|
||||||
closeButton: "text-sm",
|
closeButton: "text-sm",
|
||||||
avatar: "w-3.5 h-3.5",
|
avatar: "w-3.5 h-3.5",
|
||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
base: "px-1 h-6 text-sm",
|
base: "px-1 h-6 text-sm",
|
||||||
label: "px-1",
|
content: "px-1",
|
||||||
closeButton: "text-base",
|
closeButton: "text-base",
|
||||||
avatar: "w-4 h-4",
|
avatar: "w-4 h-4",
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
base: "px-1 h-7 text-base",
|
base: "px-1 h-7 text-base",
|
||||||
label: "px-1",
|
content: "px-2",
|
||||||
closeButton: "text-lg",
|
closeButton: "text-lg",
|
||||||
avatar: "w-5 h-5",
|
avatar: "w-5 h-5",
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
base: "px-2 h-8 text-lg",
|
base: "px-2 h-8 text-lg",
|
||||||
label: "px-1",
|
content: "px-2",
|
||||||
closeButton: "text-xl",
|
closeButton: "text-xl",
|
||||||
avatar: "w-6 h-6",
|
avatar: "w-6 h-6",
|
||||||
},
|
},
|
||||||
xl: {
|
xl: {
|
||||||
base: "px-2 h-9 text-xl",
|
base: "px-2 h-9 text-xl",
|
||||||
label: "px-1",
|
content: "px-2",
|
||||||
closeButton: "text-2xl",
|
closeButton: "text-2xl",
|
||||||
avatar: "w-7 h-7",
|
avatar: "w-7 h-7",
|
||||||
},
|
},
|
||||||
@ -118,7 +118,7 @@ const chip = tv({
|
|||||||
isOneChar: {
|
isOneChar: {
|
||||||
true: {
|
true: {
|
||||||
base: "px-0 justify-center",
|
base: "px-0 justify-center",
|
||||||
label: "px-0 flex-none",
|
content: "px-0 flex-none",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isDisabled: {
|
isDisabled: {
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const tooltip = tv({
|
|||||||
solid: "",
|
solid: "",
|
||||||
bordered: "border-2 !bg-transparent",
|
bordered: "border-2 !bg-transparent",
|
||||||
light: "!bg-transparent",
|
light: "!bg-transparent",
|
||||||
faded: "border-2",
|
faded: "border-1.5",
|
||||||
flat: "",
|
flat: "",
|
||||||
shadow: "",
|
shadow: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export * from "./components";
|
export * from "./components";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
export * from "./colors";
|
||||||
|
|
||||||
export {tv} from "tailwind-variants";
|
export {tv} from "tailwind-variants";
|
||||||
export type {VariantProps} from "tailwind-variants";
|
export type {VariantProps} from "tailwind-variants";
|
||||||
|
|||||||
@ -5,12 +5,10 @@ module.exports = {
|
|||||||
content: [
|
content: [
|
||||||
"../components/**/src/**/*.{js,jsx,ts,tsx}",
|
"../components/**/src/**/*.{js,jsx,ts,tsx}",
|
||||||
"../components/**/stories/**/*.{js,jsx,ts,tsx}",
|
"../components/**/stories/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"../core/theme/src/components/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"../core/theme/src/utils/**/*.{js,jsx,ts,tsx}",
|
||||||
"../core/theme/stories/**/*.{js,jsx,ts,tsx}",
|
"../core/theme/stories/**/*.{js,jsx,ts,tsx}",
|
||||||
],
|
],
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
plugins: [nextui(
|
plugins: [nextui()],
|
||||||
{
|
|
||||||
defaultTheme: 'dark'
|
|
||||||
}
|
|
||||||
)],
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export const AvatarIcon = () => (
|
import {IconSvgProps} from "./types";
|
||||||
|
|
||||||
|
export const AvatarIcon = (props: IconSvgProps) => (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -7,6 +9,7 @@ export const AvatarIcon = () => (
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width="1em"
|
width="1em"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M12 2C9.38 2 7.25 4.13 7.25 6.75C7.25 9.32 9.26 11.4 11.88 11.49C11.96 11.48 12.04 11.48 12.1 11.49C12.12 11.49 12.13 11.49 12.15 11.49C12.16 11.49 12.16 11.49 12.17 11.49C14.73 11.4 16.74 9.32 16.75 6.75C16.75 4.13 14.62 2 12 2Z"
|
d="M12 2C9.38 2 7.25 4.13 7.25 6.75C7.25 9.32 9.26 11.4 11.88 11.49C11.96 11.48 12.04 11.48 12.1 11.49C12.12 11.49 12.13 11.49 12.15 11.49C12.16 11.49 12.16 11.49 12.17 11.49C14.73 11.4 16.74 9.32 16.75 6.75C16.75 4.13 14.62 2 12 2Z"
|
||||||
|
|||||||
@ -1,15 +1,39 @@
|
|||||||
export const CheckIcon = () => (
|
import {IconSvgProps} from "./types";
|
||||||
<svg
|
|
||||||
fill="none"
|
export interface CheckIconProps extends IconSvgProps {
|
||||||
focusable="false"
|
filled?: boolean;
|
||||||
height="1em"
|
}
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
export const CheckIcon = ({filled = false, ...props}: CheckIconProps) =>
|
||||||
strokeLinejoin="round"
|
filled ? (
|
||||||
strokeWidth={2}
|
<svg
|
||||||
viewBox="0 0 24 24"
|
aria-hidden="true"
|
||||||
width="1em"
|
fill="none"
|
||||||
>
|
focusable="false"
|
||||||
<polyline points="20 6 9 17 4 12" />
|
height="1em"
|
||||||
</svg>
|
viewBox="0 0 24 24"
|
||||||
);
|
width="1em"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.78 9.7L11.11 15.37C10.97 15.51 10.78 15.59 10.58 15.59C10.38 15.59 10.19 15.51 10.05 15.37L7.22 12.54C6.93 12.25 6.93 11.77 7.22 11.48C7.51 11.19 7.99 11.19 8.28 11.48L10.58 13.78L15.72 8.64C16.01 8.35 16.49 8.35 16.78 8.64C17.07 8.93 17.07 9.4 16.78 9.7Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export const CloseFilledIcon = () => (
|
import {IconSvgProps} from "./types";
|
||||||
|
|
||||||
|
export const CloseFilledIcon = (props: IconSvgProps) => (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
@ -6,6 +8,7 @@ export const CloseFilledIcon = () => (
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width="1em"
|
width="1em"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M12 2a10 10 0 1010 10A10.016 10.016 0 0012 2zm3.36 12.3a.754.754 0 010 1.06.748.748 0 01-1.06 0l-2.3-2.3-2.3 2.3a.748.748 0 01-1.06 0 .754.754 0 010-1.06l2.3-2.3-2.3-2.3A.75.75 0 019.7 8.64l2.3 2.3 2.3-2.3a.75.75 0 011.06 1.06l-2.3 2.3z"
|
d="M12 2a10 10 0 1010 10A10.016 10.016 0 0012 2zm3.36 12.3a.754.754 0 010 1.06.748.748 0 01-1.06 0l-2.3-2.3-2.3 2.3a.748.748 0 01-1.06 0 .754.754 0 010-1.06l2.3-2.3-2.3-2.3A.75.75 0 019.7 8.64l2.3 2.3 2.3-2.3a.75.75 0 011.06 1.06l-2.3 2.3z"
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export const CloseIcon = () => (
|
import {IconSvgProps} from "./types";
|
||||||
|
|
||||||
|
export const CloseIcon = (props: IconSvgProps) => (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -11,6 +13,7 @@ export const CloseIcon = () => (
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width="1em"
|
width="1em"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<path d="M18 6L6 18M6 6l12 12" />
|
<path d="M18 6L6 18M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export const CopyIcon = () => (
|
import {IconSvgProps} from "./types";
|
||||||
|
|
||||||
|
export const CopyIcon = (props: IconSvgProps) => (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
@ -6,6 +8,7 @@ export const CopyIcon = () => (
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width="1em"
|
width="1em"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M20 2H10c-1.103 0-2 .897-2 2v4H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2v-4h4c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zM4 20V10h10l.002 10H4zm16-6h-4v-4c0-1.103-.897-2-2-2h-4V4h10v10z"
|
d="M20 2H10c-1.103 0-2 .897-2 2v4H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2v-4h4c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zM4 20V10h10l.002 10H4zm16-6h-4v-4c0-1.103-.897-2-2-2h-4V4h10v10z"
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
interface IconProps {
|
import {IconSvgProps} from "./types";
|
||||||
|
interface IconProps extends IconSvgProps {
|
||||||
fill?: string;
|
fill?: string;
|
||||||
filled?: boolean;
|
filled?: boolean;
|
||||||
size?: string | number;
|
size?: string | number;
|
||||||
height?: string | number;
|
height?: string | number;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
label?: string;
|
label?: string;
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sun: React.FC<IconProps> = ({fill, filled, size, height, width, ...props}) => {
|
export const Sun: React.FC<IconProps> = ({fill, filled, size, height, width, ...props}) => {
|
||||||
|
|||||||
3
packages/utilities/shared-icons/src/types.ts
Normal file
3
packages/utilities/shared-icons/src/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import {SVGProps} from "react";
|
||||||
|
|
||||||
|
export type IconSvgProps = SVGProps<SVGSVGElement>;
|
||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -421,10 +421,14 @@ importers:
|
|||||||
|
|
||||||
packages/components/checkbox:
|
packages/components/checkbox:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@nextui-org/chip': workspace:*
|
||||||
'@nextui-org/dom-utils': workspace:*
|
'@nextui-org/dom-utils': workspace:*
|
||||||
|
'@nextui-org/link': workspace:*
|
||||||
|
'@nextui-org/shared-icons': workspace:*
|
||||||
'@nextui-org/shared-utils': workspace:*
|
'@nextui-org/shared-utils': workspace:*
|
||||||
'@nextui-org/system': workspace:*
|
'@nextui-org/system': workspace:*
|
||||||
'@nextui-org/theme': workspace:*
|
'@nextui-org/theme': workspace:*
|
||||||
|
'@nextui-org/user': workspace:*
|
||||||
'@react-aria/checkbox': ^3.8.0
|
'@react-aria/checkbox': ^3.8.0
|
||||||
'@react-aria/focus': ^3.11.0
|
'@react-aria/focus': ^3.11.0
|
||||||
'@react-aria/interactions': ^3.14.0
|
'@react-aria/interactions': ^3.14.0
|
||||||
@ -449,6 +453,10 @@ importers:
|
|||||||
'@react-stately/checkbox': 3.4.0_react@18.2.0
|
'@react-stately/checkbox': 3.4.0_react@18.2.0
|
||||||
'@react-stately/toggle': 3.5.0_react@18.2.0
|
'@react-stately/toggle': 3.5.0_react@18.2.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@nextui-org/chip': link:../chip
|
||||||
|
'@nextui-org/link': link:../link
|
||||||
|
'@nextui-org/shared-icons': link:../../utilities/shared-icons
|
||||||
|
'@nextui-org/user': link:../user
|
||||||
'@react-types/checkbox': 3.4.2_react@18.2.0
|
'@react-types/checkbox': 3.4.2_react@18.2.0
|
||||||
'@react-types/shared': 3.17.0_react@18.2.0
|
'@react-types/shared': 3.17.0_react@18.2.0
|
||||||
clean-package: 2.2.0
|
clean-package: 2.2.0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user