Skip to content

Commit

Permalink
Modal prompt (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Feb 12, 2024
1 parent f26041e commit dc860f7
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 25 deletions.
62 changes: 62 additions & 0 deletions demo/components_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,56 @@ class Delivery(BaseModel):
],
class_name='border-top mt-3 pt-1',
),
c.Div(
components=[
c.Heading(text='Modal Form / Confirm prompt', level=2),
c.Markdown(text='The button below will open a modal with a form.'),
c.Button(text='Show Modal Form', on_click=PageEvent(name='modal-form')),
c.Modal(
title='Modal Form',
body=[
c.Paragraph(text='Form inside a modal!'),
c.Form(
form_fields=[
c.FormFieldInput(name='foobar', title='Foobar', required=True),
],
submit_url='/api/components/modal-form',
footer=[],
submit_trigger=PageEvent(name='modal-form-submit'),
),
],
footer=[
c.Button(
text='Cancel', named_style='secondary', on_click=PageEvent(name='modal-form', clear=True)
),
c.Button(text='Submit', on_click=PageEvent(name='modal-form-submit')),
],
open_trigger=PageEvent(name='modal-form'),
),
c.Button(text='Show Modal Prompt', on_click=PageEvent(name='modal-prompt'), class_name='+ ms-2'),
c.Modal(
title='Form Prompt',
body=[
c.Paragraph(text='Are you sure you want to do whatever?'),
c.Form(
form_fields=[],
submit_url='/api/components/modal-prompt',
loading=[c.Spinner(text='Okay, good luck...')],
footer=[],
submit_trigger=PageEvent(name='modal-form-submit'),
),
],
footer=[
c.Button(
text='Cancel', named_style='secondary', on_click=PageEvent(name='modal-prompt', clear=True)
),
c.Button(text='Submit', on_click=PageEvent(name='modal-form-submit')),
],
open_trigger=PageEvent(name='modal-prompt'),
),
],
class_name='border-top mt-3 pt-1',
),
c.Div(
components=[
c.Heading(text='Server Load', level=2),
Expand Down Expand Up @@ -240,3 +290,15 @@ class Delivery(BaseModel):
async def modal_view() -> list[AnyComponent]:
await asyncio.sleep(0.5)
return [c.Paragraph(text='This is some dynamic content. Open devtools to see me being fetched from the server.')]


@router.post('/modal-form', response_model=FastUI, response_model_exclude_none=True)
async def modal_form_submit() -> list[AnyComponent]:
await asyncio.sleep(0.5)
return [c.FireEvent(event=PageEvent(name='modal-form', clear=True))]


@router.post('/modal-prompt', response_model=FastUI, response_model_exclude_none=True)
async def modal_prompt_submit() -> list[AnyComponent]:
await asyncio.sleep(0.5)
return [c.FireEvent(event=PageEvent(name='modal-prompt', clear=True))]
6 changes: 3 additions & 3 deletions demo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,19 @@ def form_content(kind: FormKind):
return [
c.Heading(text='Login Form', level=2),
c.Paragraph(text='Simple login form with email and password.'),
c.ModelForm(model=LoginForm, submit_url='/api/forms/login'),
c.ModelForm(model=LoginForm, display_mode='page', submit_url='/api/forms/login'),
]
case 'select':
return [
c.Heading(text='Select Form', level=2),
c.Paragraph(text='Form showing different ways of doing select.'),
c.ModelForm(model=SelectForm, submit_url='/api/forms/select'),
c.ModelForm(model=SelectForm, display_mode='page', submit_url='/api/forms/select'),
]
case 'big':
return [
c.Heading(text='Large Form', level=2),
c.Paragraph(text='Form with a lot of fields.'),
c.ModelForm(model=BigModel, submit_url='/api/forms/big'),
c.ModelForm(model=BigModel, display_mode='page', submit_url='/api/forms/big'),
]
case _:
raise ValueError(f'Invalid kind {kind!r}')
Expand Down
4 changes: 2 additions & 2 deletions src/npm-fastui-bootstrap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pydantic/fastui-bootstrap",
"version": "0.0.20",
"version": "0.0.21",
"description": "Boostrap renderer for FastUI",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -29,6 +29,6 @@
"sass": "^1.69.5"
},
"peerDependencies": {
"@pydantic/fastui": "0.0.20"
"@pydantic/fastui": "0.0.21"
}
}
4 changes: 3 additions & 1 deletion src/npm-fastui-bootstrap/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ export const classNameGenerator: ClassNameGenerator = ({
default:
return 'row row-cols-lg-4 align-items-center justify-content-end'
}
} else {
} else if (props.displayMode === 'page') {
switch (subElement) {
case 'form-container':
return 'row justify-content-center'
default:
return 'col-md-4'
}
} else {
break
}
case 'FormFieldInput':
case 'FormFieldTextarea':
Expand Down
2 changes: 1 addition & 1 deletion src/npm-fastui-prebuilt/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pydantic/fastui-prebuilt",
"version": "0.0.20",
"version": "0.0.21",
"description": "Pre-built files for FastUI",
"main": "dist/index.html",
"type": "module",
Expand Down
6 changes: 5 additions & 1 deletion src/npm-fastui-prebuilt/src/main.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$primary: black;
$secondary: #666;
$secondary: white;
$link-color: #0d6efd; // bootstrap primary

