├── .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 |
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 |
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()
--------------------------------------------------------------------------------