diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index 5c306e0c17d2..f224017b6280 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -32,6 +32,7 @@ import { SnapUIInput } from '../snaps/snap-ui-input'; import { SnapUIForm } from '../snaps/snap-ui-form'; import { SnapUIButton } from '../snaps/snap-ui-button'; import { SnapUIDropdown } from '../snaps/snap-ui-dropdown'; +import { SnapUIRadioGroup } from '../snaps/snap-ui-radio-group'; import { SnapUICheckbox } from '../snaps/snap-ui-checkbox'; import { SnapUITooltip } from '../snaps/snap-ui-tooltip'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) @@ -87,6 +88,7 @@ export const safeComponentList = { SnapUIButton, SnapUIForm, SnapUIDropdown, + SnapUIRadioGroup, SnapUICheckbox, SnapUITooltip, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) diff --git a/ui/components/app/snaps/snap-ui-radio-group/index.ts b/ui/components/app/snaps/snap-ui-radio-group/index.ts new file mode 100644 index 000000000000..973dd83fc50f --- /dev/null +++ b/ui/components/app/snaps/snap-ui-radio-group/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-radio-group'; diff --git a/ui/components/app/snaps/snap-ui-radio-group/snap-ui-radio-group.tsx b/ui/components/app/snaps/snap-ui-radio-group/snap-ui-radio-group.tsx new file mode 100644 index 000000000000..4563fbc02fe3 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-radio-group/snap-ui-radio-group.tsx @@ -0,0 +1,91 @@ +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { + AlignItems, + Display, + FlexDirection, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { + Box, + HelpText, + HelpTextSeverity, + Label, + Text, +} from '../../../component-library'; + +export type SnapUIRadioOption = { value: string; name: string }; + +export type SnapUIRadioGroupProps = { + name: string; + label?: string; + error?: string; + options: SnapUIRadioOption[]; + form?: string; +}; + +export const SnapUIRadioGroup: FunctionComponent = ({ + name, + label, + error, + form, + ...props +}) => { + const { handleInputChange, getValue } = useSnapInterfaceContext(); + + const initialValue = getValue(name, form) as string; + + const [value, setValue] = useState(initialValue ?? ''); + + useEffect(() => { + if (initialValue && value !== initialValue) { + setValue(initialValue); + } + }, [initialValue]); + + const handleChange = (newValue: string) => { + setValue(newValue); + handleInputChange(name, newValue, form); + }; + + const displayRadioOptions = (options: SnapUIRadioOption[]) => { + return options.map((option: SnapUIRadioOption) => { + return ( + + handleChange(option.value)} + /> + + {option.name} + + + ); + }); + }; + + return ( + + {label && } + {displayRadioOptions(props.options)} + {error && ( + + {error} + + )} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/field.ts b/ui/components/app/snaps/snap-ui-renderer/components/field.ts index eb5b0b0e2256..61a7cab9466a 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/field.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/field.ts @@ -4,11 +4,13 @@ import { ButtonElement, JSXElement, DropdownElement, + RadioGroupElement, CheckboxElement, } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { button as buttonFn } from './button'; import { dropdown as dropdownFn } from './dropdown'; +import { radioGroup as radioGroupFn } from './radioGroup'; import { checkbox as checkboxFn } from './checkbox'; import { UIComponentFactory, UIComponentParams } from './types'; @@ -84,6 +86,24 @@ export const field: UIComponentFactory = ({ element, form }) => { }; } + case 'RadioGroup': { + const radioGroup = child as RadioGroupElement; + const radioGroupMapped = radioGroupFn({ + element: radioGroup, + } as UIComponentParams); + return { + element: 'SnapUIRadioGroup', + props: { + ...radioGroupMapped.props, + id: radioGroup.props.name, + label: element.props.label, + name: radioGroup.props.name, + form, + error: element.props.error, + }, + }; + } + case 'Checkbox': { const checkbox = child as CheckboxElement; const checkboxMapped = checkboxFn({ diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 3a84e5a67a40..51f625998a4a 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -16,6 +16,7 @@ import { italic } from './italic'; import { link } from './link'; import { field } from './field'; import { dropdown } from './dropdown'; +import { radioGroup } from './radioGroup'; import { value } from './value'; import { checkbox } from './checkbox'; import { tooltip } from './tooltip'; @@ -40,6 +41,7 @@ export const COMPONENT_MAPPING = { Link: link, Field: field, Dropdown: dropdown, + RadioGroup: radioGroup, Value: value, Checkbox: checkbox, Tooltip: tooltip, diff --git a/ui/components/app/snaps/snap-ui-renderer/components/radioGroup.ts b/ui/components/app/snaps/snap-ui-renderer/components/radioGroup.ts new file mode 100644 index 000000000000..78677e60ec35 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/radioGroup.ts @@ -0,0 +1,26 @@ +import { RadioGroupElement, RadioElement } from '@metamask/snaps-sdk/jsx'; +import { getJsxChildren } from '@metamask/snaps-utils'; + +import { UIComponentFactory } from './types'; + +export const radioGroup: UIComponentFactory = ({ + element, + form, +}) => { + const children = getJsxChildren(element) as RadioElement[]; + + const options = children.map((child) => ({ + value: child.props.value, + name: child.props.children, + })); + + return { + element: 'SnapUIRadioGroup', + props: { + id: element.props.name, + name: element.props.name, + form, + options, + }, + }; +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.test.js b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.test.js index 6b6f5b659c54..510d7466e22c 100644 --- a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.test.js +++ b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.test.js @@ -1,7 +1,7 @@ import { JSXElementStruct } from '@metamask/snaps-sdk/jsx'; import { COMPONENT_MAPPING } from './components'; -const EXCLUDED_COMPONENTS = ['Option']; +const EXCLUDED_COMPONENTS = ['Option', 'Radio']; describe('Snap UI mapping', () => { it('supports all exposed components', () => { diff --git a/ui/components/app/snaps/snap-ui-renderer/utils.ts b/ui/components/app/snaps/snap-ui-renderer/utils.ts index 4ad950d7c415..86e97bee12af 100644 --- a/ui/components/app/snaps/snap-ui-renderer/utils.ts +++ b/ui/components/app/snaps/snap-ui-renderer/utils.ts @@ -89,7 +89,7 @@ export const mapToTemplate = (params: MapToTemplateParams): UIComponent => { const { type, key } = params.element; const elementKey = key ?? generateKey(params.map, params.element); const mapped = COMPONENT_MAPPING[ - type as Exclude + type as Exclude // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any ](params as any);