diff --git a/demo/components_list.py b/demo/components_list.py index 25c9ecf9..a7ab8951 100644 --- a/demo/components_list.py +++ b/demo/components_list.py @@ -192,6 +192,19 @@ class Delivery(BaseModel): ], class_name='border-top mt-3 pt-1', ), + c.Div( + components=[ + c.Heading(text='Spinner', level=2), + c.Paragraph( + text=( + 'A component displayed while waiting for content to load, ' + 'this is also used automatically while loading server content.' + ) + ), + c.Spinner(text='Content incoming...'), + ], + class_name='border-top mt-3 pt-1', + ), c.Div( components=[ c.Heading(text='Video', level=2), diff --git a/src/npm-fastui-bootstrap/src/index.tsx b/src/npm-fastui-bootstrap/src/index.tsx index b394a3c5..05567799 100644 --- a/src/npm-fastui-bootstrap/src/index.tsx +++ b/src/npm-fastui-bootstrap/src/index.tsx @@ -150,5 +150,13 @@ export const classNameGenerator: ClassNameGenerator = ({ } else { return 'error-alert alert alert-danger m-3' } + case 'Spinner': + if (subElement === 'text') { + return 'd-flex justify-content-center mb-2' + } else if (subElement === 'animation') { + return 'd-flex justify-content-center' + } else { + return 'my-4' + } } } diff --git a/src/npm-fastui-prebuilt/src/App.tsx b/src/npm-fastui-prebuilt/src/App.tsx index 7fb00135..c43cfdd4 100644 --- a/src/npm-fastui-prebuilt/src/App.tsx +++ b/src/npm-fastui-prebuilt/src/App.tsx @@ -11,7 +11,6 @@ export default function App() { classNameGenerator={bootstrap.classNameGenerator} customRender={customRender} NotFound={NotFound} - Spinner={Spinner} Transition={Transition} /> ) @@ -30,12 +29,6 @@ const NotFound = ({ url }: { url: string }) => ( ) -const Spinner = () => ( -
-
-
-) - const Transition: FC<{ children: ReactNode; transitioning: boolean }> = ({ children, transitioning }) => ( <>
diff --git a/src/npm-fastui-prebuilt/src/main.scss b/src/npm-fastui-prebuilt/src/main.scss index b099f9c8..a4d5c8cf 100644 --- a/src/npm-fastui-prebuilt/src/main.scss +++ b/src/npm-fastui-prebuilt/src/main.scss @@ -74,17 +74,17 @@ h6 { } // custom spinner from https://cssloaders.github.io/ - -.spinner, -.spinner:before, -.spinner:after { +.fastui-spinner-animation, +.fastui-spinner-animation:before, +.fastui-spinner-animation:after { border-radius: 50%; width: 2.5em; height: 2.5em; animation-fill-mode: both; - animation: dots 1.8s infinite ease-in-out; + animation: spinner-dots 1.8s infinite ease-in-out; } -.spinner { +.fastui-spinner-animation { + top: -2.5em; color: var(--bs-dark); font-size: 7px; position: relative; @@ -105,8 +105,7 @@ h6 { left: 3.5em; } } - -@keyframes dots { +@keyframes spinner-dots { 0%, 80%, 100% { diff --git a/src/npm-fastui/src/Defaults.tsx b/src/npm-fastui/src/Defaults.tsx index 3b636da5..a8469a61 100644 --- a/src/npm-fastui/src/Defaults.tsx +++ b/src/npm-fastui/src/Defaults.tsx @@ -1,7 +1,5 @@ import { FC, ReactNode } from 'react' -export const DefaultSpinner: FC = () =>
loading...
- export const DefaultNotFound: FC<{ url: string }> = ({ url }) =>
Page not found: {url}
// default here does nothing diff --git a/src/npm-fastui/src/components/ServerLoad.tsx b/src/npm-fastui/src/components/ServerLoad.tsx index defda042..0199cb3d 100644 --- a/src/npm-fastui/src/components/ServerLoad.tsx +++ b/src/npm-fastui/src/components/ServerLoad.tsx @@ -4,7 +4,7 @@ import type { ServerLoad, PageEvent, FastProps } from '../models' import { ErrorContext } from '../hooks/error' import { useRequest, useSSE } from '../tools' -import { DefaultSpinner, DefaultNotFound, DefaultTransition } from '../Defaults' +import { DefaultNotFound, DefaultTransition } from '../Defaults' import { ConfigContext } from '../hooks/config' import { usePageEventListen } from '../events' import { EventContextProvider, useEventContext } from '../hooks/eventContext' @@ -12,6 +12,8 @@ import { LocationContext } from '../hooks/locationContext' import { AnyCompList } from './index' +import { SpinnerComp } from './spinner' + export const ServerLoadComp: FC = ({ path, components, loadTrigger, sse }) => { if (components) { return @@ -103,8 +105,7 @@ const Render: FC<{ propsList: FastProps[] | null; notFoundUrl?: string; transiti transitioning, }) => { const { error } = useContext(ErrorContext) - const { Spinner, NotFound, Transition } = useContext(ConfigContext) - const SpinnerComp = Spinner ?? DefaultSpinner + const { NotFound, Transition } = useContext(ConfigContext) const NotFoundComp = NotFound ?? DefaultNotFound const TransitionComp = Transition ?? DefaultTransition @@ -114,7 +115,7 @@ const Render: FC<{ propsList: FastProps[] | null; notFoundUrl?: string; transiti if (error) { return <> } else { - return + return } } else { return ( diff --git a/src/npm-fastui/src/components/index.tsx b/src/npm-fastui/src/components/index.tsx index 1cd84748..d0398a75 100644 --- a/src/npm-fastui/src/components/index.tsx +++ b/src/npm-fastui/src/components/index.tsx @@ -39,6 +39,7 @@ import { IframeComp } from './Iframe' import { VideoComp } from './video' import { FireEventComp } from './FireEvent' import { ErrorComp } from './error' +import { SpinnerComp } from './spinner' import { CustomComp } from './Custom' // TODO some better way to export components @@ -73,6 +74,7 @@ export { VideoComp, FireEventComp, ErrorComp, + SpinnerComp, CustomComp, LinkRender, } @@ -160,6 +162,8 @@ export const AnyComp: FC = (props) => { return case 'Error': return + case 'Spinner': + return case 'Custom': return default: diff --git a/src/npm-fastui/src/components/spinner.tsx b/src/npm-fastui/src/components/spinner.tsx new file mode 100644 index 00000000..687a6b71 --- /dev/null +++ b/src/npm-fastui/src/components/spinner.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react' + +import type { Spinner } from '../models' + +import { useClassName } from '../hooks/className' + +export const SpinnerComp: FC = (props) => { + const { text } = props + + return ( +
+
{text}
+
+
loading...
+
+
+ ) +} diff --git a/src/npm-fastui/src/index.tsx b/src/npm-fastui/src/index.tsx index 6bb8db40..9eed89b7 100644 --- a/src/npm-fastui/src/index.tsx +++ b/src/npm-fastui/src/index.tsx @@ -24,7 +24,6 @@ export interface FastUIProps { APIPathMode?: 'append' | 'query' // start of the path to remove from the URL before making a request to the API APIPathStrip?: string - Spinner?: FC NotFound?: FC<{ url: string }> Transition?: FC<{ children: ReactNode; transitioning: boolean }> classNameGenerator?: ClassNameGenerator diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts index f7eb10bd..4cac44fb 100644 --- a/src/npm-fastui/src/models.d.ts +++ b/src/npm-fastui/src/models.d.ts @@ -26,6 +26,7 @@ export type FastProps = | Video | FireEvent | Error + | Spinner | Custom | Table | Pagination @@ -256,6 +257,11 @@ export interface Error { type: 'Error' children?: ReactNode } +export interface Spinner { + text?: string + className?: ClassName + type: 'Spinner' +} export interface Custom { data: JsonData subType: string diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index b43128d6..fb728e6f 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -28,7 +28,7 @@ from .tables import Pagination, Table __all__ = ( - # first we include everything from `AnyComponent` + # first we include all components from this file 'Text', 'Paragraph', 'PageTitle', @@ -48,7 +48,9 @@ 'Iframe', 'FireEvent', 'Error', + 'Spinner', 'Custom', + # then we include components from other files 'Table', 'Pagination', 'Display', @@ -291,6 +293,12 @@ def __get_pydantic_json_schema__( return json_schema +class Spinner(_p.BaseModel, extra='forbid'): + text: _t.Union[str, None] = None + class_name: _class_name.ClassNameField = None + type: _t.Literal['Spinner'] = 'Spinner' + + class Custom(_p.BaseModel, extra='forbid'): data: _types.JsonData sub_type: str = _p.Field(serialization_alias='subType') @@ -322,6 +330,7 @@ class Custom(_p.BaseModel, extra='forbid'): Video, FireEvent, Error, + Spinner, Custom, Table, Pagination,