Skip to content

Commit

Permalink
feat: checkbox (#40)
Browse files Browse the repository at this point in the history
* feat: checkbox
* Create calm-paws-search.md
  • Loading branch information
riccardoperra authored Oct 1, 2023
1 parent 8eedf29 commit 0d2a10b
Show file tree
Hide file tree
Showing 11 changed files with 794 additions and 104 deletions.
6 changes: 6 additions & 0 deletions .changeset/calm-paws-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@codeui/kit": patch
"@codeui/playground-next": patch
---

feat: checkbox
4 changes: 2 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
},
"packageManager": "[email protected]",
"dependencies": {
"@kobalte/core": "^0.9.2",
"@kobalte/utils": "^0.7.1",
"@kobalte/core": "^0.11.0",
"@kobalte/utils": "^0.9.0",
"@kobalte/vanilla-extract": "^0.4.0",
"@motionone/solid": "^10.16.0",
"@radix-ui/colors": "^0.1.8",
Expand Down
201 changes: 201 additions & 0 deletions packages/kit/src/components/CheckBox/CheckBox.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { createTheme, style, StyleRule } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes";
import { mapFontSizeValue, mapSizeValue } from "../../foundation/sizes.css";
import { themeTokens } from "../../foundation/themes.css";
import { baseFieldTheme, FieldSizes } from "../Field/Field.css";
import { tokens } from "../../foundation/contract.css";
import { styleFieldMessage } from "../Field/fieldStyle";

export const [checkboxTheme, checkboxVars] = createTheme({
size: "",
fontSize: "",
borderColor: tokens.formAccentBorder,
background: tokens.brand,
activeHoverBackground: tokens.brandAccentHover,
nonActiveHoverBackground: tokens.formAccent,
color: tokens.foreground,
radius: `calc(${themeTokens.radii.md} * .6)`,
});

export const container = style([
baseFieldTheme,
checkboxTheme,
{
display: "inline-flex",
alignItems: "flex-start",
},
]);

export const input = style({
border: 0,
clip: "rect(0px, 0px, 0px, 0px)",
clipPath: "inset(50%)",
height: "1px",
margin: "0px -1px -1px 0px",
overflow: "hidden",
padding: "0px",
position: "absolute",
width: "1px",
whiteSpace: "nowrap",
":focus-visible": {
outline: `2px solid ${tokens.brand}`,
outlineOffset: "2px",
borderRadius: checkboxVars.radius,
},
});

const sizesCss = {
xs: "1rem",
sm: "1.25rem",
md: "1.5rem",
lg: "1.75rem",
xl: "2rem",
};

export const icon = style({
width: checkboxVars.fontSize,
height: checkboxVars.fontSize,
});

export const control = recipe({
base: [
{
position: "relative",
height: checkboxVars.size,
width: checkboxVars.size,
overflow: "hidden",
borderRadius: checkboxVars.radius,
":focus-visible": {
outline: `2px solid ${checkboxVars.background}`,
},
},
{
selectors: {
"&:hover::before": {
backgroundColor: checkboxVars.nonActiveHoverBackground,
},
"&:before": {
content: "",
border: `2px solid ${checkboxVars.borderColor}`,
borderRadius: checkboxVars.radius,
position: "absolute",
width: "100%",
height: "100%",
top: 0,
left: 0,
},
"&:after": {
content: "",
position: "absolute",
opacity: 0,
background: checkboxVars.background,
borderRadius: checkboxVars.radius,
width: "100%",
height: "100%",
top: 0,
left: 0,
transitionProperty: "transform, opacity",
transitionTimingFunction: "ease",
transitionDuration: ".25s",
transform: `scale(0.5)`,
transformOrigin: "center",
},
"&:hover::after": {
backgroundColor: checkboxVars.activeHoverBackground,
},
"[data-checked] &:after": {
opacity: 1,
transform: `scale(1)`,
},
},
},
],
variants: {
size: {
[FieldSizes.xs]: {
vars: {
[checkboxVars.size]: mapSizeValue("xs", sizesCss),
[checkboxVars.fontSize]: mapFontSizeValue("xs"),
[checkboxVars.radius]: `calc(${themeTokens.radii.md} * .5)`,
},
},
[FieldSizes.sm]: {
vars: {
[checkboxVars.size]: mapSizeValue("sm", sizesCss),
[checkboxVars.fontSize]: mapFontSizeValue("sm"),
},
},
[FieldSizes.md]: {
vars: {
[checkboxVars.size]: mapSizeValue("md", sizesCss),
[checkboxVars.fontSize]: mapFontSizeValue("md"),
},
},
[FieldSizes.lg]: {
vars: {
[checkboxVars.size]: mapSizeValue("lg", sizesCss),
[checkboxVars.fontSize]: mapFontSizeValue("md"),
[checkboxVars.radius]: `calc(${themeTokens.radii.md} * .7)`,
},
},
[FieldSizes.xl]: {
vars: {
[checkboxVars.size]: mapSizeValue("xl", sizesCss),
[checkboxVars.fontSize]: mapFontSizeValue("lg"),
[checkboxVars.radius]: `calc(${themeTokens.radii.md} * .8)`,
},
},
},
},
defaultVariants: {
size: "md",
},
});

export const indicator = style({
display: "flex",
position: "relative",
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
zIndex: themeTokens.zIndex["10"],
opacity: 0,
transitionProperty: "transform, opacity",
transitionTimingFunction: "ease",
transitionDuration: ".25s",
selectors: {
"[data-checked] &": {
opacity: 1,
},
},
});

export const label = style([
{
userSelect: "none",
selectors: {
...Object.entries(sizesCss).reduce((acc, [size, value]) => {
acc[`[data-field-size=${size}] &`] = {
height: value,
lineHeight: value,
};
return acc;
}, {} as NonNullable<StyleRule["selectors"]>),
},
},
styleFieldMessage({
xs: "xs",
sm: "xs",
md: "xs",
lg: "md",
xl: "md",
}),
]);

export const content = style({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
marginLeft: themeTokens.spacing["2"],
});
86 changes: 86 additions & 0 deletions packages/kit/src/components/CheckBox/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Checkbox as KCheckbox } from "@kobalte/core";
import { JSX, Ref, Show, splitProps } from "solid-js";
import * as styles from "./CheckBox.css";
import { BaseFieldProps, createBaseFieldProps } from "../Field/createBaseFieldProps";
import {
createFieldErrorMessageProps,
FieldWithErrorMessageSupport,
} from "../Field/FieldError/createFieldErrorMessageProps";
import { SlotProp } from "../../utils/component";
import { mergeClasses } from "../../utils/css";
import { CheckIcon } from "../../icons/CheckIcon";
import { createFieldMessageProps } from "../Field/FieldMessage/createFieldMessageProps";

