Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CheckableCard component #1443

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions documentation/specs/CheckableCard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# `CheckableCard` Component Specification

## Overview

A `CheckableCard` is a styled container with a `Checkbox` form element.

### Use Cases

- Use a CheckableCard to display a checkbox for users inside a styled container

### Features

- Supports use of `Checkbox` form element within a `Card` container
- Supports existing features of `Card` component
- Supports existing features of `Checkbox` component

### Prior Art

### Design

Design of `CheckableCard` is comprised of the `Checkbox` component wrapped by the `Card` container component.

### API

```ts
export type CheckableCardProps = CheckboxProps & {
/**
* Card children
*/
children: ReactNode;
};
```

### Example Usage

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return <CheckableCard>Checkbox item</CheckableCard>;
}
```

_Default value:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return <CheckableCard defaultSelected={true}>Checkbox item</CheckableCard>;
}
```

_Controlled:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
const [isSelected, setIsSelected] = useState(false);
return (
<CheckableCard
isSelected={isSelected}
onChange={(isSelected) => setIsSelected(isSelected)}
>
Checkbox item
</CheckableCard>
);
}
```

_Disabled:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return <CheckableCard isDisabled={true}>Checkbox item</CheckableCard>;
}
```

_Read-only:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return (
<CheckableCard isSelected={true} isReadOnly={true}>
Checkbox item
</CheckableCard>
);
}
```

_Form submission data:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return (
<CheckableCard name="checkable-card-name" value="checkable-card-value">
Checkbox item
</CheckableCard>
);
}
```

_Error:_

```tsx
import { CheckableCard } from "@easypost/easy-ui/CheckableCard";

function Component() {
return (
<CheckableCard
validationState="invalid"
errorText="This is required to proceed"
>
Checkbox item
</CheckableCard>
);
}
```

### Anatomy

The `CheckableCard` component will render a `Card` component that wraps a `Checkbox` component.

```tsx
function CheckableCard() {
return (
<Card>
<Checkbox
isSelected={isSelected}
onChange={(isSelected) => setIsSelected(isSelected)}
>
Checkbox item
</Checkbox>
</Card>
);
}
```

---

## Behavior

### Accessibility

### Dependencies

- React Aria's `useCheckbox`, `useFocusRing`, `VisuallyHidden`, `useToggleState`, and `ValidationState`
10 changes: 10 additions & 0 deletions easy-ui-react/src/CheckableCard/CheckableCard.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import { ArgTypes, Canvas, Meta, Controls } from "@storybook/blocks";
import { CheckableCard } from "./CheckableCard";
import * as CheckableCardStories from "./CheckableCard.stories";

<Meta of={CheckableCardStories} />

# CheckableCard

<Canvas of={CheckableCardStories.Default} />
21 changes: 21 additions & 0 deletions easy-ui-react/src/CheckableCard/CheckableCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { CheckableCardProps, CheckableCard } from "./CheckableCard";

type Story = StoryObj<typeof CheckableCard>;

const Template = (args: CheckableCardProps) => <CheckableCard {...args} />;

const meta: Meta<typeof CheckableCard> = {
title: "Components/CheckableCard",
component: CheckableCard,
};

export default meta;

export const Default: Story = {
render: Template.bind({}),
args: {
children: "Content",
},
};
36 changes: 36 additions & 0 deletions easy-ui-react/src/CheckableCard/CheckableCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { ReactNode, forwardRef } from "react";
import { Card } from "../Card";
import { Text } from "../Text";
import { Checkbox } from "../Checkbox";
import { HorizontalStack } from "../HorizontalStack";
import { VerticalStack } from "../VerticalStack";

export type CheckableCardProps = {
children?: ReactNode;
};

export const CheckableCard = forwardRef<HTMLInputElement, CheckableCardProps>(
(props) => {
const { children } = props;
return (
<Card>
<VerticalStack gap="2">
<HorizontalStack gap="1.5" blockAlign="center">
<Checkbox size="lg">
<VerticalStack gap="1">
<Text weight="semibold" as="h3">
Header
</Text>
<Text weight="medium" color="gray.resting">
{children}
</Text>
</VerticalStack>
</Checkbox>
</HorizontalStack>
</VerticalStack>
</Card>
);
},
);

CheckableCard.displayName = "CheckableCard";
1 change: 1 addition & 0 deletions easy-ui-react/src/CheckableCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./CheckableCard";
Loading