Skip to content

Commit

Permalink
feat: add radio buttons (Shopify#58)
Browse files Browse the repository at this point in the history
## Context

This pull request introduces a new feature to enhance the user
experience by adding lean and accessible radio buttons.

## Changes

In this update, a new package `@code-internet-applications/radio` has
been introduced. This package includes all the necessary code to
implement and handle the radio buttons effectively.

## Screenshot


![radio-variant](https://github.com/code-internet-applications/cbt-hydrogen/assets/2558163/d869403a-1d57-4140-97b2-764c0afb39ea)

## Paperwork

[SCH-34](https://codeinternetapplications.atlassian.net/browse/SCH-34)

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I've performed a self-review of my own code
- [x] I've added a changeset if this PR contains user-facing or
noteworthy changes
- [x] I've commented my code, particularly in hard-to-understand areas
- [x] I've made corresponding changes to the documentation
- [x] I've tested my code for breaking changes and added the
corresponding
      footer in this PR if needed


[SCH-34]:
https://codeinternetapplications.atlassian.net/browse/SCH-34?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
remcolakens committed Jun 26, 2023
1 parent c33327a commit 1368a2a
Show file tree
Hide file tree
Showing 23 changed files with 487 additions and 13 deletions.
9 changes: 9 additions & 0 deletions .changeset/twenty-boxes-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@code-internet-applications/select': minor
'@code-internet-applications/badge': minor
'@code-internet-applications/label': minor
'@code-internet-applications/radio': minor
'@code-internet-applications/react': minor
---

SCH-34: Added new radio buttons to match with the CBT design
49 changes: 46 additions & 3 deletions packages/components/badge/src/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { cn } from '@code-internet-applications/tailwind-utils';
import { IBadgeProps } from './types';

const badgeVariants = cva(
'inline-flex items-center justify-center rounded-5xl text-xs leading-5 [&>svg]:h-4 [&>svg]:w-4',
'inline-flex items-center justify-center rounded-5xl text-xs leading-5 transition-colors [&>svg]:h-4 [&>svg]:w-4',
{
variants: {
variant: {
Expand All @@ -19,8 +19,40 @@ const badgeVariants = cva(
icon: {
true: true,
},
withHover: {
true: true,
},
isActive: {
true: true,
},
},
compoundVariants: [
//
// hover & active variants
//
{
variant: 'primary',
withHover: true,
class: 'hover:bg-gray-600',
},
{
variant: 'primary',
isActive: true,
class: 'bg-gray-600',
},
{
variant: 'outline',
withHover: true,
class: 'hover:bg-black hover:text-white',
},
{
variant: 'outline',
isActive: true,
class: 'bg-black text-white',
},
//
// icon variants
//
{
variant: ['primary', 'outline'],
size: 'sm',
Expand All @@ -47,10 +79,21 @@ const badgeVariants = cva(
},
);

function Badge({ icon, size, className, variant, ...props }: IBadgeProps) {
function Badge({
withHover,
isActive,
icon,
size,
className,
variant,
...props
}: IBadgeProps) {
return (
<div
className={cn(badgeVariants({ icon, size, variant }), className)}
className={cn(
badgeVariants({ icon, size, variant, withHover, isActive }),
className,
)}
{...props}
/>
);
Expand Down
5 changes: 4 additions & 1 deletion packages/components/label/src/label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ const Label = forwardRef<LabelRef, LabelProps>(
({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn('text-base font-semibold leading-7', className)}
className={cn(
'cursor-pointer text-base font-semibold leading-7',
className,
)}
{...props}
/>
),
Expand Down
31 changes: 31 additions & 0 deletions packages/components/radio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# @code-internet-applications/radio

Radios are used when only one choice may be selected in a series of options.

## Installation

```
pnpm add @code-internet-applications/radio
```

## Contribution

See the
[contributing guidelines](https://github.com/code-internet-applications/cbt-hydrogen/blob/main/CONTRIBUTING.md)
for more details.

## Troubleshooting

To ensure that you can access the packages from the GitHub package registry,
make sure to add the following line to your `.npmrc` file in the root directory
of your project:

```
@code-internet-applications:registry=https://npm.pkg.github.com
public-hoist-pattern[]=@code-internet-applications/*
auto-install-peers=true
strict-peer-dependencies=false
enable-pre-post-scripts=true
node-linker=hoisted
```
43 changes: 43 additions & 0 deletions packages/components/radio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@code-internet-applications/radio",
"version": "0.0.0",
"description": "Radios are used when only one choice may be selected in a series of options.",
"author": "Code Internet Applications <info@code.nl>",
"repository": {
"type": "git",
"url": "https://github.com/code-internet-applications/cbt-hydrogen",
"directory": "components/radio"
},
"main": "src/index.ts",
"sideEffects": false,
"files": [
"dist"
],
"scripts": {
"build": "tsup src/index.ts --dts",
"dev": "tsup src/index.ts --watch",
"clean": "rimraf .turbo node_modules dist",
"typecheck": "tsc --noEmit",
"lint": "eslint \"src/**/*.ts*\""
},
"tsup": {
"clean": true,
"target": "es2019",
"format": [
"cjs",
"esm"
]
},
"dependencies": {
"@code-internet-applications/tailwind-utils": "workspace:*",
"@radix-ui/react-radio-group": "^1.1.3"
},
"peerDependencies": {
"class-variance-authority": "^0.6.0",
"react": "^18.2.0",
"tailwindcss": "^3.3.2"
},
"engines": {
"node": ">=v18.16.0"
}
}
3 changes: 3 additions & 0 deletions packages/components/radio/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './radio-group';
export * from './radio-group-item';
export type * from './types';
55 changes: 55 additions & 0 deletions packages/components/radio/src/radio-group-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';

import { cn } from '@code-internet-applications/tailwind-utils';
import { cva } from 'class-variance-authority';
import { forwardRef } from 'react';
import { Icon } from '../../icon/src';
import { RadioItemProps, RadioItemRef } from './types';

const radioVariants = cva(
'aspect-square h-4 w-4 rounded-full border focus:outline-none disabled:cursor-not-allowed disabled:opacity-5',
{
variants: {
size: {
md: 'h-4 w-4 p-0.5',
lg: 'h-10 w-10 p-1.5',
},
variant: {
hidden: 'hidden',
},
},
defaultVariants: {
size: 'md',
},
},
);

const RadioGroupItem = forwardRef<RadioItemRef, RadioItemProps>(
({ size, variant, className, ...props }, ref) => {
if (variant === 'hidden') {
return (
<RadioGroupPrimitive.Item ref={ref} {...props}>
<RadioGroupPrimitive.Indicator></RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}

return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(radioVariants({ size }), className)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Icon className="h-full w-full fill-current text-current">
<circle cx="12" cy="12" r="12" />
</Icon>
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
},
);

RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;

export { RadioGroupItem, radioVariants };
21 changes: 21 additions & 0 deletions packages/components/radio/src/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';

import { cn } from '@code-internet-applications/tailwind-utils';
import { forwardRef } from 'react';
import { RadioGroupProps, RadioGroupRef } from './types';

const RadioGroup = forwardRef<RadioGroupRef, RadioGroupProps>(
({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn('grid gap-2', className)}
{...props}
ref={ref}
/>
);
},
);

RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;

export { RadioGroup };
14 changes: 14 additions & 0 deletions packages/components/radio/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { VariantProps } from 'class-variance-authority';
import { ComponentPropsWithoutRef, ElementRef } from 'react';
import { radioVariants } from './radio-group-item';

export type RadioGroupRef = ElementRef<typeof RadioGroupPrimitive.Root>;
export type RadioGroupProps = ComponentPropsWithoutRef<
typeof RadioGroupPrimitive.Root
>;

export type RadioItemRef = ElementRef<typeof RadioGroupPrimitive.Item>;
export interface RadioItemProps
extends ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>,
VariantProps<typeof radioVariants> {}
25 changes: 25 additions & 0 deletions packages/components/radio/stories/radio.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Canvas, Meta, Story } from '@storybook/blocks';

import * as RadioStories from './radio.stories';

<Meta title="Components/Radio" name="Docs" />

# Radio

Radios are used when only one choice may be selected in a series of options.

<Canvas>
<Story of={RadioStories.regular} />
</Canvas>

### With Labels

<Canvas>
<Story of={RadioStories.withLabels} />
</Canvas>

### With color

<Canvas>
<Story of={RadioStories.withColor} />
</Canvas>
101 changes: 101 additions & 0 deletions packages/components/radio/stories/radio.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';
import { Badge } from '../../badge/src';
import { Label } from '../../label/src';
import { RadioGroup, RadioGroupItem } from '../src';

const meta: Meta<typeof RadioGroupItem> = {
title: 'Components/Radio',
};

export default meta;
type Story = StoryObj<typeof RadioGroupItem>;

const options = [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' },
{ value: 'extra-large', label: 'Extra Large' },
];

export const regular: Story = {
render: () => (
<RadioGroup defaultValue="medium">
{options.map((option) => (
<div
className="flex items-center space-x-2"
key={`regular_${option.value}`}
>
<RadioGroupItem value={option.value} id={`regular_${option.value}`} />
<Label htmlFor={`regular_${option.value}`}>{option.label}</Label>
</div>
))}
</RadioGroup>
),
};

const RadioGroupBadge = () => {
const [value, setValue] = useState<string>('small');

return (
<RadioGroup className="flex">
{options.map((option) => (
<div
className="flex items-center space-x-2"
key={`badge_${option.value}`}
>
<RadioGroupItem
onClick={() => setValue(option.value)}
value={option.value}
id={`badge_${option.value}`}
variant="hidden"
/>
<Label htmlFor={`badge_${option.value}`}>
<Badge
withHover
isActive={value === option.value}
variant="outline"
>
{option.value}
</Badge>
</Label>
</div>
))}
</RadioGroup>
);
};

export const withLabels: Story = {
render: () => <RadioGroupBadge />,
};

const radioWithColorOptions = [
{ value: 'black', label: 'Black', color: 'text-black' },
{ value: 'primary', label: 'Primary', color: 'text-primary-800' },
{ value: 'secondary', label: 'Secondary', color: 'text-secondary-800' },
{ value: 'tertiary', label: 'Tertiary', color: 'text-tertiary-800' },
{ value: 'funnel', label: 'Funnel', color: 'text-funnel-700' },
];

const RadioGroupColor = () => {
return (
<RadioGroup defaultValue="funnel" className="flex">
{radioWithColorOptions.map((option) => (
<div
className={`flex items-center space-x-2 border-gray-200 ${option.color}`}
key={`color_${option.value}`}
>
<RadioGroupItem
value={option.value}
id={`color_${option.value}`}
size="lg"
/>
</div>
))}
</RadioGroup>
);
};

export const withColor: Story = {
render: () => <RadioGroupColor />,
};

0 comments on commit 1368a2a

Please sign in to comment.