feat(components): radio & checkbox improved, custom examples added to the stories

This commit is contained in:
Junior Garcia 2023-03-06 23:27:34 -03:00
parent 2545eb7898
commit aba2e459d6
31 changed files with 550 additions and 142 deletions

View File

@ -1,6 +1,5 @@
import * as React from "react";
import {render} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {act, render} from "@testing-library/react";
import {Button} from "../src";
@ -22,7 +21,10 @@ describe("Button", () => {
const onPress = jest.fn();
const {getByRole} = render(<Button onPress={onPress} />);
act(() => {
getByRole("button").click();
});
expect(onPress).toHaveBeenCalled();
});
@ -32,9 +34,9 @@ describe("Button", () => {
const wrapper = render(<Button onClick={onClick} />);
let button = wrapper.getByRole("button");
userEvent.click(button);
act(() => {
wrapper.getByRole("button").click();
});
expect(spy).toHaveBeenCalled();
spy.mockRestore();
@ -44,7 +46,10 @@ describe("Button", () => {
const onPress = jest.fn();
const {getByRole} = render(<Button disabled onPress={onPress} />);
act(() => {
getByRole("button").click();
});
expect(onPress).not.toHaveBeenCalled();
});

View File

@ -50,6 +50,10 @@
"@react-aria/utils": "^3.15.0"
},
"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/shared": "^3.17.0",
"clean-package": "2.2.0",

View File

@ -23,7 +23,10 @@ const Checkbox = forwardRef<CheckboxProps, "label">((props, ref) => {
...props,
});
const clonedIcon = cloneElement(icon as ReactElement, getIconProps());
const clonedIcon =
typeof icon === "function"
? icon(getIconProps())
: cloneElement(icon as ReactElement, getIconProps());
return (
<Component {...getBaseProps()}>

View File

@ -8,6 +8,10 @@ export {useCheckboxGroup} from "./use-checkbox-group";
// export types
export type {CheckboxProps} from "./checkbox";
export type {CheckboxGroupProps} from "./checkbox-group";
export type {CheckboxIconProps} from "./use-checkbox";
// export context
export * from "./checkbox-group-context";
// export components
export {Checkbox, CheckboxGroup};

View File

@ -19,6 +19,14 @@ import {FocusableRef} from "@react-types/shared";
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"> {
/**
* Ref to the DOM node.
@ -36,7 +44,7 @@ interface Props extends HTMLNextUIProps<"label"> {
/**
* 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.
* if `className` is passed, it will be added to the base slot.
@ -54,7 +62,7 @@ interface Props extends HTMLNextUIProps<"label"> {
styles?: SlotsToClasses<CheckboxSlots>;
}
export type UseCheckboxProps = Omit<Props, "defaultChecked"> &
export type UseCheckboxProps = Omit<Props, "defaultChecked" | "onChange"> &
Omit<AriaCheckboxProps, keyof CheckboxVariantProps> &
Omit<CheckboxVariantProps, "isFocusVisible">;
@ -65,11 +73,13 @@ export function useCheckbox(props: UseCheckboxProps) {
const {
as,
ref,
isSelected,
value = "",
children,
icon,
name,
isRequired = false,
isReadOnly = false,
isSelected: isSelectedProp,
size = groupContext?.size ?? "md",
color = groupContext?.color ?? "primary",
radius = groupContext?.radius ?? "md",
@ -77,6 +87,7 @@ export function useCheckbox(props: UseCheckboxProps) {
isDisabled = groupContext?.isDisabled ?? false,
disableAnimation = groupContext?.disableAnimation ?? false,
isIndeterminate = false,
validationState,
defaultSelected,
styles,
onChange,
@ -85,7 +96,7 @@ export function useCheckbox(props: UseCheckboxProps) {
} = props;
if (groupContext && __DEV__) {
if (isSelected) {
if (isSelectedProp) {
warn(
"The Checkbox.Group is being used, `isSelected` will be ignored. Use the `value` of the Checkbox.Group instead.",
"Checkbox",
@ -105,34 +116,50 @@ export function useCheckbox(props: UseCheckboxProps) {
const domRef = useFocusableRef(ref as FocusableRef<HTMLLabelElement>, inputRef);
const ariaCheckboxProps = useMemo(() => {
const arialabel =
otherProps["aria-label"] || typeof children === "string" ? (children as string) : undefined;
return {
name,
value,
children,
defaultSelected,
isSelected,
isSelected: isSelectedProp,
isDisabled,
isIndeterminate,
isRequired,
isReadOnly,
validationState,
"aria-label": otherProps["aria-label"],
"aria-labelledby": otherProps["aria-labelledby"],
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
? // eslint-disable-next-line
useReactAriaCheckboxGroupItem(
{
...ariaCheckboxProps,
validationState: otherProps.validationState,
validationState,
},
groupContext.groupState,
inputRef,
)
: useReactAriaCheckbox(ariaCheckboxProps, useToggleState(ariaCheckboxProps), inputRef); // eslint-disable-line
const isSelected = inputProps.checked;
const isInvalid = useMemo(() => validationState === "invalid", [validationState]);
if (isRequired) {
inputProps.required = true;
}
@ -166,8 +193,8 @@ export function useCheckbox(props: UseCheckboxProps) {
ref: domRef,
className: slots.base({class: baseStyles}),
"data-disabled": dataAttr(isDisabled),
"data-checked": dataAttr(inputProps.checked),
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
"data-checked": dataAttr(isSelected),
"data-invalid": dataAttr(isInvalid),
...mergeProps(hoverProps, otherProps),
};
};
@ -175,12 +202,12 @@ export function useCheckbox(props: UseCheckboxProps) {
const getWrapperProps: PropGetter = () => {
return {
"data-hover": dataAttr(isHovered),
"data-checked": dataAttr(inputProps.checked),
"data-checked": dataAttr(isSelected),
"data-focus": dataAttr(isFocused),
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
"data-indeterminate": dataAttr(isIndeterminate),
"data-disabled": dataAttr(isDisabled),
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
"data-invalid": dataAttr(isInvalid),
"data-readonly": dataAttr(inputProps.readOnly),
"aria-hidden": true,
className: clsx(slots.wrapper({class: styles?.wrapper})),
@ -197,28 +224,32 @@ export function useCheckbox(props: UseCheckboxProps) {
const getLabelProps: PropGetter = useCallback(
() => ({
"data-disabled": dataAttr(isDisabled),
"data-checked": dataAttr(inputProps.checked),
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
"data-checked": dataAttr(isSelected),
"data-invalid": dataAttr(isInvalid),
className: slots.label({class: styles?.label}),
}),
[slots, isDisabled, inputProps.checked, otherProps.validationState],
[slots, styles?.label, isDisabled, isSelected, isInvalid],
);
const getIconProps: PropGetter = useCallback(
() => ({
"data-checked": dataAttr(inputProps.checked),
isSelected: inputProps.checked,
const getIconProps = useCallback(
() =>
({
"data-checked": dataAttr(isSelected),
isSelected: isSelected,
isIndeterminate: !!isIndeterminate,
disableAnimation: !!disableAnimation,
className: slots.icon({class: styles?.icon}),
}),
[slots, inputProps.checked, isIndeterminate, disableAnimation],
} as CheckboxIconProps),
[slots, styles?.icon, isSelected, isIndeterminate, disableAnimation],
);
return {
Component,
icon,
children,
isSelected,
isDisabled,
isInvalid,
getBaseProps,
getWrapperProps,
getInputProps,

View File

@ -4,6 +4,11 @@ import {checkbox} from "@nextui-org/theme";
import {CheckboxGroup, Checkbox, CheckboxGroupProps} from "../src";
import {
CustomWithStyles as CheckboxItemWithStyles,
CustomWithHooks as CheckboxItemWithHooks,
} from "./checkbox.stories";
export default {
title: "Inputs/CheckboxGroup",
component: CheckboxGroup,
@ -121,25 +126,58 @@ export const Controlled = () => {
);
};
export const Group = () => {
// eslint-disable-next-line no-console
const handleGroupChange = (value: string[]) => console.log(value);
export const CustomWithStyles = () => {
const [groupSelected, setGroupSelected] = React.useState<string[]>([]);
return (
<CheckboxGroup
color="warning"
defaultValue={["buenos-aires"]}
label="Select cities"
onChange={handleGroupChange}
>
<Checkbox color="primary" value="buenos-aires">
Buenos Aires
</Checkbox>
<Checkbox value="sydney">Sydney</Checkbox>
<Checkbox isDisabled value="london">
London
</Checkbox>
<Checkbox value="tokyo">Tokyo</Checkbox>
<>
<CheckboxGroup label="Select employees" value={groupSelected} onChange={setGroupSelected}>
<CheckboxItemWithStyles value="junior" />
<CheckboxItemWithStyles
userName="John Doe"
userProfile={{
avatar: "https://i.pravatar.cc/300?u=a042581f4e29026707d",
username: "johndoe",
url: "#",
}}
userRole="Product Designer"
value="johndoe"
/>
<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>
);
};

View File

@ -1,8 +1,20 @@
import React from "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 {
title: "Inputs/Checkbox",
@ -39,7 +51,7 @@ export default {
},
} as ComponentMeta<typeof Checkbox>;
const defaultProps = {
const defaultProps: CheckboxProps = {
...checkbox.defaultVariants,
children: "Option",
};
@ -63,6 +75,18 @@ DefaultSelected.args = {
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({});
AlwaysSelected.args = {
...defaultProps,
@ -103,3 +127,112 @@ export const Controlled = () => {
</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>
);
};

View File

@ -46,7 +46,7 @@ const Chip = forwardRef<ChipProps, "div">((props, ref) => {
return (
<Component {...getChipProps()}>
{left}
<label className={slots.label({class: styles?.label})}>{children}</label>
<span className={slots.content({class: styles?.content})}>{children}</span>
{right}
</Component>
);

View File

@ -37,7 +37,7 @@ export interface UseChipProps extends HTMLNextUIProps<"div">, ChipVariantProps {
* <Chip styles={{
* base:"base-classes",
* dot: "dot-classes",
* label: "label-classes",
* content: "content-classes",
* avatar: "avatar-classes",
* closeButton: "close-button-classes",
* }} />

View File

@ -9,5 +9,8 @@ export type {RadioGroupProps} from "./radio-group";
export {useRadio} from "./use-radio";
export {useRadioGroup} from "./use-radio-group";
// export context
export * from "./radio-group-context";
// export component
export {Radio, RadioGroup};

View File

@ -17,6 +17,7 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
getWrapperProps,
getInputProps,
getLabelProps,
getLabelWrapperProps,
getControlProps,
} = useRadio({ref, ...props});
@ -28,7 +29,7 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
<span {...getWrapperProps()}>
<span {...getControlProps()} />
</span>
<div className={slots.labelWrapper({class: styles?.labelWrapper})}>
<div {...getLabelWrapperProps()}>
{children && <span {...getLabelProps()}>{children}</span>}
{description && (
<span className={slots.description({class: styles?.description})}>{description}</span>

View File

@ -120,6 +120,8 @@ export function useRadio(props: UseRadioProps) {
inputRef,
);
const isSelected = useMemo(() => inputProps.checked, [inputProps.checked]);
const {hoverProps, isHovered} = useHover({isDisabled});
const {focusProps, isFocused, isFocusVisible} = useFocusRing({
@ -142,8 +144,9 @@ export function useRadio(props: UseRadioProps) {
const baseStyles = clsx(styles?.base, className);
const getBaseProps: PropGetter = () => {
const getBaseProps: PropGetter = (props = {}) => {
return {
...props,
ref: domRef,
className: slots.base({class: baseStyles}),
"data-disabled": dataAttr(isDisabled),
@ -153,12 +156,13 @@ export function useRadio(props: UseRadioProps) {
};
};
const getWrapperProps: PropGetter = () => {
const getWrapperProps: PropGetter = (props = {}) => {
return {
"data-active": dataAttr(inputProps.checked),
...props,
"data-active": dataAttr(isSelected),
"data-hover": dataAttr(isHovered),
"data-hover-unchecked": dataAttr(isHovered && !inputProps.checked),
"data-checked": dataAttr(inputProps.checked),
"data-hover-unchecked": dataAttr(isHovered && !isSelected),
"data-checked": dataAttr(isSelected),
"data-focus": dataAttr(isFocused),
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
"data-disabled": dataAttr(isDisabled),
@ -170,8 +174,9 @@ export function useRadio(props: UseRadioProps) {
};
};
const getInputProps: PropGetter = () => {
const getInputProps: PropGetter = (props = {}) => {
return {
...props,
ref: inputRef,
required: isRequired,
"aria-required": dataAttr(isRequired),
@ -182,23 +187,33 @@ export function useRadio(props: UseRadioProps) {
};
const getLabelProps: PropGetter = useCallback(
() => ({
(props = {}) => ({
...props,
"data-disabled": dataAttr(isDisabled),
"data-checked": dataAttr(inputProps.checked),
"data-checked": dataAttr(isSelected),
"data-invalid": dataAttr(isInvalid),
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(
() => ({
(props = {}) => ({
...props,
"data-disabled": dataAttr(isDisabled),
"data-checked": dataAttr(inputProps.checked),
"data-checked": dataAttr(isSelected),
"data-invalid": dataAttr(isInvalid),
className: slots.control({class: styles?.control}),
}),
[slots, isDisabled, inputProps.checked, isInvalid],
[slots, isDisabled, isSelected, isInvalid],
);
return {
@ -207,10 +222,15 @@ export function useRadio(props: UseRadioProps) {
slots,
styles,
description,
isSelected,
isDisabled,
isInvalid,
isFocusVisible,
getBaseProps,
getWrapperProps,
getInputProps,
getLabelProps,
getLabelWrapperProps,
getControlProps,
};
}

View File

@ -1,8 +1,17 @@
import React from "react";
import {ComponentStory, ComponentMeta} from "@storybook/react";
import {VisuallyHidden} from "@react-aria/visually-hidden";
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 {
title: "Inputs/RadioGroup",
@ -141,16 +150,22 @@ Row.args = {
description: "for",
};
export const DisableAnimation = Template.bind({});
DisableAnimation.args = {
...defaultProps,
disableAnimation: true,
};
export const Controlled = () => {
const [checked, setChecked] = React.useState<string>("london");
const [isSelected, setIsSelected] = React.useState<string>("london");
React.useEffect(() => {
// eslint-disable-next-line no-console
console.log("checked:", checked);
}, [checked]);
console.log("isSelected:", isSelected);
}, [isSelected]);
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="sydney">Sydney</Radio>
<Radio value="london">London</Radio>
@ -159,8 +174,98 @@ export const Controlled = () => {
);
};
export const DisableAnimation = Template.bind({});
DisableAnimation.args = {
...defaultProps,
disableAnimation: true,
const CustomRadio = (props: RadioProps) => {
const {children, ...otherProps} = props;
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>
);
};

View File

@ -73,7 +73,8 @@ describe("Snippet - Clipboard", () => {
act(() => {
wrapper.getByRole("button").click();
});
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
});
});
});

View File

@ -1,3 +1,16 @@
export * from "./common";
export * from "./semantic";
import {commonColors} from "./common";
import {semanticColorsLight, semanticColorsDark} from "./semantic";
export * from "./types";
const colors = {
...commonColors,
light: {
...semanticColorsLight,
},
dark: {
...semanticColorsDark,
},
};
export {colors, commonColors, semanticColorsLight, semanticColorsDark};

View File

@ -50,16 +50,16 @@ const base: SemanticBaseColors = {
contrastText: readableColor(twColors.zinc[900]),
},
content2: {
DEFAULT: twColors.zinc[800],
contrastText: readableColor(twColors.zinc[800]),
},
content3: {
DEFAULT: twColors.zinc[700],
contrastText: readableColor(twColors.zinc[700]),
},
content3: {
DEFAULT: twColors.zinc[500],
contrastText: readableColor(twColors.zinc[500]),
},
content4: {
DEFAULT: twColors.zinc[400],
contrastText: readableColor(twColors.zinc[400]),
DEFAULT: twColors.zinc[600],
contrastText: readableColor(twColors.zinc[600]),
},
},
};

View File

@ -40,7 +40,7 @@ const badge = tv({
solid: {},
flat: {},
faded: {
badge: "border-2",
badge: "border-1.5",
},
shadow: {},
},

View File

@ -34,7 +34,7 @@ const button = tv({
bordered: "border-2 !bg-transparent",
light: "!bg-transparent",
flat: "",
faded: "border-2",
faded: "border-1.5",
shadow: "",
ghost: "border-2 !bg-transparent",
},

View File

@ -50,7 +50,7 @@ const checkbox = tv({
"data-[hover=true]:before:bg-neutral-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: {
color: {
@ -75,28 +75,28 @@ const checkbox = tv({
},
size: {
xs: {
wrapper: "w-3.5 h-3.5",
label: "ml-1 text-xs",
wrapper: "w-3.5 h-3.5 mr-1",
label: "text-xs",
icon: "w-3 h-2",
},
sm: {
wrapper: "w-4 h-4",
label: "ml-1 text-sm",
wrapper: "w-4 h-4 mr-1",
label: "text-sm",
icon: "w-3 h-2",
},
md: {
wrapper: "w-5 h-5",
label: "ml-2 text-base",
wrapper: "w-5 h-5 mr-2",
label: "text-base",
icon: "w-4 h-3",
},
lg: {
wrapper: "w-6 h-6",
label: "ml-2 text-lg",
wrapper: "w-6 h-6 mr-2",
label: "text-lg",
icon: "w-5 h-4",
},
xl: {
wrapper: "w-7 h-7",
label: "ml-2 text-xl",
wrapper: "w-7 h-7 mr-2",
label: "text-xl",
icon: "w-6 h-5",
},
},

View File

@ -5,22 +5,22 @@ import {ringClasses, colorVariants} from "../utils";
/**
* Chip wrapper **Tailwind Variants** component
*
* const {base, label, dot, avatar, closeButton} = chip({...})
* const {base, content, dot, avatar, closeButton} = chip({...})
*
* @example
* <div className={base())}>
* // left content
* <span className={avatar()}/>
* <svg className={dot()}/>
* <label className={label()}>Default</label>
* <span className={content()}>Default</span>
* <svg className={closeButton()}>close button</svg>
* // right content
* </div>
*/
const chip = tv({
slots: {
base: ["relative", "inline-flex", "items-center", "justify-between", "box-border"],
label: "flex-1 text-inherit select-none font-regular",
base: ["relative", "max-w-fit", "inline-flex", "items-center", "justify-between", "box-border"],
content: "flex-1 text-inherit select-none font-regular",
dot: ["w-2", "h-2", "mx-1", "rounded-full"],
avatar: "flex-shrink-0",
closeButton: [
@ -38,18 +38,18 @@ const chip = tv({
variant: {
solid: {},
bordered: {
base: "border-2 !bg-transparent",
base: "border-1.5 !bg-transparent",
},
light: {
base: "!bg-transparent",
},
flat: {},
faded: {
base: "border-2",
base: "border-1.5",
},
shadow: {},
dot: {
base: "border-2 border-neutral text-foreground !bg-transparent",
base: "border-1.5 border-neutral text-foreground !bg-transparent",
},
},
color: {
@ -75,31 +75,31 @@ const chip = tv({
size: {
xs: {
base: "px-0.5 h-5 text-xs",
label: "px-0.5",
content: "px-1",
closeButton: "text-sm",
avatar: "w-3.5 h-3.5",
},
sm: {
base: "px-1 h-6 text-sm",
label: "px-1",
content: "px-1",
closeButton: "text-base",
avatar: "w-4 h-4",
},
md: {
base: "px-1 h-7 text-base",
label: "px-1",
content: "px-2",
closeButton: "text-lg",
avatar: "w-5 h-5",
},
lg: {
base: "px-2 h-8 text-lg",
label: "px-1",
content: "px-2",
closeButton: "text-xl",
avatar: "w-6 h-6",
},
xl: {
base: "px-2 h-9 text-xl",
label: "px-1",
content: "px-2",
closeButton: "text-2xl",
avatar: "w-7 h-7",
},
@ -118,7 +118,7 @@ const chip = tv({
isOneChar: {
true: {
base: "px-0 justify-center",
label: "px-0 flex-none",
content: "px-0 flex-none",
},
},
isDisabled: {

View File

@ -29,7 +29,7 @@ const tooltip = tv({
solid: "",
bordered: "border-2 !bg-transparent",
light: "!bg-transparent",
faded: "border-2",
faded: "border-1.5",
flat: "",
shadow: "",
},

View File

@ -1,4 +1,6 @@
export * from "./components";
export * from "./utils";
export * from "./colors";
export {tv} from "tailwind-variants";
export type {VariantProps} from "tailwind-variants";

View File

@ -5,12 +5,10 @@ module.exports = {
content: [
"../components/**/src/**/*.{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}",
],
darkMode: "class",
plugins: [nextui(
{
defaultTheme: 'dark'
}
)],
plugins: [nextui()],
};

View File

@ -1,4 +1,6 @@
export const AvatarIcon = () => (
import {IconSvgProps} from "./types";
export const AvatarIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
@ -7,6 +9,7 @@ export const AvatarIcon = () => (
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<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"

View File

@ -1,5 +1,28 @@
export const CheckIcon = () => (
import {IconSvgProps} from "./types";
export interface CheckIconProps extends IconSvgProps {
filled?: boolean;
}
export const CheckIcon = ({filled = false, ...props}: CheckIconProps) =>
filled ? (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
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"
@ -9,6 +32,7 @@ export const CheckIcon = () => (
strokeWidth={2}
viewBox="0 0 24 24"
width="1em"
{...props}
>
<polyline points="20 6 9 17 4 12" />
</svg>

View File

@ -1,4 +1,6 @@
export const CloseFilledIcon = () => (
import {IconSvgProps} from "./types";
export const CloseFilledIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
focusable="false"
@ -6,6 +8,7 @@ export const CloseFilledIcon = () => (
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<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"

View File

@ -1,4 +1,6 @@
export const CloseIcon = () => (
import {IconSvgProps} from "./types";
export const CloseIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
@ -11,6 +13,7 @@ export const CloseIcon = () => (
strokeWidth={2}
viewBox="0 0 24 24"
width="1em"
{...props}
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>

View File

@ -1,4 +1,6 @@
export const CopyIcon = () => (
import {IconSvgProps} from "./types";
export const CopyIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
focusable="false"
@ -6,6 +8,7 @@ export const CopyIcon = () => (
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<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"

View File

@ -1,13 +1,13 @@
import * as React from "react";
interface IconProps {
import {IconSvgProps} from "./types";
interface IconProps extends IconSvgProps {
fill?: string;
filled?: boolean;
size?: string | number;
height?: string | number;
width?: string | number;
label?: string;
className?: string;
}
export const Sun: React.FC<IconProps> = ({fill, filled, size, height, width, ...props}) => {

View File

@ -0,0 +1,3 @@
import {SVGProps} from "react";
export type IconSvgProps = SVGProps<SVGSVGElement>;

8
pnpm-lock.yaml generated
View File

@ -421,10 +421,14 @@ importers:
packages/components/checkbox:
specifiers:
'@nextui-org/chip': workspace:*
'@nextui-org/dom-utils': workspace:*
'@nextui-org/link': workspace:*
'@nextui-org/shared-icons': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
'@nextui-org/theme': workspace:*
'@nextui-org/user': workspace:*
'@react-aria/checkbox': ^3.8.0
'@react-aria/focus': ^3.11.0
'@react-aria/interactions': ^3.14.0
@ -449,6 +453,10 @@ importers:
'@react-stately/checkbox': 3.4.0_react@18.2.0
'@react-stately/toggle': 3.5.0_react@18.2.0
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/shared': 3.17.0_react@18.2.0
clean-package: 2.2.0