type CheckBoxSlot = "root" | "input" | "label" | "errorLabel" | "control";

export type CheckBoxProps = KCheckbox.CheckboxRootOptions &
BaseFieldProps &
FieldWithErrorMessageSupport &
SlotProp<CheckBoxSlot> & {
description?: string;
label?: JSX.Element;
ref?: Ref<HTMLInputElement>;
};

export function Checkbox(props: CheckBoxProps) {
const [local, others] = splitProps(props, [
"description",
"size",
"label",
"theme",
"errorMessage",
"ref",
"slotClasses",
]);

const errorMessageProps = createFieldErrorMessageProps(props);
const fieldLabelProps = createFieldMessageProps(props);

const inputClasses = () => mergeClasses(styles.input, local.slotClasses?.input);

const labelClasses = () => mergeClasses(styles.label, local.slotClasses?.label);

const controlClasses = () =>
mergeClasses(
styles.control({
size: local.size,
}),
local.slotClasses?.control,
);

return (
<KCheckbox.Root
data-cui={"checkbox"}
data-field-size={local.size}
class={mergeClasses(styles.container, local?.slotClasses?.root)}
{...others}
>
<KCheckbox.Input class={inputClasses()} ref={local.ref} />
<KCheckbox.Control class={controlClasses()}>
<KCheckbox.Indicator forceMount={true} class={styles.indicator}>
<CheckIcon class={styles.icon} />
</KCheckbox.Indicator>
</KCheckbox.Control>

<div class={styles.content}>
<Show when={local.label} keyed={false}>
<KCheckbox.Label class={labelClasses()}>{local.label}</KCheckbox.Label>
</Show>

<Show when={local.description} keyed={false}>
<KCheckbox.Description {...fieldLabelProps}>
{local.description}
</KCheckbox.Description>
</Show>

<Show when={errorMessageProps.errorMessage} keyed={false}>
<KCheckbox.ErrorMessage
class={mergeClasses(errorMessageProps.class, local.slotClasses?.errorLabel)}
>
{local.errorMessage}
</KCheckbox.ErrorMessage>
</Show>
</div>
</KCheckbox.Root>
);
}
3 changes: 0 additions & 3 deletions packages/kit/src/components/Field/Field.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ export const baseFieldVariants = recipe({
":focus": {
borderColor: themeTokens.colors.blue8,
},
":focus-visible": {
borderColor: themeTokens.colors.blue9,
},
},
componentStateStyles({
invalid: {
Expand Down
7 changes: 5 additions & 2 deletions packages/kit/src/foundation/sizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const ComponentSizes = {
xl: "xl",
} as const;

const sizes = {
const sizesCss = {
xs: "30px",
sm: "36px",
md: "40px",
Expand All @@ -26,7 +26,10 @@ const fontSizesCss = {
xl: themeTokens.fontSize.xl,
};

export const mapSizeValue = (size: keyof typeof ComponentSizes) => {
export const mapSizeValue = (
size: keyof typeof ComponentSizes,
sizes: Record<string, string> = sizesCss,
) => {
return sizes[size];
};

Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export type { TextFieldProps } from "./components/TextField/TextField";

export { IconButton } from "./components/IconButton/IconButton";

export { Checkbox } from "./components/CheckBox/CheckBox";
export type { CheckBoxProps } from "./components/CheckBox/CheckBox";

export {
Dialog,
DialogPanel,
Expand Down
4 changes: 2 additions & 2 deletions packages/playground-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"vite-tsconfig-paths": "^4.2.0"
},
"dependencies": {
"@kobalte/core": "^0.9.2",
"@kobalte/utils": "^0.7.1",
"@kobalte/core": "^0.11.0",
"@kobalte/utils": "^0.9.0",
"@solidjs/meta": "^0.28.2",
"@solidjs/router": "^0.8.2",
"@vanilla-extract/css": "^1.11.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/playground-next/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export default function Root() {
<SidebarItem>
<A href="/radio">Radio</A>
</SidebarItem>
<SidebarItem>
<A href="/checkbox">Checkbox</A>
</SidebarItem>
<SidebarItem>
<A href="/dropdown-menu">Dropdown Menu</A>
</SidebarItem>
Expand Down
Loading

0 comments on commit 0d2a10b

Please sign in to comment.