Initial commit.

This commit is contained in:
Nikhil Rao 2022-11-18 04:47:00 -08:00
commit e8e8eaa010
127 changed files with 9777 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
**/*.pyc
**/.DS_Store
**/*.swp
**/.web
**/*.db
**/node_modules/**
bun.lockb
poetry.lock
dist/*
pynetree/

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
opensource@pynecone.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

18
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,18 @@
# Welcome to Pynecone contributing guide! 🥳
## Getting started
To navigate our codebase with confidence, see [Pynecone Docs](https://pynecone.io/docs/getting-started/introduction) :confetti_ball:.
### Issues
#### Create a new issue
If you spot a problem with anything in Pynecone feel free to create an issue. Even if you are not sure if its a problem with the framework or your own code, create an issue and we will do our best to answer or resolve it.
#### Solve an issue
Scan through our [existing issues](https://github.com/pynecone-io/pynecone/issues) to find one that interests you. You can narrow down the search using `labels` as filters. As a general rule, we dont assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix. Any large issue changing the compiler of Pynecone should brought to the Pynecone maintainers for approval
Thank you for supporting Pynecone!🎊

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

18
README.md Normal file
View File

@ -0,0 +1,18 @@
<div align="center">
<img src="docs/images/logo.png" alt="drawing" width = 450/>
**The easiest way to build and deploy web apps.**
[![PyPI version](https://badge.fury.io/py/pynecone-io.svg)](https://badge.fury.io/py/pynecone-io)
![versions](https://img.shields.io/pypi/pyversions/pynecone-io.svg)
[![License](https://img.shields.io/badge/License-Apache_2.0-yellowgreen.svg)](https://opensource.org/licenses/Apache-2.0)
<div align="left">
## Coming Soon
Pynecone is a full-stack python framework that makes it easy to build and deploy web apps in minutes.

BIN
docs/images/Counter.gif Normal file

Binary file not shown.

After

(image error) Size: 651 KiB

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

(image error) Size: 98 KiB

View File

View File

@ -0,0 +1,83 @@
"""Welcome to Pynecone! This file outlines the steps to create a basic app."""
# Import pynecone.
import pcconfig
import pynecone as pc
docs_url = "https://pynecone.io/docs/getting-started/introduction"
title = "Welcome to Pynecone!"
filename = f"{pcconfig.APP_NAME}/{pcconfig.APP_NAME}.py"
class State(pc.State):
"""The app state."""
# The colors to cycle through.
colors = ["black", "red", "orange", "yellow", "green", "blue", "purple"]
# The index of the current color.
index = 0
def next_color(self):
"""Cycle to the next color."""
self.index = (self.index + 1) % len(self.colors)
@pc.var
def color(self):
return self.colors[self.index]
# Define views.
def welcome_text():
return pc.heading(
title,
font_size="2.5em",
on_click=State.next_color,
color=State.color,
_hover={"cursor": "pointer"},
)
def instructions():
return pc.box(
"Get started by editing ",
pc.code(
filename,
font_size="0.8em",
),
)
def doclink():
return pc.link(
"Check out our docs!",
href=docs_url,
border="0.1em solid",
padding="0.5em",
_hover={
"border_color": State.color,
"color": State.color,
},
)
def index():
return pc.container(
pc.vstack(
welcome_text(),
instructions(),
doclink(),
spacing="2em",
),
padding_y="5em",
font_size="2em",
text_align="center",
height="100vh",
)
# Add state and page to the app.
app = pc.App(state=State)
app.add_page(index, title=title)
app.compile()

Binary file not shown.

After

Width: 48px  |  Height: 48px  |  Size: 15 KiB

39
pynecone/.templates/web/.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/_static
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# DS_Store
.DS_Store

View File

@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true
};

View File

@ -0,0 +1,32 @@
{
"name": "pynecone",
"scripts": {
"dev": "next dev",
"export": "next build && next export -o _static",
"prod": "next start"
},
"dependencies": {
"@chakra-ui/icons": "^2.0.10",
"@chakra-ui/react": "1.8.8",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"axios": "^0.27.2",
"focus-visible": "^5.2.0",
"framer-motion": "^6.3.3",
"gridjs": "^4.0.0",
"gridjs-react": "^4.0.0",
"next": "^12.1.0",
"plotly.js": "2.6.4",
"prettier": "^2.7.1",
"react": "^17.0.2",
"react-confetti": "^6.1.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^17.0.2",
"react-markdown": "^8.0.3",
"react-plotly.js": "^2.6.0",
"react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^6.0.2",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1"
}
}

View File

@ -0,0 +1,19 @@
import Router from "next/router";
import { useEffect, useState } from "react";
export default function Custom404() {
const [isNotFound, setIsNotFound] = useState(false);
useEffect(() => {
const pathNameArray = window.location.pathname.split("/");
if (pathNameArray.length == 2 && pathNameArray[1] == "404") {
setIsNotFound(true);
} else {
Router.replace(window.location.pathname);
}
}, []);
if (isNotFound) return <h1>404 - Page Not Found</h1>;
return null;
}

View File

@ -0,0 +1,12 @@
import { ChakraProvider, extendTheme } from "@chakra-ui/react";
import theme from "/utils/theme";
function MyApp({ Component, pageProps }) {
return (
<ChakraProvider theme={extendTheme(theme)}>
<Component {...pageProps} />
</ChakraProvider>
);
}
export default MyApp;

View File

@ -0,0 +1,93 @@
import axios from "axios";
let token;
const TOKEN_KEY = "token";
const generateUUID = () => {
let d = new Date().getTime(),
d2 = (performance && performance.now && performance.now() * 1000) || 0;
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
let r = Math.random() * 16;
if (d > 0) {
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c == "x" ? r : (r & 0x7) | 0x8).toString(16);
});
};
export const getToken = () => {
if (token) {
return token;
}
if (window) {
if (!window.sessionStorage.getItem(TOKEN_KEY)) {
window.sessionStorage.setItem(TOKEN_KEY, generateUUID());
}
token = window.sessionStorage.getItem(TOKEN_KEY);
}
return token;
};
export const applyDelta = (state, delta) => {
for (const substate in delta) {
let s = state;
const path = substate.split(".").slice(1);
while (path.length > 0) {
s = s[path.shift()];
}
for (const key in delta[substate]) {
s[key] = delta[substate][key];
}
}
};
export const applyEvent = async (state, event, endpoint, router) => {
// Handle special events
if (event.name == "_redirect") {
router.push(event.payload.path);
return [];
}
if (event.name == "_console") {
console.log(event.payload.message);
return [];
}
if (event.name == "_alert") {
alert(event.payload.message);
return [];
}
event.token = getToken();
const update = (await axios.post(endpoint, event)).data;
applyDelta(state, update.delta);
return update.events;
};
export const updateState = async (
state,
result,
setResult,
endpoint,
router
) => {
if (result.processing || state.events.length == 0) {
return;
}
setResult({ ...result, processing: true });
const events = await applyEvent(
state,
state.events.shift(),
endpoint,
router
);
setResult({
state: state,
events: events,
processing: true,
});
};

12
pynecone/__init__.py Normal file
View File

@ -0,0 +1,12 @@
"""Import all classes and functions the end user will need to make an app.
Anything imported here will be available in the default Pynecone import as `pc.*`.
"""
from .app import App
from .base import Base
from .components import *
from .event import console_log, redirect, window_alert
from .model import Model, session
from .state import ComputedVar as var
from .state import State

344
pynecone/app.py Normal file
View File

@ -0,0 +1,344 @@
"""The main Pynecone app."""
import re
from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Type, Union
import fastapi
from fastapi.middleware import cors
from pynecone import constants, utils
from pynecone.base import Base
from pynecone.compiler import compiler
from pynecone.compiler import utils as compiler_utils
from pynecone.components.component import Component, ComponentStyle
from pynecone.event import Event
from pynecone.middleware import HydrateMiddleware, LoggingMiddleware, Middleware
from pynecone.model import Model
from pynecone.state import DefaultState, Delta, State, StateManager, StateUpdate
# Define custom types.
ComponentCallable = Callable[[], Component]
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
class App(Base):
"""A Pynecone application."""
# A map from a page route to the component to render.
pages: Dict[str, Component] = {}
# A list of URLs to stylesheets to include in the app.
stylesheets: List[str] = []
# The backend API object.
api: fastapi.FastAPI = None # type: ignore
# The state class to use for the app.
state: Type[State] = DefaultState
# Class to manage many client states.
state_manager: StateManager = StateManager()
# The styling to apply to each component.
style: ComponentStyle = {}
# Middleware to add to the app.
middleware: List[Middleware] = []
def __init__(self, *args, **kwargs):
"""Initialize the app.
Args:
*args: Args to initialize the app with.
**kwargs: Kwargs to initialize the app with.
"""
super().__init__(*args, **kwargs)
# Add middleware.
self.middleware.append(HydrateMiddleware())
self.middleware.append(LoggingMiddleware())
# Set up the state manager.
self.state_manager.set(state=self.state)
# Set up the API.
self.api = fastapi.FastAPI()
self.add_cors()
self.add_default_endpoints()
def __repr__(self) -> str:
"""Get the string representation of the app.
Returns:
The string representation of the app.
"""
return f"<App state={self.state.__name__}>"
def add_default_endpoints(self):
"""Add the default endpoints."""
# To test the server.
self.get(str(constants.Endpoint.PING))(_ping)
# To make state changes.
self.post(str(constants.Endpoint.EVENT))(_event(app=self))
def add_cors(self):
"""Add CORS middleware to the app."""
self.api.add_middleware(
cors.CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
def get(self, path: str, *args, **kwargs) -> Callable:
"""Register a get request.
Args:
path: The endpoint path to link to the request.
*args: Args to pass to the request.
**kwargs: Kwargs to pass to the request.
Returns:
A decorator to handle the request.
"""
return self.api.get(path, *args, **kwargs)
def post(self, path: str, *args, **kwargs) -> Callable:
"""Register a post request.
Args:
path: The endpoint path to link to the request.
*args: Args to pass to the request.
**kwargs: Kwargs to pass to the request.
Returns:
A decorator to handle the request.
"""
return self.api.post(path, *args, **kwargs)
def preprocess(self, state: State, event: Event) -> Optional[Delta]:
"""Preprocess the event.
This is where middleware can modify the event before it is processed.
Each middleware is called in the order it was added to the app.
If a middleware returns a delta, the event is not processed and the
delta is returned.
Args:
state: The state to preprocess.
event: The event to preprocess.
Returns:
An optional state to return.
"""
for middleware in self.middleware:
out = middleware.preprocess(app=self, state=state, event=event)
if out is not None:
return out
def postprocess(self, state: State, event: Event, delta: Delta) -> Optional[Delta]:
"""Postprocess the event.
This is where middleware can modify the delta after it is processed.
Each middleware is called in the order it was added to the app.
If a middleware returns a delta, the delta is not processed and the
delta is returned.
Args:
state: The state to postprocess.
event: The event to postprocess.
delta: The delta to postprocess.
Returns:
An optional state to return.
"""
for middleware in self.middleware:
out = middleware.postprocess(
app=self, state=state, event=event, delta=delta
)
if out is not None:
return out
def add_page(
self,
component: Union[Component, ComponentCallable],
path: Optional[str] = None,
title: str = constants.DEFAULT_TITLE,
):
"""Add a page to the app.
If the component is a callable, by default the route is the name of the
function. Otherwise, a route must be provided.
Args:
component: The component to display at the page.
path: The path to display the component at.
title: The title of the page.
"""
# If the path is not set, get it from the callable.
if path is None:
assert isinstance(
component, Callable
), "Path must be set if component is not a callable."
path = component.__name__
from pynecone.var import BaseVar
parts = path.split("/")
check = re.compile(r"^\[(.+)\]$")
args = []
for part in parts:
match = check.match(part)
if match:
v = BaseVar(
name=match.groups()[0],
type_=str,
state="router.query",
)
args.append(v)
# Generate the component if it is a callable.
component = component if isinstance(component, Component) else component(*args)
# Add the title to the component.
compiler_utils.add_title(component, title)
# Format the route.
route = utils.format_route(path)
# Add the page.
self.pages[route] = component
def compile(self, ignore_env: bool = False):
"""Compile the app and output it to the pages folder.
If the pcconfig environment is set to production, the app will
not be compiled.
Args:
ignore_env: Whether to ignore the pcconfig environment.
"""
# Get the env mode.
config = utils.get_config()
if not ignore_env and config.ENV != constants.Env.DEV.value:
print("Skipping compilation in non-dev mode.")
return
# Create the database models.
Model.create_all()
# Create the root document with base styles and fonts.
self.pages[constants.DOCUMENT_ROOT] = compiler_utils.create_document_root(
self.stylesheets
)
self.pages[constants.THEME] = compiler_utils.create_theme(self.style) # type: ignore
# Compile the pages.
for path, component in self.pages.items():
path, code = self.compile_page(path, component)
def compile_page(
self, path: str, component: Component, write: bool = True
) -> Tuple[str, str]:
"""Compile a single page.
Args:
path: The path to compile the page to.
component: The component to compile.
write: Whether to write the page to the pages folder.
Returns:
The path and code of the compiled page.
"""
# Get the path for the output file.
output_path = utils.get_page_path(path)
# Compile the document root.
if path == constants.DOCUMENT_ROOT:
code = compiler.compile_document_root(component)
# Compile the theme.
elif path == constants.THEME:
output_path = utils.get_theme_path()
code = compiler.compile_theme(component) # type: ignore
# Compile all other pages.
else:
# Add the style to the component.
component.add_style(self.style)
code = compiler.compile_component(
component=component,
state=self.state,
)
# Write the page to the pages folder.
if write:
utils.write_page(output_path, code)
return output_path, code
def get_state(self, token: str) -> State:
"""Get the state for a token.
Args:
token: The token to get the state for.
Returns:
The state for the token.
"""
return self.state_manager.get_state(token)
def set_state(self, token: str, state: State):
"""Set the state for a token.
Args:
token: The token to set the state for.
state: The state to set.
"""
self.state_manager.set_state(token, state)
async def _ping() -> str:
"""Test API endpoint.
Returns:
The response.
"""
return "pong"
def _event(app: App) -> Reducer:
"""Create an event reducer to modify the state.
Args:
app: The app to modify the state of.
Returns:
A handler that takes in an event and modifies the state.
"""
async def process(event: Event) -> StateUpdate:
# Get the state for the session.
state = app.get_state(event.token)
# Preprocess the event.
pre = app.preprocess(state, event)
if pre is not None:
return StateUpdate(delta=pre)
# Apply the event to the state.
update = await state.process(event)
app.set_state(event.token, state)
# Postprocess the event.
post = app.postprocess(state, event, update.delta)
if post is not None:
return StateUpdate(delta=post)
# Return the delta.
return update
return process

75
pynecone/base.py Normal file
View File

@ -0,0 +1,75 @@
"""Define the base Pynecone class."""
from __future__ import annotations
from typing import Any, Dict, TypeVar
import pydantic
# Typevar to represent any class subclassing Base.
PcType = TypeVar("PcType")
class Base(pydantic.BaseModel):
"""The base class subclassed by all Pynecone classes.
This class wraps Pydantic and provides common methods such as
serialization and setting fields.
Any data structure that needs to be transferred between the
frontend and backend should subclass this class.
"""
class Config:
"""Pydantic config."""
arbitrary_types_allowed = True
def json(self) -> str:
"""Convert the object to a json string.
Returns:
The object as a json string.
"""
return self.__config__.json_dumps(self.dict())
def set(self: PcType, **kwargs) -> PcType:
"""Set multiple fields and return the object.
Args:
**kwargs: The fields and values to set.
Returns:
The object with the fields set.
"""
for key, value in kwargs.items():
setattr(self, key, value)
return self
@classmethod
def get_fields(cls) -> Dict[str, Any]:
"""Get the fields of the object.
Returns:
The fields of the object.
"""
return cls.__fields__
def get_value(self, key: str) -> Any:
"""Get the value of a field.
Args:
key: The key of the field.
Returns:
The value of the field.
"""
return self._get_value(
key,
to_dict=True,
by_alias=False,
include=None,
exclude=None,
exclude_unset=False,
exclude_defaults=False,
exclude_none=False,
)

View File

@ -0,0 +1 @@
"""The Pynecone compiler."""

View File

@ -0,0 +1,68 @@
"""Compiler for the pynecone apps."""
import json
from typing import Type
from pynecone import constants
from pynecone.compiler import templates, utils
from pynecone.components.component import Component, ImportDict
from pynecone.state import State
# Imports to be included in every Pynecone app.
DEFAULT_IMPORTS: ImportDict = {
"react": {"useEffect", "useState"},
"next/router": {"useRouter"},
f"/{constants.STATE_PATH}": {"updateState"},
}
def compile_document_root(root: Component) -> str:
"""Compile the document root.
Args:
root: The document root to compile.
Returns:
The compiled document root.
"""
return templates.DOCUMENT_ROOT(
imports=utils.compile_imports(root.get_imports()),
document=root.render(),
)
def compile_theme(theme: dict) -> str:
"""Compile the theme.
Args:
theme: The theme to compile.
Returns:
The compiled theme.
"""
return templates.THEME(theme=json.dumps(theme))
def compile_component(component: Component, state: Type[State]) -> str:
"""Compile the component given the app state.
Args:
component: The component to compile.
state: The app state.
Returns:
The compiled component.
"""
# Merge the default imports with the app-specific imports.
imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
# Compile the code to render the component.
return templates.COMPONENT(
imports=utils.compile_imports(imports),
custom_code=component.get_custom_code(),
constants=utils.compile_constants(),
state=utils.compile_state(state),
events=utils.compile_events(state),
effects=utils.compile_effects(state),
render=component.render(),
)

View File

@ -0,0 +1,181 @@
"""Templates to use in the pynecone compiler."""
from typing import Callable, Optional, Set
from pynecone import constants, utils
from pynecone.utils import join
# Template for the Pynecone config file.
PCCONFIG = f"""# The Pynecone configuration file.
APP_NAME = "{{app_name}}"
API_HOST = "http://localhost:8000"
BUN_PATH = "$HOME/.bun/bin/bun"
ENV = "{constants.Env.DEV.value}"
DB_URI = "sqlite:///{constants.DB_NAME}"
"""
# Javascript formatting.
CONST = "const {name} = {value}".format
PROP = "{object}.{property}".format
IMPORT_LIB = 'import "{lib}"'.format
IMPORT_FIELDS = 'import {default}{others} from "{lib}"'.format
def format_import(lib: str, default: str = "", rest: Optional[Set[str]] = None) -> str:
"""Format an import statement.
Args:
lib: The library to import from.
default: The default field to import.
rest: The set of fields to import from the library.
Returns:
The compiled import statement.
"""
# Handle the case of direct imports with no libraries.
if lib == "":
assert default == "", "No default field allowed for empty library."
assert rest is not None and len(rest) > 0, "No fields to import."
return join([IMPORT_LIB(lib=lib) for lib in sorted(rest)])
# Handle importing from a library.
rest = rest or set()
if len(default) == 0 and len(rest) == 0:
# Handle the case of importing a library with no fields.
return IMPORT_LIB(lib=lib)
else:
# Handle importing specific fields from a library.
others = f'{{{", ".join(sorted(rest))}}}' if len(rest) > 0 else ""
if len(default) > 0 and len(rest) > 0:
default += ", "
return IMPORT_FIELDS(default=default, others=others, lib=lib)
# Code to render a NextJS Document root.
DOCUMENT_ROOT = join(
[
"{imports}",
"",
"export default function Document() {{",
"",
"return (",
"{document}",
")",
"}}",
]
).format
# Template for the theme file.
THEME = "export default {theme}".format
# Code to render a single NextJS component.
COMPONENT = join(
[
"{imports}",
"{custom_code}",
"",
"{constants}",
"",
"export default function Component() {{",
"",
"{state}",
"",
"{events}",
"",
"{effects}",
"",
"return (",
"{render}",
")",
"}}",
]
).format
# React state declarations.
USE_STATE = CONST(
name="[{state}, {set_state}]", value="useState({initial_state})"
).format
def format_state_setter(state: str) -> str:
"""Format a state setter.
Args:
state: The name of the state variable.
Returns:
The compiled state setter.
"""
return f"set{state[0].upper() + state[1:]}"
def format_state(
state: str,
initial_state: str,
) -> str:
"""Format a state declaration.
Args:
state: The name of the state variable.
initial_state: The initial state of the state variable.
Returns:
The compiled state declaration.
"""
set_state = format_state_setter(state)
return USE_STATE(state=state, set_state=set_state, initial_state=initial_state)
# Events.
EVENT_ENDPOINT = constants.Endpoint.EVENT.name
EVENT_FN = join(
[
"const E = (name, payload) => {{ return {{name, payload}} }}",
"const Event = events => {set_state}({{",
" ...{state},",
" events: [...{state}.events, ...events],",
"}})",
]
).format
def format_event_declaration(fn: Callable) -> str:
"""Format an event declaration.
Args:
fn: The function to declare.
Returns:
The compiled event declaration.
"""
name = utils.format_event_fn(fn=fn)
event = utils.to_snake_case(fn.__qualname__)
return f"const {name} = Event('{event}')"
# Effects.
USE_EFFECT = join(
[
"useEffect(() => {{",
" const update = async () => {{",
" if (result.state != null) {{",
" setState({{",
" ...result.state,",
" events: [...state.events, ...result.events],",
" }})",
" setResult({{",
" ...result,",
" state: null,",
" processing: false,",
" }})",
" }}",
f" await updateState({{state}}, {{result}}, {{set_result}}, {EVENT_ENDPOINT}, {constants.ROUTER})",
" }}",
" update()",
"}})",
]
).format
# Routing
ROUTER = f"const {constants.ROUTER} = useRouter()"

219
pynecone/compiler/utils.py Normal file
View File

@ -0,0 +1,219 @@
"""Common utility functions used in the compiler."""
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Dict, Set, Type
from pynecone import constants, utils
from pynecone.compiler import templates
from pynecone.components.base import (
Body,
DocumentHead,
Head,
Html,
Link,
Main,
Script,
Title,
)
from pynecone.components.component import ImportDict
from pynecone.state import State
from pynecone.style import Style
if TYPE_CHECKING:
from pynecone.components.component import Component
# To re-export this function.
merge_imports = utils.merge_imports
def compile_import_statement(lib: str, fields: Set[str]) -> str:
"""Compile an import statement.
Args:
lib: The library to import from.
fields: The set of fields to import from the library.
Returns:
The compiled import statement.
"""
# Check for default imports.
defaults = {
field
for field in fields
if field.lower() == lib.lower().replace("-", "").replace("/", "")
}
assert len(defaults) < 2
# Get the default import, and the specific imports.
default = next(iter(defaults), "")
rest = fields - defaults
return templates.format_import(lib=lib, default=default, rest=rest)
def compile_imports(imports: ImportDict) -> str:
"""Compile an import dict.
Args:
imports: The import dict to compile.
Returns:
The compiled import dict.
"""
return templates.join(
[compile_import_statement(lib, fields) for lib, fields in imports.items()]
)
def compile_constant_declaration(name: str, value: str) -> str:
"""Compile a constant declaration.
Args:
name: The name of the constant.
value: The value of the constant.
Returns:
The compiled constant declaration.
"""
return templates.CONST(name=name, value=json.dumps(value))
def compile_constants() -> str:
"""Compile all the necessary constants.
Returns:
A string of all the compiled constants.
"""
endpoint = constants.Endpoint.EVENT
return templates.join(
[compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())]
)
import plotly.graph_objects as go
def compile_state(state: Type[State]) -> str:
"""Compile the state of the app.
Args:
state: The app state object.
Returns:
A string of the compiled state.
"""
initial_state = state().dict()
initial_state.update(
{
"events": [{"name": utils.get_hydrate_event(state)}],
}
)
initial_state = utils.format_state(initial_state)
synced_state = templates.format_state(
state=state.get_name(), initial_state=json.dumps(initial_state)
)
initial_result = {
"state": None,
"events": [],
"processing": False,
}
result = templates.format_state(
state="result",
initial_state=json.dumps(initial_result),
)
router = templates.ROUTER
return templates.join([synced_state, result, router])
def compile_events(state: Type[State]) -> str:
"""Compile all the events for a given component.
Args:
state: The state class for the component.
Returns:
A string of the compiled events for the component.
"""
state_name = state.get_name()
state_setter = templates.format_state_setter(state_name)
return templates.EVENT_FN(state=state_name, set_state=state_setter)
def compile_effects(state: Type[State]) -> str:
"""Compile all the effects for a given component.
Args:
state: The state class for the component.
Returns:
A string of the compiled effects for the component.
"""
state_name = state.get_name()
result_name = "result"
set_result = templates.format_state_setter(result_name)
return templates.USE_EFFECT(
state=state_name, result=result_name, set_result=set_result
)
def compile_render(component: Component) -> str:
"""Compile the component's render method.
Args:
component: The component to compile the render method for.
Returns:
A string of the compiled render method.
"""
return component.render()
def create_document_root(stylesheets) -> Component:
"""Create the document root.
Args:
stylesheets: The stylesheets to include in the document root.
Returns:
The document root.
"""
sheets = [Link.create(rel="stylesheet", href=href) for href in stylesheets]
return Html.create(
DocumentHead.create(*sheets),
Body.create(
Main.create(),
Script.create(),
),
)
def create_theme(style: Style) -> Dict:
"""Create the base style for the app.
Args:
style: The style dict for the app.
Returns:
The base style for the app.
"""
return {
"styles": {
"global": Style({k: v for k, v in style.items() if not isinstance(k, type)})
},
}
def add_title(page: Component, title: str) -> Component:
"""Add a title to a page.
Args:
page: The component for the page.
title: The title to add.
Returns:
The component with the title added.
"""
page.children.append(Head.create(Title.create(title)))
return page

View File

@ -0,0 +1,91 @@
"""Import all the components."""
from pynecone import utils
from pynecone.event import EventSpec
from pynecone.var import Var
from .component import Component
from .datadisplay import *
from .disclosure import *
from .feedback import *
from .forms import *
from .graphing import *
from .layout import *
from .media import *
from .navigation import *
from .overlay import *
from .typography import *
# Add the convenience methods for all the components.
locals().update(
{
utils.to_snake_case(name): value.create
for name, value in locals().items()
if isinstance(value, type) and issubclass(value, Component)
}
)
# Add responsive styles shortcuts.
def mobile_only(*children, **props):
"""Create a component that is only visible on mobile.
Args:
*children: The children to pass to the component.
**props: The props to pass to the component.
Returns:
The component.
"""
return Box.create(*children, **props, display=["block", "none", "none", "none"])
def tablet_only(*children, **props):
"""Create a component that is only visible on tablet.
Args:
*children: The children to pass to the component.
**props: The props to pass to the component.
Returns:
The component.
"""
return Box.create(*children, **props, display=["none", "block", "block", "none"])
def desktop_only(*children, **props):
"""Create a component that is only visible on desktop.
Args:
*children: The children to pass to the component.
**props: The props to pass to the component.
Returns:
The component.
"""
return Box.create(*children, **props, display=["none", "none", "none", "block"])
def tablet_and_desktop(*children, **props):
"""Create a component that is only visible on tablet and desktop.
Args:
*children: The children to pass to the component.
**props: The props to pass to the component.
Returns:
The component.
"""
return Box.create(*children, **props, display=["none", "block", "block", "block"])
def mobile_and_tablet(*children, **props):
"""Create a component that is only visible on mobile and tablet.
Args:
*children: The children to pass to the component.
**props: The props to pass to the component.
Returns:
The component.
"""
return Box.create(*children, **props, display=["block", "block", "block", "none"])

View File

@ -0,0 +1,7 @@
"""Base components."""
from .body import Body
from .document import DocumentHead, Html, Main, Script
from .head import Head
from .link import Link
from .title import Title

View File

@ -0,0 +1,30 @@
"""A bare component."""
from __future__ import annotations
from typing import Any
from pynecone.components.component import Component
from pynecone.components.tags import Tag
from pynecone.components.tags.tagless import Tagless
from pynecone.var import Var
class Bare(Component):
"""A component with no tag."""
contents: Var[str]
@classmethod
def create(cls, contents: Any) -> Component:
"""Create a Bare component, with no tag.
Args:
contents: The contents of the component.
Returns:
The component.
"""
return cls(contents=str(contents)) # type: ignore
def _render(self) -> Tag:
return Tagless(contents=str(self.contents))

View File

@ -0,0 +1,12 @@
"""Display the page body."""
from pynecone.components.component import Component
from pynecone.components.tags import Tag
from pynecone.var import Var
class Body(Component):
"""A body component."""
def _render(self) -> Tag:
return Tag(name="body")

View File

@ -0,0 +1,33 @@
"""Document components."""
from pynecone.components.component import Component
class NextDocumentLib(Component):
"""Root document components."""
library = "next/document"
class Html(NextDocumentLib):
"""The document html."""
tag = "Html"
class DocumentHead(NextDocumentLib):
"""The document head."""
tag = "Head"
class Main(NextDocumentLib):
"""The document main section."""
tag = "Main"
class Script(NextDocumentLib):
"""The document main scripts."""
tag = "NextScript"

View File

@ -0,0 +1,15 @@
"""The head component."""
from pynecone.components.component import Component
class NextHeadLib(Component):
"""Header components."""
library = "next/head"
class Head(NextHeadLib):
"""Head Component."""
tag = "NextHead"

View File

@ -0,0 +1,21 @@
"""Display the title of the current page."""
from pynecone.components.component import Component
from pynecone.components.tags import Tag
from pynecone.var import Var
class Link(Component):
"""A component that displays the title of the current page."""
# The href.
href: Var[str]
# The type of link.
rel: Var[str]
def _render(self) -> Tag:
return Tag(name="link").add_attrs(
href=self.href,
rel=self.rel,
)

View File

@ -0,0 +1,25 @@
"""Display the title of the current page."""
from pynecone.components.base.bare import Bare
from pynecone.components.component import Component
from pynecone.components.tags import Tag
class Title(Component):
"""A component that displays the title of the current page."""
def _render(self) -> Tag:
return Tag(name="title")
def render(self) -> str:
"""Render the title component.
Returns:
The rendered title component.
"""
tag = self._render()
# Make sure the title is a single string.
assert len(self.children) == 1 and isinstance(
self.children[0], Bare
), "Title must be a single string."
return str(tag.set(contents=str(self.children[0].contents)))

View File

@ -0,0 +1,345 @@
"""Base component definitions."""
from __future__ import annotations
from abc import ABC
from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
from pynecone import utils
from pynecone.base import Base
from pynecone.components.tags import Tag
from pynecone.event import (
EVENT_ARG,
EVENT_TRIGGERS,
EventChain,
EventHandler,
EventSpec,
)
from pynecone.style import Style
from pynecone.var import Var
ImportDict = Dict[str, Set[str]]
class Component(Base, ABC):
"""The base class for all Pynecone components."""
# The children nested within the component.
children: List[Component] = []
# The style of the component.
style: Style = Style()
# A mapping of event chains to event triggers.
event_triggers: Dict[str, EventChain] = {}
# The library that the component is based on.
library: Optional[str] = None
# The tag to use when rendering the component.
tag: Optional[str] = None
# A unique key for the component.
key: Any = None
@classmethod
def __init_subclass__(cls, **kwargs):
"""Set default properties.
Args:
**kwargs: The kwargs to pass to the superclass.
"""
super().__init_subclass__(**kwargs)
# Get all the props for the component.
props = cls.get_props()
# Convert fields to props, setting default values.
for field in cls.get_fields().values():
# If the field is not a component prop, skip it.
if field.name not in props:
continue
# Set default values for any props.
if utils._issubclass(field.type_, Var):
field.required = False
field.default = Var.create(field.default)
def __init__(self, *args, **kwargs):
"""Initialize the component.
Args:
*args: Args to initialize the component.
**kwargs: Kwargs to initialize the component.
"""
# Get the component fields, triggers, and props.
fields = self.get_fields()
triggers = self.get_triggers()
props = self.get_props()
# Add any events triggers.
if "event_triggers" not in kwargs:
kwargs["event_triggers"] = {}
kwargs["event_triggers"] = kwargs["event_triggers"].copy()
# Iterate through the kwargs and set the props.
for key, value in kwargs.items():
if key in triggers:
# Event triggers are bound to event chains.
field_type = EventChain
else:
# If the key is not in the fields, skip it.
if key not in props:
continue
# Set the field type.
field_type = fields[key].type_
# Check whether the key is a component prop.
if utils._issubclass(field_type, Var):
# Convert any constants into vars and make sure the types match.
kwargs[key] = Var.create(value)
passed_type = kwargs[key].type_
expected_type = fields[key].outer_type_.__args__[0]
assert utils._issubclass(
passed_type, expected_type
), f"Invalid var passed for {key}, expected {expected_type}, got {passed_type}."
# Check if the key is an event trigger.
if key in triggers:
kwargs["event_triggers"][key] = self._create_event_chain(key, value)
# Remove any keys that were added as events.
for key in kwargs["event_triggers"]:
del kwargs[key]
# Add style props to the component.
style = kwargs["style"] if "style" in kwargs else {}
kwargs["style"] = Style(
{
**style,
**{attr: value for attr, value in kwargs.items() if attr not in fields},
}
)
# Construct the component.
super().__init__(*args, **kwargs)
def _create_event_chain(
self,
event_trigger: str,
value: Union[EventHandler, List[EventHandler], Callable],
) -> EventChain:
"""Create an event chain from a variety of input types.
Args:
event_trigger: The event trigger to bind the chain to.
value: The value to create the event chain from.
Returns:
The event chain.
"""
arg = self.get_controlled_value()
# If the input is a single event handler, wrap it in a list.
if isinstance(value, EventHandler):
value = [value]
# If the input is a list of event handlers, create an event chain.
if isinstance(value, List):
events = [utils.call_event_handler(v, arg) for v in value]
# If the input is a callable, create an event chain.
elif isinstance(value, Callable):
events = utils.call_event_fn(value, arg)
# Otherwise, raise an error.
else:
raise ValueError(f"Invalid event chain: {value}")
# Add args to the event specs if necessary.
if event_trigger in self.get_controlled_triggers():
events = [
EventSpec(
handler=e.handler,
local_args=(EVENT_ARG.name,),
args=utils.get_handler_args(e, arg),
)
for e in events
]
# Return the event chain.
return EventChain(events=events)
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return EVENT_TRIGGERS | cls.get_controlled_triggers()
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return set()
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG
def __repr__(self) -> str:
"""Represent the component in React.
Returns:
The code to render the component.
"""
return self.render()
def __str__(self) -> str:
"""Represent the component in React.
Returns:
The code to render the component.
"""
return self.render()
def _render(self) -> Tag:
"""Define how to render the component in React.
Returns:
The tag to render.
"""
tag = Tag(name=self.tag).add_attrs(
**{attr: getattr(self, attr) for attr in self.get_props()}
)
# Special case for props named `type_`.
if hasattr(self, "type_"):
tag.add_attrs(type=getattr(self, "type_"))
return tag
@classmethod
def get_props(cls) -> Set[str]:
"""Get the unique fields for the component.
Returns:
The unique fields.
"""
return set(cls.get_fields()) - set(Component.get_fields())
@classmethod
def create(cls, *children, **props) -> Component:
"""Create the component.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The component.
"""
# Import here to avoid circular imports.
from pynecone.components.base.bare import Bare
children = [
Bare.create(contents=Var.create(child, is_string=True))
if not isinstance(child, Component)
else child
for child in children
]
return cls(children=children, **props)
def _add_style(self, style):
self.style.update(style)
def add_style(self, style: ComponentStyle) -> Component:
"""Add additional style to the component and its children.
Args:
style: A dict from component to styling.
Returns:
The component with the additional style.
"""
if type(self) in style:
# Extract the style for this component.
component_style = Style(style[type(self)])
# Only add stylee props that are not overriden.
component_style = {
k: v for k, v in component_style.items() if k not in self.style
}
# Add the style to the component.
self._add_style(component_style)
# Recursively add style to the children.
for child in self.children:
child.add_style(style)
return self
def render(self) -> str:
"""Render the component.
Returns:
The code to render the component.
"""
tag = self._render()
return str(
tag.add_attrs(**self.event_triggers, key=self.key, sx=self.style).set(
contents=utils.join(
[str(tag.contents)] + [child.render() for child in self.children]
),
)
)
def _get_custom_code(self) -> str:
"""Get custom code for the component.
Returns:
The custom code.
"""
return ""
def get_custom_code(self) -> str:
"""Get custom code for the component and its children.
Returns:
The custom code.
"""
code = self._get_custom_code()
for child in self.children:
child_code = child.get_custom_code()
if child_code != "" and child_code not in code:
code += child_code
return code
def _get_imports(self) -> ImportDict:
if self.library is not None and self.tag is not None:
return {self.library: {self.tag}}
return {}
def get_imports(self) -> ImportDict:
"""Get all the libraries and fields that are used by the component.
Returns:
The import dict with the required imports.
"""
return utils.merge_imports(
self._get_imports(), *[child.get_imports() for child in self.children]
)
# Map from component to styling.
ComponentStyle = Dict[Union[str, Type[Component]], Any]

View File

@ -0,0 +1,9 @@
"""Data display components."""
from .badge import Badge
from .code import Code, CodeBlock
from .datatable import DataTable
from .divider import Divider
from .list import List, ListItem, OrderedList, UnorderedList
from .stat import Stat, StatArrow, StatGroup, StatHelpText, StatLabel, StatNumber
from .table import Table, TableCaption, TableContainer, Tbody, Td, Tfoot, Th, Thead, Tr

View File

@ -0,0 +1,16 @@
"""Badge component."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Badge(ChakraComponent):
"""A badge component."""
tag = "Badge"
# Variant of the badge ("solid" | "subtle" | "outline")
variant: Var[str]
# The color of the badge
color_scheme: Var[str]

View File

@ -0,0 +1,71 @@
"""A code component."""
import json
from typing import Dict
from pynecone import utils
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.tags import Tag
from pynecone.var import Var
class CodeBlock(Component):
"""A code block."""
library = "react-syntax-highlighter"
tag = "Prism"
# The language to use.
language: Var[str]
# If this is enabled line numbers will be shown next to the code block.
show_line_numbers: Var[bool]
# The starting line number to use.
starting_line_number: Var[int]
# Whether to wrap long lines.
wrap_long_lines: Var[bool]
# A custom style for the code block.
custom_style: Var[Dict[str, str]]
# Props passed down to the code tag.
code_tag_props: Var[Dict[str, str]]
@classmethod
def create(cls, *children, **props):
"""Create a text component.
Args:
*children: The children of the component.
**props: The props to pass to the component.
Returns:
The text component.
"""
# This component handles style in a special prop.
custom_style = props.get("custom_style", {})
# Transfer style props to the custom style prop.
for key, value in props.items():
if key not in cls.get_fields():
custom_style[key] = value
# Create the component.
return super().create(
*children,
**props,
)
def _add_style(self, style):
self.custom_style = self.custom_style or {}
self.custom_style.update(style) # type: ignore
class Code(ChakraComponent):
"""Used to display inline code."""
tag = "Code"

View File

@ -0,0 +1,55 @@
"""Table components."""
from typing import Any, Dict, List, Union
from pynecone.components.component import Component
from pynecone.components.tags import Tag
from pynecone.var import Var
class Gridjs(Component):
"""A component that wraps a nivo bar component."""
library = "gridjs-react"
class DataTable(Gridjs):
"""A data table component."""
tag = "Grid"
df: Var[Any]
# The data to display. EIther a list of lists or a pandas dataframe.
data: Any
# The columns to display.
columns: Var[List]
# Enable a search bar.
search: Var[bool]
# Enable sorting on columns.
sort: Var[bool]
# Enable resizable columns.
resizable: Var[bool]
# Enable pagination.
pagination: Var[bool]
def _get_custom_code(self) -> str:
return """
import "gridjs/dist/theme/mermaid.css";
"""
def _render(self) -> Tag:
if type(self.data).__name__ == "DataFrame":
self.columns = Var.create(list(self.data.columns.values.tolist())) # type: ignore
self.data = Var.create(list(self.data.values.tolist())) # type: ignore
if isinstance(self.df, Var):
self.columns = self.df["columns"]
self.data = self.df["data"]
return super()._render()

View File

@ -0,0 +1,16 @@
"""A line to divide parts of the layout."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Divider(ChakraComponent):
"""Dividers are used to visually separate content in a list or group."""
tag = "Divider"
# Pass the orientation prop and set it to either horizontal or vertical. If the vertical orientation is used, make sure that the parent element is assigned a height.
orientation: Var[str]
# Variant of the divider ("solid" | "dashed")
variant: Var[str]

View File

@ -0,0 +1,38 @@
"""List components."""
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class List(ChakraComponent):
"""List component is used to display list items. It renders a ul element by default."""
tag = "List"
# The space between each list item
spacing: Var[str]
# Shorthand prop for listStylePosition
style_position: Var[str]
# Shorthand prop for listStyleType
style_type: Var[str]
class ListItem(ChakraComponent):
"""ListItem composes Box so you can pass all style and pseudo style props."""
tag = "ListItem"
class OrderedList(ChakraComponent):
"""An ordered list component."""
tag = "OrderedList"
class UnorderedList(ChakraComponent):
"""An unordered list component."""
tag = "UnorderedList"

View File

@ -0,0 +1,43 @@
"""Statistics components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Stat(ChakraComponent):
"""The Stat component is used to display some statistics. It can take in a label, a number and a help text."""
tag = "Stat"
class StatLabel(ChakraComponent):
"""A stat label component."""
tag = "StatLabel"
class StatNumber(ChakraComponent):
"""The stat to display."""
tag = "StatNumber"
class StatHelpText(ChakraComponent):
"""A helper text to display under the stat."""
tag = "StatHelpText"
class StatArrow(ChakraComponent):
"""A stat arrow component indicating the direction of change."""
tag = "StatArrow"
# The type of arrow, either increase or decrease.
type_: Var[str]
class StatGroup(ChakraComponent):
"""A stat group component to evenly space out the stats."""
tag = "StatGroup"

View File

@ -0,0 +1,81 @@
"""Table components."""
from typing import List
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Table(ChakraComponent):
"""A table component."""
tag = "Table"
# The color scheme of the table
color_scheme: Var[str]
# The variant of the table style to use
variant: Var[str]
# The size of the table
size: Var[str]
# The placement of the table caption.
placement: Var[str]
class Thead(Table):
"""A table header component."""
tag = "Thead"
class Tbody(Table):
"""A table body component."""
tag = "Tbody"
class Tfoot(Table):
"""A table footer component."""
tag = "Tfoot"
class Tr(Table):
"""A table row component."""
tag = "Tr"
class Th(ChakraComponent):
"""A table header cell component."""
tag = "Th"
# Aligns the cell content to the right.
is_numeric: Var[bool]
class Td(ChakraComponent):
"""A table data cell component."""
tag = "Td"
# Aligns the cell content to the right.
is_numeric: Var[bool]
class TableCaption(ChakraComponent):
"""A table caption component."""
tag = "TableCaption"
# The placement of the table caption. This sets the `caption-side` CSS attribute.
placement: Var[str]
class TableContainer(ChakraComponent):
"""The table container component renders a div that wraps the table component."""
tag = "TableContainer"

View File

@ -0,0 +1,12 @@
"""Disclosure components."""
from .accordion import (
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
)
from .tabs import Tab, TabList, TabPanel, TabPanels, Tabs
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,60 @@
"""Container to stack elements with spacing."""
from typing import List, Optional, Union
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Accordion(ChakraComponent):
"""The wrapper that uses cloneElement to pass props to AccordionItem children."""
tag = "Accordion"
# If true, multiple accordion items can be expanded at once.
allow_multiple: Var[bool]
# If true, any expanded accordion item can be collapsed again.
allow_toggle: Var[bool]
# The initial index(es) of the expanded accordion item(s).
default_index: Var[Optional[List[int]]]
# The index(es) of the expanded accordion item
index: Var[Union[int, List[int]]]
# If true, height animation and transitions will be disabled.
reduce_motion: Var[bool]
class AccordionItem(ChakraComponent):
"""A single accordion item."""
tag = "AccordionItem"
# A unique id for the accordion item.
id_: Var[str]
# If true, the accordion item will be disabled.
is_disabled: Var[bool]
# If true, the accordion item will be focusable.
is_focusable: Var[bool]
class AccordionButton(ChakraComponent):
"""The button that toggles the expand/collapse state of the accordion item. This button must be wrapped in an element with role heading."""
tag = "AccordionButton"
class AccordionPanel(ChakraComponent):
"""The container for the details to be revealed."""
tag = "AccordionPanel"
class AccordionIcon(ChakraComponent):
"""A chevron-down icon that rotates based on the expanded/collapsed state."""
tag = "AccordionIcon"

View File

@ -0,0 +1,70 @@
"""Tab components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Tabs(ChakraComponent):
"""An accessible tabs component that provides keyboard interactions and ARIA attributes described in the WAI-ARIA Tabs Design Pattern. Tabs, provides context and state for all components."""
tag = "Tabs"
# The alignment of the tabs ("center" | "end" | "start").
align: Var[str]
# The initial index of the selected tab (in uncontrolled mode).
default_index: Var[int]
# The id of the tab.
id_: Var[str]
# If true, tabs will stretch to width of the tablist.
is_fitted: Var[bool]
# Performance booster. If true, rendering of the tab panel's will be deferred until it is selected.
is_lazy: Var[bool]
# If true, the tabs will be manually activated and display its panel by pressing Space or Enter. If false, the tabs will be automatically activated and their panel is displayed when they receive focus.
is_manual: Var[bool]
# The orientation of the tab list.
orientation: Var[str]
# "line" | "enclosed" | "enclosed-colored" | "soft-rounded" | "solid-rounded" | "unstyled"
variant: Var[str]
class Tab(ChakraComponent):
"""An element that serves as a label for one of the tab panels and can be activated to display that panel.."""
tag = "Tab"
# If true, the Tab won't be toggleable.
is_disabled: Var[bool]
# If true, the Tab will be selected.
is_selected: Var[bool]
# The id of the tab.
id_: Var[str]
# The id of the panel.
panel_id: Var[str]
class TabList(ChakraComponent):
"""Wrapper for the Tab components."""
tag = "TabList"
class TabPanels(ChakraComponent):
"""Wrapper for the Tab components."""
tag = "TabPanels"
class TabPanel(ChakraComponent):
"""An element that contains the content associated with a tab."""
tag = "TabPanel"

View File

@ -0,0 +1,7 @@
"""Convenience functions to define core components."""
from .alert import Alert, AlertDescription, AlertIcon, AlertTitle
from .circularprogress import CircularProgress, CircularProgressLabel
from .progress import Progress
from .skeleton import Skeleton, SkeletonCircle, SkeletonText
from .spinner import Spinner

View File

@ -0,0 +1,34 @@
"""Alert components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Alert(ChakraComponent):
"""Container to stack elements with spacing."""
tag = "Alert"
# The status of the alert ("success" | "info" | "warning" | "error")
status: Var[str]
# "subtle" | "left-accent" | "top-accent" | "solid"
variant: Var[str]
class AlertIcon(ChakraComponent):
"""AlertIcon composes Icon and changes the icon based on the status prop."""
tag = "AlertIcon"
class AlertTitle(ChakraComponent):
"""AlertTitle composes the Box component."""
tag = "AlertTitle"
class AlertDescription(ChakraComponent):
"""AlertDescription composes the Box component."""
tag = "AlertDescription"

View File

@ -0,0 +1,40 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class CircularProgress(ChakraComponent):
"""The CircularProgress component is used to indicate the progress for determinate and indeterminate processes."""
tag = "CircularProgress"
# If true, the cap of the progress indicator will be rounded.
cap_is_round: Var[bool]
# If true, the progress will be indeterminate and the value prop will be ignored
is_indeterminate: Var[bool]
# Maximum value defining 100% progress made (must be higher than 'min')
max_: Var[int]
# Minimum value defining 'no progress' (must be lower than 'max')
min_: Var[int]
# This defines the stroke width of the svg circle.
thickness: Var[int]
# The color name of the progress track. Use a color key in the theme object
track_color: Var[str]
# Current progress (must be between min/max).
value: Var[int]
# The desired valueText to use in place of the value.
value_text: Var[str]
class CircularProgressLabel(ChakraComponent):
"""Label of CircularProcess."""
tag = "CircularProgressLabel"

View File

@ -0,0 +1,31 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Progress(ChakraComponent):
"""A bar to display progress."""
tag = "Progress"
# If true, the progress bar will show stripe
has_striped: Var[bool]
# If true, and hasStripe is true, the stripes will be animated
is_animated: Var[bool]
# If true, the progress will be indeterminate and the value prop will be ignored
is_indeterminate: Var[bool]
# The maximum value of the progress
max_: Var[int]
# The minimum value of the progress
min_: Var[int]
# The value of the progress indicator. If undefined the progress bar will be in indeterminate state
value: Var[int]
# The color scheme of the progress bar.
color_scheme: Var[str]

View File

@ -0,0 +1,70 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Skeleton(ChakraComponent):
"""Skeleton is used to display the loading state of some components. You can use it as a standalone component. Or to wrap another component to take the same height and width."""
tag = "Skeleton"
# The color at the animation end
end_color: Var[str]
# The fadeIn duration in seconds
fade_duration: Var[float]
# If true, it'll render its children with a nice fade transition
is_loaded: Var[bool]
# The animation speed in seconds
speed: Var[float]
# The color at the animation start
start_color: Var[str]
class SkeletonCircle(ChakraComponent):
"""SkeletonCircle is used to display the loading state of some components."""
tag = "SkeletonCircle"
# The color at the animation end
end_color: Var[str]
# The fadeIn duration in seconds
fade_duration: Var[float]
# If true, it'll render its children with a nice fade transition
is_loaded: Var[bool]
# The animation speed in seconds
speed: Var[float]
# The color at the animation start
start_color: Var[str]
class SkeletonText(ChakraComponent):
"""SkeletonText is used to display the loading state of some components."""
tag = "SkeletonText"
# The color at the animation end
end_color: Var[str]
# The fadeIn duration in seconds
fade_duration: Var[float]
# If true, it'll render its children with a nice fade transition
is_loaded: Var[bool]
# The animation speed in seconds
speed: Var[float]
# The color at the animation start
start_color: Var[str]
# Number is lines of text.
no_of_lines: Var[int]

View File

@ -0,0 +1,25 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Spinner(ChakraComponent):
"""The component that spins."""
tag = "Spinner"
# The color of the empty area in the spinner
empty_color: Var[str]
# For accessibility, it is important to add a fallback loading text. This text will be visible to screen readers.
label: Var[str]
# The speed of the spinner must be as a string and in seconds '1s'. Default is '0.45s'.
speed: Var[str]
# The thickness of the spinner.
thickness: Var[int]
# "xs" | "sm" | "md" | "lg" | "xl"
size: Var[str]

View File

@ -0,0 +1,29 @@
"""Convenience functions to define core components."""
from .button import Button, ButtonGroup
from .checkbox import Checkbox, CheckboxGroup
from .editable import Editable, EditableInput, EditablePreview, EditableTextarea
from .formcontrol import FormControl, FormErrorMessage, FormHelperText, FormLabel
from .iconbutton import IconButton
from .input import Input, InputGroup, InputLeftAddon, InputRightAddon
from .numberinput import (
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
)
from .pininput import PinInput, PinInputField
from .radio import Radio, RadioGroup
from .rangeslider import (
RangeSlider,
RangeSliderFilledTrack,
RangeSliderThumb,
RangeSliderTrack,
)
from .select import Option, Select
from .slider import Slider, SliderFilledTrack, SliderMark, SliderThumb, SliderTrack
from .switch import Switch
from .textarea import TextArea
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,55 @@
"""A button component."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Button(ChakraComponent):
"""The Button component is used to trigger an event or event, such as submitting a form, opening a dialog, canceling an event, or performing a delete operation."""
tag = "Button"
# The type of button.
type: Var[str]
# The space between the button icon and label.
icon_spacing: Var[int]
# If true, the button will be styled in its active state.
is_active: Var[bool]
# If true, the button will be styled in its disabled state.
is_disabled: Var[bool]
# If true, the button will take up the full width of its container.
is_full_width: Var[bool]
# If true, the button will show a spinner.
is_loading: Var[bool]
# The label to show in the button when isLoading is true If no text is passed, it only shows the spinner.
loading_text: Var[str]
# "lg" | "md" | "sm" | "xs"
size: Var[str]
# "ghost" | "outline" | "solid" | "link" | "unstyled"
variant: Var[str]
# Built in color scheme for ease of use.
color_scheme: Var[str]
class ButtonGroup(ChakraComponent):
"""A group of buttons."""
tag = "ButtonGroup"
# If true, the borderRadius of button that are direct children will be altered to look flushed together.
is_attached: Var[bool]
# If true, all wrapped button will be disabled.
is_disabled: Var[bool]
# The spacing between the buttons.
spacing: Var[int]

View File

@ -0,0 +1,82 @@
"""A checkbox component."""
from typing import Set
from pynecone.components.component import EVENT_ARG
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Checkbox(ChakraComponent):
"""The Checkbox component is used in forms when a user needs to select multiple values from several options."""
tag = "Checkbox"
# Color scheme for checkbox.
color_scheme: Var[str]
# "sm" | "md" | "lg"
size: Var[str]
# If true, the checkbox will be checked.
is_checked: Var[bool]
# If true, the checkbox will be disabled
is_disabled: Var[bool]
# If true and is_disabled is passed, the checkbox will remain tabbable but not interactive
is_focusable: Var[bool]
# If true, the checkbox will be indeterminate. This only affects the icon shown inside checkbox and does not modify the is_checked var.
is_indeterminate: Var[bool]
# If true, the checkbox is marked as invalid. Changes style of unchecked state.
is_invalid: Var[bool]
# If true, the checkbox will be readonly
is_read_only: Var[bool]
# If true, the checkbox input is marked as required, and required attribute will be added
is_required: Var[bool]
# The name of the input field in a checkbox (Useful for form submission).
name: Var[str]
# The spacing between the checkbox and its label text (0.5rem)
spacing: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change"}
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG.target.checked
class CheckboxGroup(ChakraComponent):
"""A group of checkboxes."""
tag = "CheckboxGroup"
# The value of the checkbox group
value: Var[str]
# The initial value of the checkbox group
default_value: Var[str]
# If true, all wrapped checkbox inputs will be disabled
is_disabled: Var[bool]
# If true, input elements will receive checked attribute instead of isChecked. This assumes, you're using native radio inputs
is_native: Var[bool]

View File

@ -0,0 +1,69 @@
"""An editable component."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.tags import Tag
from pynecone.var import Var
class Editable(ChakraComponent):
"""The wrapper component that provides context value."""
tag = "Editable"
# If true, the Editable will be disabled.
is_disabled: Var[bool]
# If true, the read only view, has a tabIndex set to 0 so it can receive focus via the keyboard or click.
is_preview_focusable: Var[bool]
# The placeholder text when the value is empty.
placeholder: Var[str]
# If true, the input's text will be highlighted on focus.
select_all_on_focus: Var[bool]
# If true, the Editable will start with edit mode by default.
start_with_edit_view: Var[bool]
# If true, it'll update the value onBlur and turn off the edit mode.
submit_on_blur: Var[bool]
# The value of the Editable in both edit & preview mode
value: Var[str]
# The initial value of the Editable in both edit and preview mode.
default_value: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {
"on_change",
"on_edit",
"on_submit",
"on_cancel",
}
class EditableInput(ChakraComponent):
"""The edit view of the component. It shows when you click or focus on the text."""
tag = "EditableInput"
class EditableTextarea(ChakraComponent):
"""Use the textarea element to handle multi line text input in an editable context."""
tag = "EditableTextarea"
class EditablePreview(ChakraComponent):
"""The read-only view of the component."""
tag = "EditablePreview"

View File

@ -0,0 +1,46 @@
"""Form components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class FormControl(ChakraComponent):
"""FormControl provides context such as isInvalid, isDisabled, and isRequired to form elements."""
tag = "FormControl"
# If true, the form control will be disabled.
is_disabled: Var[bool]
# If true, the form control will be invalid.
is_invalid: Var[bool]
# If true, the form control will be readonly
is_read_only: Var[bool]
# If true, the form control will be required.
is_required: Var[bool]
# The label text used to inform users as to what information is requested for a text field.
label: Var[str]
class FormHelperText(ChakraComponent):
"""A form helper text component."""
tag = "FormHelperText"
class FormLabel(ChakraComponent):
"""A form label component."""
tag = "FormLabel"
# Link
html_for: Var[str]
class FormErrorMessage(ChakraComponent):
"""A form error message component."""
tag = "FormErrorMessage"

View File

@ -0,0 +1,34 @@
"""A button component."""
from pynecone.components.typography.text import Text
from pynecone.var import Var
class IconButton(Text):
"""A button that can be clicked."""
tag = "IconButton"
# The type of button.
type: Var[str]
# A label that describes the button
aria_label: Var[str]
# The icon to be used in the button.
icon: Var[str]
# If true, the button will be styled in its active state.
is_active: Var[bool]
# If true, the button will be disabled.
is_disabled: Var[bool]
# If true, the button will show a spinner.
is_loading: Var[bool]
# If true, the button will be perfectly round. Else, it'll be slightly round
is_round: Var[bool]
# Replace the spinner component when isLoading is set to true
spinner: Var[str]

View File

@ -0,0 +1,82 @@
"""An input component."""
from typing import Set
from pynecone.components.component import EVENT_ARG
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Input(ChakraComponent):
"""The Input component is a component that is used to get user input in a text field."""
tag = "Input"
# State var to bind the the input.
value: Var[str]
# The default value of the input.
default_value: Var[str]
# The placeholder text.
placeholder: Var[str]
# The type of input.
type_: Var[str] = "text" # type: ignore
# The border color when the input is invalid.
error_border_color: Var[str]
# The border color when the input is focused.
focus_border_color: Var[str]
# If true, the form control will be disabled. This has 2 side effects - The FormLabel will have `data-disabled` attribute - The form element (e.g, Input) will be disabled
is_disabled: Var[bool]
# If true, the form control will be invalid. This has 2 side effects - The FormLabel and FormErrorIcon will have `data-invalid` set to true - The form element (e.g, Input) will have `aria-invalid` set to true
is_invalid: Var[bool]
# If true, the form control will be readonly.
is_read_only: Var[bool]
# If true, the form control will be required. This has 2 side effects - The FormLabel will show a required indicator - The form element (e.g, Input) will have `aria-required` set to true
is_required: Var[bool]
# "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change", "on_focus", "on_blur"}
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG.target.value
class InputGroup(ChakraComponent):
"""The InputGroup component is a component that is used to group a set of inputs."""
tag = "InputGroup"
class InputLeftAddon(ChakraComponent):
"""The InputLeftAddon component is a component that is used to add an addon to the left of an input."""
tag = "InputLeftAddon"
class InputRightAddon(ChakraComponent):
"""The InputRightAddon component is a component that is used to add an addon to the right of an input."""
tag = "InputRightAddon"

View File

@ -0,0 +1,120 @@
"""A number input component."""
from typing import Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class NumberInput(ChakraComponent):
"""The wrapper that provides context and logic to the components."""
tag = "NumberInput"
# State var to bind the the input.
value: Var[int]
# If true, the input's value will change based on mouse wheel.
allow_mouse_wheel: Var[bool]
# This controls the value update when you blur out of the input. - If true and the value is greater than max, the value will be reset to max - Else, the value remains the same.
clamped_value_on_blur: Var[bool]
# The initial value of the counter. Should be less than max and greater than min
default_value: Var[int]
# The border color when the input is invalid.
error_border_color: Var[str]
# The border color when the input is focused.
focus_border_color: Var[str]
# If true, the input will be focused as you increment or decrement the value with the stepper
focus_input_on_change: Var[bool]
# Hints at the type of data that might be entered by the user. It also determines the type of keyboard shown to the user on mobile devices ("text" | "search" | "none" | "tel" | "url" | "email" | "numeric" | "decimal")
input_mode: Var[str]
# Whether the input should be disabled.
is_disabled: Var[bool]
# If true, the input will have `aria-invalid` set to true
is_invalid: Var[bool]
# If true, the input will be in readonly mode
is_read_only: Var[bool]
# Whether the input is required
is_required: Var[bool]
# Whether the pressed key should be allowed in the input. The default behavior is to allow DOM floating point characters defined by /^[Ee0-9+\-.]$/
is_valid_character: Var[str]
# This controls the value update behavior in general. - If true and you use the stepper or up/down arrow keys, the value will not exceed the max or go lower than min - If false, the value will be allowed to go out of range.
keep_within_range: Var[bool]
# The maximum value of the counter
max_: Var[int]
# The minimum value of the counter
min_: Var[int]
# "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change"}
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a number input component.
If no children are provided, a default stepper will be used.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The component.
"""
if len(children) == 0:
children = [
NumberInputField.create(),
NumberInputStepper.create(
NumberIncrementStepper.create(),
NumberDecrementStepper.create(),
),
]
return super().create(*children, **props)
class NumberInputField(ChakraComponent):
"""The input field itself."""
tag = "NumberInputField"
class NumberInputStepper(ChakraComponent):
"""The wrapper for the input's stepper buttons."""
tag = "NumberInputStepper"
class NumberIncrementStepper(ChakraComponent):
"""The button to increment the value of the input."""
tag = "NumberIncrementStepper"
class NumberDecrementStepper(ChakraComponent):
"""The button to decrement the value of the input."""
tag = "NumberDecrementStepper"

View File

@ -0,0 +1,88 @@
"""A pin input component."""
from typing import Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class PinInput(ChakraComponent):
"""The component that provides context to all the pin-input fields."""
tag = "PinInput"
# State var to bind the the input.
value: Var[str]
# If true, the pin input receives focus on mount
auto_focus: Var[bool]
# The default value of the pin input
default_value: Var[str]
# The border color when the input is invalid.
error_border_color: Var[str]
# The border color when the input is focused.
focus_border_color: Var[str]
# The top-level id string that will be applied to the input fields. The index of the input will be appended to this top-level id.
id_: Var[str]
# The length of the number input.
length: Var[int]
# If true, the pin input component is put in the disabled state
is_disabled: Var[bool]
# If true, the pin input component is put in the invalid state
is_invalid: Var[bool]
# If true, focus will move automatically to the next input once filled
manage_focus: Var[bool]
# If true, the input's value will be masked just like `type=password`
mask: Var[bool]
# The placeholder for the pin input
placeholder: Var[str]
# The type of values the pin-input should allow ("number" | "alphanumeric").
type_: Var[str]
# "outline" | "flushed" | "filled" | "unstyled"
variant: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change", "on_complete"}
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a pin input component.
If no children are passed in, the component will create a default pin input
based on the length prop.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The pin input component.
"""
if len(children) == 0 and "length" in props:
children = [PinInputField()] * props["length"]
return super().create(*children, **props)
class PinInputField(ChakraComponent):
"""The text field that user types in - must be a direct child of PinInput."""
tag = "PinInputField"

View File

@ -0,0 +1,93 @@
"""A radio component."""
from typing import Any, Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.typography.text import Text
from pynecone.var import Var
class RadioGroup(ChakraComponent):
"""A grouping of individual radio options."""
tag = "RadioGroup"
# State var to bind the the input.
value: Var[Any]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change"}
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a radio group component.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The component.
"""
if len(children) == 1 and isinstance(children[0], list):
children = [Radio.create(child) for child in children[0]]
return super().create(*children, **props)
class Radio(Text):
"""Radios are used when only one choice may be selected in a series of options."""
tag = "Radio"
# Value of radio.
value: Var[Any]
# The default value.
default_value: Var[Any]
# The color scheme.
color_scheme: Var[str]
# If true, the radio will be initially checked.
default_checked: Var[bool]
# If true, the radio will be checked. You'll need to pass onChange to update its value (since it is now controlled)
is_checked: Var[bool]
# If true, the radio will be disabled.
is_disabled: Var[bool]
# If true, the radio button will be invalid. This also sets `aria-invalid` to true.
is_invalid: Var[bool]
# If true, the radio will be read-only
is_read_only: Var[bool]
# If true, the radio button will be required. This also sets `aria-required` to true.
is_required: Var[bool]
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a radio component.
By default, the value is bound to the first child.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The radio component.
"""
if "value" not in props:
assert len(children) == 1
props["value"] = children[0]
return super().create(*children, **props)

View File

@ -0,0 +1,99 @@
"""A button component."""
from typing import List, Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class RangeSlider(ChakraComponent):
"""The RangeSlider is a multi thumb slider used to select a range of related values. A common use-case of this component is a price range picker that allows a user to set the minimum and maximum price."""
tag = "RangeSlider"
# State var to bind the the input.
value: Var[List[int]]
# The default values.
default_value: Var[List[int]]
# The writing mode ("ltr" | "rtl")
direction: Var[str]
# If false, the slider handle will not capture focus when value changes.
focus_thumb_on_change: Var[bool]
# If true, the slider will be disabled
is_disabled: Var[bool]
# If true, the slider will be in `read-only` state.
is_read_only: Var[bool]
# If true, the value will be incremented or decremented in reverse.
is_reversed: Var[bool]
# The minimum value of the slider.
min_: Var[int]
# The maximum value of the slider.
max_: Var[int]
# The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
min_steps_between_thumbs: Var[int]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {
"on_change",
"on_change_end",
"on_change_start",
}
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a RangeSlider component.
If no children are provided, a default RangeSlider will be created.
Args:
children: The children of the component.
props: The properties of the component.
Returns:
The RangeSlider component.
"""
if len(children) == 0:
children = [
RangeSliderTrack.create(
RangeSliderFilledTrack.create(),
),
RangeSliderThumb.create(index=0),
RangeSliderThumb.create(index=1),
]
return super().create(*children, **props)
class RangeSliderTrack(ChakraComponent):
"""A button component."""
tag = "RangeSliderTrack"
class RangeSliderFilledTrack(ChakraComponent):
"""A button component."""
tag = "RangeSliderFilledTrack"
class RangeSliderThumb(ChakraComponent):
"""A button component."""
tag = "RangeSliderThumb"
index: Var[int]

View File

@ -0,0 +1,108 @@
"""A select component."""
from typing import Any, Set
from pynecone import utils
from pynecone.components.component import EVENT_ARG, Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.tags import Tag
from pynecone.components.typography.text import Text
from pynecone.var import Var
class Select(ChakraComponent):
"""Select component is a component that allows users pick a value from predefined options. Ideally, it should be used when there are more than 5 options, otherwise you might consider using a radio group instead."""
tag = "Select"
# State var to bind the the select.
value: Var[str]
# The default value of the select.
default_value: Var[str]
# The placeholder text.
placeholder: Var[str]
# The border color when the select is invalid.
error_border_color: Var[str]
# The border color when the select is focused.
focus_border_color: Var[str]
# If true, the select will be disabled.
is_disabled: Var[bool]
# If true, the form control will be invalid. This has 2 side effects: - The FormLabel and FormErrorIcon will have `data-invalid` set to true - The form element (e.g, Input) will have `aria-invalid` set to true
is_invalid: Var[bool]
# If true, the form control will be readonly
is_read_only: Var[bool]
# If true, the form control will be required. This has 2 side effects: - The FormLabel will show a required indicator - The form element (e.g, Input) will have `aria-required` set to true
is_required: Var[bool]
# "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change"}
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG.target.value
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a select component.
If a list is provided as the first children, a default component
will be created for each item in the list.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The component.
"""
if len(children) == 1 and isinstance(children[0], list):
children = [Option.create(child) for child in children[0]]
return super().create(*children, **props)
class Option(Text):
"""A button component."""
tag = "option"
value: Var[Any]
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a select option component.
By default, the value of the option is the text of the option.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The component.
"""
if "value" not in props:
assert len(children) == 1
props["value"] = children[0]
return super().create(*children, **props)

View File

@ -0,0 +1,102 @@
"""A slider component."""
from typing import Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Slider(ChakraComponent):
"""The wrapper that provides context and functionality for all children."""
tag = "Slider"
# State var to bind the the input.
value: Var[int]
# The placeholder text.
default_value: Var[int]
# The writing mode ("ltr" | "rtl")
direction: Var[str]
# If false, the slider handle will not capture focus when value changes.
focus_thumb_on_change: Var[bool]
# If true, the slider will be disabled
is_disabled: Var[bool]
# If true, the slider will be in `read-only` state.
is_read_only: Var[bool]
# If true, the value will be incremented or decremented in reverse.
is_reversed: Var[bool]
# The minimum value of the slider.
min_: Var[int]
# The maximum value of the slider.
max_: Var[int]
# The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
min_steps_between_thumbs: Var[int]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {
"on_change",
"on_change_end",
"on_change_start",
}
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a slider component.
If no children are provided, a default slider will be created.
Args:
children: The children of the component.
props: The properties of the component.
Returns:
The slider component.
"""
if len(children) == 0:
children = [
SliderTrack.create(
SliderFilledTrack.create(),
),
SliderThumb.create(),
]
return super().create(*children, **props)
class SliderTrack(ChakraComponent):
"""The empty part of the slider that shows the track."""
tag = "SliderTrack"
class SliderFilledTrack(ChakraComponent):
"""The filled part of the slider."""
tag = "SliderFilledTrack"
class SliderThumb(ChakraComponent):
"""The handle that's used to change the slider value."""
tag = "SliderThumb"
class SliderMark(ChakraComponent):
"""The label or mark that shows names for specific slider values."""
tag = "SliderMark"

View File

@ -0,0 +1,57 @@
"""A switch component."""
from typing import Set
from pynecone.components.component import EVENT_ARG
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Switch(ChakraComponent):
"""Togglable switch component."""
tag = "Switch"
# If true, the switch will be checked. You'll need to pass onChange to update its value (since it is now controlled)
is_checked: Var[bool]
# If true, the switch will be disabled
is_disabled: Var[bool]
# If true and isDisabled is passed, the switch will remain tabbable but not interactive
is_focusable: Var[bool]
# If true, the switch is marked as invalid. Changes style of unchecked state.
is_invalid: Var[bool]
# If true, the switch will be readonly
is_read_only: Var[bool]
# If true, the switch will be required
is_required: Var[bool]
# The name of the input field in a switch (Useful for form submission).
name: Var[str]
# The spacing between the switch and its label text (0.5rem)
spacing: Var[str]
# The placeholder text.
placeholder: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change"}
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG.target.checked

View File

@ -0,0 +1,61 @@
"""A textarea component."""
from typing import Set
from pynecone.components.component import EVENT_ARG
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class TextArea(ChakraComponent):
"""A text area component."""
tag = "Textarea"
# State var to bind the the input.
value: Var[str]
# The default value of the textarea.
default_value: Var[str]
# The placeholder text.
placeholder: Var[str]
# The border color when the input is invalid.
error_border_color: Var[str]
# The border color when the input is focused.
focus_border_color: Var[str]
# If true, the form control will be disabled.
is_disabled: Var[bool]
# If true, the form control will be invalid.
is_invalid: Var[bool]
# If true, the form control will be readonly.
is_read_only: Var[bool]
# If true, the form control will be required.
is_required: Var[bool]
# "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str]
@classmethod
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
The controlled event triggers.
"""
return {"on_change", "on_focus", "on_blur"}
@classmethod
def get_controlled_value(cls) -> Var:
"""Get the var that is passed to the event handler for controlled triggers.
Returns:
The controlled value.
"""
return EVENT_ARG.target.value

View File

@ -0,0 +1,5 @@
"""Convenience functions to define layout components."""
from .plotly import Plotly
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,53 @@
"""Component for displaying a plotly graph."""
from typing import Dict, Union
from plotly.graph_objects import Figure
from plotly.io import to_json
from pynecone.components.component import Component
from pynecone.components.tags import Tag
from pynecone.var import Var
class PlotlyLib(Component):
"""A component that wraps a plotly lib."""
library = "react-plotly.js"
class Plotly(PlotlyLib):
"""Display a plotly graph."""
tag = "Plot"
# The figure to display. This can be a plotly figure or a plotly data json.
data: Var[Figure]
# The layout of the graph.
layout: Var[Dict]
# The width of the graph.
width: Var[str]
# The height of the graph.
height: Var[str]
def _get_imports(self):
return {}
def _get_custom_code(self) -> str:
return """
import dynamic from 'next/dynamic'
const Plot = dynamic(() => import('react-plotly.js'), { ssr: false });
"""
def _render(self) -> Tag:
if isinstance(self.data, Figure):
if self.layout is None:
if self.width is not None:
layout = Var.create({"width": self.width, "height": self.height})
assert layout is not None
self.layout = layout
return super()._render()

View File

@ -0,0 +1,14 @@
"""Convenience functions to define layout components."""
from .box import Box
from .center import Center, Circle, Square
from .cond import Cond
from .container import Container
from .flex import Flex
from .foreach import Foreach
from .grid import Grid, GridItem, ResponsiveGrid
from .spacer import Spacer
from .stack import Hstack, Stack, Vstack
from .wrap import Wrap, WrapItem
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,25 @@
"""A box component that can contain other components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.tags import Tag
from pynecone.var import Var
class Box(ChakraComponent):
"""Renders a box component that can contain other components."""
tag = "Box"
# The element to render.
element: Var[str]
def _render(self) -> Tag:
return (
super()
._render()
.add_attrs(
**{
"as": self.element,
}
)
)

View File

@ -0,0 +1,21 @@
"""A box that centers its contents."""
from pynecone.components.libs.chakra import ChakraComponent
class Center(ChakraComponent):
"""Center is a layout component that centers its child within itself. It's useful for centering text, images, and other elements. All box can be used on center to style."""
tag = "Center"
class Square(ChakraComponent):
"""Square centers its child given size (width and height). All box props can be used on Square."""
tag = "Square"
class Circle(ChakraComponent):
"""Circle a Square with round border-radius. All box props can be used on Circle."""
tag = "Circle"

View File

@ -0,0 +1,75 @@
"""Create a list of components from an iterable."""
from __future__ import annotations
from typing import Optional
import pydantic
from pynecone.components.component import Component
from pynecone.components.layout.box import Box
from pynecone.components.tags import CondTag, Tag
from pynecone.var import Var
class Cond(Component):
"""Display a conditional render."""
# The cond to determine which component to render.
cond: Var[bool]
# The component to render if the cond is true.
comp1: Component
# The component to render if the cond is false.
comp2: Component
# Whether the cond is within another cond.
is_nested: bool = False
@pydantic.validator("cond")
def validate_cond(cls, cond: Var) -> Var:
"""Validate that the cond is a boolean.
Args:
cond: The cond to validate.
Returns:
The validated cond.
"""
assert issubclass(cond.type_, bool), "The var must be a boolean."
return cond
@classmethod
def create(
cls, cond: Var, comp1: Component, comp2: Optional[Component] = None
) -> Cond:
"""Create a conditional component.
Args:
cond: The cond to determine which component to render.
comp1: The component to render if the cond is true.
comp2: The component to render if the cond is false.
Returns:
The conditional component.
"""
if comp2 is None:
comp2 = Box.create()
if isinstance(comp1, Cond):
comp1.is_nested = True
if isinstance(comp2, Cond):
comp2.is_nested = True
return cls(
cond=cond,
comp1=comp1,
comp2=comp2,
children=[comp1, comp2],
) # type: ignore
def _render(self) -> Tag:
return CondTag(
cond=self.cond,
true_value=self.comp1.render(),
false_value=self.comp2.render(),
is_nested=self.is_nested,
)

View File

@ -0,0 +1,13 @@
"""A flexbox container."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Container(ChakraComponent):
"""Container composes Box so you can pass all Box related props in addition to this."""
tag = "Container"
# If true, container will center its children regardless of their width.
center_content: Var[bool]

View File

@ -0,0 +1,31 @@
"""A reflexive container component."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Flex(ChakraComponent):
"""Flex is Box with display, flex and comes with helpful style shorthand. It renders a div element."""
tag = "Flex"
# How to align items in the flex.
align: Var[str]
# Shorthand for flexBasis style prop
basis: Var[str]
# Shorthand for flexDirection style prop
direction: Var[str]
# Shorthand for flexGrow style prop
grow: Var[str]
# The way to justify the items.
justify: Var[str]
# Shorthand for flexWrap style prop
wrap: Var[str]
# Shorthand for flexShrink style prop
shrink: Var[str]

View File

@ -0,0 +1,62 @@
"""Create a list of components from an iterable."""
from __future__ import annotations
from typing import Any, List, Protocol, runtime_checkable
from pynecone.components.component import Component
from pynecone.components.tags import IterTag, Tag
from pynecone.var import BaseVar, Var
@runtime_checkable
class RenderFn(Protocol):
"""A function that renders a component."""
def __call__(self, *args, **kwargs) -> Component:
"""Render a component.
Args:
*args: The positional arguments.
**kwargs: The keyword arguments.
Returns: # noqa: DAR202
The rendered component.
"""
...
class Foreach(Component):
"""Display a foreach."""
# The iterable to create components from.
iterable: Var[List]
# A function from the render args to the component.
render_fn: RenderFn
@classmethod
def create(cls, iterable: Var[List], render_fn: RenderFn, **props) -> Foreach:
"""Create a foreach component.
Args:
iterable: The iterable to create components from.
render_fn: A function from the render args to the component.
**props: The attributes to pass to each child component.
Returns:
The foreach component.
"""
try:
type_ = iterable.type_.__args__[0]
except:
type_ = Any
arg = BaseVar(name="_", type_=type_, is_local=True)
return cls(
iterable=iterable,
render_fn=render_fn,
children=[IterTag.render_component(render_fn, arg=arg)],
**props,
)
def _render(self) -> Tag:
return IterTag(iterable=self.iterable, render_fn=self.render_fn)

View File

@ -0,0 +1,105 @@
"""Container to stack elements with spacing."""
from typing import List
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Grid(ChakraComponent):
"""The main wrapper of th egrid component."""
tag = "Grid"
# Shorthand prop for gridAutoColumns
auto_columns: Var[str]
# Shorthand prop for gridAutoFlow
auto_flow: Var[str]
# Shorthand prop for gridAutoRows
auto_rows: Var[str]
# Shorthand prop for gridColumn
column: Var[str]
# Shorthand prop for gridRow
row: Var[str]
# Shorthand prop for gridTemplateColumns
template_columns: Var[str]
# Shorthand prop for gridTemplateRows
template_rows: Var[str]
class GridItem(ChakraComponent):
"""Used as a child of Grid to control the span, and start positions within the grid."""
tag = "GridItem"
# Shorthand prop for gridArea
area: Var[str]
# Shorthand prop for gridColumnEnd
col_end: Var[str]
# The column number the grid item should start.
col_start: Var[int]
# The number of columns the grid item should span.
col_span: Var[int]
# Shorthand prop for gridRowEnd
row_end: Var[str]
# The row number the grid item should start.
row_start: Var[int]
# The number of rows the grid item should span.
row_span: Var[int]
class ResponsiveGrid(ChakraComponent):
"""A responsive grid component."""
tag = "SimpleGrid"
# Shorthand prop for gridAutoColumns
auto_columns: Var[str]
# Shorthand prop for gridAutoFlow
auto_flow: Var[str]
# Shorthand prop for gridAutoRows
auto_rows: Var[str]
# Shorthand prop for gridColumn
column: Var[str]
# Shorthand prop for gridRow
row: Var[str]
# Alist that defines the number of columns for each breakpoint.
columns: Var[List[int]]
# The width at which child elements will break into columns. Pass a number for pixel values or a string for any other valid CSS length.
min_child_width: Var[str]
# The gap between the grid items
spacing: Var[str]
# The column gap between the grid items
spacing_x: Var[str]
# The row gap between the grid items
spacing_y: Var[str]
# Shorthand prop for gridTemplateAreas
template_areas: Var[str]
# Shorthand prop for gridTemplateColumns
template_columns: Var[str]
# Shorthand prop for gridTemplateRows
template_rows: Var[str]

View File

@ -0,0 +1,9 @@
"""A flexible space component."""
from pynecone.components.libs.chakra import ChakraComponent
class Spacer(ChakraComponent):
"""Display a flexible space."""
tag = "Spacer"

View File

@ -0,0 +1,46 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Stack(ChakraComponent):
"""Display a square box."""
tag = "Stack"
# Shorthand for alignItems style prop
align_items: Var[str]
# The direction to stack the items.
direction: Var[str]
# If true the items will be stacked horizontally.
is_inline: Var[bool]
# Shorthand for justifyContent style prop
justify_content: Var[str]
# If true, the children will be wrapped in a Box, and the Box will take the spacing props
should_wrap_children: Var[bool]
# The space between each stack item
spacing: Var[str]
# Shorthand for flexWrap style prop
wrap: Var[str]
# Alignment of contents.
justify: Var[str]
class Hstack(Stack):
"""The HStack component is a component which is only facing the horizontal direction. Additionally you can add a divider and horizontal spacing between the items."""
tag = "HStack"
class Vstack(Stack):
"""The VStack component is a component which is only facing the vertical direction. Additionally you can add a divider and vertical spacing between the items."""
tag = "VStack"

View File

@ -0,0 +1,15 @@
"""Container to stack elements with spacing."""
from pynecone.components.libs.chakra import ChakraComponent
class Wrap(ChakraComponent):
"""Layout component used to add space between elements and wraps automatically if there isn't enough space."""
tag = "Wrap"
class WrapItem(ChakraComponent):
"""Item of the Wrap component."""
tag = "WrapItem"

View File

@ -0,0 +1 @@
"""React component libraries."""

View File

@ -0,0 +1,9 @@
"""Components that are based on Chakra-UI."""
from pynecone.components.component import Component
class ChakraComponent(Component):
"""A component that wraps a Chakra component."""
library = "@chakra-ui/react"

View File

@ -0,0 +1,7 @@
"""Media components."""
from .avatar import Avatar, AvatarBadge, AvatarGroup
from .icon import Icon
from .image import Image
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,69 @@
"""Avatar components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Avatar(ChakraComponent):
"""The image that represents the user."""
tag = "Avatar"
# Function to get the initials to display.
get_initials: Var[str]
# The default avatar used as fallback when name, and src is not specified.
icon: Var[str]
# The label of the icon.
icon_label: Var[str]
# If true, opt out of the avatar's fallback logic and renders the img at all times.
ignore_fallback: Var[bool]
# Defines loading strategy ("eager" | "lazy").
loading: Var[str]
# The name of the person in the avatar.
name: Var[str]
# If true, the Avatar will show a border around it. Best for a group of avatars.
show_border: Var[bool]
# The image url of the Avatar.
src: Var[str]
# List of sources to use for different screen resolutions.
src_set: Var[str]
# "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full"
size: Var[str]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {"on_error"}
class AvatarBadge(ChakraComponent):
"""A wrapper that displays its content on the right corner of the avatar."""
tag = "AvatarBadge"
class AvatarGroup(ChakraComponent):
"""A wrapper to stack multiple Avatars together."""
tag = "AvatarGroup"
# The maximum number of visible avatars.
max_: Var[int]
# The space between the avatars in the group.
spacing: Var[int]

View File

@ -0,0 +1,15 @@
"""An image component."""
from pynecone.components.component import Component
class ChakraIconComponent(Component):
"""A component that wraps a chakra icon component."""
library = "@chakra-ui/icons"
class Icon(ChakraIconComponent):
"""The Avatar component is used to represent a user, and displays the profile picture, initials or fallback icon."""
tag = "None"

View File

@ -0,0 +1,53 @@
"""An image component."""
from __future__ import annotations
from typing import Optional, Set
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Image(ChakraComponent):
"""The Image component is used to display images. Image composes Box so you can use all the style props and add responsive styles as well."""
tag = "Image"
# How to align the image within its bounds. It maps to css `object-position` property.
align: Var[str]
# Fallback Pynecone component to show if image is loading or image fails.
fallback: Optional[Component] = None
# Fallback image src to show if image is loading or image fails.
fallback_src: Var[str]
# How the image to fit within its bounds. It maps to css `object-fit` property.
fit: Var[str]
# The native HTML height attribute to the passed to the img.
html_height: Var[str]
# The native HTML width attribute to the passed to the img.
html_width: Var[str]
# If true, opt out of the fallbackSrc logic and use as img.
ignore_fallback: Var[bool]
# "eager" | "lazy"
loading: Var[str]
# The image src attribute.
src: Var[str]
# The image srcset attribute.
src_set: Var[str]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {"on_error", "on_load"}

View File

@ -0,0 +1,8 @@
"""Navigation components."""
from .breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator
from .link import Link
from .linkoverlay import LinkBox, LinkOverlay
from .nextlink import NextLink
__all__ = [f for f in dir() if f[0].isupper()] # type: ignore

View File

@ -0,0 +1,52 @@
"""Breadcrumb components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Breadcrumb(ChakraComponent):
"""The parent container for breadcrumbs."""
tag = "Breadcrumb"
# The visual separator between each breadcrumb item
separator: Var[str]
# The left and right margin applied to the separator
separator_margin: Var[str]
class BreadcrumbItem(ChakraComponent):
"""Individual breadcrumb element containing a link and a divider."""
tag = "BreadcrumbItem"
# Is the current page of the breadcrumb.
is_current_page: Var[bool]
# Is the last child of the breadcrumb.
is_last_child: Var[bool]
# The visual separator between each breadcrumb item
separator: Var[str]
# The left and right margin applied to the separator
spacing: Var[str]
# The href of the item.
href: Var[str]
class BreadcrumbSeparator(ChakraComponent):
"""The visual separator between each breadcrumb."""
tag = "BreadcrumbSeparator"
class BreadcrumbLink(ChakraComponent):
"""The breadcrumb link."""
tag = "BreadcrumbLink"
# Is the current page of the breadcrumb.
is_current_page: Var[bool]

View File

@ -0,0 +1,42 @@
"""A link component."""
from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.components.navigation.nextlink import NextLink
from pynecone.var import Var
class Link(ChakraComponent):
"""Component the provides a link."""
tag = "Link"
# The rel.
rel: Var[str]
# The page to link to.
href: Var[str]
# The text to display.
text: Var[str]
# If true, the link will open in new tab.
is_external: Var[bool]
@classmethod
def create(cls, *children, **props) -> Component:
"""Create a NextJS link component, wrapping a Chakra link component.
Args:
*children: The children to pass to the component.
**props: The attributes to pass to the component.
Returns:
The component.
"""
kwargs = {}
if "href" in props:
kwargs["href"] = props.pop("href")
else:
kwargs["href"] = "#"
return NextLink.create(super().create(*children, **props), **kwargs)

View File

@ -0,0 +1,22 @@
"""Link overlay components."""
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class LinkOverlay(ChakraComponent):
"""Wraps cild componet in a link."""
tag = "LinkOverlay"
# If true, the link will open in new tab
is_external: Var[bool]
# Href of the link overlay.
href: Var[str]
class LinkBox(ChakraComponent):
"""The LinkBox lifts any nested links to the top using z-index to ensure proper keyboard navigation between links."""
tag = "LinkBox"

View File

@ -0,0 +1,22 @@
"""A link component."""
from pynecone.components.component import Component
from pynecone.var import Var
class NextLinkLib(Component):
"""A component that inherits from next/link."""
library = "next/link"
class NextLink(NextLinkLib):
"""Links are accessible elements used primarily for navigation. This component is styled to resemble a hyperlink and semantically renders an <a>."""
tag = "NextLink"
# The page to link to.
href: Var[str]
# Whether to pass the href prop to the child.
pass_href: Var[bool] = True # type: ignore

View File

@ -0,0 +1,50 @@
"""Overlay components."""
from .alertdialog import (
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
)
from .drawer import (
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
)
from .menu import (
Menu,
MenuButton,
MenuDivider,
MenuGroup,
MenuItem,
MenuItemOption,
MenuList,
MenuOptionGroup,
)
from .modal import (
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
)
from .popover import (
Popover,
PopoverAnchor,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
)
from .tooltip import Tooltip

View File

@ -0,0 +1,101 @@
"""Alert dialog components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class AlertDialog(ChakraComponent):
"""Provides context and state for the dialog."""
tag = "AlertDialog"
# If true, the modal will be open.
is_open: Var[bool]
# The least destructive element to focus when the dialog opens.
least_destructive_ref: Var[str]
# Handle zoom/pinch gestures on iOS devices when scroll locking is enabled. Defaults to false.
allow_pinch_zoom: Var[bool]
# If true, the modal will autofocus the first enabled and interactive element within the ModalContent
auto_focus: Var[bool]
# If true, scrolling will be disabled on the body when the modal opens.
block_scroll_on_mount: Var[bool]
# If true, the modal will close when the Esc key is pressed
close_on_esc: Var[bool]
# If true, the modal will close when the overlay is clicked
close_on_overlay_click: Var[bool]
# If true, the modal will be centered on screen.
is_centered: Var[bool]
# Enables aggressive focus capturing within iframes. If true, keep focus in the lock, no matter where lock is active. If false, allows focus to move outside of iframe.
lock_focus_across_frames: Var[bool]
# If true, a `padding-right` will be applied to the body element that's equal to the width of the scrollbar. This can help prevent some unpleasant flickering effect and content adjustment when the modal opens
preserve_scroll_bar_gap: Var[bool]
# If true, the modal will return focus to the element that triggered it when it closes.
return_focus_on_close: Var[bool]
# "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "full"
size: Var[str]
# If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
use_intert: Var[bool]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {
"on_close",
"on_close_complete",
"on_esc",
"on_overlay_click",
}
class AlertDialogBody(ChakraComponent):
"""Should contain the description announced by screen readers."""
tag = "AlertDialogBody"
class AlertDialogHeader(ChakraComponent):
"""Should contain the title announced by screen readers."""
tag = "AlertDialogHeader"
class AlertDialogFooter(ChakraComponent):
"""Should contain the events of the dialog."""
tag = "AlertDialogFooter"
class AlertDialogContent(ChakraComponent):
"""The wrapper for the alert dialog's content."""
tag = "AlertDialogContent"
class AlertDialogOverlay(ChakraComponent):
"""The dimmed overlay behind the dialog."""
tag = "AlertDialogOverlay"
class AlertDialogCloseButton(ChakraComponent):
"""The button that closes the dialog."""
tag = "AlertDialogCloseButton"

View File

@ -0,0 +1,107 @@
"""Container to stack elements with spacing."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Drawer(ChakraComponent):
"""A drawer component."""
tag = "Drawer"
# If true, the modal will be open.
is_open: Var[bool]
# Handle zoom/pinch gestures on iOS devices when scroll locking is enabled. Defaults to false.
allow_pinch_zoom: Var[bool]
# If true, the modal will autofocus the first enabled and interactive element within the ModalContent
auto_focus: Var[bool]
# If true, scrolling will be disabled on the body when the modal opens.
block_scroll_on_mount: Var[bool]
# If true, the modal will close when the Esc key is pressed
close_on_esc: Var[bool]
# If true, the modal will close when the overlay is clicked
close_on_overlay_click: Var[bool]
# If true, the modal will be centered on screen.
is_centered: Var[bool]
# If true and drawer's placement is top or bottom, the drawer will occupy the viewport height (100vh)
is_full_height: Var[bool]
# Enables aggressive focus capturing within iframes. - If true: keep focus in the lock, no matter where lock is active - If false: allows focus to move outside of iframe
lock_focus_across_frames: Var[bool]
# The placement of the drawer
placement: Var[str]
# If true, a `padding-right` will be applied to the body element that's equal to the width of the scrollbar. This can help prevent some unpleasant flickering effect and content adjustment when the modal opens
preserve_scroll_bar_gap: Var[bool]
# If true, the modal will return focus to the element that triggered it when it closes.
return_focus_on_close: Var[bool]
# "xs" | "sm" | "md" | "lg" | "xl" | "full"
size: Var[str]
# A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
use_intert: Var[bool]
# Variant of drawer
variant: Var[str]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {
"on_close",
"on_close_complete",
"on_esc",
"on_overlay_click",
}
class DrawerBody(ChakraComponent):
"""Drawer body."""
tag = "DrawerBody"
class DrawerHeader(ChakraComponent):
"""Drawer header."""
tag = "DrawerHeader"
class DrawerFooter(ChakraComponent):
"""Drawer footer."""
tag = "DrawerFooter"
class DrawerOverlay(Drawer):
"""Drawer overlay."""
tag = "DrawerOverlay"
class DrawerContent(Drawer):
"""Drawer content."""
tag = "DrawerContent"
class DrawerCloseButton(Drawer):
"""Drawer close button."""
tag = "DrawerCloseButton"

View File

@ -0,0 +1,160 @@
"""Menu components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Menu(ChakraComponent):
"""The wrapper component provides context, state, and focus management."""
tag = "Menu"
# The padding required to prevent the arrow from reaching the very edge of the popper.
arrow_padding: Var[int]
# If true, the first enabled menu item will receive focus and be selected when the menu opens.
auto_select: Var[bool]
# The boundary area for the popper. Used within the preventOverflow modifier
boundary: Var[str]
# If true, the menu will close when you click outside the menu list
close_on_blur: Var[bool]
# If true, the menu will close when a menu item is clicked
close_on_select: Var[bool]
# If by default the menu is open.
default_is_open: Var[bool]
# If rtl, poper placement positions will be flipped i.e. 'top-right' will become 'top-left' and vice-verse ("ltr" | "rtl")
direction: Var[str]
# If true, the popper will change its placement and flip when it's about to overflow its boundary area.
flip: Var[bool]
# The distance or margin between the reference and popper. It is used internally to create an offset modifier. NB: If you define offset prop, it'll override the gutter.
gutter: Var[int]
# Performance 🚀: If true, the MenuItem rendering will be deferred until the menu is open.
is_lazy: Var[bool]
# Performance 🚀: The lazy behavior of menu's content when not visible. Only works when `isLazy={true}` - "unmount": The menu's content is always unmounted when not open. - "keepMounted": The menu's content initially unmounted, but stays mounted when menu is open.
lazy_behavior: Var[str]
# Determines if the menu is open or not.
is_open: Var[bool]
# If true, the popper will match the width of the reference at all times. It's useful for autocomplete, `date-picker` and select patterns.
match_width: Var[bool]
# The placement of the popper relative to its reference.
placement: Var[str]
# If true, will prevent the popper from being cut off and ensure it's visible within the boundary area.
prevent_overflow: Var[bool]
# The CSS positioning strategy to use. ("fixed" | "absolute")
strategy: Var[str]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {"on_close", "on_open"}
class MenuButton(ChakraComponent):
"""The trigger for the menu list. Must be a direct child of Menu."""
tag = "MenuButton"
variant: Var[str]
as_: Var[str]
class MenuList(ChakraComponent):
"""The wrapper for the menu items. Must be a direct child of Menu."""
tag = "MenuList"
class MenuItem(Menu):
"""The trigger that handles menu selection. Must be a direct child of a MenuList."""
tag = "MenuItem"
# Overrides the parent menu's closeOnSelect prop.
close_on_select: Var[bool]
# Right-aligned label text content, useful for displaying hotkeys.
command: Var[str]
# The spacing between the command and menu item's label.
command_spacing: Var[int]
# If true, the menuitem will be disabled.
is_disabled: Var[bool]
# If true and the menuitem is disabled, it'll remain keyboard-focusable
is_focusable: Var[bool]
class MenuItemOption(Menu):
"""The checkable menu item, to be used with MenuOptionGroup."""
tag = "MenuItemOption"
# Overrides the parent menu's closeOnSelect prop.
close_on_select: Var[bool]
# Right-aligned label text content, useful for displaying hotkeys.
command: Var[str]
# The spacing between the command and menu item's label.
command_spacing: Var[int]
# Determines if menu item is checked.
is_checked: Var[bool]
# If true, the menuitem will be disabled.
is_disabled: Var[bool]
# If true and the menuitem is disabled, it'll remain keyboard-focusable
is_focusable: Var[bool]
# "checkbox" | "radio"
type_: Var[str]
# Value of the menu item.
value: Var[str]
class MenuGroup(Menu):
"""A wrapper to group related menu items."""
tag = "MenuGroup"
class MenuOptionGroup(Menu):
"""A wrapper for checkable menu items (radio and checkbox)."""
tag = "MenuOptionGroup"
# "checkbox" | "radio"
type_: Var[str]
# Value of the option group.
value: Var[str]
class MenuDivider(Menu):
"""A visual separator for menu items and groups."""
tag = "MenuDivider"

View File

@ -0,0 +1,101 @@
"""Modal components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Modal(ChakraComponent):
"""The wrapper that provides context for its children."""
tag = "Modal"
# If true, the modal will be open.
is_open: Var[bool]
# Handle zoom/pinch gestures on iOS devices when scroll locking is enabled. Defaults to false.
allow_pinch_zoom: Var[bool]
# If true, the modal will autofocus the first enabled and interactive element within the ModalContent
auto_focus: Var[bool]
# If true, scrolling will be disabled on the body when the modal opens.
block_scroll_on_mount: Var[bool]
# If true, the modal will close when the Esc key is pressed
close_on_esc: Var[bool]
# If true, the modal will close when the overlay is clicked
close_on_overlay_click: Var[bool]
# If true, the modal will be centered on screen.
is_centered: Var[bool]
# Enables aggressive focus capturing within iframes. - If true: keep focus in the lock, no matter where lock is active - If false: allows focus to move outside of iframe
lock_focus_across_frames: Var[bool]
# The transition that should be used for the modal
motion_preset: Var[str]
# If true, a `padding-right` will be applied to the body element that's equal to the width of the scrollbar. This can help prevent some unpleasant flickering effect and content adjustment when the modal opens
preserve_scroll_bar_gap: Var[bool]
# If true, the modal will return focus to the element that triggered it when it closes.
return_focus_on_close: Var[bool]
# "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "full"
size: Var[str]
# A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
use_intert: Var[bool]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {
"on_close",
"on_close_complete",
"on_esc",
"on_overlay_click",
}
class ModalOverlay(Modal):
"""The dimmed overlay behind the modal dialog."""
tag = "ModalOverlay"
class ModalHeader(ChakraComponent):
"""The header that labels the modal dialog."""
tag = "ModalHeader"
class ModalFooter(ChakraComponent):
"""The footer that houses the modal events."""
tag = "ModalFooter"
class ModalContent(Modal):
"""The container for the modal dialog's content."""
tag = "ModalContent"
class ModalBody(ChakraComponent):
"""The wrapper that houses the modal's main content."""
tag = "ModalBody"
class ModalCloseButton(Modal):
"""The button that closes the modal."""
tag = "ModalCloseButton"

View File

@ -0,0 +1,132 @@
"""Popover components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Popover(ChakraComponent):
"""The wrapper that provides props, state, and context to its children."""
tag = "Popover"
# The padding required to prevent the arrow from reaching the very edge of the popper.
arrow_padding: Var[int]
# The `box-shadow` of the popover arrow
arrow_shadow_color: Var[str]
# The size of the popover arrow
arrow_size: Var[int]
# If true, focus will be transferred to the first interactive element when the popover opens
auto_focus: Var[bool]
# The boundary area for the popper. Used within the preventOverflow modifier
boundary: Var[str]
# If true, the popover will close when you blur out it by clicking outside or tabbing out
close_on_blur: Var[bool]
# If true, the popover will close when you hit the Esc key
close_on_esc: Var[bool]
# If true, the popover will be initially opened.
default_is_open: Var[bool]
# Theme direction ltr or rtl. Popper's placement will be set accordingly
direction: Var[str]
# If true, the popper will change its placement and flip when it's about to overflow its boundary area.
flip: Var[bool]
# The distance or margin between the reference and popper. It is used internally to create an offset modifier. NB: If you define offset prop, it'll override the gutter.
gutter: Var[int]
# The html id attribute of the popover. If not provided, we generate a unique id. This id is also used to auto-generate the `aria-labelledby` and `aria-describedby` attributes that points to the PopoverHeader and PopoverBody
id_: Var[str]
# Performance 🚀: If true, the PopoverContent rendering will be deferred until the popover is open.
is_lazy: Var[bool]
# Performance 🚀: The lazy behavior of popover's content when not visible. Only works when `isLazy={true}` - "unmount": The popover's content is always unmounted when not open. - "keepMounted": The popover's content initially unmounted, but stays mounted when popover is open.
lazy_behavior: Var[str]
# If true, the popover will be opened in controlled mode.
is_open: Var[bool]
# If true, the popper will match the width of the reference at all times. It's useful for autocomplete, `date-picker` and select patterns.
match_width: Var[bool]
# The placement of the popover. It's used internally by Popper.js.
placement: Var[str]
# If true, will prevent the popper from being cut off and ensure it's visible within the boundary area.
prevent_overflow: Var[bool]
# If true, focus will be returned to the element that triggers the popover when it closes
return_focus_on_close: Var[bool]
# The CSS positioning strategy to use. ("fixed" | "absolute")
strategy: Var[str]
# The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard ("click" | "hover")
trigger: Var[str]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {"on_close", "on_open"}
class PopoverContent(Popover):
"""The popover itself."""
tag = "PopoverContent"
class PopoverHeader(ChakraComponent):
"""The header of the popover."""
tag = "PopoverHeader"
class PopoverFooter(ChakraComponent):
"""Display a popover footer."""
tag = "PopoverFooter"
class PopoverBody(ChakraComponent):
"""The body of the popover."""
tag = "PopoverBody"
class PopoverArrow(Popover):
"""A visual arrow that points to the reference (or trigger)."""
tag = "PopoverArrow"
class PopoverCloseButton(Popover):
"""A button to close the popover."""
tag = "PopoverCloseButton"
class PopoverAnchor(Popover):
"""Used to wrap the position-reference element."""
tag = "PopoverAnchor"
class PopoverTrigger(Popover):
"""Used to wrap the reference (or trigger) element."""
tag = "PopoverTrigger"

View File

@ -0,0 +1,72 @@
"""Tooltip components."""
from typing import Set
from pynecone.components.libs.chakra import ChakraComponent
from pynecone.var import Var
class Tooltip(ChakraComponent):
"""A tooltip message to appear."""
tag = "Tooltip"
# The padding required to prevent the arrow from reaching the very edge of the popper.
arrow_padding: Var[int]
# The color of the arrow shadow.
arrow_shadow_color: Var[str]
# Size of the arrow.
arrow_size: Var[int]
# Delay (in ms) before hiding the tooltip
delay: Var[int]
# If true, the tooltip will hide on click
close_on_click: Var[bool]
# If true, the tooltip will hide on pressing Esc key
close_on_esc: Var[bool]
# If true, the tooltip will hide while the mouse is down
close_on_mouse_down: Var[bool]
# If true, the tooltip will be initially shown
default_is_open: Var[bool]
# Theme direction ltr or rtl. Popper's placement will be set accordingly
direction: Var[str]
# The distance or margin between the reference and popper. It is used internally to create an offset modifier. NB: If you define offset prop, it'll override the gutter.
gutter: Var[int]
# If true, the tooltip will show an arrow tip
has_arrow: Var[bool]
# If true, th etooltip with be disabled.
is_disabled: Var[bool]
# If true, the tooltip will be open.
is_open: Var[bool]
# The label of the tooltip
label: Var[str]
# Delay (in ms) before showing the tooltip
open_delay: Var[int]
# The placement of the popper relative to its reference.
placement: Var[str]
# If true, the tooltip will wrap its children in a `<span/>` with `tabIndex=0`
should_wrap_children: Var[bool]
@classmethod
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component.
Returns:
The event triggers.
"""
return super().get_triggers() | {"on_close", "on_open"}

View File

@ -0,0 +1,5 @@
"""Representations for React tags."""
from .cond_tag import CondTag
from .iter_tag import IterTag
from .tag import Tag

View File

@ -0,0 +1,35 @@
"""Tag to conditionally render components."""
from pynecone import utils
from pynecone.components.tags.tag import Tag
from pynecone.var import Var
class CondTag(Tag):
"""A conditional tag."""
# The condition to determine which component to render.
cond: Var[bool]
# The code to render if the condition is true.
true_value: str
# The code to render if the condition is false.
false_value: str
# Whether the cond tag is nested.
is_nested: bool = False
def __str__(self) -> str:
"""Render the tag as a React string.
Returns:
The React code to render the tag.
"""
assert self.cond is not None, "The condition must be set."
return utils.format_cond(
cond=self.cond.full_name,
true_value=self.true_value,
false_value=self.false_value,
is_nested=self.is_nested,
)

View File

@ -0,0 +1,90 @@
"""Tag to loop through a list of components."""
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING, Any, Callable, List
from pynecone import utils
from pynecone.components.tags.tag import Tag
from pynecone.var import BaseVar, Var
if TYPE_CHECKING:
from pynecone.components.component import Component
INDEX_VAR = "i"
class IterTag(Tag):
"""An iterator tag."""
# The var to iterate over.
iterable: Var[List]
# The component render function for each item in the iterable.
render_fn: Callable
@staticmethod
def get_index_var() -> Var:
"""Get the index var for the tag.
Returns:
The index var.
"""
index = Var.create(INDEX_VAR, is_local=False)
assert index is not None
return index
@staticmethod
def get_index_var_arg() -> Var:
"""Get the index var for the tag.
Returns:
The index var.
"""
arg = Var.create(INDEX_VAR)
assert arg is not None
return arg
@staticmethod
def render_component(render_fn: Callable, arg: Var) -> Component:
"""Render the component.
Args:
render_fn: The render function.
arg: The argument to pass to the render function.
Returns:
The rendered component.
"""
args = inspect.getfullargspec(render_fn).args
index = IterTag.get_index_var()
if len(args) == 1:
component = render_fn(arg)
else:
assert len(args) == 2
component = render_fn(arg, index)
if component.key is None:
component.key = utils.wrap(str(index), "{", check_first=False)
return component
def __str__(self) -> str:
"""Render the tag as a React string.
Returns:
The React code to render the tag.
"""
try:
type_ = self.iterable.type_.__args__[0]
except:
type_ = Any
arg = BaseVar(
name=utils.get_unique_variable_name(),
type_=type_,
)
index_arg = self.get_index_var_arg()
component = self.render_component(self.render_fn, arg)
return utils.wrap(
f"{self.iterable.full_name}.map(({arg.name}, {index_arg}) => {component})",
"{",
)

View File

@ -0,0 +1,196 @@
"""A React tag."""
from __future__ import annotations
import json
import os
import re
from typing import Any, Dict, Optional, Union
import pydantic
from plotly.graph_objects import Figure
from plotly.io import to_json
from pynecone import utils
from pynecone.base import Base
from pynecone.event import EventChain
from pynecone.var import Var
class Tag(Base):
"""A React tag."""
# The name of the tag.
name: str = ""
# The attributes of the tag.
attrs: Dict[str, Any] = {}
# The inner contents of the tag.
contents: str = ""
def __init__(self, *args, **kwargs):
"""Initialize the tag.
Args:
*args: Args to initialize the tag.
**kwargs: Kwargs to initialize the tag.
"""
# Convert any attrs to properties.
if "attrs" in kwargs:
kwargs["attrs"] = {
name: Var.create(value) for name, value in kwargs["attrs"].items()
}
super().__init__(*args, **kwargs)
@staticmethod
def format_attr_value(
value: Union[Var, EventChain, Dict[str, Var], str],
) -> Union[int, float, str]:
"""Format an attribute value.
Args:
value: The value of the attribute
Returns:
The formatted value to display within the tag.
"""
def format_fn(value):
args = ",".join([":".join((name, val)) for name, val in value.args])
return f"E(\"{utils.to_snake_case(value.handler.fn.__qualname__)}\", {utils.wrap(args, '{')})"
# Handle var attributes.
if isinstance(value, Var):
if not value.is_local or value.is_string:
return str(value)
if issubclass(value.type_, str):
value = json.dumps(value.full_name)
value = re.sub('"{', "", value)
value = re.sub('}"', "", value)
value = re.sub('"`', '{"', value)
value = re.sub('`"', '"}', value)
return value
value = value.full_name
# Handle events.
elif isinstance(value, EventChain):
local_args = ",".join(value.events[0].local_args)
fns = ",".join([format_fn(event) for event in value.events])
value = f"({local_args}) => Event([{fns}])"
# Handle other types.
elif isinstance(value, str):
if utils.is_wrapped(value, "{"):
return value
return json.dumps(value)
elif isinstance(value, Figure):
value = json.loads(to_json(value))["data"]
# For dictionaries, convert any properties to strings.
else:
if isinstance(value, dict):
value = json.dumps(
{
key: str(val) if isinstance(val, Var) else val
for key, val in value.items()
}
)
else:
value = json.dumps(value)
value = re.sub('"{', "", value)
value = re.sub('}"', "", value)
value = re.sub('"`', '{"', value)
value = re.sub('`"', '"}', value)
# Wrap the variable in braces.
assert isinstance(value, str), "The value must be a string."
return utils.wrap(value, "{", check_first=False)
def format_attrs(self) -> str:
"""Format a dictionary of attributes.
Returns:
The formatted attributes.
"""
# If there are no attributes, return an empty string.
if len(self.attrs) == 0:
return ""
# Get the string representation of all the attributes joined.
# We need a space at the beginning for formatting.
return os.linesep.join(
f"{name}={self.format_attr_value(value)}"
for name, value in self.attrs.items()
if value is not None
)
def __str__(self) -> str:
"""Render the tag as a React string.
Returns:
The React code to render the tag.
"""
# Get the tag attributes.
attrs_str = self.format_attrs()
if len(attrs_str) > 0:
attrs_str = " " + attrs_str
if len(self.contents) == 0:
# If there is no inner content, we don't need a closing tag.
tag_str = utils.wrap(f"{self.name}{attrs_str}/", "<")
else:
# Otherwise wrap it in opening and closing tags.
open = utils.wrap(f"{self.name}{attrs_str}", "<")
close = utils.wrap(f"/{self.name}", "<")
tag_str = utils.wrap(self.contents, open, close)
return tag_str
def add_attrs(self, **kwargs: Optional[Any]) -> Tag:
"""Add attributes to the tag.
Args:
**kwargs: The attributes to add.
Returns:
The tag with the attributes added.
"""
self.attrs.update(
{
utils.to_camel_case(name): attr
if utils._isinstance(attr, Union[EventChain, dict])
else Var.create(attr)
for name, attr in kwargs.items()
if self.is_valid_attr(attr)
}
)
return self
def remove_attrs(self, *args: str) -> Tag:
"""Remove attributes from the tag.
Args:
*args: The attributes to remove.
Returns:
The tag with the attributes removed.
"""
for name in args:
if name in self.attrs:
del self.attrs[name]
return self
@staticmethod
def is_valid_attr(attr: Optional[Var]) -> bool:
"""Check if the attr is valid.
Args:
attr: The value to check.
Returns:
Whether the value is valid.
"""
return attr is not None and not (isinstance(attr, dict) and len(attr) == 0)

View File

@ -0,0 +1,22 @@
"""A tag with no tag."""
from pynecone import utils
from pynecone.components.tags import Tag
class Tagless(Tag):
"""A tag with no tag."""
def __str__(self) -> str:
"""Return the string representation of the tag.
Returns:
The string representation of the tag.
"""
out = self.contents
space = utils.wrap(" ", "{")
if len(self.contents) > 0 and self.contents[0] == " ":
out = space + out
if len(self.contents) > 0 and self.contents[-1] == " ":
out = out + space
return out

Some files were not shown because too many files have changed in this diff Show More