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"] +}