├── ipyantd ├── py.typed ├── icons │ ├── __init__.py │ ├── module.py │ ├── widgets.py │ └── components.py ├── _version.py ├── __init__.py ├── assets │ └── antd-jupyter.tsx ├── module.py ├── widgets.py └── components.py ├── .prettierrc.json ├── tests └── ui │ ├── snapshots │ └── tests │ │ └── ui │ │ └── jupyter_test.py │ │ ├── test_antd_in_jupyter-voila-chromium-linux-reference.png │ │ ├── test_antd_in_jupyter-solara-chromium-linux-reference.png │ │ ├── test_antd_in_jupyter-jupyter_lab-chromium-linux-reference.png │ │ └── test_antd_in_jupyter-jupyter_notebook-chromium-linux-reference.png │ └── jupyter_test.py ├── .pre-commit-config.yaml ├── .github └── workflows │ ├── code-quality.yml │ └── build.yml ├── fix_types.patch ├── LICENSE.txt ├── README.md ├── patches └── typescript-json-schema+0.62.0.patch ├── pyproject.toml ├── package.json ├── .gitignore ├── generate ├── generate-schema.mjs └── polish-schema.py └── examples ├── demo.py └── demo.ipynb /ipyantd/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ipyantd/icons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ipyantd/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140 3 | } 4 | -------------------------------------------------------------------------------- /ipyantd/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ # noqa: F401 2 | from . import module as _module # noqa: F401 3 | -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-voila-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyantd/master/tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-voila-chromium-linux-reference.png -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-solara-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyantd/master/tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-solara-chromium-linux-reference.png -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-jupyter_lab-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyantd/master/tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-jupyter_lab-chromium-linux-reference.png -------------------------------------------------------------------------------- /tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-jupyter_notebook-chromium-linux-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widgetti/ipyantd/master/tests/ui/snapshots/tests/ui/jupyter_test.py/test_antd_in_jupyter-jupyter_notebook-chromium-linux-reference.png -------------------------------------------------------------------------------- /ipyantd/icons/module.py: -------------------------------------------------------------------------------- 1 | import ipyreact 2 | from pathlib import Path 3 | 4 | HERE = Path(__file__).parent 5 | 6 | ant_design_icons_module_path = HERE.parent / "assets/@ant-design/icons.bundle.mjs" 7 | 8 | ipyreact.define_module("@ant-design/icons", ant_design_icons_module_path) 9 | -------------------------------------------------------------------------------- /ipyantd/icons/widgets.py: -------------------------------------------------------------------------------- 1 | import traitlets 2 | import ipyreact 3 | from . import module as _icon_module # noqa: F401 4 | 5 | 6 | class Icon(ipyreact.Widget): 7 | _module = traitlets.Unicode("@ant-design/icons").tag(sync=True) 8 | 9 | def __init__(self, name, **kwargs): 10 | super().__init__(**{"_type": name, **kwargs}) 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: "https://github.com/pre-commit/mirrors-prettier" 3 | rev: "v3.1.0" 4 | hooks: 5 | - id: prettier 6 | stages: [commit] 7 | - repo: https://github.com/charliermarsh/ruff-pre-commit 8 | rev: "v0.1.9" 9 | hooks: 10 | - id: ruff 11 | stages: [commit] 12 | - id: ruff-format 13 | stages: [commit] 14 | -------------------------------------------------------------------------------- /ipyantd/icons/components.py: -------------------------------------------------------------------------------- 1 | from . import widgets 2 | import reacton 3 | 4 | 5 | def Icon( 6 | name: str, 7 | props={}, 8 | events={}, 9 | children=[], 10 | ): 11 | widget_cls = widgets.Icon 12 | comp = reacton.core.ComponentWidget(widget=widget_cls) 13 | return reacton.core.Element(comp, kwargs={"name": name, "props": props, "events": events, "children": children}) 14 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: code-quality 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: 3.7 18 | - name: Install dependencies 19 | run: | 20 | pip install ".[dev]" 21 | pre-commit install 22 | - name: run pre-commit 23 | run: | 24 | pre-commit run --all-files 25 | -------------------------------------------------------------------------------- /fix_types.patch: -------------------------------------------------------------------------------- 1 | 37823c37823 2 | < 'preview': NotRequired[Union[ImagePreviewType, bool]], 3 | --- 4 | > 'preview': NotRequired[Union["ImagePreviewType", bool]], 5 | 38023c38023 6 | < 'items': NotRequired[List[ItemType]], 7 | --- 8 | > 'items': NotRequired[List["ItemType"]], 9 | 38117,38118c38117,38118 10 | < SubMenuTypeMenuItemType, 11 | < MenuItemGroupTypeMenuItemType, 12 | --- 13 | > "SubMenuTypeMenuItemType", 14 | > "MenuItemGroupTypeMenuItemType", 15 | 38146c38146 16 | < MenuItemGroupTypeMenuItemType, 17 | --- 18 | > "MenuItemGroupTypeMenuItemType", 19 | 40523c40523 20 | < ColorInput = Union[RGB, RGBA, HSL, HSLA, HSV, HSVA, TinyColor, Union[str, float]] 21 | --- 22 | > ColorInput = Union[RGB, RGBA, HSL, HSLA, HSV, HSVA, "TinyColor", Union[str, float]] 23 | -------------------------------------------------------------------------------- /tests/ui/jupyter_test.py: -------------------------------------------------------------------------------- 1 | import playwright.sync_api 2 | from IPython.display import display 3 | 4 | 5 | def test_antd_in_jupyter(ipywidgets_runner, page_session: playwright.sync_api.Page, assert_solara_snapshot): 6 | def kernel_code(): 7 | import ipyantd.widgets as antd 8 | 9 | # this is not ideal, we need the button to want on the library somehow 10 | # import time 11 | # time.sleep(2) 12 | def click_handler(*event): 13 | b.children = ["Clicked"] 14 | 15 | b = antd.Button(children=["Click me"], events={"onClick": click_handler}) 16 | display(b) 17 | 18 | ipywidgets_runner(kernel_code) 19 | button = page_session.locator("button >> text=Click me") 20 | button.click() 21 | button = page_session.locator("button >> text=Clicked") 22 | button.wait_for() 23 | page_session.wait_for_timeout(500) # let animations play out 24 | assert_solara_snapshot(button.screenshot()) 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Maarten A. Breddels 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ipywidget wrapper for [Ant Design components](https://ant.design/) using [ipyreact](https://github.com/widgetti/ipyreact/). 2 | 3 | # Demo 4 | 5 | https://github.com/widgetti/ipyantd/assets/1765949/419020e8-27ad-4722-9d00-5a5e0dad17f3 6 | 7 | - [(classical) widget demo code](https://github.com/widgetti/ipyantd/blob/master/examples/demo.ipynb) 8 | - [Solara/reacton component demo code](https://github.com/widgetti/ipyantd/blob/master/examples/demo.py) 9 | 10 | # Components 11 | 12 | The following components are wrapped (more will be coming): 13 | 14 | - General 15 | - Button 16 | - Layout 17 | - Flex 18 | - Grid (Col / Row) 19 | - Navigation 20 | - Dropdown 21 | - Data entry 22 | - DatePicker 23 | - ColorPicker 24 | - Select 25 | - Switch 26 | - Feedback 27 | - Modal 28 | 29 | # Installation 30 | 31 | ``` 32 | pip install solara-ipyantd 33 | ``` 34 | 35 | # Dev install 36 | 37 | ``` 38 | npm ci 39 | npm run build 40 | pip install -e . 41 | ``` 42 | 43 | # Sponsors 44 | 45 | Project ipyantd receives direct funding from the following sources: 46 | 47 | [![MSD](https://raw.githubusercontent.com/widgetti/ipyvue/master/resources/msd-logo.svg)](https://msd.com) 48 | -------------------------------------------------------------------------------- /patches/typescript-json-schema+0.62.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/typescript-json-schema/dist/typescript-json-schema.js b/node_modules/typescript-json-schema/dist/typescript-json-schema.js 2 | index b230291..e6fb7af 100644 3 | --- a/node_modules/typescript-json-schema/dist/typescript-json-schema.js 4 | +++ b/node_modules/typescript-json-schema/dist/typescript-json-schema.js 5 | @@ -482,6 +482,11 @@ var JsonSchemaGenerator = (function () { 6 | definition.properties = {}; 7 | definition.additionalProperties = true; 8 | } 9 | + else if (propertyTypeString === "bigint") { 10 | + definition.type = "number"; 11 | + definition.properties = {}; 12 | + definition.additionalProperties = false; 13 | + } 14 | else { 15 | var value = extractLiteralValue(propertyType); 16 | if (value !== undefined) { 17 | @@ -558,6 +563,7 @@ var JsonSchemaGenerator = (function () { 18 | } 19 | } 20 | } 21 | + 22 | else { 23 | var error = new TypeError("Unsupported type: " + propertyTypeString); 24 | error.type = propertyType; 25 | -------------------------------------------------------------------------------- /ipyantd/assets/antd-jupyter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as antd from "antd"; 3 | 4 | export function SliderStatefull({ value, setValue, ...rest }) { 5 | return setValue(v)} {...rest} />; 6 | } 7 | 8 | export function SelectStatefull({ value, setValue, ...rest }) { 9 | return setValue(v)} {...rest} />; 10 | } 11 | 12 | export function SwitchStatefull({ value, setValue, ...rest }) { 13 | return setValue(v)} {...rest} />; 14 | } 15 | 16 | export function DropdownStatefull({ value, setValue, ...rest }) { 17 | const onOpenChange = (open, info) => { 18 | setValue(open); 19 | if (rest.onOpenChange) { 20 | rest.onOpenChange({ open, info }); 21 | } 22 | }; 23 | return ; 24 | } 25 | 26 | export function ModalStatefull({ value, setValue, ...rest }) { 27 | const onOpenChange = (open) => { 28 | setValue(open); 29 | if (rest.onOpenChange) { 30 | rest.afterOpenChange(open); 31 | } 32 | }; 33 | const handleOk = () => { 34 | // setValue(false); 35 | if (rest.onOk) { 36 | rest.onOk(); 37 | } 38 | }; 39 | const handleCancel = () => { 40 | // setValue(false); 41 | if (rest.onCancel) { 42 | rest.onCancel(); 43 | } 44 | }; 45 | return ; 46 | } 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "hatchling>=1.3.1", 4 | "jupyterlab==3.*", 5 | ] 6 | build-backend = "hatchling.build" 7 | 8 | [tool.hatch.build.targets.wheel] 9 | packages = ["ipyantd"] 10 | 11 | [project] 12 | name = "solara-ipyantd" 13 | dynamic = ["version"] 14 | description = "React for ipywidgets that just works" 15 | readme = "README.md" 16 | license = { file = "LICENSE.txt" } 17 | requires-python = ">=3.7" 18 | authors = [ 19 | { name = "Maarten A. Breddels", email = "maartenbreddels@gmail.com" }, 20 | ] 21 | keywords = [ 22 | "IPython", 23 | "Jupyter", 24 | "Widgets", 25 | ] 26 | classifiers = [ 27 | "Framework :: Jupyter", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: Science/Research", 30 | "License :: OSI Approved :: BSD License", 31 | "Programming Language :: Python", 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.7", 34 | "Programming Language :: Python :: 3.8", 35 | "Programming Language :: Python :: 3.9", 36 | "Programming Language :: Python :: 3.10", 37 | ] 38 | dependencies = [ 39 | "ipywidgets>=7.0.0", 40 | "anywidget>=0.2.0", 41 | "ipyreact>=0.4.0", 42 | ] 43 | 44 | [project.optional-dependencies] 45 | docs = [ 46 | "jupyter_sphinx", 47 | "nbsphinx", 48 | "nbsphinx-link", 49 | "pypandoc", 50 | "pytest_check_links", 51 | "recommonmark", 52 | "sphinx>=1.5", 53 | "sphinx_rtd_theme", 54 | ] 55 | dev = [ 56 | "pre-commit", 57 | ] 58 | examples = [] 59 | unit-test = [ 60 | "pytest>=6.0", 61 | ] 62 | ui-test = [ 63 | "solara[pytest]", 64 | "pytest>=6.0", 65 | ] 66 | 67 | [project.urls] 68 | Homepage = "https://github.com/widgetti/ipyantd" 69 | 70 | [tool.hatch.version] 71 | path = "ipyantd/_version.py" 72 | 73 | 74 | [tool.hatch.build.targets.sdist] 75 | exclude = [ 76 | ".github", 77 | ] 78 | 79 | 80 | [tool.ruff] 81 | line-length = 160 82 | -------------------------------------------------------------------------------- /ipyantd/module.py: -------------------------------------------------------------------------------- 1 | import ipyreact 2 | from pathlib import Path 3 | 4 | HERE = Path(__file__).parent 5 | 6 | ant_module_path = HERE / "assets/antd.mjs" 7 | ant_jupyter_module_path = HERE / "assets/antd-jupyter.js" 8 | 9 | ipyreact.define_module("antd", ant_module_path) 10 | ipyreact.define_module("antd-jupyter", ant_jupyter_module_path) 11 | ipyreact.define_module( 12 | "dayjs-formatter", 13 | """ 14 | import dayjs from "https://esm.sh/dayjs@1.11.10" 15 | import weekday from "https://esm.sh/dayjs@1.11.10/plugin/weekday" 16 | import localeData from "https://esm.sh/dayjs@1.11.10/plugin/localeData" 17 | import en from "https://esm.sh/dayjs@1.11.10/locale/en" 18 | import quarterOfYear from "https://esm.sh/dayjs@1.11.10/plugin/quarterOfYear" 19 | 20 | dayjs.locale('en') // use en?? locale globally 21 | 22 | dayjs.extend(weekday) 23 | dayjs.extend(localeData) 24 | dayjs.extend(quarterOfYear) 25 | 26 | export 27 | const py2js = (pyValue) => Boolean(pyValue) ? dayjs(pyValue) : pyValue 28 | 29 | export 30 | const js2py = (jsValue) => JSON.stringify(jsValue) 31 | 32 | """, 33 | ) 34 | 35 | # def create_library(): 36 | # dayjs = ipyreact.Widget(_esm=""" 37 | # import dayjs from "https://esm.sh/dayjs@1.11.10" 38 | # import weekday from "https://esm.sh/dayjs@1.11.10/plugin/weekday" 39 | # import localeData from "https://esm.sh/dayjs@1.11.10/plugin/localeData" 40 | # import en from "https://esm.sh/dayjs@1.11.10/locale/en" 41 | # import quarterOfYear from "https://esm.sh/dayjs@1.11.10/plugin/quarterOfYear" 42 | 43 | # dayjs.locale('en') // use en?? locale globally 44 | 45 | # dayjs.extend(weekday) 46 | # dayjs.extend(localeData) 47 | # dayjs.extend(quarterOfYear) 48 | 49 | # export 50 | # const py2js = (pyValue) => Boolean(pyValue) ? dayjs(pyValue) : pyValue 51 | 52 | # export 53 | # const js2py = (jsValue) => JSON.stringify(jsValue) 54 | 55 | # """, _name="dayjs-formatter") 56 | 57 | # antd = ipyreact.Widget(_esm=ant_module_path.read_text(), _name="antd") 58 | # return None 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyter-antd", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npm run build:antd && npm run build:icons && npm run build:antd-jupyter && npm run build:schema", 9 | "watch": "npm run watch:antd-jupyter", 10 | "build:schema": "npm run build:schema:initial && npm run build:schema:final", 11 | "build:schema:initial": "node ./generate/generate-schema.mjs", 12 | "build:schema:final": "python ./generate/polish-schema.py", 13 | "build:py": "npm run build:py:types && npm run build:py:types:patch && npm run build:py:types:check", 14 | "build:py:types": "datamodel-codegen --input generate/schema-polished.json --input-file-type jsonschema --output ipyantd/types.py --output-model-type typing.TypedDict", 15 | "build:py:types:patch": "patch ipyantd/types.py < fix_types.patch", 16 | "build:py:types:check": "python -c \"import ipyantd.types\"", 17 | "build:antd": "esbuild ./node_modules/antd/es/index.js --bundle --outfile=./ipyantd/assets/antd.mjs --format=esm --external:react --external:react-dom", 18 | "build:icons": "esbuild ./node_modules/@ant-design/icons/es/index.js --bundle --outfile=./ipyantd/assets/@ant-design/icons.bundle.mjs --format=esm --external:antd --external:react --external:react-dom", 19 | "build:antd-jupyter": "esbuild ./ipyantd/assets/antd-jupyter.tsx --bundle --outfile=./ipyantd/assets/antd-jupyter.js --format=esm --external:antd --external:react --external:react-dom", 20 | "watch:antd-jupyter": "esbuild ./ipyantd/assets/antd-jupyter.tsx --bundle --outfile=./ipyantd/assets/antd-jupyter.js --format=esm --external:antd --external:react --external:react-dom --watch", 21 | "postinstall": "patch-package" 22 | }, 23 | "author": "", 24 | "license": "MIT", 25 | "private": true, 26 | "dependencies": { 27 | "@ant-design/icons": "^5.3.0", 28 | "antd": "^5.12.4", 29 | "antd-style": "^3.6.1" 30 | }, 31 | "devDependencies": { 32 | "@types/react": "^18.2.45", 33 | "esbuild": "^0.19.10", 34 | "patch-package": "^8.0.0", 35 | "typescript-json-schema": "^0.62.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | docs/source/_static/embed-bundle.js 65 | docs/source/_static/embed-bundle.js.map 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # ========================= 93 | # Operating System Files 94 | # ========================= 95 | 96 | # OSX 97 | # ========================= 98 | 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Thumbnails 104 | ._* 105 | 106 | # Files that might appear in the root of a volume 107 | .DocumentRevisions-V100 108 | .fseventsd 109 | .Spotlight-V100 110 | .TemporaryItems 111 | .Trashes 112 | .VolumeIcon.icns 113 | 114 | # Directories potentially created on remote AFP share 115 | .AppleDB 116 | .AppleDesktop 117 | Network Trash Folder 118 | Temporary Items 119 | .apdisk 120 | 121 | # Windows 122 | # ========================= 123 | 124 | # Windows image file caches 125 | Thumbs.db 126 | ehthumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | # Recycle Bin used on file shares 132 | $RECYCLE.BIN/ 133 | 134 | # Windows Installer files 135 | *.cab 136 | *.msi 137 | *.msm 138 | *.msp 139 | 140 | # Windows shortcuts 141 | *.lnk 142 | 143 | 144 | # NPM 145 | # ---- 146 | 147 | **/node_modules/ 148 | ipyantd/nbextension/index.* 149 | 150 | # Coverage data 151 | # ------------- 152 | **/coverage/ 153 | 154 | # Packed lab extensions 155 | ipyantd/labextension 156 | -------------------------------------------------------------------------------- /ipyantd/widgets.py: -------------------------------------------------------------------------------- 1 | import ipyreact 2 | import traitlets 3 | from traitlets import List, Unicode 4 | 5 | 6 | class Button(ipyreact.Widget): 7 | _module = traitlets.Unicode("antd").tag(sync=True) 8 | _type = traitlets.Unicode("Button").tag(sync=True) 9 | 10 | 11 | class Slider(ipyreact.ValueWidget): 12 | _module = traitlets.Unicode("antd-jupyter").tag(sync=True) 13 | _type = traitlets.Unicode("SliderStatefull").tag(sync=True) 14 | 15 | 16 | class Flex(ipyreact.Widget): 17 | _module = traitlets.Unicode("antd").tag(sync=True) 18 | _type = traitlets.Unicode("Flex").tag(sync=True) 19 | 20 | 21 | class Row(ipyreact.Widget): 22 | _module = traitlets.Unicode("antd").tag(sync=True) 23 | _type = traitlets.Unicode("Row").tag(sync=True) 24 | 25 | 26 | class Col(ipyreact.Widget): 27 | _module = traitlets.Unicode("antd").tag(sync=True) 28 | _type = traitlets.Unicode("Col").tag(sync=True) 29 | 30 | 31 | class Select(ipyreact.ValueWidget): 32 | _module = traitlets.Unicode("antd-jupyter").tag(sync=True) 33 | _type = traitlets.Unicode("SelectStatefull").tag(sync=True) 34 | 35 | 36 | class Switch(ipyreact.ValueWidget): 37 | _module = traitlets.Unicode("antd-jupyter").tag(sync=True) 38 | _type = traitlets.Unicode("SwitchStatefull").tag(sync=True) 39 | 40 | 41 | class Dropdown(ipyreact.ValueWidget): 42 | value = traitlets.Bool(False).tag(sync=True) 43 | _module = traitlets.Unicode("antd-jupyter").tag(sync=True) 44 | _type = traitlets.Unicode("DropdownStatefull").tag(sync=True) 45 | 46 | 47 | class Modal(ipyreact.ValueWidget): 48 | value = traitlets.Bool(False).tag(sync=True) 49 | _module = traitlets.Unicode("antd-jupyter").tag(sync=True) 50 | _type = traitlets.Unicode("ModalStatefull").tag(sync=True) 51 | 52 | 53 | class DatePicker(ipyreact.ValueWidget): 54 | # _module = traitlets.Unicode("antd").tag(sync=True) 55 | # _type = traitlets.Unicode("DatePicker").tag(sync=True) 56 | _esm = """ 57 | import React from "react"; 58 | import {DatePicker} from "antd"; 59 | import dayjs from "https://esm.sh/dayjs@1.11.10" 60 | 61 | export default ({value, setValue, children, ...rest}) => { 62 | const dayjsValue = Boolean(value) ? dayjs(value) : value 63 | console.log(value, dayjsValue) 64 | return setValue((dayjsValue))} {...rest}>{children} 65 | } 66 | 67 | """ 68 | _dependencies = List(Unicode(), ["antd", "dayjs-formatter"], allow_none=True).tag(sync=True) 69 | 70 | 71 | class ColorPicker(ipyreact.ValueWidget): 72 | _esm = """ 73 | import React from "react"; 74 | import {ColorPicker} from "antd"; 75 | 76 | export default ({value, setValue, children, ...rest}) => { 77 | //const dayjsValue = Boolean(value) ? dayjs(value) : value 78 | //console.log(value, dayjsValue) 79 | return setValue((color.metaColor))} {...rest}>{children} 80 | } 81 | 82 | """ 83 | _dependencies = List(Unicode(), ["antd"], allow_none=True).tag(sync=True) 84 | 85 | 86 | cp = ColorPicker() 87 | cp 88 | -------------------------------------------------------------------------------- /generate/generate-schema.mjs: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import * as fs from "fs/promises"; 3 | import * as TJS from "typescript-json-schema"; 4 | 5 | // optionally pass argument to schema generator 6 | const settings = { 7 | required: true, 8 | }; 9 | 10 | // optionally pass ts compiler options 11 | const compilerOptions = { 12 | strictNullChecks: true, 13 | esModuleInterop: true, 14 | }; 15 | 16 | // optionally pass a base path 17 | const basePath = "./my-dir"; 18 | const inputFilePath = "node_modules/antd/lib/index.d.ts"; 19 | const program = TJS.getProgramFromFiles( 20 | [resolve(inputFilePath)], 21 | compilerOptions, 22 | // basePath 23 | ); 24 | 25 | // Function to extract Props and save to a JSON file 26 | async function extractPropsAndSaveToFile(inputFilePath, outputFilePath) { 27 | try { 28 | // Read the input file 29 | const data = await fs.readFile(inputFilePath, "utf8"); 30 | 31 | // Regular expression to match Props 32 | const propsPattern = /(? { 63 | // try { 64 | console.log(props); 65 | generator.getSchemaForSymbol(props); 66 | // } catch (err) { 67 | // console.error('Error:', err); 68 | // } 69 | }); 70 | const schema = generator.getSchemaForSymbols(propsList); 71 | 72 | await fs.writeFile("./generate/schema.json", JSON.stringify(schema, undefined, 4), function (err) { 73 | if (err) { 74 | return console.log(err); 75 | } 76 | console.log("The file was saved!"); 77 | }); 78 | 79 | // // ... or a generator that lets us incrementally get more schemas 80 | 81 | // const generator = TJS.buildGenerator(program, settings); 82 | 83 | // // generator can be also reused to speed up generating the schema if usecase allows: 84 | // const schemaWithReusedGenerator = TJS.generateSchema(program, "MyType", settings, [], generator); 85 | 86 | // all symbols 87 | // const symbols = generator.getUserSymbols(); 88 | 89 | // Get symbols for different types from generator. 90 | // generator.getSchemaForSymbol("MyType"); 91 | // generator.getSchemaForSymbol("AnotherType"); 92 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "v*" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Install node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: "16.x" 23 | 24 | # - name: Change version for non-releases 25 | # if: ${{ ! startsWith(github.event.ref, 'refs/tags/v')}} 26 | # run: python .github/ci_version.py 27 | 28 | - name: npm install 29 | run: (npm ci) 30 | 31 | - name: Python requirements 32 | run: pip install datamodel-code-generator==0.25.2 hatch 33 | 34 | - name: Build antd es module 35 | run: (npm run build) 36 | 37 | - name: Package 38 | run: hatch build 39 | 40 | - name: Upload builds 41 | uses: actions/upload-artifact@v3 42 | with: 43 | name: ipyantd-dist-${{ github.run_number }} 44 | path: | 45 | ./dist 46 | ./*.tgz 47 | 48 | # do we need unittests? 49 | # test: 50 | # needs: [build] 51 | # runs-on: ubuntu-20.04 52 | # strategy: 53 | # fail-fast: false 54 | # matrix: 55 | # python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] 56 | 57 | # steps: 58 | # - uses: actions/checkout@v2 59 | 60 | # - uses: actions/download-artifact@v3 61 | # with: 62 | # name: ipyantd-dist-${{ github.run_number }} 63 | 64 | # - name: Install Python 65 | # uses: actions/setup-python@v2 66 | # with: 67 | # python-version: ${{ matrix.python-version }} 68 | 69 | # - name: Install 70 | # run: pip install `echo dist/*.whl`[unit-test] "jupyter_server<2" 71 | 72 | # - name: Run unit tests 73 | # run: pytest tests/unit 74 | 75 | ui-test: 76 | needs: [build] 77 | runs-on: ubuntu-20.04 78 | steps: 79 | - uses: actions/checkout@v2 80 | 81 | - uses: actions/download-artifact@v3 82 | with: 83 | name: ipyantd-dist-${{ github.run_number }} 84 | 85 | - name: Install Python 86 | uses: actions/setup-python@v2 87 | with: 88 | python-version: 3.8 89 | 90 | - name: Install ipyantd 91 | run: pip install `echo dist/*.whl`[ui-test] "jupyter_server<2" 92 | 93 | - name: Install playwright browsers 94 | run: playwright install chromium 95 | 96 | - name: Run ui-tests 97 | run: pytest tests/ui/ --video=retain-on-failure 98 | 99 | - name: Upload Test artifacts 100 | if: always() 101 | uses: actions/upload-artifact@v2 102 | with: 103 | name: ipyantd-test-results-${{ github.run_number }} 104 | path: test-results 105 | 106 | release: 107 | if: startsWith(github.event.ref, 'refs/tags/v') 108 | needs: [ui-test] 109 | # needs: [test, ui-test] 110 | runs-on: ubuntu-20.04 111 | permissions: 112 | id-token: write # this permission is mandatory for trusted publishing 113 | steps: 114 | - uses: actions/download-artifact@v3 115 | with: 116 | name: ipyantd-dist-${{ github.run_number }} 117 | 118 | - name: Install node 119 | uses: actions/setup-node@v1 120 | with: 121 | node-version: "16.x" 122 | registry-url: "https://registry.npmjs.org" 123 | 124 | - name: Install Python 125 | uses: actions/setup-python@v2 126 | with: 127 | python-version: 3.8 128 | 129 | - name: Install dependencies 130 | run: | 131 | python -m pip install --upgrade pip 132 | pip install twine wheel jupyter-packaging jupyterlab 133 | 134 | - name: Publish package distributions to PyPI 135 | uses: pypa/gh-action-pypi-publish@release/v1 136 | with: 137 | packages-dir: dist 138 | -------------------------------------------------------------------------------- /generate/polish-schema.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | import collections 4 | 5 | 6 | HERE = Path(__file__).parent 7 | 8 | schema_file = HERE / "schema.json" 9 | 10 | with open(schema_file, encoding="utf8") as f: 11 | schema = json.load(f) 12 | 13 | mapping = { 14 | "React.ReactNode": "ReactNode", 15 | "React.CSSProperties": "CSSProperties", 16 | "React.FunctionComponent

