diff --git a/demo/.eslintrc.cjs b/.eslintrc.cjs
similarity index 91%
rename from demo/.eslintrc.cjs
rename to .eslintrc.cjs
index ae920678..91bb695a 100644
--- a/demo/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -6,8 +6,9 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
+ 'prettier',
],
- ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts'],
+ ignorePatterns: ['dist', '.eslintrc.cjs', 'demo/vite.config.ts'],
parser: '@typescript-eslint/parser',
plugins: ['react', '@typescript-eslint', 'react-refresh', 'simple-import-sort'],
rules: {
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..fc8f273d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,79 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - '**'
+ pull_request:
+ types: [opened, synchronize]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - run: pip install -r python/requirements/all.txt
+
+ - run: npm install
+
+ - uses: pre-commit/action@v3.0.0
+ with:
+ extra_args: --all-files
+ env:
+ SKIP: no-commit-to-branch
+
+ check: # This job does nothing and is only used for the branch protection
+ if: always()
+ needs: [lint]
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Decide whether the needed jobs succeeded or failed
+ uses: re-actors/alls-green@release/v1
+ id: all-green
+ with:
+ jobs: ${{ toJSON(needs) }}
+
+ release:
+ needs: [check]
+ if: "success() && startsWith(github.ref, 'refs/tags/')"
+ runs-on: ubuntu-latest
+ environment: release
+
+ permissions:
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - name: install
+ run: pip install -U build
+
+ - name: check version
+ id: check-version
+ uses: samuelcolvin/check-python-version@v4.1
+ with:
+ version_file_path: 'python/fastui/__init__.py'
+
+ - name: build
+ run: python -m build
+
+ - name: Upload package to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitignore b/.gitignore
index 0250f827..d59a676b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ dist-ssr
__pycache__/
/.logfire/
+/frontend-dist/
+/scratch/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..dcedbf43
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,41 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.3.0
+ hooks:
+ - id: no-commit-to-branch
+ - id: check-yaml
+ - id: check-toml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+
+ - repo: local
+ hooks:
+ - id: python-format
+ name: python-format
+ types_or: [python]
+ entry: make format
+ language: system
+ pass_filenames: false
+ - id: python-typecheck
+ name: python-typecheck
+ types_or: [python]
+ entry: make typecheck
+ language: system
+ pass_filenames: false
+ - id: react-prettier
+ name: react-prettier
+ types_or: [javascript, jsx, ts, tsx, css, json, markdown]
+ entry: npm run prettier
+ language: system
+ - id: react-lint
+ name: react-lint
+ types_or: [ts, tsx]
+ entry: npm run lint-fix
+ language: system
+ pass_filenames: false
+ - id: react-typecheck
+ name: react-typecheck
+ types_or: [ts, tsx]
+ entry: npm run typecheck
+ language: system
+ pass_filenames: false
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..286f4f19
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 to present Samuel Colvin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..c51da711
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+.DEFAULT_GOAL:=all
+paths = python
+
+.PHONY: install
+install:
+ pip install -U pip pre-commit pip-tools
+ pip install -r python/requirements/all.txt
+ pre-commit install
+
+.PHONY: update-lockfiles
+update-lockfiles:
+ @echo "Updating requirements files using pip-compile"
+ pip-compile -q --strip-extras -o python/requirements/lint.txt python/requirements/lint.in
+ pip-compile -q --strip-extras -o python/requirements/pyproject.txt pyproject.toml
+ pip install --dry-run -r python/requirements/all.txt
+
+.PHONY: format
+format:
+ ruff check --fix-only $(paths)
+ ruff format $(paths)
+
+.PHONY: lint
+lint:
+ ruff check $(paths)
+ ruff format --check $(paths)
+
+.PHONY: typecheck
+typecheck:
+ pyright python/fastui
+
+.PHONY: test
+test:
+ coverage run -m pytest tests
+
+.PHONY: testcov
+testcov: test
+ coverage html
+
+.PHONY: dev
+dev:
+ uvicorn demo.server:app --reload
+
+.PHONY: all
+all: testcov lint
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..7efc2f70
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# FastUI
+
+[![CI](https://github.com/samuelcolvin/FastUI/workflows/CI/badge.svg?event=push)](https://github.com/samuelcolvin/FastUI/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
+[![pypi](https://img.shields.io/pypi/v/fastui.svg)](https://pypi.python.org/pypi/fastui)
+[![versions](https://img.shields.io/pypi/pyversions/fastui.svg)](https://github.com/samuelcolvin/FastUI)
+[![license](https://img.shields.io/github/license/samuelcolvin/FastUI.svg)](https://github.com/samuelcolvin/FastUI/blob/main/LICENSE)
+
+WIP
diff --git a/demo/server/main.py b/demo/server.py
similarity index 80%
rename from demo/server/main.py
rename to demo/server.py
index 287ba121..adc2595f 100644
--- a/demo/server/main.py
+++ b/demo/server.py
@@ -3,20 +3,15 @@
from datetime import date
from fastapi import FastAPI
-from pydantic import RootModel, BaseModel, Field
+from pydantic import BaseModel, Field
-import components as c
-from components import AnyComponent
-from components.events import PageEvent, GoToEvent
+from fastui import components as c
+from fastui import FastUI, PageEvent, GoToEvent, Display, AnyComponent
app = FastAPI()
-class FastUi(RootModel):
- root: AnyComponent
-
-
-@app.get('/api/', response_model=FastUi, response_model_exclude_none=True)
+@app.get('/api/', response_model=FastUI, response_model_exclude_none=True)
def read_root() -> AnyComponent:
return c.Page(
children=[
@@ -44,7 +39,7 @@ class MyTableRow(BaseModel):
enabled: bool | None = None
-@app.get('/api/table', response_model=FastUi, response_model_exclude_none=True)
+@app.get('/api/table', response_model=FastUI, response_model_exclude_none=True)
def read_foo() -> AnyComponent:
return c.Page(
children=[
@@ -57,7 +52,7 @@ def read_foo() -> AnyComponent:
],
columns=[
c.Column(field='name', on_click=GoToEvent(url='/api/more/{id}/')),
- c.Column(field='dob', display=c.Display.date),
+ c.Column(field='dob', display=Display.date),
c.Column(field='enabled'),
]
)
diff --git a/demo/src/App.tsx b/demo/src/App.tsx
index b306fbf0..fc1d584a 100644
--- a/demo/src/App.tsx
+++ b/demo/src/App.tsx
@@ -1,6 +1,6 @@
-import { FastUI, ClassNameGenerator, CustomRender } from './FastUI'
+import { FastUI, ClassNameGenerator, CustomRender } from 'fastui'
-export default function App () {
+export default function App() {
return (
diff --git a/demo/src/main.tsx b/demo/src/main.tsx
index 76528bce..0a75e0ae 100644
--- a/demo/src/main.tsx
+++ b/demo/src/main.tsx
@@ -7,5 +7,5 @@ import './main.scss'
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+ ,
)
diff --git a/demo/tsconfig.json b/demo/tsconfig.json
index d62f6758..170a281f 100644
--- a/demo/tsconfig.json
+++ b/demo/tsconfig.json
@@ -18,7 +18,10 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+ "paths": {
+ "fastui": ["../react/fastui"]
+ }
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/demo/vite.config.ts b/demo/vite.config.ts
index cac14eee..da1827c0 100644
--- a/demo/vite.config.ts
+++ b/demo/vite.config.ts
@@ -17,6 +17,7 @@ export default () => {
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
+ fastui: path.resolve(__dirname, '../react/fastui'),
},
},
server: serverConfig,
diff --git a/demo/package-lock.json b/package-lock.json
similarity index 99%
rename from demo/package-lock.json
rename to package-lock.json
index c40a8ba8..e0a9d386 100644
--- a/demo/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "fastui-demo",
+ "name": "fastui",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "fastui-demo",
+ "name": "fastui",
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
@@ -26,7 +26,6 @@
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^3.0.3",
- "sass": "^1.67.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
@@ -1054,6 +1053,8 @@
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -1231,6 +1232,8 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
+ "optional": true,
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -1349,6 +1352,8 @@
"url": "https://paulmillr.com/funding/"
}
],
+ "optional": true,
+ "peer": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -1370,6 +1375,8 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2538,7 +2545,9 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
- "dev": true
+ "dev": true,
+ "optional": true,
+ "peer": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -2641,6 +2650,8 @@
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -3170,6 +3181,8 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
+ "optional": true,
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3547,6 +3560,8 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -3729,6 +3744,8 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz",
"integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
diff --git a/demo/package.json b/package.json
similarity index 75%
rename from demo/package.json
rename to package.json
index e13fc338..cf779fce 100644
--- a/demo/package.json
+++ b/package.json
@@ -1,16 +1,18 @@
{
- "name": "fastui-demo",
+ "name": "fastui",
"private": true,
"version": "0.0.0",
"type": "module",
+ "entry": "src/index.tsx",
"scripts": {
- "dev": "vite",
+ "dev": "vite demo",
"typecheck": "tsc --noEmit",
+ "build": "tsc",
"typewatch": "tsc --noEmit --watch",
- "build": "tsc && vite build",
- "lint": "eslint . --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
- "format": "prettier . --write -- '**/*.{ts,tsx,js,css,json,md}' && npm run lint -- --fix",
- "preview": "vite preview"
+ "lint": "eslint react --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
+ "lint-fix": "npm run lint -- --fix",
+ "prettier": "prettier --write",
+ "format": "npm run prettier -- . && npm run lint-fix"
},
"prettier": {
"singleQuote": true,
@@ -39,7 +41,6 @@
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^3.0.3",
- "sass": "^1.67.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..ee84bed1
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,50 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.sdist]
+include = ["python"]
+
+[tool.hatch.build.targets.wheel]
+packages = ["python/fastui"]
+
+[tool.hatch.version]
+path = "python/fastui/__init__.py"
+
+[project]
+name = "fastui"
+description = "Build UIs fast."
+authors = [{ name = "Samuel Colvin", email = "s@muelcolvin.com" }]
+license = "MIT"
+readme = "README.md"
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Topic :: Internet",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ 'Programming Language :: Python :: 3 :: Only',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ "Intended Audience :: Developers",
+ "Intended Audience :: Information Technology",
+]
+requires-python = ">=3.8"
+dependencies = [
+ "pydantic>=2.5.0b1",
+ "fastapi>=0.104.0",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://github.com/samuelcolvin/FastUI"
+
+
+[tool.ruff]
+line-length = 120
+extend-select = ["Q", "RUF100", "UP", "I"]
+flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"}
+format.quote-style="single"
+target-version = "py38"
diff --git a/python/fastui/__init__.py b/python/fastui/__init__.py
new file mode 100644
index 00000000..210b34d4
--- /dev/null
+++ b/python/fastui/__init__.py
@@ -0,0 +1,9 @@
+__version__ = '0.0.1'
+
+import pydantic
+
+from .components import AnyComponent
+
+
+class FastUI(pydantic.RootModel):
+ root: AnyComponent
diff --git a/demo/server/components/__init__.py b/python/fastui/components/__init__.py
similarity index 94%
rename from demo/server/components/__init__.py
rename to python/fastui/components/__init__.py
index 122cf0f8..37b9790b 100644
--- a/demo/server/components/__init__.py
+++ b/python/fastui/components/__init__.py
@@ -8,10 +8,12 @@
from __future__ import annotations as _annotations
import typing
+
import pydantic
-from . import extra, events
-from .table import Table, Column, Display
+from .. import events
+from . import extra
+from .table import Table
if typing.TYPE_CHECKING:
import pydantic.fields
@@ -32,6 +34,7 @@ class Page(pydantic.BaseModel):
"""
Similar to `container` in many UI frameworks, this should be a reasonable root component for most pages.
"""
+
children: list[AnyComponent]
class_name: extra.ClassName | None = None
type: typing.Literal['Page'] = 'Page'
@@ -77,6 +80,5 @@ class Modal(pydantic.BaseModel):
AnyComponent = typing.Annotated[
- Text | Div | Page | Heading | Row | Col | Button | Modal | Table,
- pydantic.Field(discriminator='type')
+ Text | Div | Page | Heading | Row | Col | Button | Modal | Table, pydantic.Field(discriminator='type')
]
diff --git a/demo/server/components/extra.py b/python/fastui/components/extra.py
similarity index 99%
rename from demo/server/components/extra.py
rename to python/fastui/components/extra.py
index 693b4a60..cf27a1e9 100644
--- a/demo/server/components/extra.py
+++ b/python/fastui/components/extra.py
@@ -1,4 +1,5 @@
from typing import Annotated
+
from pydantic import Field
ClassName = Annotated[str | list[str] | dict[str, bool | None], Field(serialization_alias='className')]
diff --git a/demo/server/components/table.py b/python/fastui/components/table.py
similarity index 74%
rename from demo/server/components/table.py
rename to python/fastui/components/table.py
index 22758d17..bbb38b5a 100644
--- a/demo/server/components/table.py
+++ b/python/fastui/components/table.py
@@ -1,39 +1,26 @@
from __future__ import annotations as _annotations
import typing
-from enum import StrEnum
import pydantic
-from . import extra, events
+from .. import events
+from ..display import Display
+from . import extra
# TODO allow dataclasses and dicts here too
DataModel = typing.TypeVar('DataModel', bound=pydantic.BaseModel)
-class Display(StrEnum):
- """
- How to a value.
- """
- auto = 'auto' # default, same as None below
- plain = 'plain'
- datetime = 'datetime'
- date = 'date'
- duration = 'duration'
- as_title = 'as_title'
- markdown = 'markdown'
- json = 'json'
- inline_code = 'inline_code'
-
-
class Column(pydantic.BaseModel):
"""
Description of a table column.
"""
+
field: str
display: Display | None = None
title: str | None = None
- on_click: events.Event | None = pydantic.Field(None, serialization_alias='onClick')
+ on_click: typing.Annotated[events.Event | None, pydantic.Field(serialization_alias='onClick')] = None
class_name: extra.ClassName | None = None
diff --git a/python/fastui/display.py b/python/fastui/display.py
new file mode 100644
index 00000000..ff42fdf5
--- /dev/null
+++ b/python/fastui/display.py
@@ -0,0 +1,17 @@
+from enum import StrEnum
+
+
+class Display(StrEnum):
+ """
+ How to a value.
+ """
+
+ auto = 'auto' # default, same as None below
+ plain = 'plain'
+ datetime = 'datetime'
+ date = 'date'
+ duration = 'duration'
+ as_title = 'as_title'
+ markdown = 'markdown'
+ json = 'json'
+ inline_code = 'inline_code'
diff --git a/demo/server/components/events.py b/python/fastui/events.py
similarity index 88%
rename from demo/server/components/events.py
rename to python/fastui/events.py
index 2efd6fc2..82cf5335 100644
--- a/demo/server/components/events.py
+++ b/python/fastui/events.py
@@ -1,5 +1,6 @@
from typing import Annotated, Literal
-from pydantic import Field, BaseModel
+
+from pydantic import BaseModel, Field
class PageEvent(BaseModel):
diff --git a/python/requirements/all.txt b/python/requirements/all.txt
new file mode 100644
index 00000000..baff7eec
--- /dev/null
+++ b/python/requirements/all.txt
@@ -0,0 +1,3 @@
+-r ./lint.txt
+-r ./pyproject.txt
+uvicorn[standard]
diff --git a/python/requirements/lint.in b/python/requirements/lint.in
new file mode 100644
index 00000000..5d158db7
--- /dev/null
+++ b/python/requirements/lint.in
@@ -0,0 +1,2 @@
+ruff
+pyright
diff --git a/python/requirements/lint.txt b/python/requirements/lint.txt
new file mode 100644
index 00000000..46f44f48
--- /dev/null
+++ b/python/requirements/lint.txt
@@ -0,0 +1,15 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --output-file=python/requirements/lint.txt --strip-extras python/requirements/lint.in
+#
+nodeenv==1.8.0
+ # via pyright
+pyright==1.1.335
+ # via -r python/requirements/lint.in
+ruff==0.1.5
+ # via -r python/requirements/lint.in
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/python/requirements/pyproject.txt b/python/requirements/pyproject.txt
new file mode 100644
index 00000000..ab59d0b7
--- /dev/null
+++ b/python/requirements/pyproject.txt
@@ -0,0 +1,31 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --output-file=python/requirements/pyproject.txt --strip-extras pyproject.toml
+#
+annotated-types==0.6.0
+ # via pydantic
+anyio==3.7.1
+ # via
+ # fastapi
+ # starlette
+fastapi==0.104.1
+ # via fastui (pyproject.toml)
+idna==3.4
+ # via anyio
+pydantic==2.5.0b1
+ # via
+ # fastapi
+ # fastui (pyproject.toml)
+pydantic-core==2.14.1
+ # via pydantic
+sniffio==1.3.0
+ # via anyio
+starlette==0.27.0
+ # via fastapi
+typing-extensions==4.8.0
+ # via
+ # fastapi
+ # pydantic
+ # pydantic-core
diff --git a/demo/src/FastUI/DefaultLoading.tsx b/react/fastui/DefaultLoading.tsx
similarity index 100%
rename from demo/src/FastUI/DefaultLoading.tsx
rename to react/fastui/DefaultLoading.tsx
diff --git a/demo/src/FastUI/components/FormField.tsx b/react/fastui/components/FormField.tsx
similarity index 100%
rename from demo/src/FastUI/components/FormField.tsx
rename to react/fastui/components/FormField.tsx
diff --git a/demo/src/FastUI/components/Json.tsx b/react/fastui/components/Json.tsx
similarity index 100%
rename from demo/src/FastUI/components/Json.tsx
rename to react/fastui/components/Json.tsx
diff --git a/demo/src/FastUI/components/button.tsx b/react/fastui/components/button.tsx
similarity index 100%
rename from demo/src/FastUI/components/button.tsx
rename to react/fastui/components/button.tsx
diff --git a/demo/src/FastUI/components/display.tsx b/react/fastui/components/display.tsx
similarity index 100%
rename from demo/src/FastUI/components/display.tsx
rename to react/fastui/components/display.tsx
diff --git a/demo/src/FastUI/components/div.tsx b/react/fastui/components/div.tsx
similarity index 100%
rename from demo/src/FastUI/components/div.tsx
rename to react/fastui/components/div.tsx
diff --git a/demo/src/FastUI/components/heading.tsx b/react/fastui/components/heading.tsx
similarity index 91%
rename from demo/src/FastUI/components/heading.tsx
rename to react/fastui/components/heading.tsx
index 5473fe98..cc68a1e5 100644
--- a/demo/src/FastUI/components/heading.tsx
+++ b/react/fastui/components/heading.tsx
@@ -15,7 +15,7 @@ export const HeadingComp: FC
= (props) => {
return
}
-function getComponent (level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
+function getComponent(level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
switch (level) {
case 1:
return ({ text, className }) => {text}
diff --git a/demo/src/FastUI/components/index.tsx b/react/fastui/components/index.tsx
similarity index 97%
rename from demo/src/FastUI/components/index.tsx
rename to react/fastui/components/index.tsx
index 83d5be15..104ac952 100644
--- a/demo/src/FastUI/components/index.tsx
+++ b/react/fastui/components/index.tsx
@@ -81,7 +81,7 @@ interface WithChildren {
children: FastProps[]
}
-function renderWithChildren (Component: FC, props: T) {
+function renderWithChildren(Component: FC, props: T) {
const { children, ...rest } = props
// TODO is there a way to make this type safe?
return {children}
diff --git a/demo/src/FastUI/components/link.tsx b/react/fastui/components/link.tsx
similarity index 100%
rename from demo/src/FastUI/components/link.tsx
rename to react/fastui/components/link.tsx
diff --git a/demo/src/FastUI/components/modal.css b/react/fastui/components/modal.css
similarity index 100%
rename from demo/src/FastUI/components/modal.css
rename to react/fastui/components/modal.css
diff --git a/demo/src/FastUI/components/modal.tsx b/react/fastui/components/modal.tsx
similarity index 100%
rename from demo/src/FastUI/components/modal.tsx
rename to react/fastui/components/modal.tsx
diff --git a/demo/src/FastUI/components/table.tsx b/react/fastui/components/table.tsx
similarity index 100%
rename from demo/src/FastUI/components/table.tsx
rename to react/fastui/components/table.tsx
diff --git a/demo/src/FastUI/components/text.tsx b/react/fastui/components/text.tsx
similarity index 100%
rename from demo/src/FastUI/components/text.tsx
rename to react/fastui/components/text.tsx
diff --git a/demo/src/FastUI/controller.tsx b/react/fastui/controller.tsx
similarity index 97%
rename from demo/src/FastUI/controller.tsx
rename to react/fastui/controller.tsx
index 98760a15..30ac3740 100644
--- a/demo/src/FastUI/controller.tsx
+++ b/react/fastui/controller.tsx
@@ -68,7 +68,7 @@ const request = async ({ url, method, headers, body }: Request): Promise(null)
const { fullPath } = useContext(LocationContext)
diff --git a/demo/src/FastUI/display.ts b/react/fastui/display.ts
similarity index 100%
rename from demo/src/FastUI/display.ts
rename to react/fastui/display.ts
diff --git a/demo/src/FastUI/hooks/className.ts b/react/fastui/hooks/className.ts
similarity index 89%
rename from demo/src/FastUI/hooks/className.ts
rename to react/fastui/hooks/className.ts
index a24d7eed..49a76513 100644
--- a/demo/src/FastUI/hooks/className.ts
+++ b/react/fastui/hooks/className.ts
@@ -14,7 +14,7 @@ export const ClassNameContext = createContext(null)
* @param props The full props object sent from the backend, this is passed to the class name generator.
* @param dft default className to use if the class name generator is not set or returns undefined.
*/
-export function useClassNameGenerator (classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
+export function useClassNameGenerator(classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
const classNameGenerator = useContext(ClassNameContext)
if (combineClassNameProp(classNameProp)) {
if (!dft && classNameGenerator) {
@@ -33,7 +33,7 @@ export function useClassNameGenerator (classNameProp: ClassName, props: FastProp
* then we generate the default className and append the user's className to it.
* @param classNameProp
*/
-function combineClassNameProp (classNameProp: ClassName): boolean {
+function combineClassNameProp(classNameProp: ClassName): boolean {
if (Array.isArray(classNameProp)) {
// classNameProp is an array, check if it contains `+`
return classNameProp.some((c) => c === '+')
@@ -49,7 +49,7 @@ function combineClassNameProp (classNameProp: ClassName): boolean {
}
}
-function combine (cn1: ClassName, cn2: ClassName): string {
+function combine(cn1: ClassName, cn2: ClassName): string {
if (!cn1) {
return renderClassName(cn2)
} else if (!cn2) {
@@ -63,7 +63,7 @@ function combine (cn1: ClassName, cn2: ClassName): string {
* Renders the className to a string, removing plus signs.
* @param className
*/
-export function renderClassName (className: ClassName): string {
+export function renderClassName(className: ClassName): string {
if (typeof className === 'string') {
return className.replace(/^\+ /, '')
} else if (Array.isArray(className)) {
diff --git a/demo/src/FastUI/hooks/customRender.ts b/react/fastui/hooks/customRender.ts
similarity index 100%
rename from demo/src/FastUI/hooks/customRender.ts
rename to react/fastui/hooks/customRender.ts
diff --git a/demo/src/FastUI/hooks/error.tsx b/react/fastui/hooks/error.tsx
similarity index 96%
rename from demo/src/FastUI/hooks/error.tsx
rename to react/fastui/hooks/error.tsx
index 97a345bb..32c1fc59 100644
--- a/demo/src/FastUI/hooks/error.tsx
+++ b/react/fastui/hooks/error.tsx
@@ -30,7 +30,7 @@ const DefaultErrorDisplay: ErrorDisplayType = ({ title, description, children })
export const ErrorContext = createContext({
error: null,
setError: () => null,
- DisplayError: DefaultErrorDisplay
+ DisplayError: DefaultErrorDisplay,
})
const MaybeError: FC<{ children: ReactNode }> = ({ children }) => {
@@ -55,7 +55,7 @@ export const ErrorContextProvider: FC = ({ DisplayError, children }) => {
console.warn('setting error:', error)
setErrorState(error)
},
- [setErrorState]
+ [setErrorState],
)
const contextValue: ErrorContextType = { error, setError, DisplayError: DisplayError ?? DefaultErrorDisplay }
diff --git a/demo/src/FastUI/hooks/event.ts b/react/fastui/hooks/event.ts
similarity index 77%
rename from demo/src/FastUI/hooks/event.ts
rename to react/fastui/hooks/event.ts
index 3f83d785..2209ae84 100644
--- a/demo/src/FastUI/hooks/event.ts
+++ b/react/fastui/hooks/event.ts
@@ -12,14 +12,14 @@ export interface GoToEvent {
url: string
}
-function pageEventType (event: PageEvent): string {
+function pageEventType(event: PageEvent): string {
return `fastui:${event.name}`
}
-export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
+export function useFireEvent(): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
const location = useContext(LocationContext)
- function fireEvent (event?: PageEvent | GoToEvent) {
+ function fireEvent(event?: PageEvent | GoToEvent) {
if (!event) {
return
}
@@ -38,7 +38,7 @@ export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) =>
return { fireEvent }
}
-export function useEventListenerToggle (event?: PageEvent, initialState = false): [boolean, () => void] {
+export function useEventListenerToggle(event?: PageEvent, initialState = false): [boolean, () => void] {
const [state, setState] = useState(initialState)
const toggle = useCallback(() => setState((state) => !state), [])
diff --git a/demo/src/FastUI/hooks/locationContext.tsx b/react/fastui/hooks/locationContext.tsx
similarity index 92%
rename from demo/src/FastUI/hooks/locationContext.tsx
rename to react/fastui/hooks/locationContext.tsx
index 8e81c45a..bc60c354 100644
--- a/demo/src/FastUI/hooks/locationContext.tsx
+++ b/react/fastui/hooks/locationContext.tsx
@@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useState, useCallback, useContext
import { ErrorContext } from './error'
-function parseLocation (): string {
+function parseLocation(): string {
const { href, origin } = window.location
// remove origin from the beginning of href
return href.slice(origin.length)
@@ -17,12 +17,12 @@ const initialPath = parseLocation()
const initialState = {
fullPath: initialPath,
- goto: () => null
+ goto: () => null,
}
export const LocationContext = createContext(initialState)
-export function LocationProvider ({ children }: { children: ReactNode }) {
+export function LocationProvider({ children }: { children: ReactNode }) {
const [fullPath, setFullPath] = useState(initialPath)
const { setError } = useContext(ErrorContext)
@@ -66,8 +66,8 @@ export function LocationProvider ({ children }: { children: ReactNode }) {
setError(null)
setFullPath(newPath)
},
- [setError]
- )
+ [setError],
+ ),
}
return {children}
diff --git a/demo/src/FastUI/index.tsx b/react/fastui/index.tsx
similarity index 96%
rename from demo/src/FastUI/index.tsx
rename to react/fastui/index.tsx
index 2cdaeb43..d5f2784c 100644
--- a/demo/src/FastUI/index.tsx
+++ b/react/fastui/index.tsx
@@ -20,7 +20,7 @@ export interface FastUIProps {
customRender?: CustomRender
}
-export function FastUI (props: FastUIProps) {
+export function FastUI(props: FastUIProps) {
const { classNameGenerator, DisplayError, customRender, ...rest } = props
return (
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..daece3bd
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "outDir": "./react-dist",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": false,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["react"]
+}