From 8c21cbafa6921d90cb1b7f10052bbfece4d79c3c Mon Sep 17 00:00:00 2001 From: Chaojie 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 = (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 ( + handleDarkMode(!darkMode)}> + {darkMode ? ( + + + + + ) : ( + + + + )} + + ) +} 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 () => case 'Pagination': return () => + case 'DarkMode': + return () => } } 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 = (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 @@ -194,6 +197,8 @@ export const AnyComp: FC = (props) => { return case 'Custom': return + case 'DarkMode': + return default: unreachable('Unexpected component type', type, props) return 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 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 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 = (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'