": "FunctionComponent", 17 | "React.FunctionComponent<{}>": "FunctionComponent", 18 | "React.ComponentClass": "ComponentClass", 19 | "React.MutableRefObject": "object", 20 | "React.WeakValidationMap

": "object", 21 | "Iterable": "ReactNodeList", 22 | "Partial>": "SpecificCssProperties", 23 | "React.Key": "StringOrNumber", 24 | "React.ReactInstance": "ElementOrComponent", 25 | "React.Component": "Component", 26 | "React.ReactElement>_1": "Element", 27 | "React.FunctionComponent

_1": "Component", 28 | "JSX.Element": "Element", 29 | } 30 | 31 | remove = [ 32 | "React.Context", 33 | "React.ReactPortal", 34 | "React.ReactElement>", 35 | "React.Provider", 36 | "React.WeakValidationMap>", 37 | "React.Validator", 38 | "React.Validator", 39 | "React.Consumer", 40 | "React.ComponentClass<{},any>", 41 | "React.RefObject", 42 | "React.WeakValidationMap<{}>", 43 | "ValidationMap", 44 | "React.WeakValidationMap

_1", 45 | "React.RefObject", 46 | "Partial>", 47 | "React.ComponentClass_1", 48 | ] 49 | remove_refs = ["#/definitions/" + k for k in remove] 50 | 51 | events = collections.defaultdict(set) 52 | schema["definitions"]["ReactNodeList"] = { 53 | "type": "array", 54 | "items": {"$ref": "#/definitions/ReactNode"}, 55 | } 56 | 57 | ref_mapping = {"#/definitions/" + k: "#/definitions/" + v for k, v in mapping.items()} 58 | 59 | 60 | def replace_refs(obj): 61 | if isinstance(obj, dict): 62 | if "$ref" in obj: 63 | if obj["$ref"] in ref_mapping: 64 | obj["$ref"] = ref_mapping[obj["$ref"]] 65 | for v in obj.values(): 66 | replace_refs(v) 67 | elif isinstance(obj, list): 68 | for v in obj: 69 | replace_refs(v) 70 | 71 | 72 | def remove_unsupported(obj): 73 | if isinstance(obj, dict): 74 | for key, value in obj.copy().items(): 75 | if key == "$ref" and value in remove_refs: 76 | obj.pop(key) 77 | else: 78 | remove_unsupported(value) 79 | elif isinstance(obj, list): 80 | for v in obj.copy(): 81 | if isinstance(v, dict) and "$ref" in v and v["$ref"] in remove_refs: 82 | obj.remove(v) 83 | else: 84 | remove_unsupported(v) 85 | 86 | 87 | for name, definition in schema["definitions"].copy().items(): 88 | if name in mapping: 89 | schema["definitions"][mapping[name]] = definition 90 | schema["definitions"].pop(name) 91 | name = mapping[name] 92 | if name in remove: 93 | schema["definitions"].pop(name) 94 | continue 95 | replace_refs(definition) 96 | remove_unsupported(definition) 97 | if "properties" in definition: 98 | for prop_name, prop in definition["properties"].copy().items(): 99 | # lets drop all arias for now 100 | if prop_name.startswith("aria") or prop_name in ["role", "tabIndex"]: 101 | definition["properties"].pop(prop_name) 102 | continue 103 | # if "$ref" in prop: 104 | # if prop["$ref"] in ref_mapping: 105 | # prop["$ref"] = ref_mapping[prop["$ref"]] 106 | # print(prop["$ref"]) 107 | if prop_name.startswith("on"): 108 | # if "type" not in prop: 109 | # raise ValueError("Missing type for", name, prop_name, definition) 110 | if "type" in prop and prop["type"] == "object": 111 | prop["type"] = "function" 112 | events[name].add(prop_name) 113 | definition["properties"].pop(prop_name) 114 | if events[name]: 115 | definition["properties"]["events"] = {"$ref": "#/definitions/" + name + "Events"} 116 | 117 | all_events = set() 118 | for name, event_set in events.items(): 119 | all_events.update(event_set) 120 | for name, events in events.items(): 121 | if events: 122 | schema["definitions"][name + "Events"] = { 123 | "type": "object", 124 | "properties": {event: {"type": "function"} for event in events}, 125 | "additionalProperties": False, 126 | } 127 | 128 | # extra = events - all_events 129 | # if extra: 130 | # print(name, "has extra events", extra) 131 | # missing = all_events - events 132 | # if missing: 133 | # print(name, "is missing events", missing) 134 | 135 | del schema["definitions"]["ReactNode"] 136 | schema_file_polished = HERE / "schema-polished.json" 137 | with open(schema_file_polished, "w") as f: 138 | json.dump(schema, f, indent=2) 139 | -------------------------------------------------------------------------------- /examples/demo.py: -------------------------------------------------------------------------------- 1 | # run as: $ solara run examples/demo.py 2 | import ipyantd.components as antd 3 | import ipyantd.icons.components as icons 4 | 5 | import solara 6 | import ipyreact 7 | 8 | 9 | message = solara.reactive(None) 10 | dialog = solara.reactive(False) 11 | slider = solara.reactive(0) 12 | slider_range = solara.reactive([20, 70]) 13 | select = solara.reactive("pear") 14 | multi_select = solara.reactive(["apple", "pear"]) 15 | switch = solara.reactive(False) 16 | 17 | 18 | def dialog_open(event_data): 19 | message.value = "Modal opened" 20 | dialog.value = True 21 | 22 | 23 | def dialog_close(event_data): 24 | message.value = "Modal closed" 25 | dialog.value = False 26 | 27 | 28 | def report_click(event_data=None): 29 | message.value = "Clicked" 30 | 31 | 32 | options = [ 33 | dict(label="Apple", value="apple"), 34 | dict(label="Pear", value="pear"), 35 | dict(label="Cherry", value="cherry"), 36 | ] 37 | 38 | menu_items = [ 39 | dict( 40 | key="1", 41 | label=ipyreact.Widget.element(_type="div", children=["Click me 1"], events={"onClick": report_click}), 42 | ), 43 | dict( 44 | key="2", 45 | label=ipyreact.Widget.element(_type="div", children=["Click me 2"], events={"onClick": report_click}), 46 | ), 47 | ] 48 | text = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. 49 | Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. 50 | Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. 51 | Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. 52 | Duis semper. 53 | Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. 54 | """ 55 | 56 | 57 | @solara.component 58 | def Page(): 59 | with ipyreact.Widget.element(_type="div"): 60 | with antd.Row(props=dict(gutter=[48, 48], style={"padding": "100px"})): 61 | # buttons 62 | with antd.Col(props=dict(span=8, style={"boxSizing": "border-box"})): 63 | with ipyreact.Widget.element(_type="div"): 64 | antd.Button(children=["Button"], props=dict(type="primary"), events={"onClick": report_click}) 65 | antd.Button(children=["Button"]) 66 | with antd.Col(props=dict(span=8)): 67 | antd.Button(children=["Button"], props=dict(type="primary", shape="round")) 68 | antd.Button(props=dict(type="primary", shape="circle", icon=icons.Icon("SearchOutlined"))) 69 | antd.Button(props=dict(shape="circle", icon=icons.Icon("SearchOutlined"))) 70 | with antd.Col(props=dict(span=8)): 71 | antd.Button(children=["Button"], props=dict(type="text")) 72 | with antd.Col(props=dict(span=8)): 73 | antd.Button(children=["Button"], props=dict(type="primary", size="small")) 74 | antd.Button(children=["Button"], props=dict(size="small")) 75 | with antd.Col(props=dict(span=8)): 76 | antd.Button(children=["Button"], props=dict(type="primary", shape="round", size="small")) 77 | antd.Button(props=dict(type="primary", shape="circle", icon=icons.Icon("SearchOutlined"), size="small")) 78 | antd.Button(props=dict(shape="circle", icon=icons.Icon("SearchOutlined"), size="small")) 79 | with antd.Col(props=dict(span=8)): 80 | antd.Button(children=["Button"], props=dict(type="text", size="small")) 81 | 82 | # sliders 83 | with antd.Col(props=dict(span=8)): 84 | antd.Slider(value=slider.value, on_value=slider.set, props=dict(min=0, max=100)) 85 | with antd.Col(props=dict(span=8)): 86 | antd.Slider(value=slider_range.value, on_value=slider_range.set, props=dict(min=0, max=100, range=True)) 87 | with antd.Col(props=dict(span=8)): 88 | antd.Slider(value=slider.value, on_value=slider.set, props=dict(min=0, max=100, tooltip=dict(open=True))) 89 | 90 | # select 91 | with antd.Col(props=dict(span=8)): 92 | antd.Select(value=select.value, on_value=select.set, props=dict(options=options, style=dict(width="120px"))) 93 | with antd.Col(props=dict(span=8)): 94 | antd.Select(value=multi_select.value, on_value=multi_select.set, props=dict(options=options, style=dict(width="220px"), mode="multiple")) 95 | with antd.Col(props=dict(span=8)): 96 | pass 97 | # only supported in 5.31+ 98 | # antd.Select(value="pear", props=dict(options=options, variant="filled", style=dict(width="120px"))), 99 | 100 | # misc 101 | with antd.Col(props=dict(span=8)): 102 | antd.Switch(value=switch.value, on_value=switch.set) 103 | with antd.Col(props=dict(span=8)): 104 | with antd.Dropdown(props=dict(menu=dict(items=menu_items), trigger=["click"])): 105 | ipyreact.Widget.element(_type="span", children=["Menu"]) 106 | with antd.Col(props=dict(span=8)): 107 | with antd.Modal(value=dialog.value, events={"onOk": dialog_close, "onCancel": dialog_close}): 108 | ipyreact.Widget.element(_type="div", children=[text]) 109 | antd.Button(children=["Dialog"], props=dict(type="primary"), events={"onClick": dialog_open}) 110 | 111 | solara.Preformatted(f"message: {message.value or ''}") 112 | solara.Preformatted(f"slider: {slider.value}") 113 | solara.Preformatted(f"slider_range: {slider_range.value}") 114 | solara.Preformatted(f"select: {select.value}") 115 | solara.Preformatted(f"multi_select: {multi_select.value}") 116 | solara.Preformatted(f"switch: {switch.value}") 117 | -------------------------------------------------------------------------------- /ipyantd/components.py: -------------------------------------------------------------------------------- 1 | import ipyreact 2 | import reacton 3 | from . import widgets 4 | from .types import ( 5 | ButtonProps, 6 | ButtonPropsEvents, 7 | SliderSingleProps, 8 | SliderSinglePropsEvents, 9 | ColorPickerProps, 10 | ColorPickerPropsEvents, 11 | FlexProps, 12 | FlexPropsEvents, 13 | ColorModel, 14 | RGB1, 15 | RGBA1, 16 | HSB, 17 | HSBA, 18 | RowProps, 19 | RowPropsEvents, 20 | ColProps, 21 | ColPropsEvents, 22 | DatePickerProps, 23 | SelectProps, 24 | SelectPropsEvents, 25 | SwitchProps, 26 | SwitchPropsEvents, 27 | DropdownProps, 28 | DropdownPropsEvents, 29 | ModalProps, 30 | ModalPropsEvents, 31 | ) 32 | from typing import Dict, List, Union, Optional, Callable, Any 33 | 34 | 35 | ChildType = Union[ipyreact.Widget, str] 36 | ChildrenType = Union[ChildType, List[ChildType]] 37 | 38 | 39 | # not sure why this is not generated in typing 40 | DatePickerPropsEvents = Dict 41 | 42 | 43 | def Button( 44 | props: ButtonProps = {}, 45 | events: ButtonPropsEvents = {}, 46 | children: ChildrenType = [], 47 | ): 48 | widget_cls = widgets.Button 49 | comp = reacton.core.ComponentWidget(widget=widget_cls) 50 | return reacton.core.Element(comp, kwargs={"props": props, "events": events, "children": children}) 51 | 52 | 53 | def Slider( 54 | value: Union[float] = 0, 55 | on_value: Callable[[Union[float]], None] = lambda x: None, 56 | props: SliderSingleProps = {}, 57 | events: SliderSinglePropsEvents = {}, 58 | children: ChildrenType = [], 59 | ): 60 | return widgets.Slider.element(value=value, on_value=on_value, props=props, events=events, children=children) 61 | 62 | 63 | def Flex(props: FlexProps = {}, events: FlexPropsEvents = {}, children: ChildrenType = []): 64 | widget_cls = widgets.Flex 65 | comp = reacton.core.ComponentWidget(widget=widget_cls) 66 | return reacton.core.Element(comp, kwargs={"props": props, "children": children}) 67 | 68 | 69 | def Row(props: RowProps = {}, events: RowPropsEvents = {}, children: ChildrenType = []): 70 | widget_cls = widgets.Row 71 | comp = reacton.core.ComponentWidget(widget=widget_cls) 72 | return reacton.core.Element(comp, kwargs={"props": props, "events": events, "children": children}) 73 | 74 | 75 | def Col(props: ColProps = {}, events: ColPropsEvents = {}, children: ChildrenType = []): 76 | widget_cls = widgets.Col 77 | comp = reacton.core.ComponentWidget(widget=widget_cls) 78 | return reacton.core.Element(comp, kwargs={"props": props, "events": events, "children": children}) 79 | 80 | 81 | def Select( 82 | value: Optional[Union[Any, float]] = None, 83 | on_value: Optional[Callable[[Union[Any, float]], None]] = None, 84 | props: SelectProps = {}, 85 | events: SelectPropsEvents = {}, 86 | children: ChildrenType = [], 87 | ): 88 | widget_cls = widgets.Select 89 | comp = reacton.core.ComponentWidget(widget=widget_cls) 90 | return reacton.core.Element( 91 | comp, 92 | kwargs={ 93 | "value": value, 94 | "on_value": on_value, 95 | "props": props, 96 | "events": events, 97 | "children": children, 98 | }, 99 | ) 100 | 101 | 102 | def Switch( 103 | value: Optional[bool] = None, 104 | on_value: Optional[Callable[[bool], None]] = None, 105 | props: SwitchProps = {}, 106 | events: SwitchPropsEvents = {}, 107 | children: ChildrenType = [], 108 | ): 109 | widget_cls = widgets.Switch 110 | comp = reacton.core.ComponentWidget(widget=widget_cls) 111 | return reacton.core.Element( 112 | comp, 113 | kwargs={ 114 | "value": value, 115 | "on_value": on_value, 116 | "props": props, 117 | "events": events, 118 | "children": children, 119 | }, 120 | ) 121 | 122 | 123 | def Dropdown( 124 | value: bool = False, 125 | on_value: Optional[Callable[[bool], None]] = None, 126 | props: DropdownProps = {}, 127 | events: DropdownPropsEvents = {}, 128 | children: ChildrenType = [], 129 | ): 130 | widget_cls = widgets.Dropdown 131 | comp = reacton.core.ComponentWidget(widget=widget_cls) 132 | return reacton.core.Element( 133 | comp, 134 | kwargs={ 135 | "value": value, 136 | "on_value": on_value, 137 | "props": props, 138 | "events": events, 139 | "children": children, 140 | }, 141 | ) 142 | 143 | 144 | def Modal( 145 | value: bool = False, 146 | on_value: Optional[Callable[[bool], None]] = None, 147 | props: ModalProps = {}, 148 | events: ModalPropsEvents = {}, 149 | children: ChildrenType = [], 150 | ): 151 | widget_cls = widgets.Modal 152 | comp = reacton.core.ComponentWidget(widget=widget_cls) 153 | return reacton.core.Element( 154 | comp, 155 | kwargs={ 156 | "value": value, 157 | "on_value": on_value, 158 | "props": props, 159 | "events": events, 160 | "children": children, 161 | }, 162 | ) 163 | 164 | 165 | def DatePicker( 166 | value: Optional[str] = None, 167 | on_value: Optional[Callable[[str], None]] = None, 168 | props: DatePickerProps = {}, 169 | events: DatePickerPropsEvents = {}, 170 | children: ChildrenType = [], 171 | ): 172 | widget_cls = widgets.DatePicker 173 | comp = reacton.core.ComponentWidget(widget=widget_cls) 174 | return reacton.core.Element( 175 | comp, 176 | kwargs={ 177 | "value": value, 178 | "on_value": on_value, 179 | "props": props, 180 | "events": events, 181 | "children": children, 182 | }, 183 | ) 184 | 185 | 186 | def ColorPicker( 187 | value: Optional[Union[ColorModel, RGB1, RGBA1, HSB, HSBA, Union[str, float]]] = None, 188 | on_value: Optional[Callable[[Union[ColorModel, RGB1, RGBA1, HSB, HSBA, Union[str, float]]], None]] = None, 189 | props: ColorPickerProps = {}, 190 | events: ColorPickerPropsEvents = {}, 191 | children: ChildrenType = [], 192 | ): 193 | widget_cls = widgets.ColorPicker 194 | comp = reacton.core.ComponentWidget(widget=widget_cls) 195 | return reacton.core.Element( 196 | comp, 197 | kwargs={ 198 | "value": value, 199 | "on_value": on_value, 200 | "props": props, 201 | "events": events, 202 | "children": children, 203 | }, 204 | ) 205 | 206 | 207 | @reacton.component 208 | def Page(): 209 | count, set_count = reacton.use_state(0) 210 | color, set_color = reacton.use_state("red") 211 | date, set_date = reacton.use_state(None) 212 | print(count) 213 | 214 | def click(*event): 215 | print(event) 216 | set_count(lambda x: x + 1) 217 | 218 | def pick(*data): 219 | print(data) 220 | set_date(data[0]) 221 | 222 | with Flex(gap="small") as f: 223 | if date: 224 | DatePicker(onChange=pick, value=date) 225 | else: 226 | DatePicker(onChange=pick) 227 | Button(children=["Hi1 " + str(count)], props=dict(type="primary"), onClick=click) 228 | Button( 229 | children=["Hi2 " + str(date)], 230 | type="primary", 231 | onClick=click, 232 | danger=count > 4, 233 | style=dict(minWidth="300px", backgroundColor="green"), 234 | ) 235 | return f 236 | 237 | 238 | Page() 239 | -------------------------------------------------------------------------------- /examples/demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "e88d1c8e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import ipyantd.widgets as antd\n", 11 | "import ipyantd.icons.widgets as icons\n", 12 | "import ipyreact" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "id": "a713bb10", 18 | "metadata": {}, 19 | "source": [ 20 | "# Widget interface\n" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "e84fa867", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "import ipywidgets\n", 31 | "from IPython.display import clear_output\n", 32 | "\n", 33 | "options = [\n", 34 | " dict(label=\"Apple\", value=\"apple\"),\n", 35 | " dict(label=\"Pear\", value=\"pear\"),\n", 36 | " dict(label=\"Cherry\", value=\"cherry\"),\n", 37 | "]\n", 38 | "\n", 39 | "menu_items = [\n", 40 | " dict(key=\"1\", label=ipyreact.Widget(_type=\"div\", children=[\"Click me 1\"], events={\"onClick\": print})),\n", 41 | " dict(key=\"2\", label=ipyreact.Widget(_type=\"div\", children=[\"Click me 2\"], events={\"onClick\": print})),\n", 42 | "]\n", 43 | "text = \"\"\"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n", 44 | "Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. \n", 45 | "Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. \n", 46 | "Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. \n", 47 | "Duis semper.\n", 48 | "Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue.\n", 49 | "\"\"\"\n", 50 | "\n", 51 | "\n", 52 | "def dialog_open(event_data):\n", 53 | " with output:\n", 54 | " clear_output(wait=True)\n", 55 | " print(\"Modal opened\")\n", 56 | " dialog.value = True\n", 57 | "\n", 58 | "\n", 59 | "def dialog_close(event_data):\n", 60 | " with output:\n", 61 | " clear_output(wait=True)\n", 62 | " print(\"Modal closed\")\n", 63 | " dialog.value = False\n", 64 | " \n", 65 | "def show_value(change):\n", 66 | " new = change[\"new\"]\n", 67 | " with output:\n", 68 | " clear_output(wait=True)\n", 69 | " print(\"value = \", new)\n", 70 | "\n", 71 | "def report_click(event_data=None):\n", 72 | " with output:\n", 73 | " clear_output(wait=True)\n", 74 | " print(\"Clicked\")\n", 75 | "\n", 76 | "demo = ipyreact.Widget(_type=\"div\", children=[\n", 77 | " antd.Row(props=dict(gutter=[48,48], style={\"padding\": \"100px\"}), children=[\n", 78 | " antd.Col(props=dict(span=8, style={\"boxSizing\": \"border-box\"}), children=[\n", 79 | " ipyreact.Widget(_type=\"div\", children=[\n", 80 | " antd.Button(children=[\"Button\"], props=dict(type=\"primary\"), events={\"onClick\": report_click}),\n", 81 | " antd.Button(children=[\"Button\"]),\n", 82 | " ]),\n", 83 | " ]),\n", 84 | " antd.Col(props=dict(span=8), children=[\n", 85 | " antd.Button(children=[\"Button\"], props=dict(type=\"primary\", shape=\"round\")),\n", 86 | " antd.Button(props=dict(type=\"primary\", shape=\"circle\", icon=icons.Icon(\"SearchOutlined\"))),\n", 87 | " antd.Button(props=dict(shape=\"circle\", icon=icons.Icon(\"SearchOutlined\"))),\n", 88 | " ]),\n", 89 | " antd.Col(props=dict(span=8), children=[\n", 90 | " antd.Button(children=[\"Button\"], props=dict(type=\"text\")),\n", 91 | " ]),\n", 92 | " antd.Col(props=dict(span=8), children=[\n", 93 | " antd.Button(children=[\"Button\"], props=dict(type=\"primary\", size=\"small\")),\n", 94 | " antd.Button(children=[\"Button\"], props=dict(size=\"small\")),\n", 95 | " ]),\n", 96 | " antd.Col(props=dict(span=8), children=[\n", 97 | " antd.Button(children=[\"Button\"], props=dict(type=\"primary\", shape=\"round\", size=\"small\")),\n", 98 | " antd.Button(props=dict(type=\"primary\", shape=\"circle\", icon=icons.Icon(\"SearchOutlined\"), size=\"small\")),\n", 99 | " antd.Button(props=dict(shape=\"circle\", icon=icons.Icon(\"SearchOutlined\"), size=\"small\")),\n", 100 | " ]),\n", 101 | " antd.Col(props=dict(span=8), children=[\n", 102 | " antd.Button(children=[\"Button\"], props=dict(type=\"text\", size=\"small\")),\n", 103 | " ]),\n", 104 | " \n", 105 | " # sliders\n", 106 | " antd.Col(props=dict(span=8), children=[\n", 107 | " antd.Slider(value=0, props=dict(min=0, max=100)),\n", 108 | " ]),\n", 109 | " antd.Col(props=dict(span=8), children=[\n", 110 | " slider_range := antd.Slider(value=[20, 70], props=dict(min=0, max=100, range=True)),\n", 111 | " ]),\n", 112 | " antd.Col(props=dict(span=8), children=[\n", 113 | " slider := antd.Slider(value=30, props=dict(min=0, max=100, tooltip=dict(open=True))),\n", 114 | " ]),\n", 115 | "\n", 116 | " # select\n", 117 | " antd.Col(props=dict(span=8), children=[\n", 118 | " select := antd.Select(value=\"pear\", props=dict(options=options, style=dict(width=\"120px\"))),\n", 119 | " ]),\n", 120 | " antd.Col(props=dict(span=8), children=[\n", 121 | " multi_select := antd.Select(value=[\"cherry\"], props=dict(options=options, style=dict(width=\"220px\"), mode=\"multiple\"))\n", 122 | " ]),\n", 123 | " antd.Col(props=dict(span=8), children=[\n", 124 | " # only supported in 5.31+\n", 125 | " # antd.Select(value=\"pear\", props=dict(options=options, variant=\"filled\", style=dict(width=\"120px\"))),\n", 126 | " ]),\n", 127 | " \n", 128 | " # misc\n", 129 | " antd.Col(props=dict(span=8), children=[\n", 130 | " switch := antd.Switch(value=True),\n", 131 | " ]),\n", 132 | " antd.Col(props=dict(span=8), children=[\n", 133 | " dropdown := antd.Dropdown(props=dict(menu=dict(items=menu_items), trigger=[\"click\"]), children=\n", 134 | " [ipyreact.Widget(_type=\"span\", children=[\"Menu\"])]\n", 135 | " ),\n", 136 | " ]),\n", 137 | " antd.Col(props=dict(span=8), children=[\n", 138 | " dialog := antd.Modal(events={\"onOk\": dialog_close, \"onCancel\": dialog_close}, children=[\n", 139 | " ipyreact.Widget(_type=\"div\", children=[text])\n", 140 | " ]),\n", 141 | " antd.Button(children=[\"Dialog\"], props=dict(type=\"primary\"), events={\"onClick\": dialog_open}),\n", 142 | " ]),\n", 143 | "\n", 144 | " \n", 145 | " ]),\n", 146 | " output := ipywidgets.Output()\n", 147 | "])\n", 148 | "select.observe(show_value, \"value\")\n", 149 | "multi_select.observe(show_value, \"value\")\n", 150 | "slider.observe(show_value, \"value\")\n", 151 | "slider_range.observe(show_value, \"value\")\n", 152 | "switch.observe(show_value, \"value\")\n", 153 | "with output:\n", 154 | " print(\" \")\n", 155 | "demo" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "bcfd2971", 161 | "metadata": {}, 162 | "source": [ 163 | "# Solara component interface\n", 164 | "\n", 165 | "Coming soon" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "id": "8b2bc0b3", 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "import ipyantd.icons.widgets as icons" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "id": "d4ca25cc", 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "# import ipyantd.components as antd\n", 186 | "# # import solara\n", 187 | "# import reacton\n", 188 | "# import ipyreact" 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "id": "5fdc936b", 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "# @reacton.component\n", 199 | "# def Page():\n", 200 | "# with antd.Flex(props=dict(style={\"padding\": \"24px\"}, gap=\"small\")) as m:\n", 201 | "# antd.Button(children=[\"Button\"], props=dict(type=\"primary\"))\n", 202 | "# antd.Button(children=[\"Button\"])\n", 203 | "# antd.Button(children=[\"Button\"], props=dict(type=\"primary\", shape=\"round\"))\n", 204 | "# antd.Button(children=[\"Button\"], props=dict(type=\"primary\", shape=\"round\"))\n", 205 | "#