mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat: checkbox group migrated, only tests missing
This commit is contained in:
parent
77f2b0950e
commit
c302fa6e73
@ -1,18 +1,22 @@
|
||||
import type {ButtonProps} from "./index";
|
||||
import type {ReactRef} from "@nextui-org/shared-utils";
|
||||
import type {ButtonGroupVariantProps} from "@nextui-org/theme";
|
||||
import type {AriaButtonProps} from "@react-types/button";
|
||||
|
||||
import {buttonGroup} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {useMemo, useCallback} from "react";
|
||||
export interface UseButtonGroupProps
|
||||
extends HTMLNextUIProps<"div", Omit<ButtonProps, "ref" | "fullWidth">>,
|
||||
ButtonGroupVariantProps {
|
||||
interface Props extends HTMLNextUIProps<"div">, ButtonGroupVariantProps {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLDivElement | null>;
|
||||
/**
|
||||
* Whether the buttons are disabled.
|
||||
* @default false
|
||||
*/
|
||||
isDisabled?: ButtonProps["isDisabled"];
|
||||
}
|
||||
|
||||
export type ContextType = {
|
||||
@ -26,6 +30,9 @@ export type ContextType = {
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export type UseButtonGroupProps = Props &
|
||||
Omit<ButtonProps, "ref" | "fullWidth" | keyof AriaButtonProps>;
|
||||
|
||||
export function useButtonGroup(originalProps: UseButtonGroupProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, buttonGroup.variantKeys);
|
||||
|
||||
|
||||
@ -2,15 +2,15 @@ import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {Checkbox} from "../src";
|
||||
import {CheckboxGroup, Checkbox} from "../src";
|
||||
|
||||
describe("Checkbox.Group", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(
|
||||
<Checkbox.Group defaultValue={[]} label="Select cities">
|
||||
<CheckboxGroup defaultValue={[]} label="Select cities">
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
@ -20,10 +20,10 @@ describe("Checkbox.Group", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(
|
||||
<Checkbox.Group ref={ref} defaultValue={[]} label="Select cities">
|
||||
<CheckboxGroup ref={ref} defaultValue={[]} label="Select cities">
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
expect(ref.current).not.toBeNull();
|
||||
@ -31,14 +31,14 @@ describe("Checkbox.Group", () => {
|
||||
|
||||
it("should work correctly with initial value", () => {
|
||||
const {container} = render(
|
||||
<Checkbox.Group defaultValue={["sydney"]} label="Select cities">
|
||||
<CheckboxGroup defaultValue={["sydney"]} label="Select cities">
|
||||
<Checkbox data-testid="first-checkbox" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox data-testid="second-checkbox" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
// check if the first checkbox is checked
|
||||
@ -51,7 +51,7 @@ describe("Checkbox.Group", () => {
|
||||
it("should change value after click", () => {
|
||||
let value = ["sydney"];
|
||||
const {container} = render(
|
||||
<Checkbox.Group
|
||||
<CheckboxGroup
|
||||
defaultValue={["sydney"]}
|
||||
label="Select cities"
|
||||
onChange={(val) => (value = val)}
|
||||
@ -62,7 +62,7 @@ describe("Checkbox.Group", () => {
|
||||
<Checkbox data-testid="second-checkbox" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
const firstCheckbox = container.querySelector("[data-testid=first-checkbox] input");
|
||||
@ -81,14 +81,14 @@ describe("Checkbox.Group", () => {
|
||||
|
||||
it("should ignore events when disabled", () => {
|
||||
const {container} = render(
|
||||
<Checkbox.Group isDisabled defaultValue={["sydney"]} label="Select cities">
|
||||
<CheckboxGroup isDisabled defaultValue={["sydney"]} label="Select cities">
|
||||
<Checkbox data-testid="first-checkbox" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox data-testid="second-checkbox" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
const firstCheckbox = container.querySelector("[data-testid=first-checkbox] input");
|
||||
@ -110,14 +110,14 @@ describe("Checkbox.Group", () => {
|
||||
});
|
||||
|
||||
const {container} = render(
|
||||
<Checkbox.Group label="Select cities" value={checked} onChange={onChange}>
|
||||
<CheckboxGroup label="Select cities" value={checked} onChange={onChange}>
|
||||
<Checkbox data-testid="first-checkbox" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox data-testid="second-checkbox" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
</CheckboxGroup>,
|
||||
);
|
||||
|
||||
const secondCheckbox = container.querySelector("[data-testid=second-checkbox] input");
|
||||
|
||||
@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {Checkbox} from "../src";
|
||||
import {Checkbox, CheckboxProps} from "../src";
|
||||
|
||||
describe("Checkbox", () => {
|
||||
it("should render correctly", () => {
|
||||
@ -14,30 +14,33 @@ describe("Checkbox", () => {
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLLabelElement>();
|
||||
|
||||
render(<Checkbox ref={ref} label="checkbox-test" />);
|
||||
render(<Checkbox ref={ref}>Option</Checkbox>);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should work correctly with initial value", () => {
|
||||
let {container} = render(<Checkbox isSelected label="checkbox-test" />);
|
||||
let {container} = render(<Checkbox isSelected>Option</Checkbox>);
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(true);
|
||||
|
||||
container = render(<Checkbox isSelected={false} label="checkbox-test" />).container;
|
||||
container = render(<Checkbox isSelected={false}>Option</Checkbox>).container;
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(false);
|
||||
});
|
||||
|
||||
it("should change value after click", () => {
|
||||
const {container} = render(<Checkbox label="checkbox-test" />);
|
||||
const wrapper = render(<Checkbox data-testid="checkbox-test">Option</Checkbox>);
|
||||
const checkbox = wrapper.container.querySelector("input")!;
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(true);
|
||||
wrapper.getByTestId("checkbox-test").click();
|
||||
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
|
||||
it("should ignore events when disabled", () => {
|
||||
const {container} = render(<Checkbox isDisabled label="checkbox-test" />);
|
||||
const {container} = render(<Checkbox isDisabled>Option</Checkbox>);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
|
||||
@ -45,59 +48,74 @@ describe("Checkbox", () => {
|
||||
});
|
||||
|
||||
it("should work correctly with indeterminate value", () => {
|
||||
const {container} = render(<Checkbox isIndeterminate label="checkbox-test" />);
|
||||
const {container} = render(<Checkbox isIndeterminate>Option</Checkbox>);
|
||||
|
||||
expect(container.querySelector("input")?.indeterminate).toBe(true);
|
||||
});
|
||||
|
||||
it('should work correctly with "onChange" prop', () => {
|
||||
const onChange = jest.fn();
|
||||
const {container} = render(<Checkbox label="checkbox-test" onChange={onChange} />);
|
||||
const wrapper = render(
|
||||
<Checkbox data-testid="checkbox-test" onChange={onChange}>
|
||||
Option
|
||||
</Checkbox>,
|
||||
);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
wrapper.getByTestId("checkbox-test").click();
|
||||
|
||||
expect(onChange).toBeCalled();
|
||||
});
|
||||
|
||||
it('should work correctly with "onFocus" prop', () => {
|
||||
const onFocus = jest.fn();
|
||||
const {container} = render(<Checkbox label="checkbox-test" onFocus={onFocus} />);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
const wrapper = render(
|
||||
<Checkbox data-testid="checkbox-test" onFocus={onFocus}>
|
||||
Option
|
||||
</Checkbox>,
|
||||
);
|
||||
|
||||
wrapper.getByTestId("checkbox-test").focus();
|
||||
|
||||
expect(onFocus).toBeCalled();
|
||||
});
|
||||
|
||||
it('should work correctly with "isRequired" prop', () => {
|
||||
const {container} = render(<Checkbox isRequired label="checkbox-test" />);
|
||||
const {container} = render(<Checkbox isRequired>Option</Checkbox>);
|
||||
|
||||
expect(container.querySelector("input")?.required).toBe(true);
|
||||
});
|
||||
|
||||
// it("should work correctly with controlled value", () => {
|
||||
// const onChange = jest.fn();
|
||||
it("should work correctly with controlled value", () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
// const Component = (props: CheckboxProps) => {
|
||||
// const [value, setValue] = React.useState(false);
|
||||
const Component = (props: CheckboxProps) => {
|
||||
const [value, setValue] = React.useState(false);
|
||||
|
||||
// return (
|
||||
// <Checkbox
|
||||
// {...props}
|
||||
// isSelected={value}
|
||||
// onChange={(checked) => {
|
||||
// setValue(checked);
|
||||
// onChange(checked);
|
||||
// }}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
return (
|
||||
<Checkbox
|
||||
{...props}
|
||||
isSelected={value}
|
||||
onChange={(checked) => {
|
||||
setValue(checked);
|
||||
onChange(checked);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// const {container} = render(<Component label="checkbox-test" onChange={onChange} />);
|
||||
const wrapper = render(
|
||||
<Component data-testid="checkbox-test" onChange={onChange}>
|
||||
Option
|
||||
</Component>,
|
||||
);
|
||||
|
||||
// userEvent.click(container.querySelector("label")!);
|
||||
wrapper.getByTestId("checkbox-test").click();
|
||||
|
||||
// expect(onChange).toBeCalled();
|
||||
const input = wrapper.container.querySelector("input")!;
|
||||
|
||||
// expect(container.querySelector("input")?.getAttribute("aria-checked")).toBe("true");
|
||||
// });
|
||||
expect(onChange).toBeCalled();
|
||||
|
||||
expect(input?.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,64 +0,0 @@
|
||||
import {styled} from "@nextui-org/system";
|
||||
|
||||
import {StyledCheckbox} from "./checkbox.styles";
|
||||
|
||||
export const StyledCheckboxGroupContainer = styled("div", {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
variants: {
|
||||
isRow: {
|
||||
true: {
|
||||
flexDirection: "row",
|
||||
mt: 0,
|
||||
[`& ${StyledCheckbox}`]: {
|
||||
mr: "$$checkboxSize",
|
||||
},
|
||||
},
|
||||
false: {
|
||||
mr: 0,
|
||||
flexDirection: "column",
|
||||
[`& ${StyledCheckbox}:not(:first-child)`]: {
|
||||
mt: "$$checkboxSize",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const StyledCheckboxGroup = styled("div", {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
"& .nextui-checkbox-group-label": {
|
||||
d: "block",
|
||||
fontWeight: "$normal",
|
||||
fontSize: "calc($$checkboxSize * 0.8)",
|
||||
color: "$accents7",
|
||||
mb: "$3",
|
||||
},
|
||||
variants: {
|
||||
size: {
|
||||
xs: {
|
||||
$$checkboxSize: "$space$7",
|
||||
},
|
||||
sm: {
|
||||
$$checkboxSize: "$space$8",
|
||||
},
|
||||
md: {
|
||||
$$checkboxSize: "$space$9",
|
||||
},
|
||||
lg: {
|
||||
$$checkboxSize: "$space$10",
|
||||
},
|
||||
xl: {
|
||||
$$checkboxSize: "$space$11",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
"& .nextui-checkbox-group-label": {
|
||||
color: "$accents5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1,39 +1,22 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, __DEV__} from "@nextui-org/shared-utils";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {__DEV__} from "@nextui-org/shared-utils";
|
||||
|
||||
import {CheckboxGroupProvider} from "./checkbox-group-context";
|
||||
import {StyledCheckboxGroup, StyledCheckboxGroupContainer} from "./checkbox-group.styles";
|
||||
import {UseCheckboxGroupProps, useCheckboxGroup} from "./use-checkbox-group";
|
||||
|
||||
export interface CheckboxGroupProps extends UseCheckboxGroupProps {}
|
||||
export interface CheckboxGroupProps extends Omit<UseCheckboxGroupProps, "ref"> {}
|
||||
|
||||
const CheckboxGroup = forwardRef<CheckboxGroupProps, "div">((props, ref) => {
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {children, orientation, groupProps, labelProps, label, context, className, ...otherProps} =
|
||||
useCheckboxGroup(props);
|
||||
const {children, context, label, getGroupProps, getLabelProps, getWrapperProps} =
|
||||
useCheckboxGroup({ref, ...props});
|
||||
|
||||
return (
|
||||
<StyledCheckboxGroup
|
||||
ref={domRef}
|
||||
className={clsx("nextui-checkbox-group", className)}
|
||||
{...mergeProps(groupProps, otherProps)}
|
||||
>
|
||||
{label && (
|
||||
<label className="nextui-checkbox-group-label" {...labelProps}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<StyledCheckboxGroupContainer
|
||||
className="nextui-checkbox-group-items"
|
||||
isRow={orientation === "horizontal"}
|
||||
role="presentation"
|
||||
>
|
||||
<div {...getGroupProps()}>
|
||||
{label && <label {...getLabelProps()}>{label}</label>}
|
||||
<div {...getWrapperProps()}>
|
||||
<CheckboxGroupProvider value={context}>{children}</CheckboxGroupProvider>
|
||||
</StyledCheckboxGroupContainer>
|
||||
</StyledCheckboxGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@ -41,6 +24,4 @@ if (__DEV__) {
|
||||
CheckboxGroup.displayName = "NextUI.CheckboxGroup";
|
||||
}
|
||||
|
||||
CheckboxGroup.toString = () => ".nextui-checkbox-group";
|
||||
|
||||
export default CheckboxGroup;
|
||||
|
||||
46
packages/components/checkbox/src/checkbox-icon.tsx
Normal file
46
packages/components/checkbox/src/checkbox-icon.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import type {UseCheckboxReturn} from "./use-checkbox";
|
||||
|
||||
type CheckboxIconProps = Partial<ReturnType<UseCheckboxReturn["getIconProps"]>>;
|
||||
|
||||
function CheckIcon(props: CheckboxIconProps) {
|
||||
return (
|
||||
<svg aria-hidden="true" role="presentation" viewBox="0 0 17 18" {...props}>
|
||||
<polyline
|
||||
fill="none"
|
||||
points="1 9 7 14 15 4"
|
||||
stroke="currentColor"
|
||||
strokeDasharray={22}
|
||||
strokeDashoffset={props.isSelected ? 44 : 66}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
style={
|
||||
!props.disableAnimation
|
||||
? {
|
||||
transition: "stroke-dashoffset 250ms ease 0s",
|
||||
transitionDelay: "250ms",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function IndeterminateIcon(props: CheckboxIconProps) {
|
||||
return (
|
||||
<svg stroke="currentColor" strokeWidth={3} viewBox="0 0 24 24" {...props}>
|
||||
<line x1="21" x2="3" y1="12" y2="12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckboxIcon is used to visually indicate the checked or indeterminate
|
||||
* state of a checkbox.
|
||||
*/
|
||||
export function CheckboxIcon(props: CheckboxIconProps) {
|
||||
const BaseIcon = props.isIndeterminate ? IndeterminateIcon : CheckIcon;
|
||||
|
||||
return <BaseIcon {...props} />;
|
||||
}
|
||||
@ -1,544 +0,0 @@
|
||||
// import {styled} from "@nextui-org/system";
|
||||
// import {cssFocusVisible} from "@nextui-org/shared-css";
|
||||
|
||||
// export const StyledCheckbox = styled("label", {
|
||||
// $$checkboxBorderColor: "$colors$border",
|
||||
// $$checkboxBorderRadius: "$radii$squared",
|
||||
// d: "inline-flex",
|
||||
// jc: "flex-start",
|
||||
// ai: "center",
|
||||
// position: "relative",
|
||||
// w: "auto",
|
||||
// cursor: "pointer",
|
||||
// zIndex: "$1",
|
||||
// opacity: 1,
|
||||
// transition: "opacity 0.25s ease",
|
||||
// "@motion": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// variants: {
|
||||
// size: {
|
||||
// xs: {
|
||||
// $$checkboxSize: "$space$7",
|
||||
// },
|
||||
// sm: {
|
||||
// $$checkboxSize: "$space$8",
|
||||
// },
|
||||
// md: {
|
||||
// $$checkboxSize: "$space$9",
|
||||
// },
|
||||
// lg: {
|
||||
// $$checkboxSize: "$space$10",
|
||||
// },
|
||||
// xl: {
|
||||
// $$checkboxSize: "$space$11",
|
||||
// },
|
||||
// },
|
||||
// isDisabled: {
|
||||
// true: {
|
||||
// opacity: 0.75,
|
||||
// cursor: "not-allowed",
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// export const StyledCheckboxMask = styled("div", {
|
||||
// $$checkboxMaskTransition:
|
||||
// "transform 0.25s ease 0s, opacity 0.25s ease 0s, background 0.25s ease 0s, border-color 0.25s ease 0s",
|
||||
// size: "100%",
|
||||
// position: "absolute",
|
||||
// pe: "none",
|
||||
// boxSizing: "border-box",
|
||||
// dflex: "center",
|
||||
// zIndex: "-$1",
|
||||
// br: "inherit",
|
||||
// color: "$$checkboxBorderColor",
|
||||
// "&:before": {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// top: "0px",
|
||||
// left: "0px",
|
||||
// size: "100%",
|
||||
// br: "inherit",
|
||||
// transition: "$$checkboxMaskTransition",
|
||||
// zIndex: "-$1",
|
||||
// border: "$borderWeights$normal solid currentColor",
|
||||
// boxSizing: "border-box",
|
||||
// },
|
||||
// "&:after": {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// top: "0px",
|
||||
// left: "0px",
|
||||
// size: "100%",
|
||||
// bg: "$$checkboxColor",
|
||||
// scale: 0.5,
|
||||
// br: "inherit",
|
||||
// opacity: 0,
|
||||
// transition: "$$checkboxMaskTransition",
|
||||
// zIndex: "-$1",
|
||||
// },
|
||||
// "@motion": {
|
||||
// "&:before": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// variants: {
|
||||
// isChecked: {
|
||||
// true: {
|
||||
// "&:before": {
|
||||
// opacity: 0,
|
||||
// scale: 1.2,
|
||||
// },
|
||||
// "&:after": {
|
||||
// opacity: 1,
|
||||
// scale: 1,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// isIndeterminate: {
|
||||
// true: {
|
||||
// "&:before": {
|
||||
// opacity: 0,
|
||||
// scale: 1.2,
|
||||
// },
|
||||
// "&:after": {
|
||||
// opacity: 1,
|
||||
// scale: 1,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// "&:before": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// export const StyledCheckboxText = styled("span", {
|
||||
// position: "relative",
|
||||
// dflex: "center",
|
||||
// color: "$text",
|
||||
// opacity: 1,
|
||||
// pl: "calc($$checkboxSize * 0.57)",
|
||||
// ln: "$$checkboxSize",
|
||||
// fontSize: "$$checkboxSize",
|
||||
// us: "none",
|
||||
// transition: "opacity 0.25s ease 0s",
|
||||
// "@motion": {
|
||||
// transition: "none",
|
||||
// "&:before": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// variants: {
|
||||
// color: {
|
||||
// default: {
|
||||
// color: "$text",
|
||||
// },
|
||||
// primary: {
|
||||
// color: "$primary",
|
||||
// },
|
||||
// secondary: {
|
||||
// color: "$secondary",
|
||||
// },
|
||||
// success: {
|
||||
// color: "$success",
|
||||
// },
|
||||
// warning: {
|
||||
// color: "$warning",
|
||||
// },
|
||||
// error: {
|
||||
// color: "$error",
|
||||
// },
|
||||
// },
|
||||
// lineThrough: {
|
||||
// true: {
|
||||
// "&:before": {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// width: "0px",
|
||||
// height: "2px",
|
||||
// background: "$text",
|
||||
// transition: "width 0.25s ease 0s",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// isChecked: {
|
||||
// true: {
|
||||
// "&:before": {
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// isDisabled: {
|
||||
// true: {
|
||||
// color: "$accents5",
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// transition: "none",
|
||||
// "&:before": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// compoundVariants: [
|
||||
// {
|
||||
// lineThrough: true,
|
||||
// isChecked: true,
|
||||
// css: {
|
||||
// opacity: 0.6,
|
||||
// "&:before": {
|
||||
// w: "calc(100% - 10px)",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const StyledCheckboxContainer = styled(
|
||||
// "div",
|
||||
// {
|
||||
// br: "$$checkboxBorderRadius",
|
||||
// position: "relative",
|
||||
// sizeMin: "$$checkboxSize",
|
||||
// transition: "box-shadow 0.25s ease",
|
||||
// zIndex: "$1",
|
||||
// ".nextui-checkbox-input": {
|
||||
// position: "absolute",
|
||||
// width: "100%",
|
||||
// height: "100%",
|
||||
// top: "0px",
|
||||
// left: "0px",
|
||||
// margin: "0px",
|
||||
// padding: "0px",
|
||||
// opacity: 0,
|
||||
// zIndex: "$1",
|
||||
// cursor: "pointer",
|
||||
// "&:disabled": {
|
||||
// cursor: "not-allowed",
|
||||
// },
|
||||
// },
|
||||
// "@motion": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// variants: {
|
||||
// color: {
|
||||
// default: {
|
||||
// $$checkboxColor: "$colors$primary",
|
||||
// $$checkboxColorHover: "$colors$primarySolidHover",
|
||||
// },
|
||||
// primary: {
|
||||
// $$checkboxColor: "$colors$primary",
|
||||
// $$checkboxColorHover: "$colors$primarySolidHover",
|
||||
// },
|
||||
// secondary: {
|
||||
// $$checkboxColor: "$colors$secondary",
|
||||
// $$checkboxColorHover: "$colors$secondarySolidHover",
|
||||
// },
|
||||
// success: {
|
||||
// $$checkboxColor: "$colors$success",
|
||||
// $$checkboxColorHover: "$colors$successSolidHover",
|
||||
// },
|
||||
// warning: {
|
||||
// $$checkboxColor: "$colors$warning",
|
||||
// $$checkboxColorHover: "$colors$warningSolidHover",
|
||||
// },
|
||||
// error: {
|
||||
// $$checkboxColor: "$colors$error",
|
||||
// $$checkboxColorHover: "$colors$errorSolidHover",
|
||||
// },
|
||||
// gradient: {
|
||||
// $$checkboxColor: "$colors$gradient",
|
||||
// $$checkboxColorHover: "$colors$gradient",
|
||||
// },
|
||||
// },
|
||||
// isRounded: {
|
||||
// true: {
|
||||
// $$checkboxBorderRadius: "$radii$pill",
|
||||
// },
|
||||
// },
|
||||
// isDisabled: {
|
||||
// true: {
|
||||
// opacity: 0.4,
|
||||
// cursor: "not-allowed",
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// isHovered: {
|
||||
// true: {
|
||||
// [`& ${StyledCheckboxMask}:before`]: {
|
||||
// bg: "$$checkboxBorderColor",
|
||||
// border: "2px solid transparent",
|
||||
// },
|
||||
// [`& ${StyledCheckboxMask}:after`]: {
|
||||
// bg: "$$checkboxColorHover",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// cssFocusVisible,
|
||||
// );
|
||||
|
||||
// export const StyledIconCheck = styled("i", {
|
||||
// size: "$$checkboxSize",
|
||||
// dflex: "center",
|
||||
// br: "inherit",
|
||||
// opacity: 0,
|
||||
// zIndex: "$2",
|
||||
// transition: "transform 0.35s ease",
|
||||
// "&:after": {
|
||||
// content: "",
|
||||
// opacity: 0,
|
||||
// position: "relative",
|
||||
// width: "10px",
|
||||
// height: "2px",
|
||||
// br: "1px",
|
||||
// background: "$white",
|
||||
// display: "block",
|
||||
// },
|
||||
// "@motion": {
|
||||
// transition: "none",
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// variants: {
|
||||
// isIndeterminate: {
|
||||
// true: {
|
||||
// opacity: 1,
|
||||
// transform: "rotate(0deg)",
|
||||
// width: "auto",
|
||||
// height: "auto",
|
||||
// margin: "0px",
|
||||
// "&:after": {
|
||||
// opacity: 1,
|
||||
// },
|
||||
// },
|
||||
// false: {
|
||||
// width: "8px",
|
||||
// height: "14px",
|
||||
// display: "block",
|
||||
// position: "relative",
|
||||
// marginTop: "-4px",
|
||||
// },
|
||||
// },
|
||||
// size: {
|
||||
// xs: {
|
||||
// marginTop: "-2px",
|
||||
// transform: "rotate(45deg) scale(0.5)",
|
||||
// },
|
||||
// sm: {
|
||||
// marginTop: "-2px",
|
||||
// transform: "rotate(45deg) scale(0.5)",
|
||||
// },
|
||||
// md: {
|
||||
// transform: "rotate(45deg) scale(0.8)",
|
||||
// },
|
||||
// lg: {
|
||||
// transform: "rotate(45deg)",
|
||||
// },
|
||||
// xl: {
|
||||
// transform: "rotate(45deg)",
|
||||
// },
|
||||
// },
|
||||
// isChecked: {
|
||||
// true: {
|
||||
// opacity: 1,
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// transition: "none",
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// compoundVariants: [
|
||||
// // isIndeterminate && xs size
|
||||
// {
|
||||
// isIndeterminate: true,
|
||||
// size: "xs",
|
||||
// css: {
|
||||
// scale: "0.5",
|
||||
// },
|
||||
// },
|
||||
// // isIndeterminate && sm size
|
||||
// {
|
||||
// isIndeterminate: true,
|
||||
// size: "sm",
|
||||
// css: {
|
||||
// scale: "0.5",
|
||||
// },
|
||||
// },
|
||||
// // isIndeterminate && md size
|
||||
// {
|
||||
// isIndeterminate: true,
|
||||
// size: "md",
|
||||
// css: {
|
||||
// scale: "0.8",
|
||||
// },
|
||||
// },
|
||||
// // isIndeterminate && lg size
|
||||
// {
|
||||
// isIndeterminate: true,
|
||||
// size: "lg",
|
||||
// css: {
|
||||
// transform: "none",
|
||||
// },
|
||||
// },
|
||||
// // isIndeterminate && xl size
|
||||
// {
|
||||
// isIndeterminate: true,
|
||||
// size: "lg",
|
||||
// css: {
|
||||
// transform: "none",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const StyledIconCheckFirstLine = styled("div", {
|
||||
// content: "",
|
||||
// background: "transparent",
|
||||
// position: "absolute",
|
||||
// width: "8px",
|
||||
// height: "1px",
|
||||
// br: "5px",
|
||||
// zIndex: "$1",
|
||||
// bottom: "0px",
|
||||
// "&:after": {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// left: "0px",
|
||||
// width: "0%",
|
||||
// height: "2px",
|
||||
// background: "$white",
|
||||
// br: "5px 0px 0px 5px",
|
||||
// },
|
||||
// "@motion": {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// variants: {
|
||||
// isIndeterminate: {
|
||||
// true: {
|
||||
// display: "none",
|
||||
// },
|
||||
// },
|
||||
// isChecked: {
|
||||
// true: {
|
||||
// "&:after": {
|
||||
// width: "100%",
|
||||
// transition: "width 0.25s ease 0.1s",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// compoundVariants: [
|
||||
// // checked && disableAnimation
|
||||
// {
|
||||
// isChecked: true,
|
||||
// disableAnimation: true,
|
||||
// css: {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const StyledIconCheckSecondLine = styled("div", {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// h: "13px",
|
||||
// br: "5px",
|
||||
// bottom: "0",
|
||||
// right: "0",
|
||||
// zIndex: "$1",
|
||||
// background: "transparent",
|
||||
// width: "2px",
|
||||
// "&:after": {
|
||||
// content: "",
|
||||
// position: "absolute",
|
||||
// width: "2px",
|
||||
// height: "0%",
|
||||
// background: "$white",
|
||||
// left: "0px",
|
||||
// bottom: "0px",
|
||||
// br: "5px 5px 0px 0px",
|
||||
// },
|
||||
// "@motion": {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// variants: {
|
||||
// isIndeterminate: {
|
||||
// true: {
|
||||
// display: "none",
|
||||
// },
|
||||
// },
|
||||
// isChecked: {
|
||||
// true: {
|
||||
// "&:after": {
|
||||
// height: "100%",
|
||||
// transition: "height 0.2s ease 0.3s",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// disableAnimation: {
|
||||
// true: {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// compoundVariants: [
|
||||
// // checked && disableAnimation
|
||||
// {
|
||||
// isChecked: true,
|
||||
// disableAnimation: true,
|
||||
// css: {
|
||||
// "&:after": {
|
||||
// transition: "none",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
@ -1,8 +1,10 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {__DEV__} from "@nextui-org/shared-utils";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
import {cloneElement, ReactElement} from "react";
|
||||
|
||||
import {UseCheckboxProps, useCheckbox} from "./use-checkbox";
|
||||
import {CheckboxIcon} from "./checkbox-icon";
|
||||
|
||||
export interface CheckboxProps extends Omit<UseCheckboxProps, "ref"> {}
|
||||
|
||||
@ -10,10 +12,9 @@ const Checkbox = forwardRef<CheckboxProps, "label">((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
children,
|
||||
isChecked,
|
||||
disableAnimation,
|
||||
icon = <CheckboxIcon />,
|
||||
getBaseProps,
|
||||
getCheckboxProps,
|
||||
getWrapperProps,
|
||||
getInputProps,
|
||||
getIconProps,
|
||||
getLabelProps,
|
||||
@ -22,33 +23,14 @@ const Checkbox = forwardRef<CheckboxProps, "label">((props, ref) => {
|
||||
...props,
|
||||
});
|
||||
|
||||
const clonedIcon = cloneElement(icon as ReactElement, getIconProps());
|
||||
|
||||
return (
|
||||
<Component {...getBaseProps()}>
|
||||
<VisuallyHidden>
|
||||
<input {...getInputProps()} />
|
||||
</VisuallyHidden>
|
||||
<span {...getCheckboxProps()}>
|
||||
<svg aria-hidden="true" {...getIconProps()} role="presentation" viewBox="0 0 18 18">
|
||||
<polyline
|
||||
fill="none"
|
||||
points="1 9 7 14 15 4"
|
||||
stroke="currentColor"
|
||||
strokeDasharray={22}
|
||||
strokeDashoffset={isChecked ? 44 : 66}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
style={
|
||||
!disableAnimation
|
||||
? {
|
||||
transition: "all 350ms",
|
||||
transitionDelay: "200ms",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span {...getWrapperProps()}>{clonedIcon}</span>
|
||||
{children && <span {...getLabelProps()}>{children}</span>}
|
||||
</Component>
|
||||
);
|
||||
@ -58,6 +40,4 @@ if (__DEV__) {
|
||||
Checkbox.displayName = "NextUI.Checkbox";
|
||||
}
|
||||
|
||||
Checkbox.toString = () => ".nextui-checkbox";
|
||||
|
||||
export default Checkbox;
|
||||
|
||||
@ -1,50 +1,49 @@
|
||||
import type {AriaCheckboxGroupProps} from "@react-types/checkbox";
|
||||
import type {CheckboxGroupSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
import type {AriaCheckboxGroupProps, AriaCheckboxProps} from "@react-types/checkbox";
|
||||
import type {Orientation} from "@react-types/shared";
|
||||
import type {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import type {ReactRef} from "@nextui-org/shared-utils";
|
||||
|
||||
import {useMemo} from "react";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {checkboxGroup} from "@nextui-org/theme";
|
||||
import {useCheckboxGroup as useReactAriaCheckboxGroup} from "@react-aria/checkbox";
|
||||
import {CheckboxGroupState, useCheckboxGroupState} from "@react-stately/checkbox";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {CheckboxProps} from "./index";
|
||||
|
||||
export interface UseCheckboxGroupProps extends HTMLNextUIProps<"div", AriaCheckboxGroupProps> {
|
||||
interface Props extends HTMLNextUIProps<"div", AriaCheckboxGroupProps> {
|
||||
/**
|
||||
* The color of the checkboxes.
|
||||
* @default "default"
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
color?: CheckboxProps["color"];
|
||||
/**
|
||||
* The size of the checkboxes.
|
||||
* @default "md"
|
||||
*/
|
||||
size?: CheckboxProps["size"];
|
||||
/**
|
||||
* The radius of the checkboxes.
|
||||
* @default "lg"
|
||||
*/
|
||||
radius?: CheckboxProps["radius"];
|
||||
/**
|
||||
* Whether the checkboxes should have a line through.
|
||||
* @default false
|
||||
*/
|
||||
lineThrough?: CheckboxProps["lineThrough"];
|
||||
/**
|
||||
* Whether the checkboxes are disabled.
|
||||
* @default false
|
||||
*/
|
||||
isDisabled?: CheckboxProps["isDisabled"];
|
||||
/**
|
||||
* Whether the animation should be disabled.
|
||||
* @default false
|
||||
*/
|
||||
disableAnimation?: CheckboxProps["disableAnimation"];
|
||||
ref?: ReactRef<HTMLDivElement | null>;
|
||||
/**
|
||||
* The axis the checkbox group items should align with.
|
||||
* @default "vertical"
|
||||
*/
|
||||
orientation?: Orientation;
|
||||
/**
|
||||
* 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
|
||||
* <CheckboxGroup styles={{
|
||||
* base:"base-classes",
|
||||
* label: "label-classes",
|
||||
* wrapper: "wrapper-classes", // checkboxes wrapper
|
||||
* }} >
|
||||
* // checkboxes
|
||||
* </CheckboxGroup>
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<CheckboxGroupSlots>;
|
||||
}
|
||||
|
||||
export type UseCheckboxGroupProps = Props & Omit<CheckboxProps, "ref" | keyof AriaCheckboxProps>;
|
||||
|
||||
export type ContextType = {
|
||||
groupState: CheckboxGroupState;
|
||||
color?: CheckboxProps["color"];
|
||||
@ -57,16 +56,26 @@ export type ContextType = {
|
||||
|
||||
export function useCheckboxGroup(props: UseCheckboxGroupProps) {
|
||||
const {
|
||||
as,
|
||||
ref,
|
||||
styles,
|
||||
children,
|
||||
label,
|
||||
size = "md",
|
||||
color = "neutral",
|
||||
radius = "lg",
|
||||
color = "primary",
|
||||
radius = "md",
|
||||
orientation = "vertical",
|
||||
lineThrough = false,
|
||||
isDisabled = false,
|
||||
disableAnimation = false,
|
||||
className,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "div";
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const groupState = useCheckboxGroupState(otherProps);
|
||||
|
||||
const {labelProps, groupProps} = useReactAriaCheckboxGroup(otherProps, groupState);
|
||||
@ -81,13 +90,41 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps) {
|
||||
groupState,
|
||||
};
|
||||
|
||||
const slots = useMemo(() => checkboxGroup(), []);
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getGroupProps = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
...mergeProps(groupProps, otherProps),
|
||||
};
|
||||
};
|
||||
|
||||
const getLabelProps = () => {
|
||||
return {
|
||||
className: slots.label({class: styles?.label}),
|
||||
...labelProps,
|
||||
};
|
||||
};
|
||||
|
||||
const getWrapperProps = () => {
|
||||
return {
|
||||
className: slots.wrapper({class: styles?.wrapper}),
|
||||
role: "presentation",
|
||||
"data-orientation": orientation,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
size,
|
||||
orientation,
|
||||
labelProps,
|
||||
groupProps,
|
||||
Component,
|
||||
children,
|
||||
label,
|
||||
context,
|
||||
...otherProps,
|
||||
getGroupProps,
|
||||
getLabelProps,
|
||||
getWrapperProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {ReactNode, Ref, useCallback} from "react";
|
||||
import {useMemo, useRef} from "react";
|
||||
import {useToggleState} from "@react-stately/toggle";
|
||||
import {checkbox} from "@nextui-org/theme";
|
||||
import {useHover, usePress} from "@react-aria/interactions";
|
||||
import {useHover} from "@react-aria/interactions";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {useFocusableRef} from "@nextui-org/dom-utils";
|
||||
@ -19,9 +19,7 @@ import {FocusableRef} from "@react-types/shared";
|
||||
|
||||
import {useCheckboxGroupContext} from "./checkbox-group-context";
|
||||
|
||||
export interface UseCheckboxProps
|
||||
extends HTMLNextUIProps<"label", Omit<AriaCheckboxProps, keyof CheckboxVariantProps>>,
|
||||
Omit<CheckboxVariantProps, "isFocusVisible"> {
|
||||
interface Props extends HTMLNextUIProps<"label"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
@ -35,6 +33,10 @@ export interface UseCheckboxProps
|
||||
* The label of the checkbox.
|
||||
*/
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* The icon to be displayed when the checkbox is checked.
|
||||
*/
|
||||
icon?: 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.
|
||||
@ -52,6 +54,10 @@ export interface UseCheckboxProps
|
||||
styles?: SlotsToClasses<CheckboxSlots>;
|
||||
}
|
||||
|
||||
export type UseCheckboxProps = Omit<Props, "defaultChecked"> &
|
||||
Omit<AriaCheckboxProps, keyof CheckboxVariantProps> &
|
||||
Omit<CheckboxVariantProps, "isFocusVisible">;
|
||||
|
||||
export function useCheckbox(props: UseCheckboxProps) {
|
||||
const groupContext = useCheckboxGroupContext();
|
||||
const isInGroup = !!groupContext;
|
||||
@ -62,10 +68,11 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
isSelected,
|
||||
value = "",
|
||||
children,
|
||||
icon,
|
||||
isRequired = false,
|
||||
size = groupContext?.size ?? "md",
|
||||
color = groupContext?.color ?? "neutral",
|
||||
radius = groupContext?.radius ?? "sm",
|
||||
color = groupContext?.color ?? "primary",
|
||||
radius = groupContext?.radius ?? "md",
|
||||
lineThrough = groupContext?.lineThrough ?? false,
|
||||
isDisabled = groupContext?.isDisabled ?? false,
|
||||
disableAnimation = groupContext?.disableAnimation ?? false,
|
||||
@ -98,16 +105,20 @@ 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 {
|
||||
...otherProps,
|
||||
value,
|
||||
defaultSelected,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
isIndeterminate,
|
||||
isRequired,
|
||||
onChange,
|
||||
"aria-label": arialabel,
|
||||
};
|
||||
}, [isIndeterminate, otherProps]);
|
||||
}, [isIndeterminate, isDisabled]);
|
||||
|
||||
const {inputProps} = isInGroup
|
||||
? // eslint-disable-next-line
|
||||
@ -129,11 +140,6 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
isDisabled: inputProps.disabled,
|
||||
});
|
||||
|
||||
// TODO: Event's propagation wasn't stopped https://github.com/adobe/react-spectrum/issues/2383
|
||||
const {pressProps} = usePress({
|
||||
isDisabled: inputProps.disabled,
|
||||
});
|
||||
|
||||
const {focusProps, isFocused, isFocusVisible} = useFocusRing({
|
||||
autoFocus: inputProps.autoFocus,
|
||||
});
|
||||
@ -162,11 +168,11 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
"data-invalid": dataAttr(otherProps.validationState === "invalid"),
|
||||
...mergeProps(hoverProps, pressProps, otherProps),
|
||||
...mergeProps(hoverProps, otherProps),
|
||||
};
|
||||
};
|
||||
|
||||
const getCheckboxProps = () => {
|
||||
const getWrapperProps = () => {
|
||||
return {
|
||||
"data-hover": dataAttr(isHovered),
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
@ -178,7 +184,6 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
"data-readonly": dataAttr(inputProps.readOnly),
|
||||
"aria-hidden": true,
|
||||
className: clsx(slots.wrapper({class: styles?.wrapper})),
|
||||
...mergeProps(hoverProps, pressProps, otherProps),
|
||||
};
|
||||
};
|
||||
|
||||
@ -202,18 +207,20 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
const getIconProps = useCallback(
|
||||
() => ({
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
isSelected: inputProps.checked,
|
||||
isIndeterminate: !!isIndeterminate,
|
||||
disableAnimation: !!disableAnimation,
|
||||
className: slots.icon({class: styles?.icon}),
|
||||
}),
|
||||
[slots, inputProps.checked],
|
||||
[slots, inputProps.checked, isIndeterminate, disableAnimation],
|
||||
);
|
||||
|
||||
return {
|
||||
Component,
|
||||
icon,
|
||||
children,
|
||||
disableAnimation,
|
||||
isChecked: inputProps.checked,
|
||||
getBaseProps,
|
||||
getCheckboxProps,
|
||||
getWrapperProps,
|
||||
getInputProps,
|
||||
getLabelProps,
|
||||
getIconProps,
|
||||
|
||||
145
packages/components/checkbox/stories/checkbox-group.stories.tsx
Normal file
145
packages/components/checkbox/stories/checkbox-group.stories.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {checkbox} from "@nextui-org/theme";
|
||||
|
||||
import {CheckboxGroup, Checkbox, CheckboxGroupProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Inputs/CheckboxGroup",
|
||||
component: CheckboxGroup,
|
||||
argTypes: {
|
||||
color: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["neutral", "primary", "secondary", "success", "warning", "danger"],
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["none", "base", "sm", "md", "lg", "xl", "full"],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["xs", "sm", "md", "lg", "xl"],
|
||||
},
|
||||
},
|
||||
lineThrough: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Checkbox>;
|
||||
|
||||
const defaultProps = {
|
||||
...checkbox.defaultVariants,
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof CheckboxGroup> = (args: CheckboxGroupProps) => (
|
||||
<CheckboxGroup {...args}>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="san-francisco">San Francisco</Checkbox>
|
||||
<Checkbox value="london">London</Checkbox>
|
||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
</CheckboxGroup>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const WithLabel = Template.bind({});
|
||||
WithLabel.args = {
|
||||
label: "Select cities",
|
||||
};
|
||||
|
||||
export const DefaultValue = Template.bind({});
|
||||
DefaultValue.args = {
|
||||
...defaultProps,
|
||||
label: "Select cities",
|
||||
defaultValue: ["buenos-aires", "london"],
|
||||
};
|
||||
|
||||
export const Horizontal = Template.bind({});
|
||||
Horizontal.args = {
|
||||
label: "Select cities",
|
||||
orientation: "horizontal",
|
||||
};
|
||||
|
||||
export const IsDisabled = Template.bind({});
|
||||
IsDisabled.args = {
|
||||
label: "Select cities",
|
||||
isDisabled: true,
|
||||
};
|
||||
|
||||
export const LineThrough = Template.bind({});
|
||||
LineThrough.args = {
|
||||
label: "Select cities",
|
||||
lineThrough: true,
|
||||
};
|
||||
|
||||
export const DisableAnimation = Template.bind({});
|
||||
DisableAnimation.args = {
|
||||
label: "Select cities",
|
||||
disableAnimation: true,
|
||||
};
|
||||
|
||||
export const Controlled = () => {
|
||||
const [groupSelected, setGroupSelected] = React.useState<string[]>(["buenos-aires", "sydney"]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("CheckboxGroup ", groupSelected);
|
||||
}, [groupSelected]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<CheckboxGroup
|
||||
color="warning"
|
||||
label="Select cities"
|
||||
value={groupSelected}
|
||||
onChange={setGroupSelected}
|
||||
>
|
||||
<Checkbox color="primary" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="london">London</Checkbox>
|
||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Group = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
const handleGroupChange = (value: string[]) => console.log(value);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {checkbox} from "@nextui-org/theme";
|
||||
|
||||
@ -7,12 +8,6 @@ export default {
|
||||
title: "Inputs/Checkbox",
|
||||
component: Checkbox,
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["solid", "bordered", "light", "flat", "faded", "shadow", "dot"],
|
||||
},
|
||||
},
|
||||
color: {
|
||||
control: {
|
||||
type: "select",
|
||||
@ -31,6 +26,11 @@ export default {
|
||||
options: ["xs", "sm", "md", "lg", "xl"],
|
||||
},
|
||||
},
|
||||
lineThrough: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
@ -51,197 +51,83 @@ Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
// export const Default = () => {
|
||||
// return (
|
||||
// <Checkbox color="default" labelColor="default" size="md">
|
||||
// Option
|
||||
// </Checkbox>
|
||||
// );
|
||||
// };
|
||||
export const IsDisabled = Template.bind({});
|
||||
IsDisabled.args = {
|
||||
...defaultProps,
|
||||
isDisabled: true,
|
||||
};
|
||||
|
||||
// export const Label = () => {
|
||||
// return <Checkbox color="default" label="Option" labelColor="default" size="md" />;
|
||||
// };
|
||||
export const DefaultSelected = Template.bind({});
|
||||
DefaultSelected.args = {
|
||||
...defaultProps,
|
||||
defaultSelected: true,
|
||||
};
|
||||
|
||||
// export const Disabled = () => (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected size="xl">
|
||||
// Enabled
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected isDisabled size="xl">
|
||||
// Disabled
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
export const AlwaysSelected = Template.bind({});
|
||||
AlwaysSelected.args = {
|
||||
...defaultProps,
|
||||
isSelected: true,
|
||||
};
|
||||
|
||||
// export const Sizes = () => (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected size="xs">
|
||||
// mini
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected size="sm">
|
||||
// small
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected size="md">
|
||||
// medium
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected size="lg">
|
||||
// large
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected size="xl">
|
||||
// xlarge
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
export const IsIndeterminate = Template.bind({});
|
||||
IsIndeterminate.args = {
|
||||
...defaultProps,
|
||||
isIndeterminate: true,
|
||||
};
|
||||
|
||||
// export const Colors = () => (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected color="primary">
|
||||
// Primary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="secondary">
|
||||
// Secondary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="success">
|
||||
// Success
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="warning">
|
||||
// Warning
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="error">
|
||||
// Error
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
export const LineThrough = Template.bind({});
|
||||
LineThrough.args = {
|
||||
...defaultProps,
|
||||
lineThrough: true,
|
||||
};
|
||||
|
||||
// export const LabelColors = () => (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected color="primary" labelColor="primary">
|
||||
// Primary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="secondary" labelColor="secondary">
|
||||
// Secondary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="success" labelColor="success">
|
||||
// Success
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="warning" labelColor="warning">
|
||||
// Warning
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected color="error" labelColor="error">
|
||||
// Error
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
export const DisableAnimation = Template.bind({});
|
||||
DisableAnimation.args = {
|
||||
...defaultProps,
|
||||
disableAnimation: true,
|
||||
};
|
||||
|
||||
// export const Rounded = () => (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected isRounded color="primary">
|
||||
// Primary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected isRounded color="secondary">
|
||||
// Secondary
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected isRounded color="success">
|
||||
// Success
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected isRounded color="warning">
|
||||
// Warning
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected isRounded color="error">
|
||||
// Error
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
export const Controlled = () => {
|
||||
const [selected, setSelected] = React.useState<boolean>(true);
|
||||
|
||||
// export const Indeterminate = () => {
|
||||
// return (
|
||||
// <Checkbox defaultSelected isIndeterminate color="primary" size="lg">
|
||||
// Option
|
||||
// </Checkbox>
|
||||
// );
|
||||
// };
|
||||
// const [groupSelected, setGroupSelected] = React.useState<string[]>(["buenos-aires", "sydney"]);
|
||||
|
||||
// export const LineThrough = () => {
|
||||
// return (
|
||||
// <Checkbox defaultSelected lineThrough color="primary" size="lg">
|
||||
// Option
|
||||
// </Checkbox>
|
||||
// );
|
||||
// };
|
||||
React.useEffect(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Checkbox ", selected);
|
||||
}, [selected]);
|
||||
|
||||
// export const Controlled = () => {
|
||||
// const [selected, setSelected] = React.useState<boolean>(true);
|
||||
// React.useEffect(() => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log("CheckboxGroup ", groupSelected);
|
||||
// }, [groupSelected]);
|
||||
|
||||
// const [groupSelected, setGroupSelected] = React.useState<string[]>(["buenos-aires", "sydney"]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log("Checkbox ", selected);
|
||||
// }, [selected]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log("CheckboxGroup ", groupSelected);
|
||||
// }, [groupSelected]);
|
||||
|
||||
// return (
|
||||
// <div style={{display: "flex", flexDirection: "row", gap: 200}}>
|
||||
// <Checkbox color="success" isSelected={selected} onChange={setSelected}>
|
||||
// Subscribe (controlled)
|
||||
// </Checkbox>
|
||||
// <Checkbox.Group
|
||||
// color="warning"
|
||||
// label="Select cities"
|
||||
// labelColor="primary"
|
||||
// value={groupSelected}
|
||||
// onChange={setGroupSelected}
|
||||
// >
|
||||
// <Checkbox color="primary" value="buenos-aires">
|
||||
// Buenos Aires
|
||||
// </Checkbox>
|
||||
// <Checkbox labelColor="warning" value="sydney">
|
||||
// Sydney
|
||||
// </Checkbox>
|
||||
// <Checkbox labelColor="error" value="london">
|
||||
// London
|
||||
// </Checkbox>
|
||||
// <Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
// </Checkbox.Group>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export const NoAnimated = () => {
|
||||
// return (
|
||||
// <div style={{display: "flex", flexDirection: "column"}}>
|
||||
// <Checkbox defaultSelected disableAnimation={true} size="md">
|
||||
// Option
|
||||
// </Checkbox>
|
||||
// <br />
|
||||
// <Checkbox defaultSelected lineThrough disableAnimation={true} size="md">
|
||||
// Option
|
||||
// </Checkbox>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Checkbox isSelected={selected} onChange={setSelected} {...checkbox.defaultVariants}>
|
||||
Subscribe (controlled)
|
||||
</Checkbox>
|
||||
{/* <Checkbox.Group
|
||||
color="warning"
|
||||
label="Select cities"
|
||||
labelColor="primary"
|
||||
value={groupSelected}
|
||||
onChange={setGroupSelected}
|
||||
>
|
||||
<Checkbox color="primary" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
<Checkbox labelColor="warning" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox labelColor="error" value="london">
|
||||
London
|
||||
</Checkbox>
|
||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
</Checkbox.Group> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// export const Group = () => {
|
||||
// // eslint-disable-next-line no-console
|
||||
|
||||
26
packages/core/theme/src/components/checkbox-group.ts
Normal file
26
packages/core/theme/src/components/checkbox-group.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
/**
|
||||
* CheckboxGroup wrapper **Tailwind Variants** component
|
||||
*
|
||||
* const {base, label, wrapper,} = checkboxGroup({...})
|
||||
*
|
||||
* @example
|
||||
* <div className={base())}>
|
||||
* <label className={label()}>Label</label>
|
||||
* <div className={wrapper()} data-orientation="vertical/horizontal">
|
||||
* // checkboxes
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
const checkboxGroup = tv({
|
||||
slots: {
|
||||
base: "relative flex flex-col gap-2",
|
||||
label: "relative text-neutral-500",
|
||||
wrapper: "flex flex-col flex-wrap gap-2 data-[orientation=horizontal]:flex-row ",
|
||||
},
|
||||
});
|
||||
|
||||
export type CheckboxGroupSlots = keyof ReturnType<typeof checkboxGroup>;
|
||||
|
||||
export {checkboxGroup};
|
||||
@ -20,7 +20,7 @@ import {ringClasses} from "../utils";
|
||||
*/
|
||||
const checkbox = tv({
|
||||
slots: {
|
||||
base: "relative inline-flex items-center justify-start cursor-pointer",
|
||||
base: "relative max-w-fit inline-flex items-center justify-start cursor-pointer",
|
||||
wrapper: [
|
||||
"relative",
|
||||
"inline-flex",
|
||||
@ -50,7 +50,7 @@ const checkbox = tv({
|
||||
"data-[hover=true]:before:bg-neutral",
|
||||
],
|
||||
icon: "z-10 w-4 h-3 opacity-0 data-[checked=true]:opacity-100",
|
||||
label: "ml-1 text-foreground",
|
||||
label: "relative ml-1 text-foreground select-none",
|
||||
},
|
||||
variants: {
|
||||
color: {
|
||||
@ -125,32 +125,48 @@ const checkbox = tv({
|
||||
},
|
||||
lineThrough: {
|
||||
true: {
|
||||
label: "line-through",
|
||||
label: [
|
||||
"inline-flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"before:content-['']",
|
||||
"before:absolute",
|
||||
"before:bg-foreground",
|
||||
"before:w-0",
|
||||
"before:h-0.5",
|
||||
"data-[checked=true]:opacity-60",
|
||||
"data-[checked=true]:before:w-full",
|
||||
],
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
base: "opacity-50 cursor-not-allowed",
|
||||
base: "opacity-50 pointer-events-none",
|
||||
},
|
||||
},
|
||||
isFocusVisible: {
|
||||
true: [...ringClasses],
|
||||
true: {
|
||||
wrapper: [...ringClasses],
|
||||
},
|
||||
},
|
||||
disableAnimation: {
|
||||
true: {
|
||||
wrapper: "transition-none",
|
||||
},
|
||||
false: {
|
||||
wrapper: "before:transition-background after:transition-transform-opacity",
|
||||
wrapper:
|
||||
"before:transition-background after:transition-transform-opacity before:!duration-250 after:!duration-250",
|
||||
icon: "transition-opacity",
|
||||
label: "transition-opacity before:transition-width",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "neutral",
|
||||
color: "primary",
|
||||
size: "md",
|
||||
radius: "md",
|
||||
isDisabled: false,
|
||||
lineThrough: false,
|
||||
disableAnimation: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -12,3 +12,4 @@ export * from "./snippet";
|
||||
export * from "./chip";
|
||||
export * from "./badge";
|
||||
export * from "./checkbox";
|
||||
export * from "./checkbox-group";
|
||||
|
||||
@ -167,6 +167,10 @@ const corePlugin = (config: ConfigObject | ConfigFunction = {}) => {
|
||||
3: "3px",
|
||||
5: "5px",
|
||||
},
|
||||
transitionDuration: {
|
||||
0: "0ms",
|
||||
250: "250ms",
|
||||
},
|
||||
...animations,
|
||||
},
|
||||
},
|
||||
|
||||
@ -46,6 +46,11 @@ export const utilities = {
|
||||
"transition-timing-function": "ease",
|
||||
"transition-duration": DEFAULT_TRANSITION_DURATION,
|
||||
},
|
||||
".transition-width": {
|
||||
"transition-property": "width",
|
||||
"transition-timing-function": "ease",
|
||||
"transition-duration": DEFAULT_TRANSITION_DURATION,
|
||||
},
|
||||
".transition-shadow": {
|
||||
"transition-property": "box-shadow",
|
||||
"transition-timing-function": "ease",
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^17.0.2
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"tsup": {
|
||||
"clean": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user