feat: add shadcn checkbox component (#17248)

contributes to coder/preview#55

Add shadcn checkbox component matching Figma styles from Coder Kit:
https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=489-4187&t=Zx137ETWsQZtaCku-1

<img width="52" alt="Screenshot 2025-04-03 at 21 15 52"
src="https://github.com/user-attachments/assets/ff2de95c-cb12-46ed-af31-a6d230e52a31"
/>
This commit is contained in:
Jaayden Halko
2025-04-04 15:05:21 +01:00
committed by GitHub
parent ae67e33c66
commit 8a24372e4d
6 changed files with 168 additions and 0 deletions

View File

@ -51,6 +51,7 @@
"@mui/utils": "5.16.14",
"@mui/x-tree-view": "7.25.0",
"@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.4",
"@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",

32
site/pnpm-lock.yaml generated
View File

@ -67,6 +67,9 @@ importers:
'@radix-ui/react-avatar':
specifier: 1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-checkbox':
specifier: 1.1.4
version: 1.1.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collapsible':
specifier: 1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -1482,6 +1485,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-checkbox@1.1.4':
resolution: {integrity: sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==, tarball: https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-collapsible@1.1.2':
resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==, tarball: https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz}
peerDependencies:
@ -7509,6 +7525,22 @@ snapshots:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-checkbox@1.1.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-collapsible@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1

View File

@ -0,0 +1,89 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { Checkbox } from "./Checkbox";
const meta: Meta<typeof Checkbox> = {
title: "components/Checkbox",
component: Checkbox,
args: {},
argTypes: {
checked: {
control: "boolean",
description: "The controlled checked state of the checkbox",
},
defaultChecked: {
control: "boolean",
description: "The default checked state when initially rendered",
},
disabled: {
control: "boolean",
description:
"When true, prevents the user from interacting with the checkbox",
},
},
};
export default meta;
type Story = StoryObj<typeof Checkbox>;
export const Unchecked: Story = {};
export const Checked: Story = {
args: {
defaultChecked: true,
checked: true,
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
export const DisabledChecked: Story = {
args: {
disabled: true,
defaultChecked: true,
checked: true,
},
};
export const CustomStyling: Story = {
args: {
className: "h-6 w-6 rounded-full",
},
};
export const WithLabel: Story = {
render: () => (
<div className="flex gap-3">
<Checkbox id="terms" />
<div className="grid">
<label
htmlFor="terms"
className="text-sm font-medium peer-disabled:cursor-not-allowed peer-disabled:text-content-disabled"
>
Accept terms and conditions
</label>
<p className="text-sm text-content-secondary mt-1">
You agree to our Terms of Service and Privacy Policy.
</p>
</div>
</div>
),
};
export const Indeterminate: Story = {
render: () => {
const [checked, setChecked] = React.useState<boolean | "indeterminate">(
"indeterminate",
);
return (
<Checkbox
checked={checked}
onCheckedChange={(value) => setChecked(value)}
/>
);
},
};

View File

@ -0,0 +1,42 @@
/**
* Copied from shadc/ui on 04/03/2025
* @see {@link https://ui.shadcn.com/docs/components/checkbox}
*/
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check, Minus } from "lucide-react";
import * as React from "react";
import { cn } from "utils/cn";
export const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
`peer h-6 w-6 shrink-0 rounded-sm border border-border border-solid
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-content-link focus-visible:ring-offset-4 focus-visible:ring-offset-surface-primary
disabled:cursor-not-allowed disabled:bg-surface-primary disabled:data-[state=checked]:bg-surface-tertiary
data-[state=unchecked]:bg-surface-primary
data-[state=checked]:bg-surface-invert-primary data-[state=checked]:text-content-invert
hover:border-border-hover hover:data-[state=checked]:bg-surface-invert-secondary`,
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current relative")}
>
<div className="flex">
{(props.checked === true || props.defaultChecked === true) && (
<Check className="w-5 h-5" strokeWidth={2.5} />
)}
{props.checked === "indeterminate" && (
<Minus className="w-5 h-5" strokeWidth={2.5} />
)}
</div>
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));

View File

@ -31,6 +31,7 @@
--border-default: 240 6% 90%;
--border-success: 142 76% 36%;
--border-destructive: 0 84% 60%;
--border-hover: 240, 5%, 34%;
--overlay-default: 240 5% 84% / 80%;
--radius: 0.5rem;
--highlight-purple: 262 83% 58%;
@ -67,6 +68,7 @@
--border-default: 240 4% 16%;
--border-success: 142 76% 36%;
--border-destructive: 0 91% 71%;
--border-hover: 240, 5%, 34%;
--overlay-default: 240 10% 4% / 80%;
--highlight-purple: 252 95% 85%;
--highlight-green: 141 79% 85%;

View File

@ -53,6 +53,8 @@ module.exports = {
border: {
DEFAULT: "hsl(var(--border-default))",
destructive: "hsl(var(--border-destructive))",
success: "hsl(var(--border-success))",
hover: "hsl(var(--border-hover))",
},
overlay: "hsla(var(--overlay-default))",
input: "hsl(var(--input))",