mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(components): checkbox component added, react-aria dependecies were separated
This commit is contained in:
parent
00b290060e
commit
3e6673269e
@ -34,19 +34,19 @@
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0"
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*"
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@react-aria/focus": "^3.9.0",
|
||||
"@react-aria/utils": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextui-org/spacer": "workspace:*",
|
||||
"@nextui-org/icons-utils": "workspace:*",
|
||||
"react-aria": "3.18.0",
|
||||
"clean-package": "2.1.1",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ import type {
|
||||
} from "@nextui-org/shared-utils";
|
||||
|
||||
import {useState, useEffect, useMemo} from "react";
|
||||
import {useFocusRing, mergeProps} from "react-aria";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {HTMLNextUIProps, forwardRef} from "@nextui-org/system";
|
||||
import {useDOMRef, IFocusRingAria} from "@nextui-org/dom-utils";
|
||||
import {clsx, safeText, __DEV__} from "@nextui-org/shared-utils";
|
||||
|
||||
24
packages/components/checkbox/README.md
Normal file
24
packages/components/checkbox/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @nextui-org/checkbox
|
||||
|
||||
A Quick description of the component
|
||||
|
||||
> This is an internal utility, not intended for public usage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @nextui-org/checkbox
|
||||
# or
|
||||
npm i @nextui-org/checkbox
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
|
||||
for details.
|
||||
|
||||
## Licence
|
||||
|
||||
This project is licensed under the terms of the
|
||||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
|
||||
130
packages/components/checkbox/__tests__/checkbox-group.test.tsx
Normal file
130
packages/components/checkbox/__tests__/checkbox-group.test.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {Checkbox} from "../src";
|
||||
|
||||
describe("Checkbox.Group", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(
|
||||
<Checkbox.Group defaultValue={[]} label="Select cities">
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(
|
||||
<Checkbox.Group ref={ref} defaultValue={[]} label="Select cities">
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
);
|
||||
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should work correctly with initial value", () => {
|
||||
const {container} = render(
|
||||
<Checkbox.Group 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>,
|
||||
);
|
||||
|
||||
// check if the first checkbox is checked
|
||||
expect(container.querySelector("[data-testid=first-checkbox] input")).toBeChecked();
|
||||
|
||||
// second checkbox should not be checked
|
||||
expect(container.querySelector("[data-testid=second-checkbox] input")).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("should change value after click", () => {
|
||||
let value = ["sydney"];
|
||||
const {container} = render(
|
||||
<Checkbox.Group
|
||||
defaultValue={["sydney"]}
|
||||
label="Select cities"
|
||||
onChange={(val) => (value = val)}
|
||||
>
|
||||
<Checkbox data-testid="first-checkbox" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox data-testid="second-checkbox" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
</Checkbox.Group>,
|
||||
);
|
||||
|
||||
const firstCheckbox = container.querySelector("[data-testid=first-checkbox] input");
|
||||
const secondCheckbox = container.querySelector("[data-testid=second-checkbox] input");
|
||||
|
||||
expect(firstCheckbox).toBeChecked();
|
||||
expect(secondCheckbox).not.toBeChecked();
|
||||
|
||||
secondCheckbox && userEvent.click(secondCheckbox);
|
||||
|
||||
expect(firstCheckbox).toBeChecked();
|
||||
expect(secondCheckbox).toBeChecked();
|
||||
|
||||
expect(value).toEqual(["sydney", "buenos-aires"]);
|
||||
});
|
||||
|
||||
it("should ignore events when disabled", () => {
|
||||
const {container} = render(
|
||||
<Checkbox.Group 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>,
|
||||
);
|
||||
|
||||
const firstCheckbox = container.querySelector("[data-testid=first-checkbox] input");
|
||||
const secondCheckbox = container.querySelector("[data-testid=second-checkbox] input");
|
||||
|
||||
expect(firstCheckbox).toBeChecked();
|
||||
expect(secondCheckbox).not.toBeChecked();
|
||||
|
||||
secondCheckbox && userEvent.click(secondCheckbox);
|
||||
|
||||
expect(firstCheckbox).toBeChecked();
|
||||
expect(secondCheckbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("should work correctly with controlled value", () => {
|
||||
let checked = ["sydney"];
|
||||
const onChange = jest.fn((value) => {
|
||||
checked = value;
|
||||
});
|
||||
|
||||
const {container} = render(
|
||||
<Checkbox.Group 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>,
|
||||
);
|
||||
|
||||
const secondCheckbox = container.querySelector("[data-testid=second-checkbox] input");
|
||||
|
||||
secondCheckbox && userEvent.click(secondCheckbox);
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(checked).toEqual(["sydney", "buenos-aires"]);
|
||||
});
|
||||
});
|
||||
103
packages/components/checkbox/__tests__/checkbox.test.tsx
Normal file
103
packages/components/checkbox/__tests__/checkbox.test.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {Checkbox, CheckboxProps} from "../src";
|
||||
|
||||
describe("Checkbox", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Checkbox>Label</Checkbox>);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLLabelElement>();
|
||||
|
||||
render(<Checkbox ref={ref} label="checkbox-test" />);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should work correctly with initial value", () => {
|
||||
let {container} = render(<Checkbox isSelected label="checkbox-test" />);
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(true);
|
||||
|
||||
container = render(<Checkbox isSelected={false} label="checkbox-test" />).container;
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(false);
|
||||
});
|
||||
|
||||
it("should change value after click", () => {
|
||||
const {container} = render(<Checkbox label="checkbox-test" />);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(true);
|
||||
});
|
||||
|
||||
it("should ignore events when disabled", () => {
|
||||
const {container} = render(<Checkbox isDisabled label="checkbox-test" />);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
|
||||
expect(container.querySelector("input")?.checked).toBe(false);
|
||||
});
|
||||
|
||||
it("should work correctly with indeterminate value", () => {
|
||||
const {container} = render(<Checkbox isIndeterminate label="checkbox-test" />);
|
||||
|
||||
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} />);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
|
||||
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")!);
|
||||
|
||||
expect(onFocus).toBeCalled();
|
||||
});
|
||||
|
||||
it('should work correctly with "isRequired" prop', () => {
|
||||
const {container} = render(<Checkbox isRequired label="checkbox-test" />);
|
||||
|
||||
expect(container.querySelector("input")?.required).toBe(true);
|
||||
});
|
||||
|
||||
it("should work correctly with controlled value", () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const Component = (props: CheckboxProps) => {
|
||||
const [value, setValue] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
{...props}
|
||||
isSelected={value}
|
||||
onChange={(checked) => {
|
||||
setValue(checked);
|
||||
onChange(checked);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const {container} = render(<Component label="checkbox-test" onChange={onChange} />);
|
||||
|
||||
userEvent.click(container.querySelector("label")!);
|
||||
|
||||
expect(onChange).toBeCalled();
|
||||
|
||||
expect(container.querySelector("input")?.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
});
|
||||
3
packages/components/checkbox/clean-package.config.json
Normal file
3
packages/components/checkbox/clean-package.config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{ "replace": { "main": "dist/index.cjs.js", "module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts", "exports": { ".": { "import": "./dist/index.esm.js",
|
||||
"require": "./dist/index.cjs.js" }, "./package.json": "./package.json" } } }
|
||||
58
packages/components/checkbox/package.json
Normal file
58
packages/components/checkbox/package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@nextui-org/checkbox",
|
||||
"version": "1.0.0-beta.11",
|
||||
"description": "Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.",
|
||||
"keywords": [
|
||||
"checkbox"
|
||||
],
|
||||
"author": "Junior Garcia <jrgarciadev@gmail.com>",
|
||||
"homepage": "https://nextui.org",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nextui-org/nextui.git",
|
||||
"directory": "packages/components/checkbox"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextui-org/nextui/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format=esm,cjs --dts",
|
||||
"dev": "yarn build:fast -- --watch",
|
||||
"clean": "rimraf dist .turbo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:fast": "tsup src/index.ts --format=esm,cjs",
|
||||
"prepack": "clean-package",
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@react-aria/checkbox": "^3.6.0",
|
||||
"@react-aria/focus": "^3.9.0",
|
||||
"@react-aria/interactions": "^3.12.0",
|
||||
"@react-aria/visually-hidden": "^3.5.0",
|
||||
"@react-stately/checkbox": "^3.3.0",
|
||||
"@react-stately/toggle": "^3.4.2",
|
||||
"@react-aria/utils": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-types/checkbox": "^3.4.0",
|
||||
"@react-types/shared": "^3.14.1",
|
||||
"clean-package": "2.1.1",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
8
packages/components/checkbox/src/checkbox-context.ts
Normal file
8
packages/components/checkbox/src/checkbox-context.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import {createContext} from "@nextui-org/shared-utils";
|
||||
|
||||
import {ContextType} from "./use-checkbox-group";
|
||||
|
||||
export const [CheckboxGroupProvider, useCheckboxGroupContext] = createContext<ContextType>({
|
||||
name: "CheckboxGroupContext",
|
||||
strict: false,
|
||||
});
|
||||
64
packages/components/checkbox/src/checkbox-group.styles.ts
Normal file
64
packages/components/checkbox/src/checkbox-group.styles.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {styled} from "@nextui-org/system";
|
||||
|
||||
import {StyledCheckboxLabel} from "./checkbox.styles";
|
||||
|
||||
export const StyledCheckboxGroupContainer = styled("div", {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
variants: {
|
||||
isRow: {
|
||||
true: {
|
||||
flexDirection: "row",
|
||||
mt: 0,
|
||||
[`& ${StyledCheckboxLabel}`]: {
|
||||
mr: "$$checkboxSize",
|
||||
},
|
||||
},
|
||||
false: {
|
||||
mr: 0,
|
||||
flexDirection: "column",
|
||||
[`& ${StyledCheckboxLabel}: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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
46
packages/components/checkbox/src/checkbox-group.tsx
Normal file
46
packages/components/checkbox/src/checkbox-group.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
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 {CheckboxGroupProvider} from "./checkbox-context";
|
||||
import {StyledCheckboxGroup, StyledCheckboxGroupContainer} from "./checkbox-group.styles";
|
||||
import {UseCheckboxGroupProps, useCheckboxGroup} from "./use-checkbox-group";
|
||||
|
||||
export interface CheckboxGroupProps extends UseCheckboxGroupProps {}
|
||||
|
||||
const CheckboxGroup = forwardRef<CheckboxGroupProps, "div">((props, ref) => {
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {children, orientation, groupProps, labelProps, label, context, className, ...otherProps} =
|
||||
useCheckboxGroup(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"
|
||||
>
|
||||
<CheckboxGroupProvider value={context}>{children}</CheckboxGroupProvider>
|
||||
</StyledCheckboxGroupContainer>
|
||||
</StyledCheckboxGroup>
|
||||
);
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
CheckboxGroup.displayName = "NextUI.CheckboxGroup";
|
||||
}
|
||||
|
||||
CheckboxGroup.toString = () => ".nextui-checkbox-group";
|
||||
|
||||
export default CheckboxGroup;
|
||||
544
packages/components/checkbox/src/checkbox.styles.ts
Normal file
544
packages/components/checkbox/src/checkbox.styles.ts
Normal file
@ -0,0 +1,544 @@
|
||||
import {styled} from "@nextui-org/system";
|
||||
import {cssFocusVisible} from "@nextui-org/shared-css";
|
||||
|
||||
export const StyledCheckboxLabel = 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
132
packages/components/checkbox/src/checkbox.tsx
Normal file
132
packages/components/checkbox/src/checkbox.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {useFocusableRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, __DEV__} from "@nextui-org/shared-utils";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
|
||||
import {
|
||||
StyledCheckboxLabel,
|
||||
StyledCheckboxContainer,
|
||||
StyledCheckboxMask,
|
||||
StyledCheckboxText,
|
||||
StyledIconCheck,
|
||||
StyledIconCheckFirstLine,
|
||||
StyledIconCheckSecondLine,
|
||||
} from "./checkbox.styles";
|
||||
import {UseCheckboxProps, useCheckbox} from "./use-checkbox";
|
||||
import CheckboxGroup from "./checkbox-group";
|
||||
|
||||
export interface CheckboxProps extends UseCheckboxProps {}
|
||||
|
||||
type CompoundCheckbox = {
|
||||
Group: typeof CheckboxGroup;
|
||||
};
|
||||
|
||||
const Checkbox = forwardRef<CheckboxProps, "label", CompoundCheckbox>((props, ref) => {
|
||||
const {className, as, css, children, label, ...checkboxProps} = props;
|
||||
|
||||
const {
|
||||
size,
|
||||
color,
|
||||
state,
|
||||
labelColor,
|
||||
isRounded,
|
||||
isHovered,
|
||||
isFocusVisible,
|
||||
lineThrough,
|
||||
disableAnimation,
|
||||
isIndeterminate,
|
||||
inputRef,
|
||||
inputProps,
|
||||
pressProps,
|
||||
hoverProps,
|
||||
focusProps,
|
||||
containerCss,
|
||||
...otherProps
|
||||
} = useCheckbox({...checkboxProps, children: children ?? label});
|
||||
|
||||
const domRef = useFocusableRef(ref, inputRef);
|
||||
|
||||
return (
|
||||
<StyledCheckboxLabel
|
||||
ref={domRef}
|
||||
as={as}
|
||||
className={clsx("nextui-checkbox", className)}
|
||||
data-state={state}
|
||||
{...mergeProps(hoverProps, pressProps, otherProps)}
|
||||
css={css}
|
||||
disableAnimation={disableAnimation}
|
||||
isDisabled={inputProps.disabled}
|
||||
size={size}
|
||||
>
|
||||
<VisuallyHidden>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="nextui-checkbox-input"
|
||||
{...mergeProps(inputProps, focusProps)}
|
||||
/>
|
||||
</VisuallyHidden>
|
||||
<StyledCheckboxContainer
|
||||
className="nextui-checkbox-container"
|
||||
color={color}
|
||||
css={containerCss}
|
||||
disableAnimation={disableAnimation}
|
||||
isDisabled={inputProps.disabled}
|
||||
isFocusVisible={isFocusVisible}
|
||||
isHovered={isHovered}
|
||||
isRounded={isRounded}
|
||||
{...focusProps}
|
||||
>
|
||||
<StyledCheckboxMask
|
||||
className="nextui-checkbox-mask"
|
||||
disableAnimation={disableAnimation}
|
||||
isChecked={inputProps.checked}
|
||||
isIndeterminate={isIndeterminate}
|
||||
>
|
||||
<StyledIconCheck
|
||||
className="nextui-icon-check"
|
||||
disableAnimation={disableAnimation}
|
||||
isChecked={inputProps.checked}
|
||||
isIndeterminate={isIndeterminate}
|
||||
size={size}
|
||||
>
|
||||
<StyledIconCheckFirstLine
|
||||
className="nextui-icon-check-line1"
|
||||
disableAnimation={disableAnimation}
|
||||
isChecked={inputProps.checked}
|
||||
isIndeterminate={isIndeterminate}
|
||||
/>
|
||||
<StyledIconCheckSecondLine
|
||||
className="nextui-icon-check-line2"
|
||||
disableAnimation={disableAnimation}
|
||||
isChecked={inputProps.checked}
|
||||
isIndeterminate={isIndeterminate}
|
||||
/>
|
||||
</StyledIconCheck>
|
||||
</StyledCheckboxMask>
|
||||
</StyledCheckboxContainer>
|
||||
{(children || label) && (
|
||||
<StyledCheckboxText
|
||||
className="nextui-checkbox-text"
|
||||
color={labelColor}
|
||||
disableAnimation={disableAnimation}
|
||||
isChecked={inputProps.checked}
|
||||
isDisabled={inputProps.disabled}
|
||||
lineThrough={lineThrough}
|
||||
>
|
||||
{children || label}
|
||||
</StyledCheckboxText>
|
||||
)}
|
||||
</StyledCheckboxLabel>
|
||||
);
|
||||
});
|
||||
|
||||
Checkbox.Group = CheckboxGroup;
|
||||
|
||||
if (__DEV__) {
|
||||
Checkbox.displayName = "NextUI.Checkbox";
|
||||
}
|
||||
|
||||
Checkbox.toString = () => ".nextui-checkbox";
|
||||
|
||||
export default Checkbox;
|
||||
6
packages/components/checkbox/src/index.ts
Normal file
6
packages/components/checkbox/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// export types
|
||||
export type {CheckboxProps} from "./checkbox";
|
||||
export type {CheckboxGroupProps} from "./checkbox-group";
|
||||
|
||||
// export component
|
||||
export {default as Checkbox} from "./checkbox";
|
||||
69
packages/components/checkbox/src/use-checkbox-group.ts
Normal file
69
packages/components/checkbox/src/use-checkbox-group.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import type {AriaCheckboxGroupProps} from "@react-types/checkbox";
|
||||
import type {NormalSizes, NormalColors, SimpleColors} from "@nextui-org/shared-utils";
|
||||
import type {Orientation} from "@react-types/shared";
|
||||
import type {HTMLNextUIProps} from "@nextui-org/system";
|
||||
|
||||
import {useCheckboxGroup as useReactAriaCheckboxGroup} from "@react-aria/checkbox";
|
||||
import {CheckboxGroupState, useCheckboxGroupState} from "@react-stately/checkbox";
|
||||
|
||||
export interface UseCheckboxGroupProps extends HTMLNextUIProps<"div", AriaCheckboxGroupProps> {
|
||||
/**
|
||||
* The color of the checkboxes.
|
||||
* @default "default"
|
||||
*/
|
||||
color?: NormalColors;
|
||||
/**
|
||||
* The color of the label.
|
||||
* @default "default"
|
||||
*/
|
||||
labelColor?: SimpleColors;
|
||||
/**
|
||||
* The size of the checkboxes.
|
||||
* @default "md"
|
||||
*/
|
||||
size?: NormalSizes;
|
||||
/**
|
||||
* The axis the checkbox group items should align with.
|
||||
* @default "vertical"
|
||||
*/
|
||||
orientation?: Orientation;
|
||||
}
|
||||
|
||||
export type ContextType = {
|
||||
groupState: CheckboxGroupState;
|
||||
color?: UseCheckboxGroupProps["color"];
|
||||
labelColor?: UseCheckboxGroupProps["labelColor"];
|
||||
size?: UseCheckboxGroupProps["size"];
|
||||
};
|
||||
|
||||
export function useCheckboxGroup(props: UseCheckboxGroupProps) {
|
||||
const {
|
||||
size = "md",
|
||||
color = "default",
|
||||
labelColor = "default",
|
||||
orientation = "vertical",
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const groupState = useCheckboxGroupState(otherProps);
|
||||
|
||||
const {labelProps, groupProps} = useReactAriaCheckboxGroup(otherProps, groupState);
|
||||
|
||||
const context = {
|
||||
size,
|
||||
color,
|
||||
labelColor,
|
||||
groupState,
|
||||
};
|
||||
|
||||
return {
|
||||
size,
|
||||
orientation,
|
||||
labelProps,
|
||||
groupProps,
|
||||
context,
|
||||
...otherProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseCheckboxGroupReturn = ReturnType<typeof useCheckboxGroup>;
|
||||
166
packages/components/checkbox/src/use-checkbox.ts
Normal file
166
packages/components/checkbox/src/use-checkbox.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import type {AriaCheckboxProps} from "@react-types/checkbox";
|
||||
import type {HTMLNextUIProps, CSS} from "@nextui-org/system";
|
||||
import type {IPressResult, IFocusRingAria} from "@nextui-org/dom-utils";
|
||||
|
||||
import {useMemo, useRef} from "react";
|
||||
import {useToggleState} from "@react-stately/toggle";
|
||||
import {useHover, usePress} from "@react-aria/interactions";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {NormalSizes, NormalColors, SimpleColors, __DEV__} from "@nextui-org/shared-utils";
|
||||
import {
|
||||
useCheckbox as useReactAriaCheckbox,
|
||||
useCheckboxGroupItem as useReactAriaCheckboxGroupItem,
|
||||
} from "@react-aria/checkbox";
|
||||
|
||||
import {useCheckboxGroupContext} from "./checkbox-context";
|
||||
|
||||
export interface UseCheckboxProps extends HTMLNextUIProps<"label", AriaCheckboxProps> {
|
||||
/**
|
||||
* The content to display as the label.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The color of the checkbox.
|
||||
* @default "default"
|
||||
*/
|
||||
color?: NormalColors;
|
||||
/**
|
||||
* The color of the label.
|
||||
* @default "default"
|
||||
*/
|
||||
labelColor?: SimpleColors;
|
||||
/**
|
||||
* The size of the checkbox.
|
||||
* @default "md"
|
||||
*/
|
||||
size?: NormalSizes;
|
||||
/**
|
||||
* Whether the checkbox is rounded.
|
||||
* @default false
|
||||
*/
|
||||
isRounded?: boolean;
|
||||
/**
|
||||
* Line in the middle of the label when the `Checkbox` is checked
|
||||
* @default false
|
||||
*/
|
||||
lineThrough?: boolean;
|
||||
/**
|
||||
* Whether the checkbox has animations.
|
||||
* @default false
|
||||
*/
|
||||
disableAnimation?: boolean;
|
||||
/**
|
||||
* Override checkbox container CSS style
|
||||
*/
|
||||
containerCss?: CSS;
|
||||
}
|
||||
|
||||
export function useCheckbox(props: UseCheckboxProps) {
|
||||
const groupContext = useCheckboxGroupContext();
|
||||
|
||||
const {
|
||||
size = groupContext?.size ?? "md",
|
||||
color = groupContext?.color ?? "default",
|
||||
labelColor = groupContext?.labelColor ?? "default",
|
||||
lineThrough,
|
||||
isSelected,
|
||||
value = "",
|
||||
isRounded = false,
|
||||
isRequired = false,
|
||||
disableAnimation = false,
|
||||
isIndeterminate = false,
|
||||
defaultSelected,
|
||||
onChange,
|
||||
containerCss,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (groupContext && __DEV__) {
|
||||
const warningMessage =
|
||||
"The Checkbox.Group is being used, `%s` will be ignored. Use the `%s` of the Checkbox.Group instead.";
|
||||
|
||||
if (isSelected) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(warningMessage, "isSelected", "value");
|
||||
}
|
||||
if (defaultSelected) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(warningMessage, "defaultSelected", "defaultValue");
|
||||
}
|
||||
}
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const ariaCheckboxProps = useMemo(() => {
|
||||
return {
|
||||
...otherProps,
|
||||
value,
|
||||
defaultSelected,
|
||||
isSelected,
|
||||
isIndeterminate,
|
||||
isRequired,
|
||||
onChange,
|
||||
};
|
||||
}, [isIndeterminate, otherProps]);
|
||||
|
||||
const {inputProps} = groupContext
|
||||
? // eslint-disable-next-line
|
||||
useReactAriaCheckboxGroupItem(
|
||||
{
|
||||
...ariaCheckboxProps,
|
||||
validationState: otherProps.validationState,
|
||||
},
|
||||
groupContext.groupState,
|
||||
inputRef,
|
||||
)
|
||||
: useReactAriaCheckbox(ariaCheckboxProps, useToggleState(ariaCheckboxProps), inputRef); // eslint-disable-line
|
||||
|
||||
if (isRequired) {
|
||||
inputProps.required = true;
|
||||
}
|
||||
|
||||
const {hoverProps, isHovered} = useHover({
|
||||
isDisabled: inputProps.disabled,
|
||||
});
|
||||
|
||||
// TODO: Event's propagation wasn't stopped https://github.com/adobe/react-spectrum/issues/2383
|
||||
const {pressProps}: IPressResult<UseCheckboxProps> = usePress({
|
||||
isDisabled: inputProps.disabled,
|
||||
});
|
||||
|
||||
const {focusProps, isFocusVisible}: IFocusRingAria<UseCheckboxProps> = useFocusRing({
|
||||
autoFocus: inputProps.autoFocus,
|
||||
});
|
||||
|
||||
const state = useMemo(() => {
|
||||
if (isHovered) return "hovered";
|
||||
|
||||
return isIndeterminate && inputProps.checked
|
||||
? "mixed"
|
||||
: inputProps.checked
|
||||
? "checked"
|
||||
: "uncheked";
|
||||
}, [isHovered, isIndeterminate, inputProps.checked]);
|
||||
|
||||
return {
|
||||
size,
|
||||
color,
|
||||
state,
|
||||
labelColor,
|
||||
isRounded,
|
||||
lineThrough,
|
||||
disableAnimation,
|
||||
isIndeterminate,
|
||||
isFocusVisible,
|
||||
isHovered,
|
||||
inputRef,
|
||||
inputProps,
|
||||
pressProps,
|
||||
hoverProps,
|
||||
focusProps,
|
||||
containerCss,
|
||||
...otherProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseCheckboxReturn = ReturnType<typeof useCheckbox>;
|
||||
243
packages/components/checkbox/stories/checkbox.stories.tsx
Normal file
243
packages/components/checkbox/stories/checkbox.stories.tsx
Normal file
@ -0,0 +1,243 @@
|
||||
import React from "react";
|
||||
import {Meta} from "@storybook/react";
|
||||
|
||||
import {Checkbox} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Inputs/Checkbox",
|
||||
component: Checkbox,
|
||||
} as Meta;
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Checkbox color="default" labelColor="default" size="md">
|
||||
Option
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const Label = () => {
|
||||
return <Checkbox color="default" label="Option" labelColor="default" size="md" />;
|
||||
};
|
||||
|
||||
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 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 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 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 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 Indeterminate = () => {
|
||||
return (
|
||||
<Checkbox defaultSelected isIndeterminate color="primary" size="lg">
|
||||
Option
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const LineThrough = () => {
|
||||
return (
|
||||
<Checkbox defaultSelected lineThrough color="primary" size="lg">
|
||||
Option
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const Controlled = () => {
|
||||
const [selected, setSelected] = React.useState<boolean>(true);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export const Group = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
const handleGroupChange = (value: string[]) => console.log(value);
|
||||
|
||||
return (
|
||||
<Checkbox.Group
|
||||
color="warning"
|
||||
defaultValue={["buenos-aires"]}
|
||||
label="Select cities"
|
||||
labelColor="primary"
|
||||
onChange={handleGroupChange}
|
||||
>
|
||||
<Checkbox color="primary" value="buenos-aires">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
<Checkbox labelColor="warning" value="sydney">
|
||||
Sydney
|
||||
</Checkbox>
|
||||
<Checkbox isDisabled labelColor="error" value="london">
|
||||
London
|
||||
</Checkbox>
|
||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
</Checkbox.Group>
|
||||
);
|
||||
};
|
||||
|
||||
export const GroupRow = () => (
|
||||
<Checkbox.Group
|
||||
color="warning"
|
||||
defaultValue={["1"]}
|
||||
label="Select cities"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Checkbox color="primary" value="1">
|
||||
Buenos Aires
|
||||
</Checkbox>
|
||||
<Checkbox value="2">Sydney</Checkbox>
|
||||
<Checkbox value="3">London</Checkbox>
|
||||
<Checkbox value="4">Tokyo</Checkbox>
|
||||
</Checkbox.Group>
|
||||
);
|
||||
9
packages/components/checkbox/tsconfig.json
Normal file
9
packages/components/checkbox/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@stitches/react": ["../../../node_modules/@stitches/react"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
13
packages/components/checkbox/tsup.config.ts
Normal file
13
packages/components/checkbox/tsup.config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {defineConfig} from "tsup";
|
||||
import {findUpSync} from "find-up";
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
minify: false,
|
||||
treeshake: true,
|
||||
format: ["cjs", "esm"],
|
||||
outExtension(ctx) {
|
||||
return {js: `.${ctx.format}.js`};
|
||||
},
|
||||
inject: process.env.JSX ? [findUpSync("react-shim.js")!] : undefined,
|
||||
});
|
||||
@ -34,14 +34,16 @@
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0"
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*"
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@react-aria/link": "^3.3.4",
|
||||
"@react-aria/utils": "^3.14.0",
|
||||
"@react-aria/focus": "^3.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-types/link": "^3.3.3",
|
||||
@ -49,7 +51,6 @@
|
||||
"@nextui-org/grid": "workspace:*",
|
||||
"@nextui-org/text": "workspace:*",
|
||||
"clean-package": "2.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-aria": "3.18.0"
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import {HTMLAttributes} from "react";
|
||||
import {useLink as useAriaLink} from "react-aria";
|
||||
import {useLink as useAriaLink} from "@react-aria/link";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
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";
|
||||
|
||||
import {StyledLink} from "./link.styles";
|
||||
import {UseLinkProps, useLink} from "./use-link";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {AriaLinkProps} from "@react-types/link";
|
||||
|
||||
import {useMemo} from "react";
|
||||
import {useFocusRing} from "react-aria";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {HTMLNextUIProps, CSS, getTokenValue} from "@nextui-org/system";
|
||||
import {IFocusRingAria} from "@nextui-org/dom-utils";
|
||||
import {isNormalColor} from "@nextui-org//shared-utils";
|
||||
|
||||
@ -34,8 +34,7 @@
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0"
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
@ -43,11 +42,12 @@
|
||||
"@nextui-org/link": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*"
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@react-aria/focus": "^3.9.0",
|
||||
"@react-aria/utils": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-aria": "3.18.0"
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {AvatarProps} from "@nextui-org/avatar";
|
||||
|
||||
import {ReactNode, useMemo} from "react";
|
||||
import {useFocusRing} from "react-aria";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {IFocusRingAria} from "@nextui-org/dom-utils";
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {mergeProps} from "react-aria";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, __DEV__} from "@nextui-org/shared-utils";
|
||||
|
||||
@ -53,15 +53,10 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0",
|
||||
"react-stately": ">=3.16.0",
|
||||
"@stitches/react": ">=1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react": "17.0.2",
|
||||
"react-aria": "3.18.0",
|
||||
"react-stately": "3.16.0",
|
||||
"@stitches/react": "1.2.8",
|
||||
"clean-package": "2.1.1"
|
||||
}
|
||||
|
||||
@ -35,17 +35,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/use-ssr": "workspace:*"
|
||||
"@nextui-org/use-ssr": "workspace:*",
|
||||
"@react-aria/ssr": "^3.3.0",
|
||||
"@react-aria/overlays": "^3.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0",
|
||||
"@stitches/react": ">=1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"@stitches/react": "1.2.8",
|
||||
"react-aria": "3.18.0",
|
||||
"clean-package": "2.1.1",
|
||||
"react": "^17.0.2"
|
||||
"clean-package": "2.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,16 @@ export const getStitchesTheme = (targetTheme: BaseTheme): BaseTheme => {
|
||||
return deepMerge(targetTheme, commonTheme.theme);
|
||||
};
|
||||
|
||||
const stitches = createStitches({
|
||||
export const {
|
||||
styled,
|
||||
css,
|
||||
globalCss,
|
||||
keyframes,
|
||||
getCssText,
|
||||
theme,
|
||||
config,
|
||||
createTheme: createThemeBase,
|
||||
} = createStitches({
|
||||
...commonTheme,
|
||||
theme: {
|
||||
...commonTheme.theme,
|
||||
@ -29,15 +38,6 @@ const stitches = createStitches({
|
||||
},
|
||||
});
|
||||
|
||||
export const createThemeBase = stitches.createTheme;
|
||||
export const styled = stitches.styled;
|
||||
export const css = stitches.css;
|
||||
export const globalCss = stitches.globalCss;
|
||||
export const keyframes = stitches.keyframes;
|
||||
export const getCssText = stitches.getCssText;
|
||||
export const theme = stitches.theme;
|
||||
export const config = stitches.config;
|
||||
|
||||
export const createTheme = ({type, theme = {}, className}: Theme) => {
|
||||
if (!type) {
|
||||
throw new Error("Theme type is required");
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, {useState, useMemo, useEffect, ReactNode} from "react";
|
||||
import {SSRProvider, OverlayProvider} from "react-aria";
|
||||
import {SSRProvider} from "@react-aria/ssr";
|
||||
import {OverlayProvider} from "@react-aria/overlays";
|
||||
import {deepMerge, copyObject} from "@nextui-org/shared-utils";
|
||||
import {useSSR} from "@nextui-org/use-ssr";
|
||||
|
||||
|
||||
@ -34,15 +34,15 @@
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-types/shared": "^3.14.1"
|
||||
"@react-types/shared": "^3.14.1",
|
||||
"@react-aria/interactions": "^3.12.0",
|
||||
"@react-aria/focus": "^3.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-aria": ">=3.18.0"
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.1.1",
|
||||
"react-aria": "3.18.0",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import type {FocusRingAria} from "react-aria";
|
||||
|
||||
import {HTMLAttributes} from "react";
|
||||
import type {FocusRingAria} from "@react-aria/focus";
|
||||
import type {PressResult} from "@react-aria/interactions";
|
||||
import type {HTMLAttributes} from "react";
|
||||
|
||||
export interface IFocusRingAria<T extends object> extends FocusRingAria {
|
||||
focusProps: Omit<HTMLAttributes<HTMLElement>, keyof T>;
|
||||
}
|
||||
|
||||
export interface IPressResult<T extends object> extends PressResult {
|
||||
pressProps: Omit<React.HTMLAttributes<HTMLElement>, keyof T>;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type {CSS} from "@nextui-org/system";
|
||||
import {css} from "@nextui-org/system";
|
||||
|
||||
export const sharedFocus: CSS = {
|
||||
export const sharedFocus = css({
|
||||
WebkitTapHighlightColor: "transparent",
|
||||
"&:focus:not(&:focus-visible)": {
|
||||
boxShadow: "none",
|
||||
@ -13,9 +13,9 @@ export const sharedFocus: CSS = {
|
||||
WebkitTapHighlightColor: "transparent",
|
||||
outline: "none",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const cssFocusVisible: CSS = {
|
||||
export const cssFocusVisible = css({
|
||||
variants: {
|
||||
isFocusVisible: {
|
||||
true: {
|
||||
@ -28,10 +28,10 @@ export const cssFocusVisible: CSS = {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const cssNoBlurriness: CSS = {
|
||||
export const cssNoBlurriness = css({
|
||||
/* Avoid blurriness */
|
||||
transform: "translateZ(0)",
|
||||
backfaceVisibility: "hidden",
|
||||
};
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type {CSS} from "@nextui-org/system";
|
||||
import {css} from "@nextui-org/system";
|
||||
|
||||
export const sharedVisuallyHidden: CSS = {
|
||||
export const sharedVisuallyHidden = css({
|
||||
border: "0px",
|
||||
clip: "rect(0px, 0px, 0px, 0px)",
|
||||
height: "1px",
|
||||
@ -10,9 +10,9 @@ export const sharedVisuallyHidden: CSS = {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
position: "absolute",
|
||||
};
|
||||
});
|
||||
|
||||
export const cssHideIn: CSS = {
|
||||
export const cssHideIn = css({
|
||||
variants: {
|
||||
hideIn: {
|
||||
xs: {
|
||||
@ -42,9 +42,9 @@ export const cssHideIn: CSS = {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const cssShowIn: CSS = {
|
||||
export const cssShowIn = css({
|
||||
variants: {
|
||||
showIn: {
|
||||
xs: {
|
||||
@ -74,6 +74,6 @@ export const cssShowIn: CSS = {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const cssHideShowIn: CSS = {...cssHideIn, ...cssShowIn};
|
||||
export const cssHideShowIn = css(cssHideIn, cssShowIn);
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@stitches/react": ["../../../node_modules/@stitches/react"],
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
|
||||
52
packages/utilities/shared-utils/src/context.ts
Normal file
52
packages/utilities/shared-utils/src/context.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface CreateContextOptions {
|
||||
/**
|
||||
* If `true`, React will throw if context is `null` or `undefined`
|
||||
* In some cases, you might want to support nested context, so you can set it to `false`
|
||||
*/
|
||||
strict?: boolean;
|
||||
/**
|
||||
* Error message to throw if the context is `undefined`
|
||||
*/
|
||||
errorMessage?: string;
|
||||
/**
|
||||
* The display name of the context
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
type CreateContextReturn<T> = [React.Provider<T>, () => T, React.Context<T>];
|
||||
|
||||
/**
|
||||
* Creates a named context, provider, and hook.
|
||||
*
|
||||
* @param options create context options
|
||||
*/
|
||||
export function createContext<ContextType>(options: CreateContextOptions = {}) {
|
||||
const {
|
||||
strict = true,
|
||||
errorMessage = "useContext: `context` is undefined. Seems you forgot to wrap component within the Provider",
|
||||
name,
|
||||
} = options;
|
||||
|
||||
const Context = React.createContext<ContextType | undefined>(undefined);
|
||||
|
||||
Context.displayName = name;
|
||||
|
||||
function useContext() {
|
||||
const context = React.useContext(Context);
|
||||
|
||||
if (!context && strict) {
|
||||
const error = new Error(errorMessage);
|
||||
|
||||
error.name = "ContextError";
|
||||
Error.captureStackTrace?.(error, useContext);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
return [Context.Provider, useContext, Context] as CreateContextReturn<ContextType>;
|
||||
}
|
||||
@ -10,3 +10,4 @@ export * from "./text";
|
||||
export * from "./dimensions";
|
||||
export * from "./css-transition";
|
||||
export * from "./functions";
|
||||
export * from "./context";
|
||||
|
||||
1838
pnpm-lock.yaml
generated
1838
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user