@import 'bootstrap/scss/bootstrap';
Expand Down Expand Up @@ -121,3 +121,7 @@ h6 {
position: relative;
top: 60px;
}

.btn-secondary {
--bs-btn-border-color: #dee2e6;
}
2 changes: 1 addition & 1 deletion src/npm-fastui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pydantic/fastui",
"version": "0.0.20",
"version": "0.0.21",
"description": "Build better UIs faster.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
27 changes: 16 additions & 11 deletions src/npm-fastui/src/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ import { FormFieldProps } from './FormField'

export const FormComp: FC<Form | ModelForm> = (props) => {
const formRef = useRef<HTMLFormElement>(null)
const { formFields, initial, submitUrl, method, footer, displayMode, submitOnChange, submitTrigger } = props
const {
formFields,
initial,
submitUrl,
method,
footer,
displayMode,
submitOnChange,
submitTrigger,
loading: loadingComponents,
} = props

// mostly equivalent to `<input disabled`
const [locked, setLocked] = useState(false)
Expand Down Expand Up @@ -84,20 +94,14 @@ export const FormComp: FC<Form | ModelForm> = (props) => {
}

const onChange = useCallback(() => {
if (submitOnChange && formRef.current) {
const formData = new FormData(formRef.current)
submit(formData)
}
}, [submitOnChange, submit])
submitOnChange && formRef.current && formRef.current.requestSubmit()
}, [submitOnChange])

const { fireId } = usePageEventListen(submitTrigger)

useEffect(() => {
if (fireId && formRef.current) {
const formData = new FormData(formRef.current)
submit(formData)
}
}, [fireId, submit])
fireId && formRef.current && formRef.current.requestSubmit()
}, [fireId])

const fieldProps: FormFieldProps[] = formFields.map((formField) => {
const f = {
Expand Down Expand Up @@ -127,6 +131,7 @@ export const FormComp: FC<Form | ModelForm> = (props) => {
return (
<div className={containerClassName}>
<form ref={formRef} className={formClassName} onSubmit={onSubmit}>
{locked && loadingComponents && <AnyCompList propsList={loadingComponents} />}
<AnyCompList propsList={fieldProps} />
{error ? <div>Error: {error}</div> : null}
<Footer footer={footer} />
Expand Down
5 changes: 4 additions & 1 deletion src/npm-fastui/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export function usePageEventListen(event?: PageEvent, initialContext: ContextTyp
return {
eventContext,
fireId,
clear: useCallback(() => setEventContext(null), []),
clear: useCallback(() => {
setEventContext(null)
setFireId(null)
}, []),
}
}
6 changes: 4 additions & 2 deletions src/npm-fastui/src/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ export interface Form {
[k: string]: JsonData
}
method?: 'POST' | 'GOTO' | 'GET'
displayMode?: 'default' | 'inline'
displayMode?: 'default' | 'page' | 'inline'
submitOnChange?: boolean
submitTrigger?: PageEvent
loading?: FastProps[]
footer?: FastProps[]
className?: ClassName
formFields: (
Expand Down Expand Up @@ -436,9 +437,10 @@ export interface ModelForm {
[k: string]: JsonData
}
method?: 'POST' | 'GOTO' | 'GET'
displayMode?: 'default' | 'inline'
displayMode?: 'default' | 'page' | 'inline'
submitOnChange?: boolean
submitTrigger?: PageEvent
loading?: FastProps[]
footer?: FastProps[]
className?: ClassName
type: 'ModelForm'
Expand Down
2 changes: 1 addition & 1 deletion src/python-fastui/fastui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def coerce_to_list(cls, v):
return [v]


_PREBUILT_VERSION = '0.0.20'
_PREBUILT_VERSION = '0.0.21'
_PREBUILT_CDN_URL = f'https://cdn.jsdelivr.net/npm/@pydantic/fastui-prebuilt@{_PREBUILT_VERSION}/dist/assets'


Expand Down
3 changes: 2 additions & 1 deletion src/python-fastui/fastui/components/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ class BaseForm(pydantic.BaseModel, ABC, defer_build=True, extra='forbid'):
submit_url: str = pydantic.Field(serialization_alias='submitUrl')
initial: _t.Union[_t.Dict[str, _types.JsonData], None] = None
method: _t.Literal['POST', 'GOTO', 'GET'] = 'POST'
display_mode: _t.Union[_t.Literal['default', 'inline'], None] = pydantic.Field(
display_mode: _t.Union[_t.Literal['default', 'page', 'inline'], None] = pydantic.Field(
default=None, serialization_alias='displayMode'
)
submit_on_change: _t.Union[bool, None] = pydantic.Field(default=None, serialization_alias='submitOnChange')
submit_trigger: _t.Union[events.PageEvent, None] = pydantic.Field(default=None, serialization_alias='submitTrigger')
loading: '_t.Union[_t.List[AnyComponent], None]' = None
footer: '_t.Union[_t.List[AnyComponent], None]' = None
class_name: _class_name.ClassNameField = None

Expand Down

0 comments on commit dc860f7

Please sign in to comment.