From 8c21cbafa6921d90cb1b7f10052bbfece4d79c3c Mon Sep 17 00:00:00 2001 From: Chaojie <hi@chaojie.fun> Date: Thu, 21 Dec 2023 18:47:26 +0800 Subject: [PATCH 1/3] Support dark mode --- demo/shared.py | 3 ++ src/npm-fastui-bootstrap/src/DarkMode.tsx | 48 +++++++++++++++++++ src/npm-fastui-bootstrap/src/index.tsx | 3 ++ src/npm-fastui/src/components/DarkMode.tsx | 12 +++++ src/npm-fastui/src/components/index.tsx | 5 ++ .../fastui/components/__init__.py | 6 +++ .../tests/react-fastui-json-schema.json | 16 +++++++ 7 files changed, 93 insertions(+) create mode 100644 src/npm-fastui-bootstrap/src/DarkMode.tsx create mode 100644 src/npm-fastui/src/components/DarkMode.tsx diff --git a/demo/shared.py b/demo/shared.py index 731d54ff..05b9cc57 100644 --- a/demo/shared.py +++ b/demo/shared.py @@ -32,6 +32,9 @@ def demo_page(*components: AnyComponent, title: str | None = None) -> list[AnyCo on_click=GoToEvent(url='/forms/login'), active='startswith:/forms', ), + c.Link( + components=[c.DarkMode()], + ), ], ), c.Page( diff --git a/src/npm-fastui-bootstrap/src/DarkMode.tsx b/src/npm-fastui-bootstrap/src/DarkMode.tsx new file mode 100644 index 00000000..86455128 --- /dev/null +++ b/src/npm-fastui-bootstrap/src/DarkMode.tsx @@ -0,0 +1,48 @@ +import { FC, useEffect, useState } from 'react' +import { components, useClassName } from 'fastui' + +export const DarkMode: FC<components.DarkModeProps> = (props) => { + const [darkMode, setDarkMode] = useState(() => { + const localData = localStorage.getItem('fastui-dark-mode') + return localData ? JSON.parse(localData) : false + }) + + useEffect(() => { + localStorage.setItem('fastui-dark-mode', JSON.stringify(darkMode)) + document.documentElement.setAttribute('data-bs-theme', darkMode ? 'dark' : 'light') + }, [darkMode]) + + const handleDarkMode = (darkMode: boolean) => { + document.documentElement.setAttribute('data-bs-theme', darkMode ? 'dark' : 'light') + setDarkMode(darkMode) + } + + return ( + <span className={useClassName(props)} onClick={() => handleDarkMode(!darkMode)}> + {darkMode ? ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="18" + height="18" + fill="currentColor" + className="bi bi-moon-stars-fill" + viewBox="0 0 16 16" + > + <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278" /> + <path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" /> + </svg> + ) : ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="18" + height="18" + fill="currentColor" + className="bi bi-brightness-high-fill" + viewBox="0 0 16 16" + > + <path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" /> + </svg> + )} + </span> + ) +} diff --git a/src/npm-fastui-bootstrap/src/index.tsx b/src/npm-fastui-bootstrap/src/index.tsx index 8eb7c20a..e3219ce5 100644 --- a/src/npm-fastui-bootstrap/src/index.tsx +++ b/src/npm-fastui-bootstrap/src/index.tsx @@ -5,6 +5,7 @@ import type { ClassNameGenerator, CustomRender, ClassName } from 'fastui' import { Modal } from './modal' import { Navbar } from './navbar' import { Pagination } from './pagination' +import { DarkMode } from './DarkMode' export const customRender: CustomRender = (props) => { const { type } = props @@ -15,6 +16,8 @@ export const customRender: CustomRender = (props) => { return () => <Modal {...props} /> case 'Pagination': return () => <Pagination {...props} /> + case 'DarkMode': + return () => <DarkMode {...props} /> } } diff --git a/src/npm-fastui/src/components/DarkMode.tsx b/src/npm-fastui/src/components/DarkMode.tsx new file mode 100644 index 00000000..9f6b5a33 --- /dev/null +++ b/src/npm-fastui/src/components/DarkMode.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react' + +import { ClassName } from '../hooks/className' + +export interface DarkModeProps { + type: 'DarkMode' + className?: ClassName +} + +export const DarkModeComp: FC<DarkModeProps> = (props: DarkModeProps) => { + return <>`${props.type} are not implemented by pure FastUI, implement a component for 'DarkModeProps'.`</> +} diff --git a/src/npm-fastui/src/components/index.tsx b/src/npm-fastui/src/components/index.tsx index ad6d5f4b..ebcf2716 100644 --- a/src/npm-fastui/src/components/index.tsx +++ b/src/npm-fastui/src/components/index.tsx @@ -43,6 +43,7 @@ import { IframeComp, IframeProps } from './Iframe' import { VideoComp, VideoProps } from './video' import { FireEventComp, FireEventProps } from './FireEvent' import { CustomComp, CustomProps } from './Custom' +import { DarkModeComp, DarkModeProps } from './DarkMode' export type { TextProps, @@ -73,6 +74,7 @@ export type { VideoProps, FireEventProps, CustomProps, + DarkModeProps, } // TODO some better way to export components @@ -106,6 +108,7 @@ export type FastProps = | VideoProps | FireEventProps | CustomProps + | DarkModeProps export type FastClassNameProps = Exclude<FastProps, TextProps | AllDisplayProps | ServerLoadProps | PageTitleProps> @@ -194,6 +197,8 @@ export const AnyComp: FC<FastProps> = (props) => { return <FireEventComp {...props} /> case 'Custom': return <CustomComp {...props} /> + case 'DarkMode': + return <DarkModeComp {...props} /> default: unreachable('Unexpected component type', type, props) return <DisplayError title="Invalid Server Response" description={`Unknown component type: "${type}"`} /> diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 9466f47b..7bc6e691 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -251,6 +251,11 @@ class Custom(_p.BaseModel, extra='forbid'): type: _t.Literal['Custom'] = 'Custom' +class DarkMode(_p.BaseModel, extra='forbid'): + class_name: _class_name.ClassNameField = None + type: _t.Literal['DarkMode'] = 'DarkMode' + + AnyComponent = _te.Annotated[ _t.Union[ Text, @@ -273,6 +278,7 @@ class Custom(_p.BaseModel, extra='forbid'): Video, FireEvent, Custom, + DarkMode, Table, Pagination, Display, diff --git a/src/python-fastui/tests/react-fastui-json-schema.json b/src/python-fastui/tests/react-fastui-json-schema.json index 6990db17..7ca6fdf3 100644 --- a/src/python-fastui/tests/react-fastui-json-schema.json +++ b/src/python-fastui/tests/react-fastui-json-schema.json @@ -151,6 +151,19 @@ "required": ["data", "subType", "type"], "type": "object" }, + "DarkModeProps": { + "properties": { + "className": { + "$ref": "#/definitions/ClassName" + }, + "type": { + "const": "DarkMode", + "type": "string" + } + }, + "required": ["type"], + "type": "object" + }, "DetailsProps": { "properties": { "className": { @@ -426,6 +439,9 @@ }, { "$ref": "#/definitions/CustomProps" + }, + { + "$ref": "#/definitions/DarkModeProps" } ] }, From c6d4dbc6fd29172cea5fc1fc8b2cc22081626955 Mon Sep 17 00:00:00 2001 From: Chaojie <hi@chaojie.fun> Date: Thu, 28 Dec 2023 15:25:57 +0800 Subject: [PATCH 2/3] Add demo for this --- demo/components_list.py | 8 ++++++++ demo/main.py | 1 + demo/shared.py | 3 --- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/demo/components_list.py b/demo/components_list.py index 6e3622c1..c47e2294 100644 --- a/demo/components_list.py +++ b/demo/components_list.py @@ -199,6 +199,14 @@ class Delivery(BaseModel): ], class_name='border-top mt-3 pt-1', ), + c.Div( + components=[ + c.Heading(text='DarkMode', level=2), + c.Markdown(text='`DarkMode` can be used to toggle dark mode on and off.'), + c.DarkMode(), + ], + class_name='border-top mt-3 pt-1', + ), c.Div( components=[ c.Heading(text='Custom', level=2), diff --git a/demo/main.py b/demo/main.py index b2fa16fe..118d1707 100644 --- a/demo/main.py +++ b/demo/main.py @@ -33,6 +33,7 @@ def api_index() -> list[AnyComponent]: * `Image` - example [here](/components#image) * `Iframe` - example [here](/components#iframe) * `Video` - example [here](/components#video) +* `DarkMode` — example [here](/components#darkmode) * `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/demo/shared.py b/demo/shared.py index 05b9cc57..731d54ff 100644 --- a/demo/shared.py +++ b/demo/shared.py @@ -32,9 +32,6 @@ def demo_page(*components: AnyComponent, title: str | None = None) -> list[AnyCo on_click=GoToEvent(url='/forms/login'), active='startswith:/forms', ), - c.Link( - components=[c.DarkMode()], - ), ], ), c.Page( From 10ac780f49e22ac6b139edc94df371320509f3ee Mon Sep 17 00:00:00 2001 From: Chaojie <hi@chaojie.fun> Date: Fri, 27 Sep 2024 23:02:25 +0800 Subject: [PATCH 3/3] fix --- docs/api/python_components.md | 1 + src/npm-fastui/src/components/DarkMode.tsx | 2 +- src/npm-fastui/src/models.d.ts | 2 +- src/python-fastui/fastui/components/__init__.py | 5 ++++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/api/python_components.md b/docs/api/python_components.md index 1d8b5663..081e530f 100644 --- a/docs/api/python_components.md +++ b/docs/api/python_components.md @@ -28,6 +28,7 @@ - Error - Spinner - Toast + - DarkMode - Custom - Table - Pagination diff --git a/src/npm-fastui/src/components/DarkMode.tsx b/src/npm-fastui/src/components/DarkMode.tsx index aca06d86..636403c3 100644 --- a/src/npm-fastui/src/components/DarkMode.tsx +++ b/src/npm-fastui/src/components/DarkMode.tsx @@ -3,5 +3,5 @@ import { FC } from 'react' import { DarkMode } from '../models' export const DarkModeComp: FC<DarkMode> = (props: DarkMode) => { - return <>`${props.type} are not implemented by pure FastUI, implement a component for 'DarkModeProps'.`</> + return <>`${props.type} are not implemented by pure FastUI, implement a component for 'DarkModeProps'.`</> } diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts index b3f4c4c2..1e718bfd 100644 --- a/src/npm-fastui/src/models.d.ts +++ b/src/npm-fastui/src/models.d.ts @@ -341,7 +341,7 @@ export interface Custom { type: 'Custom' } /** - * DarkMode Component. + * DarkMode component */ export interface DarkMode { className?: ClassName diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 7755b472..4b25213e 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -50,6 +50,7 @@ 'Spinner', 'Toast', 'Custom', + 'DarkMode', # then we include components from other files 'Table', 'Pagination', @@ -597,7 +598,9 @@ class Custom(BaseModel, extra='forbid'): """The type of the component. Always 'Custom'.""" -class DarkMode(_p.BaseModel, extra='forbid'): +class DarkMode(BaseModel, extra='forbid'): + """DarkMode component""" + class_name: _class_name.ClassNameField = None type: _t.Literal['DarkMode'] = 'DarkMode'