Skip to content

Commit

Permalink
feat(checkbox): ✨ add text & description
Browse files Browse the repository at this point in the history
  • Loading branch information
navin-moorthy committed Aug 17, 2021
1 parent 51713fa commit 3f881a4
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 38 deletions.
1 change: 1 addition & 0 deletions preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const renderlesskitConfig = {
sm: ["14px", "115%"],
base: ["16px", "115%"],
"paragraph-cxs": ["13px", "150%"],
"paragraph-sm": ["14px", "150%"],
"2base": ["32px", "115%"],
},
inset: {
Expand Down
10 changes: 5 additions & 5 deletions src/button/stories/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,24 +141,24 @@ export const ButtonStack: ComponentStory<typeof Button> = {
parameters: { options: { showPanel: true } },
};

export const Hover: ComponentStory<typeof Button> = {
export const HoverStack: ComponentStory<typeof Button> = {
...ButtonStack,
parameters: { options: { showPanel: false }, pseudo: { hover: true } },
};
export const Active: ComponentStory<typeof Button> = {
export const ActiveStack: ComponentStory<typeof Button> = {
...ButtonStack,
parameters: { options: { showPanel: false }, pseudo: { active: true } },
};
export const Focus: ComponentStory<typeof Button> = {
export const FocusStack: ComponentStory<typeof Button> = {
...ButtonStack,
parameters: { options: { showPanel: false }, pseudo: { focusVisible: true } },
};
export const Disabled: ComponentStory<typeof Button> = {
export const DisabledStack: ComponentStory<typeof Button> = {
...ButtonStack,
args: { disabled: true },
parameters: { options: { showPanel: false } },
};
export const Loading: ComponentStory<typeof Button> = {
export const LoadingStack: ComponentStory<typeof Button> = {
...ButtonStack,
args: { loading: true },
parameters: { options: { showPanel: false } },
Expand Down
75 changes: 59 additions & 16 deletions src/checkboxNew/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { useControllableState } from "@renderlesskit/react";
import { CheckboxStateReturn as ReakitCheckboxStateReturn } from "reakit";

import { forwardRefWithAs } from "../utils/types";
import {
CheckboxDefaultIcon,
CheckboxIcon,
CheckboxIconProps,
CheckboxIconRenderProps,
} from "./CheckboxIcon";
import { CheckboxText } from "./CheckboxText";
import { CheckboxLabel } from "./CheckboxLabel";
import { CheckboxIcon, CheckboxIconProps } from "./CheckboxIcon";
import { forwardRefWithAs } from "../utils/types";
import { CheckboxDescription } from "./CheckboxDescription";
import { CheckboxInput, CheckboxInputProps } from "./CheckboxInput";

export type CheckboxProps = Omit<CheckboxInputProps, "size"> & {
Expand Down Expand Up @@ -33,9 +40,19 @@ export type CheckboxProps = Omit<CheckboxInputProps, "size"> & {
size?: CheckboxIconProps["size"];

/**
* Props to pass to the Label.
* Provide custom icons as a replacement for the default ones.
*/
icon?: (props: CheckboxIconRenderProps) => React.ReactNode;

/**
* Label for the Checkbox.
*/
labelProps?: React.HTMLAttributes<HTMLLabelElement>;
label?: string;

/**
* Description for the Checkbox.
*/
description?: string;
};

export const Checkbox = forwardRefWithAs<
Expand All @@ -49,7 +66,14 @@ export const Checkbox = forwardRefWithAs<
state: stateProp,
onStateChange,
size = "md",
labelProps,
invalid = false,
icon = CheckboxDefaultIcon,
label,
description,
// Top level styles(label) for the checkbox
className,
style,
children,
...inputProps
} = props;

Expand All @@ -60,17 +84,36 @@ export const Checkbox = forwardRefWithAs<
});
console.log("%c state", "color: #73998c", state);

return (
<CheckboxLabel {...labelProps}>
<CheckboxInput
ref={ref}
state={state}
setState={setState}
{...inputProps}
/>
<CheckboxIcon state={state} size={size} />
</CheckboxLabel>
);
if (description && !label) {
console.warn("Checkbox: `description` should be used along with `label`");
}

if (!children) {
return (
<CheckboxLabel className={className} style={style}>
<CheckboxInput
ref={ref}
state={state}
setState={setState}
{...inputProps}
/>
<CheckboxIcon state={state} size={size} invalid={invalid}>
{icon}
</CheckboxIcon>
{label && !description ? (
<CheckboxText size={size}>{label}</CheckboxText>
) : null}
{label && description ? (
<div className="flex flex-col">
<CheckboxText size={size}>{label}</CheckboxText>
<CheckboxDescription size={size}>{description}</CheckboxDescription>
</div>
) : null}
</CheckboxLabel>
);
}

return null;
});

Checkbox.displayName = "Checkbox";
30 changes: 30 additions & 0 deletions src/checkboxNew/CheckboxDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cx } from "@renderlesskit/react";

import { useTheme } from "../theme";
import { Box, BoxProps } from "../box";
import { forwardRefWithAs } from "../utils/types";

export type CheckboxDescriptionProps = BoxProps & {
size: keyof Renderlesskit.GetThemeValue<"checkboxNew", "icon", "size">;
};

export const CheckboxDescription = forwardRefWithAs<
CheckboxDescriptionProps,
HTMLSpanElement,
"span"
>((props, ref) => {
const { size, className, ...rest } = props;

const theme = useTheme("checkboxNew");
const checkboxDescriptionStyles = cx(
theme.description.base,
theme.description.size[size],
className,
);

return (
<Box as="span" ref={ref} className={checkboxDescriptionStyles} {...rest} />
);
});

CheckboxDescription.displayName = "CheckboxDescription";
60 changes: 52 additions & 8 deletions src/checkboxNew/CheckboxIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { cx } from "@renderlesskit/react";

import { useTheme } from "../theme";
import { Box, BoxProps } from "../box";
import { withIconA11y } from "../utils";
import { CheckboxProps } from "./Checkbox";
import { runIfFn, withIconA11y } from "../utils";
import { forwardRefWithAs } from "../utils/types";
import { CheckIcon, IndeterminateIcon } from "../icons";

export type CheckboxIconRenderProps = Pick<CheckboxProps, "state" | "invalid">;

export type CheckboxIconProps = BoxProps &
Required<Pick<CheckboxProps, "state">> & {
Required<Pick<CheckboxProps, "state" | "invalid">> & {
size: keyof Renderlesskit.GetThemeValue<"checkboxNew", "icon", "size">;
};

Expand All @@ -17,7 +19,7 @@ export const CheckboxIcon = forwardRefWithAs<
HTMLSpanElement,
"span"
>((props, ref) => {
const { state, size, className, children, ...rest } = props;
const { state, size, invalid, className, children, ...rest } = props;
const isChecked = state === true;
const isUnchecked = state === false;
const isIndeterminate = state === "indeterminate";
Expand All @@ -26,18 +28,60 @@ export const CheckboxIcon = forwardRefWithAs<
const baseStyles = cx(
checkbox.icon.base,
checkbox.icon.size[size],
isUnchecked ? checkbox.icon.unChecked.default : "",
isChecked ? checkbox.icon.checked.default : "",
isIndeterminate ? checkbox.icon.checked.default : "",
isUnchecked
? invalid
? checkbox.icon.unChecked.invalid
: cx(
checkbox.icon.unChecked.default,
checkbox.icon.unChecked.hover,
checkbox.icon.unChecked.active,
checkbox.icon.unChecked.focus,
checkbox.icon.unChecked.disabled,
)
: "",
isChecked
? invalid
? checkbox.icon.checked.invalid
: cx(
checkbox.icon.checked.default,
checkbox.icon.checked.hover,
checkbox.icon.checked.active,
checkbox.icon.checked.focus,
checkbox.icon.checked.disabled,
)
: "",
isIndeterminate
? invalid
? checkbox.icon.indeterminate.invalid
: cx(
checkbox.icon.checked.default,
checkbox.icon.indeterminate.hover,
checkbox.icon.indeterminate.active,
checkbox.icon.indeterminate.focus,
checkbox.icon.indeterminate.disabled,
)
: "",
className,
);

return (
<Box ref={ref} as="span" className={baseStyles} {...rest}>
{isChecked ? withIconA11y(<CheckIcon />) : null}
{isIndeterminate ? withIconA11y(<IndeterminateIcon />) : null}
{children ? runIfFn(children, { state, invalid }) : null}
</Box>
);
});

CheckboxIcon.displayName = "CheckboxIcon";

export const CheckboxDefaultIcon = (props: CheckboxIconRenderProps) => {
const { state } = props;
const isChecked = state === true;
const isIndeterminate = state === "indeterminate";

return (
<>
{isChecked ? withIconA11y(<CheckIcon />) : null}
{isIndeterminate ? withIconA11y(<IndeterminateIcon />) : null}
</>
);
};
15 changes: 13 additions & 2 deletions src/checkboxNew/CheckboxInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { cx } from "@renderlesskit/react";
import { useTheme } from "../theme";
import { forwardRefWithAs } from "../utils/types";

export type CheckboxInputProps = ReakitCheckboxProps;
export type CheckboxInputProps = ReakitCheckboxProps & {
/**
* If `true`, the checkbox will be invalid.
*
* @default false
*/
invalid?: boolean;
};

export const CheckboxInput = forwardRefWithAs<
CheckboxInputProps,
Expand All @@ -26,19 +33,23 @@ export const CheckboxInput = forwardRefWithAs<
// Because they work standalone withount the below `uncontrolled` state logic.
// <ReakitCheckbox checked={check} onChange={e => console.log(setCheck(e.target.checked))} />
// Because we are handling them using `state` and `onStateChange`
invalid,
// We are removing className & style, so users don't change the input styles
className,
style,
...rest
} = props;

const checkbox = useTheme("checkboxNew");
const baseStyles = cx(checkbox.input, className);
const baseStyles = cx(checkbox.input);

return (
<ReakitCheckbox
ref={ref}
state={state}
setState={setState}
className={baseStyles}
aria-invalid={invalid}
{...rest}
/>
);
Expand Down
28 changes: 28 additions & 0 deletions src/checkboxNew/CheckboxText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { cx } from "@renderlesskit/react";

import { useTheme } from "../theme";
import { Box, BoxProps } from "../box";
import { forwardRefWithAs } from "../utils/types";

export type CheckboxTextProps = BoxProps & {
size: keyof Renderlesskit.GetThemeValue<"checkboxNew", "icon", "size">;
};

export const CheckboxText = forwardRefWithAs<
CheckboxTextProps,
HTMLSpanElement,
"span"
>((props, ref) => {
const { size, className, ...rest } = props;

const theme = useTheme("checkboxNew");
const checkboxTextStyles = cx(
theme.text.base,
theme.text.size[size],
className,
);

return <Box as="span" ref={ref} className={checkboxTextStyles} {...rest} />;
});

CheckboxText.displayName = "CheckboxText";
Loading

0 comments on commit 3f881a4

Please sign in to comment.