├── .cursorrules ├── .gitattributes ├── .gitconfig ├── .github └── workflows │ ├── deploy.yaml.off │ ├── llms.yaml │ └── test.yaml.off ├── .gitignore ├── CHANGELOG.bak ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── MonsterUI.jpg ├── api_reference │ ├── api_reference.py │ └── logo.svg ├── apilist.txt ├── cf_addns.py ├── createllms.sh ├── custom_theme.css ├── data │ ├── mail.json │ ├── status_list.json │ └── statuses.json ├── examples │ ├── auth.py │ ├── cards.py │ ├── dashboard.py │ ├── data │ │ ├── mail.json │ │ ├── status_list.json │ │ └── statuses.json │ ├── forms.py │ ├── mail.py │ ├── music.py │ ├── playground.py │ ├── scrollspy.py │ ├── tasks.py │ └── ticket.py ├── favicon.ico ├── getting_started │ ├── GettingStarted.md │ ├── StylingRulesOfThumb.py │ └── app_product_gallery.py ├── guides │ ├── Layout.ipynb │ └── Spacing.ipynb ├── htmxindicator.py ├── llms-ctx-full.txt ├── llms-ctx.txt ├── llms.txt ├── main.py ├── requirements.txt ├── updatellms.sh └── utils.py ├── monsterui ├── __init__.py ├── _modidx.py ├── all.py ├── core.py ├── daisy.py ├── foundations.py ├── franken.py └── icon.iife.js ├── nbs ├── .!32603!screenshot_cropped.png ├── .last_checked ├── 00_foundation.ipynb ├── 01_core.ipynb ├── 02_franken.ipynb ├── 03_daisy.ipynb ├── _quarto.yml ├── logo.svg ├── nbdev.yml ├── screenshot.png ├── screenshot_cropped.png ├── sidebar.yml └── styles.css ├── pyproject.toml ├── settings.ini ├── setup.py └── testnavbar.py /.cursorrules: -------------------------------------------------------------------------------- 1 | I am trying to make the python code as consistent with tailwind as possible. For example `large` should be abbreviated `lg` like in tailwind. -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb merge=nbdev-merge 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | # Generated by nbdev_install_hooks 2 | # 3 | # If you need to disable this instrumentation do: 4 | # git config --local --unset include.path 5 | # 6 | # To restore: 7 | # git config --local include.path ../.gitconfig 8 | # 9 | [merge "nbdev-merge"] 10 | name = resolve conflicts with nbdev_fix 11 | driver = nbdev_merge %O %A %B %P 12 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml.off: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | permissions: 4 | contents: write 5 | pages: write 6 | 7 | on: 8 | push: 9 | branches: [ "main", "master" ] 10 | workflow_dispatch: 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: [uses: fastai/workflows/quarto-ghp@master] 15 | -------------------------------------------------------------------------------- /.github/workflows/llms.yaml: -------------------------------------------------------------------------------- 1 | name: Update API Lists and Context Files 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # or whatever your default branch is named 7 | 8 | jobs: 9 | update-docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.x' 18 | 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install -e . 23 | pip install fastcore llms-txt pysymbol_llm 24 | pip install -r docs/requirements.txt 25 | - name: Run update script 26 | run: | 27 | cd docs 28 | chmod +x updatellms.sh 29 | ./updatellms.sh 30 | 31 | - name: Commit changes 32 | run: | 33 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 34 | git config --local user.name "github-actions[bot]" 35 | git add docs/llms.txt docs/llms-ctx.txt docs/llms-ctx-full.txt docs/apilist.txt 36 | git commit -m "Auto-update API lists and context files" || echo "No changes to commit" 37 | git push 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml.off: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [workflow_dispatch, pull_request, push] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: [uses: fastai/workflows/nbdev-ci@master] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | .sesskey 3 | docs/static 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | _docs/* 9 | _proc/* 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 114 | .pdm.toml 115 | .pdm-python 116 | .pdm-build/ 117 | 118 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 119 | __pypackages__/ 120 | 121 | # Celery stuff 122 | celerybeat-schedule 123 | celerybeat.pid 124 | 125 | # SageMath parsed files 126 | *.sage.py 127 | 128 | # Environments 129 | .env 130 | .venv 131 | env/ 132 | venv/ 133 | ENV/ 134 | env.bak/ 135 | venv.bak/ 136 | uv.lock 137 | 138 | # Spyder project settings 139 | .spyderproject 140 | .spyproject 141 | 142 | # Rope project settings 143 | .ropeproject 144 | 145 | # mkdocs documentation 146 | /site 147 | 148 | # mypy 149 | .mypy_cache/ 150 | .dmypy.json 151 | dmypy.json 152 | 153 | # Pyre type checker 154 | .pyre/ 155 | 156 | # pytype static type analyzer 157 | .pytype/ 158 | 159 | # Cython debug symbols 160 | cython_debug/ 161 | 162 | # PyCharm 163 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 164 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 165 | # and can be added to the global gitignore or merged into this file. For a more nuclear 166 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 167 | .idea/ 168 | -------------------------------------------------------------------------------- /CHANGELOG.bak: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | 4 | 5 | ## 1.0.20 6 | 7 | ### New Features 8 | 9 | - Add `icon` header param to optionally not bring in icons js lib ([#99](https://github.com/AnswerDotAI/MonsterUI/issues/99)) 10 | 11 | ### Bugs Squashed 12 | 13 | - added fh. to unqid ([#98](https://github.com/AnswerDotAI/MonsterUI/pull/98)), thanks to [@MorsCerta-crypto](https://github.com/MorsCerta-crypto) 14 | 15 | 16 | ## 1.0.19 17 | 18 | ### New Features 19 | 20 | - Add accordion component ([#94](https://github.com/AnswerDotAI/MonsterUI/pull/94)), thanks to [@MichlF](https://github.com/MichlF) 21 | - Fix theme logic ([#93](https://github.com/AnswerDotAI/MonsterUI/pull/93)), thanks to [@curtis-allan](https://github.com/curtis-allan) 22 | 23 | 24 | ## 1.0.18 25 | 26 | - Hotfix accidental deletion of label select :( 27 | 28 | 29 | ## 1.0.16 30 | 31 | ### Bugs Squashed 32 | 33 | - Select not properly checking presence of hx-trigger 34 | 35 | ## 1.0.14 36 | 37 | ### New Features 38 | 39 | - Move CDN's to jsdelivr ([#88](https://github.com/AnswerDotAI/MonsterUI/pull/88)), thanks to [@curtis-allan](https://github.com/curtis-allan) 40 | 41 | ### Bugs Squashed 42 | 43 | - Select htmx compatibility bug 44 | - SVG Path Name Collision Issue in MonsterUI ([#85](https://github.com/AnswerDotAI/MonsterUI/issues/85)) 45 | 46 | 47 | ## 1.0.13 48 | 49 | ### Bugs Squashed 50 | 51 | - LabelCheckboxX ignores id set manually ([#80](https://github.com/AnswerDotAI/MonsterUI/issues/80)) 52 | - Select sending multiple values to HTMX 53 | 54 | 55 | ## 1.0.10 56 | 57 | ### New Features 58 | 59 | - Cusom Themes support in ThemePicker ([#71](https://github.com/AnswerDotAI/MonsterUI/pull/71)), thanks to [@ndendic](https://github.com/ndendic) 60 | 61 | 62 | ## 1.0.8 63 | 64 | - Add lightbox and Insertable select 65 | 66 | ## 1.0.7 67 | 68 | ### New Features 69 | 70 | - Add select kwargs to Select and LabelSelect ([#70](https://github.com/AnswerDotAI/MonsterUI/issues/70)) 71 | 72 | ### Bugs Squashed 73 | 74 | - Update component classes to align with Franken UI v2.0 ([#67](https://github.com/AnswerDotAI/MonsterUI/pull/67)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 75 | - Updated ContainerT, CardT, and TableT class names to match the documented patterns from Franken UI v2.0 docs 76 | 77 | ## 1.0.6 78 | 79 | 80 | ### Bugs Squashed 81 | 82 | - Update component classes to align with Franken UI v2.0 ([#67](https://github.com/AnswerDotAI/MonsterUI/pull/67)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 83 | 84 | 85 | ## 1.0.5 86 | 87 | - Add Center component 88 | 89 | ## 1.0.4 90 | 91 | ### New Features 92 | 93 | - Bug fix to correct theming conflict in theme initialization 94 | 95 | 96 | ## 1.0.2 97 | 98 | - Bug fix to add dark mode theme selector to TW config, thanks to [@curtis-allan](https://github.com/curtis-allan) 99 | 100 | ## 1.0.1 101 | 102 | - Theme bug fix not allowing theme changes to stick, thanks to [@zaseem-bisquared](https://github.com/Zaseem-BIsquared) 103 | - Documentation bug fix on tutorial app, thanks to [@decherd](https://github.com/decherd) 104 | 105 | 106 | ## 1.0.0 107 | 108 | ### New Features 109 | 110 | - Migrated to use FrankenUI version 2.0 version 111 | 112 | - Brand new simplified NavBar api, including enhanced sticky and scrollspy options, thanks to [@ohmeow](https://github.com/ohmeow) and [@curtis-allan](https://github.com/curtis-allan) 113 | 114 | - Latex rendering enabled via katex ([#58](https://github.com/AnswerDotAI/MonsterUI/pull/58)), thanks to [@algal](https://github.com/algal) 115 | 116 | - Add API References and Guides to llms.txt ([#54](https://github.com/AnswerDotAI/MonsterUI/issues/54)) 117 | 118 | - New Range component, which includes min/max range 119 | 120 | - New center component thanks to inspiration from Carson of HTMX ([#52](https://github.com/AnswerDotAI/MonsterUI/issues/52)) 121 | 122 | - Better Theme Picker ([#51](https://github.com/AnswerDotAI/MonsterUI/issues/51)) 123 | 124 | - Upload Zone and Upload Button Components added ([#50](https://github.com/AnswerDotAI/MonsterUI/issues/50)) 125 | 126 | ### Bugs Squashed 127 | 128 | - fix: correct PaddingT class naming for padding variants ([#55](https://github.com/AnswerDotAI/MonsterUI/pull/55)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 129 | 130 | 131 | ## 0.0.34 132 | 133 | 134 | ### Bugs Squashed 135 | 136 | - Table markdown rendering bug fix ([#46](https://github.com/AnswerDotAI/MonsterUI/issues/46)) 137 | 138 | 139 | ## 0.0.33 140 | 141 | ### New Features 142 | 143 | - Add subtitle function for common semantic styling option ([#44](https://github.com/AnswerDotAI/MonsterUI/pull/44)) 144 | 145 | - Add semantic text styling to docs examples ([#42](https://github.com/AnswerDotAI/MonsterUI/pull/42)) 146 | 147 | - Created higher level Navbars function for auto-responsive collapse to mobile and easier interface ([#33](https://github.com/AnswerDotAI/MonsterUI/issues/33)), thanks to @curtis-allan 148 | 149 | - Added Scrollspy to Nav and NavBar and example that demonstrates it ([#31](https://github.com/AnswerDotAI/MonsterUI/issues/31)), thanks to @99ch 150 | 151 | - Added Highlight JS integration to headers ([#28](https://github.com/AnswerDotAI/MonsterUI/issues/28)), thanks to @99ch 152 | 153 | ### Bugs Squashed 154 | 155 | - Improve defaults for Navbar when components are passed so it doesn't override styling specified toby user ([#43](https://github.com/AnswerDotAI/MonsterUI/pull/43)) 156 | 157 | 158 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | 4 | 5 | ## 1.0.21 6 | 7 | ### New Features 8 | 9 | - The new `ApexChart` component can create line charts, pie charts and [more](https://monsterui.answer.ai/api_ref/docs_charts). ([#110](https://github.com/AnswerDotAI/MonsterUI/pull/110)), thanks to [@ndendic](https://github.com/ndendic) 10 | - Toasts now disappear automatically after 5 seconds. You can adjust the duration by using the `dur` field (e.g. `Toast('My Toast', dur=10.0)`). ([#109](https://github.com/AnswerDotAI/MonsterUI/pull/109)), thanks to [@comhar](https://github.com/comhar) 11 | 12 | ### Breaking Change 13 | - Katex is no longer included in the default headers. To include it set `katex=True` when calling `.headers`. (i.e. `Theme.slate.headers(katex=True)`). ([#105](https://github.com/AnswerDotAI/MonsterUI/pull/105)), thanks to [@comhar](https://github.com/comhar) 14 | 15 | 16 | ## 1.0.20 17 | 18 | ### New Features 19 | 20 | - Add `icon` header param to optionally not bring in icons js lib ([#99](https://github.com/AnswerDotAI/MonsterUI/issues/99)) 21 | 22 | ### Bugs Squashed 23 | 24 | - added fh. to unqid ([#98](https://github.com/AnswerDotAI/MonsterUI/pull/98)), thanks to [@MorsCerta-crypto](https://github.com/MorsCerta-crypto) 25 | 26 | 27 | ## 1.0.19 28 | 29 | ### New Features 30 | 31 | - Add accordion component ([#94](https://github.com/AnswerDotAI/MonsterUI/pull/94)), thanks to [@MichlF](https://github.com/MichlF) 32 | - Fix theme logic ([#93](https://github.com/AnswerDotAI/MonsterUI/pull/93)), thanks to [@curtis-allan](https://github.com/curtis-allan) 33 | 34 | 35 | ## 1.0.18 36 | 37 | - Hotfix accidental deletion of label select :( 38 | 39 | 40 | ## 1.0.16 41 | 42 | ### Bugs Squashed 43 | 44 | - Select not properly checking presence of hx-trigger 45 | 46 | ## 1.0.14 47 | 48 | ### New Features 49 | 50 | - Move CDN's to jsdelivr ([#88](https://github.com/AnswerDotAI/MonsterUI/pull/88)), thanks to [@curtis-allan](https://github.com/curtis-allan) 51 | 52 | ### Bugs Squashed 53 | 54 | - Select htmx compatibility bug 55 | - SVG Path Name Collision Issue in MonsterUI ([#85](https://github.com/AnswerDotAI/MonsterUI/issues/85)) 56 | 57 | 58 | ## 1.0.13 59 | 60 | ### Bugs Squashed 61 | 62 | - LabelCheckboxX ignores id set manually ([#80](https://github.com/AnswerDotAI/MonsterUI/issues/80)) 63 | - Select sending multiple values to HTMX 64 | 65 | 66 | ## 1.0.10 67 | 68 | ### New Features 69 | 70 | - Cusom Themes support in ThemePicker ([#71](https://github.com/AnswerDotAI/MonsterUI/pull/71)), thanks to [@ndendic](https://github.com/ndendic) 71 | 72 | 73 | ## 1.0.8 74 | 75 | - Add lightbox and Insertable select 76 | 77 | ## 1.0.7 78 | 79 | ### New Features 80 | 81 | - Add select kwargs to Select and LabelSelect ([#70](https://github.com/AnswerDotAI/MonsterUI/issues/70)) 82 | 83 | ### Bugs Squashed 84 | 85 | - Update component classes to align with Franken UI v2.0 ([#67](https://github.com/AnswerDotAI/MonsterUI/pull/67)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 86 | - Updated ContainerT, CardT, and TableT class names to match the documented patterns from Franken UI v2.0 docs 87 | 88 | ## 1.0.6 89 | 90 | 91 | ### Bugs Squashed 92 | 93 | - Update component classes to align with Franken UI v2.0 ([#67](https://github.com/AnswerDotAI/MonsterUI/pull/67)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 94 | 95 | 96 | ## 1.0.5 97 | 98 | - Add Center component 99 | 100 | ## 1.0.4 101 | 102 | ### New Features 103 | 104 | - Bug fix to correct theming conflict in theme initialization 105 | 106 | 107 | ## 1.0.2 108 | 109 | - Bug fix to add dark mode theme selector to TW config, thanks to [@curtis-allan](https://github.com/curtis-allan) 110 | 111 | ## 1.0.1 112 | 113 | - Theme bug fix not allowing theme changes to stick, thanks to [@zaseem-bisquared](https://github.com/Zaseem-BIsquared) 114 | - Documentation bug fix on tutorial app, thanks to [@decherd](https://github.com/decherd) 115 | 116 | 117 | ## 1.0.0 118 | 119 | ### New Features 120 | 121 | - Migrated to use FrankenUI version 2.0 version 122 | 123 | - Brand new simplified NavBar api, including enhanced sticky and scrollspy options, thanks to [@ohmeow](https://github.com/ohmeow) and [@curtis-allan](https://github.com/curtis-allan) 124 | 125 | - Latex rendering enabled via katex ([#58](https://github.com/AnswerDotAI/MonsterUI/pull/58)), thanks to [@algal](https://github.com/algal) 126 | 127 | - Add API References and Guides to llms.txt ([#54](https://github.com/AnswerDotAI/MonsterUI/issues/54)) 128 | 129 | - New Range component, which includes min/max range 130 | 131 | - New center component thanks to inspiration from Carson of HTMX ([#52](https://github.com/AnswerDotAI/MonsterUI/issues/52)) 132 | 133 | - Better Theme Picker ([#51](https://github.com/AnswerDotAI/MonsterUI/issues/51)) 134 | 135 | - Upload Zone and Upload Button Components added ([#50](https://github.com/AnswerDotAI/MonsterUI/issues/50)) 136 | 137 | ### Bugs Squashed 138 | 139 | - fix: correct PaddingT class naming for padding variants ([#55](https://github.com/AnswerDotAI/MonsterUI/pull/55)), thanks to [@Zaseem-BIsquared](https://github.com/Zaseem-BIsquared) 140 | 141 | 142 | ## 0.0.34 143 | 144 | 145 | ### Bugs Squashed 146 | 147 | - Table markdown rendering bug fix ([#46](https://github.com/AnswerDotAI/MonsterUI/issues/46)) 148 | 149 | 150 | ## 0.0.33 151 | 152 | ### New Features 153 | 154 | - Add subtitle function for common semantic styling option ([#44](https://github.com/AnswerDotAI/MonsterUI/pull/44)) 155 | 156 | - Add semantic text styling to docs examples ([#42](https://github.com/AnswerDotAI/MonsterUI/pull/42)) 157 | 158 | - Created higher level Navbars function for auto-responsive collapse to mobile and easier interface ([#33](https://github.com/AnswerDotAI/MonsterUI/issues/33)), thanks to @curtis-allan 159 | 160 | - Added Scrollspy to Nav and NavBar and example that demonstrates it ([#31](https://github.com/AnswerDotAI/MonsterUI/issues/31)), thanks to @99ch 161 | 162 | - Added Highlight JS integration to headers ([#28](https://github.com/AnswerDotAI/MonsterUI/issues/28)), thanks to @99ch 163 | 164 | ### Bugs Squashed 165 | 166 | - Improve defaults for Navbar when components are passed so it doesn't override styling specified toby user ([#43](https://github.com/AnswerDotAI/MonsterUI/pull/43)) 167 | 168 | 169 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | ## Library Contributions 4 | 5 | > The code for all components in the library in the Jupyter notebooks (.ipynb files). Edit those, not the .py files. 6 | 7 | This is an [nbdev](https://nbdev.fast.ai/) library. The notebooks are located in `nbs`. 8 | 9 | ### Exporting the Modules 10 | 11 | You can use `nbdev_export` to export the notebooks to the library directory, `monsterui`. 12 | 13 | ### Cleaning NB metadad 14 | 15 | You can use `nbdev_clean` to clean the nb metadata from the .py files. 16 | 17 | ## Docs Contributions 18 | 19 | The docs are run using [FastHTML](https://fastht.ml/) and can be run locally with: 20 | 21 | ```bash 22 | cd docs 23 | pip install -r requirements.txt 24 | python main.py 25 | ``` 26 | 27 | This will start up the MonsterUI documentation site, which is a FastHTML app. Then you can see the site locally at http://localhost:5001/ 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022, fastai 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-exclude * __pycache__ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MonsterUI 2 | 3 | MonsterUI is a UI framework for FastHTML for building beautiful web interfaces with minimal code. It combines the simplicity of Python with the power of Tailwind. Perfect for data scientists, ML engineers, and developers who want to quickly turn their Python code into polished web apps without the complexity of traditional UI frameworks. Follows semantic HTML patterns when possible. 4 | 5 | MonsterUI adds the following Tailwind-based libraries [Franken UI](https://franken-ui.dev/) and [DaisyUI](https://daisyui.com/) to FastHTML, as well as Python's [Mistletoe](https://github.com/miyuchina/mistletoe) for Markdown, [HighlightJS](https://highlightjs.org/) for code highlighting, and [Katex](https://katex.org/) for latex support. 6 | 7 | # Getting Started 8 | 9 | 10 | ## Installation 11 | 12 | To install this library, uses 13 | 14 | `pip install MonsterUI` 15 | 16 | ## Getting Started 17 | 18 | ### TLDR 19 | 20 | Run `python file.py` on this to start: 21 | 22 | ``` python 23 | from fasthtml.common import * 24 | from monsterui.all import * 25 | 26 | # Choose a theme color (blue, green, red, etc) 27 | hdrs = Theme.blue.headers() 28 | 29 | # Create your app with the theme 30 | app, rt = fast_app(hdrs=hdrs) 31 | 32 | @rt 33 | def index(): 34 | socials = (('github','https://github.com/AnswerDotAI/MonsterUI'), 35 | ('twitter','https://twitter.com/isaac_flath/'), 36 | ('linkedin','https://www.linkedin.com/in/isaacflath/')) 37 | return Titled("Your First App", 38 | Card( 39 | H1("Welcome!"), 40 | P("Your first MonsterUI app", cls=TextPresets.muted_sm), 41 | P("I'm excited to see what you build with MonsterUI!"), 42 | footer=DivLAligned(*[UkIconLink(icon,href=url) for icon,url in socials]))) 43 | 44 | serve() 45 | ``` 46 | 47 | ## LLM context files 48 | 49 | Using LLMs for development is a best practice way to get started and 50 | explore. While LLMs cannot code for you, they can be helpful assistants. 51 | You must check, refactor, test, and vet any code any LLM generates for 52 | you - but they are helpful productivity tools. Take a look inside the 53 | `llms.txt` file to see links to particularly useful context files! 54 | 55 | - [llms.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms.txt): Links to what is included 56 | - [llms-ctx.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms-ctx.txt): MonsterUI Documentation Pages 57 | - [API list](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/apilist.txt): API list for MonsterUI (included in llms-ctx.txt) 58 | - [llms-ctx-full.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms-ctx-full.txt): Full context that includes all api reference pages as markdown 59 | 60 | In addition you can add `/md` (for markdown) to a url to get a markdown representation and `/rmd` for rendered markdown representation (nice for looking to see what would be put into context. 61 | 62 | ### Step by Step 63 | 64 | To get started, check out: 65 | 66 | 1. Start by importing the modules as follows: 67 | 68 | ``` python 69 | from fasthtml.common import * 70 | from monsterui.all import * 71 | ``` 72 | 73 | 2. Instantiate the app with the MonsterUI headers 74 | 75 | ``` python 76 | app = FastHTML(hdrs=Theme.blue.headers()) 77 | 78 | # Alternatively, using the fast_app method 79 | app, rt = fast_app(hdrs=Theme.slate.headers()) 80 | ``` 81 | 82 | > *The color option can be any of the theme options available out of the 83 | > box* 84 | 85 | > `katex` and `highlightjs` are not included by default. To include them set `katex=True` or `highlightjs=True` when calling `.headers`. (i.e. `Theme.slate.headers(katex=True)`)* 86 | 87 | From here, you can explore the API Reference & examples to see how to 88 | implement the components. You can also check out these demo videos to as 89 | a quick start guide: 90 | 91 | - MonsterUI [documentation page and Tutorial 92 | app](https://monsterui.answer.ai/tutorial_app) 93 | - Isaac & Hamel : [Building his website’s team 94 | page](https://youtu.be/22Jn46-mmM0) 95 | - Isaac & Audrey : [Building a blog](https://youtu.be/gVWAsywxLXE) 96 | - Isaac : [Building a blog](https://youtu.be/22NJgfAqgko) 97 | 98 | More resources and improvements to the documentation will be added here 99 | soon! 100 | -------------------------------------------------------------------------------- /docs/MonsterUI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/docs/MonsterUI.jpg -------------------------------------------------------------------------------- /docs/api_reference/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/cf_addns.py: -------------------------------------------------------------------------------- 1 | 2 | # https://github.com/cloudflare/cloudflare-python/blob/main/api.md 3 | from fastcore.script import * 4 | from cloudflare import Cloudflare 5 | 6 | @call_parse 7 | def add_dns_record( 8 | record_type: str, # Type of DNS record (CNAME or A) 9 | target: str, # Target IP address or domain name 10 | record: str, # Record name (without the zone) 11 | zone: str, # Zone name 12 | proxied: bool_arg=True # Use CF proxy? 13 | ): 14 | cf = Cloudflare() 15 | zones = cf.zones.list(name=zone) 16 | if not zones: raise ValueError(f"Zone '{zone}' not found") 17 | zid = zones.result[0].id 18 | cf.dns.records.create(zone_id=zid, type=record_type.upper(), name=f"{record}.{zone}", content=target, proxied=proxied) 19 | -------------------------------------------------------------------------------- /docs/createllms.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from fastcore.utils import * 4 | import httpx 5 | import api_reference.api_reference as api_reference 6 | def fname2title(ref_fn_name): return ref_fn_name[5:].replace('_',' | ').title() 7 | 8 | def create_llms_txt(): 9 | # Get examples 10 | base = "https://monsterui.answer.ai" 11 | examples = [f for f in Path('examples').glob('*.py') if not f.name.startswith('__') and f.name.endswith('.py')] 12 | example_links = [] 13 | for f in examples: 14 | content = httpx.get(f"{base}/{f.stem}/md").text 15 | first_line = content.split('\n')[0].strip('# ').strip('"') 16 | example_links.append(f"[{f.stem.title()}]({base}/{f.name[:-3]}/md): {first_line}") 17 | 18 | # Get reference files with their H1 headings 19 | reference_fns = L([o for o in dir(api_reference) if o.startswith('docs_')]) 20 | api_links = [] 21 | for f in reference_fns: 22 | content = httpx.get(f"{base}/api_ref/{f}/md").text 23 | first_heading = content.split('\n')[0].strip('# ') 24 | api_links.append(f"[{fname2title(f)}]({base}/api_ref/{f}/md): {first_heading}") 25 | 26 | # Create content 27 | content = [ 28 | "# MonsterUI Documentation", 29 | ''' 30 | > MonsterUI is a python library which brings styling to python for FastHTML apps. 31 | 32 | ''' 33 | "## API Reference", 34 | '- [API List](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/apilist.txt): Complete API Reference', 35 | "", 36 | "## Examples", 37 | *[f'- {a}' for a in example_links], 38 | "", 39 | "## Optional", 40 | *[f'- {a}' for a in api_links], 41 | "- [Layout](https://monsterui.answer.ai/tutorial_layout/md): MonsterUI Page Layout Guide", 42 | "- [Spacing](https://monsterui.answer.ai/tutorial_spacing/md): Padding & Margin & Spacing, Oh my! (MonsterUI Spacing Guide)", 43 | ] 44 | 45 | # Write to file 46 | Path('llms.txt').write_text('\n'.join(content)) 47 | 48 | create_llms_txt() 49 | -------------------------------------------------------------------------------- /docs/custom_theme.css: -------------------------------------------------------------------------------- 1 | /* custom_theme.css */ 2 | 3 | .uk-theme-grass { 4 | --background: 78 47% 99%; 5 | --foreground: 78 51% 0%; 6 | --muted: 78 12% 85%; 7 | --muted-foreground: 78 8% 38%; 8 | --popover: 78 47% 99%; 9 | --popover-foreground: 78 51% 0%; 10 | --card: 78 47% 99%; 11 | --card-foreground: 78 51% 0%; 12 | --border: 78 2% 93%; 13 | --input: 78 2% 93%; 14 | --primary: 78 28% 60%; 15 | --primary-foreground: 0 0% 0%; 16 | --secondary: 78 8% 81%; 17 | --secondary-foreground: 78 8% 21%; 18 | --accent: 78 8% 81%; 19 | --accent-foreground: 78 8% 21%; 20 | --destructive: 17 86% 32%; 21 | --destructive-foreground: 17 86% 92%; 22 | --ring: 78 28% 60%; 23 | --chart-1: 78 28% 60%; 24 | --chart-2: 78 8% 81%; 25 | --chart-3: 78 8% 81%; 26 | --chart-4: 78 8% 84%; 27 | --chart-5: 78 31% 60%; 28 | --radius: 0.5rem; 29 | } 30 | 31 | .dark.uk-theme-grass { 32 | --background: 78 48% 2%; 33 | --foreground: 78 24% 99%; 34 | --muted: 78 12% 15%; 35 | --muted-foreground: 78 8% 62%; 36 | --popover: 78 48% 2%; 37 | --popover-foreground: 78 24% 99%; 38 | --card: 78 48% 2%; 39 | --card-foreground: 78 24% 99%; 40 | --border: 78 2% 14%; 41 | --input: 78 2% 14%; 42 | --primary: 78 28% 60%; 43 | --primary-foreground: 0 0% 0%; 44 | --secondary: 78 8% 14%; 45 | --secondary-foreground: 78 8% 74%; 46 | --accent: 78 8% 14%; 47 | --accent-foreground: 78 8% 74%; 48 | --destructive: 17 86% 60%; 49 | --destructive-foreground: 17 86% 0%; 50 | --ring: 78 28% 60%; 51 | --chart-1: 78 28% 60%; 52 | --chart-2: 78 8% 14%; 53 | --chart-3: 78 8% 14%; 54 | --chart-4: 78 8% 17%; 55 | --chart-5: 78 31% 60%; 56 | } -------------------------------------------------------------------------------- /docs/data/mail.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775a", 4 | "initial": "WS", 5 | "name": "William Smith", 6 | "email": "williamsmith@example.com", 7 | "subject": "Meeting Tomorrow", 8 | "text": "Hi, let's have a meeting tomorrow to discuss the project. I've been reviewing the project details and have some ideas I'd like to share. It's crucial that we align on our next steps to ensure the project's success.\n\nPlease come prepared with any questions or insights you may have. Looking forward to our meeting!\n\nBest regards, William", 9 | "date": "2023-10-22T09:00:00", 10 | "read": true, 11 | "active": true, 12 | "labels": ["meeting", "work", "important"] 13 | }, 14 | { 15 | "id": "110e8400-e29b-11d4-a716-446655440000", 16 | "initial": "AS", 17 | "name": "Alice Smith", 18 | "email": "alicesmith@example.com", 19 | "subject": "Re: Project Update", 20 | "text": "Thank you for the project update. It looks great! I've gone through the report, and the progress is impressive. The team has done a fantastic job, and I appreciate the hard work everyone has put in.\n\nI have a few minor suggestions that I'll include in the attached document.\n\nLet's discuss these during our next meeting. Keep up the excellent work!\n\nBest regards, Alice", 21 | "date": "2023-10-22T10:30:00", 22 | "read": true, 23 | "active": false, 24 | "labels": ["work", "important"] 25 | }, 26 | { 27 | "id": "3e7c3f6d-bdf5-46ae-8d90-171300f27ae2", 28 | "initial": "BJ", 29 | "name": "Bob Johnson", 30 | "email": "bobjohnson@example.com", 31 | "subject": "Weekend Plans", 32 | "text": "Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's been a while since we had some outdoor fun.\n\nIf you're interested, let me know, and we can plan the details. It'll be a great way to unwind and enjoy nature.\n\nLooking forward to your response!\n\nBest, Bob", 33 | "date": "2023-04-10T11:45:00", 34 | "read": true, 35 | "active": false, 36 | "labels": ["personal"] 37 | }, 38 | { 39 | "id": "61c35085-72d7-42b4-8d62-738f700d4b92", 40 | "initial": "ED", 41 | "name": "Emily Davis", 42 | "email": "emilydavis@example.com", 43 | "subject": "Re: Question about Budget", 44 | "text": "I have a question about the budget for the upcoming project. It seems like there's a discrepancy in the allocation of resources.\n\nI've reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project's quality.\n\nI've attached a detailed analysis for your reference. Let's discuss this further in our next meeting.\n\nThanks, Emily", 45 | "date": "2023-03-25T13:15:00", 46 | "read": false, 47 | "active": false, 48 | "labels": ["work", "budget"] 49 | }, 50 | { 51 | "id": "8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97", 52 | "initial": "MW", 53 | "name": "Michael Wilson", 54 | "email": "michaelwilson@example.com", 55 | "subject": "Important Announcement", 56 | "text": "I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We've received valuable feedback from our beta testers, and I believe it's time to make some adjustments to better meet our customers' needs.\n\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\n\nRegards, Michael", 57 | "date": "2023-03-10T15:00:00", 58 | "read": false, 59 | "active": false, 60 | "labels": ["meeting", "work", "important"] 61 | }, 62 | { 63 | "id": "1f0f2c02-e299-40de-9b1d-86ef9e42126b", 64 | "initial": "SB", 65 | "name": "Sarah Brown", 66 | "email": "sarahbrown@example.com", 67 | "subject": "Re: Feedback on Proposal", 68 | "text": "Thank you for your feedback on the proposal. It looks great! I'm pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\n\nI've attached the revised proposal for your review.\n\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\n\nBest regards, Sarah", 69 | "date": "2023-02-15T16:30:00", 70 | "read": true, 71 | "active": false, 72 | "labels": ["work"] 73 | }, 74 | { 75 | "id": "17c0a96d-4415-42b1-8b4f-764efab57f66", 76 | "initial": "DL", 77 | "name": "David Lee", 78 | "email": "davidlee@example.com", 79 | "subject": "New Project Idea", 80 | "text": "I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\n\nI've prepared a detailed proposal outlining the potential benefits and the strategy for execution.\n\nThis project has the potential to significantly impact our business positively. Let's set up a meeting to dive into the details and determine if it aligns with our current goals.\n\nBest regards, David", 81 | "date": "2023-01-28T17:45:00", 82 | "read": false, 83 | "active": false, 84 | "labels": ["meeting", "work", "important"] 85 | }, 86 | { 87 | "id": "2f0130cb-39fc-44c4-bb3c-0a4337edaaab", 88 | "initial": "OW", 89 | "name": "Olivia Wilson", 90 | "email": "oliviawilson@example.com", 91 | "subject": "Vacation Plans", 92 | "text": "Let's plan our vacation for next month. What do you think? I've been thinking of visiting a tropical paradise, and I've put together some destination options.\n\nI believe it's time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\n\nWe can start making arrangements to ensure a smooth and enjoyable trip.\n\nExcited to hear your thoughts! Olivia", 93 | "date": "2022-12-20T18:30:00", 94 | "read": true, 95 | "active": false, 96 | "labels": ["personal"] 97 | }, 98 | { 99 | "id": "de305d54-75b4-431b-adb2-eb6b9e546014", 100 | "initial": "JM", 101 | "name": "James Martin", 102 | "email": "jamesmartin@example.com", 103 | "subject": "Re: Conference Registration", 104 | "text": "I've completed the registration for the conference next month. The event promises to be a great networking opportunity, and I'm looking forward to attending the various sessions and connecting with industry experts.\n\nI've also attached the conference schedule for your reference.\n\nIf there are any specific topics or sessions you'd like me to explore, please let me know. It's an exciting event, and I'll make the most of it.\n\nBest regards, James", 105 | "date": "2022-11-30T19:15:00", 106 | "read": true, 107 | "active": false, 108 | "labels": ["work", "conference"] 109 | }, 110 | { 111 | "id": "7dd90c63-00f6-40f3-bd87-5060a24e8ee7", 112 | "initial": "SW", 113 | "name": "Sophia White", 114 | "email": "sophiawhite@example.com", 115 | "subject": "Team Dinner", 116 | "text": "Let's have a team dinner next week to celebrate our success. We've achieved some significant milestones, and it's time to acknowledge our hard work and dedication.\n\nI've made reservations at a lovely restaurant, and I'm sure it'll be an enjoyable evening.\n\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\n\nBest, Sophia", 117 | "date": "2022-11-05T20:30:00", 118 | "read": false, 119 | "active": false, 120 | "labels": ["meeting", "work"] 121 | }, 122 | { 123 | "id": "99a88f78-3eb4-4d87-87b7-7b15a49a0a05", 124 | "initial": "DJ", 125 | "name": "Daniel Johnson", 126 | "email": "danieljohnson@example.com", 127 | "subject": "Feedback Request", 128 | "text": "I'd like your feedback on the latest project deliverables. We've made significant progress, and I value your input to ensure we're on the right track.\n\nI've attached the deliverables for your review, and I'm particularly interested in any areas where you think we can further enhance the quality or efficiency.\n\nYour feedback is invaluable, and I appreciate your time and expertise. Let's work together to make this project a success.\n\nRegards, Daniel", 129 | "date": "2022-10-22T09:30:00", 130 | "read": false, 131 | "active": false, 132 | "labels": ["work"] 133 | }, 134 | { 135 | "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", 136 | "initial": "AT", 137 | "name": "Ava Taylor", 138 | "email": "avataylor@example.com", 139 | "subject": "Re: Meeting Agenda", 140 | "text": "Here's the agenda for our meeting next week. I've included all the topics we need to cover, as well as time allocations for each.\n\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\n\nIt's essential that our meeting is productive and addresses all relevant matters.\n\nLooking forward to our meeting! Ava", 141 | "date": "2022-10-10T10:45:00", 142 | "read": true, 143 | "active": false, 144 | "labels": ["meeting", "work"] 145 | }, 146 | { 147 | "id": "c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6", 148 | "initial": "WA", 149 | "name": "William Anderson", 150 | "email": "williamanderson@example.com", 151 | "subject": "Product Launch Update", 152 | "text": "The product launch is on track. I'll provide an update during our call. We've made substantial progress in the development and marketing of our new product.\n\nI'm excited to share the latest updates with you during our upcoming call. It's crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\n\nLet's make this product launch a resounding success!\n\nBest regards, William", 153 | "date": "2022-09-20T12:00:00", 154 | "read": false, 155 | "active": false, 156 | "labels": ["meeting", "work", "important"] 157 | }, 158 | { 159 | "id": "ba54eefd-4097-4949-99f2-2a9ae4d1a836", 160 | "initial": "MH", 161 | "name": "Mia Harris", 162 | "email": "miaharris@example.com", 163 | "subject": "Re: Travel Itinerary", 164 | "text": "I've received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I've reviewed the schedule and the accommodations, and everything seems to be in order. I'm looking forward to the trip, and I'm confident it'll be a smooth and enjoyable experience.\n\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\n\nExcited for the trip! Mia", 165 | "date": "2022-09-10T13:15:00", 166 | "read": true, 167 | "active": false, 168 | "labels": ["personal", "travel"] 169 | }, 170 | { 171 | "id": "df09b6ed-28bd-4e0c-85a9-9320ec5179aa", 172 | "initial": "EC", 173 | "name": "Ethan Clark", 174 | "email": "ethanclark@example.com", 175 | "subject": "Team Building Event", 176 | "text": "Let's plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I've done some research and have a few ideas for fun and engaging activities.\n\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\n\nTogether, we'll strengthen our team and boost our performance.\n\nRegards, Ethan", 177 | "date": "2022-08-25T15:30:00", 178 | "read": false, 179 | "active": false, 180 | "labels": ["meeting", "work"] 181 | }, 182 | { 183 | "id": "d67c1842-7f8b-4b4b-9be1-1b3b1ab4611d", 184 | "initial": "CH", 185 | "name": "Chloe Hall", 186 | "email": "chloehall@example.com", 187 | "subject": "Re: Budget Approval", 188 | "text": "The budget has been approved. We can proceed with the project. I'm delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\n\nI've attached the finalized budget for your reference. Let's ensure that we stay on track and deliver the project on time and within budget.\n\nIt's an exciting time for us! Chloe", 189 | "date": "2022-08-10T16:45:00", 190 | "read": true, 191 | "active": false, 192 | "labels": ["work", "budget"] 193 | }, 194 | { 195 | "id": "6c9a7f94-8329-4d70-95d3-51f68c186ae1", 196 | "initial": "ST", 197 | "name": "Samuel Turner", 198 | "email": "samuelturner@example.com", 199 | "subject": "Weekend Hike", 200 | "text": "Who's up for a weekend hike in the mountains? I've been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you're up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\n\nI've done some research and have a few routes in mind.\n\nLet me know if you're interested, and we can plan the details.\n\nIt's sure to be a memorable experience! Samuel", 201 | "date": "2022-07-28T17:30:00", 202 | "read": false, 203 | "active": false, 204 | "labels": ["personal"] 205 | } 206 | ] -------------------------------------------------------------------------------- /docs/data/statuses.json: -------------------------------------------------------------------------------- 1 | { 2 | "backlog": { 3 | "icon": "", 4 | "text": "Backlog" 5 | }, 6 | "cancelled": { 7 | "icon": "", 8 | "text": "Cancelled" 9 | }, 10 | "done": { 11 | "icon": "", 12 | "text": "Done" 13 | }, 14 | "progress": { 15 | "icon": "", 16 | "text": "In Progress" 17 | }, 18 | "todo": { 19 | "icon": "", 20 | "text": "Todo" 21 | } 22 | } -------------------------------------------------------------------------------- /docs/examples/auth.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Auth Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | from monsterui.all import * 5 | from fasthtml.svg import * 6 | 7 | app, rt = fast_app(hdrs=Theme.blue.headers()) 8 | 9 | @rt 10 | def index(): 11 | left = Div(cls="col-span-1 hidden flex-col justify-between bg-zinc-900 p-8 text-white lg:flex")( 12 | Div(cls=(TextT.bold))("Acme Inc"), 13 | Blockquote(cls="space-y-2")( 14 | P(cls=TextT.lg)('"This library has saved me countless hours of work and helped me deliver stunning designs to my clients faster than ever before."'), 15 | Footer(cls=TextT.sm)("Sofia Davis"))) 16 | 17 | right = Div(cls="col-span-2 flex flex-col p-8 lg:col-span-1")( 18 | DivRAligned(Button("Login", cls=ButtonT.ghost)), 19 | DivCentered(cls='flex-1')( 20 | Container( 21 | DivVStacked( 22 | H3("Create an account"), 23 | Small("Enter your email below to create your account", cls=TextT.muted)), 24 | Form( 25 | Input(placeholder="name@example.com"), 26 | Button(Span(cls="mr-2", uk_spinner="ratio: 0.54"), "Sign in with Email", cls=(ButtonT.primary, "w-full"), disabled=True), 27 | DividerSplit(Small("Or continue with"),cls=TextT.muted), 28 | Button(UkIcon('github',cls='mr-2'), "Github", cls=(ButtonT.default, "w-full")), 29 | cls='space-y-6'), 30 | DivVStacked(Small( 31 | "By clicking continue, you agree to our ", 32 | A(cls=AT.muted, href="#demo")("Terms of Service")," and ", 33 | A(cls=AT.muted, href="#demo")("Privacy Policy"),".", 34 | cls=(TextT.muted,"text-center"))), 35 | cls="space-y-6"))) 36 | 37 | return Title("Auth Example"),Grid(left,right,cols=2, gap=0,cls='h-screen') 38 | 39 | 40 | serve() 41 | -------------------------------------------------------------------------------- /docs/examples/cards.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Cards Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | from fasthtml.components import Uk_input_tag 5 | from fasthtml.svg import * 6 | from monsterui.all import * 7 | import calendar 8 | from datetime import datetime 9 | 10 | app, rt = fast_app(hdrs=Theme.blue.headers()) 11 | 12 | CreateAccount = Card( 13 | Grid(Button(DivLAligned(UkIcon('github'),Div('Github'))),Button('Google')), 14 | DividerSplit("OR CONTINUE WITH", text_cls=TextPresets.muted_sm), 15 | LabelInput('Email', id='email', placeholder='m@example.com'), 16 | LabelInput('Password', id='password',placeholder='Password', type='Password'), 17 | header=(H3('Create an Account'),Subtitle('Enter your email below to create your account')), 18 | footer=Button('Create Account',cls=(ButtonT.primary,'w-full'))) 19 | 20 | PaypalSVG_data = "M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z" 21 | AppleSVG_data = "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" 22 | Card1Svg = Svg(viewBox="0 0 24 24", fill="none", stroke="currentColor", stroke_linecap="round", stroke_linejoin="round", stroke_width="2", cls="h-6 w-6 mr-1")(Rect(width="20", height="14", x="2", y="5", rx="2"),Path(d="M2 10h20")) 23 | PaypalSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=PaypalSVG_data, fill="currentColor")), 24 | AppleSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=AppleSVG_data, fill="currentColor")) 25 | 26 | PaymentMethod = Card( 27 | Grid(Button(DivCentered(Card1Svg, "Card"), cls='h-20 border-2 border-primary'), 28 | Button(DivCentered(PaypalSvg, "PayPal"), cls='h-20'), 29 | Button(DivCentered(AppleSvg, "Apple"), cls='h-20')), 30 | Form(LabelInput('Name', id='name', placeholder='John Doe'), 31 | LabelInput('Card Number', id='card_number', placeholder='m@example.com'), 32 | Grid(LabelSelect(*Options(*calendar.month_name[1:],selected_idx=0),label='Expires',id='expire_month'), 33 | LabelSelect(*Options(*range(2024,2030),selected_idx=0), label='Year', id='expire_year'), 34 | LabelInput('CVV', id='cvv',placeholder='CVV', cls='mt-0'))), 35 | header=(H3('Payment Method'),Subtitle('Add a new payment method to your account.'))) 36 | 37 | area_opts = ('Team','Billing','Account','Deployment','Support') 38 | severity_opts = ('Severity 1 (Highest)', 'Severity 2', 'Severity 3', 'Severity 4 (Lowest)') 39 | ReportIssue = Card( 40 | Grid(Div(LabelSelect(*Options(*area_opts), label='Area', id='area')), 41 | Div(LabelSelect(*Options(*severity_opts),label='Severity',id='area'))), 42 | LabelInput( label='Subject', id='subject', placeholder='I need help with'), 43 | LabelTextArea( label='Description', id='description',placeholder='Please include all information relevant to your issue'), 44 | Div(FormLabel('Tags', fr='#tags'), 45 | Uk_input_tag(name="Tags",state="danger", value="Spam,Invalid", uk_cloak=True, id='tags')), 46 | header=(H3('Report Issue'),Subtitle('What area are you having problems with?')), 47 | footer = DivFullySpaced(Button('Cancel'), Button(cls=ButtonT.primary)('Submit'))) 48 | 49 | monster_desc ="Python-first beautifully designed components because you deserve to focus on features that matter and your app deserves to be beautiful from day one." 50 | MonsterUI = Card(H4("Monster UI"), 51 | Subtitle(monster_desc), 52 | DivLAligned( 53 | Div("Python"), 54 | DivLAligned(UkIcon('star'),Div("20k"), cls='space-x-1'), 55 | Div(datetime.now().strftime("%B %d, %Y")), 56 | cls=('space-x-4',TextPresets.muted_sm))) 57 | 58 | def CookieTableRow(heading, description, active=False): 59 | return Tr(Td(H5(heading)), 60 | Td(P(description, cls=TextPresets.muted_sm)), 61 | Td(Switch(checked=active))) 62 | 63 | CookieSettings = Card( 64 | Table(Tbody( 65 | CookieTableRow('Strictly Necessary', 'These cookies are essential in order to use the website and use its features.', True), 66 | CookieTableRow('Functional Cookies', 'These cookies allow the website to provide personalized functionality.'), 67 | CookieTableRow('Performance Cookies', 'These cookies help to improve the performance of the website.'))), 68 | header=(H4('Cookie Settings'),Subtitle('Manage your cookie settings here.')), 69 | footer=Button('Save Preferences', cls=(ButtonT.primary, 'w-full'))) 70 | 71 | team_members = [("Sofia Davis", "m@example.com", "Owner"),("Jackson Lee", "p@example.com", "Member"),] 72 | def TeamMemberRow(name, email, role): 73 | return DivFullySpaced( 74 | DivLAligned( 75 | DiceBearAvatar(name, 10,10), 76 | Div(P(name, cls=(TextT.sm, TextT.medium)), 77 | P(email, cls=TextPresets.muted_sm))), 78 | Button(role, UkIcon('chevron-down', cls='ml-4')), 79 | DropDownNavContainer(map(NavCloseLi, [ 80 | A(Div('Viewer', NavSubtitle('Can view and comment.'))), 81 | A(Div('Developer', NavSubtitle('Can view, comment and edit.'))), 82 | A(Div('Billing', NavSubtitle('Can view, comment and manage billing.'))), 83 | A(Div('Owner', NavSubtitle('Admin-level access to all resources.')))]))) 84 | 85 | TeamMembers = Card(*[TeamMemberRow(*member) for member in team_members], 86 | header = (H4('Team Members'),Subtitle('Invite your team members to collaborate.'))) 87 | 88 | access_roles = ("Read and write access", "Read-only access") 89 | team_members = [("Olivia Martin", "m@example.com", "Read and write access"), 90 | ("Isabella Nguyen", "b@example.com", "Read-only access"), 91 | ("Sofia Davis", "p@example.com", "Read-only access")] 92 | 93 | def TeamMemberRow(name, email, role): 94 | return DivFullySpaced( 95 | DivLAligned(DiceBearAvatar(name, 10,10), 96 | Div(P(name, cls=(TextT.sm, TextT.medium)), 97 | P(email, cls=TextPresets.muted_sm))), 98 | Select(*Options(*access_roles, selected_idx=access_roles.index(role)))) 99 | 100 | ShareDocument = Card( 101 | DivLAligned(Input(value='http://example.com/link/to/document'),Button('Copy link', cls='whitespace-nowrap')), 102 | Divider(), 103 | H4('People with access', cls=TextPresets.bold_sm), 104 | *[TeamMemberRow(*member) for member in team_members], 105 | header = (H4('Share this document'),Subtitle('Anyone with the link can view this document.'))) 106 | 107 | DateCard = Card(Button('Jan 20, 2024 - Feb 09, 2024')) 108 | 109 | section_content =(('bell','Everything',"Email digest, mentions & all activity."), 110 | ('user',"Available","Only mentions and comments"), 111 | ('ban', "Ignoring","Turn of all notifications")) 112 | 113 | def NotificationRow(icon, name, desc): 114 | return Li(cls='-mx-1')(A(DivLAligned(UkIcon(icon),Div(P(name),P(desc, cls=TextPresets.muted_sm))))) 115 | 116 | Notifications = Card( 117 | NavContainer( 118 | *[NotificationRow(*row) for row in section_content], 119 | cls=NavT.secondary), 120 | header = (H4('Notification'),Subtitle('Choose what you want to be notified about.')), 121 | body_cls='pt-0') 122 | 123 | TeamCard = Card( 124 | DivLAligned( 125 | DiceBearAvatar("Isaac Flath", h=24, w=24), 126 | Div(H3("Isaac Flath"), P("Library Creator"))), 127 | footer=DivFullySpaced( 128 | DivHStacked(UkIcon("map-pin", height=16), P("Alexandria, VA")), 129 | DivHStacked(*(UkIconLink(icon, height=16) for icon in ("mail", "linkedin", "github")))), 130 | cls=CardT.hover) 131 | 132 | @rt 133 | def index(): 134 | return Title("Cards Example"),Container(Grid( 135 | *map(Div,( 136 | Div(PaymentMethod,CreateAccount, TeamCard, cls='space-y-4'), 137 | Div(TeamMembers, ShareDocument,DateCard,Notifications, cls='space-y-4'), 138 | Div(ReportIssue,MonsterUI,CookieSettings, cls='space-y-4'))), 139 | cols_md=1, cols_lg=2, cols_xl=3)) 140 | 141 | serve() 142 | -------------------------------------------------------------------------------- /docs/examples/dashboard.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Dashboard Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * # Bring in all of fasthtml 4 | import fasthtml.common as fh # Used to get unstyled components 5 | from monsterui.all import * # Bring in all of monsterui, including shadowing fasthtml components with styled components 6 | from fasthtml.svg import * 7 | import numpy as np 8 | import plotly.express as px 9 | import pandas as pd 10 | import numpy as np 11 | 12 | app, rt = fast_app(hdrs=Theme.blue.headers()) 13 | 14 | def generate_chart(num_points=30): 15 | df = pd.DataFrame({ 16 | 'Date': pd.date_range('2024-01-01', periods=num_points), 17 | 'Revenue': np.random.normal(100, 10, num_points).cumsum(), 18 | 'Users': np.random.normal(80, 8, num_points).cumsum(), 19 | 'Growth': np.random.normal(60, 6, num_points).cumsum()}) 20 | 21 | fig = px.line(df, x='Date', y=['Revenue', 'Users', 'Growth'], template='plotly_white', line_shape='spline') 22 | 23 | fig.update_traces(mode='lines+markers') 24 | fig.update_layout( 25 | margin=dict(l=20, r=20, t=20, b=20), hovermode='x unified', 26 | showlegend=True, legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1), 27 | plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', 28 | xaxis=dict(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)'), 29 | yaxis=dict(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')) 30 | 31 | return fig.to_html(include_plotlyjs=True, full_html=False, config={'displayModeBar': False}) 32 | 33 | def InfoCard(title, value, change): return Card(H3(value),P(change, cls=TextPresets.muted_sm), header = H4(title)) 34 | 35 | rev = InfoCard("Total Revenue", "$45,231.89", "+20.1% from last month") 36 | sub = InfoCard("Subscriptions", "+2350", "+180.1% from last month") 37 | sal = InfoCard("Sales", "+12,234", "+19% from last month") 38 | act = InfoCard("Active Now", "+573", "+201 since last hour") 39 | 40 | info_card_data = [("Total Revenue", "$45,231.89", "+20.1% from last month"), 41 | ("Subscriptions", "+2350", "+180.1% from last month"), 42 | ("Sales", "+12,234", "+19% from last month"), 43 | ("Active Now", "+573", "+201 since last hour")] 44 | 45 | top_info_row = Grid(*[InfoCard(*row) for row in info_card_data]) 46 | 47 | def AvatarItem(name, email, amount): 48 | return DivFullySpaced( 49 | DivLAligned( 50 | DiceBearAvatar(name, 9,9), 51 | Div(Strong(name, cls=TextT.sm), 52 | Address(A(email,href=f'mailto:{email}')))), 53 | fh.Data(amount, cls="ml-auto font-medium", value=amount[2:])) 54 | 55 | recent_sales = Card( 56 | Div(cls="space-y-8")( 57 | *[AvatarItem(n,e,d) for (n,e,d) in ( 58 | ("Olivia Martin", "olivia.martin@email.com", "+$1,999.00"), 59 | ("Jackson Lee", "jackson.lee@email.com", "+$39.00"), 60 | ("Isabella Nguyen", "isabella.nguyen@email.com", "+$299.00"), 61 | ("William Kim", "will@email.com", "+$99.00"), 62 | ("Sofia Davis", "sofia.davis@email.com", "+$39.00"))]), 63 | header=Div(H3("Recent Sales"),Subtitle("You made 265 sales this month.")), 64 | cls='col-span-3') 65 | 66 | teams = [["Alicia Koch"],['Acme Inc', 'Monster Inc.'],['Create a Team']] 67 | 68 | opt_hdrs = ["Personal", "Team", ""] 69 | 70 | team_dropdown = Select( 71 | Optgroup(Option(A("Alicia Koch")), label="Personal Account"), 72 | Optgroup(Option(A("Acme Inc")), Option(A("Monster Inc.")), label="Teams"), 73 | Option(A("Create a Team")), 74 | cls='flex items-center') 75 | 76 | hotkeys = [('Profile','⇧⌘P'),('Billing','⌘B'),('Settings','⌘S'),('New Team', ''), ('Logout', '')] 77 | 78 | def NavSpacedLi(t,s): return NavCloseLi(A(DivFullySpaced(P(t),P(s,cls=TextPresets.muted_sm)))) 79 | 80 | avatar_dropdown = Div( 81 | DiceBearAvatar('Alicia Koch',8,8), 82 | DropDownNavContainer( 83 | NavHeaderLi('sveltecult',NavSubtitle("leader@sveltecult.com")), 84 | *[NavSpacedLi(*hk) for hk in hotkeys],)) 85 | 86 | top_nav = NavBar( 87 | team_dropdown, *map(A, ["Overview", "Customers", "Products", "Settings"]), 88 | brand=DivLAligned(avatar_dropdown, Input(placeholder='Search'))) 89 | 90 | @rt 91 | def index(): 92 | return Title("Dashboard Example"), Container( 93 | top_nav, 94 | H2('Dashboard'), 95 | TabContainer( 96 | Li(A("Overview"),cls='uk-active'), 97 | *map(lambda x: Li(A(x)), ["Analytics", "Reports", "Notifications"]), 98 | alt=True), 99 | top_info_row, 100 | Grid( 101 | Card(Safe(generate_chart(100)), cls='col-span-4'), 102 | recent_sales, 103 | gap=4,cols_xl=7,cols_lg=7,cols_md=1,cols_sm=1,cols_xs=1), 104 | cls=('space-y-4', ContainerT.xl)) 105 | 106 | serve() 107 | -------------------------------------------------------------------------------- /docs/examples/data/mail.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775a", 4 | "initial": "WS", 5 | "name": "William Smith", 6 | "email": "williamsmith@example.com", 7 | "subject": "Meeting Tomorrow", 8 | "text": "Hi, let's have a meeting tomorrow to discuss the project. I've been reviewing the project details and have some ideas I'd like to share. It's crucial that we align on our next steps to ensure the project's success.\n\nPlease come prepared with any questions or insights you may have. Looking forward to our meeting!\n\nBest regards, William", 9 | "date": "2023-10-22T09:00:00", 10 | "read": true, 11 | "active": true, 12 | "labels": ["meeting", "work", "important"] 13 | }, 14 | { 15 | "id": "110e8400-e29b-11d4-a716-446655440000", 16 | "initial": "AS", 17 | "name": "Alice Smith", 18 | "email": "alicesmith@example.com", 19 | "subject": "Re: Project Update", 20 | "text": "Thank you for the project update. It looks great! I've gone through the report, and the progress is impressive. The team has done a fantastic job, and I appreciate the hard work everyone has put in.\n\nI have a few minor suggestions that I'll include in the attached document.\n\nLet's discuss these during our next meeting. Keep up the excellent work!\n\nBest regards, Alice", 21 | "date": "2023-10-22T10:30:00", 22 | "read": true, 23 | "active": false, 24 | "labels": ["work", "important"] 25 | }, 26 | { 27 | "id": "3e7c3f6d-bdf5-46ae-8d90-171300f27ae2", 28 | "initial": "BJ", 29 | "name": "Bob Johnson", 30 | "email": "bobjohnson@example.com", 31 | "subject": "Weekend Plans", 32 | "text": "Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's been a while since we had some outdoor fun.\n\nIf you're interested, let me know, and we can plan the details. It'll be a great way to unwind and enjoy nature.\n\nLooking forward to your response!\n\nBest, Bob", 33 | "date": "2023-04-10T11:45:00", 34 | "read": true, 35 | "active": false, 36 | "labels": ["personal"] 37 | }, 38 | { 39 | "id": "61c35085-72d7-42b4-8d62-738f700d4b92", 40 | "initial": "ED", 41 | "name": "Emily Davis", 42 | "email": "emilydavis@example.com", 43 | "subject": "Re: Question about Budget", 44 | "text": "I have a question about the budget for the upcoming project. It seems like there's a discrepancy in the allocation of resources.\n\nI've reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project's quality.\n\nI've attached a detailed analysis for your reference. Let's discuss this further in our next meeting.\n\nThanks, Emily", 45 | "date": "2023-03-25T13:15:00", 46 | "read": false, 47 | "active": false, 48 | "labels": ["work", "budget"] 49 | }, 50 | { 51 | "id": "8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97", 52 | "initial": "MW", 53 | "name": "Michael Wilson", 54 | "email": "michaelwilson@example.com", 55 | "subject": "Important Announcement", 56 | "text": "I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We've received valuable feedback from our beta testers, and I believe it's time to make some adjustments to better meet our customers' needs.\n\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\n\nRegards, Michael", 57 | "date": "2023-03-10T15:00:00", 58 | "read": false, 59 | "active": false, 60 | "labels": ["meeting", "work", "important"] 61 | }, 62 | { 63 | "id": "1f0f2c02-e299-40de-9b1d-86ef9e42126b", 64 | "initial": "SB", 65 | "name": "Sarah Brown", 66 | "email": "sarahbrown@example.com", 67 | "subject": "Re: Feedback on Proposal", 68 | "text": "Thank you for your feedback on the proposal. It looks great! I'm pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\n\nI've attached the revised proposal for your review.\n\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\n\nBest regards, Sarah", 69 | "date": "2023-02-15T16:30:00", 70 | "read": true, 71 | "active": false, 72 | "labels": ["work"] 73 | }, 74 | { 75 | "id": "17c0a96d-4415-42b1-8b4f-764efab57f66", 76 | "initial": "DL", 77 | "name": "David Lee", 78 | "email": "davidlee@example.com", 79 | "subject": "New Project Idea", 80 | "text": "I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\n\nI've prepared a detailed proposal outlining the potential benefits and the strategy for execution.\n\nThis project has the potential to significantly impact our business positively. Let's set up a meeting to dive into the details and determine if it aligns with our current goals.\n\nBest regards, David", 81 | "date": "2023-01-28T17:45:00", 82 | "read": false, 83 | "active": false, 84 | "labels": ["meeting", "work", "important"] 85 | }, 86 | { 87 | "id": "2f0130cb-39fc-44c4-bb3c-0a4337edaaab", 88 | "initial": "OW", 89 | "name": "Olivia Wilson", 90 | "email": "oliviawilson@example.com", 91 | "subject": "Vacation Plans", 92 | "text": "Let's plan our vacation for next month. What do you think? I've been thinking of visiting a tropical paradise, and I've put together some destination options.\n\nI believe it's time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\n\nWe can start making arrangements to ensure a smooth and enjoyable trip.\n\nExcited to hear your thoughts! Olivia", 93 | "date": "2022-12-20T18:30:00", 94 | "read": true, 95 | "active": false, 96 | "labels": ["personal"] 97 | }, 98 | { 99 | "id": "de305d54-75b4-431b-adb2-eb6b9e546014", 100 | "initial": "JM", 101 | "name": "James Martin", 102 | "email": "jamesmartin@example.com", 103 | "subject": "Re: Conference Registration", 104 | "text": "I've completed the registration for the conference next month. The event promises to be a great networking opportunity, and I'm looking forward to attending the various sessions and connecting with industry experts.\n\nI've also attached the conference schedule for your reference.\n\nIf there are any specific topics or sessions you'd like me to explore, please let me know. It's an exciting event, and I'll make the most of it.\n\nBest regards, James", 105 | "date": "2022-11-30T19:15:00", 106 | "read": true, 107 | "active": false, 108 | "labels": ["work", "conference"] 109 | }, 110 | { 111 | "id": "7dd90c63-00f6-40f3-bd87-5060a24e8ee7", 112 | "initial": "SW", 113 | "name": "Sophia White", 114 | "email": "sophiawhite@example.com", 115 | "subject": "Team Dinner", 116 | "text": "Let's have a team dinner next week to celebrate our success. We've achieved some significant milestones, and it's time to acknowledge our hard work and dedication.\n\nI've made reservations at a lovely restaurant, and I'm sure it'll be an enjoyable evening.\n\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\n\nBest, Sophia", 117 | "date": "2022-11-05T20:30:00", 118 | "read": false, 119 | "active": false, 120 | "labels": ["meeting", "work"] 121 | }, 122 | { 123 | "id": "99a88f78-3eb4-4d87-87b7-7b15a49a0a05", 124 | "initial": "DJ", 125 | "name": "Daniel Johnson", 126 | "email": "danieljohnson@example.com", 127 | "subject": "Feedback Request", 128 | "text": "I'd like your feedback on the latest project deliverables. We've made significant progress, and I value your input to ensure we're on the right track.\n\nI've attached the deliverables for your review, and I'm particularly interested in any areas where you think we can further enhance the quality or efficiency.\n\nYour feedback is invaluable, and I appreciate your time and expertise. Let's work together to make this project a success.\n\nRegards, Daniel", 129 | "date": "2022-10-22T09:30:00", 130 | "read": false, 131 | "active": false, 132 | "labels": ["work"] 133 | }, 134 | { 135 | "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", 136 | "initial": "AT", 137 | "name": "Ava Taylor", 138 | "email": "avataylor@example.com", 139 | "subject": "Re: Meeting Agenda", 140 | "text": "Here's the agenda for our meeting next week. I've included all the topics we need to cover, as well as time allocations for each.\n\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\n\nIt's essential that our meeting is productive and addresses all relevant matters.\n\nLooking forward to our meeting! Ava", 141 | "date": "2022-10-10T10:45:00", 142 | "read": true, 143 | "active": false, 144 | "labels": ["meeting", "work"] 145 | }, 146 | { 147 | "id": "c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6", 148 | "initial": "WA", 149 | "name": "William Anderson", 150 | "email": "williamanderson@example.com", 151 | "subject": "Product Launch Update", 152 | "text": "The product launch is on track. I'll provide an update during our call. We've made substantial progress in the development and marketing of our new product.\n\nI'm excited to share the latest updates with you during our upcoming call. It's crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\n\nLet's make this product launch a resounding success!\n\nBest regards, William", 153 | "date": "2022-09-20T12:00:00", 154 | "read": false, 155 | "active": false, 156 | "labels": ["meeting", "work", "important"] 157 | }, 158 | { 159 | "id": "ba54eefd-4097-4949-99f2-2a9ae4d1a836", 160 | "initial": "MH", 161 | "name": "Mia Harris", 162 | "email": "miaharris@example.com", 163 | "subject": "Re: Travel Itinerary", 164 | "text": "I've received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I've reviewed the schedule and the accommodations, and everything seems to be in order. I'm looking forward to the trip, and I'm confident it'll be a smooth and enjoyable experience.\n\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\n\nExcited for the trip! Mia", 165 | "date": "2022-09-10T13:15:00", 166 | "read": true, 167 | "active": false, 168 | "labels": ["personal", "travel"] 169 | }, 170 | { 171 | "id": "df09b6ed-28bd-4e0c-85a9-9320ec5179aa", 172 | "initial": "EC", 173 | "name": "Ethan Clark", 174 | "email": "ethanclark@example.com", 175 | "subject": "Team Building Event", 176 | "text": "Let's plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I've done some research and have a few ideas for fun and engaging activities.\n\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\n\nTogether, we'll strengthen our team and boost our performance.\n\nRegards, Ethan", 177 | "date": "2022-08-25T15:30:00", 178 | "read": false, 179 | "active": false, 180 | "labels": ["meeting", "work"] 181 | }, 182 | { 183 | "id": "d67c1842-7f8b-4b4b-9be1-1b3b1ab4611d", 184 | "initial": "CH", 185 | "name": "Chloe Hall", 186 | "email": "chloehall@example.com", 187 | "subject": "Re: Budget Approval", 188 | "text": "The budget has been approved. We can proceed with the project. I'm delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\n\nI've attached the finalized budget for your reference. Let's ensure that we stay on track and deliver the project on time and within budget.\n\nIt's an exciting time for us! Chloe", 189 | "date": "2022-08-10T16:45:00", 190 | "read": true, 191 | "active": false, 192 | "labels": ["work", "budget"] 193 | }, 194 | { 195 | "id": "6c9a7f94-8329-4d70-95d3-51f68c186ae1", 196 | "initial": "ST", 197 | "name": "Samuel Turner", 198 | "email": "samuelturner@example.com", 199 | "subject": "Weekend Hike", 200 | "text": "Who's up for a weekend hike in the mountains? I've been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you're up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\n\nI've done some research and have a few routes in mind.\n\nLet me know if you're interested, and we can plan the details.\n\nIt's sure to be a memorable experience! Samuel", 201 | "date": "2022-07-28T17:30:00", 202 | "read": false, 203 | "active": false, 204 | "labels": ["personal"] 205 | } 206 | ] -------------------------------------------------------------------------------- /docs/examples/data/statuses.json: -------------------------------------------------------------------------------- 1 | { 2 | "backlog": { 3 | "icon": "", 4 | "text": "Backlog" 5 | }, 6 | "cancelled": { 7 | "icon": "", 8 | "text": "Cancelled" 9 | }, 10 | "done": { 11 | "icon": "", 12 | "text": "Done" 13 | }, 14 | "progress": { 15 | "icon": "", 16 | "text": "In Progress" 17 | }, 18 | "todo": { 19 | "icon": "", 20 | "text": "Todo" 21 | } 22 | } -------------------------------------------------------------------------------- /docs/examples/forms.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Forms Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | 4 | from fasthtml.common import * 5 | from monsterui.all import * 6 | from fasthtml.svg import * 7 | 8 | app, rt = fast_app(hdrs=Theme.blue.headers()) 9 | 10 | def HelpText(c): return P(c,cls=TextPresets.muted_sm) 11 | 12 | def heading(): 13 | return Div(cls="space-y-5")( 14 | H2("Settings"), 15 | Subtitle("Manage your account settings and set e-mail preferences."), 16 | DividerSplit()) 17 | 18 | 19 | sidebar = NavContainer( 20 | *map(lambda x: Li(A(x)), ("Profile", "Account", "Appearance", "Notifications", "Display")), 21 | uk_switcher="connect: #component-nav; animation: uk-animation-fade", 22 | cls=(NavT.secondary,"space-y-4 p-4 w-1/5")) 23 | 24 | 25 | def FormSectionDiv(*c, cls='space-y-2', **kwargs): return Div(*c, cls=cls, **kwargs) 26 | 27 | def FormLayout(title, subtitle, *content, cls='space-y-3 mt-4'): return Container(Div(H3(title), Subtitle(subtitle), DividerLine(), Form(*content, cls=cls))) 28 | 29 | def profile_form(): 30 | content = (FormSectionDiv( 31 | LabelInput("Username", placeholder='sveltecult', id='username'), 32 | HelpText("This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.")), 33 | FormSectionDiv( 34 | LabelSelect( 35 | Option("Select a verified email to display", value="", selected=True, disabled=True), 36 | *[Option(o, value=o) for o in ('m@example.com', 'm@yahoo.com', 'm@cloud.com')], 37 | label="Email", id="email"), 38 | HelpText("You can manage verified email addresses in your email settings.")), 39 | FormSectionDiv( 40 | LabelTextArea("Bio", id="bio", placeholder="Tell us a little bit about yourself"), 41 | HelpText("You can @mention other users and organizations to link to them."), 42 | P("String must contain at least 4 character(s)", cls="text-destructive")), 43 | FormSectionDiv( 44 | FormLabel("URLs"), 45 | HelpText("Add links to your website, blog, or social media profiles."), 46 | Input(value="https://www.franken-ui.dev"), 47 | Input(value="https://github.com/sveltecult/franken-ui"), 48 | Button("Add URL")), 49 | Button('Update profile', cls=ButtonT.primary)) 50 | 51 | return FormLayout('Profile', 'This is how others will see you on the site.', *content) 52 | 53 | def account_form(): 54 | content = ( 55 | FormSectionDiv( 56 | LabelInput("Name", placeholder="Your name", id="name"), 57 | HelpText("This is the name that will be displayed on your profile and in emails.")), 58 | FormSectionDiv( 59 | LabelInput("Date of Birth", type="date", placeholder="Pick a date", id="date_of_birth"), 60 | HelpText("Your date of birth is used to calculate your age.")), 61 | FormSectionDiv( 62 | LabelSelect(*Options("Select a language", "English", "French", "German", "Spanish", "Portuguese", selected_idx=1, disabled_idxs={0}), 63 | label='Language', id="language"), 64 | HelpText("This is the language that will be used in the dashboard.")), 65 | Button('Update profile', cls=ButtonT.primary)) 66 | 67 | return FormLayout('Account', 'Update your account settings. Set your preferred language and timezone.', *content) 68 | 69 | def appearance_form(): 70 | def theme_item(bg_color, content_bg, text_bg): 71 | common_content = f"space-y-2 rounded-md {content_bg} p-2 shadow-sm" 72 | item_row = lambda: Div(cls=f"flex items-center space-x-2 {common_content}")( 73 | Div(cls=f"h-4 w-4 rounded-full {text_bg}"), 74 | Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}")) 75 | 76 | return Div(cls=f"space-y-2 rounded-sm {bg_color} p-2")( 77 | Div(cls=common_content)( 78 | Div(cls=f"h-2 w-[80px] rounded-lg {text_bg}"), 79 | Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}")), 80 | item_row(), 81 | item_row()) 82 | 83 | common_toggle_cls = "block cursor-pointer items-center rounded-md border-2 border-muted p-1 ring-ring" 84 | 85 | content = ( 86 | FormSectionDiv( 87 | LabelSelect(*Options('Select a font family', 'Inter', 'Geist', 'Open Sans', selected_idx=2, disabled_idxs={0}), 88 | label='Font Family', id='font_family'), 89 | HelpText("Set the font you want to use in the dashboard.")), 90 | FormSectionDiv( 91 | FormLabel("Theme"), 92 | HelpText("Select the theme for the dashboard."), 93 | Grid( 94 | A(id="theme-toggle-light", cls=common_toggle_cls)(theme_item("bg-[#ecedef]", "bg-white", "bg-[#ecedef]")), 95 | A(id="theme-toggle-dark", cls=f"{common_toggle_cls} bg-popover")(theme_item("bg-slate-950", "bg-slate-800", "bg-slate-400")), 96 | cols_max=2,cls=('max-w-md','gap-8'))), 97 | Button('Update preferences', cls=ButtonT.primary)) 98 | 99 | return FormLayout('Appearance', 'Customize the appearance of the app. Automatically switch between day and night themes.', *content) 100 | 101 | 102 | notification_items = [ 103 | {"title": "Communication emails", "description": "Receive emails about your account activity.", "checked": False, "disabled": False}, 104 | {"title": "Marketing emails", "description": "Receive emails about new products, features, and more.", "checked": False, "disabled": False}, 105 | {"title": "Social emails", "description": "Receive emails for friend requests, follows, and more.", "checked": True, "disabled": False}, 106 | {"title": "Security emails", "description": "Receive emails about your account activity and security.", "checked": True, "disabled": True}] 107 | 108 | def notifications_form(): 109 | def RadioLabel(label): return DivLAligned(Radio(name="notification", checked=(label=="Nothing")), FormLabel(label)) 110 | 111 | def NotificationCard(item): 112 | return Card( 113 | Div(cls="space-y-0.5")( 114 | FormLabel(Strong(item['title'], cls=TextT.sm), 115 | HelpText(item['description'])))) 116 | content = Div( 117 | FormSectionDiv( 118 | FormLabel("Notify me about"), 119 | *map(RadioLabel, ["All new messages", "Direct messages and mentions", "Nothing"])), 120 | Div( 121 | H4("Email Notifications", cls="mb-4"), 122 | Grid(*map(NotificationCard, notification_items), cols=1)), 123 | LabelCheckboxX("Use different settings for my mobile devices", id="notification_mobile"), 124 | HelpText("You can manage your mobile notifications in the mobile settings page."), 125 | Button('Update notifications', cls=ButtonT.primary)) 126 | 127 | return FormLayout('Notifications', 'Configure how you receive notifications.', *content) 128 | 129 | def display_form(): 130 | content = ( 131 | Div(cls="space-y-2")( 132 | Div(cls="mb-4")( 133 | H5("Sidebar"), 134 | Subtitle("Select the items you want to display in the sidebar.")), 135 | *[Div(CheckboxX(id=f"display_{i}", checked=i in [0, 1, 2]),FormLabel(label)) 136 | for i, label in enumerate(["Recents", "Home", "Applications", "Desktop", "Downloads", "Documents"])]), 137 | Button('Update display', cls=ButtonT.primary)) 138 | return FormLayout('Display', 'Turn items on or off to control what\'s displayed in the app.', *content) 139 | 140 | @rt 141 | def index(): 142 | return Title("Forms Example"),Container( 143 | heading(), 144 | Div(cls="flex gap-x-12")( 145 | sidebar, 146 | Ul(id="component-nav", cls="uk-switcher max-w-2xl")( 147 | Li(cls="uk-active")(profile_form(), 148 | *map(Li, [account_form(), appearance_form(), notifications_form(), display_form()]))))) 149 | 150 | serve() 151 | -------------------------------------------------------------------------------- /docs/examples/mail.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Mail Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | from monsterui.all import * 5 | from fasthtml.svg import * 6 | import pathlib, json 7 | from datetime import datetime 8 | 9 | app, rt = fast_app(hdrs=Theme.blue.headers()) 10 | 11 | sidebar_group1 = (('home', 'Inbox', '128'), ('file-text', 'Drafts', '9'), (' arrow-up-right', 'Sent', ''), 12 | ('ban', 'Junk', '23'), ('trash', 'Trash', ''), ('folder', 'Archive', '')) 13 | 14 | sidebar_group2 = (('globe','Social','972'),('info','Updates','342'),('messages-square','Forums','128'), 15 | ('shopping-cart','Shopping','8'),('shopping-bag','Promotions','21'),) 16 | 17 | def MailSbLi(icon, title, cnt): 18 | return Li(A(DivLAligned(Span(UkIcon(icon)),Span(title),P(cnt, cls=TextPresets.muted_sm)),href='#', cls='hover:bg-secondary p-4')) 19 | 20 | sidebar = NavContainer( 21 | NavHeaderLi(H3("Email"), cls='p-3'), 22 | Li(Select(map(Option, ('alicia@example.com','alicia@gmail.com', 'alicia@yahoo.com')))), 23 | *[MailSbLi(i, t, c) for i, t, c in sidebar_group1], 24 | Li(Hr()), 25 | *[MailSbLi(i, t, c) for i, t, c in sidebar_group2], 26 | cls='mt-3') 27 | 28 | mail_data = json.load(open(pathlib.Path('data/mail.json'))) 29 | 30 | def format_date(date_str): 31 | date_obj = datetime.fromisoformat(date_str) 32 | return date_obj.strftime("%Y-%m-%d %I:%M %p") 33 | 34 | def MailItem(mail): 35 | cls_base = 'relative rounded-lg border border-border p-3 text-sm hover:bg-secondary space-y-2' 36 | cls = f"{cls_base} {'bg-muted' if mail == mail_data[0] else ''} {'tag-unread' if not mail['read'] else 'tag-mail'}" 37 | 38 | return Li( 39 | DivFullySpaced( 40 | DivLAligned( 41 | Strong(mail['name']), 42 | Span(cls='flex h-2 w-2 rounded-full bg-blue-600') if not mail['read'] else ''), 43 | Time(format_date(mail['date']), cls='text-xs')), 44 | Small(mail['subject'], href=f"#mail-{mail['id']}"), 45 | Div(mail['text'][:100] + '...', cls=TextPresets.muted_sm), 46 | DivLAligned( 47 | *[Label(A(label, href='#'), cls='uk-label-primary' if label == 'work' else '') for label in mail['labels']]), 48 | cls=cls) 49 | 50 | def MailList(mails): return Ul(cls='js-filter space-y-2 p-4 pt-0')(*[MailItem(mail) for mail in mails]) 51 | 52 | def MailContent(): 53 | return Div(cls='flex flex-col',uk_filter="target: .js-filter")( 54 | Div(cls='flex px-4 py-2 ')( 55 | H3('Inbox'), 56 | TabContainer(Li(A("All Mail",href='#', role='button'),cls='uk-active', uk_filter_control="filter: .tag-mail"), 57 | Li(A("Unread",href='#', role='button'), uk_filter_control="filter: .tag-unread"), 58 | alt=True, cls='ml-auto max-w-40', )), 59 | Div(cls='flex flex-1 flex-col')( 60 | Div(cls='p-4')( 61 | Div(cls='uk-inline w-full')( 62 | Span(cls='uk-form-icon text-muted-foreground')(UkIcon('search')), 63 | Input(placeholder='Search'))), 64 | Div(cls='flex-1 overflow-y-auto max-h-[600px]')(MailList(mail_data)))) 65 | 66 | def IconNavItem(*d): return [Li(A(UkIcon(o[0],uk_tooltip=o[1]))) for o in d] 67 | def IconNav(*c,cls=''): return Ul(cls=f'uk-iconnav {cls}')(*c) 68 | 69 | def MailDetailView(mail): 70 | top_icons = [('folder','Archive'), ('ban','Move to junk'), ('trash','Move to trash')] 71 | reply_icons = [('reply','Reply'), ('reply','Reply all'), ('forward','Forward')] 72 | dropdown_items = ['Mark as unread', 'Star read', 'Add Label', 'Mute Thread'] 73 | 74 | return Container( 75 | DivFullySpaced( 76 | DivLAligned( 77 | DivLAligned(*[UkIcon(o[0],uk_tooltip=o[1]) for o in top_icons]), 78 | Div(UkIcon('clock', uk_tooltip='Snooze'), cls='pl-2'), 79 | cls='space-x-2 divide-x divide-border'), 80 | DivLAligned( 81 | *[UkIcon(o[0],uk_tooltip=o[1]) for o in reply_icons], 82 | Div(UkIcon('ellipsis-vertical',button=True)), 83 | DropDownNavContainer(*map(lambda x: Li(A(x)), dropdown_items)))), 84 | DivLAligned( 85 | Span(mail['name'][:2], cls='flex h-10 w-10 items-center justify-center rounded-full bg-muted'), 86 | Div(Strong(mail['name']), 87 | Div(mail['subject']), 88 | DivLAligned(P('Reply-To:'), A(mail['email'], href=f"mailto:{mail['email']}"), cls='space-x-1'), 89 | P(Time(format_date(mail['date']))), 90 | cls='space-y-1'+TextT.sm), 91 | cls='m-4 space-x-4'), 92 | DividerLine(), 93 | P(mail['text'], cls=TextT.sm +'p-4'), 94 | DividerLine(), 95 | Div(TextArea(id='message', placeholder=f"Reply {mail['name']}"), 96 | DivFullySpaced( 97 | LabelSwitch('Mute this thread',id='mute'), 98 | Button('Send', cls=ButtonT.primary)), 99 | cls='space-y-4')) 100 | 101 | @rt 102 | def index(): 103 | return Title("Mail Example"),Container( 104 | Grid(Div(sidebar, cls='col-span-1'), 105 | Div(MailContent(), cls='col-span-2'), 106 | Div(MailDetailView(mail_data[0]), cls='col-span-2'), 107 | cols_sm=1, cols_md=1, cols_lg=5, cols_xl=5, 108 | gap=0, cls='flex-1'), 109 | cls=('flex', ContainerT.xl)) 110 | 111 | serve() 112 | -------------------------------------------------------------------------------- /docs/examples/music.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Music Example build with MonsterUI (Original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | 5 | from monsterui.all import * 6 | 7 | app, rt = fast_app(hdrs=Theme.blue.headers()) 8 | 9 | def MusicLi(t,hk=''): return Li(A(DivFullySpaced(t,P(hk,cls=TextPresets.muted_sm)))) 10 | 11 | music_items = [("About Music", "" ), 12 | ("Preferences", "⌘" ), 13 | ("Hide Music" , "⌘H" ), 14 | ("Hide Others", "⇧⌘H"), 15 | ("Quit Music" , "⌘Q" )] 16 | 17 | file_dd_items = [("New", ""), 18 | ("Open Stream URL", "⌘U"), 19 | ("Close Window", "⌘W"), 20 | ("Library", ""), 21 | ("Import", "⌘O"), 22 | ("Burn Playlist to Disc", ""), 23 | ("Show in Finder", "⇧⌘R"), 24 | ("Convert", ""), 25 | ("Page Setup", "Print")] 26 | 27 | edit_actions = [("Undo", "⌘Z"), 28 | ("Redo", "⇧⌘Z"), 29 | ("Cut", "⌘X"), 30 | ("Copy", "⌘C"), 31 | ("Paste", "⌘V"), 32 | ("Select All", "⌘A"), 33 | ("Deselect All", "⇧⌘A")] 34 | 35 | view_dd_data = ["Show Playing Next", "Show Lyrics", "Show Status Bar", "Hide Sidebar", "Enter Full Screen"] 36 | 37 | 38 | music_headers = NavBar( 39 | Button("Music", cls=ButtonT.ghost+TextT.gray),DropDownNavContainer(Li(A("Music"),NavContainer(map(lambda x: MusicLi(*x), music_items)))), 40 | Button("File", cls=ButtonT.ghost+TextT.gray), DropDownNavContainer(Li(A("File"), NavContainer(map(lambda x: MusicLi(*x), file_dd_items)))), 41 | Button("Edit", cls=ButtonT.ghost+TextT.gray), DropDownNavContainer(Li(A("Edit")),NavContainer( 42 | *map(lambda x: MusicLi(*x), edit_actions), 43 | Li(A(DivFullySpaced("Smart Dictation",UkIcon("mic")))), 44 | Li(A(DivFullySpaced("Emojis & Symbols",UkIcon("globe")))))), 45 | Button("View", cls=ButtonT.ghost+TextT.gray),DropDownNavContainer(Li(A("View"),NavContainer(map(lambda x: MusicLi(x), view_dd_data)))), 46 | brand=DivLAligned(H2("Purrify")) 47 | ) 48 | 49 | 50 | 51 | 52 | # music_headers = NavBarContainer( 53 | # NavBarLSide( 54 | # NavBarNav( 55 | # Li(A("Music"),NavBarNavContainer(map(lambda x: MusicLi(*x), music_items))), 56 | # Li(A("File"), NavBarNavContainer(map(lambda x: MusicLi(*x), file_dd_items))), 57 | # Li(A("Edit")), 58 | # NavBarNavContainer( 59 | # *map(lambda x: MusicLi(*x), edit_actions), 60 | # Li(A(DivFullySpaced("Smart Dictation",UkIcon("mic")))), 61 | # Li(A(DivFullySpaced("Emojis & Symbols",UkIcon("globe"))))), 62 | # Li(A("View"), 63 | # NavBarNavContainer(map(lambda x: MusicLi(x), view_dd_data))), 64 | # Li(A("Account"), 65 | # NavBarNavContainer( 66 | # NavHeaderLi("Switch Account"), 67 | # *map(MusicLi, ("Andy", "Benoit", "Luis", "Manage Family", "Add Account"))))))) 68 | 69 | 70 | def Album(title,artist): 71 | img_url = 'https://ucarecdn.com/e5607eaf-2b2a-43b9-ada9-330824b6afd7/music1.webp' 72 | return Div( 73 | Div(cls="overflow-hidden rounded-md")(Img(cls="transition-transform duration-200 hover:scale-105", src=img_url)), 74 | Div(cls='space-y-1')(Strong(title),P(artist,cls=TextT.muted))) 75 | 76 | listen_now_albums = (("Roar", "Catty Perry"), ("Feline on a Prayer", "Cat Jovi"),("Fur Elise", "Ludwig van Beethovpurr"),("Purrple Rain", "Prince's Cat")) 77 | 78 | made_for_you_albums = [("Like a Feline", "Catdonna"), 79 | ("Livin' La Vida Purrda", "Ricky Catin"), 80 | ("Meow Meow Rocket", "Elton Cat"), 81 | ("Rolling in the Purr", "Catdelle"), 82 | ("Purrs of Silence", "Cat Garfunkel"), 83 | ("Meow Me Maybe", "Carly Rae Purrsen"),] 84 | 85 | music_content = (Div(H3("Listen Now"), cls="mt-6 space-y-1"), 86 | Subtitle("Top picks for you. Updated daily."), 87 | DividerLine(), 88 | Grid(*[Album(t,a) for t,a in listen_now_albums], cls='gap-8'), 89 | Div(H3("Made for You"), cls="mt-6 space-y-1"), 90 | Subtitle("Your personal playlists. Updated daily."), 91 | DividerLine(), 92 | Grid(*[Album(t,a) for t,a in made_for_you_albums], cols_xl=6)) 93 | 94 | tabs = TabContainer( 95 | Li(A('Music', href='#'), cls='uk-active'), 96 | Li(A('Podcasts', href='#')), 97 | Li(A('Live', cls='opacity-50'), cls='uk-disabled'), 98 | uk_switcher='connect: #component-nav; animation: uk-animation-fade', 99 | alt=True) 100 | 101 | def podcast_tab(): 102 | return Div( 103 | Div(cls='space-y-3 mt-6')( 104 | H3("New Episodes"), 105 | Subtitle("Your favorite podcasts. Updated daily.")), 106 | Div(cls="uk-placeholder flex h-[450px] items-center justify-center rounded-md mt-4",uk_placeholder=True)( 107 | DivVStacked(cls="space-y-6")( 108 | UkIcon("microphone", 3), 109 | H4("No episodes added"), 110 | Subtitle("You have not added any podcasts. Add one below."), 111 | Button("Add Podcast", cls=ButtonT.primary)))) 112 | 113 | discoved_data = [("play-circle","Listen Now"), ("binoculars", "Browse"), ("rss","Radio")] 114 | library_data = [("play-circle", "Playlists"), ("music", "Songs"), ("user", "Made for You"), ("users", "Artists"), ("bookmark", "Albums")] 115 | playlists_data = [("library","Recently Added"), ("library","Recently Played")] 116 | 117 | def MusicSidebarLi(icon, text): return Li(A(DivLAligned(UkIcon(icon), P(text)))) 118 | sidebar = NavContainer( 119 | NavHeaderLi(H3("Discover")), *[MusicSidebarLi(*o) for o in discoved_data], 120 | NavHeaderLi(H3("Library")), *[MusicSidebarLi(*o) for o in library_data], 121 | NavHeaderLi(H3("Playlists")),*[MusicSidebarLi(*o) for o in playlists_data], 122 | cls=(NavT.primary,'space-y-3','pl-8')) 123 | 124 | @rt 125 | def index(): 126 | return Title("Music Example"),Container(music_headers, DividerSplit(), 127 | Grid(sidebar, 128 | Div(cls="col-span-4 border-l border-border")( 129 | Div(cls="px-8 py-6")( 130 | DivFullySpaced( 131 | Div(cls="max-w-80")(tabs), 132 | Button(cls=ButtonT.primary)(DivLAligned(UkIcon('circle-plus')),Div("Add music"))), 133 | Ul(id="component-nav", cls="uk-switcher")( 134 | Li(*music_content), 135 | Li(podcast_tab())))), 136 | cols_sm=1, cols_md=1, cols_lg=5, cols_xl=5)) 137 | 138 | serve() 139 | -------------------------------------------------------------------------------- /docs/examples/playground.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Playground Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | from monsterui.all import * 5 | from fasthtml.svg import * 6 | 7 | app, rt = fast_app(hdrs=Theme.blue.headers()) 8 | 9 | preset_options = ["Grammatical Standard English", "Summarize for a 2nd grader", 10 | "Text to command","Q&A","English to other languages","Parse unstructured data", 11 | "Classification","Natural language to Python","Explain code","Chat","More examples"] 12 | 13 | def playground_navbar(): 14 | save_modal = Modal( 15 | ModalTitle("Save preset"), 16 | P("This will save the current playground state as a preset which you can access later or share with others.",cls=("mt-1.5", TextPresets.muted_sm)), 17 | LabelInput("Name", id="name"), 18 | LabelInput("Description", id="description"), 19 | ModalCloseButton("Save", cls=ButtonT.primary), 20 | id="save") 21 | 22 | share_dd = Div(cls="space-y-6 p-4")( 23 | H3("Share preset"), 24 | P("Anyone who has this link and an OpenAI account will be able to view this.", cls=TextPresets.muted_sm), 25 | Div(Input(value="https://platform.openai.com/playground/p/7bbKYQvsVkNmVb8NGcdUOLae?model=text-davinci-003", readonly=True), 26 | Button(UkIcon('copy'), cls=(ButtonT.primary, "uk-drop-close",'mt-4')))) 27 | 28 | rnav = ( 29 | Select(*Options(*preset_options), name='preset', optgroup_label="Examples", 30 | placeholder='Load a preset', searchable=True, cls='h-9 w-[200px] lg:w-[300px]'), 31 | Button("Save", cls=ButtonT.secondary, data_uk_toggle="#save"),save_modal, 32 | Button("View Code", cls=ButtonT.secondary), 33 | Button("Share", cls=ButtonT.secondary),DropDownNavContainer(share_dd), 34 | Button(UkIcon(icon="ellipsis"), cls=ButtonT.secondary), 35 | DropDownNavContainer( 36 | Li(A("Content filter preferences")), 37 | NavDividerLi(), 38 | Li(A("Delete preset", cls="text-destructive")), 39 | uk_dropdown="mode: click")) 40 | 41 | return NavBar(*rnav, brand=H4('Playground')) 42 | 43 | rsidebar = NavContainer( 44 | Select( 45 | Optgroup(map(Option,("text-davinci-003", "text-curie-001", "text-babbage-001", "text-ada-001")),label='GPT-3'), 46 | Optgroup(map(Option,("code-davinci-002", "code-cushman-001")),label='Codex'), 47 | label="Model", 48 | searchable=True), 49 | LabelRange(label='Temperature', value='12'), 50 | LabelRange(label='Maximum Length', value='80'), 51 | LabelRange(label='Top P', value='40'), 52 | cls='space-y-6 mt-8') 53 | 54 | @rt 55 | def index(): 56 | navbar = playground_navbar() 57 | main_content = Div( 58 | Div(cls="flex-1")( 59 | Textarea(cls="uk-textarea h-full p-4", placeholder="Write a tagline for an ice cream shop")), 60 | cls="flex h-[700px] p-8 w-4/5") 61 | 62 | bottom_buttons = Div( 63 | Button("Submit", cls=ButtonT.primary), 64 | Button(UkIcon(icon="history"), cls=ButtonT.secondary), 65 | cls="flex gap-x-2") 66 | 67 | return Title("Playground Example"),Div(navbar, Div(cls="flex w-full")(main_content, rsidebar), bottom_buttons) 68 | 69 | serve() 70 | -------------------------------------------------------------------------------- /docs/examples/scrollspy.py: -------------------------------------------------------------------------------- 1 | "MonsterUI Scrollspy Example application" 2 | 3 | from fasthtml.common import * 4 | from monsterui.all import * 5 | import random 6 | 7 | # Using the "slate" theme with Highlight.js enabled 8 | hdrs = Theme.slate.headers(highlightjs=True) 9 | app, rt = fast_app(hdrs=hdrs) 10 | 11 | ################################ 12 | ### Example Data and Content ### 13 | ################################ 14 | products = [ 15 | {"name": "Laptop", "price": "$999"}, 16 | {"name": "Smartphone", "price": "$599"} 17 | ] 18 | 19 | code_example = """ 20 | # Python Code Example 21 | def greet(name): 22 | return f"Hello, {name}!" 23 | 24 | print(greet("World")) 25 | """ 26 | testimonials = [ 27 | {"name": "Alice", "feedback": "Great products and excellent customer service!"}, 28 | {"name": "Bob", "feedback": "Fast shipping and amazing quality!"}, 29 | {"name": "Charlie", "feedback": "Amazing experience! Will definitely buy again."}, 30 | {"name": "Diana", "feedback": "Affordable prices and great variety!"}, 31 | {"name": "Edward", "feedback": "Customer support was very helpful."}, 32 | {"name": "Fiona", "feedback": "Loved the design and quality!"} 33 | ] 34 | 35 | # Team members 36 | team = [ 37 | {"name": "Isaac Flath", "role": "CEO"}, 38 | {"name": "Benjamin Clavié", "role": "AI Researcher"}, 39 | {"name": "Alexis Gallagher", "role": "ML Engineer"}, 40 | {"name": "Hamel Husain", "role": "Data Scientist"}, 41 | {"name": "Austin Huang", "role": "Software Engineer"}, 42 | {"name": "Benjamin Warner", "role": "Product Manager"}, 43 | {"name": "Jonathan Whitaker", "role": "UX Designer"}, 44 | {"name": "Kerem Turgutlu", "role": "DevOps Engineer"}, 45 | {"name": "Curtis Allan", "role": "DevOps Engineer"}, 46 | {"name": "Audrey Roy Greenfeld", "role": "Security Analyst"}, 47 | {"name": "Nathan Cooper", "role": "Full Stack Developer"}, 48 | {"name": "Jeremy Howard", "role": "CTO"}, 49 | {"name": "Wayde Gilliam", "role": "Cloud Architect"}, 50 | {"name": "Daniel Roy Greenfeld", "role": "Blockchain Expert"}, 51 | {"name": "Tommy Collins", "role": "AI Ethics Researcher"} 52 | ] 53 | 54 | 55 | def ProductCard(p,img_id=1): 56 | return Card( 57 | PicSumImg(w=500, height=100, id=img_id), 58 | DivFullySpaced(H4(p["name"]), P(Strong(p["price"], cls=TextT.sm))), 59 | Button("Details", cls=(ButtonT.primary, "w-full"))) 60 | 61 | def TestimonialCard(t,img_id=1): 62 | return Card( 63 | DivLAligned(PicSumImg(w=50, h=50, cls='rounded-full', id=img_id), H4(t["name"])), 64 | P(Q((t["feedback"])))) 65 | 66 | 67 | def TeamCard(m,img_id=1): 68 | return Card( 69 | DivLAligned( 70 | PicSumImg(w=50, h=50, cls='rounded-full', id=img_id), 71 | Div(H4(m["name"]), P(m["role"]))), 72 | DivRAligned( 73 | UkIcon('twitter', cls='w-5 h-5'), 74 | UkIcon('linkedin', cls='w-5 h-5'), 75 | UkIcon('github', cls='w-5 h-5'), 76 | cls=TextT.gray+'space-x-2' 77 | ), 78 | cls='p-3') 79 | 80 | ################################ 81 | ### Navigation and Scrollspy ### 82 | ################################ 83 | 84 | scrollspy_links = ( 85 | A("Welcome", href="#welcome-section"), 86 | A("Products", href="#products-section"), 87 | A("Testimonials", href="#testimonials-section"), 88 | A("Team", href="#team-section"), 89 | A("Code Example", href="#code-section")) 90 | @rt 91 | def index(): 92 | def _Section(*c, **kwargs): return Section(*c, cls='space-y-3 my-48',**kwargs) 93 | return Container( 94 | NavBar( 95 | *scrollspy_links, 96 | brand=DivLAligned(H3("Scrollspy Demo!"),UkIcon('rocket',height=30,width=30)), 97 | sticky=True, uk_scrollspy_nav=True, 98 | scrollspy_cls=ScrollspyT.bold), 99 | NavContainer( 100 | *map(Li, scrollspy_links), 101 | uk_scrollspy_nav=True, 102 | sticky=True, 103 | cls=(NavT.primary,'pt-20 px-5 pr-10')), 104 | Container( 105 | # Notice the ID of each section corresponds to the `scrollspy_links` dictionary 106 | # So in scollspy `NavContainer` the `href` of each `Li` is the ID of the section 107 | DivCentered( 108 | H1("Welcome to the Store!"), 109 | Subtitle("Explore our products and enjoy dynamic code examples."), 110 | id="welcome-section"), 111 | _Section(H2("Products"), 112 | Grid(*[ProductCard(p,img_id=i) for i,p in enumerate(products)], cols_lg=2), 113 | id="products-section"), 114 | _Section(H2("Testimonials"), 115 | Slider(*[TestimonialCard(t,img_id=i) for i,t in enumerate(testimonials)]), 116 | id="testimonials-section"), 117 | _Section(H2("Our Team"), 118 | Grid(*[TeamCard(m,img_id=i) for i,m in enumerate(team)], cols_lg=2, cols_max=3), 119 | id="team-section"), 120 | _Section(H2("Code Example"), 121 | CodeBlock(code_example, lang="python"), 122 | id="code-section")), 123 | cls=(ContainerT.xl,'uk-container-expand')) 124 | 125 | serve() 126 | -------------------------------------------------------------------------------- /docs/examples/tasks.py: -------------------------------------------------------------------------------- 1 | """FrankenUI Tasks Example built with MonsterUI (original design by ShadCN)""" 2 | 3 | from fasthtml.common import * 4 | from monsterui.all import * 5 | from fasthtml.svg import * 6 | import json 7 | 8 | app, rt = fast_app(hdrs=Theme.blue.headers()) 9 | 10 | def LAlignedCheckTxt(txt): return DivLAligned(UkIcon(icon='check'), P(txt, cls=TextPresets.muted_sm)) 11 | 12 | with open('data/status_list.json', 'r') as f: data = json.load(f) 13 | with open('data/statuses.json', 'r') as f: statuses = json.load(f) 14 | 15 | def _create_tbl_data(d): 16 | return {'Done': d['selected'], 'Task': d['id'], 'Title': d['title'], 17 | 'Status' : d['status'], 'Priority': d['priority'] } 18 | 19 | data = [_create_tbl_data(d) for d in data] 20 | page_size = 15 21 | current_page = 0 22 | paginated_data = data[current_page*page_size:(current_page+1)*page_size] 23 | 24 | priority_dd = [{'priority': "low", 'count': 36 }, {'priority': "medium", 'count': 33 }, {'priority': "high", 'count': 31 }] 25 | 26 | status_dd = [{'status': "backlog", 'count': 21 },{'status': "todo", 'count': 21 },{'status': "progress", 'count': 20 },{'status': "done",'count': 19 },{'status': "cancelled", 'count': 19 }] 27 | 28 | def create_hotkey_li(hotkey): return NavCloseLi(A(DivFullySpaced(hotkey[0], Span(hotkey[1], cls=TextPresets.muted_sm)))) 29 | 30 | hotkeys_a = (('Profile','⇧⌘P'),('Billing','⌘B'),('Settings','⌘S'),('New Team','')) 31 | hotkeys_b = (('Logout',''), ) 32 | 33 | avatar_opts = DropDownNavContainer( 34 | NavHeaderLi(P('sveltecult'),NavSubtitle('leader@sveltecult.com')), 35 | NavDividerLi(), 36 | *map(create_hotkey_li, hotkeys_a), 37 | NavDividerLi(), 38 | *map(create_hotkey_li, hotkeys_b),) 39 | 40 | def CreateTaskModal(): 41 | return Modal( 42 | Div(cls='p-6')( 43 | ModalTitle('Create Task'), 44 | P('Fill out the information below to create a new task', cls=TextPresets.muted_sm), 45 | Br(), 46 | Form(cls='space-y-6')( 47 | Grid(Div(Select(*map(Option,('Documentation', 'Bug', 'Feature')), label='Task Type', id='task_type')), 48 | Div(Select(*map(Option,('In Progress', 'Backlog', 'Todo', 'Cancelled', 'Done')), label='Status', id='task_status')), 49 | Div(Select(*map(Option, ('Low', 'Medium', 'High')), label='Priority', id='task_priority'))), 50 | TextArea(label='Title', placeholder='Please describe the task that needs to be completed'), 51 | DivRAligned( 52 | ModalCloseButton('Cancel', cls=ButtonT.ghost), 53 | ModalCloseButton('Submit', cls=ButtonT.primary), 54 | cls='space-x-5'))), 55 | id='TaskForm') 56 | 57 | page_heading = DivFullySpaced(cls='space-y-2')( 58 | Div(cls='space-y-2')( 59 | H2('Welcome back!'),P("Here's a list of your tasks for this month!", cls=TextPresets.muted_sm)), 60 | Div(DiceBearAvatar("sveltcult",8,8),avatar_opts)) 61 | 62 | table_controls =(Input(cls='w-[250px]',placeholder='Filter task'), 63 | Button("Status"), 64 | DropDownNavContainer(map(NavCloseLi,[A(DivFullySpaced(P(a['status']), P(a['count'])),cls='capitalize') for a in status_dd])), 65 | Button("Priority"), 66 | DropDownNavContainer(map(NavCloseLi,[A(DivFullySpaced(LAlignedCheckTxt(a['priority']), a['count']),cls='capitalize') for a in priority_dd])), 67 | Button("View"), 68 | DropDownNavContainer(map(NavCloseLi,[A(LAlignedCheckTxt(o)) for o in ['Title','Status','Priority']])), 69 | Button('Create Task',cls=(ButtonT.primary, TextPresets.bold_sm), data_uk_toggle="target: #TaskForm")) 70 | 71 | def task_dropdown(): 72 | return Div(Button(UkIcon('ellipsis')), 73 | DropDownNavContainer( 74 | map(NavCloseLi,[ 75 | *map(A,('Edit', 'Make a copy', 'Favorite')), 76 | A(DivFullySpaced(*[P(o, cls=TextPresets.muted_sm) for o in ('Delete', '⌘⌫')]))]))) 77 | def header_render(col): 78 | match col: 79 | case "Done": return Th(CheckboxX(), shrink=True) 80 | case 'Actions': return Th("", shrink=True) 81 | case _: return Th(col, expand=True) 82 | 83 | def cell_render(col, val): 84 | def _Td(*args,cls='', **kwargs): return Td(*args, cls=f'p-2 {cls}',**kwargs) 85 | match col: 86 | case "Done": return _Td(shrink=True)(CheckboxX(selected=val)) 87 | case "Task": return _Td(val, cls='uk-visible@s') # Hide on small screens 88 | case "Title": return _Td(val, cls='font-medium', expand=True) 89 | case "Status" | "Priority": return _Td(cls='uk-visible@m uk-text-nowrap capitalize')(Span(val)) 90 | case "Actions": return _Td(task_dropdown(), shrink=True) 91 | case _: raise ValueError(f"Unknown column: {col}") 92 | 93 | task_columns = ["Done", 'Task', 'Title', 'Status', 'Priority', 'Actions'] 94 | 95 | tasks_table = Div(cls='mt-4')( 96 | TableFromDicts( 97 | header_data=task_columns, 98 | body_data=paginated_data, 99 | body_cell_render=cell_render, 100 | header_cell_render=header_render, 101 | sortable=True, 102 | cls=(TableT.responsive, TableT.sm, TableT.divider))) 103 | 104 | 105 | def footer(): 106 | total_pages = (len(data) + page_size - 1) // page_size 107 | return DivFullySpaced( 108 | Div('1 of 100 row(s) selected.', cls=TextPresets.muted_sm), 109 | DivLAligned( 110 | DivCentered(f'Page {current_page + 1} of {total_pages}', cls=TextT.sm), 111 | DivLAligned(*[UkIconLink(icon=i, button=True) for i in ('chevrons-left', 'chevron-left', 'chevron-right', 'chevrons-right')]))) 112 | 113 | tasks_ui = Div(DivFullySpaced(DivLAligned(table_controls), cls='mt-8'), tasks_table, footer()) 114 | 115 | @rt 116 | def index(): return Container(page_heading, tasks_ui, CreateTaskModal()) 117 | 118 | serve() 119 | -------------------------------------------------------------------------------- /docs/examples/ticket.py: -------------------------------------------------------------------------------- 1 | """MonsterUI Help Desk Example - Professional Dashboard with DaisyUI components""" 2 | from fasthtml.common import * 3 | from monsterui.all import * 4 | from datetime import datetime 5 | 6 | app, rt = fast_app(hdrs=Theme.blue.headers(daisy=True)) 7 | 8 | def TicketSteps(step): 9 | return Steps( 10 | LiStep("Submitted", data_content="📝", 11 | cls=StepT.success if step > 0 else StepT.primary if step == 0 else StepT.neutral), 12 | LiStep("In Review", data_content="🔎", 13 | cls=StepT.success if step > 1 else StepT.primary if step == 1 else StepT.neutral), 14 | LiStep("Processing", data_content="⚙️", 15 | cls=StepT.success if step > 2 else StepT.primary if step == 2 else StepT.neutral), 16 | LiStep("Resolved", data_content="✅", 17 | cls=StepT.success if step > 3 else StepT.primary if step == 3 else StepT.neutral), 18 | cls="w-full") 19 | 20 | def StatusBadge(status): 21 | styles = {'high': AlertT.error, 'medium': AlertT.warning,'low': AlertT.info} 22 | alert_type = styles.get(status, AlertT.info) 23 | return Alert(f"{status.title()} Priority", cls=(alert_type,"w-32 shadow-sm")) 24 | 25 | def TicketCard(id, title, description, status, step, department): 26 | return Card( 27 | CardHeader( 28 | DivFullySpaced( 29 | Div(H3(f"#{id}", cls=TextT.muted), 30 | H4(title), 31 | cls='space-y-2'), 32 | StatusBadge(status))), 33 | CardBody( 34 | P(description, cls=(TextT.muted, "mb-6")), 35 | DividerSplit(cls="my-6"), 36 | TicketSteps(step), 37 | DividerSplit(cls="my-6"), 38 | DivFullySpaced( 39 | Div(Strong("Department"), 40 | P(department), 41 | cls=('space-y-3', TextPresets.muted_sm)), 42 | Div(Strong("Last Updated"), 43 | P(Time(datetime.now().strftime('%b %d, %H:%M'))), 44 | cls=('space-y-3', TextPresets.muted_sm)), 45 | Button("View Details", cls=ButtonT.primary), 46 | cls='mt-6')), 47 | cls=CardT.hover) 48 | 49 | def NewTicketModal(): 50 | return Modal( 51 | ModalHeader(H3("Create New Support Ticket")), 52 | ModalBody( 53 | Alert( 54 | DivLAligned(UkIcon("info"), Span("Please provide as much detail as possible to help us assist you quickly.")), 55 | cls=(AlertT.info,"mb-4")), 56 | Form( 57 | Grid(LabelInput("Title", id="title", placeholder="Brief description of your issue"), 58 | LabelSelect(Options("IT Support", "HR", "Facilities", "Finance"), label="Department", id="department")), 59 | LabelSelect(Options("Low", "Medium", "High"), label="Priority Level", id="priority"), 60 | LabelTextArea("Description", id="description", placeholder="Please provide detailed information about your issue"), 61 | DivRAligned( 62 | Button("Cancel", cls=ButtonT.ghost, data_uk_toggle="target: #new-ticket"), 63 | Button(Loading(cls=LoadingT.spinner), "Submit Ticket", cls=ButtonT.primary, data_uk_toggle="target: #success-toast; target: #new-ticket")), 64 | cls='space-y-8')), 65 | id="new-ticket") 66 | 67 | @rt 68 | def index(): 69 | tickets = [ 70 | {'id': "TK-1001", 'title': "Cloud Storage Access Error", 71 | 'description': "Unable to access cloud storage with persistent authorization errors. Multiple users affected across marketing department.", 72 | 'status': 'high', 'step': 2, 'department': 'IT Support'}, 73 | {'id': "TK-1002", 'title': "Email Integration Issue", 74 | 'description': "Exchange server not syncing with mobile devices. Affecting external client communications.", 75 | 'status': 'medium', 'step': 1, 'department': 'IT Support'}, 76 | {'id': "TK-1003", 'title': "Office Equipment Setup", 77 | 'description': "New department printer needs configuration and network integration. Required for upcoming client presentation.", 78 | 'status': 'low', 'step': 0, 'department': 'Facilities'} 79 | ] 80 | 81 | return Title("Help Desk Dashboard"), Container( 82 | Section( 83 | DivFullySpaced( 84 | H2("Active Tickets"), 85 | Button(UkIcon("plus-circle", cls="mr-2"), "New Ticket", cls=ButtonT.primary, data_uk_toggle="target: #new-ticket"), 86 | cls='mb-8'), 87 | Grid(*[TicketCard(**ticket) for ticket in tickets], cols=1), 88 | cls="my-6"), 89 | NewTicketModal(), 90 | Toast(DivLAligned(UkIcon('check-circle', cls='mr-2'), "Ticket submitted successfully! Our team will review it shortly."), id="success-toast", alert_cls=AlertT.success, cls=(ToastHT.end, ToastVT.bottom)), 91 | Loading(htmx_indicator=True, type=LoadingT.dots, cls="fixed top-0 right-0 m-4"), 92 | cls="mx-auto max-w-7xl" 93 | ) 94 | 95 | serve() -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/docs/favicon.ico -------------------------------------------------------------------------------- /docs/getting_started/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # MonsterUI 2 | 3 | MonsterUI is a UI framework for FastHTML for building beautiful web interfaces with minimal code. It combines the simplicity of Python with the power of Tailwind. Perfect for data scientists, ML engineers, and developers who want to quickly turn their Python code into polished web apps without the complexity of traditional UI frameworks. Follows semantic HTML patterns when possible. 4 | 5 | MonsterUI adds the following Tailwind-based libraries [Franken UI](https://franken-ui.dev/) and [DaisyUI](https://daisyui.com/) to FastHTML, as well as Python's [Mistletoe](https://github.com/miyuchina/mistletoe) for Markdown, [HighlightJS](https://highlightjs.org/) for code highlighting, and [Katex](https://katex.org/) for latex support. 6 | 7 | # Getting Started 8 | 9 | ## Installation 10 | 11 | To install this library, uses 12 | 13 | `pip install MonsterUI` 14 | 15 | ## Getting Started 16 | 17 | ### TLDR 18 | 19 | Run `python file.py` on this to start: 20 | 21 | ``` python 22 | from fasthtml.common import * 23 | from monsterui.all import * 24 | 25 | # Choose a theme color (blue, green, red, etc) 26 | hdrs = Theme.blue.headers() 27 | 28 | # Create your app with the theme 29 | app, rt = fast_app(hdrs=hdrs) 30 | 31 | @rt 32 | def index(): 33 | socials = (('github','https://github.com/AnswerDotAI/MonsterUI'), 34 | ('twitter','https://twitter.com/isaac_flath/'), 35 | ('linkedin','https://www.linkedin.com/in/isaacflath/')) 36 | return Titled("Your First App", 37 | Card( 38 | H1("Welcome!"), 39 | P("Your first MonsterUI app", cls=TextPresets.muted_sm), 40 | P("I'm excited to see what you build with MonsterUI!"), 41 | footer=DivLAligned(*[UkIconLink(icon,href=url) for icon,url in socials]))) 42 | 43 | serve() 44 | ``` 45 | 46 | ## LLM context files 47 | 48 | Using LLMs for development is a best practice way to get started and 49 | explore. While LLMs cannot code for you, they can be helpful assistants. 50 | You must check, refactor, test, and vet any code any LLM generates for 51 | you - but they are helpful productivity tools. Take a look inside the 52 | `llms.txt` file to see links to particularly useful context files! 53 | 54 | - [llms.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms.txt): Links to what is included 55 | - [llms-ctx.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms-ctx.txt): MonsterUI Documentation Pages 56 | - [API list](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/apilist.txt): API list for MonsterUI (included in llms-ctx.txt) 57 | - [llms-ctx-full.txt](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms-ctx-full.txt): Full context that includes all api reference pages as markdown 58 | 59 | In addition you can add `/md` (for markdown) to a url to get a markdown representation and `/rmd` for rendered markdown representation (nice for looking to see what would be put into context. 60 | 61 | ### Step by Step 62 | 63 | To get started, check out: 64 | 65 | 1. Start by importing the modules as follows: 66 | 67 | ``` python 68 | from fasthtml.common import * 69 | from monsterui.all import * 70 | ``` 71 | 72 | 2. Instantiate the app with the MonsterUI headers 73 | 74 | ``` python 75 | app = FastHTML(hdrs=Theme.blue.headers()) 76 | 77 | # Alternatively, using the fast_app method 78 | app, rt = fast_app(hdrs=Theme.slate.headers()) 79 | ``` 80 | 81 | > *The color option can be any of the theme options available out of the 82 | > box* 83 | 84 | > `katex` and `highlightjs` are not included by default. To include them set `katex=True` or `highlightjs=True` when calling `.headers`. (i.e. `Theme.slate.headers(katex=True)`)* 85 | 86 | From here, you can explore the API Reference & examples to see how to 87 | implement the components. You can also check out these demo videos to as 88 | a quick start guide: 89 | 90 | - MonsterUI [documentation page and Tutorial 91 | app](https://monsterui.answer.ai/tutorial_app) 92 | - Isaac & Hamel : [Building his website’s team 93 | page](https://youtu.be/22Jn46-mmM0) 94 | - Isaac & Audrey : [Building a blog](https://youtu.be/gVWAsywxLXE) 95 | - Isaac : [Building a blog](https://youtu.be/22NJgfAqgko) 96 | 97 | More resources and improvements to the documentation will be added here 98 | soon! 99 | -------------------------------------------------------------------------------- /docs/getting_started/StylingRulesOfThumb.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | from monsterui.all import * 3 | from utils import create_flippable_card, fn2code_string 4 | 5 | def prerequisites(): 6 | return Section( 7 | H2("Prerequisites"), 8 | P("""The MonsterUI library automatically handles a lot of styling for you, but it assumes you structure your page with HTML. 9 | If you aren't familiar with the basics of HTML, check out the """, 10 | A("W3 Schools HTML references", href="https://www.w3schools.com/html", cls=AT.muted), 11 | " to learn more about the basics of HTML."), 12 | cls='my-4 py-4' 13 | ) 14 | 15 | def next_steps(): 16 | return Section( 17 | H2("Next Steps"), 18 | P("""Once you have a good grasp of HTML, you can reference the following resources to continue to expand your capabilities. Instead of trying to learn all of these, focus on the ones that are relevant to something specific you want to do."""), 19 | List(map(Li, [ 20 | A("Improving Aesthetics with Spacing", href="https://frankenui.fasthtml.io/docs/getting_started/SpacingTutorial", cls=AT.muted), 21 | A("Manipulating Page and Element Layout", href="https://frankenui.fasthtml.io/docs/getting_started/LayoutTutorial", cls=AT.muted), 22 | A("FlexBox Froggy", href="https://flexboxfroggy.com/", cls=AT.muted), 23 | A("Accessibility", href="https://frankenui.fasthtml.io/docs/getting_started/Accessibility", cls=AT.muted), 24 | ]), cls=ListT.bullet), 25 | cls='my-4 py-4' 26 | ) 27 | 28 | def page(): 29 | return Article( 30 | ArticleTitle("Styling Rules of Thumb"), 31 | ArticleMeta("A guide to making a pretty good looking website in a hurry"), 32 | prerequisites(), 33 | next_steps(), 34 | # button_section(), 35 | # typography_section(), 36 | ) 37 | 38 | 39 | def accessibility_section(): 40 | # TODO: Alt tags 41 | # TODO: Aria labels 42 | # TODO: High contrast 43 | # TODO: Prefers reduced motion 44 | pass 45 | 46 | def further_reading(): 47 | # TODO: Tailwind CSS 48 | # TODO: FlexBox Froggy 49 | # TODO: Accessibility 50 | pass 51 | 52 | def button_section(): 53 | def _ex_buttons(): 54 | return Form( 55 | Grid(LabelInput("Email"), 56 | LabelInput("Name"), 57 | cols=2), 58 | Grid(Button("Submit Information", cls=ButtonT.primary), 59 | Button("Delete Information", cls=ButtonT.destructive), 60 | Button("Cancel"), 61 | cols=3) 62 | ) 63 | 64 | return Section( 65 | H2("Pick button styles based on desired behavior"), 66 | Blockquote("The aethetic of a button should match the desired behavior",cls='uk-blockquote mb-8'), 67 | Strong("What to do:"), 68 | List( 69 | Li("Use ButtonT.primary for the most important actions (ie add to card, checkout, etc.)"), 70 | Li("Use ButtonT.secondary for actions that are important but not the primary action (ie save, etc.)"), 71 | Li("Use ButtonT.destructive for destructive actions"), 72 | Li("Use default styling for UX actions (ie go cancel, close etc.)"), 73 | cls=ListT.bullet 74 | ), 75 | create_flippable_card(*fn2code_string(_ex_buttons)), 76 | cls='my-4 py-4' 77 | ) 78 | 79 | def typography_section(): 80 | def _ex_typography(): 81 | return Div(cls='space-y-5')( 82 | Div(cls='space-y-3')( 83 | H1("The main heading"), 84 | Blockquote("A section describing easy ways to make generally good looking text"), 85 | ), 86 | Div(cls='space-y-3')( 87 | H2("My First Section"), 88 | P("A short description of what's in this section", cls=TextPresets.muted_sm), 89 | P("""Now I can write the main content of the page with a normal P tag. 90 | We can use this for longer text like paragraphs. 91 | It's ideal because this text is highly readable. 92 | I can write longer sentences and paragraphs without is being really hard to read with highly styled text. 93 | Often if you aren't careful with styling you can make the text hard to read, especially if you aren't thinking about light vs dark backgrounds. 94 | """)) 95 | ) 96 | 97 | 98 | return Section( 99 | H2("Use consistent typography"), 100 | Blockquote("Consistency is key to a polished user experience",cls='uk-blockquote mb-8'), 101 | Strong("What to do:"), 102 | List( 103 | Li("Use H1-4 for headings"), 104 | Li("Use P with cls=TextPresets.muted_sm subheadings like "), 105 | Li("Use P for most body text"), 106 | cls=ListT.bullet), 107 | create_flippable_card(*fn2code_string(_ex_typography)), 108 | cls='my-4 py-4' 109 | ) 110 | 111 | -------------------------------------------------------------------------------- /docs/getting_started/app_product_gallery.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | # MonsterUI shadows fasthtml components with the same name 3 | from monsterui.all import * 4 | # If you don't want shadowing behavior, you use import monsterui.core as ... style instead 5 | 6 | # Get frankenui and tailwind headers via CDN using Theme.blue.headers() 7 | hdrs = Theme.blue.headers() 8 | 9 | # fast_app is shadowed by MonsterUI to make it default to no Pico, and add body classes 10 | # needed for frankenui theme styling 11 | app, rt = fast_app(hdrs=hdrs) 12 | 13 | # Examples Product Data to render in the gallery and detail pages   14 | products = [ 15 | {"name": "Laptop", "price": "$999", "img": "https://picsum.photos/400/100?random=1"}, 16 | {"name": "Smartphone", "price": "$599", "img": "https://picsum.photos/400/100?random=2"}, 17 | {"name": "Headphones", "price": "$199", "img": "https://picsum.photos/400/100?random=3"}, 18 | {"name": "Smartwatch", "price": "$299", "img": "https://picsum.photos/400/100?random=4"}, 19 | {"name": "Tablet", "price": "$449", "img": "https://picsum.photos/400/100?random=5"}, 20 | {"name": "Camera", "price": "$799", "img": "https://picsum.photos/400/100?random=6"},] 21 | 22 | def ProductCard(p): 23 | # Card does lots of boilerplate classes so you can just pass in the content 24 | return Card( 25 | # width:100% makes the image take the full width so we are guarenteed that we won't 26 | # have the image cut off or not large enough. Because all our images are a consistent 27 | # size we do not need to worry about stretching or skewing the image, this is ideal. 28 | # If you have images of different sizes, you will need to use object-fit:cover and/or 29 | # height to either strech, shrink, or crop the image. It is much better to adjust your 30 | # images to be a consistent size upfront so you don't have to handle edge cases of 31 | # different images skeweing/stretching differently. 32 | Img(src=p["img"], alt=p["name"], style="width:100%"), 33 | # All components can take a cls argument to add additional styling - `mt-2` adds margin 34 | # to the top (see spacing tutorial for details on spacing). 35 | # 36 | # Often adding space makes a site look more put together - usually the 2 - 5 range is a 37 | # good choice 38 | H4(p["name"], cls="mt-2"), 39 | # There are helpful Enums, such as TextPresetsT, ButtonT, ContainerT, etc that allow for easy 40 | # discoverability of class options. 41 | # bold_sm is helpful for things that you want to look like regular text, but stand out 42 | # visually for emphasis. 43 | P(p["price"], cls=TextPresets.bold_sm), 44 | # ButtonT.primary is useful for actions you really want the user to take (like adding 45 | # something to the card) - these stand out visually. For dangerous actions (like 46 | # deleting something) you generally would want to use ButtonT.destructive. For UX actions 47 | # that aren't a goal of the page (like cancelling something that hasn't been submitted) 48 | # you generally want the default styling. 49 | Button("Click me!", cls=(ButtonT.primary, "mt-2"), 50 | # HTMX can be used as normal on any component 51 | hx_get=product_detail.to(product_name=p['name']), 52 | hx_push_url='true', 53 | hx_target='body')) 54 | 55 | @rt 56 | def index(): 57 | # Titled using a H1 title, sets the page title, and wraps contents in Main(Container(...)) using 58 | # frankenui styles. Generally you will want to use Titled for all of your pages 59 | return Titled("Example Store Front!", 60 | Grid(*[ProductCard(p) for p in products], cols_lg=3)) 61 | 62 | example_product_description = """\n 63 | This is a sample detailed description of the {product_name}. You can see when clicking on the card 64 | from the gallery you can: 65 | 66 | + Have a detailed description of the product on the page 67 | + Have an order form to fill out and submit 68 | + Anything else you want! 69 | """ 70 | 71 | @rt 72 | def product_detail(product_name:str): 73 | return Titled("Product Detail", 74 | # Grid lays out its children in a responsive grid 75 | Grid( 76 | Div( 77 | H1(product_name), 78 | # render_md is a helper that renders markdown into HTML using frankenui styles. 79 | render_md(example_product_description.format(product_name=product_name))), 80 | Div( 81 | H3("Order Form"), 82 | # Form automatically has a class of 'space-y-3' for a margin between each child. 83 | Form( 84 | # LabelInput is a convience wrapper for a label and input that links them. 85 | LabelInput("Name", id='name'), 86 | LabelInput("Email", id='email'), 87 | LabelInput("Quantity", id='quantity'), 88 | # ButtonT.primary because this is the primary action of the page! 89 | Button("Submit", cls=ButtonT.primary)) 90 | 91 | ), 92 | # Grid has defaults and args for cols at different breakpoints, but you can pass in 93 | # your own to customize responsiveness. 94 | cols_lg=2)) 95 | 96 | serve() 97 | -------------------------------------------------------------------------------- /docs/htmxindicator.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | from monsterui.all import * 3 | import time 4 | from fasthtml.components import Uk_theme_switcher 5 | 6 | app, rt = fast_app(hdrs=Theme.blue.headers()) 7 | 8 | @rt 9 | def index(): 10 | return Titled("Loading Demo", 11 | Button("Load", id='load', 12 | hx_get=load, hx_target='#content', hx_swap='beforeend', 13 | hx_indicator='#loading'), 14 | Div(id='content'), 15 | Loading(id='loading', htmx_indicator=True)) 16 | 17 | @rt 18 | def load(): 19 | time.sleep(1) 20 | return P("Loading Demo") 21 | 22 | @rt 23 | def theme(): return Uk_theme_switcher() 24 | 25 | serve() 26 | -------------------------------------------------------------------------------- /docs/llms.txt: -------------------------------------------------------------------------------- 1 | # MonsterUI Documentation 2 | 3 | > MonsterUI is a python library which brings styling to python for FastHTML apps. 4 | 5 | ## API Reference 6 | - [API List](https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/apilist.txt): Complete API Reference 7 | 8 | ## Examples 9 | - [Mail](https://monsterui.answer.ai/mail/md): FrankenUI Mail Example built with MonsterUI (original design by ShadCN) 10 | - [Forms](https://monsterui.answer.ai/forms/md): FrankenUI Forms Example built with MonsterUI (original design by ShadCN) 11 | - [Cards](https://monsterui.answer.ai/cards/md): FrankenUI Cards Example built with MonsterUI (original design by ShadCN) 12 | - [Ticket](https://monsterui.answer.ai/ticket/md): MonsterUI Help Desk Example - Professional Dashboard with DaisyUI components 13 | - [Auth](https://monsterui.answer.ai/auth/md): FrankenUI Auth Example built with MonsterUI (original design by ShadCN) 14 | - [Tasks](https://monsterui.answer.ai/tasks/md): FrankenUI Tasks Example built with MonsterUI (original design by ShadCN) 15 | - [Playground](https://monsterui.answer.ai/playground/md): FrankenUI Playground Example built with MonsterUI (original design by ShadCN) 16 | - [Music](https://monsterui.answer.ai/music/md): FrankenUI Music Example build with MonsterUI (Original design by ShadCN) 17 | - [Dashboard](https://monsterui.answer.ai/dashboard/md): FrankenUI Dashboard Example built with MonsterUI (original design by ShadCN) 18 | - [Scrollspy](https://monsterui.answer.ai/scrollspy/md): MonsterUI Scrollspy Example application 19 | 20 | ## Optional 21 | - [Accordion | Link](https://monsterui.answer.ai/api_ref/docs_accordion_link/md): Internal Server Error 22 | - [Button | Link](https://monsterui.answer.ai/api_ref/docs_button_link/md): Internal Server Error 23 | - [Cards](https://monsterui.answer.ai/api_ref/docs_cards/md): Internal Server Error 24 | - [Charts](https://monsterui.answer.ai/api_ref/docs_charts/md): Internal Server Error 25 | - [Containers](https://monsterui.answer.ai/api_ref/docs_containers/md): Internal Server Error 26 | - [Dividers](https://monsterui.answer.ai/api_ref/docs_dividers/md): Internal Server Error 27 | - [Forms](https://monsterui.answer.ai/api_ref/docs_forms/md): Internal Server Error 28 | - [Html](https://monsterui.answer.ai/api_ref/docs_html/md): Internal Server Error 29 | - [Icons | Images](https://monsterui.answer.ai/api_ref/docs_icons_images/md): Internal Server Error 30 | - [Layout](https://monsterui.answer.ai/api_ref/docs_layout/md): Internal Server Error 31 | - [Lightbox](https://monsterui.answer.ai/api_ref/docs_lightbox/md): Internal Server Error 32 | - [Lists](https://monsterui.answer.ai/api_ref/docs_lists/md): Internal Server Error 33 | - [Loading](https://monsterui.answer.ai/api_ref/docs_loading/md): Internal Server Error 34 | - [Markdown](https://monsterui.answer.ai/api_ref/docs_markdown/md): Internal Server Error 35 | - [Modals](https://monsterui.answer.ai/api_ref/docs_modals/md): Internal Server Error 36 | - [Navigation](https://monsterui.answer.ai/api_ref/docs_navigation/md): Internal Server Error 37 | - [Notifications](https://monsterui.answer.ai/api_ref/docs_notifications/md): Internal Server Error 38 | - [Sliders](https://monsterui.answer.ai/api_ref/docs_sliders/md): Internal Server Error 39 | - [Steps](https://monsterui.answer.ai/api_ref/docs_steps/md): Internal Server Error 40 | - [Tables](https://monsterui.answer.ai/api_ref/docs_tables/md): Internal Server Error 41 | - [Theme | Headers](https://monsterui.answer.ai/api_ref/docs_theme_headers/md): Internal Server Error 42 | - [Typography](https://monsterui.answer.ai/api_ref/docs_typography/md): Internal Server Error 43 | - [Layout](https://monsterui.answer.ai/tutorial_layout/md): MonsterUI Page Layout Guide 44 | - [Spacing](https://monsterui.answer.ai/tutorial_spacing/md): Padding & Margin & Spacing, Oh my! (MonsterUI Spacing Guide) -------------------------------------------------------------------------------- /docs/main.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | from functools import partial 3 | from monsterui.all import * 4 | from fasthtml.components import Uk_theme_switcher 5 | from utils import render_nb 6 | from pathlib import Path 7 | from toolslm.download import read_html,html2md 8 | from starlette.responses import PlainTextResponse 9 | import httpx 10 | 11 | def _not_found(req, exc): 12 | _path = req.url.path.rstrip('/') 13 | if _path.endswith('.md') or _path.endswith('.rmd'): 14 | url = f'https://monsterui.answer.ai{_path[:-3].rstrip("/").rstrip(".")}'.rstrip("/").rstrip(".") 15 | try: 16 | r = httpx.head(url, follow_redirects=True, timeout=1.0) 17 | if r.status_code < 400: # Accept 2xx and 3xx status codes 18 | if _path.endswith('.rmd') or _path.endswith('/rmd'): return Container(render_md(read_html(url, sel='#content'))) 19 | elif _path.endswith('.md') or _path.endswith('/md'): return PlainTextResponse(read_html(url, sel='#content')) 20 | except (httpx.TimeoutException, httpx.NetworkError): pass 21 | return _create_page( 22 | Container(Card(CardBody(H1("404 - Page Not Found"), P("The page you're looking for doesn't exist.")))), 23 | req, 24 | None) 25 | 26 | 27 | app,rt = fast_app(exception_handlers={404:_not_found}, pico=False, 28 | hdrs=(*Theme.blue.headers(highlightjs=True,apex_charts=True), Link(rel="icon", type="image/x-icon", href="/favicon.ico"), 29 | Link(rel="stylesheet", href="/custom_theme.css", type="text/css")), 30 | ) 31 | 32 | def is_htmx(request=None): 33 | "Check if the request is an HTMX request" 34 | return request and 'hx-request' in request.headers 35 | 36 | def _create_page(content, # The content to display (without the layout/sidebar) 37 | request, # Request object to determine if HTMX request 38 | sidebar_section, # The open section on the sidebar 39 | ): 40 | "Makes page load sidebar if direct request, otherwise loads content only via HTMX" 41 | if is_htmx(request): return content 42 | else: return with_layout(sidebar_section, content) 43 | 44 | def with_layout(sidebar_section, content): 45 | "Puts the sidebar and content into a layout" 46 | return Title(f"MonsterUI"), Div(cls="flex flex-col md:flex-row w-full")( 47 | Button(UkIcon("menu",50,50,cls='mt-4'), cls="md:hidden mb-4", data_uk_toggle="target: #mobile-sidebar"), 48 | Div(sidebar(sidebar_section), id='mobile-sidebar', hidden=True), 49 | Div(cls="md:flex w-full")( 50 | Div(sidebar(sidebar_section), cls="hidden md:block w-1/5 max-w-52"), 51 | Div(content, cls='md:w-4/5 w-full mr-5', id="content", ))) 52 | 53 | 54 | ### 55 | # Build the Example Pages 56 | ### 57 | 58 | from examples.tasks import index as tasks_homepage 59 | from examples.cards import index as cards_homepage 60 | from examples.dashboard import index as dashboard_homepage 61 | from examples.forms import index as forms_homepage 62 | from examples.music import index as music_homepage 63 | from examples.auth import index as auth_homepage 64 | from examples.playground import index as playground_homepage 65 | from examples.mail import index as mail_homepage 66 | from examples.scrollspy import index as scrollspy_homepage 67 | from examples.ticket import index as ticket_homepage 68 | def _example_route(name, homepage, o:str, request=None): 69 | match o: 70 | case 'code' | 'rmd': return Div(render_md(f'''```python\n\n{open(f'examples/{name}.py').read()}\n\n```''')) 71 | case 'md': return PlainTextResponse(open(f'examples/{name}.py').read()) 72 | case _: return _create_example_page(homepage, request) 73 | 74 | _create_example_page = partial(_create_page, sidebar_section='Examples') 75 | 76 | @rt('/scrollspy') 77 | @rt('/scrollspy/{o}') 78 | def scrollspy(o:str='', request=None): return _example_route('scrollspy', Div(DivRAligned(A("See Code",href='/scrollspy/code',cls='m-4 uk-btn'+ButtonT.default)),scrollspy_homepage()), o, request) 79 | 80 | @rt('/ticket') 81 | @rt('/ticket/{o}') 82 | def ticket(o:str='', request=None): return _example_route('ticket', Div(DivRAligned(A("See Code",href='/ticket/code',cls='m-4 uk-btn'+ButtonT.default)),ticket_homepage()), o, request) 83 | 84 | @rt('/tasks') 85 | @rt('/tasks/{o}') 86 | def tasks(o:str='', request=None): return _example_route('tasks', Div(DivRAligned(A("See Code",href='/tasks/code',cls='m-4 uk-btn'+ButtonT.default)),tasks_homepage()), o, request) 87 | 88 | @rt('/cards') 89 | @rt('/cards/{o}') 90 | def cards(o:str, request=None): return _example_route('cards', Div(DivRAligned(A("See Code",href='/cards/code',cls='m-4 uk-btn'+ButtonT.default)),cards_homepage()), o, request) 91 | 92 | @rt('/dashboard') 93 | @rt('/dashboard/{o}') 94 | def dashboard(o:str, request=None): return _example_route('dashboard', Div(DivRAligned(A("See Code",href='/dashboard/code',cls='m-4 uk-btn'+ButtonT.default)),dashboard_homepage()), o, request) 95 | 96 | @rt('/forms') 97 | @rt('/forms/{o}') 98 | def forms(o:str, request=None): return _example_route('forms', Div(DivRAligned(A("See Code",href='/forms/code',cls='m-4 uk-btn'+ButtonT.default)),forms_homepage()), o, request) 99 | 100 | @rt('/music') 101 | @rt('/music/{o}') 102 | def music(o:str, request=None): return _example_route('music', Div(DivRAligned(A("See Code",href='/music/code',cls='m-4 uk-btn'+ButtonT.default)),music_homepage()), o, request) 103 | 104 | @rt('/auth') 105 | @rt('/auth/{o}') 106 | def auth(o:str, request=None): return _example_route('auth', Div(DivRAligned(A("See Code",href='/auth/code',cls='m-4 uk-btn'+ButtonT.default)),auth_homepage()), o, request) 107 | 108 | @rt('/playground') 109 | @rt('/playground/{o}') 110 | def playground(o:str, request=None): return _example_route('playground', Div(DivRAligned(A("See Code",href='/playground/code',cls='m-4 uk-btn'+ButtonT.default)),playground_homepage()), o, request) 111 | 112 | @rt('/mail') 113 | @rt('/mail/{o}') 114 | def mail(o:str, request=None): return _example_route('mail', Div(DivRAligned(A("See Code",href='/mail/code',cls='m-4 uk-btn'+ButtonT.default)),mail_homepage()), o, request) 115 | 116 | ### 117 | # Build the API Reference Pages 118 | ### 119 | 120 | import api_reference.api_reference as api_reference 121 | def fname2title(ref_fn_name): return ref_fn_name[5:].replace('_',' | ').title() 122 | 123 | reference_fns = L([o for o in dir(api_reference) if o.startswith('docs_')]) 124 | @rt('/api_ref/{o}') 125 | def api_route(request, o:str): 126 | if o not in reference_fns: raise HTTPException(404) 127 | content = getattr(api_reference, o)() 128 | return _create_page(Container(content), request=request, sidebar_section='API Reference') 129 | 130 | @rt('/api_ref/{o}/md') 131 | def api_route_md(request, o:str): 132 | if o not in reference_fns: raise HTTPException(404) 133 | content = getattr(api_reference, o)() 134 | return PlainTextResponse(html2md(to_xml(content))) 135 | 136 | @rt('/api_ref/{o}rmd') 137 | def api_route_md(request, o:str): 138 | if o not in reference_fns: raise HTTPException(404) 139 | content = getattr(api_reference, o)() 140 | return Div(render_md(html2md(to_xml(content)))) 141 | 142 | ### 143 | # Build the Guides Pages 144 | ### 145 | @rt('/tutorial_spacing') 146 | @rt('/tutorial_spacing/{o}') 147 | def tutorial_spacing(o:str='', request=None): 148 | if o=='md': return PlainTextResponse(read_html(f'https://monsterui.answer.ai/tutorial_spacing', sel='#content')) 149 | if o=='rmd': return Div(render_md(read_html(f'https://monsterui.answer.ai/tutorial_spacing', sel='#content'))) 150 | return _create_page(render_nb('guides/Spacing.ipynb'), request, 'Guides') 151 | @rt('/tutorial_layout') 152 | @rt('/tutorial_layout/{o}') 153 | def tutorial_layout(o:str='', request=None): 154 | if o=='md': return PlainTextResponse(read_html(f'https://monsterui.answer.ai/tutorial_layout', sel='#content')) 155 | if o=='rmd': return Div(render_md(read_html(f'https://monsterui.answer.ai/tutorial_layout', sel='#content'))) 156 | return _create_page(render_nb('guides/Layout.ipynb'), request, 'Guides',) 157 | 158 | ### 159 | # Build the Theme Switcher Page 160 | ### 161 | 162 | @rt 163 | def theme_switcher(request): 164 | return _create_page(Div(ThemePicker(custom_themes=[("Grass", "#10b981")]),cls="p-12"), request, None) 165 | 166 | ### 167 | # Build the Getting Started Pages 168 | ### 169 | 170 | gs_path = Path('getting_started') 171 | 172 | @rt('/tutorial_app') 173 | @rt('/tutorial_app/{o}') 174 | def tutorial_app(o:str='', request=None): 175 | pass 176 | if o=='md': return PlainTextResponse(read_html(f'https://monsterui.answer.ai/tutorial_app', sel='#content')) 177 | if o=='rmd': return Div(render_md(read_html(f'https://monsterui.answer.ai/tutorial_app', sel='#content'))) 178 | app_code = open(gs_path/'app_product_gallery.py').read() 179 | app_rendered = Div(Pre(Code(app_code))) 180 | content = Container(cls='space-y-4')( 181 | H1("Tutorial App"), 182 | render_md("""This is a heavily commented example of a product gallery app built with MonsterUI for those that like to learn by example. \ 183 | This tutorial app assumes you have some familiarity with fasthtml apps already, so focuses on what MonsterUI adds on top of fasthtml. 184 | To make the most out of this tutorial, you should follow these steps:"""), 185 | Ol( 186 | Li("Briefly read through this to get an overview of what is happening, without focusing on any details"), 187 | Li("Install fasthtml and MonsterUI"), 188 | Li("Copy the code into your own project locally and run it using `python app.py`"), 189 | Li("Go through the code in detail to understand how it works by experimenting with changing things"), 190 | cls=ListT.decimal+TextT.lg 191 | ), 192 | render_md("""> Tip: Try adding `import fasthtml.common as fh`, so you can replace things with the base fasthtml components to see what happens! 193 | For example, try replacing `H4` with `fh.H4` or `Button` with `fh.Button`."""), 194 | Divider(), 195 | app_rendered) 196 | return _create_page(content, request, 'Getting Started') 197 | 198 | @rt('/') 199 | @rt('/{o}') 200 | def index(o:str='', request=None): 201 | if o=='md': return PlainTextResponse(read_html(f'https://monsterui.answer.ai/getting_started', sel='#content')) 202 | if o=='rmd': return Div(render_md(read_html(f'https://monsterui.answer.ai/getting_started', sel='#content'))) 203 | content = Container(render_md(open(gs_path/'GettingStarted.md').read())) 204 | return _create_page(content, request, 'Getting Started') 205 | 206 | ### 207 | # Build the Sidebar 208 | ### 209 | 210 | def sidebar(open_section): 211 | def create_li(title, href): 212 | return Li(A(title,hx_target="#content", hx_get=href, hx_push_url='true')) 213 | 214 | return NavContainer( 215 | NavParentLi( 216 | A(DivFullySpaced("Getting Started", )), 217 | NavContainer(create_li("Getting Started", index), 218 | create_li("Tutorial App", tutorial_app), 219 | parent=False), 220 | cls='uk-open' if open_section=='Getting Started' else '' 221 | ), 222 | NavParentLi( 223 | A(DivFullySpaced("API Reference", )), 224 | NavContainer( 225 | *[create_li(fname2title(o), f"/api_ref/{o}") for o in reference_fns], 226 | parent=False, 227 | ), 228 | cls='uk-open' if open_section=='API Reference' else '' 229 | ), 230 | NavParentLi( 231 | A(DivFullySpaced('Guides', )), 232 | NavContainer( 233 | *[create_li(title, href) for title, href in [ 234 | ('Spacing', tutorial_spacing), 235 | ('Layout', tutorial_layout), 236 | ]], 237 | parent=False 238 | ), 239 | cls='uk-open' if open_section=='Guides' else '' 240 | ), 241 | 242 | NavParentLi( 243 | A(DivFullySpaced('Examples', )), 244 | NavContainer( 245 | *[create_li(title, href) for title, href in [ 246 | ('Task', '/tasks/'), 247 | ('Card', '/cards/'), 248 | ('Dashboard', '/dashboard/'), 249 | ('Form', '/forms/'), 250 | ('Music', '/music/'), 251 | ('Auth', '/auth/'), 252 | ('Playground', '/playground/'), 253 | ('Mail', '/mail/'), 254 | ('Ticket', '/ticket/'), 255 | ('Scrollspy', '/scrollspy/'), 256 | ]], 257 | parent=False 258 | ), 259 | cls='uk-open' if open_section=='Examples' else '' 260 | ), 261 | create_li("Theme", theme_switcher), 262 | uk_nav=True, 263 | cls=(NavT.primary, "space-y-4 p-4 w-full md:w-full") 264 | ) 265 | 266 | serve() 267 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | toolslm 2 | python-fasthtml 3 | pandas 4 | plotly 5 | monsterui @ git+https://github.com/AnswerDotAI/monsterui 6 | fastcore 7 | fh_matplotlib 8 | numpy 9 | matplotlib 10 | pysymbol_llm @ git+https://github.com/AnswerDotAI/pysymbol-llm.git 11 | mistletoe 12 | lxml 13 | -------------------------------------------------------------------------------- /docs/updatellms.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./createllms.sh 3 | pysym2md --output_file apilist.txt monsterui 4 | llms_txt2ctx llms.txt >llms-ctx.txt 5 | llms_txt2ctx llms.txt --optional True > llms-ctx-full.txt 6 | -------------------------------------------------------------------------------- /docs/utils.py: -------------------------------------------------------------------------------- 1 | """Utilities for building the docs page that don't belong anywhere else""" 2 | 3 | 4 | __all__ = ['hjs', 'HShow', 'create_server'] 5 | 6 | from fasthtml.common import * 7 | from monsterui.all import * 8 | from fasthtml.jupyter import * 9 | from collections.abc import Callable 10 | import inspect 11 | import ast 12 | def get_last_statement(code): return ast.unparse(ast.parse(code).body[-1]) 13 | import json 14 | from pathlib import Path 15 | 16 | 17 | def create_flippable_card(content, source_code, extra_cls=None): 18 | "Creates a card that flips between content and source code" 19 | _id = 'f'+str(unqid()) 20 | _card = Card( 21 | Button( 22 | DivFullySpaced(UkIcon('corner-down-right', 20, 20, 3),"See Source"), 23 | data_uk_toggle=f"target: #{_id}", id=_id, cls=ButtonT.primary), 24 | Button( 25 | DivFullySpaced(UkIcon('corner-down-right', 20, 20, 3),"See Output"), 26 | data_uk_toggle=f"target: #{_id}", id=_id, cls=ButtonT.primary, hidden=True), 27 | Div(content, id=_id), 28 | Div(Pre(Code(source_code, cls="hljs language-python")), id=_id, hidden=True), 29 | cls='my-8') 30 | return Div(_card, cls=extra_cls) if extra_cls else _card 31 | 32 | def fn2code_string(fn: Callable) -> tuple: return fn(), inspect.getsource(fn) 33 | 34 | 35 | def render_nb(path): 36 | "Renders a Jupyter notebook with markdown cells and flippable code cards" 37 | namespace = globals().copy() 38 | # Read and parse the notebook 39 | nb_content = json.loads(Path(path).read_text()) 40 | cells = nb_content['cells'] 41 | 42 | # Convert cells to appropriate HTML elements 43 | rendered_cells = [] 44 | for cell in cells: 45 | if cell['cell_type'] == 'markdown': 46 | # Combine all markdown lines and render 47 | md_content = ''.join(cell['source']) 48 | rendered_cells.append(render_md(md_content)) 49 | elif cell['cell_type'] == 'code': 50 | # Skip empty code cells 51 | if not ''.join(cell['source']).strip(): continue 52 | # Create flippable card for code 53 | code_content = ''.join(cell['source']) 54 | exec(code_content, namespace) 55 | result = eval(get_last_statement(code_content), namespace) 56 | 57 | rendered_cells.append(create_flippable_card(result, code_content)) 58 | 59 | # Return all cells wrapped in a container with vertical spacing 60 | return Container(cls='space-y-4')(*rendered_cells) 61 | -------------------------------------------------------------------------------- /monsterui/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.22" -------------------------------------------------------------------------------- /monsterui/all.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | from .franken import * 3 | from .daisy import * 4 | -------------------------------------------------------------------------------- /monsterui/core.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_core.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['HEADER_URLS', 'daisy_styles', 'scrollspy_style', 'fast_app', 'FastHTML', 'ThemeRadii', 'ThemeShadows', 'ThemeFont', 5 | 'Theme'] 6 | 7 | # %% ../nbs/01_core.ipynb 8 | import fasthtml.common as fh 9 | from .foundations import * 10 | from fasthtml.common import FastHTML, fast_app 11 | from enum import Enum, auto 12 | from fastcore.all import * 13 | import httpx 14 | from pathlib import Path 15 | 16 | # %% ../nbs/01_core.ipynb 17 | @delegates(fh.fast_app, but=['pico']) 18 | def fast_app(*args, pico=False, **kwargs): 19 | "Create a FastHTML or FastHTMLWithLiveReload app with `bg-background text-foreground` to bodykw for frankenui themes" 20 | if 'bodykw' not in kwargs: kwargs['bodykw'] = {} 21 | if 'class' not in kwargs['bodykw']: kwargs['bodykw']['class'] = '' 22 | kwargs['bodykw']['class'] = stringify((kwargs['bodykw']['class'],'bg-background text-foreground')) 23 | return fh.fast_app(*args, pico=pico, **kwargs) 24 | 25 | # %% ../nbs/01_core.ipynb 26 | @delegates(fh.FastHTML, but=['pico']) 27 | def FastHTML(*args, pico=False, **kwargs): 28 | "Create a FastHTML app and adds `bg-background text-foreground` to bodykw for frankenui themes" 29 | if 'bodykw' not in kwargs: kwargs['bodykw'] = {} 30 | if 'class' not in kwargs['bodykw']: kwargs['bodykw']['class'] = '' 31 | kwargs['bodykw']['class'] = stringify((kwargs['bodykw']['class'],'bg-background text-foreground')) 32 | bodykw = kwargs.pop('bodykw',{}) 33 | return fh.FastHTML(*args, pico=pico, **bodykw, **kwargs) 34 | 35 | # %% ../nbs/01_core.ipynb 36 | class ThemeRadii(VEnum): 37 | none = 'uk-radii-none' 38 | sm = 'uk-radii-sm' 39 | md = 'uk-radii-md' 40 | lg = 'uk-radii-lg' 41 | 42 | class ThemeShadows: 43 | none = 'uk-shadows-none' 44 | sm = 'uk-shadows-sm' 45 | md = 'uk-shadows-md' 46 | lg = 'uk-shadows-lg' 47 | 48 | class ThemeFont: 49 | sm = 'uk-font-sm' 50 | default = 'uk-font-base' 51 | 52 | # %% ../nbs/01_core.ipynb 53 | def _headers_theme(color, mode='auto', radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm): 54 | franken_init = ''' 55 | const __FRANKEN__ = JSON.parse(localStorage.getItem("__FRANKEN__") || "{}"); 56 | ''' 57 | 58 | mode_script = { 59 | 'auto': f''' 60 | {franken_init} 61 | if ( 62 | __FRANKEN__.mode === "dark" || 63 | (!__FRANKEN__.mode && 64 | window.matchMedia("(prefers-color-scheme: dark)").matches) 65 | ) {{ 66 | htmlElement.classList.add("dark"); 67 | }} else {{ 68 | htmlElement.classList.remove("dark"); 69 | }} 70 | ''', 71 | 'light': f'{franken_init}\nhtmlElement.classList.remove("dark");', 72 | 'dark': f'{franken_init}\nhtmlElement.classList.add("dark");' 73 | } 74 | 75 | return fh.Script(f''' 76 | const htmlElement = document.documentElement; 77 | {mode_script[mode]} 78 | htmlElement.classList.add(__FRANKEN__.theme || "uk-theme-{color}"); 79 | htmlElement.classList.add(__FRANKEN__.radii || "{radii}"); 80 | htmlElement.classList.add(__FRANKEN__.shadows || "{shadows}"); 81 | htmlElement.classList.add(__FRANKEN__.font || "{font}"); 82 | ''') 83 | 84 | # %% ../nbs/01_core.ipynb 85 | HEADER_URLS = { 86 | 'franken_css': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/css/core.min.css", 87 | 'franken_js_core': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/core.iife.js", 88 | 'franken_icons': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/icon.iife.js", 89 | 'tailwind': "https://cdn.tailwindcss.com/3.4.16", 90 | 'daisyui': "https://cdn.jsdelivr.net/npm/daisyui@4.12.24/dist/full.min.css", 91 | 'apex_charts': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/chart.iife.js", 92 | 'highlight_js': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js", 93 | 'highlight_python': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/python.min.js", 94 | 'highlight_light_css': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/atom-one-light.css", 95 | 'highlight_dark_css': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/atom-one-dark.css", 96 | 'highlight_copy': "https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.js", 97 | 'highlight_copy_css': "https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.css", 98 | } 99 | 100 | def _download_resource(url, static_dir): 101 | "Download a single resource and return its local path" 102 | static = Path(static_dir) 103 | fname = static/f"{url[0]}.{'js' if 'js' in url[1] else 'css'}" 104 | content = httpx.get(url[1], follow_redirects=True).content 105 | fname.write_bytes(content) 106 | return (url[0], f"/{static_dir}/{fname.name}") 107 | 108 | # %% ../nbs/01_core.ipynb 109 | daisy_styles = Style(""" 110 | :root { 111 | --b1: from hsl(var(--background)) l c h; 112 | --bc: from hsl(var(--foreground)) l c h; 113 | --m: from hsl(var(--muted)) l c h; 114 | --mc: from hsl(var(--muted-foreground)) l c h; 115 | --po: from hsl(var(--popover)) l c h; 116 | --poc: from hsl(var(--popover-foreground)) l c h; 117 | --b2: from hsl(var(--card)) l c h; 118 | --b2c: from hsl(var(--card-foreground)) l c h; 119 | --br: from hsl(var(--border)) l c h; 120 | --in: from hsl(var(--input)) l c h; 121 | --p: from hsl(var(--primary)) l c h; 122 | --pc: from hsl(var(--primary-foreground)) l c h; 123 | --s: from hsl(var(--secondary)) l c h; 124 | --sc: from hsl(var(--secondary-foreground)) l c h; 125 | --a: from hsl(var(--accent)) l c h; 126 | --ac: from hsl(var(--accent-foreground)) l c h; 127 | --er: from hsl(var(--destructive)) l c h; 128 | --erc: from hsl(var(--destructive-foreground)) l c h; 129 | --b3: from hsl(var(--ring)) l c h; 130 | --ch1: from hsl(var(--chart-1)) l c h; 131 | --ch2: from hsl(var(--chart-2)) l c h; 132 | --ch3: from hsl(var(--chart-3)) l c h; 133 | --ch4: from hsl(var(--chart-4)) l c h; 134 | --ch5: from hsl(var(--chart-5)) l c h; 135 | --rd: var(--radius); 136 | } 137 | """) 138 | 139 | 140 | # %% ../nbs/01_core.ipynb 141 | scrollspy_style= Style(''' 142 | .monster-navbar.navbar-bold a { 143 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 144 | } 145 | .monster-navbar.navbar-bold a.uk-active { 146 | transform: scale(1.15) ; 147 | font-weight: bold; 148 | text-shadow: 0 0 12px rgba(var(--p-rgb), 0.4); 149 | letter-spacing: 0.02em; 150 | color: hsl(var(--p) / 1); 151 | } 152 | .monster-navbar.navbar-underline a.uk-active { position: relative; } 153 | .monster-navbar.navbar-underline a.uk-active::after { 154 | content: ''; 155 | position: absolute; 156 | left: 0; 157 | bottom: -2px; 158 | width: 100%; 159 | height: 2px; 160 | background: currentColor; 161 | animation: slideIn 0.3s ease forwards; 162 | } 163 | @keyframes slideIn { 164 | from { transform: scaleX(0); } 165 | to { transform: scaleX(1); } 166 | } 167 | ''') 168 | 169 | # %% ../nbs/01_core.ipynb 170 | class Theme(Enum): 171 | "Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind + daisyui + highlight.js options" 172 | def _generate_next_value_(name, start, count, last_values): return name 173 | slate = auto() 174 | stone = auto() 175 | gray = auto() 176 | neutral = auto() 177 | red = auto() 178 | rose = auto() 179 | orange = auto() 180 | green = auto() 181 | blue = auto() 182 | yellow = auto() 183 | violet = auto() 184 | zinc = auto() 185 | 186 | def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs=False, katex=False, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm): 187 | "Create header elements with given URLs" 188 | hdrs = [ 189 | fh.Link(rel="stylesheet", href=urls['franken_css']), 190 | fh.Script(type="module", src=urls['franken_js_core']), 191 | fh.Script(src=urls['tailwind']), 192 | fh.Script(""" 193 | tailwind.config = { 194 | darkMode: 'selector', 195 | } 196 | """), 197 | _headers_theme(self.value, mode=mode, radii=radii, shadows=shadows, font=font), 198 | scrollspy_style] 199 | 200 | if icons: hdrs.append(fh.Script(type="module", src=urls['franken_icons'])) 201 | if daisy: hdrs += [fh.Link(rel="stylesheet", href=urls['daisyui']), daisy_styles] 202 | if apex_charts: hdrs += [fh.Script(type='module', src=urls['apex_charts'])] 203 | 204 | if highlightjs: 205 | hdrs += [ 206 | fh.Script(src=urls['highlight_js']), 207 | fh.Script(src=urls['highlight_python']), 208 | fh.Link(rel="stylesheet", href=urls['highlight_light_css'], id='hljs-light'), 209 | fh.Link(rel="stylesheet", href=urls['highlight_dark_css'], id='hljs-dark'), 210 | fh.Script(src=urls['highlight_copy']), 211 | fh.Link(rel="stylesheet", href=urls['highlight_copy_css']), 212 | fh.Script(''' 213 | hljs.addPlugin(new CopyButtonPlugin()); 214 | hljs.configure({ 215 | cssSelector: 'pre code', 216 | languages: ['python'], 217 | ignoreUnescapedHTML: true 218 | }); 219 | function updateTheme() { 220 | const isDark = document.documentElement.classList.contains('dark'); 221 | document.getElementById('hljs-dark').disabled = !isDark; 222 | document.getElementById('hljs-light').disabled = isDark; 223 | } 224 | new MutationObserver(mutations => 225 | mutations.forEach(m => m.target.tagName === 'HTML' && 226 | m.attributeName === 'class' && updateTheme()) 227 | ).observe(document.documentElement, { attributes: true }); 228 | updateTheme(); 229 | htmx.onLoad(hljs.highlightAll); 230 | ''', type='module'), 231 | ] 232 | 233 | if katex: 234 | hdrs += [ 235 | fh.Link(rel="stylesheet", 236 | href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css"), 237 | fh.Script(""" 238 | import katex from 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.mjs'; 239 | import autoRender from 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.mjs'; 240 | const options = { 241 | delimiters: [ 242 | {left: '$$', right: '$$', display: true}, 243 | {left: '$', right: '$', display: false} 244 | ], 245 | ignoredClasses: ['nomath'] 246 | }; 247 | 248 | document.addEventListener('htmx:load', evt => { 249 | const element = evt.detail.elt || document.body; 250 | autoRender(element,options); 251 | }); 252 | """,type="module"), 253 | ] 254 | return hdrs 255 | 256 | def headers(self, mode='auto', icons=True, daisy=True, highlightjs=False, katex=False, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm ): 257 | "Create frankenui and tailwind cdns" 258 | return self._create_headers(HEADER_URLS, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font) 259 | 260 | def local_headers(self, mode='auto', static_dir='static', icons=True, daisy=True, highlightjs=False, katex=False, apex_charts=False, radii='md', shadows='sm', font='sm'): 261 | "Create headers using local files downloaded from CDNs" 262 | Path(static_dir).mkdir(exist_ok=True) 263 | local_urls = dict([_download_resource(url, static_dir) for url in HEADER_URLS.items()]) 264 | return self._create_headers(local_urls, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font) 265 | -------------------------------------------------------------------------------- /monsterui/daisy.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_daisy.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['AlertT', 'Alert', 'StepsT', 'StepT', 'Steps', 'LiStep', 'LoadingT', 'Loading', 'ToastHT', 'ToastVT', 'Toast'] 5 | 6 | # %% ../nbs/03_daisy.ipynb 7 | import fasthtml.common as fh 8 | from .foundations import * 9 | from fasthtml.common import Div, Span, FT 10 | from fasthtml.svg import * 11 | from enum import auto 12 | from fastcore.all import * 13 | 14 | 15 | # %% ../nbs/03_daisy.ipynb 16 | class AlertT(VEnum): 17 | "Alert styles from DaisyUI" 18 | def _generate_next_value_(name, start, count, last_values): return f"alert-{name}" 19 | info = auto() 20 | success = auto() 21 | warning = auto() 22 | error = auto() 23 | 24 | # %% ../nbs/03_daisy.ipynb 25 | def Alert(*c, # Content for Alert (often text and/or icon) 26 | cls='', # Class for the alert (often an `AlertT` option) 27 | **kwargs # Additional arguments for outer Div 28 | )->FT: # Div(Span(...), cls='alert', role='alert') 29 | "Alert informs users about important events." 30 | return Div(Span(*c), cls=('alert', stringify(cls)), role='alert', **kwargs) 31 | 32 | # %% ../nbs/03_daisy.ipynb 33 | class StepsT(VEnum): 34 | "Options for Steps" 35 | def _generate_next_value_(name, start, count, last_values): return f'steps-{name}' 36 | vertical = auto() 37 | horizonal = auto() 38 | 39 | # %% ../nbs/03_daisy.ipynb 40 | class StepT(VEnum): 41 | 'Step styles for LiStep' 42 | def _generate_next_value_(name, start, count, last_values): return f'step-{name}' 43 | primary = auto() 44 | secondary = auto() 45 | accent = auto() 46 | info = auto() 47 | success = auto() 48 | warning = auto() 49 | error = auto() 50 | neutral = auto() 51 | 52 | # %% ../nbs/03_daisy.ipynb 53 | def Steps(*li, # Each `Li` represent a step (generally use `LiStep`) 54 | cls='', # class for Steps (generally a `StepsT` option) 55 | **kwargs # Additional args for outer wrapper (`Ul` component) 56 | )->FT: # Ul(..., cls='steps') 57 | "Creates a steps container" 58 | return Ul(*li, cls=('steps',stringify(cls)), **kwargs) 59 | 60 | def LiStep(*c, # Description for Step that goes next to bubble (often text) 61 | cls='', # Additional step classes (generally a `StepT` component) 62 | data_content=None, # Content for inside bubble (defaults to number, often an emoji) 63 | **kwargs # Aditional arguments for the step (`Li` component) 64 | )->FT: # Li(..., cls='step') 65 | "Creates a step list item" 66 | return Li(*c, cls=('step', stringify(cls)), data_content=data_content, **kwargs) 67 | 68 | # %% ../nbs/03_daisy.ipynb 69 | class LoadingT(VEnum): 70 | def _generate_next_value_(name, start, count, last_values): return f'loading-{name}' 71 | spinner = auto() 72 | dots = auto() 73 | ring = auto() 74 | ball = auto() 75 | bars = auto() 76 | infinity = auto() 77 | 78 | xs = 'loading-xsmall' 79 | sm = 'loading-small' 80 | md = 'loading-medium' 81 | lg = 'loading-large' 82 | 83 | # %% ../nbs/03_daisy.ipynb 84 | def Loading(cls=(LoadingT.bars, LoadingT.lg), # Classes for indicator (generally `LoadingT` options) 85 | htmx_indicator=False, # Add htmx-indicator class 86 | **kwargs # additional args for outer conainter (`Span`) 87 | )->FT: # Span(cls=...) 88 | "Creates a loading animation component" 89 | classes = ['loading', stringify(cls)] 90 | if htmx_indicator: classes.append('htmx-indicator') 91 | return Span(cls=classes, **kwargs) 92 | 93 | # %% ../nbs/03_daisy.ipynb 94 | class ToastHT(VEnum): 95 | "Horizontal position for Toast" 96 | def _generate_next_value_(name, start, count, last_values): return f'toast-{name}' 97 | start = auto() 98 | center = auto() 99 | end = auto() 100 | 101 | class ToastVT(VEnum): 102 | "Vertical position for Toast" 103 | def _generate_next_value_(name, start, count, last_values): return f'toast-{name}' 104 | top = auto() 105 | middle = auto() 106 | bottom = auto() 107 | 108 | # %% ../nbs/03_daisy.ipynb 109 | def Toast(*c, # Content for toast (often test) 110 | cls='', # Classes for toast (often `ToastHT` and `ToastVT` options) 111 | alert_cls='', # classes for altert (often `AlertT` options) 112 | dur=5.0, # no. of seconds before the toast disappears 113 | **kwargs # Additional args for outer container (`Div` tag) 114 | )->FT: # Div(Alert(...), cls='toast') 115 | "Toasts are stacked announcements, positioned on the corner of page." 116 | a = Alert(*c, cls=alert_cls) 117 | _id = fh.unqid() 118 | js = '''(() => setTimeout(() => document.querySelector('[data-mui="%s"]').remove(),%s))()'''%(_id,dur*1000) 119 | return Div(a, NotStr(f""), data_mui=_id, cls=('toast', stringify(cls)), **kwargs) 120 | -------------------------------------------------------------------------------- /monsterui/foundations.py: -------------------------------------------------------------------------------- 1 | """Data Structures and Utilties""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_foundation.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['stringify', 'str2ukcls', 'VEnum'] 7 | 8 | # %% ../nbs/00_foundation.ipynb 9 | from enum import Enum, auto 10 | from fastcore.all import * 11 | 12 | # %% ../nbs/00_foundation.ipynb 13 | # need a better name, stringify might be too general for what it does 14 | def stringify(o # String, Tuple, or Enum options we want stringified 15 | ): # String that can be passed FT comp args (such as `cls=`) 16 | "Converts input types into strings that can be passed to FT components" 17 | if is_listy(o): return ' '.join(map(str,o)) if o else "" 18 | return o.__str__() 19 | 20 | # %% ../nbs/00_foundation.ipynb 21 | def str2ukcls(base, txt): return f"uk-{base}-{txt.replace('_', '-')}".strip('-') 22 | 23 | # %% ../nbs/00_foundation.ipynb 24 | class VEnum(Enum): 25 | def __str__(self): return self.value 26 | def __add__(self, other): return stringify((self,other)) 27 | def __radd__(self, other): return stringify((other,self)) 28 | -------------------------------------------------------------------------------- /nbs/.!32603!screenshot_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/nbs/.!32603!screenshot_cropped.png -------------------------------------------------------------------------------- /nbs/.last_checked: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/nbs/.last_checked -------------------------------------------------------------------------------- /nbs/00_foundation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Foundation\n", 8 | "\n", 9 | "> Data Structures and Utilties" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "\n" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "#| default_exp foundations" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "## Imports" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "#| hide\n", 42 | "#| export\n", 43 | "from enum import Enum, auto\n", 44 | "from fastcore.all import *" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#| hide\n", 54 | "from nbdev.showdoc import show_doc" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "#| hide\n", 64 | "show_doc = partial(show_doc, title_level=4)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "## Stringification" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "#| export\n", 81 | "# need a better name, stringify might be too general for what it does \n", 82 | "def stringify(o # String, Tuple, or Enum options we want stringified\n", 83 | " ): # String that can be passed FT comp args (such as `cls=`)\n", 84 | " \"Converts input types into strings that can be passed to FT components\" \n", 85 | " if is_listy(o): return ' '.join(map(str,o)) if o else \"\"\n", 86 | " return o.__str__()" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "assert stringify('abc') == 'abc'\n", 96 | "assert stringify(('abc','def')) == 'abc def'\n", 97 | "assert 'uk-input ' + stringify(()) == 'uk-input '\n", 98 | "assert 'uk-input ' + stringify(\"\") == 'uk-input '" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "## Enum Utilities" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "#| export\n", 115 | "def str2ukcls(base, txt): return f\"uk-{base}-{txt.replace('_', '-')}\".strip('-')" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "#| export\n", 125 | "class VEnum(Enum):\n", 126 | " def __str__(self): return self.value\n", 127 | " def __add__(self, other): return stringify((self,other))\n", 128 | " def __radd__(self, other): return stringify((other,self))" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "class TextT(VEnum):\n", 138 | " def _generate_next_value_(name, start, count, last_values): return str2ukcls('text', name)\n", 139 | " \n", 140 | " foo = '1234'\n", 141 | " red = auto()" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "assert TextT.foo + TextT.red == '1234 uk-text-red'\n", 151 | "assert TextT.red + TextT.foo == 'uk-text-red 1234'\n", 152 | "assert 'uk-text-red' + TextT.foo == 'uk-text-red 1234'\n", 153 | "assert TextT.red + '1234' == 'uk-text-red 1234'\n", 154 | "assert stringify((TextT.red,TextT.foo)) == 'uk-text-red 1234'" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "#| hide\n", 164 | "import nbdev; nbdev.nbdev_export()" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "python3", 178 | "language": "python", 179 | "name": "python3" 180 | } 181 | }, 182 | "nbformat": 4, 183 | "nbformat_minor": 4 184 | } 185 | -------------------------------------------------------------------------------- /nbs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | resources: 4 | - "*txt" 5 | 6 | format: 7 | html: 8 | theme: cosmo 9 | css: styles.css 10 | toc: true 11 | keep-md: true 12 | commonmark: default 13 | 14 | website: 15 | twitter-card: true 16 | open-graph: true 17 | repo-actions: [issue] 18 | navbar: 19 | background: primary 20 | search: true 21 | sidebar: 22 | style: floating 23 | 24 | metadata-files: [nbdev.yml, sidebar.yml] 25 | -------------------------------------------------------------------------------- /nbs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /nbs/nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "MonsterUI" 6 | site-url: "https://Isaac-Flath.github.io/MonsterUI" 7 | description: "The simplicity of FastHTML with the power of Tailwind" 8 | repo-branch: main 9 | repo-url: "https://github.com/Isaac-Flath/MonsterUI" 10 | -------------------------------------------------------------------------------- /nbs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/nbs/screenshot.png -------------------------------------------------------------------------------- /nbs/screenshot_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/db9286cd6d1fb12b99c6715e9aad617fc1698956/nbs/screenshot_cropped.png -------------------------------------------------------------------------------- /nbs/sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | sidebar: 3 | contents: 4 | - index.ipynb 5 | - 00_foundation.ipynb 6 | - 01_core.ipynb 7 | - 02_components.ipynb 8 | -------------------------------------------------------------------------------- /nbs/styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 0; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 0; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name="monsterui" 7 | requires-python=">=3.10" 8 | dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"] 9 | 10 | [tool.uv] 11 | cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }] 12 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | repo = MonsterUI 3 | lib_name = monsterui 4 | version = 1.0.22 5 | min_python = 3.10 6 | license = apache2 7 | black_formatting = False 8 | lib_path = monsterui 9 | nbs_path = nbs 10 | recursive = True 11 | tst_flags = notest 12 | put_version_in_init = True 13 | cell_number = False 14 | branch = main 15 | custom_sidebar = False 16 | doc_host = https://monsterui.answer.ai 17 | doc_baseurl = /MonsterUI 18 | title = MonsterUI 19 | git_url = https://github.com/AnswerDotAI/MonsterUI 20 | user = AnswerDotAI 21 | audience = Developers 22 | author = isaac flath 23 | author_email = isaac.flath@gmail.com 24 | copyright = 2024 onwards, isaac flath 25 | description = The simplicity of FastHTML with the power of Tailwind 26 | keywords = nbdev jupyter notebook python 27 | language = English 28 | status = 3 29 | requirements = python-fasthtml fastcore lxml mistletoe 30 | dev_requirements = pandas jinja2 llms-txt pysymbol_llm 31 | doc_path = _docs 32 | readme_nb = index.ipynb 33 | allowed_metadata_keys = 34 | allowed_cell_metadata_keys = 35 | jupyter_hooks = False 36 | clean_ids = True 37 | clear_all = False 38 | skip_procs = 39 | update_pyproject = True 40 | 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | from configparser import ConfigParser 3 | import setuptools, shlex 4 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 5 | 6 | # note: all settings are in settings.ini; edit there, not here 7 | config = ConfigParser(delimiters=['=']) 8 | config.read('settings.ini', encoding='utf-8') 9 | cfg = config['DEFAULT'] 10 | 11 | cfg_keys = 'version description keywords author author_email'.split() 12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 14 | setup_cfg = {o:cfg[o] for o in cfg_keys} 15 | 16 | licenses = { 17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 21 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 22 | } 23 | statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 24 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 25 | py_versions = '3.6 3.7 3.8 3.9 3.10'.split() 26 | 27 | requirements = shlex.split(cfg.get('requirements', '')) 28 | if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', '')) 29 | min_python = cfg['min_python'] 30 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 31 | dev_requirements = (cfg.get('dev_requirements') or '').split() 32 | 33 | setuptools.setup( 34 | name = cfg['lib_name'], 35 | license = lic[0], 36 | classifiers = [ 37 | 'Development Status :: ' + statuses[int(cfg['status'])], 38 | 'Intended Audience :: ' + cfg['audience'].title(), 39 | 'Natural Language :: ' + cfg['language'].title(), 40 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 41 | url = cfg['git_url'], 42 | packages = setuptools.find_packages(), 43 | include_package_data = True, 44 | install_requires = requirements, 45 | extras_require={ 'dev': dev_requirements }, 46 | dependency_links = cfg.get('dep_links','').split(), 47 | python_requires = '>=' + cfg['min_python'], 48 | long_description = open('README.md', encoding='utf-8').read(), 49 | long_description_content_type = 'text/markdown', 50 | zip_safe = False, 51 | entry_points = { 52 | 'console_scripts': cfg.get('console_scripts','').split(), 53 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 54 | }, 55 | **setup_cfg) 56 | 57 | 58 | -------------------------------------------------------------------------------- /testnavbar.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | from monsterui.all import * 3 | hdrs = Theme.blue.headers(mode='light') 4 | app, rt = fast_app(hdrs=hdrs, live=True) 5 | @rt 6 | def index(): 7 | return Title('Hello World'), Container( 8 | H2(A('Pretty URL', href='', cls=AT.primary)), 9 | P('I love monsterui!'), 10 | A('Go to Google', href='https://google.com', cls=AT.classic), 11 | Card('A Fancy Card :)') 12 | ) 13 | 14 | 15 | serve() --------------------------------------------------------------------------------