Skip to content

Commit

Permalink
KDS-1752 Radio, RadioGroup, RadioField (#4101)
Browse files Browse the repository at this point in the history
* add Radio

* Moved radio

* add Label + RadioField

* add RadioGroup

* add Search Field

* remove search field

* remove search field

* RadioField tests

* a working radio example

* remove old Label

* fix up docs + useId

* add changeset

* ignore label a11y violation

* remove useless tests

* fix stickersheet alringment
  • Loading branch information
gyfchong authored Sep 28, 2023
1 parent d252d72 commit 8f6cbc3
Show file tree
Hide file tree
Showing 21 changed files with 960 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .changeset/swift-olives-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
93 changes: 93 additions & 0 deletions packages/components/src/Radio/Radio/Radio.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
@import "~@kaizen/design-tokens/sass/border";
@import "~@kaizen/design-tokens/sass/color";
@import "../../../styles/forms";
@import "../../../styles/form-variables";

$radio-size: 24px;
$icon-size: 10px;
$icon-offset: 5px;
$focus-ring-offset: 2px;

$dt-color-radio-background-color-hover: $color-gray-200;
$dt-color-radio-disc-color-base: $color-gray-600;
$dt-color-radio-border-color-focus: $color-blue-500;
$dt-color-radio-border-color-focus-reversed: $color-blue-300;

.radioInput {
@include form-input-visually-hidden;
}

.icon {
top: $icon-offset;
left: $icon-offset;
width: $icon-size;
height: $icon-size;
border-radius: 50%;
background: $dt-color-radio-disc-color-base;
position: absolute;

&.reversed {
background: $color-white;
}
}

.box {
display: block;
position: relative;
background: $color-white;
height: $radio-size;
width: $radio-size;
top: 0;
border: $border-solid-border-width $border-solid-border-style
$dt-color-form-border-color;
box-sizing: border-box;
border-radius: 50%;

.radioInput:focus:not([disabled]) + & {
border-color: $dt-color-radio-disc-color-base;
}

.radioInput:focus:not([disabled]) + &::after {
pointer-events: none;
content: "";
box-sizing: border-box;
position: absolute;
background: transparent;
border-radius: $radio-size + $focus-ring-offset;
border-width: $border-focus-ring-border-width;
border-style: $border-focus-ring-border-style;
border-color: $dt-color-radio-border-color-focus;
top: -$focus-ring-offset - ($radio-size / 8);
left: -$focus-ring-offset - ($radio-size / 8);
width: calc(
#{$radio-size} + #{$focus-ring-offset} + #{$border-solid-border-width} * 2
);
height: calc(
#{$radio-size} + #{$focus-ring-offset} + #{$border-solid-border-width} * 2
);
}

.radioInput:not([disabled]) + &:hover {
border-color: $dt-color-form-border-color-hover;
background-color: $dt-color-radio-background-color-hover;
}

&.reversed {
border: $border-solid-border-width $border-solid-border-style
rgba($color-white-rgb, 0.65);
background: transparent;

.radioInput:focus:not([disabled]) + & {
border-color: $color-white;
}

.radioInput:focus:not([disabled]) + &::after {
border-color: $dt-color-radio-border-color-focus-reversed;
}

.radioInput:not([disabled]) + &:hover {
border-color: $color-white;
background-color: transparent;
}
}
}
62 changes: 62 additions & 0 deletions packages/components/src/Radio/Radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { InputHTMLAttributes } from "react"
import classnames from "classnames"
import { OverrideClassName } from "~types/OverrideClassName"
import styles from "./Radio.module.scss"

export type RadioProps = OverrideClassName<
Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "checked">
> & {
/**
* This needs to be enforced to ensure the Radio always gets an id to match a label when composed.
*/
id: string
name: string
value: string
selectedStatus?: boolean
reversed?: boolean
}

const renderSelected = (
selectedStatus: boolean,
reversed: boolean
): React.ReactNode => {
if (selectedStatus) {
return (
<div className={classnames(styles.icon, reversed && styles.reversed)} />
)
}
return
}

export const Radio = ({
name,
value,
selectedStatus = false,
reversed = false,
onChange,
classNameOverride,
...restProps
}: RadioProps): JSX.Element => (
// @todo: Move classNameOverride to span
<span>
<input
type="radio"
name={name}
value={value}
checked={selectedStatus}
className={classnames(
styles.radioInput,
classNameOverride,
reversed && styles.reversed
)}
onChange={onChange}
readOnly={onChange === undefined}
{...restProps}
/>
<span className={classnames(styles.box, reversed && styles.reversed)}>
{renderSelected(selectedStatus, reversed)}
</span>
</span>
)

Radio.displayName = "Radio"
30 changes: 30 additions & 0 deletions packages/components/src/Radio/Radio/_docs/Radio.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Canvas, Controls, Meta } from "@storybook/blocks"
import { ResourceLinks, KaioNotification, Installation } from "~storybook/components"
import { LinkTo } from "~storybook/components/LinkTo"
import * as RadioStories from "./Radio.stories"

<Meta of={RadioStories} />

