diff --git a/demo/components_list.py b/demo/components_list.py index e7ecd0c5..8132fca1 100644 --- a/demo/components_list.py +++ b/demo/components_list.py @@ -282,6 +282,20 @@ class Delivery(BaseModel): ], class_name='border-top mt-3 pt-1', ), + c.Div( + components=[ + c.Heading(text='Button and Toast', level=2), + c.Paragraph(text='The button below will open a toast.'), + c.Button(text='Show Toast', on_click=PageEvent(name='show-toast')), + c.Toast( + title='Toast', + body=[c.Paragraph(text='This is a toast.')], + open_trigger=PageEvent(name='show-toast'), + position='bottom-end', + ), + ], + class_name='border-top mt-3 pt-1', + ), title='Components', ) diff --git a/demo/main.py b/demo/main.py index cdba7e22..00cee5c7 100644 --- a/demo/main.py +++ b/demo/main.py @@ -34,6 +34,7 @@ def api_index() -> list[AnyComponent]: * `Image` - example [here](/components#image) * `Iframe` - example [here](/components#iframe) * `Video` - example [here](/components#video) +* `Toast` - example [here](/components#toast) * `Table` — See [cities table](/table/cities) and [users table](/table/users) * `Pagination` — See the bottom of the [cities table](/table/cities) * `ModelForm` — See [forms](/forms/login) diff --git a/src/npm-fastui-bootstrap/src/index.tsx b/src/npm-fastui-bootstrap/src/index.tsx index 2a9f01e5..3341cb6a 100644 --- a/src/npm-fastui-bootstrap/src/index.tsx +++ b/src/npm-fastui-bootstrap/src/index.tsx @@ -6,6 +6,7 @@ import { Modal } from './modal' import { Navbar } from './navbar' import { Pagination } from './pagination' import { Footer } from './footer' +import { Toast } from './toast' export const customRender: CustomRender = (props) => { const { type } = props @@ -18,6 +19,8 @@ export const customRender: CustomRender = (props) => { return () => case 'Pagination': return () => + case 'Toast': + return () => } } diff --git a/src/npm-fastui-bootstrap/src/toast.tsx b/src/npm-fastui-bootstrap/src/toast.tsx new file mode 100644 index 00000000..989d6949 --- /dev/null +++ b/src/npm-fastui-bootstrap/src/toast.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react' +import { components, events, renderClassName, EventContextProvider, models } from 'fastui' +import BootstrapToast from 'react-bootstrap/Toast' +import BootstrapToastContainer from 'react-bootstrap/ToastContainer' + +export const Toast: FC = (props) => { + const { className, title, body, position, openTrigger, openContext } = props + + const { eventContext, fireId, clear } = events.usePageEventListen(openTrigger, openContext) + + return ( + + + + + {title} + + + + + + + + ) +} diff --git a/src/npm-fastui/src/components/index.tsx b/src/npm-fastui/src/components/index.tsx index d0398a75..d82b5754 100644 --- a/src/npm-fastui/src/components/index.tsx +++ b/src/npm-fastui/src/components/index.tsx @@ -41,6 +41,7 @@ import { FireEventComp } from './FireEvent' import { ErrorComp } from './error' import { SpinnerComp } from './spinner' import { CustomComp } from './Custom' +import { ToastComp } from './toast' // TODO some better way to export components export { @@ -77,6 +78,7 @@ export { SpinnerComp, CustomComp, LinkRender, + ToastComp, } export type FastClassNameProps = Exclude @@ -166,6 +168,8 @@ export const AnyComp: FC = (props) => { return case 'Custom': return + case 'Toast': + return default: unreachable('Unexpected component type', type, props) return diff --git a/src/npm-fastui/src/components/toast.tsx b/src/npm-fastui/src/components/toast.tsx new file mode 100644 index 00000000..01eb1658 --- /dev/null +++ b/src/npm-fastui/src/components/toast.tsx @@ -0,0 +1,22 @@ +import { FC, useEffect } from 'react' + +import type { Toast } from '../models' + +import { usePageEventListen } from '../events' + +export const ToastComp: FC = (props) => { + const { title, openTrigger, openContext } = props + + const { fireId, clear } = usePageEventListen(openTrigger, openContext) + + useEffect(() => { + if (fireId) { + setTimeout(() => { + alert(`${title}\n\nNote: modals are not implemented by pure FastUI, implement a component for 'ToastProps'.`) + clear() + }) + } + }, [fireId, title, clear]) + + return <> +} diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts index a0ca91cd..9877b2ca 100644 --- a/src/npm-fastui/src/models.d.ts +++ b/src/npm-fastui/src/models.d.ts @@ -40,6 +40,7 @@ export type FastProps = | FormFieldSelect | FormFieldSelectSearch | ModelForm + | Toast export type ClassName = | string | ClassName[] @@ -460,3 +461,21 @@ export interface ModelForm { | FormFieldSelectSearch )[] } +export interface Toast { + title: string + body: FastProps[] + position?: + | 'top-start' + | 'top-center' + | 'top-end' + | 'middle-start' + | 'middle-center' + | 'middle-end' + | 'bottom-start' + | 'bottom-center' + | 'bottom-end' + openTrigger?: PageEvent + openContext?: ContextType + className?: ClassName + type: 'Toast' +} diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index f2d3f423..6594863e 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -303,6 +303,29 @@ class Spinner(_p.BaseModel, extra='forbid'): type: _t.Literal['Spinner'] = 'Spinner' +class Toast(_p.BaseModel, extra='forbid'): + title: str + body: '_t.List[AnyComponent]' + position: _t.Union[ + _t.Literal[ + 'top-start', + 'top-center', + 'top-end', + 'middle-start', + 'middle-center', + 'middle-end', + 'bottom-start', + 'bottom-center', + 'bottom-end', + ], + None, + ] = None + open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None, serialization_alias='openTrigger') + open_context: _t.Union[events.ContextType, None] = _p.Field(default=None, serialization_alias='openContext') + class_name: _class_name.ClassNameField = None + type: _t.Literal['Toast'] = 'Toast' + + class Custom(_p.BaseModel, extra='forbid'): data: _types.JsonData sub_type: str = _p.Field(serialization_alias='subType') @@ -343,6 +366,7 @@ class Custom(_p.BaseModel, extra='forbid'): Form, FormField, ModelForm, + Toast, ], _p.Field(discriminator='type'), ]