From f3877765a1ac42d693c9e2de044dc54dc4cce848 Mon Sep 17 00:00:00 2001 From: Tim R Date: Tue, 5 Nov 2024 13:29:38 -0600 Subject: [PATCH 1/6] Initial Radio button creation --- .../RadioButton/RadioButton.stories.tsx | 108 ++++++++++++ .../components/RadioButton/RadioButton.tsx | 163 ++++++++++++++++++ .../src/components/shared/CheckboxRadio.tsx | 157 +++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 packages/components/src/components/RadioButton/RadioButton.stories.tsx create mode 100644 packages/components/src/components/RadioButton/RadioButton.tsx create mode 100644 packages/components/src/components/shared/CheckboxRadio.tsx diff --git a/packages/components/src/components/RadioButton/RadioButton.stories.tsx b/packages/components/src/components/RadioButton/RadioButton.stories.tsx new file mode 100644 index 00000000..7fdc9fee --- /dev/null +++ b/packages/components/src/components/RadioButton/RadioButton.stories.tsx @@ -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 = { + title: 'Radio button', + component: RadioButton, + decorators: [ + (Story) => ( + + {Story()} + + ), + ], + parameters: { + docs: generateDocs({ + name: 'Radio button', + docUrl: + 'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Selection%20and%20Input/Checkbox/', + }), + }, +} + +export default meta + +type Story = StoryObj + +const statefulComponentRenderer = (props: RadioButtonProps) => { + const { error, header, hint, items, required, tile } = props + + const [selectedItems, setSelectedItems] = useState<(string | number)[]>([]) + + return ( + setSelectedItems(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, + }, +} diff --git a/packages/components/src/components/RadioButton/RadioButton.tsx b/packages/components/src/components/RadioButton/RadioButton.tsx new file mode 100644 index 00000000..32330516 --- /dev/null +++ b/packages/components/src/components/RadioButton/RadioButton.tsx @@ -0,0 +1,163 @@ +import { View, ViewStyle } from 'react-native' +import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import React, { FC, Fragment } from 'react' + +import { CheckboxRadio } from '../shared/CheckboxRadio' +import { ComponentWrapper } from '../../wrapper' +import { Error, Header, Hint } from '../shared/FormText' +import { + FormElementProps, + StringOrTextWithA11y, + TextWithA11y, +} from '../../types' +import { Spacer } from '../Spacer/Spacer' +import { useTheme } from '../../utils' + +type TextWithA11yAndValue = TextWithA11y & { + /** Description for checkbox item */ + description?: StringOrTextWithA11y + /** Value or ID for checkbox item if different than checkbox label */ + value?: string | number + /** Optional TestID */ + testID?: string +} + +export type RadioButtonProps = FormElementProps & { + /** Array of checkbox options. Can be an array containing strings or objects if values or a11y overrides are needed */ + items: string[] | TextWithA11yAndValue[] + /** Callback function that receives an updated array of selected values when checkboxes are pressed */ + onSelectionChange: (selected: (string | number)[]) => void + /** Array of the labels or values (if provided) of currently selected checkboxes */ + selectedItems: (string | number)[] + /** True to apply tile styling */ + tile?: boolean +} + +/** + * ### 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 + * 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`: + * + * ```jsx + * export const ParentComponent = () => { + * const [selectedItems, setSelectedItems] = useState([]) + * + * const onSelectionChange = (updatedItems) => setSelectedItems(updatedItems) + * + * const items = ['Option 1', 'Option 2', 'Option 3'] + * + * return ( + * + * ) + * + * } + * ``` + * + * ### Providing values or accessibility labels + * CheckboxGroup can accept a simple array of strings to display as checkboxes as shown above. If you want to provide + * values for each item that differ from display labels, or you want to provide accessibility labels for certain items, + * you can pass an array of objects containing these optional fields as well. For example: + * + * ```jsx + * export const ParentComponent = () => { + * const [selectedItems, setSelectedItems] = useState([]) + * + * const onSelectionChange = (updatedItems) => setSelectedItems(updatedItems) + * + * const items = [ + * { text: 'Minnesota', value: 'MN' }, + * { text: 'California', value: 'CA' }, + * { text: 'New Jersey', value: 'NJ' }, + * { text: 'Washington D.C.', value: 'DC', a11yLabel: 'District of Columbia' }, + * ] + * + * return ( + * + * ) + * + * } + * ``` + */ +export const RadioButton: FC = ({ + items, + selectedItems, + error, + header, + hint, + onSelectionChange, + required, + testID, + tile, +}) => { + const theme = useTheme() + + const handleCheckboxChange = (value: string | number) => { + if (selectedItems.includes(value)) { + onSelectionChange( + selectedItems.filter((itemValue) => itemValue !== value), + ) + } else { + onSelectionChange([...selectedItems, value]) + } + } + + /** + * Container styling + */ + let containerStyle: ViewStyle = { + width: '100%', + } + + if (error) { + containerStyle = { + ...containerStyle, + borderLeftWidth: spacing.vadsSpace2xs, + borderColor: theme.vadsColorFormsBorderError, + paddingLeft: spacing.vadsSpaceMd, + } + } + + return ( + + +
+ {header && } + + + {hint && } + + + {error && } + + {items.map((item, index) => { + const isObject = typeof item === 'object' + const value = isObject ? item.value || item.text : item + + return ( + + handleCheckboxChange(value)} + radio + testID={isObject ? item.testID : undefined} + tile={tile} + /> + {index < items.length - 1 && } + + ) + })} + + + ) +} diff --git a/packages/components/src/components/shared/CheckboxRadio.tsx b/packages/components/src/components/shared/CheckboxRadio.tsx new file mode 100644 index 00000000..6f79ba39 --- /dev/null +++ b/packages/components/src/components/shared/CheckboxRadio.tsx @@ -0,0 +1,157 @@ +import { + Pressable, + StyleProp, + View, + ViewStyle, + useWindowDimensions, +} from 'react-native' +import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import React, { FC } from 'react' + +import { CheckboxRadioProps, FormElementProps } 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 { useTheme } from '../../utils' + +export type _CheckboxRadioProps = FormElementProps & + CheckboxRadioProps & { + /** True to make checkbox appear as checked */ + checked?: boolean + /** True to apply indeterminate icon to checkbox */ + indeterminate?: boolean + /** True to render as a radio button */ + radio?: boolean + } + +export const CheckboxRadio: FC<_CheckboxRadioProps> = ({ + checked, + label, + description, + error, + header, + hint, + indeterminate, + onPress, + radio, + required, + testID, + tile, +}) => { + const theme = useTheme() + const fontScale = useWindowDimensions().fontScale + + /** + * Container styling + */ + let containerStyle: ViewStyle = { + width: '100%', + } + + if (error) { + containerStyle = { + ...containerStyle, + borderLeftWidth: spacing.vadsSpace2xs, + borderColor: theme.vadsColorFormsBorderError, + paddingLeft: spacing.vadsSpaceMd, + } + } + + /** + * Pressable styling + */ + const pressableBaseStyle: StyleProp = { + 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', + } + + let iconName: IconProps['name'] + + if (radio) { + iconName = checked ? 'RadioButtonChecked' : 'RadioButtonUnchecked' + } else { + iconName = indeterminate + ? 'IndeterminateCheckBox' + : checked + ? 'CheckBox' + : 'CheckBoxOutlineBlank' + } + + const iconProps: IconProps = { + name: iconName, + fill: + checked || indeterminate + ? theme.vadsColorFormsForegroundActive + : theme.vadsColorFormsBorderDefault, + } + + const _icon = ( + + + + ) + + return ( + + +
+ {header && } + + + {hint && } + + + {error && } + + + {_icon} + + + + + + + ) +} From 3208b340fdfaf6cf9aef3c0145fe7e5ce8ce7d69 Mon Sep 17 00:00:00 2001 From: Tim R Date: Wed, 13 Nov 2024 12:23:26 -0600 Subject: [PATCH 2/6] Radio button refinement, shift to shared CheckboxRadio internal component --- .../src/components/Checkbox/Checkbox.tsx | 152 ++---------------- .../CheckboxGroup/CheckboxGroup.tsx | 2 +- .../RadioButton/RadioButton.stories.tsx | 6 +- .../components/RadioButton/RadioButton.tsx | 63 ++++---- .../src/components/shared/CheckboxRadio.tsx | 110 ++++++------- packages/components/src/types/forms.ts | 39 +++-- 6 files changed, 125 insertions(+), 247 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index a0d74383..aa3c386b 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -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 = ({ +export const Checkbox: FC = ({ a11yListPosition, checked, label, @@ -45,115 +18,24 @@ export const Checkbox: FC = ({ 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 = { - 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 = ( - - - - ) - - /** - * Combined a11yLabel on Pressable required for Android Talkback - */ - const a11yLabel = - getA11yLabel(label) + - (required ? ', ' + t('required') : '') + - (description ? `, ${getA11yLabel(description)}` : '') - return ( - -
- {header && } - - - {hint && } - - - {error && } - - - {_icon} - - - - - + ) } diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 68eb6326..c5ca4a5b 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -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`: diff --git a/packages/components/src/components/RadioButton/RadioButton.stories.tsx b/packages/components/src/components/RadioButton/RadioButton.stories.tsx index 7fdc9fee..15955836 100644 --- a/packages/components/src/components/RadioButton/RadioButton.stories.tsx +++ b/packages/components/src/components/RadioButton/RadioButton.stories.tsx @@ -37,16 +37,16 @@ type Story = StoryObj const statefulComponentRenderer = (props: RadioButtonProps) => { const { error, header, hint, items, required, tile } = props - const [selectedItems, setSelectedItems] = useState<(string | number)[]>([]) + const [selectedItem, setSelectedItem] = useState() return ( setSelectedItems(selected)} + onSelectionChange={(selected) => setSelectedItem(selected)} required={required} tile={tile} /> diff --git a/packages/components/src/components/RadioButton/RadioButton.tsx b/packages/components/src/components/RadioButton/RadioButton.tsx index 32330516..812ae70e 100644 --- a/packages/components/src/components/RadioButton/RadioButton.tsx +++ b/packages/components/src/components/RadioButton/RadioButton.tsx @@ -1,5 +1,6 @@ import { View, ViewStyle } from 'react-native' import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import { useTranslation } from 'react-i18next' import React, { FC, Fragment } from 'react' import { CheckboxRadio } from '../shared/CheckboxRadio' @@ -14,43 +15,43 @@ import { Spacer } from '../Spacer/Spacer' import { useTheme } from '../../utils' type TextWithA11yAndValue = TextWithA11y & { - /** Description for checkbox item */ + /** Description for radio item */ description?: StringOrTextWithA11y - /** Value or ID for checkbox item if different than checkbox label */ + /** Value or ID for radio item if different than radio label */ value?: string | number /** Optional TestID */ testID?: string } export type RadioButtonProps = FormElementProps & { - /** Array of checkbox options. Can be an array containing strings or objects if values or a11y overrides are needed */ + /** Array of radio options. Can be an array containing strings or objects if values or a11y overrides are needed */ items: string[] | TextWithA11yAndValue[] - /** Callback function that receives an updated array of selected values when checkboxes are pressed */ - onSelectionChange: (selected: (string | number)[]) => void - /** Array of the labels or values (if provided) of currently selected checkboxes */ - selectedItems: (string | number)[] + /** Callback function that receives an updated selected value when a radio is pressed */ + onSelectionChange: (selected: string | number) => void + /** The label or value (if provided) of currently selected radio, if any */ + selectedItem?: string | number /** True to apply tile styling */ tile?: boolean } /** * ### 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 - * function is fired and passed an array of the newly `selectedItems`, which can be used to update the parent + * The state of the selected radio item should be provided to RadioButton via the `selectedItem` prop and updated + * using the `onSelectionChange` callback. When a radio is tapped, the provided `onSelectionChange` callback + * function is fired and passed the newly `selectedItem`, 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`: + * example using the `useState` hook to store the state of the `selectedItem`: * * ```jsx * export const ParentComponent = () => { - * const [selectedItems, setSelectedItems] = useState([]) + * const [selectedItem, setSelectedItem] = useState() * - * const onSelectionChange = (updatedItems) => setSelectedItems(updatedItems) + * const onSelectionChange = (updatedItem) => setSelectedItem(updatedItem) * * const items = ['Option 1', 'Option 2', 'Option 3'] * * return ( - * @@ -60,15 +61,15 @@ export type RadioButtonProps = FormElementProps & { * ``` * * ### Providing values or accessibility labels - * CheckboxGroup can accept a simple array of strings to display as checkboxes as shown above. If you want to provide + * RadioButton can accept a simple array of strings to display as radios as shown above. If you want to provide * values for each item that differ from display labels, or you want to provide accessibility labels for certain items, * you can pass an array of objects containing these optional fields as well. For example: * * ```jsx * export const ParentComponent = () => { - * const [selectedItems, setSelectedItems] = useState([]) + * const [selectedItem, setSelectedItem] = useState() * - * const onSelectionChange = (updatedItems) => setSelectedItems(updatedItems) + * const onSelectionChange = (updatedItem) => setSelectedItem(updatedItem) * * const items = [ * { text: 'Minnesota', value: 'MN' }, @@ -78,7 +79,7 @@ export type RadioButtonProps = FormElementProps & { * ] * * return ( - * @@ -89,7 +90,7 @@ export type RadioButtonProps = FormElementProps & { */ export const RadioButton: FC = ({ items, - selectedItems, + selectedItem, error, header, hint, @@ -99,16 +100,7 @@ export const RadioButton: FC = ({ tile, }) => { const theme = useTheme() - - const handleCheckboxChange = (value: string | number) => { - if (selectedItems.includes(value)) { - onSelectionChange( - selectedItems.filter((itemValue) => itemValue !== value), - ) - } else { - onSelectionChange([...selectedItems, value]) - } - } + const { t } = useTranslation() /** * Container styling @@ -128,7 +120,7 @@ export const RadioButton: FC = ({ return ( - +
{header && } @@ -141,14 +133,19 @@ export const RadioButton: FC = ({ {items.map((item, index) => { const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item + const a11yListPosition = t('listPosition', { + position: index + 1, + total: items.length, + }) return ( - + handleCheckboxChange(value)} + checked={selectedItem === value} + onPress={() => onSelectionChange(value)} radio testID={isObject ? item.testID : undefined} tile={tile} diff --git a/packages/components/src/components/shared/CheckboxRadio.tsx b/packages/components/src/components/shared/CheckboxRadio.tsx index 6f79ba39..b648553f 100644 --- a/packages/components/src/components/shared/CheckboxRadio.tsx +++ b/packages/components/src/components/shared/CheckboxRadio.tsx @@ -1,15 +1,9 @@ -import { - Pressable, - StyleProp, - View, - ViewStyle, - useWindowDimensions, -} from 'react-native' +import { Pressable, StyleProp, View, ViewStyle } 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 { ComponentWrapper } from '../../wrapper' +import { CheckboxRadioProps } from '../../types/forms' import { Description, Error, @@ -20,19 +14,15 @@ import { } from '../shared/FormText' import { Icon, IconProps } from '../Icon/Icon' import { Spacer } from '../Spacer/Spacer' -import { useTheme } from '../../utils' - -export type _CheckboxRadioProps = FormElementProps & - CheckboxRadioProps & { - /** True to make checkbox appear as checked */ - checked?: boolean - /** True to apply indeterminate icon to checkbox */ - indeterminate?: boolean - /** True to render as a radio button */ - radio?: boolean - } - -export const CheckboxRadio: FC<_CheckboxRadioProps> = ({ +import { getA11yLabel, useTheme } from '../../utils' + +/** + * Internal component for rendering a checkbox or radio button identically besides the icon + * Note: Should not be used directly. Use the `Checkbox` or `RadioButton` components instead as this does not include + * ComponentWrapper + */ +export const CheckboxRadio: FC = ({ + a11yListPosition, checked, label, description, @@ -47,7 +37,7 @@ export const CheckboxRadio: FC<_CheckboxRadioProps> = ({ tile, }) => { const theme = useTheme() - const fontScale = useWindowDimensions().fontScale + const { t } = useTranslation() /** * Container styling @@ -91,15 +81,6 @@ export const CheckboxRadio: FC<_CheckboxRadioProps> = ({ /** * 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', - } - let iconName: IconProps['name'] if (radio) { @@ -118,40 +99,43 @@ export const CheckboxRadio: FC<_CheckboxRadioProps> = ({ checked || indeterminate ? theme.vadsColorFormsForegroundActive : theme.vadsColorFormsBorderDefault, + alignWithTextLineHeight: fontLabel.lineHeight, } - const _icon = ( - - - - ) + /** + * Combined a11yLabel on Pressable required for Android Talkback + */ + const a11yLabel = + getA11yLabel(label) + + (required ? ', ' + t('required') : '') + + (description ? `, ${getA11yLabel(description)}` : '') return ( - - -
- {header && } - - - {hint && } - - - {error && } - - - {_icon} - - - - - - + +
+ {header && } + + + {hint && } + + + {error && } + + + + + + + + ) } diff --git a/packages/components/src/types/forms.ts b/packages/components/src/types/forms.ts index 03a7cd39..51b80866 100644 --- a/packages/components/src/types/forms.ts +++ b/packages/components/src/types/forms.ts @@ -16,18 +16,33 @@ export type FormElementProps = { testID?: string } +type CheckboxOrRadioProps = + | { + /** True to apply indeterminate icon to checkbox */ + indeterminate?: boolean + radio?: never + } + | { + indeterminate?: never + /** True to render as a radio button */ + radio?: boolean + } + /** * Props that are common to Checkbox and Radio */ -export type CheckboxRadioProps = { - /** Primary text for checkbox */ - label: StringOrTextWithA11y - /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ - onPress: () => void - /** Textual description of position within list of checkboxes */ - a11yListPosition?: string - /** Description that appears below label */ - description?: StringOrTextWithA11y - /** True to apply tile styling */ - tile?: boolean -} +export type CheckboxRadioProps = CheckboxOrRadioProps & + FormElementProps & { + /** True to make checkbox/radio appear as checked */ + checked?: boolean + /** Primary text for checkbox */ + label: StringOrTextWithA11y + /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ + onPress: () => void + /** Textual description of position within list of checkboxes */ + a11yListPosition?: string + /** Description that appears below label */ + description?: StringOrTextWithA11y + /** True to apply tile styling */ + tile?: boolean + } From b83af7e75150266adeaa81eef93dbd9c5604f178 Mon Sep 17 00:00:00 2001 From: Tim R Date: Tue, 19 Nov 2024 13:49:45 -0600 Subject: [PATCH 3/6] Update storybook->doc site link target --- .../src/components/RadioButton/RadioButton.stories.tsx | 2 +- packages/components/src/components/RadioButton/RadioButton.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/RadioButton/RadioButton.stories.tsx b/packages/components/src/components/RadioButton/RadioButton.stories.tsx index 15955836..37f9bac7 100644 --- a/packages/components/src/components/RadioButton/RadioButton.stories.tsx +++ b/packages/components/src/components/RadioButton/RadioButton.stories.tsx @@ -25,7 +25,7 @@ const meta: Meta = { docs: generateDocs({ name: 'Radio button', docUrl: - 'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Selection%20and%20Input/Checkbox/', + 'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Selection%20and%20Input/RadioButton/', }), }, } diff --git a/packages/components/src/components/RadioButton/RadioButton.tsx b/packages/components/src/components/RadioButton/RadioButton.tsx index 812ae70e..5489318f 100644 --- a/packages/components/src/components/RadioButton/RadioButton.tsx +++ b/packages/components/src/components/RadioButton/RadioButton.tsx @@ -120,7 +120,7 @@ export const RadioButton: FC = ({ return ( - +
{header && } From 7f95fe0b4e265834dde6a398348030a3c348e7c9 Mon Sep 17 00:00:00 2001 From: VA Automation Bot Date: Tue, 19 Nov 2024 19:52:20 +0000 Subject: [PATCH 4/6] Version bump: components-v0.27.2-alpha.0 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index fe940e83..cd8fc751 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@department-of-veterans-affairs/mobile-component-library", - "version": "0.27.0", + "version": "0.27.2-alpha.0", "description": "VA Design System Mobile Component Library", "main": "src/index.tsx", "scripts": { From d20598d1c84b60f261f0f568709c2253681a0ea2 Mon Sep 17 00:00:00 2001 From: Tim R Date: Tue, 19 Nov 2024 16:43:25 -0600 Subject: [PATCH 5/6] Update project exports with RadioButton --- packages/components/src/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/src/index.tsx b/packages/components/src/index.tsx index 34e20175..cc776d15 100644 --- a/packages/components/src/index.tsx +++ b/packages/components/src/index.tsx @@ -18,6 +18,7 @@ export { CheckboxGroup } from './components/CheckboxGroup/CheckboxGroup' export { Icon } from './components/Icon/Icon' export { Link } from './components/Link/Link' export { LoadingIndicator } from './components/LoadingIndicator/LoadingIndicator' +export { RadioButton } from './components/RadioButton/RadioButton' export { SegmentedControl } from './components/SegmentedControl/SegmentedControl' export { SnackbarProvider, @@ -29,11 +30,12 @@ export { Text } from './components/Text/Text' // Export Prop Types export type { AlertProps } from './components/Alert/Alert' export type { ButtonProps } from './components/Button/Button' -export type { CheckboxProps } from './components/Checkbox/Checkbox' +export type { CheckboxRadioProps } from './types/forms' export type { CheckboxGroupProps } from './components/CheckboxGroup/CheckboxGroup' export type { IconProps } from './components/Icon/Icon' export type { LinkProps } from './components/Link/Link' export type { LoadingIndicatorProps } from './components/LoadingIndicator/LoadingIndicator' +export type { RadioButtonProps } from './components/RadioButton/RadioButton' export type { SegmentedControlProps } from './components/SegmentedControl/SegmentedControl' export type { SpacerProps } from './components/Spacer/Spacer' export type { TextProps } from './components/Text/Text' From 83eb2d8bce0f21cafa199eef5c76c6fe7914b96a Mon Sep 17 00:00:00 2001 From: VA Automation Bot Date: Wed, 20 Nov 2024 19:43:31 +0000 Subject: [PATCH 6/6] Version bump: components-v0.27.2-alpha.1 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 69b2ea19..1d285d93 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@department-of-veterans-affairs/mobile-component-library", - "version": "0.27.2-alpha.0", + "version": "0.27.2-alpha.1", "description": "VA Design System Mobile Component Library", "main": "src/index.tsx", "scripts": {