From 8208b786ee1589522668a065470b280dec683dba Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:40:17 +0800 Subject: [PATCH] docs(dify-ui): clarify radio composition stories (#38456) --- .../dify-ui/src/radio-group/index.stories.tsx | 115 +++++++++++++++--- packages/dify-ui/src/radio/index.stories.tsx | 9 +- packages/dify-ui/src/radio/index.tsx | 10 +- 3 files changed, 106 insertions(+), 28 deletions(-) diff --git a/packages/dify-ui/src/radio-group/index.stories.tsx b/packages/dify-ui/src/radio-group/index.stories.tsx index 6c87f6583a5..be005f0491e 100644 --- a/packages/dify-ui/src/radio-group/index.stories.tsx +++ b/packages/dify-ui/src/radio-group/index.stories.tsx @@ -8,7 +8,7 @@ import { FieldRoot, } from '../field' import { FieldsetLegend, FieldsetRoot } from '../fieldset' -import { Radio, RadioControl, RadioRoot } from '../radio' +import { Radio, RadioControl, RadioIndicator, RadioRoot } from '../radio' const meta = { title: 'Base/Form/RadioGroup', @@ -17,7 +17,7 @@ const meta = { layout: 'centered', docs: { description: { - component: 'RadioGroup primitive built on Base UI. For normal form rows, compose FieldRoot, FieldsetRoot, FieldLabel, RadioGroup, and Radio. For option cards, wrap each option in FieldItem and make the card itself a RadioRoot with variant="unstyled".', + component: '`RadioGroup` owns single-selection state. Use `Radio` for plain form rows, `RadioRoot` when an entire row or card is the radio item, `RadioControl` for the standard visual dot inside custom roots, and `RadioIndicator` only when the design owns a custom control shell.', }, }, }, @@ -60,7 +60,7 @@ export const StandardFormRows: Story = { parameters: { docs: { description: { - story: 'Default form composition. Most product code should use this shape: RadioGroup owns value, FieldsetLegend names the group, and FieldLabel makes each row clickable.', + story: 'Plain form-row composition. `RadioGroup` owns value, `FieldsetLegend` names the group, `FieldLabel` makes each option label clickable, and `Radio` renders the default dot.', }, }, }, @@ -107,31 +107,39 @@ export const BooleanInline: Story = { }, } +type PromptMode = 'default' | 'custom' + function OptionCardsDemo() { - const [value, setValue] = React.useState('default') + const [value, setValue] = React.useState('default') + + const options = [ + { + value: 'default', + title: 'Default prompt', + description: 'Use the built-in prompt for consistent output.', + }, + { + value: 'custom', + title: 'Custom prompt', + description: 'Write a prompt for this app and keep full control.', + }, + ] satisfies Array<{ + value: PromptMode + title: string + description: string + }> return ( + value={value} onValueChange={setValue} className="flex-col items-stretch gap-3" /> )} > Prompt mode - {[ - { - value: 'default', - title: 'Default prompt', - description: 'Use the built-in prompt for consistent output.', - }, - { - value: 'custom', - title: 'Custom prompt', - description: 'Write a prompt for this app and keep full control.', - }, - ].map(option => ( + {options.map(option => ( - value={option.value} variant="unstyled" nativeButton @@ -162,7 +170,76 @@ export const OptionCards: Story = { parameters: { docs: { description: { - story: 'Wrap each option card in FieldItem, then use RadioRoot with variant="unstyled" when the entire card is the radio. RadioControl renders the visual dot inside the card.', + story: 'Product option cards should make the whole card the radio item with `RadioRoot variant="unstyled"`. `RadioControl` renders the standard visual dot inside the custom root.', + }, + }, + }, +} + +type ApprovalMode = 'automatic' | 'manual' + +function CustomIndicatorPartDemo() { + const [value, setValue] = React.useState('automatic') + + const options = [ + { + value: 'automatic', + title: 'Automatic approval', + description: 'Approve requests that match policy.', + }, + { + value: 'manual', + title: 'Manual review', + description: 'Ask an admin to review each request.', + }, + ] satisfies Array<{ + value: ApprovalMode + title: string + description: string + }> + + return ( + + value={value} onValueChange={setValue} className="flex-col items-stretch gap-2" /> + )} + > + Approval mode + {options.map(option => ( + + + value={option.value} + variant="unstyled" + nativeButton + render={