# Radio

<ResourceLinks
sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/Radio"
className="!mb-8"
/>

<KaioNotification />

<Installation
installCommand="yarn add @kaizen/components"
importStatement='import { Radio } from "@kaizen/components"'
/>

## Overview

A primitive, not to be used alone.


Commonly used via <LinkTo pageId="components-radio-controls-radiofield">RadioField</LinkTo> or <LinkTo pageId="components-radio-controls-radiogroup">RadioGroup</LinkTo>.

<Canvas of={RadioStories.Playground} />
<Controls of={RadioStories.Playground} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from "react"
import { Meta } from "@storybook/react"
import {
StickerSheet,
StickerSheetStory,
} from "~storybook/components/StickerSheet"
import { Radio } from "../index"

export default {
title: "KAIO-staging/Radio controls/Radio",
parameters: {
chromatic: { disable: false },
controls: { disable: true },
a11y: {
config: {
rules: [
{
// Built with no label on purpose, to be used within `RadioField` where label is present
id: "label",
enabled: false,
},
],
},
},
},
} satisfies Meta

const StickerSheetTemplate: StickerSheetStory = {
render: ({ isReversed }) => (
<StickerSheet isReversed={isReversed}>
<StickerSheet.Header
headings={["Default", "Focus", "Hover"]}
hasVerticalHeadings
/>
<StickerSheet.Body>
<StickerSheet.Row rowTitle="Off">
<Radio
id="radio1"
name="radio1"
value="radio1"
reversed={isReversed}
/>
<Radio
id="radio2"
name="radio2"
value="radio2"
reversed={isReversed}
data-sb-pseudo-styles="focus"
/>
<Radio
id="radio3"
name="radio3"
value="radio3"
reversed={isReversed}
data-sb-pseudo-styles="hover"
/>
</StickerSheet.Row>
<StickerSheet.Row rowTitle="On">
<Radio
id="radio11"
name="radio11"
value="radio11"
reversed={isReversed}
selectedStatus
/>
<Radio
id="radio22"
name="radio22"
value="radio22"
reversed={isReversed}
selectedStatus
data-sb-pseudo-styles="focus"
/>
<Radio
id="radio33"
name="radio33"
value="radio33"
reversed={isReversed}
selectedStatus
data-sb-pseudo-styles="hover"
/>
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
),
parameters: {
pseudo: {
focus: '[data-sb-pseudo-styles="focus"]',
hover: '[data-sb-pseudo-styles="hover"] + span',
},
},
}

export const StickerSheetDefault: StickerSheetStory = {
...StickerSheetTemplate,
name: "Sticker Sheet (Default)",
}

export const StickerSheetReversed: StickerSheetStory = {
...StickerSheetTemplate,
name: "Sticker Sheet (Reversed)",
parameters: {
...StickerSheetTemplate.parameters,
backgrounds: { default: "Purple 700" },
},
args: { isReversed: true },
}
39 changes: 39 additions & 0 deletions packages/components/src/Radio/Radio/_docs/Radio.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Meta, StoryObj } from "@storybook/react"
import { Radio } from "../index"

const meta = {
title: "KAIO-staging/Radio controls/Radio",
component: Radio,
args: {
id: "radio",
name: "Radio",
value: "Radio",
},
parameters: {
a11y: {
config: {
rules: [
{
// Built with no label on purpose, to be used within `RadioField` where label is present
id: "label",
enabled: false,
},
],
},
},
},
} satisfies Meta<typeof Radio>

export default meta

type Story = StoryObj<typeof meta>

export const Playground: Story = {
parameters: {
docs: {
canvas: {
sourceState: "shown",
},
},
},
}
1 change: 1 addition & 0 deletions packages/components/src/Radio/Radio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Radio"
49 changes: 49 additions & 0 deletions packages/components/src/Radio/RadioField/RadioField.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@import "~@kaizen/design-tokens/sass/color";
@import "~@kaizen/design-tokens/sass/spacing";
@import "~@kaizen/design-tokens/sass/typography";
@import "../../../styles/form-variables";

$dt-color-radio-background-color-hover: $color-gray-200;

.container {
position: relative;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
margin-bottom: $spacing-sm;

label {
-webkit-tap-highlight-color: transparent;

:global(.ideal-sans) & {
// This is to override bootstrap styles. Remove when appropriate
font-size: $typography-paragraph-body-font-size;
padding-top: 0;
}
}

&.selected {
label {
font-family: $typography-paragraph-body-font-family;
font-size: $typography-paragraph-body-font-size;
line-height: $typography-paragraph-body-line-height;
letter-spacing: $typography-paragraph-body-letter-spacing;
font-weight: $typography-paragraph-bold-font-weight;
position: static;
}
}

&:not(.reversed) label:hover {
input:not([disabled]) + span {
border-color: $dt-color-form-border-color-hover;
background-color: $dt-color-radio-background-color-hover;
}
}

&.reversed label:hover {
input:not([disabled]) + span {
border-color: $color-white;
background-color: transparent;
}
}
}
Loading

0 comments on commit 8f6cbc3

Please sign in to comment.