Components

Radio group

One choice of a few, each with a line that explains it.

tone
size
scheme
$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,
  })

The recipe ↗This code ↗