Skip to content

Commit

Permalink
Merge pull request #586 from department-of-veterans-affairs/feature/5…
Browse files Browse the repository at this point in the history
…21-CreateRadioButtonComponent

[Feature] Create Radio Button Component
  • Loading branch information
TimRoe authored Dec 3, 2024
2 parents 74fb2a8 + 34e9f65 commit 7f1ed6b
Show file tree
Hide file tree
Showing 8 changed files with 458 additions and 150 deletions.
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@department-of-veterans-affairs/mobile-component-library",
"version": "0.27.1",
"version": "0.27.2-alpha.1",
"description": "VA Design System Mobile Component Library",
"main": "src/index.tsx",
"scripts": {
Expand Down
152 changes: 17 additions & 135 deletions packages/components/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,10 @@
import {
Pressable,
StyleProp,
View,
ViewStyle,
useWindowDimensions,
} from 'react-native'
import { spacing } from '@department-of-veterans-affairs/mobile-tokens'
import { useTranslation } from 'react-i18next'
import React, { FC } from 'react'

import { CheckboxRadioProps, FormElementProps } from '../../types/forms'
import { CheckboxRadio } from '../shared/CheckboxRadio'
import { CheckboxRadioProps } from '../../types/forms'
import { ComponentWrapper } from '../../wrapper'
import {
Description,
Error,
Header,
Hint,
Label,
fontLabel,
} from '../shared/FormText'
import { Icon, IconProps } from '../Icon/Icon'
import { Spacer } from '../Spacer/Spacer'
import { getA11yLabel, useTheme } from '../../utils'

export type CheckboxProps = FormElementProps &
CheckboxRadioProps & {
/** True to make checkbox appear as checked */
checked?: boolean
/** True to apply indeterminate icon to checkbox */
indeterminate?: boolean
}

export const Checkbox: FC<CheckboxProps> = ({
export const Checkbox: FC<CheckboxRadioProps> = ({
a11yListPosition,
checked,
label,
Expand All @@ -45,115 +18,24 @@ export const Checkbox: FC<CheckboxProps> = ({
testID,
tile,
}) => {
const theme = useTheme()
const { t } = useTranslation()
const fontScale = useWindowDimensions().fontScale

/**
* Container styling
*/
let containerStyle: ViewStyle = {
width: '100%',
}

if (error) {
containerStyle = {
...containerStyle,
borderLeftWidth: spacing.vadsSpace2xs,
borderColor: theme.vadsColorFormsBorderError,
paddingLeft: spacing.vadsSpaceMd,
}
const props = {
a11yListPosition,
checked,
description,
error,
header,
hint,
indeterminate,
label,
onPress,
required,
testID,
tile,
}

/**
* Pressable styling
*/
const pressableBaseStyle: StyleProp<ViewStyle> = {
width: '100%',
flexDirection: 'row',
alignItems: 'flex-start',
}

const tileStyle: ViewStyle = {
...pressableBaseStyle,
borderWidth: 2,
borderRadius: 4,
padding: spacing.vadsSpaceSm,
paddingRight: spacing.vadsSpaceMd,
borderColor: checked
? theme.vadsColorFormsBorderActive
: theme.vadsColorFormsBorderSubtle,
backgroundColor: checked
? theme.vadsColorFormsSurfaceActive
: theme.vadsColorSurfaceDefault,
}

/**
* Icon
*/
const iconViewStyle: ViewStyle = {
// Below keeps icon aligned with first row of text, centered, and scalable
alignSelf: 'flex-start',
// TODO: Replace lineHeight with typography token
minHeight: fontLabel.lineHeight * fontScale,
alignItems: 'center',
justifyContent: 'center',
}

const iconProps: IconProps = {
name: indeterminate
? 'IndeterminateCheckBox'
: checked
? 'CheckBox'
: 'CheckBoxOutlineBlank',
fill:
checked || indeterminate
? theme.vadsColorFormsForegroundActive
: theme.vadsColorFormsBorderDefault,
}

const _icon = (
<View style={iconViewStyle}>
<Icon {...iconProps} />
</View>
)

/**
* Combined a11yLabel on Pressable required for Android Talkback
*/
const a11yLabel =
getA11yLabel(label) +
(required ? ', ' + t('required') : '') +
(description ? `, ${getA11yLabel(description)}` : '')

return (
<ComponentWrapper>
<View style={containerStyle} testID={testID}>
<Header text={header} />
{header && <Spacer size="xs" />}

<Hint text={hint} />
{hint && <Spacer size="xs" />}

<Error text={error} />
{error && <Spacer size="xs" />}

<Pressable
onPress={onPress}
style={tile ? tileStyle : pressableBaseStyle}
aria-checked={indeterminate ? 'mixed' : checked}
aria-valuetext={a11yListPosition}
aria-label={a11yLabel}
role="checkbox">
{_icon}
<Spacer size="xs" horizontal />
<View style={{ flexShrink: 1 }}>
<Label text={label} error={error} required={required} />
{description && <Spacer size="xs" />}
<Description text={description} />
</View>
</Pressable>
</View>
<CheckboxRadio {...props} />
</ComponentWrapper>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type CheckboxGroupProps = FormElementProps & {
/**
* ### Managing checked item state
* The state of the selected checkbox items should be provided to CheckboxGroup via the `selectedItems` prop and updated
* using the `onSelectionChange` callback. When a checkbox is tapped, the provided `onSelectionChange` callback
* using the `onSelectionChange` callback. When a checkbox is tapped, the provided `onSelectionChange` callback
* function is fired and passed an array of the newly `selectedItems`, which can be used to update the parent
* component's state, whether that be redux, zustand, useState, or any other state management methods. Here is a basic
* example using the `useState` hook to store the state of the `selectedItems`:
Expand Down
108 changes: 108 additions & 0 deletions packages/components/src/components/RadioButton/RadioButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Meta, StoryObj } from '@storybook/react'
import { View } from 'react-native'
import React, { useState } from 'react'

import { RadioButton, RadioButtonProps } from './RadioButton'
import { generateDocs } from '../../utils/storybook'

const meta: Meta<RadioButtonProps> = {
title: 'Radio button',
component: RadioButton,
decorators: [
(Story) => (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
margin: 8,
}}>
{Story()}
</View>
),
],
parameters: {
docs: generateDocs({
name: 'Radio button',
docUrl:
'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Selection%20and%20Input/RadioButton/',
}),
},
}

export default meta

type Story = StoryObj<RadioButtonProps>

const statefulComponentRenderer = (props: RadioButtonProps) => {
const { error, header, hint, items, required, tile } = props

const [selectedItem, setSelectedItem] = useState<string | number>()

return (
<RadioButton
items={items}
selectedItem={selectedItem}
error={error}
header={header}
hint={hint}
onSelectionChange={(selected) => setSelectedItem(selected)}
required={required}
tile={tile}
/>
)
}

const items = [
{ text: 'Option 1', description: 'Description for option 1' },
{
text: 'Option 2',
a11yLabel: 'Accessibility override for option 2',
value: '2',
description: {
text: 'Description for option 2',
a11yLabel: 'Accessibility override for description',
},
},
{ text: 'Option 3' },
{ text: 'Option 4' },
{ text: 'Option 5' },
{ text: 'Option 6' },
]

const simpleItems = ['Option 1', 'Option 2', 'Option 3', 'Option 4']

const header = 'Header'
const hint = { text: 'Hint text', a11yLabel: 'Accessibility override for hint' }
const error = { text: 'Error text' }

export const _Default: Story = {
render: statefulComponentRenderer,
args: {
header,
hint,
items,
required: true,
},
}

export const __Tile: Story = {
render: statefulComponentRenderer,
args: {
header,
hint,
items: simpleItems,
tile: true,
},
}

export const ___Error: Story = {
render: statefulComponentRenderer,
args: {
error,
header,
hint,
items,
required: true,
},
}
Loading

0 comments on commit 7f1ed6b

Please sign in to comment.