From 9f83c864e052429c3a349a008c0661103b7ccf44 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 12:14:18 +0000
Subject: [PATCH 01/13] init add formfieldradio

---
 demo/forms.py                                 |  1 +
 src/npm-fastui/src/components/FormField.tsx   | 58 +++++++++++++++++++
 src/npm-fastui/src/components/index.tsx       |  3 +
 src/npm-fastui/src/models.d.ts                | 16 +++++
 .../fastui/components/__init__.py             |  2 +
 src/python-fastui/fastui/components/forms.py  | 14 ++++-
 src/python-fastui/fastui/json_schema.py       | 31 ++++++----
 7 files changed, 114 insertions(+), 11 deletions(-)

diff --git a/demo/forms.py b/demo/forms.py
index f0f7c7ef..ef81be3f 100644
--- a/demo/forms.py
+++ b/demo/forms.py
@@ -123,6 +123,7 @@ class ToolEnum(str, enum.Enum):
 
 class SelectForm(BaseModel):
     select_single: ToolEnum = Field(title='Select Single')
+    select_radio: ToolEnum = Field(title='Select Radio', json_schema_extra={'mode': 'radio'}, description='test ')
     select_multiple: list[ToolEnum] = Field(title='Select Multiple')
     search_select_single: str = Field(json_schema_extra={'search_url': '/api/forms/search'})
     search_select_multiple: list[str] = Field(json_schema_extra={'search_url': '/api/forms/search'})
diff --git a/src/npm-fastui/src/components/FormField.tsx b/src/npm-fastui/src/components/FormField.tsx
index 0e67959b..d9306065 100644
--- a/src/npm-fastui/src/components/FormField.tsx
+++ b/src/npm-fastui/src/components/FormField.tsx
@@ -12,6 +12,7 @@ import type {
   SelectOption,
   SelectOptions,
   SelectGroup,
+  FormFieldRadio,
 } from '../models'
 
 import { useClassName } from '../hooks/className'
@@ -304,6 +305,62 @@ export const FormFieldSelectSearchComp: FC<FormFieldSelectSearchProps> = (props)
   )
 }
 
