Skip to content

Commit

Permalink
Feat/checkbox v1 (#182)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
paanSinghCoder authored Nov 21, 2024
1 parent d5318cb commit 51485a6
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 29 deletions.
100 changes: 76 additions & 24 deletions apps/www/content/primitives/components/checkbox.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Checkbox
description: Checkbox is a user interface control that enables users to toggle between a checked and unchecked state
description: Checkbox is a user interface control that enables users to toggle between checked, unchecked, and indeterminate states
radix:
link: https://www.radix-ui.com/docs/primitives/components/checkbox
api: https://www.radix-ui.com/docs/primitives/components/checkbox#api-reference
Expand All @@ -10,14 +10,14 @@ radix:

<Preview>
<Flex
style={{
flexDirection: "column",
alignItems: "center",
gap: "8px",
}}
direction="row"
align="center"
gap="extra-large"
>
<Checkbox />
<Checkbox size="medium" />
<Checkbox />
<Checkbox checked={true} />
<Checkbox checked="indeterminate" />
<Checkbox disabled />
</Flex>
</Preview>

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

## Usage

### Controlled Component
You can control the checkbox state using the `checked` and `onCheckedChange` props:

<LiveProvider>
<LiveEditor code={`
const [checked, setChecked] = useState(false);
<Checkbox
checked={checked}
onCheckedChange={(value) => setChecked(value)}
/>
`} border />
</LiveProvider>

### Indeterminate State
The indeterminate state is useful for representing partial selection, commonly used in parent checkboxes:

<LiveProvider>
<LiveEditor code={`
const [checked, setChecked] = useState<boolean | 'indeterminate'>('indeterminate');
<Checkbox
checked={checked}
onCheckedChange={(value) => setChecked(value)}
/>
`} border />
</LiveProvider>

## Props

The `Checkbox` component accepts the following props:

- `checked`: The controlled state of the checkbox (`boolean | "indeterminate"`)
- `defaultChecked`: The default state when initially rendered (`boolean | "indeterminate"`)
- `onCheckedChange`: Event handler called when the state changes (`(checked: boolean | "indeterminate") => void`)
- `disabled`: When true, prevents the user from interacting with the checkbox (`boolean`)

## Anatomy
Import all parts and piece them together.

<LiveProvider>
<LiveEditor code={`
import { Checkbox } from '@raystack/apsara'
import { Checkbox } from '@raystack/apsara/v1'
// Basic usage
<Checkbox />
<Checkbox size="medium" />
`} border />
</LiveProvider>
// Checked state
<Checkbox checked={true} />
## Scale
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:
// Indeterminate state
<Checkbox checked="indeterminate" />
- Small
- Medium
// Disabled state
<Checkbox disabled />
`} border />
</LiveProvider>

## States
The Checkbox component supports multiple states to represent different selection conditions:

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.
- Unchecked: Default state
- Checked: Selected state
- Indeterminate: Partial selection state
- Disabled: Disabled state

<Playground
scope={{ Checkbox }}
tabs={[

{
name: "Small",
code: `<Checkbox size="small" />`,
name: "Unchecked",
code: `<Checkbox />`,
},
{
name: "Checked",
code: `<Checkbox checked={true} />`,
},
{
name: "Indeterminate",
code: `<Checkbox checked="indeterminate" />`,
},
{
name: "Medium",
code: `<Checkbox size="medium" />`,
name: "Disabled",
code: `<Checkbox disabled />`,
},

]}
/>
/>
20 changes: 16 additions & 4 deletions apps/www/examples/shield-ts/assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import React, { useState, useCallback, useEffect } from "react";
import dayjs from "dayjs";
import { PlusIcon, BlendingModeIcon, HomeIcon, ChevronDownIcon } from "@radix-ui/react-icons";
import {
Checkbox,
DataTable,
Flex,
Text,
Title,
useTable
} from "@raystack/apsara";

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

const AssetsHeader = () => {
const { filteredColumns } = useTable();
const [checked, setChecked] = useState<boolean | 'indeterminate'>('indeterminate');
const handleCheckedChange = (newChecked: boolean | 'indeterminate') => {
if (newChecked !== 'indeterminate') {
setChecked(newChecked);
}
};
const isFiltered = filteredColumns.length > 0;
const items = [
{ label: 'Home', href: '/', icon: <HomeIcon /> },
Expand All @@ -206,6 +209,15 @@ const AssetsHeader = () => {
<Flex gap="extra-large" align="center">
<Text style={{ fontWeight: 500 }}>Assets</Text>
<Spinner size={3} />
<div>
<Checkbox
checked={checked}
onCheckedChange={(value) => {
setChecked(value);
console.log('New value:', value);
}}
/>
</div>
<Button variant="outline">Click here</Button>
<Breadcrumb items={items} />
<DropdownMenu>
Expand Down
2 changes: 1 addition & 1 deletion apps/www/utils/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const primitivesRoutes = [
},
{ title: "Calendar", slug: "docs/primitives/components/calendar" },
{ title: "Command", slug: "docs/primitives/components/command" },
{ title: "Checkbox", slug: "docs/primitives/components/checkbox" },
{ title: "Checkbox", slug: "docs/primitives/components/checkbox", newBadge: true },
{ title: "Container", slug: "docs/primitives/components/container" },
{ title: "Datatable", slug: "docs/primitives/components/datatable" },
{ title: "Dialog", slug: "docs/primitives/components/dialog" },
Expand Down
3 changes: 3 additions & 0 deletions packages/raystack/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export interface CheckboxProps
VariantProps<typeof checkbox>,
CheckboxPrimitive.CheckboxProps {}

/**
* @deprecated Use Checkbox from '@raystack/apsara/v1' instead.
*/
export const Checkbox = forwardRef<
ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
Expand Down
66 changes: 66 additions & 0 deletions packages/raystack/v1/components/checkbox/checkbox.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
.checkbox {
all: unset;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--rs-space-5);
height: var(--rs-space-5);
min-width: var(--rs-space-5);
min-height: var(--rs-space-5);
border-radius: var(--rs-radius-1);
background: var(--rs-color-background-base-primary);
border: 1px solid var(--rs-color-border-base-secondary);
cursor: pointer;
flex-shrink: 0;
}

.checkbox:hover {
background: var(--rs-color-background-base-primary-hover);
border-color: var(--rs-color-border-base-focus);
}

.checkbox[data-state="checked"] {
background: var(--rs-color-background-accent-emphasis);
border: none;
}

.checkbox[data-state="checked"]:hover {
background: var(--rs-color-background-accent-emphasis-hover);
}

/* Indeterminate state */
.checkbox-indeterminate[data-state="checked"] {
background: var(--rs-color-background-neutral-tertiary);
border: none;
}

.checkbox-indeterminate[data-state="checked"]:hover {
background: var(--rs-color-background-neutral-tertiary);
border: none;
}

.checkbox-disabled {
opacity: 0.5;
cursor: not-allowed;
}

.checkbox-disabled:hover {
background: var(--rs-color-background-base-primary);
border-color: var(--rs-color-border-base-primary);
}

.indicator {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: var(--rs-color-text-accent-emphasis);
}

.icon {
width: var(--rs-space-5);
height: var(--rs-space-5);
}

94 changes: 94 additions & 0 deletions packages/raystack/v1/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import clsx from 'clsx';
import { cva, VariantProps } from "class-variance-authority";
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
import styles from "./checkbox.module.css";


const CheckMarkIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
className={styles.icon}
>
<path
fillRule="evenodd"
clipRule="evenodd"
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"
fill="currentColor"
/>
</svg>
);

const IndeterminateIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
className={styles.icon}
>
<path
fillRule="evenodd"
clipRule="evenodd"
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"
fill="currentColor"
/>
</svg>
);

const checkbox = cva(styles.checkbox);

type CheckboxVariants = VariantProps<typeof checkbox>;
type CheckedState = boolean | 'indeterminate';

export interface CheckboxProps
extends Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, keyof CheckboxVariants>,
CheckboxVariants {
checked?: CheckedState;
defaultChecked?: CheckedState;
onCheckedChange?: (checked: CheckedState) => void;
}

export const Checkbox = forwardRef<
ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
>(({ className, disabled, checked, defaultChecked, onCheckedChange, ...props }, forwardedRef) => {
const isIndeterminate = checked === 'indeterminate' || defaultChecked === 'indeterminate';

return (
<CheckboxPrimitive.Root
className={checkbox({
className: clsx(className, {
[styles["checkbox-disabled"]]: disabled,
[styles["checkbox-indeterminate"]]: isIndeterminate
})
})}
checked={isIndeterminate || (checked === true)}
defaultChecked={defaultChecked === true}
onCheckedChange={(value) => {
if (onCheckedChange) {
// If it's currently indeterminate, next state will be unchecked
if (checked === 'indeterminate') {
onCheckedChange(false);
} else {
onCheckedChange(value);
}
}
}}
disabled={disabled}
ref={forwardedRef}
{...props}
>
<CheckboxPrimitive.Indicator className={styles.indicator}>
{isIndeterminate ? <IndeterminateIcon /> : <CheckMarkIcon />}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
});

Checkbox.displayName = "Checkbox";
1 change: 1 addition & 0 deletions packages/raystack/v1/components/checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Checkbox } from "./checkbox";
1 change: 1 addition & 0 deletions packages/raystack/v1/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { DropdownMenu } from "./components/dropdownMenu";
export { Text } from "./components/text";
export { Flex } from "./components/flex";
export { EmptyState } from "./components/emptystate";
export { Checkbox } from "./components/checkbox";

export {
ThemeProvider,
Expand Down

0 comments on commit 51485a6

Please sign in to comment.