Skip to content

Commit 51485a6

Browse files
Feat/checkbox v1 (#182)
* feat: add checkbox * feat: add checkbox doc * feat: add checkbox example * feat: export checkbox * chore: add new badge * chore: comment * chore: minor refactor * chore: change clsx * chore: mark old checkbox deprecated
1 parent d5318cb commit 51485a6

File tree

8 files changed

+258
-29
lines changed

8 files changed

+258
-29
lines changed

Diff for: apps/www/content/primitives/components/checkbox.mdx

+76-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Checkbox
3-
description: Checkbox is a user interface control that enables users to toggle between a checked and unchecked state
3+
description: Checkbox is a user interface control that enables users to toggle between checked, unchecked, and indeterminate states
44
radix:
55
link: https://www.radix-ui.com/docs/primitives/components/checkbox
66
api: https://www.radix-ui.com/docs/primitives/components/checkbox#api-reference
@@ -10,14 +10,14 @@ radix:
1010

1111
<Preview>
1212
<Flex
13-
style={{
14-
flexDirection: "column",
15-
alignItems: "center",
16-
gap: "8px",
17-
}}
13+
direction="row"
14+
align="center"
15+
gap="extra-large"
1816
>
19-
<Checkbox />
20-
<Checkbox size="medium" />
17+
<Checkbox />
18+
<Checkbox checked={true} />
19+
<Checkbox checked="indeterminate" />
20+
<Checkbox disabled />
2121
</Flex>
2222
</Preview>
2323

@@ -29,40 +29,92 @@ Install the component from your command line.
2929
<LiveEditor code={`npm install @raystack/apsara`} border/>
3030
</LiveProvider>
3131

32+
## Usage
33+
34+
### Controlled Component
35+
You can control the checkbox state using the `checked` and `onCheckedChange` props:
36+
37+
<LiveProvider>
38+
<LiveEditor code={`
39+
const [checked, setChecked] = useState(false);
40+
41+
<Checkbox
42+
checked={checked}
43+
onCheckedChange={(value) => setChecked(value)}
44+
/>
45+
`} border />
46+
</LiveProvider>
47+
48+
### Indeterminate State
49+
The indeterminate state is useful for representing partial selection, commonly used in parent checkboxes:
50+
51+
<LiveProvider>
52+
<LiveEditor code={`
53+
const [checked, setChecked] = useState<boolean | 'indeterminate'>('indeterminate');
54+
55+
<Checkbox
56+
checked={checked}
57+
onCheckedChange={(value) => setChecked(value)}
58+
/>
59+
`} border />
60+
</LiveProvider>
61+
62+
## Props
63+
64+
The `Checkbox` component accepts the following props:
65+
66+
- `checked`: The controlled state of the checkbox (`boolean | "indeterminate"`)
67+
- `defaultChecked`: The default state when initially rendered (`boolean | "indeterminate"`)
68+
- `onCheckedChange`: Event handler called when the state changes (`(checked: boolean | "indeterminate") => void`)
69+
- `disabled`: When true, prevents the user from interacting with the checkbox (`boolean`)
3270

3371
## Anatomy
3472
Import all parts and piece them together.
3573

3674
<LiveProvider>
3775
<LiveEditor code={`
38-
import { Checkbox } from '@raystack/apsara'
76+
import { Checkbox } from '@raystack/apsara/v1'
3977
78+
// Basic usage
4079
<Checkbox />
41-
<Checkbox size="medium" />
42-
`} border />
43-
</LiveProvider>
4480
81+
// Checked state
82+
<Checkbox checked={true} />
4583
46-
## Scale
47-
The Checkbox component offers different size options to suit various design needs. By selecting the "Medium" size, you can create a larger Checkbox that provides enhanced visibility and ease of interaction within the user interface. Additionally, the Checkbox component also supports the following size options:
84+
// Indeterminate state
85+
<Checkbox checked="indeterminate" />
4886
49-
- Small
50-
- Medium
87+
// Disabled state
88+
<Checkbox disabled />
89+
`} border />
90+
</LiveProvider>
91+
92+
## States
93+
The Checkbox component supports multiple states to represent different selection conditions:
5194

52-
These different size options allow you to customize the appearance of the Checkbox to match your specific design preferences and ensure a seamless user experience.
95+
- Unchecked: Default state
96+
- Checked: Selected state
97+
- Indeterminate: Partial selection state
98+
- Disabled: Disabled state
5399

54100
<Playground
55101
scope={{ Checkbox }}
56102
tabs={[
57-
58103
{
59-
name: "Small",
60-
code: `<Checkbox size="small" />`,
104+
name: "Unchecked",
105+
code: `<Checkbox />`,
106+
},
107+
{
108+
name: "Checked",
109+
code: `<Checkbox checked={true} />`,
110+
},
111+
{
112+
name: "Indeterminate",
113+
code: `<Checkbox checked="indeterminate" />`,
61114
},
62115
{
63-
name: "Medium",
64-
code: `<Checkbox size="medium" />`,
116+
name: "Disabled",
117+
code: `<Checkbox disabled />`,
65118
},
66-
67119
]}
68-
/>
120+
/>

Diff for: apps/www/examples/shield-ts/assets.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import React, { useState, useCallback, useEffect } from "react";
22
import dayjs from "dayjs";
33
import { PlusIcon, BlendingModeIcon, HomeIcon, ChevronDownIcon } from "@radix-ui/react-icons";
44
import {
5-
Checkbox,
65
DataTable,
7-
Flex,
8-
Text,
96
Title,
107
useTable
118
} from "@raystack/apsara";
129

13-
import { toast, ToastContainer, Avatar, AvatarGroup, Button, Spinner, DropdownMenu, Breadcrumb } from "@raystack/apsara/v1";
10+
import { toast, ToastContainer, Avatar, AvatarGroup, Button, Spinner, DropdownMenu, Breadcrumb, Flex, Text, Checkbox} from "@raystack/apsara/v1";
1411
import { getData, Payment } from "./data";
1512
import { ApsaraColumnDef } from "@raystack/apsara/table/datatables.types";
1613
const TOTAL_PAGES = 100;
@@ -181,6 +178,12 @@ export const Assets = () => {
181178

182179
const AssetsHeader = () => {
183180
const { filteredColumns } = useTable();
181+
const [checked, setChecked] = useState<boolean | 'indeterminate'>('indeterminate');
182+
const handleCheckedChange = (newChecked: boolean | 'indeterminate') => {
183+
if (newChecked !== 'indeterminate') {
184+
setChecked(newChecked);
185+
}
186+
};
184187
const isFiltered = filteredColumns.length > 0;
185188
const items = [
186189
{ label: 'Home', href: '/', icon: <HomeIcon /> },
@@ -206,6 +209,15 @@ const AssetsHeader = () => {
206209
<Flex gap="extra-large" align="center">
207210
<Text style={{ fontWeight: 500 }}>Assets</Text>
208211
<Spinner size={3} />
212+
<div>
213+
<Checkbox
214+
checked={checked}
215+
onCheckedChange={(value) => {
216+
setChecked(value);
217+
console.log('New value:', value);
218+
}}
219+
/>
220+
</div>
209221
<Button variant="outline">Click here</Button>
210222
<Breadcrumb items={items} />
211223
<DropdownMenu>

Diff for: apps/www/utils/routes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const primitivesRoutes = [
3030
},
3131
{ title: "Calendar", slug: "docs/primitives/components/calendar" },
3232
{ title: "Command", slug: "docs/primitives/components/command" },
33-
{ title: "Checkbox", slug: "docs/primitives/components/checkbox" },
33+
{ title: "Checkbox", slug: "docs/primitives/components/checkbox", newBadge: true },
3434
{ title: "Container", slug: "docs/primitives/components/container" },
3535
{ title: "Datatable", slug: "docs/primitives/components/datatable" },
3636
{ title: "Dialog", slug: "docs/primitives/components/dialog" },

Diff for: packages/raystack/checkbox/checkbox.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export interface CheckboxProps
2323
VariantProps<typeof checkbox>,
2424
CheckboxPrimitive.CheckboxProps {}
2525

26+
/**
27+
* @deprecated Use Checkbox from '@raystack/apsara/v1' instead.
28+
*/
2629
export const Checkbox = forwardRef<
2730
ElementRef<typeof CheckboxPrimitive.Root>,
2831
CheckboxProps
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
.checkbox {
2+
all: unset;
3+
box-sizing: border-box;
4+
display: inline-flex;
5+
align-items: center;
6+
justify-content: center;
7+
width: var(--rs-space-5);
8+
height: var(--rs-space-5);
9+
min-width: var(--rs-space-5);
10+
min-height: var(--rs-space-5);
11+
border-radius: var(--rs-radius-1);
12+
background: var(--rs-color-background-base-primary);
13+
border: 1px solid var(--rs-color-border-base-secondary);
14+
cursor: pointer;
15+
flex-shrink: 0;
16+
}
17+
18+
.checkbox:hover {
19+
background: var(--rs-color-background-base-primary-hover);
20+
border-color: var(--rs-color-border-base-focus);
21+
}
22+
23+
.checkbox[data-state="checked"] {
24+
background: var(--rs-color-background-accent-emphasis);
25+
border: none;
26+
}
27+
28+
.checkbox[data-state="checked"]:hover {
29+
background: var(--rs-color-background-accent-emphasis-hover);
30+
}
31+
32+
/* Indeterminate state */
33+
.checkbox-indeterminate[data-state="checked"] {
34+
background: var(--rs-color-background-neutral-tertiary);
35+
border: none;
36+
}
37+
38+
.checkbox-indeterminate[data-state="checked"]:hover {
39+
background: var(--rs-color-background-neutral-tertiary);
40+
border: none;
41+
}
42+
43+
.checkbox-disabled {
44+
opacity: 0.5;
45+
cursor: not-allowed;
46+
}
47+
48+
.checkbox-disabled:hover {
49+
background: var(--rs-color-background-base-primary);
50+
border-color: var(--rs-color-border-base-primary);
51+
}
52+
53+
.indicator {
54+
display: flex;
55+
align-items: center;
56+
justify-content: center;
57+
width: 100%;
58+
height: 100%;
59+
color: var(--rs-color-text-accent-emphasis);
60+
}
61+
62+
.icon {
63+
width: var(--rs-space-5);
64+
height: var(--rs-space-5);
65+
}
66+
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2+
import clsx from 'clsx';
3+
import { cva, VariantProps } from "class-variance-authority";
4+
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
5+
import styles from "./checkbox.module.css";
6+
7+
8+
const CheckMarkIcon = () => (
9+
<svg
10+
xmlns="http://www.w3.org/2000/svg"
11+
width="16"
12+
height="16"
13+
viewBox="0 0 16 16"
14+
fill="none"
15+
className={styles.icon}
16+
>
17+
<path
18+
fillRule="evenodd"
19+
clipRule="evenodd"
20+
d="M11.9005 4.9671C12.0894 4.6782 12.0083 4.29086 11.7194 4.10197C11.4305 3.91307 11.0432 3.99414 10.8543 4.28304L7.15577 9.93961L5.04542 8.02112C4.79001 7.78893 4.39473 7.80775 4.16254 8.06316C3.93035 8.31857 3.94917 8.71385 4.20458 8.94605L6.85731 11.3576C6.99274 11.4807 7.17532 11.5383 7.35686 11.5151C7.53841 11.492 7.70068 11.3904 7.80084 11.2372L11.9005 4.9671Z"
21+
fill="currentColor"
22+
/>
23+
</svg>
24+
);
25+
26+
const IndeterminateIcon = () => (
27+
<svg
28+
xmlns="http://www.w3.org/2000/svg"
29+
width="16"
30+
height="16"
31+
viewBox="0 0 16 16"
32+
fill="none"
33+
className={styles.icon}
34+
>
35+
<path
36+
fillRule="evenodd"
37+
clipRule="evenodd"
38+
d="M11.5 8.5H4.5C4.22386 8.5 4 8.27614 4 8C4 7.72386 4.22386 7.5 4.5 7.5H11.5C11.7761 7.5 12 7.72386 12 8C12 8.27614 11.7761 8.5 11.5 8.5Z"
39+
fill="currentColor"
40+
/>
41+
</svg>
42+
);
43+
44+
const checkbox = cva(styles.checkbox);
45+
46+
type CheckboxVariants = VariantProps<typeof checkbox>;
47+
type CheckedState = boolean | 'indeterminate';
48+
49+
export interface CheckboxProps
50+
extends Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, keyof CheckboxVariants>,
51+
CheckboxVariants {
52+
checked?: CheckedState;
53+
defaultChecked?: CheckedState;
54+
onCheckedChange?: (checked: CheckedState) => void;
55+
}
56+
57+
export const Checkbox = forwardRef<
58+
ElementRef<typeof CheckboxPrimitive.Root>,
59+
CheckboxProps
60+
>(({ className, disabled, checked, defaultChecked, onCheckedChange, ...props }, forwardedRef) => {
61+
const isIndeterminate = checked === 'indeterminate' || defaultChecked === 'indeterminate';
62+
63+
return (
64+
<CheckboxPrimitive.Root
65+
className={checkbox({
66+
className: clsx(className, {
67+
[styles["checkbox-disabled"]]: disabled,
68+
[styles["checkbox-indeterminate"]]: isIndeterminate
69+
})
70+
})}
71+
checked={isIndeterminate || (checked === true)}
72+
defaultChecked={defaultChecked === true}
73+
onCheckedChange={(value) => {
74+
if (onCheckedChange) {
75+
// If it's currently indeterminate, next state will be unchecked
76+
if (checked === 'indeterminate') {
77+
onCheckedChange(false);
78+
} else {
79+
onCheckedChange(value);
80+
}
81+
}
82+
}}
83+
disabled={disabled}
84+
ref={forwardedRef}
85+
{...props}
86+
>
87+
<CheckboxPrimitive.Indicator className={styles.indicator}>
88+
{isIndeterminate ? <IndeterminateIcon /> : <CheckMarkIcon />}
89+
</CheckboxPrimitive.Indicator>
90+
</CheckboxPrimitive.Root>
91+
);
92+
});
93+
94+
Checkbox.displayName = "Checkbox";

Diff for: packages/raystack/v1/components/checkbox/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Checkbox } from "./checkbox";

Diff for: packages/raystack/v1/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export { DropdownMenu } from "./components/dropdownMenu";
99
export { Text } from "./components/text";
1010
export { Flex } from "./components/flex";
1111
export { EmptyState } from "./components/emptystate";
12+
export { Checkbox } from "./components/checkbox";
1213

1314
export {
1415
ThemeProvider,

0 commit comments

Comments
 (0)