+interface FormFieldRadioProps extends FormFieldRadio {
+  onChange?: PrivateOnChange
+}
+
+export const FormFieldRadioComp: FC<FormFieldRadioProps> = (props) => {
+  const { name, required, locked, options, initial } = props
+  const className = useClassName(props)
+  const inputClassName = useClassName(props, { el: 'input' })
+
+  return (
+    <div className={className}>
+      <Label {...props} />
+      {options.map((option, i) => {
+        if ('options' in option) {
+          // option is a SelectGroup
+          return option.options.map((subOption, j) => (
+            <div key={`${i}-${j}`}>
+              <input
+                type="radio"
+                id={`${inputId(props)}-${i}-${j}`}
+                className={inputClassName}
+                name={name}
+                value={subOption.value}
+                defaultChecked={subOption.value === initial}
+                required={required}
+                disabled={locked}
+                aria-describedby={descId(props)}
+              />
+              <label htmlFor={`${inputId(props)}-${i}-${j}`}>{subOption.label}</label>
+            </div>
+          ))
+        } else {
+          // option is a SelectOption
+          return (
+            <div key={i}>
+              <input
+                type="radio"
+                id={`${inputId(props)}-${i}`}
+                className={inputClassName}
+                name={name}
+                value={option.value}
+                defaultChecked={option.value === initial}
+                required={required}
+                disabled={locked}
+                aria-describedby={descId(props)}
+              />
+              <label htmlFor={`${inputId(props)}-${i}`}>{option.label}</label>
+            </div>
+          )
+        }
+      })}
+      <ErrorDescription {...props} />
+    </div>
+  )
+}
+
 const Label: FC<FormFieldProps> = (props) => {
   let { title } = props
   if (!Array.isArray(title)) {
@@ -327,6 +384,7 @@ export type FormFieldProps =
   | FormFieldFileProps
   | FormFieldSelectProps
   | FormFieldSelectSearchProps
+  | FormFieldRadio
 
 const inputId = (props: FormFieldProps) => `form-field-${props.name}`
 const descId = (props: FormFieldProps) => (props.description ? `${inputId(props)}-desc` : undefined)
diff --git a/src/npm-fastui/src/components/index.tsx b/src/npm-fastui/src/components/index.tsx
index 1cd84748..87362056 100644
--- a/src/npm-fastui/src/components/index.tsx
+++ b/src/npm-fastui/src/components/index.tsx
@@ -21,6 +21,7 @@ import {
   FormFieldSelectComp,
   FormFieldSelectSearchComp,
   FormFieldFileComp,
+  FormFieldRadioComp,
 } from './FormField'
 import { ButtonComp } from './button'
 import { LinkComp, LinkRender } from './link'
@@ -136,6 +137,8 @@ export const AnyComp: FC<FastProps> = (props) => {
         return <FormFieldSelectComp {...props} />
       case 'FormFieldSelectSearch':
         return <FormFieldSelectSearchComp {...props} />
+      case 'FormFieldRadio':
+        return <FormFieldRadioComp {...props} />
       case 'Modal':
         return <ModalComp {...props} />
       case 'Table':
diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts
index 461118fe..ddabbd90 100644
--- a/src/npm-fastui/src/models.d.ts
+++ b/src/npm-fastui/src/models.d.ts
@@ -38,6 +38,7 @@ export type FastProps =
   | FormFieldFile
   | FormFieldSelect
   | FormFieldSelectSearch
+  | FormFieldRadio
   | ModelForm
 export type ClassName =
   | string
@@ -324,6 +325,7 @@ export interface Form {
     | FormFieldFile
     | FormFieldSelect
     | FormFieldSelectSearch
+    | FormFieldRadio
   )[]
   type: 'Form'
 }
@@ -422,6 +424,19 @@ export interface FormFieldSelectSearch {
   placeholder?: string
   type: 'FormFieldSelectSearch'
 }
+export interface FormFieldRadio {
+  name: string
+  title: string[] | string
+  required?: boolean
+  error?: string
+  locked?: boolean
+  description?: string
+  displayMode?: 'default' | 'inline'
+  className?: ClassName
+  options: SelectOptions
+  initial?: string
+  type: 'FormFieldRadio'
+}
 export interface ModelForm {
   submitUrl: string
   initial?: {
@@ -441,5 +456,6 @@ export interface ModelForm {
     | FormFieldFile
     | FormFieldSelect
     | FormFieldSelectSearch
+    | FormFieldRadio
   )[]
 }
diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py
index 2969a05f..dff43178 100644
--- a/src/python-fastui/fastui/components/__init__.py
+++ b/src/python-fastui/fastui/components/__init__.py
@@ -21,6 +21,7 @@
     FormFieldBoolean,
     FormFieldFile,
     FormFieldInput,
+    FormFieldRadio,
     FormFieldSelect,
     FormFieldSelectSearch,
     ModelForm,
@@ -65,6 +66,7 @@
     'FormFieldInput',
     'FormFieldSelect',
     'FormFieldSelectSearch',
+    'FormFieldRadio',
 )
 
 
diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py
index 539fb6b5..d6a0d370 100644
--- a/src/python-fastui/fastui/components/forms.py
+++ b/src/python-fastui/fastui/components/forms.py
@@ -73,8 +73,20 @@ class FormFieldSelectSearch(BaseFormField):
     type: _t.Literal['FormFieldSelectSearch'] = 'FormFieldSelectSearch'
 
 
+class FormFieldRadio(BaseFormField):
+    options: forms.SelectOptions
+    initial: _t.Union[str, None] = None
+    type: _t.Literal['FormFieldRadio'] = 'FormFieldRadio'
+
+
 FormField = _t.Union[
-    FormFieldInput, FormFieldTextarea, FormFieldBoolean, FormFieldFile, FormFieldSelect, FormFieldSelectSearch
+    FormFieldInput,
+    FormFieldTextarea,
+    FormFieldBoolean,
+    FormFieldFile,
+    FormFieldSelect,
+    FormFieldSelectSearch,
+    FormFieldRadio,
 ]
 
 
diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py
index eb209e6f..fd271a61 100644
--- a/src/python-fastui/fastui/json_schema.py
+++ b/src/python-fastui/fastui/json_schema.py
@@ -10,6 +10,7 @@
     FormFieldBoolean,
     FormFieldFile,
     FormFieldInput,
+    FormFieldRadio,
     FormFieldSelect,
     FormFieldSelectSearch,
     FormFieldTextarea,
@@ -259,16 +260,26 @@ def special_string_field(
             )
         elif enum := schema.get('enum'):
             enum_labels = schema.get('enum_labels', {})
-            return FormFieldSelect(
-                name=name,
-                title=title,
-                placeholder=schema.get('placeholder'),
-                required=required,
-                multiple=multiple,
-                options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
-                initial=schema.get('default'),
-                description=schema.get('description'),
-            )
+            if schema.get('mode') == 'radio' and not multiple:
+                return FormFieldRadio(
+                    name=name,
+                    title=title,
+                    required=required,
+                    options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
+                    initial=schema.get('default'),
+                    description=schema.get('description'),
+                )
+            else:
+                return FormFieldSelect(
+                    name=name,
+                    title=title,
+                    placeholder=schema.get('placeholder'),
+                    required=required,
+                    multiple=multiple,
+                    options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
+                    initial=schema.get('default'),
+                    description=schema.get('description'),
+                )
         elif search_url := schema.get('search_url'):
             return FormFieldSelectSearch(
                 search_url=search_url,

From 32a3a92e6a06fd243cba4fb35c38561223da6a86 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 15:32:33 +0000
Subject: [PATCH 02/13] some fixes

---
 demo/forms.py                               | 2 +-
 src/npm-fastui/src/components/FormField.tsx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/demo/forms.py b/demo/forms.py
index ef81be3f..26d6f69b 100644
--- a/demo/forms.py
+++ b/demo/forms.py
@@ -123,7 +123,7 @@ class ToolEnum(str, enum.Enum):
 
 class SelectForm(BaseModel):
     select_single: ToolEnum = Field(title='Select Single')
-    select_radio: ToolEnum = Field(title='Select Radio', json_schema_extra={'mode': 'radio'}, description='test ')
+    select_radio: ToolEnum = Field(title='Select Radio', json_schema_extra={'mode': 'radio'})
     select_multiple: list[ToolEnum] = Field(title='Select Multiple')
     search_select_single: str = Field(json_schema_extra={'search_url': '/api/forms/search'})
     search_select_multiple: list[str] = Field(json_schema_extra={'search_url': '/api/forms/search'})
diff --git a/src/npm-fastui/src/components/FormField.tsx b/src/npm-fastui/src/components/FormField.tsx
index d9306065..43c2a689 100644
--- a/src/npm-fastui/src/components/FormField.tsx
+++ b/src/npm-fastui/src/components/FormField.tsx
@@ -312,7 +312,7 @@ interface FormFieldRadioProps extends FormFieldRadio {
 export const FormFieldRadioComp: FC<FormFieldRadioProps> = (props) => {
   const { name, required, locked, options, initial } = props
   const className = useClassName(props)
-  const inputClassName = useClassName(props, { el: 'input' })
+  const inputClassName = useClassName(props, { el: 'radio-option' })
 
   return (
     <div className={className}>

From e060a43f6dc246f8f3abe499be604f979713f791 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 15:34:32 +0000
Subject: [PATCH 03/13] add to bootstrap package

---
 src/npm-fastui-bootstrap/src/index.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/npm-fastui-bootstrap/src/index.tsx b/src/npm-fastui-bootstrap/src/index.tsx
index b2fef51f..86ba3169 100644
--- a/src/npm-fastui-bootstrap/src/index.tsx
+++ b/src/npm-fastui-bootstrap/src/index.tsx
@@ -75,6 +75,7 @@ export const classNameGenerator: ClassNameGenerator = ({
     case 'FormFieldBoolean':
     case 'FormFieldSelect':
     case 'FormFieldSelectSearch':
+    case 'FormFieldRadio':
     case 'FormFieldFile':
       switch (subElement) {
         case 'textarea':

From 50ca37edf241203e5a6cf998dedc239c13427b5f Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 15:43:22 +0000
Subject: [PATCH 04/13] add test for radio

---
 src/python-fastui/tests/test_forms.py | 35 ++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py
index b0919fad..ea62a71d 100644
--- a/src/python-fastui/tests/test_forms.py
+++ b/src/python-fastui/tests/test_forms.py
@@ -1,4 +1,5 @@
 from contextlib import asynccontextmanager
+from enum import Enum
 from io import BytesIO
 from typing import List, Tuple, Union
 
@@ -6,7 +7,7 @@
 from fastapi import HTTPException
 from fastui import components
 from fastui.forms import FormFile, Textarea, fastui_form
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 from starlette.datastructures import FormData, Headers, UploadFile
 from typing_extensions import Annotated
 
@@ -469,3 +470,35 @@ def test_form_textarea_form_fields():
             }
         ],
     }
+
+
+def test_form_radio_form_fields():
+    class RadioChoices(Enum):
+        foo = 'foo'
+        bar = 'bar'
+        baz = 'baz'
+
+    class FormRadioSelection(BaseModel):
+        choice: RadioChoices = Field(..., json_schema_extra={'mode': 'radio'})
+
+    m = components.ModelForm(model=FormRadioSelection, submit_url='/foobar/')
+
+    assert m.model_dump(by_alias=True, exclude_none=True) == {
+        'submitUrl': '/foobar/',
+        'method': 'POST',
+        'type': 'ModelForm',
+        'formFields': [
+            {
+                'name': 'choice',
+                'title': ['RadioChoices'],
+                'required': True,
+                'locked': False,
+                'type': 'FormFieldRadio',
+                'options': [
+                    {'label': 'Foo', 'value': 'foo'},
+                    {'label': 'Bar', 'value': 'bar'},
+                    {'label': 'Baz', 'value': 'baz'},
+                ],
+            }
+        ],
+    }

From 9929abed2b5172686e2e34c7fa2898800c8816ea Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 16:06:15 +0000
Subject: [PATCH 05/13] add tests for select choices forms

---
 src/python-fastui/tests/test_forms.py | 51 +++++++++++++++++++++++----
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py
index ea62a71d..0e78238c 100644
--- a/src/python-fastui/tests/test_forms.py
+++ b/src/python-fastui/tests/test_forms.py
@@ -472,15 +472,17 @@ def test_form_textarea_form_fields():
     }
 
 
-def test_form_radio_form_fields():
-    class RadioChoices(Enum):
-        foo = 'foo'
-        bar = 'bar'
-        baz = 'baz'
+class Choices(Enum):
+    foo = 'foo'
+    bar = 'bar'
+    baz = 'baz'
+
 
-    class FormRadioSelection(BaseModel):
-        choice: RadioChoices = Field(..., json_schema_extra={'mode': 'radio'})
+class FormRadioSelection(BaseModel):
+    choice: Choices = Field(..., json_schema_extra={'mode': 'radio'})
 
+
+def test_form_radio_form_fields():
     m = components.ModelForm(model=FormRadioSelection, submit_url='/foobar/')
 
     assert m.model_dump(by_alias=True, exclude_none=True) == {
@@ -502,3 +504,38 @@ class FormRadioSelection(BaseModel):
             }
         ],
     }
+
+
+@pytest.mark.parametrize('multiple', [True, False])
+def test_form_from_select(multiple: bool):
+    if multiple:
+
+        class FormSelect(BaseModel):
+            choice: list[Choices]
+    else:
+
+        class FormSelect(BaseModel):
+            choice: Choices
+
+    m = components.ModelForm(model=FormSelect, submit_url='/foobar/')
+
+    assert m.model_dump(by_alias=True, exclude_none=True) == {
+        'submitUrl': '/foobar/',
+        'method': 'POST',
+        'type': 'ModelForm',
+        'formFields': [
+            {
+                'name': 'choice',
+                'multiple': multiple,
+                'title': ['Choice'] if multiple else ['Choices'],
+                'required': True,
+                'locked': False,
+                'type': 'FormFieldSelect',
+                'options': [
+                    {'label': 'Foo', 'value': 'foo'},
+                    {'label': 'Bar', 'value': 'bar'},
+                    {'label': 'Baz', 'value': 'baz'},
+                ],
+            }
+        ],
+    }

From 1bf95803d3cc863cd34a35237ea769e9c4b9b577 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 16:13:35 +0000
Subject: [PATCH 06/13] fix tests

---
 src/python-fastui/tests/test_forms.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py
index 0e78238c..7615c59c 100644
--- a/src/python-fastui/tests/test_forms.py
+++ b/src/python-fastui/tests/test_forms.py
@@ -492,7 +492,7 @@ def test_form_radio_form_fields():
         'formFields': [
             {
                 'name': 'choice',
-                'title': ['RadioChoices'],
+                'title': ['Choices'],
                 'required': True,
                 'locked': False,
                 'type': 'FormFieldRadio',
@@ -511,7 +511,7 @@ def test_form_from_select(multiple: bool):
     if multiple:
 
         class FormSelect(BaseModel):
-            choice: list[Choices]
+            choices: list[Choices]
     else:
 
         class FormSelect(BaseModel):
@@ -525,9 +525,9 @@ class FormSelect(BaseModel):
         'type': 'ModelForm',
         'formFields': [
             {
-                'name': 'choice',
+                'name': 'choices' if multiple else 'choice',
                 'multiple': multiple,
-                'title': ['Choice'] if multiple else ['Choices'],
+                'title': ['Choices'],
                 'required': True,
                 'locked': False,
                 'type': 'FormFieldSelect',

From f63fe009fce54e807e1456bce996e5554cb93943 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Fri, 9 Feb 2024 16:16:17 +0000
Subject: [PATCH 07/13] type needs to be 3.8 compat

---
 src/python-fastui/tests/test_forms.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py
index 7615c59c..9451647e 100644
--- a/src/python-fastui/tests/test_forms.py
+++ b/src/python-fastui/tests/test_forms.py
@@ -511,7 +511,7 @@ def test_form_from_select(multiple: bool):
     if multiple:
 
         class FormSelect(BaseModel):
-            choices: list[Choices]
+            choices: List[Choices]
     else:
 
         class FormSelect(BaseModel):

From b66f657b90c00207dc9e57ee584a1ed3ec44ad71 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 16:55:50 +0000
Subject: [PATCH 08/13] make option logic computation only once

---
 src/python-fastui/fastui/json_schema.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py
index fd271a61..d21c0f83 100644
--- a/src/python-fastui/fastui/json_schema.py
+++ b/src/python-fastui/fastui/json_schema.py
@@ -261,11 +261,12 @@ def special_string_field(
         elif enum := schema.get('enum'):
             enum_labels = schema.get('enum_labels', {})
             if schema.get('mode') == 'radio' and not multiple:
+            options = [SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum]
                 return FormFieldRadio(
                     name=name,
                     title=title,
                     required=required,
-                    options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
+                    options=options,
                     initial=schema.get('default'),
                     description=schema.get('description'),
                 )
@@ -276,7 +277,7 @@ def special_string_field(
                     placeholder=schema.get('placeholder'),
                     required=required,
                     multiple=multiple,
-                    options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
+                    options=options,
                     initial=schema.get('default'),
                     description=schema.get('description'),
                 )

From a6875b14c4a3e11e76b9dbfcf628c6071dae61f7 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 16:56:08 +0000
Subject: [PATCH 09/13] raise if multiple and radio

---
 src/python-fastui/fastui/json_schema.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py
index d21c0f83..b9fe6200 100644
--- a/src/python-fastui/fastui/json_schema.py
+++ b/src/python-fastui/fastui/json_schema.py
@@ -260,8 +260,10 @@ def special_string_field(
             )
         elif enum := schema.get('enum'):
             enum_labels = schema.get('enum_labels', {})
-            if schema.get('mode') == 'radio' and not multiple:
             options = [SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum]
+            if schema.get('mode') == 'radio' and multiple:
+                raise ValueError('Radio buttons are not supported for multiple choice fields')
+            elif schema.get('mode') == 'radio' and not multiple:
                 return FormFieldRadio(
                     name=name,
                     title=title,

From 52dc4595f241645fb45aa2d54a27203f8c0fd154 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 16:56:36 +0000
Subject: [PATCH 10/13] remove duplication in react component

---
 src/npm-fastui/src/components/FormField.tsx | 76 +++++++++------------
 1 file changed, 34 insertions(+), 42 deletions(-)

diff --git a/src/npm-fastui/src/components/FormField.tsx b/src/npm-fastui/src/components/FormField.tsx
index 43c2a689..d916f77b 100644
--- a/src/npm-fastui/src/components/FormField.tsx
+++ b/src/npm-fastui/src/components/FormField.tsx
@@ -310,57 +310,49 @@ interface FormFieldRadioProps extends FormFieldRadio {
 }
 
 export const FormFieldRadioComp: FC<FormFieldRadioProps> = (props) => {
-  const { name, required, locked, options, initial } = props
-  const className = useClassName(props)
-  const inputClassName = useClassName(props, { el: 'radio-option' })
+  const { name, required, locked, options, initial } = props;
+  const className = useClassName(props);
+  const inputClassName = useClassName(props, { el: 'radio-input' });
+  const labelClassName = useClassName(props, { el: 'radio-label' });
+
+  const renderRadioInput = (option: SelectOption, i: number, j: number | null = null) => {
+    const index = j !== null ? `${i}-${j}` : `${i}`;
+    return (
+      <div key={index}>
+        <input
+          type="radio"
+          id={`${inputId(props)}-${index}`}
+          className={inputClassName}
+          name={name}
+          value={option.value}
+          defaultChecked={option.value === initial}
+          required={required}
+          disabled={locked}
+          aria-describedby={descId(props)}
+        />
+        <label htmlFor={`${inputId(props)}-${index}`} className={labelClassName}>{option.label}</label>
+      </div>
+    );
+  };
+
 
   return (
     <div className={className}>
-      <Label {...props} />
+      < Label {...props}/>
       {options.map((option, i) => {
-        if ('options' in option) {
-          // option is a SelectGroup
-          return option.options.map((subOption, j) => (
-            <div key={`${i}-${j}`}>
-              <input
-                type="radio"
-                id={`${inputId(props)}-${i}-${j}`}
-                className={inputClassName}
-                name={name}
-                value={subOption.value}
-                defaultChecked={subOption.value === initial}
-                required={required}
-                disabled={locked}
-                aria-describedby={descId(props)}
-              />
-              <label htmlFor={`${inputId(props)}-${i}-${j}`}>{subOption.label}</label>
-            </div>
-          ))
+        if ('options' in option && option.options) {
+          return option.options.map((subOption, j) =>
+            renderRadioInput(subOption, i, j)
+          );
         } else {
-          // option is a SelectOption
-          return (
-            <div key={i}>
-              <input
-                type="radio"
-                id={`${inputId(props)}-${i}`}
-                className={inputClassName}
-                name={name}
-                value={option.value}
-                defaultChecked={option.value === initial}
-                required={required}
-                disabled={locked}
-                aria-describedby={descId(props)}
-              />
-              <label htmlFor={`${inputId(props)}-${i}`}>{option.label}</label>
-            </div>
-          )
+          option = option as SelectOption;
+          return renderRadioInput(option, i, null);
         }
       })}
       <ErrorDescription {...props} />
     </div>
-  )
-}
-
+  );
+};
 const Label: FC<FormFieldProps> = (props) => {
   let { title } = props
   if (!Array.isArray(title)) {

From 391e41488449f9c340b404146f27f23f2d5bfaab Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 16:57:19 +0000
Subject: [PATCH 11/13] add bootstrap classes for radio buttons

---
 src/npm-fastui-bootstrap/src/index.tsx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/npm-fastui-bootstrap/src/index.tsx b/src/npm-fastui-bootstrap/src/index.tsx
index 86ba3169..2f8b9d4c 100644
--- a/src/npm-fastui-bootstrap/src/index.tsx
+++ b/src/npm-fastui-bootstrap/src/index.tsx
@@ -99,6 +99,10 @@ export const classNameGenerator: ClassNameGenerator = ({
           return 'invalid-feedback'
         case 'description':
           return 'form-text'
+        case 'radio-input':
+          return 'form-check-input'
+        case 'radio-label':
+          return 'form-check-label'
         default:
           return {
             'mb-3': true,

From aee5458af05141c44e0eb1d378d06fd74f8e93fb Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 17:15:31 +0000
Subject: [PATCH 12/13] lint js

---
 src/npm-fastui/src/components/FormField.tsx | 33 ++++++++++-----------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/src/npm-fastui/src/components/FormField.tsx b/src/npm-fastui/src/components/FormField.tsx
index d916f77b..8d3dffd3 100644
--- a/src/npm-fastui/src/components/FormField.tsx
+++ b/src/npm-fastui/src/components/FormField.tsx
@@ -310,13 +310,13 @@ interface FormFieldRadioProps extends FormFieldRadio {
 }
 
 export const FormFieldRadioComp: FC<FormFieldRadioProps> = (props) => {
-  const { name, required, locked, options, initial } = props;
-  const className = useClassName(props);
-  const inputClassName = useClassName(props, { el: 'radio-input' });
-  const labelClassName = useClassName(props, { el: 'radio-label' });
+  const { name, required, locked, options, initial } = props
+  const className = useClassName(props)
+  const inputClassName = useClassName(props, { el: 'radio-input' })
+  const labelClassName = useClassName(props, { el: 'radio-label' })
 
   const renderRadioInput = (option: SelectOption, i: number, j: number | null = null) => {
-    const index = j !== null ? `${i}-${j}` : `${i}`;
+    const index = j !== null ? `${i}-${j}` : `${i}`
     return (
       <div key={index}>
         <input
@@ -330,29 +330,28 @@ export const FormFieldRadioComp: FC<FormFieldRadioProps> = (props) => {
           disabled={locked}
           aria-describedby={descId(props)}
         />
-        <label htmlFor={`${inputId(props)}-${index}`} className={labelClassName}>{option.label}</label>
+        <label htmlFor={`${inputId(props)}-${index}`} className={labelClassName}>
+          {option.label}
+        </label>
       </div>
-    );
-  };
-
+    )
+  }
 
   return (
     <div className={className}>
-      < Label {...props}/>
+      <Label {...props} />
       {options.map((option, i) => {
         if ('options' in option && option.options) {
-          return option.options.map((subOption, j) =>
-            renderRadioInput(subOption, i, j)
-          );
+          return option.options.map((subOption, j) => renderRadioInput(subOption, i, j))
         } else {
-          option = option as SelectOption;
-          return renderRadioInput(option, i, null);
+          option = option as SelectOption
+          return renderRadioInput(option, i, null)
         }
       })}
       <ErrorDescription {...props} />
     </div>
-  );
-};
+  )
+}
 const Label: FC<FormFieldProps> = (props) => {
   let { title } = props
   if (!Array.isArray(title)) {

From bce110e79933686bf61e223800108ea8071204d3 Mon Sep 17 00:00:00 2001
From: tim <timothee.heller@habitat.energy>
Date: Mon, 12 Feb 2024 17:31:07 +0000
Subject: [PATCH 13/13] fix demo by having radio below multiple

---
 demo/forms.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/demo/forms.py b/demo/forms.py
index 26d6f69b..07fb678f 100644
--- a/demo/forms.py
+++ b/demo/forms.py
@@ -123,8 +123,8 @@ class ToolEnum(str, enum.Enum):
 
 class SelectForm(BaseModel):
     select_single: ToolEnum = Field(title='Select Single')
-    select_radio: ToolEnum = Field(title='Select Radio', json_schema_extra={'mode': 'radio'})
     select_multiple: list[ToolEnum] = Field(title='Select Multiple')
+    select_radio: ToolEnum = Field(title='Select Radio', json_schema_extra={'mode': 'radio'})
     search_select_single: str = Field(json_schema_extra={'search_url': '/api/forms/search'})
     search_select_multiple: list[str] = Field(json_schema_extra={'search_url': '/api/forms/search'})