diff --git a/packages/mini-demo/src/app.config.ts b/packages/mini-demo/src/app.config.ts
index 8dbac41b2..e733e8a0f 100644
--- a/packages/mini-demo/src/app.config.ts
+++ b/packages/mini-demo/src/app.config.ts
@@ -5,6 +5,7 @@ export default defineAppConfig({
'pages/popup/index',
'pages/list/index',
'pages/collapse/index',
+ 'pages/radio/index',
'pages/tabs/index',
],
window: {
diff --git a/packages/mini-demo/src/pages/radio/component/basic.tsx b/packages/mini-demo/src/pages/radio/component/basic.tsx
new file mode 100644
index 000000000..8d7d0f900
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/component/basic.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+
+
+
+ 普通
+
+
+ 默认选中
+
+
+ 禁用
+
+
+
+ 选中且禁用
+
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/mini-demo/src/pages/radio/component/button.tsx b/packages/mini-demo/src/pages/radio/component/button.tsx
new file mode 100644
index 000000000..41fccbfc5
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/component/button.tsx
@@ -0,0 +1,39 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/mini-demo/src/pages/radio/component/group.tsx b/packages/mini-demo/src/pages/radio/component/group.tsx
new file mode 100644
index 000000000..78c0f2b32
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/component/group.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ const [value, setValue] = React.useState([]);
+
+ const onChange = (val) => {
+ console.log('onChange', val);
+ setValue(val);
+ };
+
+ return (
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/mini-demo/src/pages/radio/component/list.tsx b/packages/mini-demo/src/pages/radio/component/list.tsx
new file mode 100644
index 000000000..4d6c30e9b
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/component/list.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+ <>
+
+
+ 选项一
+ 选项二
+
+ 选项三(禁止选择)
+
+
+
+
+
+
+ 选项一
+ 选项二
+
+
+
+ >
+ );
+};
+
+export default Demo;
diff --git a/packages/mini-demo/src/pages/radio/index.config.ts b/packages/mini-demo/src/pages/radio/index.config.ts
new file mode 100644
index 000000000..90dd9fb8a
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/index.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+ navigationBarTitleText: 'Radio',
+});
diff --git a/packages/mini-demo/src/pages/radio/index.tsx b/packages/mini-demo/src/pages/radio/index.tsx
new file mode 100644
index 000000000..137664e4b
--- /dev/null
+++ b/packages/mini-demo/src/pages/radio/index.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import Basic from './component/basic';
+import Button from './component/button';
+import Group from './component/group';
+import List from './component/list';
+
+export default () => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/packages/mini-demo/src/site.ts b/packages/mini-demo/src/site.ts
index ba4fba15f..db03bce9f 100644
--- a/packages/mini-demo/src/site.ts
+++ b/packages/mini-demo/src/site.ts
@@ -14,41 +14,41 @@ const siteMap = {
// }
],
},
- // input: {
- // title: '数据录入',
- // components: [
- // {
- // key: 'Checkbox',
- // name: '复选框',
- // page: '/pages/checkbox/index',
- // },
- // {
- // key: 'Input',
- // name: '输入框',
- // page: '/pages/input/index',
- // },
- // {
- // key: 'Keyboard',
- // name: '键盘',
- // page: '/pages/keyboard/index',
- // },
- // {
- // key: 'Radio',
- // name: '复选框',
- // page: '/pages/radio/index',
- // },
- // {
- // key: 'Stepper',
- // name: '步进器',
- // page: '/pages/stepper/index',
- // },
- // {
- // key: 'Collapse',
- // name: '折叠面板',
- // page: '/pages/collapse/index',
- // },
- // ]
- // },
+ input: {
+ title: '数据录入',
+ components: [
+ // {
+ // key: 'Checkbox',
+ // name: '复选框',
+ // page: '/pages/checkbox/index',
+ // },
+ // {
+ // key: 'Input',
+ // name: '输入框',
+ // page: '/pages/input/index',
+ // },
+ // {
+ // key: 'Keyboard',
+ // name: '键盘',
+ // page: '/pages/keyboard/index',
+ // },
+ {
+ key: 'Radio',
+ name: '复选框',
+ page: '/pages/radio/index',
+ },
+ // {
+ // key: 'Stepper',
+ // name: '步进器',
+ // page: '/pages/stepper/index',
+ // },
+ // {
+ // key: 'Collapse',
+ // name: '折叠面板',
+ // page: '/pages/collapse/index',
+ // },
+ ],
+ },
view: {
title: '数据展示',
components: [
diff --git a/packages/zarm/src/index.mini.ts b/packages/zarm/src/index.mini.ts
index 8f1bd29f2..ac8ccd2d7 100644
--- a/packages/zarm/src/index.mini.ts
+++ b/packages/zarm/src/index.mini.ts
@@ -10,6 +10,8 @@ export { default as Panel } from './panel/index.mini';
export type { PanelProps } from './panel/index.mini';
export { default as Popup } from './popup/index.mini';
export type { PopupProps } from './popup/index.mini';
+export { default as Radio } from './radio/index.mini';
+export type { RadioGroupProps, RadioProps } from './radio/index.mini';
export { default as Select } from './select';
export { default as Tabs } from './tabs/index.mini';
export type { TabPanelProps, TabsCssVars, TabsProps } from './tabs/index.mini';
diff --git a/packages/zarm/src/radio/Radio.mini.tsx b/packages/zarm/src/radio/Radio.mini.tsx
new file mode 100644
index 000000000..eb00e886b
--- /dev/null
+++ b/packages/zarm/src/radio/Radio.mini.tsx
@@ -0,0 +1,228 @@
+import { View } from '@tarojs/components';
+import { createBEM } from '@zarm-design/bem';
+import { Minus as MinusIcon, Success as SuccessIcon } from '@zarm-design/icons';
+import React, {
+ ChangeEvent,
+ forwardRef,
+ ReactNode,
+ useContext,
+ useEffect,
+ useImperativeHandle,
+ useRef,
+ useState,
+} from 'react';
+import Button from '../button/Button.mini';
+import { ConfigContext } from '../config-provider';
+import List from '../list/index.mini';
+import type { HTMLProps } from '../utils/utilityTypes';
+import { RadioGroupContext } from './context';
+import type { BaseRadioProps } from './interface';
+
+export interface RadioCssVars {
+ '--icon-size'?: React.CSSProperties['height'];
+ '--icon-background'?: React.CSSProperties['background'];
+ '--icon-border-radius'?: React.CSSProperties['borderRadius'];
+ '--icon-border-width'?: React.CSSProperties['borderWidth'];
+ '--icon-border-color'?: React.CSSProperties['borderColor'];
+ '--tick-font-size'?: React.CSSProperties['fontSize'];
+ '--tick-color'?: React.CSSProperties['color'];
+ '--tick-transition'?: React.CSSProperties['transition'];
+ '--text-margin-horizontal'?: React.CSSProperties['marginLeft'];
+ '--active-opacity'?: React.CSSProperties['opacity'];
+ '--checked-icon-background'?: React.CSSProperties['background'];
+ '--checked-icon-border-color'?: React.CSSProperties['borderColor'];
+ '--checked-tick-color'?: React.CSSProperties['color'];
+ '--disabled-icon-background'?: React.CSSProperties['background'];
+ '--disabled-icon-border-color'?: React.CSSProperties['borderColor'];
+ '--disabled-text-color'?: React.CSSProperties['color'];
+ '--disabled-tick-color'?: React.CSSProperties['color'];
+ '--group-spacing-vertical'?: React.CSSProperties['marginBottom'];
+ '--group-spacing-horizontal'?: React.CSSProperties['marginRight'];
+}
+
+export type RadioProps = BaseRadioProps &
+ HTMLProps & {
+ renderIcon?: (props: RadioProps) => ReactNode;
+ render?: (props: RadioProps) => ReactNode;
+ onChange?: (e: ChangeEvent) => void;
+ };
+
+const getChecked = (props: RadioProps, defaultChecked?: boolean) => {
+ return props.checked ?? props.defaultChecked ?? defaultChecked;
+};
+
+export interface RadioRef {
+ check: () => void;
+}
+
+const Radio = forwardRef((props, ref) => {
+ const inputRef = useRef(null);
+ let [checked, setChecked] = useState(getChecked(props, false));
+ let { disabled } = props;
+
+ const groupContext = useContext(RadioGroupContext);
+ if (groupContext && props.value !== undefined) {
+ checked = groupContext.value === props.value;
+ setChecked = (changedChecked: boolean) => {
+ if (changedChecked) {
+ groupContext.check(props.value);
+ }
+ };
+ disabled = disabled || groupContext.disabled;
+ }
+
+ const { prefixCls } = useContext(ConfigContext);
+ const bem = createBEM('radio', { prefixCls });
+ const cls = bem([
+ {
+ disabled,
+ checked: checked && !props.indeterminate,
+ untext: !props.children,
+ indeterminate: props.indeterminate,
+ },
+ props.className,
+ ]);
+
+ const currentProps = { ...props, checked };
+
+ const textRender = props.children && {props.children};
+
+ const iconRender = (
+
+ {props.renderIcon ? (
+ props.renderIcon(currentProps)
+ ) : (
+ {props.indeterminate ? : }
+ )}
+
+ );
+
+ const inputRender = (
+ {
+ inputRef.current = node;
+ }}
+ id={props.id}
+ type="radio"
+ className={bem('input')}
+ aria-checked={checked}
+ disabled={disabled}
+ value={props.value}
+ checked={checked}
+ onChange={(e: ChangeEvent) => {
+ if (disabled) return;
+ if (!('checked' in props)) {
+ setChecked(true);
+ }
+ props.onChange?.(e);
+ }}
+ />
+ );
+
+ useImperativeHandle(ref, () => {
+ return {
+ check: () => {
+ if (checked) return;
+ props.onChange?.({
+ target: { value: props.value, checked: true },
+ } as ChangeEvent);
+ setChecked(true);
+ },
+ };
+ });
+
+ useEffect(() => {
+ if (props.checked === undefined) return;
+ if (props.checked === checked) return;
+
+ setChecked(getChecked({ checked: props.checked, defaultChecked: props.defaultChecked }, false));
+ }, [props.checked, props.defaultChecked]);
+
+ if (groupContext?.type === 'button') {
+ return (
+
+ );
+ }
+
+ if (groupContext?.type === 'list') {
+ const tickRender = (
+ <>
+ {inputRender}
+ {iconRender}
+ >
+ );
+
+ return (
+ {
+ if (disabled) return;
+ props.onChange?.({
+ target: { value: props.value, checked: true },
+ } as ChangeEvent);
+ setChecked(true);
+ }
+ : undefined
+ }
+ />
+ );
+ }
+
+ const contentRender = props.render ? (
+ props.render(currentProps)
+ ) : (
+ {
+ if (disabled) return;
+ if (!('checked' in props)) {
+ setChecked(true);
+ }
+ props.onChange?.({
+ target: { value: props.value, checked: true },
+ } as ChangeEvent);
+ }}
+ >
+ {iconRender}
+ {textRender}
+
+ );
+
+ return (
+
+ );
+});
+
+Radio.displayName = 'Radio';
+
+Radio.defaultProps = {
+ indeterminate: false,
+};
+
+export default Radio;
diff --git a/packages/zarm/src/radio/demo/basic.mini.tsx b/packages/zarm/src/radio/demo/basic.mini.tsx
new file mode 100644
index 000000000..8d7d0f900
--- /dev/null
+++ b/packages/zarm/src/radio/demo/basic.mini.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+
+
+
+ 普通
+
+
+ 默认选中
+
+
+ 禁用
+
+
+
+ 选中且禁用
+
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/zarm/src/radio/demo/button.mini.tsx b/packages/zarm/src/radio/demo/button.mini.tsx
new file mode 100644
index 000000000..41fccbfc5
--- /dev/null
+++ b/packages/zarm/src/radio/demo/button.mini.tsx
@@ -0,0 +1,39 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/zarm/src/radio/demo/group.mini.tsx b/packages/zarm/src/radio/demo/group.mini.tsx
new file mode 100644
index 000000000..78c0f2b32
--- /dev/null
+++ b/packages/zarm/src/radio/demo/group.mini.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ const [value, setValue] = React.useState([]);
+
+ const onChange = (val) => {
+ console.log('onChange', val);
+ setValue(val);
+ };
+
+ return (
+
+
+
+
+ 选项一
+ 选项二
+ 选项三
+
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/packages/zarm/src/radio/demo/list.mini.tsx b/packages/zarm/src/radio/demo/list.mini.tsx
new file mode 100644
index 000000000..4d6c30e9b
--- /dev/null
+++ b/packages/zarm/src/radio/demo/list.mini.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import { List, Panel, Radio } from 'zarm/mini';
+
+const Demo = () => {
+ return (
+ <>
+
+
+ 选项一
+ 选项二
+
+ 选项三(禁止选择)
+
+
+
+
+
+
+ 选项一
+ 选项二
+
+
+
+ >
+ );
+};
+
+export default Demo;
diff --git a/packages/zarm/src/radio/index.mini.ts b/packages/zarm/src/radio/index.mini.ts
new file mode 100644
index 000000000..74cdb9da1
--- /dev/null
+++ b/packages/zarm/src/radio/index.mini.ts
@@ -0,0 +1,10 @@
+import attachPropertiesToComponent from '../utils/attachPropertiesToComponent';
+import Radio from './Radio.mini';
+import Group from './RadioGroup';
+
+export type { RadioCssVars, RadioProps, RadioRef } from './Radio.mini';
+export type { RadioGroupCssVars, RadioGroupProps } from './RadioGroup';
+
+export default attachPropertiesToComponent(Radio, {
+ Group,
+});
diff --git a/packages/zarm/src/radio/style/index.mini.ts b/packages/zarm/src/radio/style/index.mini.ts
new file mode 100644
index 000000000..5229a6cd9
--- /dev/null
+++ b/packages/zarm/src/radio/style/index.mini.ts
@@ -0,0 +1,5 @@
+import '../../button/style/index.mini';
+import '../../icon/style';
+import '../../list/style/index.mini';
+import '../../style';
+import './index.scss';