feat(radio): almost complete, only tests missing

This commit is contained in:
Junior Garcia 2023-03-05 16:23:34 -03:00
parent c12d54f1d2
commit 7b99e9f3f1
12 changed files with 299 additions and 642 deletions

View File

@ -2,14 +2,14 @@ import * as React from "react";
import {render} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {Radio, RadioProps} from "../src";
import {RadioGroup, Radio, RadioProps} from "../src";
describe("Radio", () => {
it("should render correctly", () => {
const wrapper = render(
<Radio.Group label="Options">
<RadioGroup label="Options">
<Radio value="1">Option 1</Radio>
</Radio.Group>,
</RadioGroup>,
);
expect(() => wrapper.unmount()).not.toThrow();
@ -19,31 +19,31 @@ describe("Radio", () => {
const ref = React.createRef<HTMLLabelElement>();
render(
<Radio.Group label="Options">
<RadioGroup label="Options">
<Radio ref={ref} value="1">
Option 1
</Radio>
</Radio.Group>,
</RadioGroup>,
);
expect(ref.current).not.toBeNull();
});
it("should work correctly with initial value", () => {
let {container} = render(
<Radio.Group label="Options" value="1">
<RadioGroup label="Options" value="1">
<Radio value="1">Option 1</Radio>
</Radio.Group>,
</RadioGroup>,
);
expect(container.querySelector("input")?.checked).toBe(true);
let wrapper = render(
<Radio.Group defaultValue="2" label="Options">
<RadioGroup defaultValue="2" label="Options">
<Radio value="1">Option 1</Radio>
<Radio data-testid="radio-test-2" value="2">
Option 1
</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio2 = wrapper.getByTestId("radio-test-2") as HTMLInputElement;
@ -53,12 +53,12 @@ describe("Radio", () => {
it("should change value after click", () => {
const {container} = render(
<Radio.Group label="Options">
<RadioGroup label="Options">
<Radio value="1">Option 1</Radio>
<Radio className="radio-test-2" value="2">
Option 1
</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio2 = container
@ -73,12 +73,12 @@ describe("Radio", () => {
it("should ignore events when disabled", () => {
const {container} = render(
<Radio.Group label="Options">
<RadioGroup label="Options">
<Radio isDisabled className="radio-test-1" value="1">
Option 1
</Radio>
<Radio value="2">Option 2</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio1 = container
@ -93,12 +93,12 @@ describe("Radio", () => {
it('should work correctly with "onChange" prop', () => {
const onChange = jest.fn();
const {container} = render(
<Radio.Group label="Options" onChange={onChange}>
<RadioGroup label="Options" onChange={onChange}>
<Radio value="1">Option 1</Radio>
<Radio className="radio-test-2" value="2">
Option 2
</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio2 = container
@ -114,12 +114,12 @@ describe("Radio", () => {
it('should work correctly with "onFocus" prop', () => {
const onFocus = jest.fn();
const {container} = render(
<Radio.Group label="Options" onFocus={onFocus}>
<RadioGroup label="Options" onFocus={onFocus}>
<Radio value="1">Option 1</Radio>
<Radio className="radio-test-2" value="2">
Option 2
</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio2 = container
@ -133,12 +133,12 @@ describe("Radio", () => {
it('should work correctly with "isRequired" prop', () => {
const {container} = render(
<Radio.Group isRequired label="Options">
<RadioGroup isRequired label="Options">
<Radio value="1">Option 1</Radio>
<Radio className="radio-test-2" value="2">
Option 2
</Radio>
</Radio.Group>,
</RadioGroup>,
);
let radio2 = container
@ -155,19 +155,19 @@ describe("Radio", () => {
const [value, setValue] = React.useState("1");
return (
<Radio.Group
<RadioGroup
label="Options"
value={value}
onChange={(next) => {
setValue(next);
onChange?.(next);
onChange?.(next as any);
}}
>
<Radio value="1">Option 1</Radio>
<Radio className="radio-test-2" value="2">
Option 2
</Radio>
</Radio.Group>
</RadioGroup>
);
};

View File

@ -1,245 +0,0 @@
// import {styled} from "@nextui-org/system";
// import {cssFocusVisible} from "@nextui-org/shared-css";
// export const StyledRadioText = styled("span", {
// fontSize: "$$radioSize",
// us: "none",
// d: "inline-flex",
// ai: "center",
// variants: {
// color: {
// default: {
// color: "$text",
// },
// primary: {
// color: "$primary",
// },
// secondary: {
// color: "$secondary",
// },
// success: {
// color: "$success",
// },
// warning: {
// color: "$warning",
// },
// error: {
// color: "$error",
// },
// },
// isDisabled: {
// true: {
// color: "$accents5",
// },
// },
// isInvalid: {
// true: {
// color: "$error",
// },
// },
// },
// });
// export const StyledRadioPoint = styled(
// "span",
// {
// size: "$$radioSize",
// br: "$$radioRadii",
// position: "relative",
// d: "inline-block",
// mr: "calc($$radioSize * 0.375)",
// "&:after": {
// content: "",
// d: "block",
// position: "absolute",
// size: "$$radioSize",
// br: "$$radioRadii",
// boxSizing: "border-box",
// border: "2px solid $border",
// },
// },
// cssFocusVisible,
// );
// export const StyledRadio = styled("label", {
// d: "flex",
// w: "initial",
// ai: "flex-start",
// position: "relative",
// fd: "column",
// jc: "flex-start",
// cursor: "pointer",
// "@motion": {
// [`& ${StyledRadioPoint}`]: {
// transition: "none",
// "&:after": {
// transition: "none",
// },
// },
// },
// variants: {
// color: {
// default: {
// $$radioColor: "$colors$primary",
// $$radioColorHover: "$colors$primarySolidHover",
// },
// primary: {
// $$radioColor: "$colors$primary",
// $$radioColorHover: "$colors$primarySolidHover",
// },
// secondary: {
// $$radioColor: "$colors$secondary",
// $$radioColorHover: "$colors$secondarySolidHover",
// },
// success: {
// $$radioColor: "$colors$success",
// $$radioColorHover: "$colors$successSolidHover",
// },
// warning: {
// $$radioColor: "$colors$warning",
// $$radioColorHover: "$colors$warningSolidHover",
// },
// error: {
// $$radioColor: "$colors$error",
// $$radioColorHover: "$colors$errorSolidHover",
// },
// },
// size: {
// xs: {
// $$radioSize: "$space$7",
// },
// sm: {
// $$radioSize: "$space$8",
// },
// md: {
// $$radioSize: "$space$9",
// },
// lg: {
// $$radioSize: "$space$10",
// },
// xl: {
// $$radioSize: "$space$11",
// },
// },
// isHovered: {
// true: {},
// },
// isInvalid: {
// true: {
// $$radioColor: "$colors$error",
// $$radioColorHover: "$colors$errorSolidHover",
// [`& ${StyledRadioPoint}`]: {
// "&:after": {
// borderColor: "$colors$error",
// },
// },
// },
// },
// isDisabled: {
// true: {
// cursor: "not-allowed",
// $$radioColor: "$colors$accents4",
// },
// },
// isSquared: {
// true: {
// $$radioRadii: "$radii$squared",
// },
// false: {
// $$radioRadii: "$radii$rounded",
// },
// },
// isChecked: {
// true: {
// [`& ${StyledRadioPoint}`]: {
// "&:after": {
// border: "calc($$radioSize * 0.34) solid $$radioColor",
// },
// },
// },
// },
// disableAnimation: {
// true: {
// [`& ${StyledRadioPoint}`]: {
// transition: "none",
// "&:after": {
// transition: "none",
// },
// },
// },
// false: {
// [`& ${StyledRadioPoint}`]: {
// transition: "$default",
// "&:after": {
// transition: "$default",
// },
// },
// },
// },
// },
// compoundVariants: [
// // isChecked && isHovered
// {
// isChecked: true,
// isHovered: true,
// css: {
// [`& ${StyledRadioPoint}`]: {
// "&:after": {
// border: "calc($$radioSize * 0.34) solid $$radioColorHover",
// },
// },
// },
// },
// // isChecked && isDisabled & isHovered
// {
// isChecked: true,
// isDisabled: true,
// isHovered: true,
// css: {
// [`& ${StyledRadioPoint}`]: {
// "&:after": {
// border: "calc($$radioSize * 0.34) solid $$radioColor",
// },
// },
// },
// },
// // !isChecked && !isDisabled && isHovered
// {
// isChecked: false,
// isDisabled: false,
// isHovered: true,
// css: {
// [`& ${StyledRadioPoint}`]: {
// bg: "$border",
// },
// },
// },
// ],
// });
// export const StyledRadioDescription = styled("span", {
// color: "$accents7",
// fontSize: "calc($$radioSize * 0.85)",
// paddingLeft: "calc($$radioSize + $$radioSize * 0.375)",
// variants: {
// isInvalid: {
// true: {
// color: "$red500",
// },
// },
// isDisabled: {
// true: {
// color: "$accents5",
// },
// },
// },
// });
// export const StyledRadioContainer = styled("div", {
// w: "initial",
// position: "relative",
// d: "flex",
// fd: "row",
// ai: "center",
// jc: "flex-start",
// });

View File

@ -17,6 +17,7 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
getWrapperProps,
getInputProps,
getLabelProps,
getControlProps,
} = useRadio({ref, ...props});
return (
@ -25,12 +26,14 @@ const Radio = forwardRef<RadioProps, "label">((props, ref) => {
<input {...getInputProps()} />
</VisuallyHidden>
<span {...getWrapperProps()}>
<span className={slots.point({class: styles?.point})} />
<span {...getControlProps()} />
</span>
{children && <span {...getLabelProps()}>{children}</span>}
{description && (
<span className={slots.description({class: styles?.description})}>{description}</span>
)}
<div className={slots.labelWrapper({class: styles?.labelWrapper})}>
{children && <span {...getLabelProps()}>{children}</span>}
{description && (
<span className={slots.description({class: styles?.description})}>{description}</span>
)}
</div>
</Component>
);
});

View File

@ -69,7 +69,7 @@ export function useRadioGroup(props: UseRadioGroupProps) {
isDisabled = false,
disableAnimation = false,
orientation = "vertical",
isRequired,
isRequired = false,
validationState,
className,
...otherProps

View File

@ -36,6 +36,7 @@ interface Props extends HTMLNextUIProps<"label"> {
* base:"base-classes",
* wrapper: "wrapper-classes",
* point: "control-classes", // inner circle
* labelWrapper: "label-wrapper-classes", // this wraps the label and description
* label: "label-classes",
* description: "description-classes",
* }} />
@ -55,6 +56,8 @@ export function useRadio(props: UseRadioProps) {
as,
ref,
styles,
id,
value,
children,
description,
size = groupContext?.size ?? "md",
@ -71,7 +74,7 @@ export function useRadio(props: UseRadioProps) {
if ("checked" in otherProps) {
warn('Remove props "checked" if in the Radio.Group.', "Radio");
}
if (otherProps.value === undefined) {
if (value === undefined) {
warn('Props "value" must be defined if in the Radio.Group.', "Radio");
}
}
@ -89,7 +92,7 @@ export function useRadio(props: UseRadioProps) {
);
const ariaRadioProps = useMemo(() => {
const arialabel =
const ariaLabel =
otherProps["aria-label"] || typeof children === "string" ? (children as string) : undefined;
const ariaDescribedBy =
otherProps["aria-describedby"] || typeof description === "string"
@ -99,15 +102,16 @@ export function useRadio(props: UseRadioProps) {
return {
isDisabled,
isRequired,
"aria-label": arialabel,
"aria-labelledby": otherProps["aria-labelledby"] || arialabel,
"aria-label": ariaLabel,
"aria-describedby": otherProps["aria-describedby"] || ariaDescribedBy,
};
}, [isDisabled, isRequired]);
const {inputProps} = useReactAriaRadio(
{
...otherProps,
id,
value,
children,
...groupContext,
...ariaRadioProps,
},
@ -127,11 +131,12 @@ export function useRadio(props: UseRadioProps) {
color,
size,
radius,
isInvalid,
isDisabled,
isFocusVisible,
disableAnimation,
}),
[color, size, radius, isDisabled, isFocusVisible, disableAnimation],
[color, size, radius, isDisabled, isInvalid, isFocusVisible, disableAnimation],
);
const baseStyles = clsx(styles?.base, className);
@ -151,6 +156,7 @@ export function useRadio(props: UseRadioProps) {
return {
"data-active": dataAttr(inputProps.checked),
"data-hover": dataAttr(isHovered),
"data-hover-unchecked": dataAttr(isHovered && !inputProps.checked),
"data-checked": dataAttr(inputProps.checked),
"data-focus": dataAttr(isFocused),
"data-focus-visible": dataAttr(isFocused && isFocusVisible),
@ -166,6 +172,9 @@ export function useRadio(props: UseRadioProps) {
const getInputProps: PropGetter = () => {
return {
ref: inputRef,
required: isRequired,
"aria-required": dataAttr(isRequired),
"data-invalid": dataAttr(isInvalid),
"data-readonly": dataAttr(inputProps.readOnly),
...mergeProps(inputProps, focusProps),
};
@ -181,6 +190,16 @@ export function useRadio(props: UseRadioProps) {
[slots, isDisabled, inputProps.checked, isInvalid],
);
const getControlProps: PropGetter = useCallback(
() => ({
"data-disabled": dataAttr(isDisabled),
"data-checked": dataAttr(inputProps.checked),
"data-invalid": dataAttr(isInvalid),
className: slots.control({class: styles?.control}),
}),
[slots, isDisabled, inputProps.checked, isInvalid],
);
return {
Component,
children,
@ -191,6 +210,7 @@ export function useRadio(props: UseRadioProps) {
getWrapperProps,
getInputProps,
getLabelProps,
getControlProps,
};
}

View File

@ -1,6 +1,6 @@
import React from "react";
import {ComponentStory, ComponentMeta} from "@storybook/react";
import {radio} from "@nextui-org/theme";
import {radio, button} from "@nextui-org/theme";
import {RadioGroup, Radio, RadioGroupProps} from "../src";
@ -40,264 +40,121 @@ const defaultProps = {
label: "Options",
};
const Template: ComponentStory<typeof RadioGroup> = (args: RadioGroupProps) => (
<RadioGroup {...args}>
<Radio value="A">Option A</Radio>
<Radio value="B">Option B</Radio>
<Radio value="C">Option C</Radio>
<Radio value="D">Option D</Radio>
</RadioGroup>
);
const Template: ComponentStory<typeof RadioGroup> = (args: RadioGroupProps) => {
const radioProps = args.description
? {
a: {
description: "Description for Option A",
},
b: {
description: "Description for Option B",
},
c: {
description: "Description for Option C",
},
d: {
description: "Description for Option D",
},
}
: {
a: {},
b: {},
c: {},
d: {},
};
const items = (
<>
<Radio value="A" {...radioProps.a}>
Option A
</Radio>
<Radio value="B" {...radioProps.b}>
Option B
</Radio>
<Radio value="C" {...radioProps.c}>
Option C
</Radio>
<Radio value="D" {...radioProps.d}>
Option D
</Radio>
</>
);
return args.isRequired ? (
<form
className="flex flex-col items-start gap-4"
onSubmit={(e) => {
e.preventDefault();
alert("Submitted!");
}}
>
<RadioGroup {...args}>{items}</RadioGroup>
<button className={button({color: "primary"})} type="submit">
Submit
</button>
</form>
) : (
<RadioGroup {...args}>{items}</RadioGroup>
);
};
export const Default = Template.bind({});
Default.args = {
...defaultProps,
};
// import React from "react";
// import {Meta} from "@storybook/react";
// import {Button} from "@nextui-org/button";
// import {Spacer} from "@nextui-org/spacer";
export const IsDisabled = Template.bind({});
IsDisabled.args = {
...defaultProps,
isDisabled: true,
};
// import {Radio} from "../src";
export const IsRequired = Template.bind({});
IsRequired.args = {
...defaultProps,
isRequired: true,
};
// export default {
// title: "Inputs/Radio",
// component: Radio,
// onChange: {action: "changed"},
// } as Meta;
export const WithDescription = Template.bind({});
WithDescription.args = {
...defaultProps,
description: "for",
};
// export const Default = () => (
// <Radio.Group label="Options">
// <Radio value="A">Option A</Radio>
// <Radio value="B">Option B</Radio>
// <Radio value="C">Option C</Radio>
// <Radio value="D">Option D</Radio>
// </Radio.Group>
// );
export const Invalid = Template.bind({});
Invalid.args = {
...defaultProps,
validationState: "invalid",
description: "for",
};
// const handleSubmit = (e: any) => {
// e.preventDefault();
// alert("Submitted!");
// };
export const Row = Template.bind({});
Row.args = {
...defaultProps,
orientation: "horizontal",
description: "for",
};
// export const Required = () => (
// <form onSubmit={handleSubmit}>
// <Radio.Group isRequired label="Options">
// <Radio value="A">Option A</Radio>
// <Radio value="B">Option B</Radio>
// <Radio value="C">Option C</Radio>
// <Radio value="D">Option D</Radio>
// </Radio.Group>
// <Spacer y={1} />
// <Button type="submit">Submit</Button>
// </form>
// );
export const Controlled = () => {
const [checked, setChecked] = React.useState<string>("london");
// export const Disabled = () => (
// <Radio.Group isDisabled defaultValue="A" label="Options">
// <Radio description="Description for Option A" value="A">
// Option A
// </Radio>
// <Radio value="B">Option B</Radio>
// <Radio value="C">Option C</Radio>
// <Radio value="D">Option D</Radio>
// </Radio.Group>
// );
React.useEffect(() => {
// eslint-disable-next-line no-console
console.log("checked:", checked);
}, [checked]);
// export const Sizes = () => {
// return (
// <div style={{display: "flex", flexDirection: "row", gap: 200}}>
// <Radio.Group defaultValue="md" label="Sizes">
// <Radio size="xs" value="xs">
// mini
// </Radio>
// <Radio size="sm" value="sm">
// small
// </Radio>
// <Radio size="md" value="md">
// medium
// </Radio>
// <Radio size="lg" value="lg">
// large
// </Radio>
// <Radio size="xl" value="xl">
// xlarge
// </Radio>
// </Radio.Group>
// <Radio.Group defaultValue="md" label="Sizes">
// <Radio description="Description for Option mini" size="xs" value="xs">
// mini
// </Radio>
// <Radio description="Description for Option small" size="sm" value="sm">
// small
// </Radio>
// <Radio description="Description for Option medium" size="md" value="md">
// medium
// </Radio>
// <Radio description="Description for Option large" size="lg" value="lg">
// large
// </Radio>
// <Radio description="Description for Option xlarge" size="xl" value="xl">
// xlarge
// </Radio>
// </Radio.Group>
// </div>
// );
// };
return (
<RadioGroup label="Select city" value={checked} onChange={setChecked}>
<Radio value="buenos-aires">Buenos Aires</Radio>
<Radio value="sydney">Sydney</Radio>
<Radio value="london">London</Radio>
<Radio value="tokyo">Tokyo</Radio>
</RadioGroup>
);
};
// export const Colors = () => {
// return (
// <Radio.Group defaultValue="primary" label="Colors">
// <Radio color="primary" value="primary">
// primary
// </Radio>
// <Radio color="secondary" value="secondary">
// secondary
// </Radio>
// <Radio color="success" value="success">
// success
// </Radio>
// <Radio color="warning" value="warning">
// warning
// </Radio>
// <Radio color="error" value="error">
// error
// </Radio>
// </Radio.Group>
// );
// };
// export const LabelColors = () => {
// return (
// <Radio.Group defaultValue="primary" label="Label colors">
// <Radio color="primary" labelColor="primary" value="primary">
// primary
// </Radio>
// <Radio color="secondary" labelColor="secondary" value="secondary">
// secondary
// </Radio>
// <Radio color="success" labelColor="success" value="success">
// success
// </Radio>
// <Radio color="warning" labelColor="warning" value="warning">
// warning
// </Radio>
// <Radio color="error" labelColor="error" value="error">
// error
// </Radio>
// </Radio.Group>
// );
// };
// export const Squared = () => (
// <Radio.Group defaultValue="A" label="Options">
// <Radio isSquared value="A">
// Option A
// </Radio>
// <Radio isSquared value="B">
// Option B
// </Radio>
// <Radio isSquared value="C">
// Option C
// </Radio>
// <Radio isSquared value="D">
// Option D
// </Radio>
// </Radio.Group>
// );
// export const Description = () => (
// <Radio.Group defaultValue="A" label="Options">
// <Radio description="Description for Option A" value="A">
// Option A
// </Radio>
// <Radio description="Description for Option B" value="B">
// Option B
// </Radio>
// <Radio description="Description for Option C" value="C">
// Option C
// </Radio>
// <Radio description="Description for Option D" value="D">
// Option D
// </Radio>
// </Radio.Group>
// );
// export const Invalid = () => (
// <Radio.Group defaultValue="A" label="Options" validationState="invalid">
// <Radio description="Description for Option A" value="A">
// Option A
// </Radio>
// <Radio description="Description for Option B" value="B">
// Option B
// </Radio>
// <Radio description="Description for Option C" value="C">
// Option C
// </Radio>
// <Radio description="Description for Option D" value="D">
// Option D
// </Radio>
// </Radio.Group>
// );
// export const Row = () => (
// <div style={{display: "flex", flexDirection: "column", gap: 100}}>
// <Radio.Group defaultValue="A" label="Options" orientation="horizontal">
// <Radio value="A">Option A</Radio>
// <Radio value="B">Option B</Radio>
// <Radio value="C">Option C</Radio>
// <Radio value="D">Option D</Radio>
// </Radio.Group>
// <Radio.Group defaultValue="A" label="Options" orientation="horizontal">
// <Radio description="Description for Option A" value="A">
// Option A
// </Radio>
// <Radio description="Description for Option B" value="B">
// Option B
// </Radio>
// <Radio description="Description for Option C" value="C">
// Option C
// </Radio>
// <Radio description="Description for Option D" value="D">
// Option D
// </Radio>
// </Radio.Group>
// </div>
// );
// export const Controlled = () => {
// const [checked, setChecked] = React.useState<string>("london");
// React.useEffect(() => {
// console.log("checked:", checked);
// }, [checked]);
// return (
// <Radio.Group label="Check cities" value={checked} onChange={(value) => setChecked(value)}>
// <Radio value="buenos-aires">Buenos Aires</Radio>
// <Radio value="sydney">Sydney</Radio>
// <Radio value="london">London</Radio>
// <Radio value="tokyo">Tokyo</Radio>
// </Radio.Group>
// );
// };
// export const DisableAnimation = () => {
// return (
// <Radio.Group defaultValue="A" label="Options">
// <Radio disableAnimation value="A">
// Option A
// </Radio>
// <Radio disableAnimation value="B">
// Option B
// </Radio>
// <Radio disableAnimation value="C">
// Option C
// </Radio>
// <Radio disableAnimation value="D">
// Option D
// </Radio>
// </Radio.Group>
// );
// };
export const DisableAnimation = Template.bind({});
DisableAnimation.args = {
...defaultProps,
disableAnimation: true,
};

View File

@ -1 +0,0 @@
export * from "./dist/config";

View File

@ -1 +0,0 @@
module.exports = require("./dist/config");

View File

@ -46,8 +46,8 @@ const checkbox = tv({
"data-[checked=true]:after:scale-100",
"data-[checked=true]:after:opacity-100",
// hover
"hover:before:bg-neutral",
"data-[hover=true]:before:bg-neutral",
"hover:before:bg-neutral-100",
"data-[hover=true]:before:bg-neutral-100",
],
icon: "z-10 w-4 h-3 opacity-0 data-[checked=true]:opacity-100",
label: "relative ml-1 text-foreground select-none",
@ -154,8 +154,7 @@ const checkbox = tv({
wrapper: "transition-none",
},
false: {
wrapper:
"before:transition-background after:transition-transform-opacity before:!duration-250 after:!duration-250",
wrapper: ["before:transition-background", "after:transition-transform-opacity"],
icon: "transition-opacity",
label: "transition-opacity before:transition-width",
},

View File

@ -5,56 +5,157 @@ import {ringClasses} from "../utils";
/**
* Radio wrapper **Tailwind Variants** component
*
* const {base, wrapper, point, label, description} = radio({...})
* const {base, wrapper, point, labelWrapper, label, description} = radio({...})
*
* @example
* <label className={base())}>
* // input
* <span className={wrapper()} aria-hidden="true" data-checked={checked}>
* <span className={wrapper()} aria-hidden="true" data-checked={checked} data-hover-unchecked={hoverUnchecked}>
* <span className={point()}/>
* </span>
* <span className={label()}>Label</span>
* <span className={description()}>Description</span>
* <div className={labelWrapper()}>
* <span className={label()}>Label</span>
* <span className={description()}>Description</span>
* </div>
* </label>
*/
const radio = tv({
slots: {
base: "relative max-w-fit inline-flex items-center justify-start cursor-pointer",
wrapper: "",
point: "",
label: "relative ml-1 text-foreground select-none",
description: "relative ml-1 text-neutral-500 select-none",
wrapper: [
"relative",
"inline-flex",
"items-center",
"justify-center",
"flex-shrink-0",
"overflow-hidden",
"border-solid",
"border-2",
"box-border",
"border-neutral",
"data-[hover-unchecked=true]:bg-neutral-100",
],
labelWrapper: "flex flex-col ml-1",
control: [
"z-10",
"w-2",
"h-2",
"opacity-0",
"scale-0",
"origin-center",
"data-[checked=true]:opacity-100",
"data-[checked=true]:scale-100",
],
label: "relative text-foreground select-none",
description: "relative text-neutral-400",
},
variants: {
color: {
neutral: {},
primary: {},
secondary: {},
success: {},
warning: {},
danger: {},
neutral: {
control: "bg-neutral-500 text-neutral-contrastText",
wrapper: "data-[checked=true]:border-neutral-500",
},
primary: {
control: "bg-primary text-primary-contrastText",
wrapper: "data-[checked=true]:border-primary",
},
secondary: {
control: "bg-secondary text-secondary-contrastText",
wrapper: "data-[checked=true]:border-secondary",
},
success: {
control: "bg-success text-success-contrastText",
wrapper: "data-[checked=true]:border-success",
},
warning: {
control: "bg-warning text-warning-contrastText",
wrapper: "data-[checked=true]:border-warning",
},
danger: {
control: "bg-danger text-danger-contrastText",
wrapper: "data-[checked=true]:border-danger",
},
},
size: {
xs: {},
sm: {},
md: {},
lg: {},
xl: {},
xs: {
wrapper: "w-3.5 h-3.5",
control: "w-1 h-1",
labelWrapper: "ml-1",
label: "text-xs",
description: "text-xs",
},
sm: {
wrapper: "w-4 h-4",
control: "w-1.5 h-1.5",
labelWrapper: "ml-1",
label: "text-sm",
description: "text-xs",
},
md: {
wrapper: "w-5 h-5",
control: "w-2 h-2",
labelWrapper: "ml-2",
label: "text-base",
description: "text-sm",
},
lg: {
wrapper: "w-6 h-6",
control: "w-2.5 h-2.5",
labelWrapper: "ml-2",
label: "text-lg",
description: "text-base",
},
xl: {
wrapper: "w-7 h-7",
control: "w-3 h-3",
labelWrapper: "ml-3",
label: "text-xl",
description: "text-lg",
},
},
radius: {
none: {},
base: {},
sm: {},
md: {},
lg: {},
xl: {},
full: {},
none: {
wrapper: "rounded-none",
control: "rounded-none",
},
base: {
wrapper: "rounded",
control: "rounded",
},
sm: {
wrapper: "rounded-sm",
control: "rounded-sm",
},
md: {
wrapper: "rounded-md",
control: "rounded-sm",
},
lg: {
wrapper: "rounded-lg",
control: "rounded",
},
xl: {
wrapper: "rounded-xl",
control: "rounded-md",
},
full: {
wrapper: "rounded-full",
control: "rounded-full",
},
},
isDisabled: {
true: {
base: "opacity-50 pointer-events-none",
},
},
isInvalid: {
true: {
control: "bg-danger text-danger-contrastText",
wrapper: "border-danger data-[checked=true]:border-danger",
label: "text-danger",
description: "text-danger-300",
},
},
isFocusVisible: {
true: {
wrapper: [...ringClasses],
@ -62,14 +163,18 @@ const radio = tv({
},
disableAnimation: {
true: {},
false: {},
false: {
wrapper: "transition-background",
control: "transition-transform-opacity",
},
},
},
defaultVariants: {
color: "primary",
size: "md",
radius: "md",
radius: "full",
isDisabled: false,
isInvalid: false,
disableAnimation: false,
},
});

View File

@ -1,80 +0,0 @@
import type {Config as TWConfig} from "tailwindcss/types/config";
import get from "lodash.get";
import deepMerge from "deepmerge";
import resolveConfig from "tailwindcss/resolveConfig";
import {commonColors} from "./colors";
// import {theme} from "./plugin";
export type NextUIDefaultColors = {
background: string;
foreground: string;
border: string;
neutral: string;
primary: string;
secondary: string;
success: string;
danger: string;
warning: string;
};
export type ColorValue =
| NextUIDefaultColors
| Record<string, string>
| Record<string, Record<number, string>>;
export type Colors = {
common?: ColorValue;
light?: ColorValue;
dark?: ColorValue;
};
export type ExtendedThemeConfig = TWConfig["theme"] & {
extend?: Omit<TWConfig["theme"], "extend"> & {
colors?: Colors;
};
};
export interface Config extends TWConfig {
theme?: ExtendedThemeConfig;
}
export type WithNextUI = {
<C extends Config>(nextuiConfig: Config): C;
};
export const withNextUI = (tailwindConfig: Config) => {
let config = resolveConfig(tailwindConfig);
const userColors = get(config.theme, "colors", {});
// const userLightColors = get(userColors, "light", {});
// const userDarkColors = get(userColors, "dark", {});
if (userColors && config.theme?.colors) {
config.theme.colors = deepMerge(userColors, commonColors);
}
// config.plugins = [
// theme({
// light: {
// primary: "#0072f5",
// secondary: "darkblue",
// brand: "#F3F3F3",
// },
// dark: {
// primary: "#17c964",
// secondary: "tomato",
// brand: "#4A4A4A",
// },
// }),
// theme({
// light: deepMerge(semanticColors.light, userLightColors),
// dark: deepMerge(semanticColors.dark, userDarkColors),
// }),
// ];
// console.log(config);
return config;
};

View File

@ -1,4 +1,4 @@
const DEFAULT_TRANSITION_DURATION = "200ms";
const DEFAULT_TRANSITION_DURATION = "250ms";
export const utilities = {
/**