Radio group
One choice of a few, each with a line that explains it.
$12 a month, cancel any time.
$120 a year, two months free.
$400 once.
forSlots(RadioGroupSlots)(Recipes.RadioGroup({}))
Usage
The function that drew the preview, over the theme module Create a theme writes. The preview passes it the style picked above instead of its default.
export const RadioGroupStyle = forSlots(RadioGroupSlots)(Recipes.RadioGroup({}))
export const Billing = Schema.Literals(['monthly', 'yearly', 'lifetime'])
export type Billing = typeof Billing.Type
export const BillingGroup: ReturnType<typeof UiRadioGroup.create<Billing>> =
UiRadioGroup.create<Billing>()
const billings: Record<Billing, { readonly label: string; readonly text: string }> = {
monthly: { label: 'Monthly', text: '$12 a month, cancel any time.' },
yearly: { label: 'Yearly', text: '$120 a year, two months free.' },
lifetime: { label: 'Lifetime', text: '$400 once.' },
}
/** The group's focus is the component's; the choice is the Model's. */
export interface RadioGroupState {
readonly group: UiRadioGroup.Model
readonly billing: Billing
}
export const radioGroup = <Message>(
style: NamedStyle<typeof RadioGroupSlots>,
state: RadioGroupState,
toMessage: (message: UiRadioGroup.Message) => Message,
h: HtmlBuilder<Message>,
): Html =>
h.submodel({
slotId: state.group.id,
model: state.group,
view: BillingGroup.view,
viewInputs: {
selectedValue: Option.some(state.billing),
options: Billing.literals,
ariaLabel: 'Billing',
hasOptionDescription: () => true,
toView: RadioGroup.toView<Billing, Message>([style.mixin], { h }, ({ group, options }) =>
h.div(
group,
options.map(option =>
h.div(
[h.Style({ display: 'flex', gap: '0.5rem', alignItems: 'baseline' })],
[
h.button(option.option, []),
h.div(
[h.Style({ display: 'grid' })],
[
h.label(option.label, [billings[option.value].label]),
h.span(option.description, [billings[option.value].text]),
],
),
],
),
),
),
),
},
toParentMessage: toMessage,
})