diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76d5515 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Nix +result +result-* +.direnv/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv/ +pip-log.txt +pip-delete-this-directory.txt +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +*.egg-info/ +dist/ +build/ +*.egg + +# SQLite databases +*.db +*.db-journal +*.db-shm +*.db-wal + +# Local data directory +.animaltrack/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Local configuration +.env +*.local +.sesskey diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..49b0cce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,95 @@ +# AnimalTrack Project Instructions + +## Project Status +Check `PLAN.md` for current progress. Each step has a checkbox to track completion. + +## Critical Workflow Rules + +### 1. Commit Frequently +**ALWAYS commit after completing any step.** Don't let changes accumulate. + +### 2. Update Plan +After completing work, update `PLAN.md`: +- Check off completed items with `[x]` +- Add commit hash if significant + +### 3. Run Tests +```bash +pytest tests/ -v +``` + +## Framework Documentation + +**ALWAYS consult docs before implementing UI.** Use vendored documentation. + +| Framework | Main Context | Examples | +|-----------|-------------|----------| +| FastHTML | `docs/vendor/fasthtml/llms-ctx.txt` | API refs in `docs/vendor/fasthtml/` | +| MonsterUI | `docs/vendor/monsterui/llms.txt` | `docs/vendor/monsterui/examples/` | + +### Idiomatic Patterns +- **Always check docs for the idiomatic way** before implementing anything with FastHTML/MonsterUI +- MonsterUI components: `Card`, `Alert`, `Grid(cols_sm/md/lg)`, `DivFullySpaced` +- Use `AlertT` enums for alerts, `TextPresets` for typography +- Forms: Use MonsterUI form components, not raw HTML + +## Quick Reference + +### Database (APSW + FastLite) +This project uses APSW (not standard sqlite3) via fastlite. Key patterns: +```python +from fastlite import database + +db = database('animaltrack.db') +# db.execute() returns APSW cursor +# Use db.t for table access +``` + +### Timestamps +All timestamps are ms since Unix epoch, stored as INTEGER: +```python +import time +ts_utc = int(time.time() * 1000) # Current time in ms +``` + +### IDs +All entity IDs are ULIDs (26 chars): +```python +from ulid import ULID +id = str(ULID()) # "01ARZ3NDEKTSV4RRFFQ69G5FAV" +``` + +### Money +Store all prices as integer cents. Display prices with 2 decimals, cost/egg with 3. + +## Project-Specific Rules + +### Event Sourcing +- Every state change is an event +- Events are immutable (edits create revisions, deletes create tombstones) +- Projections are updated synchronously in the same transaction + +### Selection Context +When handling animal selections: +1. Client sends filter + resolved_ids + roster_hash +2. Server re-resolves at ts_utc +3. On mismatch: return 409 with diff, require `confirmed=true` to proceed + +### Feed Costing +- Block FeedGiven if no FeedPurchased exists <= ts_utc +- Store feed amounts in grams (INTEGER) for precision +- Display as kg with 3 decimals + +## Refreshing Docs +```bash +# FastHTML +curl -s https://www.fastht.ml/docs/llms-ctx.txt -o docs/vendor/fasthtml/llms-ctx.txt + +# MonsterUI +curl -s https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms.txt -o docs/vendor/monsterui/llms.txt +``` + +## E2E Tests +The spec defines 8 authoritative acceptance tests in §21. These are the source of truth for correct behavior. When in doubt, refer to the spec. + +Numeric comparisons on REAL values use tolerance `±0.001`. diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..9fd7c2c --- /dev/null +++ b/PLAN.md @@ -0,0 +1,419 @@ +# AnimalTrack Implementation Plan + +Check off items as completed. Each phase builds on the previous. + +--- + +## Phase 1: Foundation + +### Step 1.1: Project Structure & Configuration +- [ ] Create Python package structure (`src/animaltrack/`) +- [ ] Create `pyproject.toml` with dependencies +- [ ] Create `flake.nix` with dev environment +- [ ] Create `.envrc`, `.gitignore` +- [ ] Implement `config.py` with all env vars from spec §18 +- [ ] Write tests for config loading and validation +- [ ] **Commit checkpoint** + +### Step 1.2: Database Connection & Pragmas +- [ ] Create `db.py` with connection factory +- [ ] Set pragmas: WAL, synchronous=FULL, foreign_keys=ON, busy_timeout=5000 +- [ ] Create transaction context manager (BEGIN IMMEDIATE) +- [ ] Create `constants.py` with END_OF_TIME_UTC +- [ ] Write tests for pragmas and transactions +- [ ] **Commit checkpoint** + +### Step 1.3: Migration Framework +- [ ] Create `migrations.py` using FastMigrate patterns +- [ ] Create `cli.py` with `migrate` and `create-migration` commands +- [ ] Create initial migration for `schema_migrations` table +- [ ] Write tests for migration discovery and application +- [ ] **Commit checkpoint** + +### Step 1.4: Reference Tables Schema +- [ ] Create migration for species, locations, products, feed_types, users tables +- [ ] Create Pydantic models for each reference type +- [ ] Write tests for model validation and constraints +- [ ] **Commit checkpoint** + +### Step 1.5: Reference Data Repository & Seeds +- [ ] Create repositories for each reference type (CRUD operations) +- [ ] Create `seeds.py` with idempotent seeder +- [ ] Seed: users (ppetru, ines, guest), locations (Strip 1-4, Nursery 1-4) +- [ ] Seed: species (duck, goose active; sheep inactive) +- [ ] Seed: products (egg.duck, meat/offal/fat/bones/feathers/down) +- [ ] Seed: feed types (starter, grower, layer - 20kg default) +- [ ] Create `id_gen.py` for ULID generation +- [ ] Write tests for repositories and seeding idempotency +- [ ] **Commit checkpoint** + +--- + +## Phase 2: Event Infrastructure + +### Step 2.1: Events Table Schema +- [ ] Create migration for events, event_revisions, event_tombstones +- [ ] Create migration for idempotency_nonces, event_animals tables +- [ ] Create Pydantic models for Event, EventRevision, EventTombstone +- [ ] Write tests for table constraints (ULID checks, JSON validation) +- [ ] **Commit checkpoint** + +### Step 2.2: Event Store Core +- [ ] Create `events/store.py` with append_event, get_event, list_events +- [ ] Implement nonce validation (reject duplicates) +- [ ] Implement clock skew guard (reject ts_utc > now + 5 min) +- [ ] Create `events/types.py` with event type constants +- [ ] Write tests for event storage, nonce rejection, clock skew +- [ ] **Commit checkpoint** + +### Step 2.3: Event Type Payloads +- [ ] Create Pydantic models for all event payloads (spec §6): + - [ ] LocationCreated, LocationRenamed, LocationArchived + - [ ] AnimalCohortCreated, AnimalPromoted, AnimalMoved + - [ ] AnimalAttributesUpdated, AnimalTagged, AnimalTagEnded + - [ ] HatchRecorded, AnimalOutcome + - [ ] ProductCollected, ProductSold + - [ ] FeedPurchased, FeedGiven + - [ ] AnimalMerged, AnimalStatusCorrected +- [ ] Create `events/validation.py` for payload validation +- [ ] Implement tag normalization (spec §20) +- [ ] Write tests for each payload model +- [ ] **Commit checkpoint** + +### Step 2.4: Projection Infrastructure +- [ ] Create `projections/base.py` with Projection base class +- [ ] Create ProjectionRegistry +- [ ] Create `events/processor.py` with process_event function +- [ ] Write tests for projection registration and processing +- [ ] **Commit checkpoint** + +--- + +## Phase 3: Animal Domain + +### Step 3.1: Animal Registry Schema +- [ ] Create migration for animal_registry table with indexes +- [ ] Create migration for live_animals_by_location table +- [ ] Create migration for animal_aliases table +- [ ] Create Pydantic models: Animal, enums (Sex, ReproStatus, LifeStage, Status, Origin) +- [ ] Write tests for table constraints and unique nickname constraint +- [ ] **Commit checkpoint** + +### Step 3.2: Interval Projections Schema +- [ ] Create migration for animal_location_intervals +- [ ] Create migration for animal_tag_intervals +- [ ] Create migration for animal_attr_intervals +- [ ] Create Pydantic models for interval types +- [ ] Write tests for CHECK constraints (end_utc > start_utc) +- [ ] **Commit checkpoint** + +### Step 3.3: Animal Cohort Creation +- [ ] Create `projections/animal_registry.py` for cohort creation +- [ ] Create `projections/intervals.py` for location and attribute intervals +- [ ] Create `services/animal.py` with create_cohort function +- [ ] Populate animal_registry, live_animals_by_location, event_animals +- [ ] Create initial location and attribute intervals +- [ ] Write tests: cohort validation, correct animal count, all projections updated +- [ ] **Commit checkpoint** + +### Step 3.4: Animal Movement +- [ ] Update projections for AnimalMoved event +- [ ] Close old location interval, open new one +- [ ] Update animal_registry and live_animals_by_location +- [ ] Add move_animals to services/animal.py +- [ ] Write tests: move validation (to!=from, single from_location), interval updates +- [ ] **Commit checkpoint** + +### Step 3.5: Animal Attributes Update +- [ ] Update projections for AnimalAttributesUpdated event +- [ ] Close old attribute intervals, open new ones for changed attrs +- [ ] Add update_attributes to services/animal.py +- [ ] Write tests: only changed attrs create new intervals +- [ ] **Commit checkpoint** + +### Step 3.6: Animal Tagging +- [ ] Create migration for tag_suggestions table +- [ ] Create `projections/tags.py` for tag intervals and suggestions +- [ ] Update live_animals_by_location tags JSON +- [ ] Add add_tag, end_tag to services/animal.py +- [ ] Write tests: tag normalization, interval open/close, no-op handling +- [ ] **Commit checkpoint** + +--- + +## Phase 4: Products & Feed + +### Step 4.1: Feed Inventory Schema & Purchase +- [ ] Create migration for feed_inventory table +- [ ] Create `projections/feed.py` with FeedInventoryProjection +- [ ] Implement apply_feed_purchased (increment purchased_kg, update price) +- [ ] Implement revert_feed_purchased +- [ ] Create `services/feed.py` with purchase_feed function +- [ ] Write tests: purchase increments, price stored as cents, revert works +- [ ] **Commit checkpoint** + +### Step 4.2: Feed Given Event +- [ ] Implement apply_feed_given (increment given_kg, decrement balance) +- [ ] Implement revert_feed_given +- [ ] Add give_feed to services/feed.py +- [ ] Block if no purchase <= ts_utc +- [ ] Write tests: give updates inventory, blocked without purchase, revert +- [ ] **Commit checkpoint** + +### Step 4.3: Product Collection Event +- [ ] Create `projections/products.py` for ProductCollected +- [ ] Create `services/products.py` with collect_product function +- [ ] Create `selection/resolver.py` with basic resolve_selection +- [ ] Populate event_animals for each resolved animal +- [ ] Write tests: event created, event_animals populated +- [ ] **Commit checkpoint** + +### Step 4.4: 30-Day Stats Computation +- [ ] Create migration for egg_stats_30d_by_location table +- [ ] Create `projections/stats.py` with EggStatsProjection +- [ ] Implement bird-days calculation from intervals +- [ ] Implement layer-eligible days filtering +- [ ] Implement feed proration formula +- [ ] Implement cost calculations (integer cents) +- [ ] Create `services/stats.py` with get_egg_stats (compute on read) +- [ ] Write tests: verify E2E test #1 baseline values (spec §21) +- [ ] **Commit checkpoint** + +### Step 4.5: Product Sold Event +- [ ] Implement apply_product_sold projection +- [ ] Add sell_product to services/products.py +- [ ] Calculate unit_price_cents = floor(total/qty) +- [ ] Write tests: event stored, unit price calculated +- [ ] **Commit checkpoint** + +--- + +## Phase 5: Selection & Historical Queries + +### Step 5.1: Selection Filter DSL Parser +- [ ] Create `selection/parser.py` for filter parsing +- [ ] Support: AND (default), OR (|), negate (-), quotes +- [ ] Fields: location, species, sex, life_stage, identified, tag +- [ ] Create `selection/ast.py` for filter AST nodes +- [ ] Write tests for all filter syntax variations +- [ ] **Commit checkpoint** + +### Step 5.2: Historical State Resolver +- [ ] Update `selection/resolver.py` for point-in-time resolution +- [ ] Use interval tables for historical state (spec §7 query pattern) +- [ ] Create `selection/hash.py` with roster hash (xxhash64) +- [ ] Write tests: resolver returns correct animals before/after events +- [ ] **Commit checkpoint** + +### Step 5.3: Optimistic Locking +- [ ] Create `selection/validation.py` for selection validation +- [ ] Re-resolve on submit, compute hash, return diff on mismatch +- [ ] Create SelectionContext and SelectionDiff models +- [ ] Write tests: mismatch detected, diff correct, confirmed bypasses +- [ ] **Commit checkpoint** + +--- + +## Phase 6: Event Lifecycle + +### Step 6.1: Event Editing +- [ ] Create `events/edit.py` with edit_event function +- [ ] Store old version in event_revisions +- [ ] Increment version +- [ ] Create `projections/replay.py` for unbounded replay +- [ ] Truncate affected rows >= ts_utc, re-apply events in order +- [ ] Write tests: revision stored, version incremented, projections updated +- [ ] Write test: E2E test #5 (edit egg event) +- [ ] **Commit checkpoint** + +### Step 6.2: Event Deletion +- [ ] Create `events/delete.py` with delete_event function +- [ ] Create tombstone, trigger replay/revert +- [ ] Create `events/dependencies.py` for finding dependents +- [ ] Implement recorder vs admin rules (spec §10) +- [ ] Write tests: tombstone created, projections updated +- [ ] Write tests: recorder blocked if dependents (409), admin cascade +- [ ] Write test: E2E test #6 +- [ ] **Commit checkpoint** + +### Step 6.3: Animal Lifecycle Events +- [ ] Implement HatchRecorded (creates hatchlings) +- [ ] Implement AnimalOutcome (death/harvest/sold with yields) +- [ ] Implement AnimalPromoted (identified=true, nickname) +- [ ] Implement AnimalMerged (status=merged_into, aliases) +- [ ] Implement AnimalStatusCorrected (admin-only with reason) +- [ ] Write tests for each event type +- [ ] Write test: E2E test #7 (harvest with yields) +- [ ] **Commit checkpoint** + +--- + +## Phase 7: HTTP API + +### Step 7.1: FastHTML App Shell +- [ ] Create `web/app.py` with FastHTML setup +- [ ] Configure HTMX extensions (head-support, preload, etc.) +- [ ] Create `web/middleware.py`: + - [ ] Auth middleware (X-Oidc-Username, TRUSTED_PROXY_IPS) + - [ ] CSRF middleware (cookie + header + Origin/Referer) + - [ ] Request logging (NDJSON format) + - [ ] Request ID generation +- [ ] Create `web/auth.py` with get_current_user, require_role +- [ ] Write tests: auth extraction, CSRF validation, untrusted IP rejection +- [ ] **Commit checkpoint** + +### Step 7.2: Health & Static Assets +- [ ] Create `web/routes/health.py` with /healthz (DB writable check) +- [ ] Optional /metrics (env gated) +- [ ] Configure static file serving (/static/vN/... with immutable cache) +- [ ] Vendor MonsterUI assets +- [ ] Create `web/templates/base.py` with base HTML template +- [ ] Create bottom nav component (Egg • Feed • Move • Registry) +- [ ] Write tests: healthz returns 200/503, static cache headers +- [ ] **Commit checkpoint** + +### Step 7.3: Egg Quick Capture +- [ ] Create `web/routes/eggs.py`: + - [ ] GET / - Egg Quick Capture form + - [ ] POST /actions/product-collected +- [ ] Create `web/templates/eggs.py` with form +- [ ] Implement UX defaults (integer only, min=1, location sticks, qty clears) +- [ ] Write tests: form renders, POST creates event, validation errors (422) +- [ ] **Commit checkpoint** + +### Step 7.4: Feed Quick Capture +- [ ] Create `web/routes/feed.py`: + - [ ] GET /feed - Feed Quick Capture form + - [ ] POST /actions/feed-given + - [ ] POST /actions/feed-purchased +- [ ] Create `web/templates/feed.py` with forms +- [ ] Implement defaults per spec §22 +- [ ] Write tests: form renders, POST creates events, blocked without purchase +- [ ] **Commit checkpoint** + +### Step 7.5: Move Animals +- [ ] Create `web/routes/move.py`: + - [ ] GET /move - Move Animals form + - [ ] POST /actions/animal-move +- [ ] Create `web/templates/move.py` with selection UI +- [ ] Implement mismatch confirmation flow +- [ ] Write tests: form renders, move validation, mismatch (409) +- [ ] Write test: E2E test #8 (optimistic lock) +- [ ] **Commit checkpoint** + +--- + +## Phase 8: Registry & Event Log + +### Step 8.1: Animal Registry View +- [ ] Create `web/routes/registry.py`: + - [ ] GET /registry with filters and pagination +- [ ] Create `web/templates/registry.py`: + - [ ] Table: ID, species, sex, life_stage, location, tags, last event, status + - [ ] Facet sidebar with counts +- [ ] Create `repositories/animals.py` with list_animals, get_facet_counts +- [ ] Implement infinite scroll (50/page, cursor=base64) +- [ ] Write tests: renders with animals, pagination works, filters apply +- [ ] **Commit checkpoint** + +### Step 8.2: Event Log Projection & View +- [ ] Create migration for event_log_by_location table with cap trigger +- [ ] Create `projections/event_log.py` for event summaries +- [ ] Create `web/routes/events.py`: + - [ ] GET /event-log?location_id=... +- [ ] Create `web/templates/events.py` +- [ ] Write tests: events appear, capped at 500, ordered by ts_utc DESC +- [ ] **Commit checkpoint** + +### Step 8.3: Animal Detail Drawer +- [ ] Create `web/routes/animals.py`: + - [ ] GET /animals/{animal_id} +- [ ] Create `web/templates/animal_detail.py`: + - [ ] Header summary + - [ ] Timeline (newest first) + - [ ] Quick actions +- [ ] Create `repositories/animal_timeline.py` +- [ ] Write tests: detail renders, timeline shows events, merge info shown +- [ ] **Commit checkpoint** + +--- + +## Phase 9: Remaining Actions + +### Step 9.1: Animal Actions Routes +- [ ] POST /actions/animal-attrs +- [ ] POST /actions/hatch-recorded +- [ ] POST /actions/animal-outcome +- [ ] POST /actions/animal-cohort +- [ ] POST /actions/animal-promote +- [ ] POST /actions/animal-tag-add +- [ ] POST /actions/animal-tag-end +- [ ] POST /actions/animal-status-correct (admin-only) +- [ ] Create form templates for each +- [ ] Write tests for each action +- [ ] **Commit checkpoint** + +### Step 9.2: Product Sold Route +- [ ] POST /actions/product-sold +- [ ] Create form template +- [ ] Write tests: sale creates event, unit price calculated +- [ ] **Commit checkpoint** + +### Step 9.3: User Defaults +- [ ] Create migration for user_defaults table +- [ ] Create `repositories/user_defaults.py` +- [ ] Integrate defaults into form rendering +- [ ] Write tests: defaults saved and loaded +- [ ] **Commit checkpoint** + +--- + +## Phase 10: Polish & E2E + +### Step 10.1: Full E2E Test Suite +- [ ] E2E test #1: Baseline eggs+feed+costs +- [ ] E2E test #2: Mixed group proration +- [ ] E2E test #3: Split flock, per-location stats +- [ ] E2E test #4: Backdated eggs use historical roster +- [ ] E2E test #5: Edit egg event (if not already done) +- [ ] E2E test #6: Deletes: recorder vs admin cascade (if not already done) +- [ ] E2E test #7: Harvest with yields (if not already done) +- [ ] E2E test #8: Optimistic lock with confirm (if not already done) +- [ ] **Commit checkpoint** + +### Step 10.2: Location Events & Error Handling +- [ ] Implement LocationCreated (idempotent for seeding) +- [ ] Implement LocationRenamed +- [ ] Implement LocationArchived +- [ ] Standardize error responses (422, 409, 401/403) +- [ ] Toast via HX-Trigger +- [ ] Write tests for location events and error rendering +- [ ] **Commit checkpoint** + +### Step 10.3: CLI, Docker & Deployment +- [ ] Complete CLI: serve, seed, migrate commands +- [ ] Update flake.nix for Docker image build +- [ ] Create docker.nix +- [ ] Document deployment configuration +- [ ] Write tests: CLI commands work +- [ ] **Final commit** + +--- + +## Summary + +**Total steps**: 40 steps across 10 phases +**Each step**: Self-contained, testable, ends with integration + +### Key Milestones +- Phase 1 complete: Project boots, DB works +- Phase 3 complete: Animals can be created and tracked +- Phase 4 complete: Feed/egg collection works with costs +- Phase 7 complete: Web UI is functional +- Phase 10 complete: Production ready + +### Notes +- TDD: Write tests first +- Each step should be committable +- Refer to spec.md for authoritative behavior +- Check CLAUDE.md for framework patterns diff --git a/docker.nix b/docker.nix new file mode 100644 index 0000000..95e20a6 --- /dev/null +++ b/docker.nix @@ -0,0 +1,66 @@ +{ pkgs, pythonEnv, python }: + +let + # Build animaltrack as a package + animaltrack = python.pkgs.buildPythonApplication { + pname = "animaltrack"; + version = "0.1.0"; + src = ./.; + format = "pyproject"; + + nativeBuildInputs = [ python.pkgs.setuptools ]; + + propagatedBuildInputs = [ pythonEnv ]; + + doCheck = false; + + # Don't wrap, we'll handle PATH manually + dontWrapPythonPrograms = true; + }; +in +pkgs.dockerTools.buildImage { + name = "gitea.v.paler.net/ppetru/animaltrack"; + tag = "latest"; + + copyToRoot = pkgs.buildEnv { + name = "animaltrack-env"; + paths = with pkgs; [ + # System utilities + busybox + bash + sqlite + + # Python environment with all packages + pythonEnv + + # Animaltrack application + animaltrack + ] ++ [ + # Docker filesystem helpers + pkgs.dockerTools.usrBinEnv + pkgs.dockerTools.binSh + pkgs.dockerTools.fakeNss + pkgs.dockerTools.caCertificates + ]; + }; + + runAsRoot = '' + #!${pkgs.stdenv.shell} + mkdir -p -m 1777 /tmp + mkdir -p /var/lib/animaltrack + ''; + + config = { + Env = [ + "DB_PATH=/var/lib/animaltrack/animaltrack.db" + "PATH=${pkgs.lib.makeBinPath [ pkgs.busybox pkgs.bash pkgs.sqlite pythonEnv ]}" + "PYTHONPATH=${pythonEnv}/${pythonEnv.sitePackages}" + "PYTHONUNBUFFERED=1" + ]; + ExposedPorts = { + "5000/tcp" = {}; + }; + Cmd = [ "sh" "-c" "animaltrack migrate && animaltrack serve" ]; + WorkingDir = "/var/lib/animaltrack"; + }; +} diff --git a/docs/vendor/README.md b/docs/vendor/README.md new file mode 100644 index 0000000..7656ef7 --- /dev/null +++ b/docs/vendor/README.md @@ -0,0 +1,43 @@ +# Vendored Documentation + +Locally-stored documentation for FastHTML and MonsterUI frameworks. + +## Contents + +### FastHTML (`fasthtml/`) + +- `llms-ctx.txt` - LLM-optimized FastHTML documentation +- `api-core.html` - Core classes (FastHTML, serve, cookie, APIRouter) +- `api-xtend.html` - Extended components (Form, Titled, Script) +- `api-components.html` - Form components and utilities +- `api-pico.html` - Pico CSS integration + +### MonsterUI (`monsterui/`) + +- `llms.txt` - LLM-optimized MonsterUI documentation +- `apilist.txt` - List of all API components +- `api_ref/` - Component documentation (cards, forms, modals, etc.) +- `examples/` - Real-world usage examples + +## Updating Documentation + +### FastHTML + +```bash +curl -s https://www.fastht.ml/docs/llms-ctx.txt -o docs/vendor/fasthtml/llms-ctx.txt +curl -s https://www.fastht.ml/docs/api/core.html -o docs/vendor/fasthtml/api-core.html +curl -s https://www.fastht.ml/docs/api/xtend.html -o docs/vendor/fasthtml/api-xtend.html +curl -s https://www.fastht.ml/docs/api/components.html -o docs/vendor/fasthtml/api-components.html +curl -s https://www.fastht.ml/docs/api/pico.html -o docs/vendor/fasthtml/api-pico.html +``` + +### MonsterUI + +```bash +curl -s https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/llms.txt -o docs/vendor/monsterui/llms.txt +curl -s https://raw.githubusercontent.com/AnswerDotAI/MonsterUI/refs/heads/main/docs/apilist.txt -o docs/vendor/monsterui/apilist.txt +``` + +## Last Updated + +**Date**: 2025-12-27 (copied from fitdata project) diff --git a/docs/vendor/fasthtml/api-components.html b/docs/vendor/fasthtml/api-components.html new file mode 100644 index 0000000..dcf20fd --- /dev/null +++ b/docs/vendor/fasthtml/api-components.html @@ -0,0 +1,1361 @@ + + + + + + + + + + +Components – fasthtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Components

+
+ +
+
+ ft_html and ft_hx functions to add some conveniences to ft, along with a full set of basic HTML components, and functions to work with forms and FT conversion +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from collections import UserDict
+from lxml import html as lx
+from pprint import pprint
+
+
+

Str and repr

+

In notebooks, FT components are rendered as their syntax highlighted XML/HTML:

+
+
sentence = P(Strong("FastHTML is ", I("Fast")), id='sentence_id')
+sentence
+
+
<p id="sentence_id">
+<strong>FastHTML is <i>Fast</i></strong></p>
+
+
+

Elsewhere, they are represented as their underlying data structure:

+
+
print(repr(sentence))
+
+
p((strong(('FastHTML is ', i(('Fast',),{})),{}),),{'id': 'sentence_id'})
+
+
+
+

source

+
+
+

FT.__str__

+
+
 FT.__str__ ()
+
+

Return str(self).

+

If they have an id, then that id is used as the component’s str representation:

+
+
f'hx_target=#{sentence}'
+
+
'hx_target=#sentence_id'
+
+
+
+

source

+
+
+

FT.__radd__

+
+
 FT.__radd__ (b)
+
+
+
'hx_target=#' + sentence
+
+
'hx_target=#sentence_id'
+
+
+
+

source

+
+
+

FT.__add__

+
+
 FT.__add__ (b)
+
+
+
sentence + '...'
+
+
'sentence_id...'
+
+
+
+
+

fh_html and fh_hx

+
+

source

+
+
+

attrmap_x

+
+
 attrmap_x (o)
+
+
+

source

+
+
+

ft_html

+
+
 ft_html (tag:str, *c, id=None, cls=None, title=None, style=None,
+          attrmap=None, valmap=None, ft_cls=None, **kwargs)
+
+
+
ft_html('a', **{'@click.away':1})
+
+
<a @click.away="1"></a>
+
+
+
+
ft_html('a', {'@click.away':1})
+
+
<a @click.away="1"></a>
+
+
+
+
ft_html('a', UserDict({'@click.away':1}))
+
+
<a @click.away="1"></a>
+
+
+
+
c = Div(id='someid')
+
+
+
ft_html('a', id=c)
+
+
<a id="someid" name="someid"></a>
+
+
+
+

source

+
+
+

ft_hx

+
+
 ft_hx (tag:str, *c, target_id=None, hx_vals=None, hx_target=None,
+        id=None, cls=None, title=None, style=None, accesskey=None,
+        contenteditable=None, dir=None, draggable=None, enterkeyhint=None,
+        hidden=None, inert=None, inputmode=None, lang=None, popover=None,
+        spellcheck=None, tabindex=None, translate=None, hx_get=None,
+        hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+        hx_trigger=None, hx_swap=None, hx_swap_oob=None, hx_include=None,
+        hx_select=None, hx_select_oob=None, hx_indicator=None,
+        hx_push_url=None, hx_confirm=None, hx_disable=None,
+        hx_replace_url=None, hx_disabled_elt=None, hx_ext=None,
+        hx_headers=None, hx_history=None, hx_history_elt=None,
+        hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None,
+        hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+        hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+        hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+        hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+        hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+        hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+        hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+        hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+        hx_on__abort=None, hx_on__after_on_load=None,
+        hx_on__after_process_node=None, hx_on__after_request=None,
+        hx_on__after_settle=None, hx_on__after_swap=None,
+        hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+        hx_on__before_process_node=None, hx_on__before_request=None,
+        hx_on__before_swap=None, hx_on__before_send=None,
+        hx_on__before_transition=None, hx_on__config_request=None,
+        hx_on__confirm=None, hx_on__history_cache_error=None,
+        hx_on__history_cache_miss=None,
+        hx_on__history_cache_miss_error=None,
+        hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+        hx_on__before_history_save=None, hx_on__load=None,
+        hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+        hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+        hx_on__oob_error_no_target=None, hx_on__prompt=None,
+        hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+        hx_on__response_error=None, hx_on__send_abort=None,
+        hx_on__send_error=None, hx_on__sse_error=None,
+        hx_on__sse_open=None, hx_on__swap_error=None,
+        hx_on__target_error=None, hx_on__timeout=None,
+        hx_on__validation_validate=None, hx_on__validation_failed=None,
+        hx_on__validation_halted=None, hx_on__xhr_abort=None,
+        hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+        hx_on__xhr_progress=None, **kwargs)
+
+
+
ft_hx('a', hx_vals={'a':1})
+
+
<a hx-vals='{"a": 1}'></a>
+
+
+
+
ft_hx('a', hx_target=c)
+
+
<a hx-target="#someid"></a>
+
+
+

For tags that have a name attribute, it will be set to the value of id if not provided explicitly:

+
+
Form(Button(target_id='foo', id='btn'),
+     hx_post='/', target_id='tgt', id='frm')
+
+
<form hx-post="/" hx-target="#tgt" id="frm" name="frm"><button hx-target="#foo" id="btn" name="btn"></button></form>
+
+
+
+

source

+
+
+

File

+
+
 File (fname)
+
+

Use the unescaped text in file fname directly

+
+
a = Input(name='nm')
+a
+
+
<input name="nm">
+
+
+
+
a(hx_swap_oob='true')
+
+
<input name="nm" hx-swap-oob="true">
+
+
+
+
a
+
+
<input name="nm" hx-swap-oob="true">
+
+
+
+
+

show

+
+

source

+
+
+

show

+
+
 show (ft, *rest, iframe=False, height='auto', style=None)
+
+

Renders FT Components into HTML within a Jupyter notebook.

+

When placed within the show() function, this will render the HTML in Jupyter notebooks.

+
+
show(sentence)
+
+

+FastHTML is Fast

+
+
+

You can also display full embedded pages in an iframe:

+
+
picocss = "https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.min.css"
+picolink = (Link(rel="stylesheet", href=picocss))
+
+fullpage = Html(
+    Head(picolink),
+    Body(
+        H2("Heading 2"),
+        P("Paragraph")
+    )
+)
+
+show(fullpage, height=100, iframe=True)
+
+ +
+
+
+
+

fill_form and find_inputs

+
+

source

+
+
+

fill_form

+
+
 fill_form (form:fastcore.xml.FT, obj)
+
+

Fills named items in form using attributes in obj

+
+
@dataclass
+class TodoItem:
+    title:str; id:int; done:bool; details:str; opt:str='a'
+
+todo = TodoItem(id=2, title="Profit", done=True, details="Details", opt='b')
+check = Label(Input(type="checkbox", cls="checkboxer", name="done", data_foo="bar"), "Done", cls='px-2')
+form = Form(Fieldset(Input(cls="char", id="title", value="a"), check, Input(type="hidden", id="id"),
+                     Select(Option(value='a'), Option(value='b'), name='opt'),
+                     Textarea(id='details'), Button("Save"),
+                     name="stuff"))
+form = fill_form(form, todo)
+assert '<textarea id="details" name="details">Details</textarea>' in to_xml(form)
+form
+
+
<form><fieldset name="stuff">    <input value="Profit" id="title" class="char" name="title">
+<label class="px-2">      <input type="checkbox" name="done" data-foo="bar" class="checkboxer" checked="1">
+Done</label>    <input type="hidden" id="id" name="id" value="2">
+<select name="opt"><option value="a"></option><option value="b" selected="1"></option></select><textarea id="details" name="details">Details</textarea><button>Save</button></fieldset></form>
+
+
+
+
@dataclass
+class MultiSelect:
+    items: list[str]
+
+multiselect = MultiSelect(items=['a', 'c'])
+multiform = Form(Select(Option('a', value='a'), Option('b', value='b'), Option('c', value='c'), multiple='1', name='items'))
+multiform = fill_form(multiform, multiselect)
+assert '<option value="a" selected="1">a</option>' in to_xml(multiform)
+assert '<option value="b">b</option>' in to_xml(multiform)
+assert '<option value="c" selected="1">c</option>' in to_xml(multiform)
+multiform
+
+
<form><select multiple="1" name="items"><option value="a" selected="1">a</option><option value="b">b</option><option value="c" selected="1">c</option></select></form>
+
+
+
+
@dataclass
+class MultiCheck:
+    items: list[str]
+
+multicheck = MultiCheck(items=['a', 'c'])
+multiform = Form(Fieldset(Label(Input(type='checkbox', name='items', value='a'), 'a'),
+                          Label(Input(type='checkbox', name='items', value='b'), 'b'),
+                          Label(Input(type='checkbox', name='items', value='c'), 'c')))
+multiform = fill_form(multiform, multicheck)
+assert '<input type="checkbox" name="items" value="a" checked="1">' in to_xml(multiform)
+assert '<input type="checkbox" name="items" value="b">' in to_xml(multiform)
+assert '<input type="checkbox" name="items" value="c" checked="1">' in to_xml(multiform)
+multiform
+
+
<form><fieldset><label>      <input type="checkbox" name="items" value="a" checked="1">
+a</label><label>      <input type="checkbox" name="items" value="b">
+b</label><label>      <input type="checkbox" name="items" value="c" checked="1">
+c</label></fieldset></form>
+
+
+
+

source

+
+
+

fill_dataclass

+
+
 fill_dataclass (src, dest)
+
+

Modifies dataclass in-place and returns it

+
+
nt = TodoItem('', 0, False, '')
+fill_dataclass(todo, nt)
+nt
+
+
TodoItem(title='Profit', id=2, done=True, details='Details', opt='b')
+
+
+
+

source

+
+
+

find_inputs

+
+
 find_inputs (e, tags='input', **kw)
+
+

Recursively find all elements in e with tags and attrs matching kw

+
+
inps = find_inputs(form, id='title')
+test_eq(len(inps), 1)
+inps
+
+
[input((),{'value': 'Profit', 'id': 'title', 'class': 'char', 'name': 'title'})]
+
+
+

You can also use lxml for more sophisticated searching:

+
+
elem = lx.fromstring(to_xml(form))
+test_eq(elem.xpath("//input[@id='title']/@value"), ['Profit'])
+
+
+

source

+
+
+

getattr

+
+
 __getattr__ (tag)
+
+
+
+

html2ft

+
+

source

+
+
+

html2ft

+
+
 html2ft (html, attr1st=False)
+
+

Convert HTML to an ft expression

+
+
h = to_xml(form)
+hl_md(html2ft(h), 'python')
+
+
Form(
+    Fieldset(
+        Input(value='Profit', id='title', name='title', cls='char'),
+        Label(
+            Input(type='checkbox', name='done', data_foo='bar', checked='1', cls='checkboxer'),
+            'Done',
+            cls='px-2'
+        ),
+        Input(type='hidden', id='id', name='id', value='2'),
+        Select(
+            Option(value='a'),
+            Option(value='b', selected='1'),
+            name='opt'
+        ),
+        Textarea('Details', id='details', name='details'),
+        Button('Save'),
+        name='stuff'
+    )
+)
+
+
+
+
hl_md(html2ft(h, attr1st=True), 'python')
+
+
Form(
+    Fieldset(name='stuff')(
+        Input(value='Profit', id='title', name='title', cls='char')(),
+        Label(cls='px-2')(
+            Input(type='checkbox', name='done', data_foo='bar', checked='1', cls='checkboxer')(),
+            'Done'
+        ),
+        Input(type='hidden', id='id', name='id', value='2')(),
+        Select(name='opt')(
+            Option(value='a')(),
+            Option(value='b', selected='1')()
+        ),
+        Textarea(id='details', name='details')('Details'),
+        Button()('Save')
+    )
+)
+
+
+
+

source

+
+
+

sse_message

+
+
 sse_message (elm, event='message')
+
+

Convert element elm into a format suitable for SSE streaming

+
+
print(sse_message(Div(P('hi'), P('there'))))
+
+
event: message
+data: <div>
+data:   <p>hi</p>
+data:   <p>there</p>
+data: </div>
+
+
+
+
+
+
+

Tests

+
+
test_html2ft('<input value="Profit" name="title" id="title" class="char">', attr1st=True)
+test_html2ft('<input value="Profit" name="title" id="title" class="char">')
+test_html2ft('<div id="foo"></div>')
+test_html2ft('<div id="foo">hi</div>')
+test_html2ft('<div x-show="open" x-transition:enter="transition duration-300" x-transition:enter-start="opacity-0 scale-90">Hello 👋</div>')
+test_html2ft('<div x-transition:enter.scale.80 x-transition:leave.scale.90>hello</div>')
+
+
+
assert html2ft('<div id="foo">hi</div>', attr1st=True) == "Div(id='foo')('hi')"
+assert html2ft('<div id="foo" hidden>hi</div>', attr1st=True) == "Div(id='foo', hidden=True)('hi')"
+assert html2ft("""
+  <div x-show="open" x-transition:enter="transition duration-300" x-transition:enter-start="opacity-0 scale-90">Hello 👋</div>
+""") == "Div('Hello 👋', x_show='open', **{'x-transition:enter': 'transition duration-300', 'x-transition:enter-start': 'opacity-0 scale-90'})"
+assert html2ft('<div x-transition:enter.scale.80 x-transition:leave.scale.90>hello</div>') == "Div('hello', **{'x-transition:enter.scale.80': True, 'x-transition:leave.scale.90': True})"
+assert html2ft("<img alt=' ' />") == "Img(alt=' ')"
+
+

“## Export -

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/vendor/fasthtml/api-core.html b/docs/vendor/fasthtml/api-core.html new file mode 100644 index 0000000..5f21188 --- /dev/null +++ b/docs/vendor/fasthtml/api-core.html @@ -0,0 +1,2438 @@ + + + + + + + + + + +Core – fasthtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Core

+
+ +
+
+ The FastHTML subclass of Starlette, along with the RouterX and RouteX classes it automatically uses. +
+
+ + +
+ + + + +
+ + + +
+ + + +

This is the source code to fasthtml. You won’t need to read this unless you want to understand how things are built behind the scenes, or need full details of a particular API. The notebook is converted to the Python module fasthtml/core.py using nbdev.

+
+

Imports and utils

+
+
import time
+
+from IPython import display
+from enum import Enum
+from pprint import pprint
+
+from fastcore.test import *
+from starlette.testclient import TestClient
+from starlette.requests import Headers
+from starlette.datastructures import UploadFile
+
+

We write source code first, and then tests come after. The tests serve as both a means to confirm that the code works and also serves as working examples. The first exported function, parsed_date, is an example of this pattern.

+
+

source

+
+

parsed_date

+
+
 parsed_date (s:str)
+
+

Convert s to a datetime

+
+
parsed_date('2pm')
+
+
datetime.datetime(2025, 7, 2, 14, 0)
+
+
+
+
isinstance(date.fromtimestamp(0), date)
+
+
True
+
+
+
+

source

+
+
+

snake2hyphens

+
+
 snake2hyphens (s:str)
+
+

Convert s from snake case to hyphenated and capitalised

+
+
snake2hyphens("snake_case")
+
+
'Snake-Case'
+
+
+
+

source

+
+
+

HtmxHeaders

+
+
 HtmxHeaders (boosted:str|None=None, current_url:str|None=None,
+              history_restore_request:str|None=None, prompt:str|None=None,
+              request:str|None=None, target:str|None=None,
+              trigger_name:str|None=None, trigger:str|None=None)
+
+
+
def test_request(url: str='/', headers: dict={}, method: str='get') -> Request:
+    scope = {
+        'type': 'http',
+        'method': method,
+        'path': url,
+        'headers': Headers(headers).raw,
+        'query_string': b'',
+        'scheme': 'http',
+        'client': ('127.0.0.1', 8000),
+        'server': ('127.0.0.1', 8000),
+    }
+    receive = lambda: {"body": b"", "more_body": False}
+    return Request(scope, receive)
+
+
+
h = test_request(headers=Headers({'HX-Request':'1'}))
+_get_htmx(h.headers)
+
+
HtmxHeaders(boosted=None, current_url=None, history_restore_request=None, prompt=None, request='1', target=None, trigger_name=None, trigger=None)
+
+
+
+
+
+

Request and response

+
+
test_eq(_fix_anno(Union[str,None], 'a'), 'a')
+test_eq(_fix_anno(float, 0.9), 0.9)
+test_eq(_fix_anno(int, '1'), 1)
+test_eq(_fix_anno(int, ['1','2']), 2)
+test_eq(_fix_anno(list[int], ['1','2']), [1,2])
+test_eq(_fix_anno(list[int], '1'), [1])
+
+
+
d = dict(k=int, l=List[int])
+test_eq(_form_arg('k', "1", d), 1)
+test_eq(_form_arg('l', "1", d), [1])
+test_eq(_form_arg('l', ["1","2"], d), [1,2])
+
+
+

source

+
+

HttpHeader

+
+
 HttpHeader (k:str, v:str)
+
+
+
_to_htmx_header('trigger_after_settle')
+
+
'HX-Trigger-After-Settle'
+
+
+
+

source

+
+
+

HtmxResponseHeaders

+
+
 HtmxResponseHeaders (location=None, push_url=None, redirect=None,
+                      refresh=None, replace_url=None, reswap=None,
+                      retarget=None, reselect=None, trigger=None,
+                      trigger_after_settle=None, trigger_after_swap=None)
+
+

HTMX response headers

+
+
HtmxResponseHeaders(trigger_after_settle='hi')
+
+
HttpHeader(k='HX-Trigger-After-Settle', v='hi')
+
+
+
+

source

+
+
+

form2dict

+
+
 form2dict (form:starlette.datastructures.FormData)
+
+

Convert starlette form data to a dict

+
+
d = [('a',1),('a',2),('b',0)]
+fd = FormData(d)
+res = form2dict(fd)
+test_eq(res['a'], [1,2])
+test_eq(res['b'], 0)
+
+
+

source

+
+
+

parse_form

+
+
 parse_form (req:starlette.requests.Request)
+
+

Starlette errors on empty multipart forms, so this checks for that situation

+
+

source

+
+
+

JSONResponse

+
+
 JSONResponse (content:Any, status_code:int=200,
+               headers:collections.abc.Mapping[str,str]|None=None,
+               media_type:str|None=None,
+               background:starlette.background.BackgroundTask|None=None)
+
+

Same as starlette’s version, but auto-stringifies non serializable types

+
+
async def f(req):
+    def _f(p:HttpHeader): ...
+    p = first(_params(_f).values())
+    result = await _from_body(req, p)
+    return JSONResponse(result.__dict__)
+
+client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))
+
+d = dict(k='value1',v=['value2','value3'])
+response = client.post('/', data=d)
+print(response.json())
+
+
{'k': 'value1', 'v': 'value3'}
+
+
+
+
async def f(req): return Response(str(req.query_params.getlist('x')))
+client = TestClient(Starlette(routes=[Route('/', f, methods=['GET'])]))
+client.get('/?x=1&x=2').text
+
+
"['1', '2']"
+
+
+
+
def g(req, this:Starlette, a:str, b:HttpHeader): ...
+
+async def f(req):
+    a = await _wrap_req(req, _params(g))
+    return Response(str(a))
+
+client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))
+response = client.post('/?a=1', data=d)
+print(response.text)
+
+
[<starlette.requests.Request object>, <starlette.applications.Starlette object>, '1', HttpHeader(k='value1', v='value3')]
+
+
+
+
def g(req, this:Starlette, a:str, b:HttpHeader): ...
+
+async def f(req):
+    a = await _wrap_req(req, _params(g))
+    return Response(str(a))
+
+client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))
+response = client.post('/?a=1', data=d)
+print(response.text)
+
+
[<starlette.requests.Request object>, <starlette.applications.Starlette object>, '1', HttpHeader(k='value1', v='value3')]
+
+
+

Missing Request Params

+

If a request param has a default value (e.g. a:str=''), the request is valid even if the user doesn’t include the param in their request.

+
+
def g(req, this:Starlette, a:str=''): ...
+
+async def f(req):
+    a = await _wrap_req(req, _params(g))
+    return Response(str(a))
+
+client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))
+response = client.post('/', json={}) # no param in request
+print(response.text)
+
+
[<starlette.requests.Request object>, <starlette.applications.Starlette object>, '']
+
+
+

If we remove the default value and re-run the request, we should get the following error Missing required field: a.

+
+
def g(req, this:Starlette, a:str): ...
+
+async def f(req):
+    a = await _wrap_req(req, _params(g))
+    return Response(str(a))
+
+client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))
+response = client.post('/', json={}) # no param in request
+print(response.text)
+
+
Missing required field: a
+
+
+
+

source

+
+
+

flat_xt

+
+
 flat_xt (lst)
+
+

Flatten lists

+
+
x = ft('a',1)
+test_eq(flat_xt([x, x, [x,x]]), (x,)*4)
+test_eq(flat_xt(x), (x,))
+
+
+

source

+
+
+

Beforeware

+
+
 Beforeware (f, skip=None)
+
+

Initialize self. See help(type(self)) for accurate signature.

+
+
+
+

Websockets / SSE

+
+
def on_receive(self, msg:str): return f"Message text was: {msg}"
+c = _ws_endp(on_receive)
+cli = TestClient(Starlette(routes=[WebSocketRoute('/', _ws_endp(on_receive))]))
+with cli.websocket_connect('/') as ws:
+    ws.send_text('{"msg":"Hi!"}')
+    data = ws.receive_text()
+    assert data == 'Message text was: Hi!'
+
+
+

source

+
+

EventStream

+
+
 EventStream (s)
+
+

Create a text/event-stream response from s

+
+

source

+
+
+

signal_shutdown

+
+
 signal_shutdown ()
+
+
+
+
+

Routing and application

+
+

source

+
+

uri

+
+
 uri (_arg, **kwargs)
+
+
+

source

+
+
+

decode_uri

+
+
 decode_uri (s)
+
+
+

source

+
+
+

StringConvertor.to_string

+
+
 StringConvertor.to_string (value:str)
+
+
+

source

+
+
+

HTTPConnection.url_path_for

+
+
 HTTPConnection.url_path_for (name:str, **path_params)
+
+
+

source

+
+
+

flat_tuple

+
+
 flat_tuple (o)
+
+

Flatten lists

+
+

source

+
+
+

noop_body

+
+
 noop_body (c, req)
+
+

Default Body wrap function which just returns the content

+
+

source

+
+
+

respond

+
+
 respond (req, heads, bdy)
+
+

Default FT response creation function

+

Render fragment if HX-Request header is present and HX-History-Restore-Request header is absent.

+
+

source

+
+
+

is_full_page

+
+
 is_full_page (req, resp)
+
+
+

source

+
+
+

Redirect

+
+
 Redirect (loc)
+
+

Use HTMX or Starlette RedirectResponse as required to redirect to loc

+

The FastHTML exts param supports the following:

+
+
print(' '.join(htmx_exts))
+
+
morph head-support preload class-tools loading-states multi-swap path-deps remove-me ws chunked-transfer
+
+
+
+

source

+
+
+

get_key

+
+
 get_key (key=None, fname='.sesskey')
+
+
+

source

+
+
+

qp

+
+
 qp (p:str, **kw)
+
+

Add parameters kw to path p

+

qp adds query parameters to route path strings

+
+
vals = {'a':5, 'b':False, 'c':[1,2], 'd':'bar', 'e':None, 'ab':42}
+
+
+
res = qp('/foo', **vals)
+test_eq(res, '/foo?a=5&b=&c=1&c=2&d=bar&e=&ab=42')
+
+

qp checks to see if each param should be sent as a query parameter or as part of the route, and encodes that properly.

+
+
path = '/foo/{a}/{d}/{ab:int}'
+res = qp(path, **vals)
+test_eq(res, '/foo/5/bar/42?b=&c=1&c=2&e=')
+
+
+

source

+
+
+

def_hdrs

+
+
 def_hdrs (htmx=True, surreal=True)
+
+

Default headers for a FastHTML app

+
+

source

+
+
+

FastHTML

+
+
 FastHTML (debug=False, routes=None, middleware=None, title:str='FastHTML
+           page', exception_handlers=None, on_startup=None,
+           on_shutdown=None, lifespan=None, hdrs=None, ftrs=None,
+           exts=None, before=None, after=None, surreal=True, htmx=True,
+           default_hdrs=True, sess_cls=<class
+           'starlette.middleware.sessions.SessionMiddleware'>,
+           secret_key=None, session_cookie='session_', max_age=31536000,
+           sess_path='/', same_site='lax', sess_https_only=False,
+           sess_domain=None, key_fname='.sesskey', body_wrap=<function
+           noop_body>, htmlkw=None, nb_hdrs=False, canonical=True,
+           **bodykw)
+
+

Creates an Starlette application.

+
+

source

+
+
+

FastHTML.add_route

+
+
 FastHTML.add_route (route)
+
+
+

source

+
+
+

FastHTML.ws

+
+
 FastHTML.ws (path:str, conn=None, disconn=None, name=None,
+              middleware=None)
+
+

Add a websocket route at path

+
+

source

+
+
+

nested_name

+
+
 nested_name (f)
+
+

*Get name of function f using ’_’ to join nested function names*

+
+
def f():
+    def g(): ...
+    return g
+
+
+
func = f()
+nested_name(func)
+
+
'f_g'
+
+
+
+

source

+
+
+

FastHTML.route

+
+
 FastHTML.route (path:str=None, methods=None, name=None,
+                 include_in_schema=True, body_wrap=None)
+
+

Add a route at path

+
+
app = FastHTML()
+@app.get
+def foo(a:str, b:list[int]): ...
+
+foo.to(a='bar', b=[1,2])
+
+
'/foo?a=bar&b=1&b=2'
+
+
+
+
@app.get('/foo/{a}')
+def foo(a:str, b:list[int]): ...
+
+foo.to(a='bar', b=[1,2])
+
+
'/foo/bar?b=1&b=2'
+
+
+
+

source

+
+
+

FastHTML.set_lifespan

+
+
 FastHTML.set_lifespan (value)
+
+
+

source

+
+
+

serve

+
+
 serve (appname=None, app='app', host='0.0.0.0', port=None, reload=True,
+        reload_includes:list[str]|str|None=None,
+        reload_excludes:list[str]|str|None=None)
+
+

Run the app in an async server, with live reload set as the default.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appnameNoneTypeNoneName of the module
appstrappApp instance to be served
hoststr0.0.0.0If host is 0.0.0.0 will convert to localhost
portNoneTypeNoneIf port is None it will default to 5001 or the PORT environment variable
reloadboolTrueDefault is to reload the app upon code changes
reload_includeslist[str] | str | NoneNoneAdditional files to watch for changes
reload_excludeslist[str] | str | NoneNoneFiles to ignore for changes
+
+

source

+
+
+

Client

+
+
 Client (app, url='http://testserver')
+
+

A simple httpx ASGI client that doesn’t require async

+
+
app = FastHTML(routes=[Route('/', lambda _: Response('test'))])
+cli = Client(app)
+
+cli.get('/').text
+
+
'test'
+
+
+

Note that you can also use Starlette’s TestClient instead of FastHTML’s Client. They should be largely interchangable.

+
+
+
+

FastHTML Tests

+
+
def get_cli(app): return app,TestClient(app),app.route
+
+
+
app,cli,rt = get_cli(FastHTML(secret_key='soopersecret'))
+
+
+
app,cli,rt = get_cli(FastHTML(title="My Custom Title"))
+@app.get
+def foo(): return Div("Hello World")
+
+print(app.routes)
+
+response = cli.get('/foo')
+assert '<title>My Custom Title</title>' in response.text
+
+foo.to(param='value')
+
+
[Route(path='/foo', name='foo', methods=['GET', 'HEAD'])]
+
+
+
'/foo?param=value'
+
+
+
+
app,cli,rt = get_cli(FastHTML())
+
+@rt('/xt2')
+def get(): return H1('bar')
+
+txt = cli.get('/xt2').text
+assert '<title>FastHTML page</title>' in txt and '<h1>bar</h1>' in txt and '<html>' in txt
+
+
+
@rt("/hi")
+def get(): return 'Hi there'
+
+r = cli.get('/hi')
+r.text
+
+
'Hi there'
+
+
+
+
@rt("/hi")
+def post(): return 'Postal'
+
+cli.post('/hi').text
+
+
'Postal'
+
+
+
+
@app.get("/hostie")
+def show_host(req): return req.headers['host']
+
+cli.get('/hostie').text
+
+
'testserver'
+
+
+
+
@app.get("/setsess")
+def set_sess(session):
+   session['foo'] = 'bar'
+   return 'ok'
+
+@app.ws("/ws")
+def ws(self, msg:str, ws:WebSocket, session): return f"Message text was: {msg} with session {session.get('foo')}, from client: {ws.client}"
+
+cli.get('/setsess')
+with cli.websocket_connect('/ws') as ws:
+    ws.send_text('{"msg":"Hi!"}')
+    data = ws.receive_text()
+assert 'Message text was: Hi! with session bar' in data
+print(data)
+
+
Message text was: Hi! with session bar, from client: Address(host='testclient', port=50000)
+
+
+
+
@rt
+def yoyo(): return 'a yoyo'
+
+cli.post('/yoyo').text
+
+
'a yoyo'
+
+
+
+
@app.get
+def autopost(): return Html(Div('Text.', hx_post=yoyo()))
+print(cli.get('/autopost').text)
+
+
 <!doctype html>
+ <html>
+   <div hx-post="a yoyo">Text.</div>
+ </html>
+
+
+
+
+
@app.get
+def autopost2(): return Html(Body(Div('Text.', cls='px-2', hx_post=show_host.to(a='b'))))
+print(cli.get('/autopost2').text)
+
+
 <!doctype html>
+ <html>
+   <body>
+     <div class="px-2" hx-post="/hostie?a=b">Text.</div>
+   </body>
+ </html>
+
+
+
+
+
@app.get
+def autoget2(): return Html(Div('Text.', hx_get=show_host))
+print(cli.get('/autoget2').text)
+
+
 <!doctype html>
+ <html>
+   <div hx-get="/hostie">Text.</div>
+ </html>
+
+
+
+
+
@rt('/user/{nm}', name='gday')
+def get(nm:str=''): return f"Good day to you, {nm}!"
+cli.get('/user/Alexis').text
+
+
'Good day to you, Alexis!'
+
+
+
+
@app.get
+def autolink(): return Html(Div('Text.', link=uri('gday', nm='Alexis')))
+print(cli.get('/autolink').text)
+
+
 <!doctype html>
+ <html>
+   <div href="/user/Alexis">Text.</div>
+ </html>
+
+
+
+
+
@rt('/link')
+def get(req): return f"{req.url_for('gday', nm='Alexis')}; {req.url_for('show_host')}"
+
+cli.get('/link').text
+
+
'http://testserver/user/Alexis; http://testserver/hostie'
+
+
+
+
@app.get("/background")
+async def background_task(request):
+    async def long_running_task():
+        await asyncio.sleep(0.1)
+        print("Background task completed!")
+    return P("Task started"), BackgroundTask(long_running_task)
+
+response = cli.get("/background")
+
+
Background task completed!
+
+
+
+
test_eq(app.router.url_path_for('gday', nm='Jeremy'), '/user/Jeremy')
+
+
+
hxhdr = {'headers':{'hx-request':"1"}}
+
+@rt('/ft')
+def get(): return Title('Foo'),H1('bar')
+
+txt = cli.get('/ft').text
+assert '<title>Foo</title>' in txt and '<h1>bar</h1>' in txt and '<html>' in txt
+
+@rt('/xt2')
+def get(): return H1('bar')
+
+txt = cli.get('/xt2').text
+assert '<title>FastHTML page</title>' in txt and '<h1>bar</h1>' in txt and '<html>' in txt
+
+assert cli.get('/xt2', **hxhdr).text.strip() == '<h1>bar</h1>'
+
+@rt('/xt3')
+def get(): return Html(Head(Title('hi')), Body(P('there')))
+
+txt = cli.get('/xt3').text
+assert '<title>FastHTML page</title>' not in txt and '<title>hi</title>' in txt and '<p>there</p>' in txt
+
+
+
@rt('/oops')
+def get(nope): return nope
+test_warns(lambda: cli.get('/oops?nope=1'))
+
+
+
def test_r(cli, path, exp, meth='get', hx=False, **kwargs):
+    if hx: kwargs['headers'] = {'hx-request':"1"}
+    test_eq(getattr(cli, meth)(path, **kwargs).text, exp)
+
+ModelName = str_enum('ModelName', "alexnet", "resnet", "lenet")
+fake_db = [{"name": "Foo"}, {"name": "Bar"}]
+
+
+
@rt('/html/{idx}')
+async def get(idx:int): return Body(H4(f'Next is {idx+1}.'))
+
+
+
@rt("/models/{nm}")
+def get(nm:ModelName): return nm
+
+@rt("/files/{path}")
+async def get(path: Path): return path.with_suffix('.txt')
+
+@rt("/items/")
+def get(idx:int|None = 0): return fake_db[idx]
+
+@rt("/idxl/")
+def get(idx:list[int]): return str(idx)
+
+
+
r = cli.get('/html/1', headers={'hx-request':"1"})
+assert '<h4>Next is 2.</h4>' in r.text
+test_r(cli, '/models/alexnet', 'alexnet')
+test_r(cli, '/files/foo', 'foo.txt')
+test_r(cli, '/items/?idx=1', '{"name":"Bar"}')
+test_r(cli, '/items/', '{"name":"Foo"}')
+assert cli.get('/items/?idx=g').text=='404 Not Found'
+assert cli.get('/items/?idx=g').status_code == 404
+test_r(cli, '/idxl/?idx=1&idx=2', '[1, 2]')
+assert cli.get('/idxl/?idx=1&idx=g').status_code == 404
+
+
+
app = FastHTML()
+rt = app.route
+cli = TestClient(app)
+@app.route(r'/static/{path:path}.jpg')
+def index(path:str): return f'got {path}'
+@app.route(r'/static/{path:path}')
+def foo(path:str, a:int): return f'also got {path},{a}'
+cli.get('/static/sub/a.b.jpg').text
+
+
'got sub/a.b'
+
+
+
+
cli.get('/static/sub/a.b?a=1').text
+
+
'also got sub/a.b,1'
+
+
+
+
app.chk = 'foo'
+
+
+
@app.get("/booly/")
+def _(coming:bool=True): return 'Coming' if coming else 'Not coming'
+
+@app.get("/datie/")
+def _(d:parsed_date): return d
+
+@app.get("/ua")
+async def _(user_agent:str): return user_agent
+
+@app.get("/hxtest")
+def _(htmx): return htmx.request
+
+@app.get("/hxtest2")
+def _(foo:HtmxHeaders, req): return foo.request
+
+@app.get("/app")
+def _(app): return app.chk
+
+@app.get("/app2")
+def _(foo:FastHTML): return foo.chk,HttpHeader("mykey", "myval")
+
+@app.get("/app3")
+def _(foo:FastHTML): return HtmxResponseHeaders(location="http://example.org")
+
+@app.get("/app4")
+def _(foo:FastHTML): return Redirect("http://example.org")
+
+
+
test_r(cli, '/booly/?coming=true', 'Coming')
+test_r(cli, '/booly/?coming=no', 'Not coming')
+date_str = "17th of May, 2024, 2p"
+test_r(cli, f'/datie/?d={date_str}', '2024-05-17 14:00:00')
+test_r(cli, '/ua', 'FastHTML', headers={'User-Agent':'FastHTML'})
+test_r(cli, '/hxtest' , '1', headers={'HX-Request':'1'})
+test_r(cli, '/hxtest2', '1', headers={'HX-Request':'1'})
+test_r(cli, '/app' , 'foo')
+
+
+
r = cli.get('/app2', **hxhdr)
+test_eq(r.text, 'foo')
+test_eq(r.headers['mykey'], 'myval')
+
+
+
r = cli.get('/app3')
+test_eq(r.headers['HX-Location'], 'http://example.org')
+
+
+
r = cli.get('/app4', follow_redirects=False)
+test_eq(r.status_code, 303)
+
+
+
r = cli.get('/app4', headers={'HX-Request':'1'})
+test_eq(r.headers['HX-Redirect'], 'http://example.org')
+
+
+
@rt
+def meta():
+    return ((Title('hi'),H1('hi')),
+        (Meta(property='image'), Meta(property='site_name'))
+    )
+
+t = cli.post('/meta').text
+assert re.search(r'<body>\s*<h1>hi</h1>\s*</body>', t)
+assert '<meta' in t
+
+
+
@app.post('/profile/me')
+def profile_update(username: str): return username
+
+test_r(cli, '/profile/me', 'Alexis', 'post', data={'username' : 'Alexis'})
+test_r(cli, '/profile/me', 'Missing required field: username', 'post', data={})
+
+
+
# Example post request with parameter that has a default value
+@app.post('/pet/dog')
+def pet_dog(dogname: str = None): return dogname
+
+# Working post request with optional parameter
+test_r(cli, '/pet/dog', '', 'post', data={})
+
+
+
@dataclass
+class Bodie: a:int;b:str
+
+@rt("/bodie/{nm}")
+def post(nm:str, data:Bodie):
+    res = asdict(data)
+    res['nm'] = nm
+    return res
+
+@app.post("/bodied/")
+def bodied(data:dict): return data
+
+nt = namedtuple('Bodient', ['a','b'])
+
+@app.post("/bodient/")
+def bodient(data:nt): return asdict(data)
+
+class BodieTD(TypedDict): a:int;b:str='foo'
+
+@app.post("/bodietd/")
+def bodient(data:BodieTD): return data
+
+class Bodie2:
+    a:int|None; b:str
+    def __init__(self, a, b='foo'): store_attr()
+
+@rt("/bodie2/", methods=['get','post'])
+def bodie(d:Bodie2): return f"a: {d.a}; b: {d.b}"
+
+
+
from fasthtml.xtend import Titled
+
+
+
d = dict(a=1, b='foo')
+
+test_r(cli, '/bodie/me', '{"a":1,"b":"foo","nm":"me"}', 'post', data=dict(a=1, b='foo', nm='me'))
+test_r(cli, '/bodied/', '{"a":"1","b":"foo"}', 'post', data=d)
+test_r(cli, '/bodie2/', 'a: 1; b: foo', 'post', data={'a':1})
+test_r(cli, '/bodie2/?a=1&b=foo&nm=me', 'a: 1; b: foo')
+test_r(cli, '/bodient/', '{"a":"1","b":"foo"}', 'post', data=d)
+test_r(cli, '/bodietd/', '{"a":1,"b":"foo"}', 'post', data=d)
+
+
+
# Testing POST with Content-Type: application/json
+@app.post("/")
+def index(it: Bodie): return Titled("It worked!", P(f"{it.a}, {it.b}"))
+
+s = json.dumps({"b": "Lorem", "a": 15})
+response = cli.post('/', headers={"Content-Type": "application/json"}, data=s).text
+assert "<title>It worked!</title>" in response and "<p>15, Lorem</p>" in response
+
+
+
# Testing POST with Content-Type: application/json
+@app.post("/bodytext")
+def index(body): return body
+
+response = cli.post('/bodytext', headers={"Content-Type": "application/json"}, data=s).text
+test_eq(response, '{"b": "Lorem", "a": 15}')
+
+
+
files = [ ('files', ('file1.txt', b'content1')),
+         ('files', ('file2.txt', b'content2')) ]
+
+
+
@rt("/uploads")
+async def post(files:list[UploadFile]):
+    return ','.join([(await file.read()).decode() for file in files])
+
+res = cli.post('/uploads', files=files)
+print(res.status_code)
+print(res.text)
+
+
200
+content1,content2
+
+
+
+
res = cli.post('/uploads', files=[files[0]])
+print(res.status_code)
+print(res.text)
+
+
200
+content1
+
+
+
+
@rt("/setsess")
+def get(sess, foo:str=''):
+    now = datetime.now()
+    sess['auth'] = str(now)
+    return f'Set to {now}'
+
+@rt("/getsess")
+def get(sess): return f'Session time: {sess["auth"]}'
+
+print(cli.get('/setsess').text)
+time.sleep(0.01)
+
+cli.get('/getsess').text
+
+
Set to 2025-05-29 08:31:48.235262
+
+
+
'Session time: 2025-05-29 08:31:48.235262'
+
+
+
+
@rt("/sess-first")
+def post(sess, name: str):
+    sess["name"] = name
+    return str(sess)
+
+cli.post('/sess-first', data={'name': 2})
+
+@rt("/getsess-all")
+def get(sess): return sess['name']
+
+test_eq(cli.get('/getsess-all').text, '2')
+
+
+
@rt("/upload")
+async def post(uf:UploadFile): return (await uf.read()).decode()
+
+with open('../../CHANGELOG.md', 'rb') as f:
+    print(cli.post('/upload', files={'uf':f}, data={'msg':'Hello'}).text[:15])
+
+
# Release notes
+
+
+
+
@rt("/form-submit/{list_id}")
+def options(list_id: str):
+    headers = {
+        'Access-Control-Allow-Origin': '*',
+        'Access-Control-Allow-Methods': 'POST',
+        'Access-Control-Allow-Headers': '*',
+    }
+    return Response(status_code=200, headers=headers)
+
+
+
h = cli.options('/form-submit/2').headers
+test_eq(h['Access-Control-Allow-Methods'], 'POST')
+
+
+
from fasthtml.authmw import user_pwd_auth
+
+
+
def _not_found(req, exc): return Div('nope')
+
+app,cli,rt = get_cli(FastHTML(exception_handlers={404:_not_found}))
+
+txt = cli.get('/').text
+assert '<div>nope</div>' in txt
+assert '<!doctype html>' in txt
+
+
+
app,cli,rt = get_cli(FastHTML())
+
+@rt("/{name}/{age}")
+def get(name: str, age: int):
+    return Titled(f"Hello {name.title()}, age {age}")
+
+assert '<title>Hello Uma, age 5</title>' in cli.get('/uma/5').text
+assert '404 Not Found' in cli.get('/uma/five').text
+
+
+
auth = user_pwd_auth(testuser='spycraft')
+app,cli,rt = get_cli(FastHTML(middleware=[auth]))
+
+@rt("/locked")
+def get(auth): return 'Hello, ' + auth
+
+test_eq(cli.get('/locked').text, 'not authenticated')
+test_eq(cli.get('/locked', auth=("testuser","spycraft")).text, 'Hello, testuser')
+
+
+
auth = user_pwd_auth(testuser='spycraft')
+app,cli,rt = get_cli(FastHTML(middleware=[auth]))
+
+@rt("/locked")
+def get(auth): return 'Hello, ' + auth
+
+test_eq(cli.get('/locked').text, 'not authenticated')
+test_eq(cli.get('/locked', auth=("testuser","spycraft")).text, 'Hello, testuser')
+
+
+
+

APIRouter

+
+

source

+
+

RouteFuncs

+
+
 RouteFuncs ()
+
+

Initialize self. See help(type(self)) for accurate signature.

+
+

source

+
+
+

APIRouter

+
+
 APIRouter (prefix:str|None=None, body_wrap=<function noop_body>)
+
+

Add routes to an app

+
+
ar = APIRouter()
+
+
+
@ar("/hi")
+def get(): return 'Hi there'
+@ar("/hi")
+def post(): return 'Postal'
+@ar
+def ho(): return 'Ho ho'
+@ar("/hostie")
+def show_host(req): return req.headers['host']
+@ar
+def yoyo(): return 'a yoyo'
+@ar
+def index(): return "home page"
+
+@ar.ws("/ws")
+def ws(self, msg:str): return f"Message text was: {msg}"
+
+
+
app,cli,_ = get_cli(FastHTML())
+ar.to_app(app)
+
+
+
assert str(yoyo) == '/yoyo'
+# ensure route functions are properly discoverable on `APIRouter` and `APIRouter.rt_funcs`
+assert ar.prefix == ''
+assert str(ar.rt_funcs.index) == '/'
+assert str(ar.index) == '/'
+with ExceptionExpected(): ar.blah()
+with ExceptionExpected(): ar.rt_funcs.blah()
+# ensure any route functions named using an HTTPMethod are not discoverable via `rt_funcs`
+assert "get" not in ar.rt_funcs._funcs.keys()
+
+
+
test_eq(cli.get('/hi').text, 'Hi there')
+test_eq(cli.post('/hi').text, 'Postal')
+test_eq(cli.get('/hostie').text, 'testserver')
+test_eq(cli.post('/yoyo').text, 'a yoyo')
+
+test_eq(cli.get('/ho').text, 'Ho ho')
+test_eq(cli.post('/ho').text, 'Ho ho')
+
+
+
with cli.websocket_connect('/ws') as ws:
+    ws.send_text('{"msg":"Hi!"}')
+    data = ws.receive_text()
+    assert data == 'Message text was: Hi!'
+
+
+
ar2 = APIRouter("/products")
+
+
+
@ar2("/hi")
+def get(): return 'Hi there'
+@ar2("/hi")
+def post(): return 'Postal'
+@ar2
+def ho(): return 'Ho ho'
+@ar2("/hostie")
+def show_host(req): return req.headers['host']
+@ar2
+def yoyo(): return 'a yoyo'
+@ar2
+def index(): return "home page"
+
+@ar2.ws("/ws")
+def ws(self, msg:str): return f"Message text was: {msg}"
+
+
+
app,cli,_ = get_cli(FastHTML())
+ar2.to_app(app)
+
+
+
assert str(yoyo) == '/products/yoyo'
+assert ar2.prefix == '/products'
+assert str(ar2.rt_funcs.index) == '/products/'
+assert str(ar2.index) == '/products/'
+assert str(ar.index) == '/'
+with ExceptionExpected(): ar2.blah()
+with ExceptionExpected(): ar2.rt_funcs.blah()
+assert "get" not in ar2.rt_funcs._funcs.keys()
+
+
+
test_eq(cli.get('/products/hi').text, 'Hi there')
+test_eq(cli.post('/products/hi').text, 'Postal')
+test_eq(cli.get('/products/hostie').text, 'testserver')
+test_eq(cli.post('/products/yoyo').text, 'a yoyo')
+
+test_eq(cli.get('/products/ho').text, 'Ho ho')
+test_eq(cli.post('/products/ho').text, 'Ho ho')
+
+
+
with cli.websocket_connect('/products/ws') as ws:
+    ws.send_text('{"msg":"Hi!"}')
+    data = ws.receive_text()
+    assert data == 'Message text was: Hi!'
+
+
+
@ar.get
+def hi2(): return 'Hi there'
+@ar.get("/hi3")
+def _(): return 'Hi there'
+@ar.post("/post2")
+def _(): return 'Postal'
+
+@ar2.get
+def hi2(): return 'Hi there'
+@ar2.get("/hi3")
+def _(): return 'Hi there'
+@ar2.post("/post2")
+def _(): return 'Postal'
+
+
+
+
+

Extras

+
+
app,cli,rt = get_cli(FastHTML(secret_key='soopersecret'))
+
+
+

source

+ +
+

reg_re_param

+
+
 reg_re_param (m, s)
+
+
+

source

+
+
+

FastHTML.static_route_exts

+
+
 FastHTML.static_route_exts (prefix='/', static_path='.', exts='static')
+
+

Add a static route at URL path prefix with files from static_path and exts defined by reg_re_param()

+
+
reg_re_param("imgext", "ico|gif|jpg|jpeg|webm|pdf")
+
+@rt(r'/static/{path:path}{fn}.{ext:imgext}')
+def get(fn:str, path:str, ext:str): return f"Getting {fn}.{ext} from /{path}"
+
+test_r(cli, '/static/foo/jph.me.ico', 'Getting jph.me.ico from /foo/')
+
+
+
app.static_route_exts()
+assert 'These are the source notebooks for FastHTML' in cli.get('/README.txt').text
+
+
+

source

+
+
+

FastHTML.static_route

+
+
 FastHTML.static_route (ext='', prefix='/', static_path='.')
+
+

Add a static route at URL path prefix with files from static_path and single ext (including the ‘.’)

+
+
app.static_route('.md', static_path='../..')
+assert 'THIS FILE WAS AUTOGENERATED' in cli.get('/README.md').text
+
+
+

source

+
+
+

MiddlewareBase

+
+
 MiddlewareBase ()
+
+

Initialize self. See help(type(self)) for accurate signature.

+
+

source

+
+
+

FtResponse

+
+
 FtResponse (content, status_code:int=200, headers=None, cls=<class
+             'starlette.responses.HTMLResponse'>,
+             media_type:str|None=None,
+             background:starlette.background.BackgroundTask|None=None)
+
+

Wrap an FT response with any Starlette Response

+
+
@rt('/ftr')
+def get():
+    cts = Title('Foo'),H1('bar')
+    return FtResponse(cts, status_code=201, headers={'Location':'/foo/1'})
+
+r = cli.get('/ftr')
+
+test_eq(r.status_code, 201)
+test_eq(r.headers['location'], '/foo/1')
+txt = r.text
+assert '<title>Foo</title>' in txt and '<h1>bar</h1>' in txt and '<html>' in txt
+
+

Test on a single background task:

+
+
def my_slow_task():
+    print('Starting slow task')    
+    time.sleep(0.001)
+    print('Finished slow task')        
+
+@rt('/background')
+def get():
+    return P('BG Task'), BackgroundTask(my_slow_task)
+
+r = cli.get('/background')
+
+test_eq(r.status_code, 200)
+
+
Starting slow task
+Finished slow task
+
+
+

Test multiple background tasks:

+
+
def increment(amount):
+    amount = amount/1000
+    print(f'Sleeping for {amount}s')    
+    time.sleep(amount)
+    print(f'Slept for {amount}s')
+
+
+
@rt
+def backgrounds():
+    tasks = BackgroundTasks()
+    for i in range(3): tasks.add_task(increment, i)
+    return P('BG Tasks'), tasks
+
+r = cli.get('/backgrounds')
+test_eq(r.status_code, 200)
+
+
Sleeping for 0.0s
+Slept for 0.0s
+Sleeping for 0.001s
+Slept for 0.001s
+Sleeping for 0.002s
+Slept for 0.002s
+
+
+
+
@rt
+def backgrounds2():
+    tasks = [BackgroundTask(increment,i) for i in range(3)]
+    return P('BG Tasks'), *tasks
+
+r = cli.get('/backgrounds2')
+test_eq(r.status_code, 200)
+
+
Sleeping for 0.0s
+Slept for 0.0s
+Sleeping for 0.001s
+Slept for 0.001s
+Sleeping for 0.002s
+Slept for 0.002s
+
+
+
+
@rt
+def backgrounds3():
+    tasks = [BackgroundTask(increment,i) for i in range(3)]
+    return {'status':'done'}, *tasks
+
+r = cli.get('/backgrounds3')
+test_eq(r.status_code, 200)
+r.json()
+
+
Sleeping for 0.0s
+Slept for 0.0s
+Sleeping for 0.001s
+Slept for 0.001s
+Sleeping for 0.002s
+Slept for 0.002s
+
+
+
{'status': 'done'}
+
+
+
+

source

+
+
+

unqid

+
+
 unqid (seeded=False)
+
+
+

source

+
+
+

FastHTML.setup_ws

+
+
 FastHTML.setup_ws (app:__main__.FastHTML, f=<function noop>)
+
+
+

source

+
+
+

FastHTML.devtools_json

+
+
 FastHTML.devtools_json (path=None, uuid=None)
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/vendor/fasthtml/api-pico.html b/docs/vendor/fasthtml/api-pico.html new file mode 100644 index 0000000..d765ce4 --- /dev/null +++ b/docs/vendor/fasthtml/api-pico.html @@ -0,0 +1,1284 @@ + + + + + + + + + + +Pico.css components – fasthtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Pico.css components

+
+ +
+
+ Basic components for generating Pico CSS tags +
+
+ + +
+ + + + +
+ + + +
+ + + +

picocondlink is the class-conditional css link tag, and picolink is the regular tag.

+
+
show(picocondlink)
+
+ + +
+
+
+

source

+
+

set_pico_cls

+
+
 set_pico_cls ()
+
+

Run this to make jupyter outputs styled with pico:

+
+
set_pico_cls()
+
+ +
+
+
+

source

+
+
+

Card

+
+
 Card (*c, header=None, footer=None, target_id=None, hx_vals=None,
+       hx_target=None, id=None, cls=None, title=None, style=None,
+       accesskey=None, contenteditable=None, dir=None, draggable=None,
+       enterkeyhint=None, hidden=None, inert=None, inputmode=None,
+       lang=None, popover=None, spellcheck=None, tabindex=None,
+       translate=None, hx_get=None, hx_post=None, hx_put=None,
+       hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap=None,
+       hx_swap_oob=None, hx_include=None, hx_select=None,
+       hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+       hx_confirm=None, hx_disable=None, hx_replace_url=None,
+       hx_disabled_elt=None, hx_ext=None, hx_headers=None,
+       hx_history=None, hx_history_elt=None, hx_inherit=None,
+       hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None,
+       hx_sync=None, hx_validate=None, hx_on_blur=None, hx_on_change=None,
+       hx_on_contextmenu=None, hx_on_focus=None, hx_on_input=None,
+       hx_on_invalid=None, hx_on_reset=None, hx_on_select=None,
+       hx_on_submit=None, hx_on_keydown=None, hx_on_keypress=None,
+       hx_on_keyup=None, hx_on_click=None, hx_on_dblclick=None,
+       hx_on_mousedown=None, hx_on_mouseenter=None, hx_on_mouseleave=None,
+       hx_on_mousemove=None, hx_on_mouseout=None, hx_on_mouseover=None,
+       hx_on_mouseup=None, hx_on_wheel=None, hx_on__abort=None,
+       hx_on__after_on_load=None, hx_on__after_process_node=None,
+       hx_on__after_request=None, hx_on__after_settle=None,
+       hx_on__after_swap=None, hx_on__before_cleanup_element=None,
+       hx_on__before_on_load=None, hx_on__before_process_node=None,
+       hx_on__before_request=None, hx_on__before_swap=None,
+       hx_on__before_send=None, hx_on__before_transition=None,
+       hx_on__config_request=None, hx_on__confirm=None,
+       hx_on__history_cache_error=None, hx_on__history_cache_miss=None,
+       hx_on__history_cache_miss_error=None,
+       hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+       hx_on__before_history_save=None, hx_on__load=None,
+       hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+       hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+       hx_on__oob_error_no_target=None, hx_on__prompt=None,
+       hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+       hx_on__response_error=None, hx_on__send_abort=None,
+       hx_on__send_error=None, hx_on__sse_error=None,
+       hx_on__sse_open=None, hx_on__swap_error=None,
+       hx_on__target_error=None, hx_on__timeout=None,
+       hx_on__validation_validate=None, hx_on__validation_failed=None,
+       hx_on__validation_halted=None, hx_on__xhr_abort=None,
+       hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+       hx_on__xhr_progress=None, **kwargs)
+
+

A PicoCSS Card, implemented as an Article with optional Header and Footer

+
+
show(Card('body', header=P('head'), footer=P('foot')))
+
+
+

head

+
+body +

foot

+
+
+
+
+
+

source

+
+
+

Group

+
+
 Group (*c, target_id=None, hx_vals=None, hx_target=None, id=None,
+        cls=None, title=None, style=None, accesskey=None,
+        contenteditable=None, dir=None, draggable=None, enterkeyhint=None,
+        hidden=None, inert=None, inputmode=None, lang=None, popover=None,
+        spellcheck=None, tabindex=None, translate=None, hx_get=None,
+        hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+        hx_trigger=None, hx_swap=None, hx_swap_oob=None, hx_include=None,
+        hx_select=None, hx_select_oob=None, hx_indicator=None,
+        hx_push_url=None, hx_confirm=None, hx_disable=None,
+        hx_replace_url=None, hx_disabled_elt=None, hx_ext=None,
+        hx_headers=None, hx_history=None, hx_history_elt=None,
+        hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None,
+        hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+        hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+        hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+        hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+        hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+        hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+        hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+        hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+        hx_on__abort=None, hx_on__after_on_load=None,
+        hx_on__after_process_node=None, hx_on__after_request=None,
+        hx_on__after_settle=None, hx_on__after_swap=None,
+        hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+        hx_on__before_process_node=None, hx_on__before_request=None,
+        hx_on__before_swap=None, hx_on__before_send=None,
+        hx_on__before_transition=None, hx_on__config_request=None,
+        hx_on__confirm=None, hx_on__history_cache_error=None,
+        hx_on__history_cache_miss=None,
+        hx_on__history_cache_miss_error=None,
+        hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+        hx_on__before_history_save=None, hx_on__load=None,
+        hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+        hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+        hx_on__oob_error_no_target=None, hx_on__prompt=None,
+        hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+        hx_on__response_error=None, hx_on__send_abort=None,
+        hx_on__send_error=None, hx_on__sse_error=None,
+        hx_on__sse_open=None, hx_on__swap_error=None,
+        hx_on__target_error=None, hx_on__timeout=None,
+        hx_on__validation_validate=None, hx_on__validation_failed=None,
+        hx_on__validation_halted=None, hx_on__xhr_abort=None,
+        hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+        hx_on__xhr_progress=None, **kwargs)
+
+

A PicoCSS Group, implemented as a Fieldset with role ‘group’

+
+
show(Group(Input(), Button("Save")))
+
+
+ + +
+
+
+
+

source

+
+ +
+

Grid

+
+
 Grid (*c, cls='grid', target_id=None, hx_vals=None, hx_target=None,
+       id=None, title=None, style=None, accesskey=None,
+       contenteditable=None, dir=None, draggable=None, enterkeyhint=None,
+       hidden=None, inert=None, inputmode=None, lang=None, popover=None,
+       spellcheck=None, tabindex=None, translate=None, hx_get=None,
+       hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+       hx_trigger=None, hx_swap=None, hx_swap_oob=None, hx_include=None,
+       hx_select=None, hx_select_oob=None, hx_indicator=None,
+       hx_push_url=None, hx_confirm=None, hx_disable=None,
+       hx_replace_url=None, hx_disabled_elt=None, hx_ext=None,
+       hx_headers=None, hx_history=None, hx_history_elt=None,
+       hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None,
+       hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+       hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+       hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+       hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+       hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+       hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+       hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+       hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+       hx_on__abort=None, hx_on__after_on_load=None,
+       hx_on__after_process_node=None, hx_on__after_request=None,
+       hx_on__after_settle=None, hx_on__after_swap=None,
+       hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+       hx_on__before_process_node=None, hx_on__before_request=None,
+       hx_on__before_swap=None, hx_on__before_send=None,
+       hx_on__before_transition=None, hx_on__config_request=None,
+       hx_on__confirm=None, hx_on__history_cache_error=None,
+       hx_on__history_cache_miss=None,
+       hx_on__history_cache_miss_error=None,
+       hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+       hx_on__before_history_save=None, hx_on__load=None,
+       hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+       hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+       hx_on__oob_error_no_target=None, hx_on__prompt=None,
+       hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+       hx_on__response_error=None, hx_on__send_abort=None,
+       hx_on__send_error=None, hx_on__sse_error=None,
+       hx_on__sse_open=None, hx_on__swap_error=None,
+       hx_on__target_error=None, hx_on__timeout=None,
+       hx_on__validation_validate=None, hx_on__validation_failed=None,
+       hx_on__validation_halted=None, hx_on__xhr_abort=None,
+       hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+       hx_on__xhr_progress=None, **kwargs)
+
+

A PicoCSS Grid, implemented as child Divs in a Div with class ‘grid’

+
+
colors = [Input(type="color", value=o) for o in ('#e66465', '#53d2c5', '#f6b73c')]
+show(Grid(*colors))
+
+
+
+
+
+
+
+
+
+
+
+
+

source

+
+
+

DialogX

+
+
 DialogX (*c, open=None, header=None, footer=None, id=None,
+          target_id=None, hx_vals=None, hx_target=None, cls=None,
+          title=None, style=None, accesskey=None, contenteditable=None,
+          dir=None, draggable=None, enterkeyhint=None, hidden=None,
+          inert=None, inputmode=None, lang=None, popover=None,
+          spellcheck=None, tabindex=None, translate=None, hx_get=None,
+          hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+          hx_trigger=None, hx_swap=None, hx_swap_oob=None,
+          hx_include=None, hx_select=None, hx_select_oob=None,
+          hx_indicator=None, hx_push_url=None, hx_confirm=None,
+          hx_disable=None, hx_replace_url=None, hx_disabled_elt=None,
+          hx_ext=None, hx_headers=None, hx_history=None,
+          hx_history_elt=None, hx_inherit=None, hx_params=None,
+          hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None,
+          hx_validate=None, hx_on_blur=None, hx_on_change=None,
+          hx_on_contextmenu=None, hx_on_focus=None, hx_on_input=None,
+          hx_on_invalid=None, hx_on_reset=None, hx_on_select=None,
+          hx_on_submit=None, hx_on_keydown=None, hx_on_keypress=None,
+          hx_on_keyup=None, hx_on_click=None, hx_on_dblclick=None,
+          hx_on_mousedown=None, hx_on_mouseenter=None,
+          hx_on_mouseleave=None, hx_on_mousemove=None,
+          hx_on_mouseout=None, hx_on_mouseover=None, hx_on_mouseup=None,
+          hx_on_wheel=None, hx_on__abort=None, hx_on__after_on_load=None,
+          hx_on__after_process_node=None, hx_on__after_request=None,
+          hx_on__after_settle=None, hx_on__after_swap=None,
+          hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+          hx_on__before_process_node=None, hx_on__before_request=None,
+          hx_on__before_swap=None, hx_on__before_send=None,
+          hx_on__before_transition=None, hx_on__config_request=None,
+          hx_on__confirm=None, hx_on__history_cache_error=None,
+          hx_on__history_cache_miss=None,
+          hx_on__history_cache_miss_error=None,
+          hx_on__history_cache_miss_load=None,
+          hx_on__history_restore=None, hx_on__before_history_save=None,
+          hx_on__load=None, hx_on__no_sse_source_error=None,
+          hx_on__on_load_error=None, hx_on__oob_after_swap=None,
+          hx_on__oob_before_swap=None, hx_on__oob_error_no_target=None,
+          hx_on__prompt=None, hx_on__pushed_into_history=None,
+          hx_on__replaced_in_history=None, hx_on__response_error=None,
+          hx_on__send_abort=None, hx_on__send_error=None,
+          hx_on__sse_error=None, hx_on__sse_open=None,
+          hx_on__swap_error=None, hx_on__target_error=None,
+          hx_on__timeout=None, hx_on__validation_validate=None,
+          hx_on__validation_failed=None, hx_on__validation_halted=None,
+          hx_on__xhr_abort=None, hx_on__xhr_loadend=None,
+          hx_on__xhr_loadstart=None, hx_on__xhr_progress=None, **kwargs)
+
+

A PicoCSS Dialog, with children inside a Card

+
+
hdr = Div(Button(aria_label="Close", rel="prev"), P('confirm'))
+ftr = Div(Button('Cancel', cls="secondary"), Button('Confirm'))
+d = DialogX('thank you!', header=hdr, footer=ftr, open=None, id='dlgtest')
+# use js or htmx to display modal
+
+
+

source

+
+
+

Container

+
+
 Container (*args, target_id=None, hx_vals=None, hx_target=None, id=None,
+            cls=None, title=None, style=None, accesskey=None,
+            contenteditable=None, dir=None, draggable=None,
+            enterkeyhint=None, hidden=None, inert=None, inputmode=None,
+            lang=None, popover=None, spellcheck=None, tabindex=None,
+            translate=None, hx_get=None, hx_post=None, hx_put=None,
+            hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap=None,
+            hx_swap_oob=None, hx_include=None, hx_select=None,
+            hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+            hx_confirm=None, hx_disable=None, hx_replace_url=None,
+            hx_disabled_elt=None, hx_ext=None, hx_headers=None,
+            hx_history=None, hx_history_elt=None, hx_inherit=None,
+            hx_params=None, hx_preserve=None, hx_prompt=None,
+            hx_request=None, hx_sync=None, hx_validate=None,
+            hx_on_blur=None, hx_on_change=None, hx_on_contextmenu=None,
+            hx_on_focus=None, hx_on_input=None, hx_on_invalid=None,
+            hx_on_reset=None, hx_on_select=None, hx_on_submit=None,
+            hx_on_keydown=None, hx_on_keypress=None, hx_on_keyup=None,
+            hx_on_click=None, hx_on_dblclick=None, hx_on_mousedown=None,
+            hx_on_mouseenter=None, hx_on_mouseleave=None,
+            hx_on_mousemove=None, hx_on_mouseout=None,
+            hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+            hx_on__abort=None, hx_on__after_on_load=None,
+            hx_on__after_process_node=None, hx_on__after_request=None,
+            hx_on__after_settle=None, hx_on__after_swap=None,
+            hx_on__before_cleanup_element=None,
+            hx_on__before_on_load=None, hx_on__before_process_node=None,
+            hx_on__before_request=None, hx_on__before_swap=None,
+            hx_on__before_send=None, hx_on__before_transition=None,
+            hx_on__config_request=None, hx_on__confirm=None,
+            hx_on__history_cache_error=None,
+            hx_on__history_cache_miss=None,
+            hx_on__history_cache_miss_error=None,
+            hx_on__history_cache_miss_load=None,
+            hx_on__history_restore=None, hx_on__before_history_save=None,
+            hx_on__load=None, hx_on__no_sse_source_error=None,
+            hx_on__on_load_error=None, hx_on__oob_after_swap=None,
+            hx_on__oob_before_swap=None, hx_on__oob_error_no_target=None,
+            hx_on__prompt=None, hx_on__pushed_into_history=None,
+            hx_on__replaced_in_history=None, hx_on__response_error=None,
+            hx_on__send_abort=None, hx_on__send_error=None,
+            hx_on__sse_error=None, hx_on__sse_open=None,
+            hx_on__swap_error=None, hx_on__target_error=None,
+            hx_on__timeout=None, hx_on__validation_validate=None,
+            hx_on__validation_failed=None, hx_on__validation_halted=None,
+            hx_on__xhr_abort=None, hx_on__xhr_loadend=None,
+            hx_on__xhr_loadstart=None, hx_on__xhr_progress=None, **kwargs)
+
+

A PicoCSS Container, implemented as a Main with class ‘container’

+
+

source

+
+
+

PicoBusy

+
+
 PicoBusy ()
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/vendor/fasthtml/api-xtend.html b/docs/vendor/fasthtml/api-xtend.html new file mode 100644 index 0000000..cd2d3e1 --- /dev/null +++ b/docs/vendor/fasthtml/api-xtend.html @@ -0,0 +1,1482 @@ + + + + + + + + + + +Component extensions – fasthtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Component extensions

+
+ +
+
+ Simple extensions to standard HTML components, such as adding sensible defaults +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from pprint import pprint
+
+
+

source

+
+

A

+
+
 A (*c, hx_get=None, target_id=None, hx_swap=None, href='#', hx_vals=None,
+    hx_target=None, id=None, cls=None, title=None, style=None,
+    accesskey=None, contenteditable=None, dir=None, draggable=None,
+    enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None,
+    popover=None, spellcheck=None, tabindex=None, translate=None,
+    hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+    hx_trigger=None, hx_swap_oob=None, hx_include=None, hx_select=None,
+    hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+    hx_confirm=None, hx_disable=None, hx_replace_url=None,
+    hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None,
+    hx_history_elt=None, hx_inherit=None, hx_params=None,
+    hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None,
+    hx_validate=None, hx_on_blur=None, hx_on_change=None,
+    hx_on_contextmenu=None, hx_on_focus=None, hx_on_input=None,
+    hx_on_invalid=None, hx_on_reset=None, hx_on_select=None,
+    hx_on_submit=None, hx_on_keydown=None, hx_on_keypress=None,
+    hx_on_keyup=None, hx_on_click=None, hx_on_dblclick=None,
+    hx_on_mousedown=None, hx_on_mouseenter=None, hx_on_mouseleave=None,
+    hx_on_mousemove=None, hx_on_mouseout=None, hx_on_mouseover=None,
+    hx_on_mouseup=None, hx_on_wheel=None, hx_on__abort=None,
+    hx_on__after_on_load=None, hx_on__after_process_node=None,
+    hx_on__after_request=None, hx_on__after_settle=None,
+    hx_on__after_swap=None, hx_on__before_cleanup_element=None,
+    hx_on__before_on_load=None, hx_on__before_process_node=None,
+    hx_on__before_request=None, hx_on__before_swap=None,
+    hx_on__before_send=None, hx_on__before_transition=None,
+    hx_on__config_request=None, hx_on__confirm=None,
+    hx_on__history_cache_error=None, hx_on__history_cache_miss=None,
+    hx_on__history_cache_miss_error=None,
+    hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+    hx_on__before_history_save=None, hx_on__load=None,
+    hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+    hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+    hx_on__oob_error_no_target=None, hx_on__prompt=None,
+    hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+    hx_on__response_error=None, hx_on__send_abort=None,
+    hx_on__send_error=None, hx_on__sse_error=None, hx_on__sse_open=None,
+    hx_on__swap_error=None, hx_on__target_error=None, hx_on__timeout=None,
+    hx_on__validation_validate=None, hx_on__validation_failed=None,
+    hx_on__validation_halted=None, hx_on__xhr_abort=None,
+    hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+    hx_on__xhr_progress=None, **kwargs)
+
+

An A tag; href defaults to ‘#’ for more concise use with HTMX

+
+
A('text', ht_get='/get', target_id='id')
+
+
<a href="#" ht-get="/get" hx-target="#id">text</a>
+
+
+
+

source

+
+
+

AX

+
+
 AX (txt, hx_get=None, target_id=None, hx_swap=None, href='#',
+     hx_vals=None, hx_target=None, id=None, cls=None, title=None,
+     style=None, accesskey=None, contenteditable=None, dir=None,
+     draggable=None, enterkeyhint=None, hidden=None, inert=None,
+     inputmode=None, lang=None, popover=None, spellcheck=None,
+     tabindex=None, translate=None, hx_post=None, hx_put=None,
+     hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap_oob=None,
+     hx_include=None, hx_select=None, hx_select_oob=None,
+     hx_indicator=None, hx_push_url=None, hx_confirm=None,
+     hx_disable=None, hx_replace_url=None, hx_disabled_elt=None,
+     hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None,
+     hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None,
+     hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+     hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+     hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+     hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+     hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+     hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+     hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+     hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+     hx_on__abort=None, hx_on__after_on_load=None,
+     hx_on__after_process_node=None, hx_on__after_request=None,
+     hx_on__after_settle=None, hx_on__after_swap=None,
+     hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+     hx_on__before_process_node=None, hx_on__before_request=None,
+     hx_on__before_swap=None, hx_on__before_send=None,
+     hx_on__before_transition=None, hx_on__config_request=None,
+     hx_on__confirm=None, hx_on__history_cache_error=None,
+     hx_on__history_cache_miss=None, hx_on__history_cache_miss_error=None,
+     hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+     hx_on__before_history_save=None, hx_on__load=None,
+     hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+     hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+     hx_on__oob_error_no_target=None, hx_on__prompt=None,
+     hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+     hx_on__response_error=None, hx_on__send_abort=None,
+     hx_on__send_error=None, hx_on__sse_error=None, hx_on__sse_open=None,
+     hx_on__swap_error=None, hx_on__target_error=None,
+     hx_on__timeout=None, hx_on__validation_validate=None,
+     hx_on__validation_failed=None, hx_on__validation_halted=None,
+     hx_on__xhr_abort=None, hx_on__xhr_loadend=None,
+     hx_on__xhr_loadstart=None, hx_on__xhr_progress=None, **kwargs)
+
+

An A tag with just one text child, allowing hx_get, target_id, and hx_swap to be positional params

+
+
AX('text', '/get', 'id')
+
+
<a href="#" hx-get="/get" hx-target="#id">text</a>
+
+
+
+
+

Forms

+
+

source

+
+

Form

+
+
 Form (*c, enctype='multipart/form-data', target_id=None, hx_vals=None,
+       hx_target=None, id=None, cls=None, title=None, style=None,
+       accesskey=None, contenteditable=None, dir=None, draggable=None,
+       enterkeyhint=None, hidden=None, inert=None, inputmode=None,
+       lang=None, popover=None, spellcheck=None, tabindex=None,
+       translate=None, hx_get=None, hx_post=None, hx_put=None,
+       hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap=None,
+       hx_swap_oob=None, hx_include=None, hx_select=None,
+       hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+       hx_confirm=None, hx_disable=None, hx_replace_url=None,
+       hx_disabled_elt=None, hx_ext=None, hx_headers=None,
+       hx_history=None, hx_history_elt=None, hx_inherit=None,
+       hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None,
+       hx_sync=None, hx_validate=None, hx_on_blur=None, hx_on_change=None,
+       hx_on_contextmenu=None, hx_on_focus=None, hx_on_input=None,
+       hx_on_invalid=None, hx_on_reset=None, hx_on_select=None,
+       hx_on_submit=None, hx_on_keydown=None, hx_on_keypress=None,
+       hx_on_keyup=None, hx_on_click=None, hx_on_dblclick=None,
+       hx_on_mousedown=None, hx_on_mouseenter=None, hx_on_mouseleave=None,
+       hx_on_mousemove=None, hx_on_mouseout=None, hx_on_mouseover=None,
+       hx_on_mouseup=None, hx_on_wheel=None, hx_on__abort=None,
+       hx_on__after_on_load=None, hx_on__after_process_node=None,
+       hx_on__after_request=None, hx_on__after_settle=None,
+       hx_on__after_swap=None, hx_on__before_cleanup_element=None,
+       hx_on__before_on_load=None, hx_on__before_process_node=None,
+       hx_on__before_request=None, hx_on__before_swap=None,
+       hx_on__before_send=None, hx_on__before_transition=None,
+       hx_on__config_request=None, hx_on__confirm=None,
+       hx_on__history_cache_error=None, hx_on__history_cache_miss=None,
+       hx_on__history_cache_miss_error=None,
+       hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+       hx_on__before_history_save=None, hx_on__load=None,
+       hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+       hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+       hx_on__oob_error_no_target=None, hx_on__prompt=None,
+       hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+       hx_on__response_error=None, hx_on__send_abort=None,
+       hx_on__send_error=None, hx_on__sse_error=None,
+       hx_on__sse_open=None, hx_on__swap_error=None,
+       hx_on__target_error=None, hx_on__timeout=None,
+       hx_on__validation_validate=None, hx_on__validation_failed=None,
+       hx_on__validation_halted=None, hx_on__xhr_abort=None,
+       hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+       hx_on__xhr_progress=None, **kwargs)
+
+

A Form tag; identical to plain ft_hx version except default enctype='multipart/form-data'

+
+

source

+
+
+

Hidden

+
+
 Hidden (value:Any='', id:Any=None, target_id=None, hx_vals=None,
+         hx_target=None, cls=None, title=None, style=None, accesskey=None,
+         contenteditable=None, dir=None, draggable=None,
+         enterkeyhint=None, hidden=None, inert=None, inputmode=None,
+         lang=None, popover=None, spellcheck=None, tabindex=None,
+         translate=None, hx_get=None, hx_post=None, hx_put=None,
+         hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap=None,
+         hx_swap_oob=None, hx_include=None, hx_select=None,
+         hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+         hx_confirm=None, hx_disable=None, hx_replace_url=None,
+         hx_disabled_elt=None, hx_ext=None, hx_headers=None,
+         hx_history=None, hx_history_elt=None, hx_inherit=None,
+         hx_params=None, hx_preserve=None, hx_prompt=None,
+         hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+         hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+         hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+         hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+         hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+         hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+         hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+         hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+         hx_on__abort=None, hx_on__after_on_load=None,
+         hx_on__after_process_node=None, hx_on__after_request=None,
+         hx_on__after_settle=None, hx_on__after_swap=None,
+         hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+         hx_on__before_process_node=None, hx_on__before_request=None,
+         hx_on__before_swap=None, hx_on__before_send=None,
+         hx_on__before_transition=None, hx_on__config_request=None,
+         hx_on__confirm=None, hx_on__history_cache_error=None,
+         hx_on__history_cache_miss=None,
+         hx_on__history_cache_miss_error=None,
+         hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+         hx_on__before_history_save=None, hx_on__load=None,
+         hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+         hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+         hx_on__oob_error_no_target=None, hx_on__prompt=None,
+         hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+         hx_on__response_error=None, hx_on__send_abort=None,
+         hx_on__send_error=None, hx_on__sse_error=None,
+         hx_on__sse_open=None, hx_on__swap_error=None,
+         hx_on__target_error=None, hx_on__timeout=None,
+         hx_on__validation_validate=None, hx_on__validation_failed=None,
+         hx_on__validation_halted=None, hx_on__xhr_abort=None,
+         hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+         hx_on__xhr_progress=None, **kwargs)
+
+

An Input of type ‘hidden’

+
+

source

+
+
+

CheckboxX

+
+
 CheckboxX (checked:bool=False, label=None, value='1', id=None, name=None,
+            target_id=None, hx_vals=None, hx_target=None, cls=None,
+            title=None, style=None, accesskey=None, contenteditable=None,
+            dir=None, draggable=None, enterkeyhint=None, hidden=None,
+            inert=None, inputmode=None, lang=None, popover=None,
+            spellcheck=None, tabindex=None, translate=None, hx_get=None,
+            hx_post=None, hx_put=None, hx_delete=None, hx_patch=None,
+            hx_trigger=None, hx_swap=None, hx_swap_oob=None,
+            hx_include=None, hx_select=None, hx_select_oob=None,
+            hx_indicator=None, hx_push_url=None, hx_confirm=None,
+            hx_disable=None, hx_replace_url=None, hx_disabled_elt=None,
+            hx_ext=None, hx_headers=None, hx_history=None,
+            hx_history_elt=None, hx_inherit=None, hx_params=None,
+            hx_preserve=None, hx_prompt=None, hx_request=None,
+            hx_sync=None, hx_validate=None, hx_on_blur=None,
+            hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+            hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+            hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+            hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+            hx_on_dblclick=None, hx_on_mousedown=None,
+            hx_on_mouseenter=None, hx_on_mouseleave=None,
+            hx_on_mousemove=None, hx_on_mouseout=None,
+            hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+            hx_on__abort=None, hx_on__after_on_load=None,
+            hx_on__after_process_node=None, hx_on__after_request=None,
+            hx_on__after_settle=None, hx_on__after_swap=None,
+            hx_on__before_cleanup_element=None,
+            hx_on__before_on_load=None, hx_on__before_process_node=None,
+            hx_on__before_request=None, hx_on__before_swap=None,
+            hx_on__before_send=None, hx_on__before_transition=None,
+            hx_on__config_request=None, hx_on__confirm=None,
+            hx_on__history_cache_error=None,
+            hx_on__history_cache_miss=None,
+            hx_on__history_cache_miss_error=None,
+            hx_on__history_cache_miss_load=None,
+            hx_on__history_restore=None, hx_on__before_history_save=None,
+            hx_on__load=None, hx_on__no_sse_source_error=None,
+            hx_on__on_load_error=None, hx_on__oob_after_swap=None,
+            hx_on__oob_before_swap=None, hx_on__oob_error_no_target=None,
+            hx_on__prompt=None, hx_on__pushed_into_history=None,
+            hx_on__replaced_in_history=None, hx_on__response_error=None,
+            hx_on__send_abort=None, hx_on__send_error=None,
+            hx_on__sse_error=None, hx_on__sse_open=None,
+            hx_on__swap_error=None, hx_on__target_error=None,
+            hx_on__timeout=None, hx_on__validation_validate=None,
+            hx_on__validation_failed=None, hx_on__validation_halted=None,
+            hx_on__xhr_abort=None, hx_on__xhr_loadend=None,
+            hx_on__xhr_loadstart=None, hx_on__xhr_progress=None, **kwargs)
+
+

A Checkbox optionally inside a Label, preceded by a Hidden with matching name

+
+
show(CheckboxX(True, 'Check me out!'))
+
+ + +
+
+
+

source

+
+
+

Script

+
+
 Script (code:str='', id=None, cls=None, title=None, style=None,
+         attrmap=None, valmap=None, ft_cls=None, **kwargs)
+
+

A Script tag that doesn’t escape its code

+
+

source

+
+
+

Style

+
+
 Style (*c, id=None, cls=None, title=None, style=None, attrmap=None,
+        valmap=None, ft_cls=None, **kwargs)
+
+

A Style tag that doesn’t escape its code

+
+
+
+

Style and script templates

+
+

source

+
+

double_braces

+
+
 double_braces (s)
+
+

Convert single braces to double braces if next to special chars or newline

+
+

source

+
+
+

undouble_braces

+
+
 undouble_braces (s)
+
+

Convert double braces to single braces if next to special chars or newline

+
+

source

+
+
+

loose_format

+
+
 loose_format (s, **kw)
+
+

String format s using kw, without being strict about braces outside of template params

+
+

source

+
+
+

ScriptX

+
+
 ScriptX (fname, src=None, nomodule=None, type=None, _async=None,
+          defer=None, charset=None, crossorigin=None, integrity=None,
+          **kw)
+
+

A script element with contents read from fname

+
+

source

+
+
+

replace_css_vars

+
+
 replace_css_vars (css, pre='tpl', **kwargs)
+
+

Replace var(--) CSS variables with kwargs if name prefix matches pre

+
+

source

+
+
+

StyleX

+
+
 StyleX (fname, **kw)
+
+

A style element with contents read from fname and variables replaced from kw

+
+

source

+
+
+

Nbsp

+
+
 Nbsp ()
+
+

A non-breaking space

+
+
+
+

Surreal and JS

+
+

source

+
+

Surreal

+
+
 Surreal (code:str)
+
+

Wrap code in domReadyExecute and set m=me() and p=me('-')

+
+

source

+
+
+

On

+
+
 On (code:str, event:str='click', sel:str='', me=True)
+
+

An async surreal.js script block event handler for event on selector sel,p, making available parent p, event ev, and target e

+
+

source

+
+
+

Prev

+
+
 Prev (code:str, event:str='click')
+
+

An async surreal.js script block event handler for event on previous sibling, with same vars as On

+
+

source

+
+
+

Now

+
+
 Now (code:str, sel:str='')
+
+

An async surreal.js script block on selector me(sel)

+
+

source

+
+
+

AnyNow

+
+
 AnyNow (sel:str, code:str)
+
+

An async surreal.js script block on selector any(sel)

+
+

source

+
+
+

run_js

+
+
 run_js (js, id=None, **kw)
+
+

Run js script, auto-generating id based on name of caller if needed, and js-escaping any kw params

+
+

source

+
+
+

HtmxOn

+
+
 HtmxOn (eventname:str, code:str)
+
+
+

source

+
+
+

jsd

+
+
 jsd (org, repo, root, path, prov='gh', typ='script', ver=None, esm=False,
+      **kwargs)
+
+

jsdelivr Script or CSS Link tag, or URL

+
+
+
+

Other helpers

+
+

source

+
+

Fragment

+
+
 Fragment (*c)
+
+

An empty tag, used as a container

+
+
fts = Fragment(P('1st'), P('2nd'))
+print(to_xml(fts))
+
+
  <p>1st</p>
+  <p>2nd</p>
+
+
+
+
+

source

+
+
+

Titled

+
+
 Titled (title:str='FastHTML app', *args, cls='container', target_id=None,
+         hx_vals=None, hx_target=None, id=None, style=None,
+         accesskey=None, contenteditable=None, dir=None, draggable=None,
+         enterkeyhint=None, hidden=None, inert=None, inputmode=None,
+         lang=None, popover=None, spellcheck=None, tabindex=None,
+         translate=None, hx_get=None, hx_post=None, hx_put=None,
+         hx_delete=None, hx_patch=None, hx_trigger=None, hx_swap=None,
+         hx_swap_oob=None, hx_include=None, hx_select=None,
+         hx_select_oob=None, hx_indicator=None, hx_push_url=None,
+         hx_confirm=None, hx_disable=None, hx_replace_url=None,
+         hx_disabled_elt=None, hx_ext=None, hx_headers=None,
+         hx_history=None, hx_history_elt=None, hx_inherit=None,
+         hx_params=None, hx_preserve=None, hx_prompt=None,
+         hx_request=None, hx_sync=None, hx_validate=None, hx_on_blur=None,
+         hx_on_change=None, hx_on_contextmenu=None, hx_on_focus=None,
+         hx_on_input=None, hx_on_invalid=None, hx_on_reset=None,
+         hx_on_select=None, hx_on_submit=None, hx_on_keydown=None,
+         hx_on_keypress=None, hx_on_keyup=None, hx_on_click=None,
+         hx_on_dblclick=None, hx_on_mousedown=None, hx_on_mouseenter=None,
+         hx_on_mouseleave=None, hx_on_mousemove=None, hx_on_mouseout=None,
+         hx_on_mouseover=None, hx_on_mouseup=None, hx_on_wheel=None,
+         hx_on__abort=None, hx_on__after_on_load=None,
+         hx_on__after_process_node=None, hx_on__after_request=None,
+         hx_on__after_settle=None, hx_on__after_swap=None,
+         hx_on__before_cleanup_element=None, hx_on__before_on_load=None,
+         hx_on__before_process_node=None, hx_on__before_request=None,
+         hx_on__before_swap=None, hx_on__before_send=None,
+         hx_on__before_transition=None, hx_on__config_request=None,
+         hx_on__confirm=None, hx_on__history_cache_error=None,
+         hx_on__history_cache_miss=None,
+         hx_on__history_cache_miss_error=None,
+         hx_on__history_cache_miss_load=None, hx_on__history_restore=None,
+         hx_on__before_history_save=None, hx_on__load=None,
+         hx_on__no_sse_source_error=None, hx_on__on_load_error=None,
+         hx_on__oob_after_swap=None, hx_on__oob_before_swap=None,
+         hx_on__oob_error_no_target=None, hx_on__prompt=None,
+         hx_on__pushed_into_history=None, hx_on__replaced_in_history=None,
+         hx_on__response_error=None, hx_on__send_abort=None,
+         hx_on__send_error=None, hx_on__sse_error=None,
+         hx_on__sse_open=None, hx_on__swap_error=None,
+         hx_on__target_error=None, hx_on__timeout=None,
+         hx_on__validation_validate=None, hx_on__validation_failed=None,
+         hx_on__validation_halted=None, hx_on__xhr_abort=None,
+         hx_on__xhr_loadend=None, hx_on__xhr_loadstart=None,
+         hx_on__xhr_progress=None, **kwargs)
+
+

An HTML partial containing a Title, and H1, and any provided children

+
+
show(Titled('my page', P('para')))
+
+my page +

my page

+

para

+
+
+
+
+

source

+
+
+

Socials

+
+
 Socials (title, site_name, description, image, url=None, w=1200, h=630,
+          twitter_site=None, creator=None, card='summary')
+
+

OG and Twitter social card headers

+
+

source

+
+
+

YouTubeEmbed

+
+
 YouTubeEmbed (video_id:str, width:int=560, height:int=315,
+               start_time:int=0, no_controls:bool=False,
+               title:str='YouTube video player', cls:str='', **kwargs)
+
+

Embed a YouTube video

+
+

source

+
+
+

Favicon

+
+
 Favicon (light_icon, dark_icon)
+
+

Light and dark favicon headers

+
+

source

+
+
+

clear

+
+
 clear (id)
+
+
+

source

+
+
+

with_sid

+
+
 with_sid (app, dest, path='/')
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/vendor/fasthtml/llms-ctx.txt b/docs/vendor/fasthtml/llms-ctx.txt new file mode 100644 index 0000000..4ba1ca3 --- /dev/null +++ b/docs/vendor/fasthtml/llms-ctx.txt @@ -0,0 +1,2611 @@ +Things to remember when writing FastHTML apps: + +- Although parts of its API are inspired by FastAPI, it is *not* compatible with FastAPI syntax and is not targeted at creating API services +- FastHTML includes support for Pico CSS and the fastlite sqlite library, although using both are optional; sqlalchemy can be used directly or via the fastsql library, and any CSS framework can be used. Support for the Surreal and css-scope-inline libraries are also included, but both are optional +- FastHTML is compatible with JS-native web components and any vanilla JS library, but not with React, Vue, or Svelte +- Use `serve()` for running uvicorn (`if __name__ == "__main__"` is not needed since it's automatic) +- When a title is needed with a response, use `Titled`; note that that already wraps children in `Container`, and already includes both the meta title as well as the H1 element.# Concise reference + + + +## About FastHTML + +``` python +from fasthtml.common import * +``` + +FastHTML is a python library which brings together Starlette, Uvicorn, +HTMX, and fastcore’s `FT` “FastTags” into a library for creating +server-rendered hypermedia applications. The +[`FastHTML`](api-core.html#fasthtml) class +itself inherits from `Starlette`, and adds decorator-based routing with +many additions, Beforeware, automatic `FT` to HTML rendering, and much +more. + +Things to remember when writing FastHTML apps: + +- *Not* compatible with FastAPI syntax; FastHTML is for HTML-first apps, + not API services (although it can implement APIs too) +- FastHTML includes support for Pico CSS and the fastlite sqlite + library, although using both are optional; sqlalchemy can be used + directly or via the fastsql library, and any CSS framework can be + used. MonsterUI is a richer FastHTML-first component framework with + similar capabilities to shadcn +- FastHTML is compatible with JS-native web components and any vanilla + JS library, but not with React, Vue, or Svelte +- Use [`serve()`](api-core.html#serve) for + running uvicorn (`if __name__ == "__main__"` is not needed since it’s + automatic) +- When a title is needed with a response, use + [`Titled`](api-xtend.html#titled); note + that that already wraps children in + [`Container`](api-pico.html#container), and + already includes both the meta title as well as the H1 element. + +## Minimal App + +The code examples here use fast.ai style: prefer ternary op, 1-line +docstring, minimize vertical space, etc. (Normally fast.ai style uses +few if any comments, but they’re added here as documentation.) + +A minimal FastHTML app looks something like this: + +``` python +# Meta-package with all key symbols from FastHTML and Starlette. Import it like this at the start of every FastHTML app. +from fasthtml.common import * +# The FastHTML app object and shortcut to `app.route` +app,rt = fast_app() + +# Enums constrain the values accepted for a route parameter +name = str_enum('names', 'Alice', 'Bev', 'Charlie') + +# Passing a path to `rt` is optional. If not passed (recommended), the function name is the route ('/foo') +# Both GET and POST HTTP methods are handled by default +# Type-annotated params are passed as query params (recommended) unless a path param is defined (which it isn't here) +@rt +def foo(nm: name): + # `Title` and `P` here are FastTags: direct m-expression mappings of HTML tags to Python functions with positional and named parameters. All standard HTML tags are included in the common wildcard import. + # When a tuple is returned, this returns concatenated HTML partials. HTMX by default will use a title HTML partial to set the current page name. HEAD tags (e.g. Meta, Link, etc) in the returned tuple are automatically placed in HEAD; everything else is placed in BODY. + # FastHTML will automatically return a complete HTML document with appropriate headers if a normal HTTP request is received. For an HTMX request, however, just the partials are returned. + return Title("FastHTML"), H1("My web app"), P(f"Hello, {name}!") +# By default `serve` runs uvicorn on port 5001. Never write `if __name__ == "__main__"` since `serve` checks it internally. +serve() +``` + +To run this web app: + +``` bash +python main.py # access via localhost:5001 +``` + +## FastTags (aka FT Components or FTs) + +FTs are m-expressions plus simple sugar. Positional params map to +children. Named parameters map to attributes. Aliases must be used for +Python reserved words. + +``` python +tags = Title("FastHTML"), H1("My web app"), P(f"Let's do this!", cls="myclass") +tags +``` + + (title(('FastHTML',),{}), + h1(('My web app',),{}), + p(("Let's do this!",),{'class': 'myclass'})) + +This example shows key aspects of how FTs handle attributes: + +``` python +Label( + "Choose an option", + Select( + Option("one", value="1", selected=True), # True renders just the attribute name + Option("two", value=2, selected=False), # Non-string values are converted to strings. False omits the attribute entirely + cls="selector", id="counter", # 'cls' becomes 'class' + **{'@click':"alert('Clicked');"}, # Dict unpacking for attributes with special chars + ), + _for="counter", # '_for' becomes 'for' (can also use 'fr') +) +``` + +Classes with `__ft__` defined are rendered using that method. + +``` python +class FtTest: + def __ft__(self): return P('test') + +to_xml(FtTest()) +``` + + '

test

\n' + +You can create new FTs by importing the new component from +`fasthtml.components`. If the FT doesn’t exist within that module, +FastHTML will create it. + +``` python +from fasthtml.components import Some_never_before_used_tag + +Some_never_before_used_tag() +``` + +``` html + +``` + +FTs can be combined by defining them as a function. + +``` python +def Hero(title, statement): return Div(H1(title),P(statement), cls="hero") +to_xml(Hero("Hello World", "This is a hero statement")) +``` + + '
\n

Hello World

\n

This is a hero statement

\n
\n' + +When handling a response, FastHTML will automatically render FTs using +the `to_xml` function. + +``` python +to_xml(tags) +``` + + 'FastHTML\n

My web app

\n

Let's do this!

\n' + +## JS + +The [`Script`](api-xtend.html#script) +function allows you to include JavaScript. You can use Python to +generate parts of your JS or JSON like this: + +``` python +# In future snippets this import will not be shown, but is required +from fasthtml.common import * +app,rt = fast_app(hdrs=[Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js")]) +# `index` is a special function name which maps to the `/` route. +@rt +def index(): + data = {'somedata':'fill me in…'} + # `Titled` returns a title tag and an h1 tag with the 1st param, with remaining params as children in a `Main` parent. + return Titled("Chart Demo", Div(id="myDiv"), Script(f"var data = {data}; Plotly.newPlot('myDiv', data);")) +# In future snippets `serve() will not be shown, but is required +serve() +``` + +Prefer Python whenever possible over JS. Never use React or shadcn. + +## fast_app hdrs + +``` python +# In future snippets we'll skip showing the `fast_app` call if it has no params +app, rt = fast_app( + pico=False, # The Pico CSS framework is included by default, so pass `False` to disable it if needed. No other CSS frameworks are included. + # These are added to the `head` part of the page for non-HTMX requests. + hdrs=( + Link(rel='stylesheet', href='assets/normalize.min.css', type='text/css'), + Link(rel='stylesheet', href='assets/sakura.css', type='text/css'), + Style("p {color: red;}"), + # `MarkdownJS` and `HighlightJS` are available via concise functions + MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']), + # by default, all standard static extensions are served statically from the web app dir, + # which can be modified using e.g `static_path='public'` + ) +) + +@rt +def index(req): return Titled("Markdown rendering example", + # This will be client-side rendered to HTML with highlight-js + Div("*hi* there",cls="marked"), + # This will be syntax highlighted + Pre(Code("def foo(): pass"))) +``` + +## Responses + +Routes can return various types: + +1. FastTags or tuples of FastTags (automatically rendered to HTML) +2. Standard Starlette responses (used directly) +3. JSON-serializable types (returned as JSON in a plain text response) + +``` python +@rt("/{fname:path}.{ext:static}") +async def serve_static_file(fname:str, ext:str): return FileResponse(f'public/{fname}.{ext}') + +app, rt = fast_app(hdrs=(MarkdownJS(), HighlightJS(langs=['python', 'javascript']))) +@rt +def index(): + return Titled("Example", + Div("*markdown* here", cls="marked"), + Pre(Code("def foo(): pass"))) +``` + +Route functions can be used in attributes like `href` or `action` and +will be converted to paths. Use `.to()` to generate paths with query +parameters. + +``` python +@rt +def profile(email:str): return fill_form(profile_form, profiles[email]) + +profile_form = Form(action=profile)( + Label("Email", Input(name="email")), + Button("Save", type="submit") +) + +user_profile_path = profile.to(email="user@example.com") # '/profile?email=user%40example.com' +``` + +``` python +from dataclasses import dataclass + +app,rt = fast_app() +``` + +When a route handler function is used as a fasttag attribute (such as +`href`, `hx_get`, or `action`) it is converted to that route’s path. +[`fill_form`](api-components.html#fill_form) +is used to copy an object’s matching attrs into matching-name form +fields. + +``` python +@dataclass +class Profile: email:str; phone:str; age:int +email = 'john@example.com' +profiles = {email: Profile(email=email, phone='123456789', age=5)} +@rt +def profile(email:str): return fill_form(profile_form, profiles[email]) + +profile_form = Form(method="post", action=profile)( + Fieldset( + Label('Email', Input(name="email")), + Label("Phone", Input(name="phone")), + Label("Age", Input(name="age"))), + Button("Save", type="submit")) +``` + +## Testing + +We can use `TestClient` for testing. + +``` python +from starlette.testclient import TestClient +``` + +``` python +path = "/profile?email=john@example.com" +client = TestClient(app) +htmx_req = {'HX-Request':'1'} +print(client.get(path, headers=htmx_req).text) +``` + +
+ +## Form Handling and Data Binding + +When a dataclass, namedtuple, etc. is used as a type annotation, the +form body will be unpacked into matching attribute names automatically. + +``` python +@rt +def edit_profile(profile: Profile): + profiles[email]=profile + return RedirectResponse(url=path) + +new_data = dict(email='john@example.com', phone='7654321', age=25) +print(client.post("/edit_profile", data=new_data, headers=htmx_req).text) +``` + +
+ +## fasttag Rendering Rules + +The general rules for rendering children inside tuples or fasttag +children are: - `__ft__` method will be called (for default components +like `P`, `H2`, etc. or if you define your own components) - If you pass +a string, it will be escaped - On other python objects, `str()` will be +called + +If you want to include plain HTML tags directly into e.g. a `Div()` they +will get escaped by default (as a security measure to avoid code +injections). This can be avoided by using `Safe(...)`, e.g to show a +data frame use `Div(NotStr(df.to_html()))`. + +## Exceptions + +FastHTML allows customization of exception handlers. + +``` python +def not_found(req, exc): return Titled("404: I don't exist!") +exception_handlers = {404: not_found} +app, rt = fast_app(exception_handlers=exception_handlers) +``` + +## Cookies + +We can set cookies using the +[`cookie()`](api-core.html#cookie) function. + +``` python +@rt +def setcook(): return P(f'Set'), cookie('mycookie', 'foobar') +print(client.get('/setcook', headers=htmx_req).text) +``` + +

Set

+ +``` python +@rt +def getcook(mycookie:str): return f'Got {mycookie}' +# If handlers return text instead of FTs, then a plaintext response is automatically created +print(client.get('/getcook').text) +``` + + Got foobar + +FastHTML provide access to Starlette’s request object automatically +using special `request` parameter name (or any prefix of that name). + +``` python +@rt +def headers(req): return req.headers['host'] +``` + +## Request and Session Objects + +FastHTML provides access to Starlette’s session middleware automatically +using the special `session` parameter name (or any prefix of that name). + +``` python +@rt +def profile(req, sess, user_id: int=None): + ip = req.client.host + sess['last_visit'] = datetime.now().isoformat() + visits = sess.setdefault('visit_count', 0) + 1 + sess['visit_count'] = visits + user = get_user(user_id or sess.get('user_id')) + return Titled(f"Profile: {user.name}", + P(f"Visits: {visits}"), + P(f"IP: {ip}"), + Button("Logout", hx_post=logout)) +``` + +Handler functions can return the +[`HtmxResponseHeaders`](api-core.html#htmxresponseheaders) +object to set HTMX-specific response headers. + +``` python +@rt +def htmlredirect(app): return HtmxResponseHeaders(location="http://example.org") +``` + +## APIRouter + +[`APIRouter`](api-core.html#apirouter) lets +you organize routes across multiple files in a FastHTML app. + +``` python +# products.py +ar = APIRouter() + +@ar +def details(pid: int): return f"Here are the product details for ID: {pid}" + +@ar +def all_products(req): + return Div( + Div( + Button("Details",hx_get=details.to(pid=42),hx_target="#products_list",hx_swap="outerHTML",), + ), id="products_list") +``` + +``` python +# main.py +from products import ar,all_products + +app, rt = fast_app() +ar.to_app(app) + +@rt +def index(): + return Div( + "Products", + hx_get=all_products, hx_swap="outerHTML") +``` + +## Toasts + +Toasts can be of four types: + +- info +- success +- warning +- error + +Toasts require the use of the `setup_toasts()` function, plus every +handler needs: + +- The session argument +- Must return FT components + +``` python +setup_toasts(app) + +@rt +def toasting(session): + add_toast(session, f"cooked", "info") + add_toast(session, f"ready", "success") + return Titled("toaster") +``` + +`setup_toasts(duration)` allows you to specify how long a toast will be +visible before disappearing.10 seconds. + +Authentication and authorization are handled with Beforeware, which +functions that run before the route handler is called. + +## Auth + +``` python +def user_auth_before(req, sess): + # `auth` key in the request scope is automatically provided to any handler which requests it and can not be injected + auth = req.scope['auth'] = sess.get('auth', None) + if not auth: return RedirectResponse('/login', status_code=303) + +beforeware = Beforeware( + user_auth_before, + skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login', '/'] +) + +app, rt = fast_app(before=beforeware) +``` + +## Server-Side Events (SSE) + +FastHTML supports the HTMX SSE extension. + +``` python +import random +hdrs=(Script(src="https://unpkg.com/htmx-ext-sse@2.2.3/sse.js"),) +app,rt = fast_app(hdrs=hdrs) + +@rt +def index(): return Div(hx_ext="sse", sse_connect="/numstream", hx_swap="beforeend show:bottom", sse_swap="message") + +# `signal_shutdown()` gets an event that is set on shutdown +shutdown_event = signal_shutdown() + +async def number_generator(): + while not shutdown_event.is_set(): + data = Article(random.randint(1, 100)) + yield sse_message(data) + +@rt +async def numstream(): return EventStream(number_generator()) +``` + +## Websockets + +FastHTML provides useful tools for HTMX’s websockets extension. + +``` python +# These HTMX extensions are available through `exts`: +# head-support preload class-tools loading-states multi-swap path-deps remove-me ws chunked-transfer +app, rt = fast_app(exts='ws') + +def mk_inp(): return Input(id='msg', autofocus=True) + +@rt +async def index(request): + # `ws_send` tells HTMX to send a message to the nearest websocket based on the trigger for the form element + cts = Div( + Div(id='notifications'), + Form(mk_inp(), id='form', ws_send=True), + hx_ext='ws', ws_connect='/ws') + return Titled('Websocket Test', cts) + +async def on_connect(send): await send(Div('Hello, you have connected', id="notifications")) +async def on_disconnect(ws): print('Disconnected!') + +@app.ws('/ws', conn=on_connect, disconn=on_disconnect) +async def ws(msg:str, send): + # websocket hander returns/sends are treated as OOB swaps + await send(Div('Hello ' + msg, id="notifications")) + return Div('Goodbye ' + msg, id="notifications"), mk_inp() +``` + +Sample chatbot that uses FastHTML’s `setup_ws` function: + +``` py +app = FastHTML(exts='ws') +rt = app.route +msgs = [] + +@rt('/') +def home(): + return Div(hx_ext='ws', ws_connect='/ws')( + Div(Ul(*[Li(m) for m in msgs], id='msg-list')), + Form(Input(id='msg'), id='form', ws_send=True) + ) + +async def ws(msg:str): + msgs.append(msg) + await send(Ul(*[Li(m) for m in msgs], id='msg-list')) + +send = setup_ws(app, ws) +``` + +### Single File Uploads + +[`Form`](api-xtend.html#form) defaults to +“multipart/form-data”. A Starlette UploadFile is passed to the handler. + +``` python +upload_dir = Path("filez") + +@rt +def index(): + return ( + Form(hx_post=upload, hx_target="#result")( + Input(type="file", name="file"), + Button("Upload", type="submit")), + Div(id="result") + ) + +# Use `async` handlers where IO is used to avoid blocking other clients +@rt +async def upload(file: UploadFile): + filebuffer = await file.read() + (upload_dir / file.filename).write_bytes(filebuffer) + return P('Size: ', file.size) +``` + +For multi-file, use `Input(..., multiple=True)`, and a type annotation +of `list[UploadFile]` in the handler. + +## Fastlite + +Fastlite and the MiniDataAPI specification it’s built on are a +CRUD-oriented API for working with SQLite. APSW and apswutils is used to +connect to SQLite, optimized for speed and clean error handling. + +``` python +from fastlite import * +``` + +``` python +db = database(':memory:') # or database('data/app.db') +``` + +Tables are normally constructed with classes, field types are specified +as type hints. + +``` python +class Book: isbn: str; title: str; pages: int; userid: int +# The transform arg instructs fastlite to change the db schema when fields change. +# Create only creates a table if the table doesn't exist. +books = db.create(Book, pk='isbn', transform=True) + +class User: id: int; name: str; active: bool = True +# If no pk is provided, id is used as the primary key. +users = db.create(User, transform=True) +users +``` + + + +### Fastlite CRUD operations + +Every operation in fastlite returns a full superset of dataclass +functionality. + +``` python +user = users.insert(name='Alex',active=False) +user +``` + + User(id=1, name='Alex', active=0) + +``` python +# List all records +users() +``` + + [User(id=1, name='Alex', active=0)] + +``` python +# Limit, offset, and order results: +users(order_by='name', limit=2, offset=1) + +# Filter on the results +users(where="name='Alex'") + +# Placeholder for avoiding injection attacks +users("name=?", ('Alex',)) + +# A single record by pk +users[user.id] +``` + + User(id=1, name='Alex', active=0) + +Test if a record exists by using `in` keyword on primary key: + +``` python +1 in users +``` + + True + +Updates (which take a dict or a typed object) return the updated record. + +``` python +user.name='Lauren' +user.active=True +users.update(user) +``` + + User(id=1, name='Lauren', active=1) + +`.xtra()` to automatically constrain queries, updates, and inserts from +there on: + +``` python +users.xtra(active=True) +users() +``` + + [User(id=1, name='Lauren', active=1)] + +Deleting by pk: + +``` python +users.delete(user.id) +``` + +
+ +NotFoundError is raised by pk `[]`, updates, and deletes. + +``` python +try: users['Amy'] +except NotFoundError: print('User not found') +``` + + User not found + +## MonsterUI + +MonsterUI is a shadcn-like component library for FastHTML. It adds the +Tailwind-based libraries FrankenUI and DaisyUI to FastHTML, as well as +Python’s mistletoe for Markdown, HighlightJS for code highlighting, and +Katex for latex support, following semantic HTML patterns when possible. +It is recommended for when you wish to go beyond the basics provided by +FastHTML’s built-in pico support. + +A minimal app: + +``` python +from fasthtml.common import * +from monsterui.all import * + +app, rt = fast_app(hdrs=Theme.blue.headers(highlightjs=True)) # Use MonsterUI blue theme and highlight code in markdown + +@rt +def index(): + socials = (('github','https://github.com/AnswerDotAI/MonsterUI'),) + return Titled("App", + Card( + P("App", cls=TextPresets.muted_sm), + # LabelInput, DivLAigned, and UkIconLink are non-semantic MonsterUI FT Components, + LabelInput('Email', type='email', required=True), + footer=DivLAligned(*[UkIconLink(icon,href=url) for icon,url in socials]))) +``` + +MonsterUI recommendations: + +- Use defaults as much as possible, for example + [`Container`](api-pico.html#container) in + monsterui already has defaults for margins +- Use `*T` for button styling consistency, for example + `cls=ButtonT.destructive` for a red delete button or + `cls=ButtonT.primary` for a CTA button +- Use `Label*` functions for forms as much as possible + (e.g. `LabelInput`, `LabelRange`) which creates and links both the + `FormLabel` and user input appropriately to avoid boiler plate. + +Flex Layout Elements (such as `DivLAligned` and `DivFullySpaced`) can be +used to create layouts concisely + +``` python +def TeamCard(name, role, location="Remote"): + icons = ("mail", "linkedin", "github") + return Card( + DivLAligned( + DiceBearAvatar(name, h=24, w=24), + Div(H3(name), P(role))), + footer=DivFullySpaced( + DivHStacked(UkIcon("map-pin", height=16), P(location)), + DivHStacked(*(UkIconLink(icon, height=16) for icon in icons)))) +``` + +Forms are styled and spaced for you without significant additional +classes. + +``` python +def MonsterForm(): + relationship = ["Parent",'Sibling', "Friend", "Spouse", "Significant Other", "Relative", "Child", "Other"] + return Div( + DivCentered( + H3("Emergency Contact Form"), + P("Please fill out the form completely", cls=TextPresets.muted_sm)), + Form( + Grid(LabelInput("Name",id='name'),LabelInput("Email", id='email')), + H3("Relationship to patient"), + Grid(*[LabelCheckboxX(o) for o in relationship], cols=4, cls='space-y-3'), + DivCentered(Button("Submit Form", cls=ButtonT.primary))), + cls='space-y-4') +``` + +Text can be styled with markdown automatically with MonsterUI + +```` python +render_md(""" +# My Document + +> Important note here + ++ List item with **bold** ++ Another with `code` + +```python +def hello(): + print("world") +``` +""") +```` + + '

My Document

\n
\n

Important note here

\n
\n
    \n
  • List item with bold
  • \n
  • Another with code
  • \n
\n
def hello():\n    print("world")\n
\n
' + +Or using semantic HTML: + +``` python +def SemanticText(): + return Card( + H1("MonsterUI's Semantic Text"), + P( + Strong("MonsterUI"), " brings the power of semantic HTML to life with ", + Em("beautiful styling"), " and ", Mark("zero configuration"), "."), + Blockquote( + P("Write semantic HTML in pure Python, get modern styling for free."), + Cite("MonsterUI Team")), + footer=Small("Released February 2025"),) +```+++ +title = "Reference" ++++ + +## Contents + +* [htmx Core Attributes](#attributes) +* [htmx Additional Attributes](#attributes-additional) +* [htmx CSS Classes](#classes) +* [htmx Request Headers](#request_headers) +* [htmx Response Headers](#response_headers) +* [htmx Events](#events) +* [htmx Extensions](/extensions) +* [JavaScript API](#api) +* [Configuration Options](#config) + +## Core Attribute Reference {#attributes} + +The most common attributes when using htmx. + +
+ +| Attribute | Description | +|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL | +| [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL | +| [`hx-on*`](@/attributes/hx-on.md) | handle events with inline scripts on elements | +| [`hx-push-url`](@/attributes/hx-push-url.md) | push a URL into the browser location bar to create history | +| [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response | +| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, somewhere other than the target (out of band) | +| [`hx-swap`](@/attributes/hx-swap.md) | controls how content will swap in (`outerHTML`, `beforeend`, `afterend`, ...) | +| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | mark element to swap in from a response (out of band) | +| [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped | +| [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request | +| [`hx-vals`](@/attributes/hx-vals.md) | add values to submit with the request (JSON format) | + +
+ +## Additional Attribute Reference {#attributes-additional} + +All other attributes available in htmx. + +
+ +| Attribute | Description | +|------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| [`hx-boost`](@/attributes/hx-boost.md) | add [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms | +| [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request | +| [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL | +| [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes | +| [`hx-disabled-elt`](@/attributes/hx-disabled-elt.md) | adds the `disabled` attribute to the specified elements while a request is in flight | +| [`hx-disinherit`](@/attributes/hx-disinherit.md) | control and disable automatic attribute inheritance for child nodes | +| [`hx-encoding`](@/attributes/hx-encoding.md) | changes the request encoding type | +| [`hx-ext`](@/attributes/hx-ext.md) | extensions to use for this element | +| [`hx-headers`](@/attributes/hx-headers.md) | adds to the headers that will be submitted with the request | +| [`hx-history`](@/attributes/hx-history.md) | prevent sensitive data being saved to the history cache | +| [`hx-history-elt`](@/attributes/hx-history-elt.md) | the element to snapshot and restore during history navigation | +| [`hx-include`](@/attributes/hx-include.md) | include additional data in requests | +| [`hx-indicator`](@/attributes/hx-indicator.md) | the element to put the `htmx-request` class on during the request | +| [`hx-inherit`](@/attributes/hx-inherit.md) | control and enable automatic attribute inheritance for child nodes if it has been disabled by default | +| [`hx-params`](@/attributes/hx-params.md) | filters the parameters that will be submitted with a request | +| [`hx-patch`](@/attributes/hx-patch.md) | issues a `PATCH` to the specified URL | +| [`hx-preserve`](@/attributes/hx-preserve.md) | specifies elements to keep unchanged between requests | +| [`hx-prompt`](@/attributes/hx-prompt.md) | shows a `prompt()` before submitting a request | +| [`hx-put`](@/attributes/hx-put.md) | issues a `PUT` to the specified URL | +| [`hx-replace-url`](@/attributes/hx-replace-url.md) | replace the URL in the browser location bar | +| [`hx-request`](@/attributes/hx-request.md) | configures various aspects of the request | +| [`hx-sync`](@/attributes/hx-sync.md) | control how requests made by different elements are synchronized | +| [`hx-validate`](@/attributes/hx-validate.md) | force elements to validate themselves before a request | +| [`hx-vars`](@/attributes/hx-vars.md) | adds values dynamically to the parameters to submit with the request (deprecated, please use [`hx-vals`](@/attributes/hx-vals.md)) | + +
+ +## CSS Class Reference {#classes} + +
+ +| Class | Description | +|-----------|-------------| +| `htmx-added` | Applied to a new piece of content before it is swapped, removed after it is settled. +| `htmx-indicator` | A dynamically generated class that will toggle visible (opacity:1) when a `htmx-request` class is present +| `htmx-request` | Applied to either the element or the element specified with [`hx-indicator`](@/attributes/hx-indicator.md) while a request is ongoing +| `htmx-settling` | Applied to a target after content is swapped, removed after it is settled. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). +| `htmx-swapping` | Applied to a target before any content is swapped, removed after it is swapped. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). + +
+ +## HTTP Header Reference {#headers} + +### Request Headers Reference {#request_headers} + +
+ +| Header | Description | +|--------|-------------| +| `HX-Boosted` | indicates that the request is via an element using [hx-boost](@/attributes/hx-boost.md) +| `HX-Current-URL` | the current URL of the browser +| `HX-History-Restore-Request` | "true" if the request is for history restoration after a miss in the local history cache +| `HX-Prompt` | the user response to an [hx-prompt](@/attributes/hx-prompt.md) +| `HX-Request` | always "true" +| `HX-Target` | the `id` of the target element if it exists +| `HX-Trigger-Name` | the `name` of the triggered element if it exists +| `HX-Trigger` | the `id` of the triggered element if it exists + +
+ +### Response Headers Reference {#response_headers} + +
+ +| Header | Description | +|------------------------------------------------------|-------------| +| [`HX-Location`](@/headers/hx-location.md) | allows you to do a client-side redirect that does not do a full page reload +| [`HX-Push-Url`](@/headers/hx-push-url.md) | pushes a new url into the history stack +| [`HX-Redirect`](@/headers/hx-redirect.md) | can be used to do a client-side redirect to a new location +| `HX-Refresh` | if set to "true" the client-side will do a full refresh of the page +| [`HX-Replace-Url`](@/headers/hx-replace-url.md) | replaces the current URL in the location bar +| `HX-Reswap` | allows you to specify how the response will be swapped. See [hx-swap](@/attributes/hx-swap.md) for possible values +| `HX-Retarget` | a CSS selector that updates the target of the content update to a different element on the page +| `HX-Reselect` | a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing [`hx-select`](@/attributes/hx-select.md) on the triggering element +| [`HX-Trigger`](@/headers/hx-trigger.md) | allows you to trigger client-side events +| [`HX-Trigger-After-Settle`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the settle step +| [`HX-Trigger-After-Swap`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the swap step + +
+ +## Event Reference {#events} + +
+ +| Event | Description | +|-------|-------------| +| [`htmx:abort`](@/events.md#htmx:abort) | send this event to an element to abort a request +| [`htmx:afterOnLoad`](@/events.md#htmx:afterOnLoad) | triggered after an AJAX request has completed processing a successful response +| [`htmx:afterProcessNode`](@/events.md#htmx:afterProcessNode) | triggered after htmx has initialized a node +| [`htmx:afterRequest`](@/events.md#htmx:afterRequest) | triggered after an AJAX request has completed +| [`htmx:afterSettle`](@/events.md#htmx:afterSettle) | triggered after the DOM has settled +| [`htmx:afterSwap`](@/events.md#htmx:afterSwap) | triggered after new content has been swapped in +| [`htmx:beforeCleanupElement`](@/events.md#htmx:beforeCleanupElement) | triggered before htmx [disables](@/attributes/hx-disable.md) an element or removes it from the DOM +| [`htmx:beforeOnLoad`](@/events.md#htmx:beforeOnLoad) | triggered before any response processing occurs +| [`htmx:beforeProcessNode`](@/events.md#htmx:beforeProcessNode) | triggered before htmx initializes a node +| [`htmx:beforeRequest`](@/events.md#htmx:beforeRequest) | triggered before an AJAX request is made +| [`htmx:beforeSwap`](@/events.md#htmx:beforeSwap) | triggered before a swap is done, allows you to configure the swap +| [`htmx:beforeSend`](@/events.md#htmx:beforeSend) | triggered just before an ajax request is sent +| [`htmx:beforeTransition`](@/events.md#htmx:beforeTransition) | triggered before the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) wrapped swap occurs +| [`htmx:configRequest`](@/events.md#htmx:configRequest) | triggered before the request, allows you to customize parameters, headers +| [`htmx:confirm`](@/events.md#htmx:confirm) | triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request +| [`htmx:historyCacheError`](@/events.md#htmx:historyCacheError) | triggered on an error during cache writing +| [`htmx:historyCacheHit`](@/events.md#htmx:historyCacheHit) | triggered on a cache hit in the history subsystem +| [`htmx:historyCacheMiss`](@/events.md#htmx:historyCacheMiss) | triggered on a cache miss in the history subsystem +| [`htmx:historyCacheMissLoadError`](@/events.md#htmx:historyCacheMissLoadError) | triggered on a unsuccessful remote retrieval +| [`htmx:historyCacheMissLoad`](@/events.md#htmx:historyCacheMissLoad) | triggered on a successful remote retrieval +| [`htmx:historyRestore`](@/events.md#htmx:historyRestore) | triggered when htmx handles a history restoration action +| [`htmx:beforeHistorySave`](@/events.md#htmx:beforeHistorySave) | triggered before content is saved to the history cache +| [`htmx:load`](@/events.md#htmx:load) | triggered when new content is added to the DOM +| [`htmx:noSSESourceError`](@/events.md#htmx:noSSESourceError) | triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined +| [`htmx:onLoadError`](@/events.md#htmx:onLoadError) | triggered when an exception occurs during the onLoad handling in htmx +| [`htmx:oobAfterSwap`](@/events.md#htmx:oobAfterSwap) | triggered after an out of band element as been swapped in +| [`htmx:oobBeforeSwap`](@/events.md#htmx:oobBeforeSwap) | triggered before an out of band element swap is done, allows you to configure the swap +| [`htmx:oobErrorNoTarget`](@/events.md#htmx:oobErrorNoTarget) | triggered when an out of band element does not have a matching ID in the current DOM +| [`htmx:prompt`](@/events.md#htmx:prompt) | triggered after a prompt is shown +| [`htmx:pushedIntoHistory`](@/events.md#htmx:pushedIntoHistory) | triggered after a url is pushed into history +| [`htmx:replacedInHistory`](@/events.md#htmx:replacedInHistory) | triggered after a url is replaced in history +| [`htmx:responseError`](@/events.md#htmx:responseError) | triggered when an HTTP response error (non-`200` or `300` response code) occurs +| [`htmx:sendAbort`](@/events.md#htmx:sendAbort) | triggered when a request is aborted +| [`htmx:sendError`](@/events.md#htmx:sendError) | triggered when a network error prevents an HTTP request from happening +| [`htmx:sseError`](@/events.md#htmx:sseError) | triggered when an error occurs with a SSE source +| [`htmx:sseOpen`](/events#htmx:sseOpen) | triggered when a SSE source is opened +| [`htmx:swapError`](@/events.md#htmx:swapError) | triggered when an error occurs during the swap phase +| [`htmx:targetError`](@/events.md#htmx:targetError) | triggered when an invalid target is specified +| [`htmx:timeout`](@/events.md#htmx:timeout) | triggered when a request timeout occurs +| [`htmx:validation:validate`](@/events.md#htmx:validation:validate) | triggered before an element is validated +| [`htmx:validation:failed`](@/events.md#htmx:validation:failed) | triggered when an element fails validation +| [`htmx:validation:halted`](@/events.md#htmx:validation:halted) | triggered when a request is halted due to validation errors +| [`htmx:xhr:abort`](@/events.md#htmx:xhr:abort) | triggered when an ajax request aborts +| [`htmx:xhr:loadend`](@/events.md#htmx:xhr:loadend) | triggered when an ajax request ends +| [`htmx:xhr:loadstart`](@/events.md#htmx:xhr:loadstart) | triggered when an ajax request starts +| [`htmx:xhr:progress`](@/events.md#htmx:xhr:progress) | triggered periodically during an ajax request that supports progress events + +
+ +## JavaScript API Reference {#api} + +
+ +| Method | Description | +|-------|-------------| +| [`htmx.addClass()`](@/api.md#addClass) | Adds a class to the given element +| [`htmx.ajax()`](@/api.md#ajax) | Issues an htmx-style ajax request +| [`htmx.closest()`](@/api.md#closest) | Finds the closest parent to the given element matching the selector +| [`htmx.config`](@/api.md#config) | A property that holds the current htmx config object +| [`htmx.createEventSource`](@/api.md#createEventSource) | A property holding the function to create SSE EventSource objects for htmx +| [`htmx.createWebSocket`](@/api.md#createWebSocket) | A property holding the function to create WebSocket objects for htmx +| [`htmx.defineExtension()`](@/api.md#defineExtension) | Defines an htmx [extension](https://htmx.org/extensions) +| [`htmx.find()`](@/api.md#find) | Finds a single element matching the selector +| [`htmx.findAll()` `htmx.findAll(elt, selector)`](@/api.md#find) | Finds all elements matching a given selector +| [`htmx.logAll()`](@/api.md#logAll) | Installs a logger that will log all htmx events +| [`htmx.logger`](@/api.md#logger) | A property set to the current logger (default is `null`) +| [`htmx.off()`](@/api.md#off) | Removes an event listener from the given element +| [`htmx.on()`](@/api.md#on) | Creates an event listener on the given element, returning it +| [`htmx.onLoad()`](@/api.md#onLoad) | Adds a callback handler for the `htmx:load` event +| [`htmx.parseInterval()`](@/api.md#parseInterval) | Parses an interval declaration into a millisecond value +| [`htmx.process()`](@/api.md#process) | Processes the given element and its children, hooking up any htmx behavior +| [`htmx.remove()`](@/api.md#remove) | Removes the given element +| [`htmx.removeClass()`](@/api.md#removeClass) | Removes a class from the given element +| [`htmx.removeExtension()`](@/api.md#removeExtension) | Removes an htmx [extension](https://htmx.org/extensions) +| [`htmx.swap()`](@/api.md#swap) | Performs swapping (and settling) of HTML content +| [`htmx.takeClass()`](@/api.md#takeClass) | Takes a class from other elements for the given element +| [`htmx.toggleClass()`](@/api.md#toggleClass) | Toggles a class from the given element +| [`htmx.trigger()`](@/api.md#trigger) | Triggers an event on an element +| [`htmx.values()`](@/api.md#values) | Returns the input values associated with the given element + +
+ + +## Configuration Reference {#config} + +Htmx has some configuration options that can be accessed either programmatically or declaratively. They are +listed below: + +
+ +| Config Variable | Info | +|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `htmx.config.historyEnabled` | defaults to `true`, really only useful for testing | +| `htmx.config.historyCacheSize` | defaults to 10 | +| `htmx.config.refreshOnHistoryMiss` | defaults to `false`, if set to `true` htmx will issue a full page refresh on history misses rather than use an AJAX request | +| `htmx.config.defaultSwapStyle` | defaults to `innerHTML` | +| `htmx.config.defaultSwapDelay` | defaults to 0 | +| `htmx.config.defaultSettleDelay` | defaults to 20 | +| `htmx.config.includeIndicatorStyles` | defaults to `true` (determines if the indicator styles are loaded) | +| `htmx.config.indicatorClass` | defaults to `htmx-indicator` | +| `htmx.config.requestClass` | defaults to `htmx-request` | +| `htmx.config.addedClass` | defaults to `htmx-added` | +| `htmx.config.settlingClass` | defaults to `htmx-settling` | +| `htmx.config.swappingClass` | defaults to `htmx-swapping` | +| `htmx.config.allowEval` | defaults to `true`, can be used to disable htmx's use of eval for certain features (e.g. trigger filters) | +| `htmx.config.allowScriptTags` | defaults to `true`, determines if htmx will process script tags found in new content | +| `htmx.config.inlineScriptNonce` | defaults to `''`, meaning that no nonce will be added to inline scripts | +| `htmx.config.inlineStyleNonce` | defaults to `''`, meaning that no nonce will be added to inline styles | +| `htmx.config.attributesToSettle` | defaults to `["class", "style", "width", "height"]`, the attributes to settle during the settling phase | +| `htmx.config.wsReconnectDelay` | defaults to `full-jitter` | +| `htmx.config.wsBinaryType` | defaults to `blob`, the [the type of binary data](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) being received over the WebSocket connection | +| `htmx.config.disableSelector` | defaults to `[hx-disable], [data-hx-disable]`, htmx will not process elements with this attribute on it or a parent | +| `htmx.config.disableInheritance` | defaults to `false`. If it is set to `true`, the inheritance of attributes is completely disabled and you can explicitly specify the inheritance with the [hx-inherit](@/attributes/hx-inherit.md) attribute. +| `htmx.config.withCredentials` | defaults to `false`, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates | +| `htmx.config.timeout` | defaults to 0, the number of milliseconds a request can take before automatically being terminated | +| `htmx.config.scrollBehavior` | defaults to 'instant', the scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)). | +| `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier. | +| `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId` | +| `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. | +| `htmx.config.methodsThatUseUrlParams` | defaults to `["get", "delete"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body | +| `htmx.config.selfRequestsOnly` | defaults to `true`, whether to only allow AJAX requests to the same domain as the current document | +| `htmx.config.ignoreTitle` | defaults to `false`, if set to `true` htmx will not update the title of the document when a `title` tag is found in new content | +| `htmx.config.scrollIntoViewOnBoost` | defaults to `true`, whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top. | +| `htmx.config.triggerSpecsCache` | defaults to `null`, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) | +| `htmx.config.responseHandling` | the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error | +| `htmx.config.allowNestedOobSwaps` | defaults to `true`, whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps). | +| `htmx.config.historyRestoreAsHxRequest`| defaults to `true`, Whether to treat history cache miss full page reload requests as a "HX-Request" by returning this response header. This should always be disabled when using HX-Request header to optionally return partial responses | + + +
+ +You can set them directly in javascript, or you can use a `meta` tag: + +```html + +```
# 🌟 Starlette Quick Manual + + +2020-02-09 + +Starlette is the ASGI web framework used as the foundation of FastHTML. Listed here are some Starlette features FastHTML developers can use directly, since the `FastHTML` class inherits from the `Starlette` class (but note that FastHTML has its own customised `RouteX` and `RouterX` classes for routing, to handle FT element trees etc). + +## Get uploaded file content + +``` +async def handler(request): + inp = await request.form() + uploaded_file = inp["filename"] + filename = uploaded_file.filename # abc.png + content_type = uploaded.content_type # MIME type, e.g. image/png + content = await uploaded_file.read() # image content + +``` + +## Return a customized response (status code and headers) + +``` +import json +from starlette.responses import Response + +async def handler(request): + data = { + "name": "Bo" + } + return Response(json.dumps(data), media_type="application/json") + +``` + +`Response` takes `status_code`, `headers` and `media_type`, so if we want to change a response's status code, we can do: + +``` +return Response(content, statu_code=404) + +``` + +And customized headers: + +``` +headers = { + "x-extra-key": "value" +} +return Response(content, status_code=200, headers=headers) + +``` + +## Redirect + +``` +from starlette.responses import RedirectResponse + +async handler(request): + # Customize status_code: + # 301: permanent redirect + # 302: temporary redirect + # 303: see others + # 307: temporary redirect (default) + return RedirectResponse(url=url, status_code=303) + +``` + +## Request context + +### URL Object: `request.url` + + * Get request full url: `url = str(request.url)` + * Get scheme: `request.url.scheme` (http, https, ws, wss) + * Get netloc: `request.url.netloc`, e.g.: example.com:8080 + * Get path: `request.url.path`, e.g.: /search + * Get query string: `request.url.query`, e.g.: kw=hello + * Get hostname: `request.url.hostname`, e.g.: example.com + * Get port: `request.url.port`, e.g.: 8080 + * If using secure scheme: `request.url.is_secure`, True is schme is `https` or `wss` + +### Headers: `request.headers` + +``` +{ + 'host': 'example.com:8080', + 'connection': 'keep-alive', + 'cache-control': 'max-age=0', + 'sec-ch-ua': 'Google Chrome 80', + 'dnt': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) ...', + 'sec-fetch-dest': 'document', + 'accept': 'text/html,image/apng,*/*;q=0.8;v=b3;q=0.9', + 'sec-origin-policy': '0', + 'sec-fetch-site': 'none', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-user': '?1', + 'accept-encoding': 'gzip, deflate, br', + 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6', + 'cookie': 'session=eyJhZG1pbl91c2_KiQ...' +} + +``` + +### Client: `request.client` + + * `request.client.host`: get client sock IP + * `request.client.port`: get client sock port + +### Method: `request.method` + + * `request.method`: GET, POST, etc. + +### Get Data + + * `await request.body()`: get raw data from body + * `await request.json()`: get passed data and parse it as JSON + * `await request.form()`: get posted data and pass it as dictionary + +### Scope: `request.scope` + +``` +{ + 'type': 'http', + 'http_version': '1.1', + 'server': ('127.0.0.1', 9092), + 'client': ('127.0.0.1', 53102), + 'scheme': 'https', + 'method': 'GET', + 'root_path': '', + 'path': '/', + 'raw_path': b'/', + 'query_string': b'kw=hello', + 'headers': [ + (b'host', b'example.com:8080'), + (b'connection', b'keep-alive'), + (b'cache-control', b'max-age=0'), + ... + ], + 'app': , + 'session': {'uid': '57ba03ea7333f72a25f837cf'}, + 'router': , + 'endpoint': , + 'path_params': {} +} + +``` + +## Put varaible in request & app scope + +``` +app.state.dbconn = get_db_conn() +request.state.start_time = time.time() +# use app-scope state variable in a request +request.app.state.dbconn + +``` + +## Utility functions + +### Use `State` to wrap a dictionary + +``` +from starlette.datastructures import State + +data = { + "name": "Bo" +} +print(data["name"]) +# now wrap it with State function +wrapped = State(data) +# You can use the dot syntaxt, but can't use `wrapped["name"]` any more. +print(wrapped.name) + +``` + +### login_required wrapper function + +NB: This is easier to do in FastHTML using Beforeware. + +``` +import functools +from starlette.endpoints import HTTPEndpoint +from starlette.responses import Response + +def login_required(login_url="/signin"): + def decorator(handler): + @functools.wraps(handler) + async def new_handler(obj, req, *args, **kwargs): + user = req.session.get("login_user") + if user is None: + return seeother(login_url) + return await handler(obj, req, *args, **kwargs) + return new_handler + return decorator + +class MyAccount(HTTPEndpiont): + @login_required() + async def get(self, request): + # some logic here + content = "hello" + return Response(content) + +``` + +## Exceptions + +Handle exception and customize 403, 404, 503, 500 page: + +``` +from starlette.exceptions import HTTPException + +async def exc_handle_403(request, exc): + return HTMLResponse("My 403 page", status_code=exc.status_code) + +async def exc_handle_404(request, exc): + return HTMLResponse("My 404 page", status_code=exc.status_code) + +async def exc_handle_503(request, exc): + return HTMLResponse("Failed, please try it later", status_code=exc.status_code) + +# error is not exception, 500 is server side unexpected error, all other status code will be treated as Exception +async def err_handle_500(request, exc): + import traceback + Log.error(traceback.format_exc()) + return HTMLResponse("My 500 page", status_code=500) + +# To add handler, we can add either status_code or Exception itself as key +exception_handlers = { + 403: exc_handle_403, + 404: exc_handle_404, + 503: exc_handle_503, + 500: err_handle_500, + #HTTPException: exc_handle_500, +} + +app = Starlette(routes=routes, exception_handlers=exception_handlers) + +``` + +## Background Task + +### Put some async task as background task + +``` +import aiofiles +from starlette.background import BackgroundTask +from starlette.responses import Response + +aiofiles_remove = aiofiles.os.wrap(os.remove) + +async def del_file(fpath): + await aiofiles_remove(fpath) + +async def handler(request): + content = "" + fpath = "/tmp/tmpfile.txt" + task = BackgroundTask(del_file, fpath=fpath) + return Response(content, background=task) + +``` + +### Put multiple tasks as background task + +``` +from starlette.background import BackgroundTasks + +async def task1(name): + pass + +async def task2(email): + pass + +async def handler(request): + tasks = BackgroundTasks() + tasks.add_task(task1, name="John") + tasks.add_task(task2, email="info@example.com") + content = "" + return Response(content, background=tasks) + +``` + +## Write middleware + +There are 2 ways to write middleware: + +### Define `__call__` function: + +``` +class MyMiddleware: + def __init__(self, app): + self.app = app + + async def __call__(self, scope, receive, send): + # see above scope dictionary as reference + headers = dict(scope["headers"]) + # do something + # pass to next middleware + return await self.app(scope, receive, send) + +``` + +### Use `BaseHTTPMiddleware` + +``` +from starlette.middleware.base import BaseHTTPMiddleware + +class CustomHeaderMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request, call_next): + # do something before pass to next middleware + response = await call_next(request) + # do something after next middleware returned + response.headers['X-Author'] = 'John' + return response + +```# fasthtml Module Documentation + +## fasthtml.authmw + +- `class BasicAuthMiddleware` + - `def __init__(self, app, cb, skip)` + - `def __call__(self, scope, receive, send)` + - `def authenticate(self, conn)` + +## fasthtml.cli + +- `@call_parse def railway_link()` + Link the current directory to the current project's Railway service + +- `@call_parse def railway_deploy(name, mount)` + Deploy a FastHTML app to Railway + +## fasthtml.components + +> `ft_html` and `ft_hx` functions to add some conveniences to `ft`, along with a full set of basic HTML components, and functions to work with forms and `FT` conversion + +- `def File(fname)` + Use the unescaped text in file `fname` directly + +- `def show(ft, *rest)` + Renders FT Components into HTML within a Jupyter notebook. + +- `def fill_form(form, obj)` + Fills named items in `form` using attributes in `obj` + +- `def fill_dataclass(src, dest)` + Modifies dataclass in-place and returns it + +- `def find_inputs(e, tags, **kw)` + Recursively find all elements in `e` with `tags` and attrs matching `kw` + +- `def html2ft(html, attr1st)` + Convert HTML to an `ft` expression + +- `def sse_message(elm, event)` + Convert element `elm` into a format suitable for SSE streaming + +## fasthtml.core + +> The `FastHTML` subclass of `Starlette`, along with the `RouterX` and `RouteX` classes it automatically uses. + +- `def parsed_date(s)` + Convert `s` to a datetime + +- `def snake2hyphens(s)` + Convert `s` from snake case to hyphenated and capitalised + +- `@dataclass class HtmxHeaders` + - `def __bool__(self)` + - `def __init__(self, boosted, current_url, history_restore_request, prompt, request, target, trigger_name, trigger)` + +- `@dataclass class HttpHeader` + - `def __init__(self, k, v)` + +- `@use_kwargs_dict(**htmx_resps) def HtmxResponseHeaders(**kwargs)` + HTMX response headers + +- `def form2dict(form)` + Convert starlette form data to a dict + +- `def parse_form(req)` + Starlette errors on empty multipart forms, so this checks for that situation + +- `class JSONResponse` + Same as starlette's version, but auto-stringifies non serializable types + + - `def render(self, content)` + +- `def flat_xt(lst)` + Flatten lists + +- `class Beforeware` + - `def __init__(self, f, skip)` + +- `def EventStream(s)` + Create a text/event-stream response from `s` + +- `def flat_tuple(o)` + Flatten lists + +- `def noop_body(c, req)` + Default Body wrap function which just returns the content + +- `def respond(req, heads, bdy)` + Default FT response creation function + +- `class Redirect` + Use HTMX or Starlette RedirectResponse as required to redirect to `loc` + + - `def __init__(self, loc)` + - `def __response__(self, req)` + +- `def qp(p, **kw)` + Add parameters kw to path p + +- `def def_hdrs(htmx, surreal)` + Default headers for a FastHTML app + +- `class FastHTML` + - `def __init__(self, debug, routes, middleware, title, exception_handlers, on_startup, on_shutdown, lifespan, hdrs, ftrs, exts, before, after, surreal, htmx, default_hdrs, sess_cls, secret_key, session_cookie, max_age, sess_path, same_site, sess_https_only, sess_domain, key_fname, body_wrap, htmlkw, nb_hdrs, canonical, **bodykw)` + +- `@patch def ws(self, path, conn, disconn, name, middleware)` + Add a websocket route at `path` + +- `def nested_name(f)` + Get name of function `f` using '_' to join nested function names + +- `@patch def route(self, path, methods, name, include_in_schema, body_wrap)` + Add a route at `path` + +- `def serve(appname, app, host, port, reload, reload_includes, reload_excludes)` + Run the app in an async server, with live reload set as the default. + +- `class Client` + A simple httpx ASGI client that doesn't require `async` + + - `def __init__(self, app, url)` + +- `class RouteFuncs` + - `def __init__(self)` + - `def __setattr__(self, name, value)` + - `def __getattr__(self, name)` + - `def __dir__(self)` + +- `class APIRouter` + Add routes to an app + + - `def __init__(self, prefix, body_wrap)` + - `def __call__(self, path, methods, name, include_in_schema, body_wrap)` + Add a route at `path` + + - `def __getattr__(self, name)` + - `def to_app(self, app)` + Add routes to `app` + + - `def ws(self, path, conn, disconn, name, middleware)` + Add a websocket route at `path` + + +- `def cookie(key, value, max_age, expires, path, domain, secure, httponly, samesite)` + Create a 'set-cookie' `HttpHeader` + +- `@patch def static_route_exts(self, prefix, static_path, exts)` + Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()` + +- `@patch def static_route(self, ext, prefix, static_path)` + Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.') + +- `class MiddlewareBase` + - `def __call__(self, scope, receive, send)` + +- `class FtResponse` + Wrap an FT response with any Starlette `Response` + + - `def __init__(self, content, status_code, headers, cls, media_type, background)` + - `def __response__(self, req)` + +## fasthtml.fastapp + +> The `fast_app` convenience wrapper + +- `def fast_app(db_file, render, hdrs, ftrs, tbls, before, middleware, live, debug, title, routes, exception_handlers, on_startup, on_shutdown, lifespan, default_hdrs, pico, surreal, htmx, exts, canonical, secret_key, key_fname, session_cookie, max_age, sess_path, same_site, sess_https_only, sess_domain, htmlkw, bodykw, reload_attempts, reload_interval, static_path, body_wrap, nb_hdrs, **kwargs)` + Create a FastHTML or FastHTMLWithLiveReload app. + +## fasthtml.js + +> Basic external Javascript lib wrappers + +- `def light_media(css)` + Render light media for day mode views + +- `def dark_media(css)` + Render dark media for night mode views + +- `def MarkdownJS(sel)` + Implements browser-based markdown rendering. + +- `def HighlightJS(sel, langs, light, dark)` + Implements browser-based syntax highlighting. Usage example [here](/tutorials/quickstart_for_web_devs.html#code-highlighting). + +- `def MermaidJS(sel, theme)` + Implements browser-based Mermaid diagram rendering. + +## fasthtml.jupyter + +> Use FastHTML in Jupyter notebooks + +- `def nb_serve(app, log_level, port, host, **kwargs)` + Start a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` + +- `def nb_serve_async(app, log_level, port, host, **kwargs)` + Async version of `nb_serve` + +- `def is_port_free(port, host)` + Check if `port` is free on `host` + +- `def wait_port_free(port, host, max_wait)` + Wait for `port` to be free on `host` + +- `class JupyUvi` + Start and stop a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` + + - `def __init__(self, app, log_level, host, port, start, **kwargs)` + - `def start(self)` + - `def start_async(self)` + - `def stop(self)` + +- `class JupyUviAsync` + Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` + + - `def __init__(self, app, log_level, host, port, **kwargs)` + - `def start(self)` + - `def stop(self)` + +- `def HTMX(path, host, app, port, height, link, iframe)` + An iframe which displays the HTMX application in a notebook. + +## fasthtml.live_reload + +- `class FastHTMLWithLiveReload` + `FastHTMLWithLiveReload` enables live reloading. + This means that any code changes saved on the server will automatically + trigger a reload of both the server and browser window. + + How does it work? + - a websocket is created at `/live-reload` + - a small js snippet `LIVE_RELOAD_SCRIPT` is injected into each webpage + - this snippet connects to the websocket at `/live-reload` and listens for an `onclose` event + - when the `onclose` event is detected the browser is reloaded + + Why do we listen for an `onclose` event? + When code changes are saved the server automatically reloads if the --reload flag is set. + The server reload kills the websocket connection. The `onclose` event serves as a proxy + for "developer has saved some changes". + + Usage + >>> from fasthtml.common import * + >>> app = FastHTMLWithLiveReload() + + Run: + serve() + + - `def __init__(self, *args, **kwargs)` + +## fasthtml.oauth + +> Basic scaffolding for handling OAuth + +- `class GoogleAppClient` + A `WebApplicationClient` for Google oauth2 + + - `def __init__(self, client_id, client_secret, code, scope, project_id, **kwargs)` + - `@classmethod def from_file(cls, fname, code, scope, **kwargs)` + +- `class GitHubAppClient` + A `WebApplicationClient` for GitHub oauth2 + + - `def __init__(self, client_id, client_secret, code, scope, **kwargs)` + +- `class HuggingFaceClient` + A `WebApplicationClient` for HuggingFace oauth2 + + - `def __init__(self, client_id, client_secret, code, scope, state, **kwargs)` + +- `class DiscordAppClient` + A `WebApplicationClient` for Discord oauth2 + + - `def __init__(self, client_id, client_secret, is_user, perms, scope, **kwargs)` + - `def login_link(self, redirect_uri, scope, state)` + - `def parse_response(self, code, redirect_uri)` + +- `class Auth0AppClient` + A `WebApplicationClient` for Auth0 OAuth2 + + - `def __init__(self, domain, client_id, client_secret, code, scope, redirect_uri, **kwargs)` + - `def login_link(self, req)` + +- `@patch def login_link(self, redirect_uri, scope, state, **kwargs)` + Get a login link for this client + +- `def redir_url(request, redir_path, scheme)` + Get the redir url for the host in `request` + +- `@patch def parse_response(self, code, redirect_uri)` + Get the token from the oauth2 server response + +- `@patch def get_info(self, token)` + Get the info for authenticated user + +- `@patch def retr_info(self, code, redirect_uri)` + Combines `parse_response` and `get_info` + +- `@patch def retr_id(self, code, redirect_uri)` + Call `retr_info` and then return id/subscriber value + +- `class OAuth` + - `def __init__(self, app, cli, skip, redir_path, error_path, logout_path, login_path, https, http_patterns)` + - `def redir_login(self, session)` + - `def redir_url(self, req)` + - `def login_link(self, req, scope, state)` + - `def check_invalid(self, req, session, auth)` + - `def logout(self, session)` + - `def get_auth(self, info, ident, session, state)` + +- `@patch() def consent_url(self, proj)` + Get Google OAuth consent screen URL + +- `@patch def save(self, fname)` + Save credentials to `fname` + +- `def load_creds(fname)` + Load credentials from `fname` + +- `@patch def creds(self)` + Create `Credentials` from the client, refreshing if needed + +## fasthtml.pico + +> Basic components for generating Pico CSS tags + +- `@delegates(ft_hx, keep=True) def Card(*c, **kwargs)` + A PicoCSS Card, implemented as an Article with optional Header and Footer + +- `@delegates(ft_hx, keep=True) def Group(*c, **kwargs)` + A PicoCSS Group, implemented as a Fieldset with role 'group' + +- `@delegates(ft_hx, keep=True) def Search(*c, **kwargs)` + A PicoCSS Search, implemented as a Form with role 'search' + +- `@delegates(ft_hx, keep=True) def Grid(*c, **kwargs)` + A PicoCSS Grid, implemented as child Divs in a Div with class 'grid' + +- `@delegates(ft_hx, keep=True) def DialogX(*c, **kwargs)` + A PicoCSS Dialog, with children inside a Card + +- `@delegates(ft_hx, keep=True) def Container(*args, **kwargs)` + A PicoCSS Container, implemented as a Main with class 'container' + +## fasthtml.stripe_otp + +- `def create_price(app_nm, amt, currency)` + Create a product and bind it to a price object. If product already exist just return the price list. + +- `def archive_price(app_nm)` + Archive a price - useful for cleanup if testing. + +- `class Payment` + +## fasthtml.svg + +> Simple SVG FT elements + +- `def Svg(*args, **kwargs)` + An SVG tag; xmlns is added automatically, and viewBox defaults to height and width if not provided + +- `@delegates(ft_hx) def ft_svg(tag, *c, **kwargs)` + Create a standard `FT` element with some SVG-specific attrs + +- `@delegates(ft_svg) def Rect(width, height, x, y, fill, stroke, stroke_width, rx, ry, **kwargs)` + A standard SVG `rect` element + +- `@delegates(ft_svg) def Circle(r, cx, cy, fill, stroke, stroke_width, **kwargs)` + A standard SVG `circle` element + +- `@delegates(ft_svg) def Ellipse(rx, ry, cx, cy, fill, stroke, stroke_width, **kwargs)` + A standard SVG `ellipse` element + +- `def transformd(translate, scale, rotate, skewX, skewY, matrix)` + Create an SVG `transform` kwarg dict + +- `@delegates(ft_svg) def Line(x1, y1, x2, y2, stroke, w, stroke_width, **kwargs)` + A standard SVG `line` element + +- `@delegates(ft_svg) def Polyline(*args, **kwargs)` + A standard SVG `polyline` element + +- `@delegates(ft_svg) def Polygon(*args, **kwargs)` + A standard SVG `polygon` element + +- `@delegates(ft_svg) def Text(*args, **kwargs)` + A standard SVG `text` element + +- `class PathFT` + - `def M(self, x, y)` + Move to. + + - `def L(self, x, y)` + Line to. + + - `def H(self, x)` + Horizontal line to. + + - `def V(self, y)` + Vertical line to. + + - `def Z(self)` + Close path. + + - `def C(self, x1, y1, x2, y2, x, y)` + Cubic Bézier curve. + + - `def S(self, x2, y2, x, y)` + Smooth cubic Bézier curve. + + - `def Q(self, x1, y1, x, y)` + Quadratic Bézier curve. + + - `def T(self, x, y)` + Smooth quadratic Bézier curve. + + - `def A(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y)` + Elliptical Arc. + + +- `def SvgOob(*args, **kwargs)` + Wraps an SVG shape as required for an HTMX OOB swap + +- `def SvgInb(*args, **kwargs)` + Wraps an SVG shape as required for an HTMX inband swap + +## fasthtml.xtend + +> Simple extensions to standard HTML components, such as adding sensible defaults + +- `@delegates(ft_hx, keep=True) def A(*c, **kwargs)` + An A tag; `href` defaults to '#' for more concise use with HTMX + +- `@delegates(ft_hx, keep=True) def AX(txt, hx_get, target_id, hx_swap, href, **kwargs)` + An A tag with just one text child, allowing hx_get, target_id, and hx_swap to be positional params + +- `@delegates(ft_hx, keep=True) def Form(*c, **kwargs)` + A Form tag; identical to plain `ft_hx` version except default `enctype='multipart/form-data'` + +- `@delegates(ft_hx, keep=True) def Hidden(value, id, **kwargs)` + An Input of type 'hidden' + +- `@delegates(ft_hx, keep=True) def CheckboxX(checked, label, value, id, name, **kwargs)` + A Checkbox optionally inside a Label, preceded by a `Hidden` with matching name + +- `@delegates(ft_html, keep=True) def Script(code, **kwargs)` + A Script tag that doesn't escape its code + +- `@delegates(ft_html, keep=True) def Style(*c, **kwargs)` + A Style tag that doesn't escape its code + +- `def double_braces(s)` + Convert single braces to double braces if next to special chars or newline + +- `def undouble_braces(s)` + Convert double braces to single braces if next to special chars or newline + +- `def loose_format(s, **kw)` + String format `s` using `kw`, without being strict about braces outside of template params + +- `def ScriptX(fname, src, nomodule, type, _async, defer, charset, crossorigin, integrity, **kw)` + A `script` element with contents read from `fname` + +- `def replace_css_vars(css, pre, **kwargs)` + Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre` + +- `def StyleX(fname, **kw)` + A `style` element with contents read from `fname` and variables replaced from `kw` + +- `def Nbsp()` + A non-breaking space + +- `def Surreal(code)` + Wrap `code` in `domReadyExecute` and set `m=me()` and `p=me('-')` + +- `def On(code, event, sel, me)` + An async surreal.js script block event handler for `event` on selector `sel,p`, making available parent `p`, event `ev`, and target `e` + +- `def Prev(code, event)` + An async surreal.js script block event handler for `event` on previous sibling, with same vars as `On` + +- `def Now(code, sel)` + An async surreal.js script block on selector `me(sel)` + +- `def AnyNow(sel, code)` + An async surreal.js script block on selector `any(sel)` + +- `def run_js(js, id, **kw)` + Run `js` script, auto-generating `id` based on name of caller if needed, and js-escaping any `kw` params + +- `def jsd(org, repo, root, path, prov, typ, ver, esm, **kwargs)` + jsdelivr `Script` or CSS `Link` tag, or URL + +- `class Fragment` + An empty tag, used as a container + + - `def __init__(self, *c)` + +- `@delegates(ft_hx, keep=True) def Titled(title, *args, **kwargs)` + An HTML partial containing a `Title`, and `H1`, and any provided children + +- `def Socials(title, site_name, description, image, url, w, h, twitter_site, creator, card)` + OG and Twitter social card headers + +- `def YouTubeEmbed(video_id, **kwargs)` + Embed a YouTube video + +- `def Favicon(light_icon, dark_icon)` + Light and dark favicon headers +# monsterui Module Documentation + +## monsterui.core + +- `class ThemeRadii(Enum)` + Members: none, sm, md, lg + + +- `class ThemeShadows` + +- `class ThemeFont` + +- `class Theme(Enum)` + Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind + daisyui + highlight.js options + Members: slate, stone, gray, neutral, red, rose, orange, green, blue, yellow, violet, zinc + + - `headers(self, mode, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` + Create frankenui and tailwind cdns + + - `local_headers(self, mode, static_dir, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` + Create headers using local files downloaded from CDNs + + +## monsterui.daisy + +- `class AlertT(Enum)` + Alert styles from DaisyUI + Members: info, success, warning, error + + +- `def Alert(*c, **kwargs)` + Alert informs users about important events. + +- `class StepsT(Enum)` + Options for Steps + Members: vertical, horizonal + + +- `class StepT(Enum)` + Step styles for LiStep + Members: primary, secondary, accent, info, success, warning, error, neutral + + +- `def Steps(*li, **kwargs)` + Creates a steps container + +- `def LiStep(*c, **kwargs)` + Creates a step list item + +- `class LoadingT(Enum)` + Members: spinner, dots, ring, ball, bars, infinity, xs, sm, md, lg + + +- `def Loading(cls, htmx_indicator, **kwargs)` + Creates a loading animation component + +- `class ToastHT(Enum)` + Horizontal position for Toast + Members: start, center, end + + +- `class ToastVT(Enum)` + Vertical position for Toast + Members: top, middle, bottom + + +## monsterui.foundations + +> Data Structures and Utilties + +- `def stringify(o)` + Converts input types into strings that can be passed to FT components + +- `class VEnum(Enum)` + Members: + + - `__str__(self)` + - `__add__(self, other)` + - `__radd__(self, other)` + +## monsterui.franken + +- `class TextT(Enum)` + Text Styles from https://franken-ui.dev/docs/text + Members: paragraph, lead, meta, gray, italic, xs, sm, lg, xl, light, normal, medium, bold, extrabold, muted, primary, secondary, success, warning, error, info, left, right, center, justify, start, end, top, middle, bottom, truncate, break_, nowrap, underline, highlight + + +- `class TextPresets(Enum)` + Common Typography Presets + Members: muted_sm, muted_lg, bold_sm, bold_lg, md_weight_sm, md_weight_muted + + +- `def CodeSpan(*c, **kwargs)` + A CodeSpan with Styling + +- `def CodeBlock(*c, **kwargs)` + CodeBlock with Styling + +- `def H1(*c, **kwargs)` + H1 with styling and appropriate size + +- `def H2(*c, **kwargs)` + H2 with styling and appropriate size + +- `def H3(*c, **kwargs)` + H3 with styling and appropriate size + +- `def H4(*c, **kwargs)` + H4 with styling and appropriate size + +- `def H5(*c, **kwargs)` + H5 with styling and appropriate size + +- `def H6(*c, **kwargs)` + H6 with styling and appropriate size + +- `def Subtitle(*c, **kwargs)` + Styled muted_sm text designed to go under Headings and Titles + +- `def Q(*c, **kwargs)` + Styled quotation mark + +- `def Em(*c, **kwargs)` + Styled emphasis text + +- `def Strong(*c, **kwargs)` + Styled strong text + +- `def I(*c, **kwargs)` + Styled italic text + +- `def Small(*c, **kwargs)` + Styled small text + +- `def Mark(*c, **kwargs)` + Styled highlighted text + +- `def Del(*c, **kwargs)` + Styled deleted text + +- `def Ins(*c, **kwargs)` + Styled inserted text + +- `def Sub(*c, **kwargs)` + Styled subscript text + +- `def Sup(*c, **kwargs)` + Styled superscript text + +- `def Blockquote(*c, **kwargs)` + Blockquote with Styling + +- `def Caption(*c, **kwargs)` + Styled caption text + +- `def Cite(*c, **kwargs)` + Styled citation text + +- `def Time(*c, **kwargs)` + Styled time element + +- `def Address(*c, **kwargs)` + Styled address element + +- `def Abbr(*c, **kwargs)` + Styled abbreviation with dotted underline + +- `def Dfn(*c, **kwargs)` + Styled definition term with italic and medium weight + +- `def Kbd(*c, **kwargs)` + Styled keyboard input with subtle background + +- `def Samp(*c, **kwargs)` + Styled sample output with subtle background + +- `def Var(*c, **kwargs)` + Styled variable with italic monospace + +- `def Figure(*c, **kwargs)` + Styled figure container with card-like appearance + +- `def Details(*c, **kwargs)` + Styled details element + +- `def Summary(*c, **kwargs)` + Styled summary element + +- `def Data(*c, **kwargs)` + Styled data element + +- `def Meter(*c, **kwargs)` + Styled meter element + +- `def S(*c, **kwargs)` + Styled strikethrough text (different semantic meaning from Del) + +- `def U(*c, **kwargs)` + Styled underline (for proper names in Chinese, proper spelling etc) + +- `def Output(*c, **kwargs)` + Styled output element for form results + +- `def PicSumImg(h, w, id, grayscale, blur, **kwargs)` + Creates a placeholder image using https://picsum.photos/ + +- `def AccordionItem(title, *c)` + Creates a single item for use within an Accordion component, handling title, content, and open state. + +- `def Accordion(*c, **kwargs)` + Creates a styled Accordion container using accordion component. + +- `class ButtonT(Enum)` + Options for styling Buttons + Members: default, ghost, primary, secondary, destructive, text, link, xs, sm, lg, xl, icon + + +- `def Button(*c, **kwargs)` + Button with Styling (defaults to `submit` for form submission) + +- `class ContainerT(Enum)` + Max width container sizes from https://franken-ui.dev/docs/container + Members: xs, sm, lg, xl, expand + + +- `class BackgroundT(Enum)` + Members: muted, primary, secondary, default + + +- `def Container(*c, **kwargs)` + Div to be used as a container that often wraps large sections or a page of content + +- `def Titled(title, *c, **kwargs)` + Creates a standard page structure for titled page. Main(Container(title, content)) + +- `class DividerT(Enum)` + Divider Styles from https://franken-ui.dev/docs/divider + Members: icon, sm, vertical + + +- `def Divider(*c, **kwargs)` + Divider with default styling and margin + +- `def DividerSplit(*c)` + Creates a simple horizontal line divider with configurable thickness and vertical spacing + +- `def Article(*c, **kwargs)` + A styled article container for blog posts or similar content + +- `def ArticleTitle(*c, **kwargs)` + A title component for use within an Article + +- `def ArticleMeta(*c, **kwargs)` + A metadata component for use within an Article showing things like date, author etc + +- `class SectionT(Enum)` + Section styles from https://franken-ui.dev/docs/section + Members: default, muted, primary, secondary, xs, sm, lg, xl, remove_vertical + + +- `def Section(*c, **kwargs)` + Section with styling and margins + +- `def Form(*c, **kwargs)` + A Form with default spacing between form elements + +- `def Fieldset(*c, **kwargs)` + A Fieldset with default styling + +- `def Legend(*c, **kwargs)` + A Legend with default styling + +- `def Input(*c, **kwargs)` + An Input with default styling + +- `def Radio(*c, **kwargs)` + A Radio with default styling + +- `def CheckboxX(*c, **kwargs)` + A Checkbox with default styling + +- `def Range(*c, **kwargs)` + A Range with default styling + +- `def TextArea(*c, **kwargs)` + A Textarea with default styling + +- `def Switch(*c, **kwargs)` + A Switch with default styling + +- `def Upload(*c, **kwargs)` + A file upload component with default styling + +- `def UploadZone(*c, **kwargs)` + A file drop zone component with default styling + +- `def FormLabel(*c, **kwargs)` + A Label with default styling + +- `class LabelT(Enum)` + Members: primary, secondary, destructive + + +- `def Label(*c, **kwargs)` + FrankenUI labels, which look like pills + +- `def UkFormSection(title, description, *c)` + A form section with a title, description and optional button + +- `def GenericLabelInput(label, lbl_cls, input_cls, container, cls, id, input_fn, **kwargs)` + `Div(Label,Input)` component with Uk styling injected appropriately. Generally you should higher level API, such as `LabelInput` which is created for you in this library + +- `def LabelInput(label, lbl_cls, input_cls, cls, id, **kwargs)` + A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id + +- `def LabelRadio(label, lbl_cls, input_cls, container, cls, id, **kwargs)` + A FormLabel and Radio pair that provides default spacing and links/names them based on id + +- `def LabelCheckboxX(label, lbl_cls, input_cls, container, cls, id, **kwargs)` + A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id + +- `def Options(*c)` + Helper function to wrap things into `Option`s for use in `Select` + +- `def Select(*option, **kwargs)` + Creates a select dropdown with uk styling and option for adding a search box + +- `def LabelSelect(*option, **kwargs)` + A FormLabel and Select pair that provides default spacing and links/names them based on id + +- `@delegates(GenericLabelInput, but=['input_fn', 'cls']) def LabelRange(label, lbl_cls, input_cls, cls, id, value, min, max, step, label_range, **kwargs)` + A FormLabel and Range pair that provides default spacing and links/names them based on id + +- `class AT(Enum)` + Link styles from https://franken-ui.dev/docs/link + Members: muted, text, reset, primary, classic + + +- `class ListT(Enum)` + List styles using Tailwind CSS + Members: disc, circle, square, decimal, hyphen, bullet, divider, striped + + +- `def ModalContainer(*c, **kwargs)` + Creates a modal container that components go in + +- `def ModalDialog(*c, **kwargs)` + Creates a modal dialog + +- `def ModalHeader(*c, **kwargs)` + Creates a modal header + +- `def ModalBody(*c, **kwargs)` + Creates a modal body + +- `def ModalFooter(*c, **kwargs)` + Creates a modal footer + +- `def ModalTitle(*c, **kwargs)` + Creates a modal title + +- `def ModalCloseButton(*c, **kwargs)` + Creates a button that closes a modal with js + +- `def Modal(*c, **kwargs)` + Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you + +- `def Placeholder(*c, **kwargs)` + Creates a placeholder + +- `def Progress(*c, **kwargs)` + Creates a progress bar + +- `def UkIcon(icon, height, width, stroke_width, cls, **kwargs)` + Creates an icon using lucide icons + +- `def UkIconLink(icon, height, width, stroke_width, cls, button, **kwargs)` + Creates an icon link using lucide icons + +- `def DiceBearAvatar(seed_name, h, w)` + Creates an Avatar using https://dicebear.com/ + +- `def Center(*c, **kwargs)` + Centers contents both vertically and horizontally by default + +- `class FlexT(Enum)` + Flexbox modifiers using Tailwind CSS + Members: block, inline, left, center, right, between, around, stretch, top, middle, bottom, row, row_reverse, column, column_reverse, nowrap, wrap, wrap_reverse + + +- `def Grid(*div, **kwargs)` + Creates a responsive grid layout with smart defaults based on content + +- `def DivFullySpaced(*c, **kwargs)` + Creates a flex div with it's components having as much space between them as possible + +- `def DivCentered(*c, **kwargs)` + Creates a flex div with it's components centered in it + +- `def DivLAligned(*c, **kwargs)` + Creates a flex div with it's components aligned to the left + +- `def DivRAligned(*c, **kwargs)` + Creates a flex div with it's components aligned to the right + +- `def DivVStacked(*c, **kwargs)` + Creates a flex div with it's components stacked vertically + +- `def DivHStacked(*c, **kwargs)` + Creates a flex div with it's components stacked horizontally + +- `class NavT(Enum)` + Members: default, primary, secondary + + +- `def NavContainer(*li, **kwargs)` + Creates a navigation container (useful for creating a sidebar navigation). A Nav is a list (NavBar is something different) + +- `def NavParentLi(*nav_container, **kwargs)` + Creates a navigation list item with a parent nav for nesting + +- `def NavDividerLi(*c, **kwargs)` + Creates a navigation list item with a divider + +- `def NavHeaderLi(*c, **kwargs)` + Creates a navigation list item with a header + +- `def NavSubtitle(*c, **kwargs)` + Creates a navigation subtitle + +- `def NavCloseLi(*c, **kwargs)` + Creates a navigation list item with a close button + +- `class ScrollspyT(Enum)` + Members: underline, bold + + +- `def NavBar(*c)` + Creates a responsive navigation bar with mobile menu support + +- `def SliderContainer(*c, **kwargs)` + Creates a slider container + +- `def SliderItems(*c, **kwargs)` + Creates a slider items container + +- `def SliderNav(cls, prev_cls, next_cls, **kwargs)` + Navigation arrows for Slider component + +- `def Slider(*c, **kwargs)` + Creates a slider with optional navigation arrows + +- `def DropDownNavContainer(*li, **kwargs)` + A Nav that is part of a DropDown + +- `def TabContainer(*li, **kwargs)` + A TabContainer where children will be different tabs + +- `class CardT(Enum)` + Card styles from UIkit + Members: default, primary, secondary, destructive, hover + + +- `def CardTitle(*c, **kwargs)` + Creates a card title + +- `def CardHeader(*c, **kwargs)` + Creates a card header + +- `def CardBody(*c, **kwargs)` + Creates a card body + +- `def CardFooter(*c, **kwargs)` + Creates a card footer + +- `def CardContainer(*c, **kwargs)` + Creates a card container + +- `def Card(*c, **kwargs)` + Creates a Card with a header, body, and footer + +- `class TableT(Enum)` + Members: divider, striped, hover, sm, lg, justify, middle, responsive + + +- `def Table(*c, **kwargs)` + Creates a table + +- `def TableFromLists(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` + Creates a Table from a list of header data and a list of lists of body data + +- `def TableFromDicts(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` + Creates a Table from a list of header data and a list of dicts of body data + +- `def apply_classes(html_str, class_map, class_map_mods)` + Apply classes to html string + +- `def render_md(md_content, class_map, class_map_mods)` + Renders markdown using mistletoe and lxml + +- `def get_franken_renderer(img_dir)` + Create a renderer class with the specified img_dir + +- `def ThemePicker(color, radii, shadows, font, mode, cls, custom_themes)` + Theme picker component with configurable sections + +- `def LightboxContainer(*lightboxitem, **kwargs)` + Lightbox container that will hold `LightboxItems` + +- `def LightboxItem(*c, **kwargs)` + Anchor tag with appropriate structure to go inside a `LightBoxContainer` + +- `def ApexChart(**kws)` + Apex chart component +from asyncio import sleep +from fasthtml.common import * + +app = FastHTML(exts='ws') +rt = app.route + +def mk_inp(): return Input(id='msg') +nid = 'notifications' + +@rt('/') +async def get(): + cts = Div( + Div(id=nid), + Form(mk_inp(), id='form', ws_send=True), + hx_ext='ws', ws_connect='/ws') + return Titled('Websocket Test', cts) + +async def on_connect(send): await send(Div('Hello, you have connected', id=nid)) +async def on_disconnect( ): print('Disconnected!') + +@app.ws('/ws', conn=on_connect, disconn=on_disconnect) +async def ws(msg:str, send): + await send(Div('Hello ' + msg, id=nid)) + await sleep(2) + return Div('Goodbye ' + msg, id=nid), mk_inp() + +serve() +### Walkthrough of an idiomatic fasthtml app ### + +# This fasthtml app includes functionality from fastcore, starlette, fastlite, and fasthtml itself. +# Run with: `python adv_app.py` +# Importing from `fasthtml.common` brings the key parts of all of these together. We recommend using a wildcard import since only necessary parts are exported by the module. +from fasthtml.common import * +from hmac import compare_digest + +# We recommend using sqlite for most apps, as it is simple, fast, and scalable. `database()` creates the db if it doesn't exist. +db = database('data/utodos.db') +# Create regular classes for your database tables. There are auto-converted to fastcore flexiclasses, which are like dataclasses, but with some extra functionality. +class User: name:str; pwd:str +class Todo: id:int; title:str; done:bool; name:str; details:str; priority:int +# The `create` method creates a table in the database, if it doesn't already exist. The `pk` argument specifies the primary key for the table. If not provided, it defaults to 'id'. +users = db.create(User, pk='name') +# The `transform` argument is used to automatically update the database table, if it exists, to match the class definition. It is a simple and effective migration system for less complex needs. Use the `fastmigrate` package for more sophisticated migrations. +todos = db.create(Todo, transform=True) + +# Any Starlette response class can be returned by a FastHTML route handler. In that case, FastHTML won't change it at all. +login_redir = RedirectResponse('/login', status_code=303) + +# The `before` function is a *Beforeware* function. These are functions that run before a route handler is called. +def before(req, sess): + # This sets the `auth` attribute in the request scope, and gets it from the session. The session is a Starlette session, which is a dict-like object which is cryptographically signed, so it can't be tampered with. + # The `auth` key in the scope is automatically provided to any handler which requests it, and can not be injected by the user using query params, cookies, etc, so it should be secure to use. + auth = req.scope['auth'] = sess.get('auth', None) + if not auth: return login_redir + # `xtra` adds a filter to queries and DDL statements, to ensure that the user can only see/edit their own todos. + todos.xtra(name=auth) + +# Beforeware objects require the function itself, and optionally a list of regexes to skip. +bware = Beforeware(before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', '/login', '/send_login']) + +markdown_js = """ +import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js"; +proc_htmx('.markdown', e => e.innerHTML = marked.parse(e.textContent)); +""" + +# The `FastHTML` class is a subclass of `Starlette`, so you can use any parameters that `Starlette` accepts. In addition, you can add your Beforeware here, and any headers you want included in HTML responses. +def _not_found(req, exc): return Titled('Oh no!', Div('We could not find that page :(')) +app = FastHTML(before=bware, + # These are the same as Starlette exception_handlers, except they also support `FT` results + exception_handlers={404: _not_found}, + # PicoCSS is a simple CSS system for getting started; for more complex styling try MonsterUI (which wraps uikit and Tailwind) + hdrs=(picolink, # PicoCSS headers + # Look at fasthtml/js.py to see how to add Javascript libraries to FastHTML, like this one. + SortableJS('.sortable'), + # MarkdownJS is actually provided as part of FastHTML, but we've included the js code here so that you can see how it works. + Script(markdown_js, type='module')) + ) +# We add `rt` as a shortcut for `app.route`, which is what we'll use to decorate our route handlers. +rt = app.route + +# FastHTML uses Starlette's path syntax, and adds a `static` type which matches standard static file extensions. You can define your own regex path specifiers -- for instance this is how `static` is defined in FastHTML `reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|xml|html")` +# Provide param to `rt` to use full Starlette route syntax. +@rt("/{fname:path}.{ext:static}", methods=['GET']) +def static_handler(fname:str, ext:str): return FileResponse(f'{fname}.{ext}') + +# This function handles GET and POST requests to the `/login` path, because the name of the function automatically becomes the path for the route handler, and GET/POST are available by default. We recommend generally sticking to just these two HTTP verbs. +@rt +def login(): + # This creates a form with two input fields, and a submit button. `Input`, `Form`, etc are `FT` (fasttag) objects. FastHTML composes them from trees and auto-converts them to HTML when needed. + # You can also use plain HTML strings in handlers and headers, which will be auto-escaped, unless you use `Safe(...string...)`. If you want other custom tags (e.g. `MyTag`), they can be auto-generated by e.g: + # `from fasthtml.components import MyTag`. + # fasttag objects are callable. Calling them adds children and attributes to the tag. Therefore you can use them like this: + frm = Form(action=send_login, method='post')( + # Tags with a `name` attr will have `name` auto-set to the same as `id` if not provided + Input(id='name', placeholder='Name'), + Input(id='pwd', type='password', placeholder='Password'), + Button('login')) + # If a user visits the URL directly, FastHTML auto-generates a full HTML page. However, if the URL is accessed by HTMX, then one HTML partial is created for each element of the tuple. + # To avoid this auto-generation of a full page, return a `HTML` object, or a Starlette `Response`. + # `Titled` returns a tuple of a `Title` with the first arg and a `Container` with the rest. + # A handler can return either a single `FT` object or string, or a tuple of them. + # In the case of a tuple, the stringified objects are concatenated and returned to the browser. The `Title` tag has a special purpose: it sets the title of the page (this is HTMX's built in behavior for title HTML partials). + return Titled("Login", frm) + +# Handlers are passed whatever information they "request" in the URL, as keyword arguments. +# This handler is called when a POST request is made to the `/login` path. The `login` argument is an instance of the `Login` class, which has been auto-instantiated from the form data. +# There are a number of special parameter names, which will be passed useful information about the request: `session`: the Starlette session; `request`: the Starlette request; `auth`: the value of `scope['auth']`, `htmx`: the HTMX headers, if any; `app`: the FastHTML app object. +# You can also pass any string prefix of `request` or `session`. +@rt +def send_login(name:str, pwd:str, sess): + if not name or not pwd: return login_redir + # Indexing into a table queries by primary key, which is `name` here. + try: u = users[name] + # If the primary key does not exist, the method raises a `NotFoundError`. Here we use this to just generate a user -- in practice you'd probably to redirect to a signup page. + # Note that `insert` (and all similar db methods) returns the row object, so we can use it to get the new user. + except NotFoundError: u = users.insert(name=name, pwd=pwd) + if not compare_digest(u.pwd.encode("utf-8"), pwd.encode("utf-8")): return login_redir + # Because the session is signed, we can securely add information to it. It's stored in the browser cookies. If you don't pass a secret signing key to `FastHTML`, it will auto-generate one and store it in a file `./sesskey`. + sess['auth'] = u.name + return RedirectResponse('/', status_code=303) + +@rt +def logout(sess): + del sess['auth'] + return login_redir + +# Refactoring components in FastHTML is as simple as creating Python functions. The `clr_details` function creates a Div with specific HTMX attributes. +# `hx_swap_oob='innerHTML'` tells HTMX to swap the inner HTML of the target element out-of-band, meaning it will update this element regardless of where the HTMX request originated from. This returned div is empty, so it will clear the details view. +def clr_details(): return Div(hx_swap_oob='innerHTML', id='current-todo') + +# Dataclasses, dicts, namedtuples, TypedDicts, and custom classes are automatically instantiated from form data. +# In this case, the `Todo` class is a flexiblass (a subclass of dataclass), so the handler will be passed all the field names of it. +@rt +def update(todo: Todo): + # The updated todo is returned. By returning the updated todo, we can update the list directly. Because we return a tuple with `clr_details()`, the details view is also cleared. + # Later on, the `__ft__` method of the `Todo` class will be called to convert it to a fasttag. + return todos.update(todo), clr_details() + +@rt +def edit(id:int): + # `target_id` specifies which element will be updated with the server's response (it's a shortcut for hx_target=f"#{...}"). + # CheckboxX add an extra hidden field with the same name as the checkbox, so that it can be submitted as part of the form data. This is useful for boolean fields, where you want to know if the field was checked or not. + res = Form(hx_post=update, target_id=f'todo-{id}', id="edit")( + Group(Input(id="title"), Button("Save")), + Hidden(id="id"), CheckboxX(id="done", label='Done'), + Textarea(id="details", name="details", rows=10)) + # `fill_form` populates the form with existing todo data, and returns the result. Indexing into a table (`todos`) queries by primary key, which is `id` here. It also includes `xtra`, so this will only return the id if it belongs to the current user. + return fill_form(res, todos[id]) + +@rt +def rm(id:int): + # `delete` removes the item with the given primary key. + todos.delete(id) + # Returning `clr_details()` ensures the details view is cleared after deletion, leveraging HTMX's out-of-band swap feature. + # Note that we are not returning *any* FT component that doesn't have an "OOB" swap, so the target element inner HTML is simply deleted. + return clr_details() + +@rt +def show(id:int): + todo = todos[id] + # `hx_swap` determines how the update should occur. We use "outerHTML" to replace the entire todo `Li` element. + # `rm.to(id=todo.id)` is a shortcut for `f'/rm?id={todo.id}'`. All routes have this `to` method. + btn = Button('delete', hx_post=rm.to(id=todo.id), + hx_target=f'#todo-{todo.id}', hx_swap="outerHTML") + # The "markdown" class is used here because that's the CSS selector we used in the JS earlier. This will trigger the JS to parse the markdown. + # Because `class` is a reserved keyword in Python, we use `cls` instead, which FastHTML auto-converts. + return Div(H2(todo.title), Div(todo.details, cls="markdown"), btn) + +# `fastcore.patch` adds a method to an existing class. +# The `__ft__` method is a special method that FastHTML uses to convert the object into an `FT` object, so that it can be composed into an FT tree, and later rendered into HTML. +@patch +def __ft__(self:Todo): + # Some FastHTML tags have an 'X' suffix, which means they're "extended" in some way. For instance, here `AX` is an extended `A` tag, which takes 3 positional arguments: `(text, hx_get, target_id)`. + # All underscores in FT attrs are replaced with hyphens, so this will create an `hx-get` attr, which HTMX uses to trigger a GET request. + # Generally, most of your route handlers in practice (as in this demo app) are likely to be HTMX handlers. + ashow = AX(self.title, show.to(id=self.id), 'current-todo') + aedit = AX('edit', edit.to(id=self.id), 'current-todo') + dt = '✅ ' if self.done else '' + # FastHTML provides some shortcuts. For instance, `Hidden` is defined as simply: `return Input(type="hidden", value=value, **kwargs)` + cts = (dt, ashow, ' | ', aedit, Hidden(id="id", value=self.id), Hidden(id="priority", value="0")) + # Any FT object can take a list of children as positional args, and a dict of attrs as keyword args. + return Li(*cts, id=f'todo-{self.id}') + +@rt +def create(todo:Todo): + # `hx_swap_oob='true'` tells HTMX to perform an out-of-band swap, updating this element wherever it appears. This is used to clear the input field after adding the new todo. + new_inp = Input(id="new-title", name="title", placeholder="New Todo", hx_swap_oob='true') + # `insert` returns the inserted todo, which is appended to the list start, because we used `hx_swap='afterbegin'` when creating the form. + return todos.insert(todo), new_inp + +# Because the todo list form created earlier included hidden inputs with the todo IDs, they are included in the form data. By using a parameter called (e.g) "id", FastHTML will try to find something suitable in the request with this name. In order, it searches as follows: path; query; cookies; headers; session keys; form data. +# FastHTML will use your parameter's type annotation to try to cast the value to the requested type. In the case of form data, there can be multiple values with the same key. So in this case, the parameter is a list of ints. +@rt +def reorder(id:list[int]): + # Normally running a query in a loop like this would be really slow. But sqlite is at least as fast as a file system, so this pattern is actually idiomatic and efficient. + for i,id_ in enumerate(id): todos.update({'priority':i}, id_) + # HTMX by default replaces the inner HTML of the calling element, which in this case is the todo list form. Therefore, we return the list of todos, now in the correct order, which will be auto-converted to FT for us. + # In this case, it's not strictly necessary, because sortable.js has already reorder the DOM elements. However, by returning the updated data, we can be assured that there aren't sync issues between the DOM and the server. + return tuple(todos(order_by='priority')) + +# This is the handler for the main todo list application. By including the `auth` parameter, it gets passed the current username, for displaying in the title. `index()` is a special name for the main route handler, and is called when the root path `/` is accessed. +@rt +def index(auth): + title = f"{auth}'s Todo list" + top = Grid(H1(title), Div(A('logout', href=logout), style='text-align: right')) + new_inp = Input(id="new-title", name="title", placeholder="New Todo") + add = Form(Group(new_inp, Button("Add")), + hx_post=create, target_id='todo-list', hx_swap="afterbegin") + # Treating a table as a callable (i.e with `todos(...)` here) queries the table. Because we called `xtra` in our Beforeware, this queries the todos for the current user only. + # We can include the todo objects directly as children of the `Form`, because the `Todo` class has `__ft__` defined. This is automatically called by FastHTML to convert the `Todo` objects into `FT` objects when needed. + # The reason we put the todo list inside a form is so that we can use the 'sortable' js library to reorder them. That library calls the js `end` event when dragging is complete, so our trigger here causes our `/reorder` handler to be called. + frm = Form(*todos(order_by='priority'), + id='todo-list', cls='sortable', hx_post=reorder, hx_trigger="end") + # We create an empty 'current-todo' Div at the bottom of our page, as a target for the details and editing views. + card = Card(P('Drag/drop todos to reorder them'), + Ul(frm), + header=add, footer=Div(id='current-todo')) + # PicoCSS uses `
` page content; `Container` is a tiny function that generates that. + return Title(title), Container(top, card) + +# You do not need `if __name__ == '__main__':` in FastHTML apps, because `serve()` handles this automatically. By default it reloads the app when the source code changes. +serve() diff --git a/docs/vendor/monsterui/api_ref/docs_accordion_link.md b/docs/vendor/monsterui/api_ref/docs_accordion_link.md new file mode 100644 index 0000000..8670912 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_accordion_link.md @@ -0,0 +1,228 @@ +# Accordion API Reference + +### Example Accordions + +A simple accordion with fluid collapsing and expanding animation where only a single Section can be exanded at any time. + +See Source + +See Output + +## Accordion Header + + * Section 1 + +Content for the first section. + +More content here. + + * Section 2 + +Content for the second section. + +A label inside! + + * Section 3 - The last one! + +Content for the third section. + +[code] + + def ex_accordion_1(): + return Div( + H2("Accordion Header"), + Accordion( + AccordionItem( + "Section 1", + P("Content for the first section."), + P("More content here."), + ), + AccordionItem( + "Section 2", + P("Content for the second section."), + Label("A label inside!"), + li_kwargs={"id": "section-2"}, + ), + AccordionItem( + "Section 3 - The last one!", P("Content for the third section.") + ), + multiple=False, + animation=True, + ), + ), + +[/code] + +An accordion with fluid collapsing and expanding animation where one section is already expanded at startup and multiple section can be expanded at any time. + +See Source + +See Output + +## Accordion Header + + * Section 1 + +Content for the first section. + +More content here. + + * Section 2 + +Content for the second section. + +A label inside! + + * Section 3 - The last one! + +Content for the third section. + +[code] + + def ex_accordion_2(): + return Div( + H2("Accordion Header"), + Accordion( + AccordionItem( + "Section 1", + P("Content for the first section."), + P("More content here."), + open=True, + ), + AccordionItem( + "Section 2", + P("Content for the second section."), + Label("A label inside!"), + li_kwargs={"id": "section-2"}, + ), + AccordionItem( + "Section 3 - The last one!", P("Content for the third section.") + ), + multiple=True, + animation=True, + ), + ), + +[/code] + +An accordion with no collapsing and expanding animation where only a single Section can be exanded at any time. + +See Source + +See Output + +## Accordion Header + + * Section 1 + +Content for the first section. + +More content here. + + * Section 2 + +Content for the second section. + +A label inside! + + * Section 3 - The last one! + +Content for the third section. + +[code] + + def ex_accordion_3(): + return Div( + H2("Accordion Header"), + Accordion( + AccordionItem( + "Section 1", + P("Content for the first section."), + P("More content here."), + ), + AccordionItem( + "Section 2", + P("Content for the second section."), + Label("A label inside!"), + li_kwargs={"id": "section-2"}, + ), + AccordionItem( + "Section 3 - The last one!", P("Content for the third section.") + ), + multiple=False, + animation=False, + ), + ), + +[/code] + +### API Reference + +### Accordion + +Source + +[code] + + Accordion(*c: 'AccordionItem', cls: Union[str, enum.Enum, tuple] = (), multiple: Optional[bool] = None, collapsible: Optional[bool] = None, animation: Optional[bool] = None, duration: Optional[int] = None, active: Optional[int] = None, transition: Optional[str] = None, tag: str = 'ul', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a styled Accordion container using accordion component. + +**Params** + + * `c` One or more `AccordionItem` components + + * `cls` Additional classes for the container (`Ul` or `Div`) + + * `multiple` Allow multiple items to be open simultaneously (UIkit option) + + * `collapsible` Allow all items to be closed (UIkit option, default True) + + * `animation` Enable/disable animation (UIkit option, default True) + + * `duration` Animation duration in ms (UIkit option, default 200) + + * `active` Index (0-based) of the item to be open by default (UIkit option) + + * `transition` Animation transition timing function (UIkit option, e.g., 'ease-out') + + * `tag` HTML tag for the container ('ul' or 'div') + + * `kwargs` + +**Returns:** Ul(*items...) or Div(*items...) + +### AccordionItem + +Source + +[code] + + AccordionItem(title: Union[str, fastcore.xml.FT], *c: fastcore.xml.FT, cls: Union[str, enum.Enum, tuple] = (), title_cls: Union[str, enum.Enum, tuple] = ('flex justify-between items-center w-full',), content_cls: Union[str, enum.Enum, tuple] = (), open: bool = False, li_kwargs: Optional[Dict] = None, a_kwargs: Optional[Dict] = None, div_kwargs: Optional[Dict] = None) -> fastcore.xml.FT +[/code] + +> Creates a single item for use within an Accordion component, handling title, content, and open state. + +**Params** + + * `title` Content for the accordion item title + + * `c` Content to display when the item is open + + * `cls` Additional classes for the outer `Li` container + + * `title_cls` Additional classes for the title `A` tag + + * `content_cls` Additional classes for the content `Div` + + * `open` Whether this item should be open by default + + * `li_kwargs` Additional attributes for the outer `Li` tag + + * `a_kwargs` Additional attributes for the title `A` tag + + * `div_kwargs` Additional attributes for the content `Div` tag + +**Returns:** Li(A(title, Span(Icon, Icon)), Div(content)) + diff --git a/docs/vendor/monsterui/api_ref/docs_button_link.md b/docs/vendor/monsterui/api_ref/docs_button_link.md new file mode 100644 index 0000000..b12c1a6 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_button_link.md @@ -0,0 +1,90 @@ +# Buttons & Links API Reference + +See Source + +See Output + +DefaultPrimarySecondaryDangerTextLinkGhost + +[code] + + def ex_buttons(): + return Grid( + Button("Default"), + Button("Primary", cls=ButtonT.primary), + Button("Secondary", cls=ButtonT.secondary), + Button("Danger", cls=ButtonT.destructive), + Button("Text", cls=ButtonT.text), + Button("Link", cls=ButtonT.link), + Button("Ghost", cls=ButtonT.ghost), + ) + +[/code] + +See Source + +See Output + +Default LinkMuted LinkText LinkReset LinkPrimary LinkClassic Link + +[code] + + def ex_links(): + return Div(cls='space-x-4')( + A('Default Link'), + A('Muted Link', cls=AT.muted), + A('Text Link', cls=AT.text), + A('Reset Link', cls=AT.reset), + A('Primary Link', cls=AT.primary), + A('Classic Link', cls=AT.classic),) + +[/code] + +### Button + +Source + +[code] + + Button(*c: Union[str, fastcore.xml.FT], cls: Union[str, enum.Enum] = , submit=True, **kwargs) -> fastcore.xml.FT +[/code] + +> Button with Styling (defaults to `submit` for form submission) + +**Params** + + * `c` Contents of `Button` tag (often text) + + * `cls` Classes in addition to `Button` styling (use `ButtonT` for built in styles) + + * `submit` Whether the button should submit a form + + * `kwargs` + +**Returns:** Button(..., cls='uk-btn') + +* * * + +### ButtonT + +_Options for styling Buttons_ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +default | uk-btn-default | ghost | uk-btn-ghost | primary | uk-btn-primary +secondary | uk-btn-secondary | destructive | uk-btn-destructive | text | uk-btn-text +link | uk-btn-link | xs | uk-btn-xs | sm | uk-btn-sm +lg | uk-btn-lg | xl | uk-btn-xl | icon | uk-btn-icon + +* * * + +### AT + +_Link styles from https://franken-ui.dev/docs/link_ + +Option | Value | Option | Value +---|---|---|--- +muted | uk-link-muted | text | uk-link-text +reset | uk-link-reset | primary | uk-link text-primary hover:text-primary-focus underline +classic | text-blue-600 hover:text-blue-800 underline | | + diff --git a/docs/vendor/monsterui/api_ref/docs_cards.md b/docs/vendor/monsterui/api_ref/docs_cards.md new file mode 100644 index 0000000..faaa15d --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_cards.md @@ -0,0 +1,310 @@ +# Cards API Reference + +### Example Usage + +See Source + +See Output + +Header + +A card with header and footer + +Input + +Range + +Footer Submit Button + +[code] + + def ex_card(): + return Card( + Form(LabelInput("Input"), + LabelRange("Range")), + header=Div( + CardTitle("Header"), + P("A card with header and footer",cls=TextPresets.muted_sm)), + footer=DivLAligned(Button("Footer Submit Button"))) + +[/code] + +See Source + +See Output + +#### Creating Custom FastHTML Tags for Markdown Rendering + +A step by step tutorial to rendering markdown in FastHTML using zero-md inside of DaisyUI chat bubbles + +Isaac Flath20-October-2024 + +FastHTMLHTMXWeb Apps + +Read + +[code] + + def ex_card2_wide(): + def Tags(cats): return DivLAligned(map(Label, cats)) + + return Card( + DivLAligned( + A(Img(src="https://picsum.photos/200/200?random=12", style="width:200px"),href="#"), + Div(cls='space-y-3 uk-width-expand')( + H4("Creating Custom FastHTML Tags for Markdown Rendering"), + P("A step by step tutorial to rendering markdown in FastHTML using zero-md inside of DaisyUI chat bubbles"), + DivFullySpaced(map(Small, ["Isaac Flath", "20-October-2024"]), cls=TextT.muted), + DivFullySpaced( + Tags(["FastHTML", "HTMX", "Web Apps"]), + Button("Read", cls=(ButtonT.primary,'h-6'))))), + cls=CardT.hover) + +[/code] + +See Source + +See Output + +#### Creating Custom FastHTML Tags for Markdown Rendering + +A step by step tutorial to rendering markdown in FastHTML using zero-md inside of DaisyUI chat bubbles + +Isaac Flath20-October-2024 + +FastHTMLHTMXWeb Apps + +Read + +[code] + + def ex_card2_tall(): + def Tags(cats): return DivLAligned(map(Label, cats)) + + return Card( + Div( + A(Img(src="https://picsum.photos/400/200?random=14"), href="#"), + Div(cls='space-y-3 uk-width-expand')( + H4("Creating Custom FastHTML Tags for Markdown Rendering"), + P("A step by step tutorial to rendering markdown in FastHTML using zero-md inside of DaisyUI chat bubbles"), + DivFullySpaced(map(Small, ["Isaac Flath", "20-October-2024"]), cls=TextT.muted), + DivFullySpaced( + Tags(["FastHTML", "HTMX", "Web Apps"]), + Button("Read", cls=(ButtonT.primary,'h-6'))))), + cls=CardT.hover) + +[/code] + +See Source + +See Output + +### Sarah Chen + +Engineering Lead + +San Francisco + +### James Wilson + +Senior Developer + +New York + +### Maria Garcia + +UX Designer + +London + +### Alex Kumar + +Product Manager + +Singapore + +### Emma Brown + +DevOps Engineer + +Toronto + +### Marcus Johnson + +Frontend Developer + +Berlin + +[code] + + def ex_card3(): + def team_member(name, role, location="Remote"): + return Card( + DivLAligned( + DiceBearAvatar(name, h=24, w=24), + Div(H3(name), P(role))), + footer=DivFullySpaced( + DivHStacked(UkIcon("map-pin", height=16), P(location)), + DivHStacked(*(UkIconLink(icon, height=16) for icon in ("mail", "linkedin", "github"))))) + team = [ + team_member("Sarah Chen", "Engineering Lead", "San Francisco"), + team_member("James Wilson", "Senior Developer", "New York"), + team_member("Maria Garcia", "UX Designer", "London"), + team_member("Alex Kumar", "Product Manager", "Singapore"), + team_member("Emma Brown", "DevOps Engineer", "Toronto"), + team_member("Marcus Johnson", "Frontend Developer", "Berlin") + ] + return Grid(*team, cols_sm=1, cols_md=1, cols_lg=2, cols_xl=3) + +[/code] + +### API Reference + +### Card + +Source + +[code] + + Card(*c, header: Union[fastcore.xml.FT, Iterable[fastcore.xml.FT]] = None, footer: Union[fastcore.xml.FT, Iterable[fastcore.xml.FT]] = None, body_cls='space-y-6', header_cls=(), footer_cls=(), cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a Card with a header, body, and footer + +**Params** + + * `c` Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.) + + * `header` Component(s) that goes in the header (often a `ModalTitle` and a subtitle) + + * `footer` Component(s) that goes in the footer (often a `ModalCloseButton`) + + * `body_cls` classes for the body + + * `header_cls` classes for the header + + * `footer_cls` classes for the footer + + * `cls` class for outermost component + + * `kwargs` + +**Returns:** Card component + +### CardTitle + +Source + +[code] + + CardTitle(*c, cls=(), **kwargs) +[/code] + +> Creates a card title + +**Params** + + * `c` Components (often a string) + + * `cls` Additional classes on the div + + * `kwargs` + +* * * + +### CardT + +_Card styles from UIkit_ + +Option | Value | Option | Value +---|---|---|--- +default | uk-card-default | primary | uk-card-primary +secondary | uk-card-secondary | destructive | uk-card-destructive +hover | uk-card hover:shadow-lg hover:-translate-y-1 transition-all duration-200 | | + +The remainder of these are only needed if you're doing something really special. They are used in the `Card` function to generate the boilerplate for you. + +### CardContainer + +Source + +[code] + + CardContainer(*c, cls=, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a card container + +**Params** + + * `c` Components (typically `CardHeader`, `CardBody`, `CardFooter`) + + * `cls` Additional classes on the div + + * `kwargs` + +**Returns:** Container for a card + +### CardHeader + +Source + +[code] + + CardHeader(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a card header + +**Params** + + * `c` Components that goes in the header (often a `ModalTitle` and description) + + * `cls` Additional classes on the div + + * `kwargs` + +**Returns:** Container for the header of a card + +### CardBody + +Source + +[code] + + CardBody(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a card body + +**Params** + + * `c` Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.) + + * `cls` Additional classes on the div + + * `kwargs` + +**Returns:** Container for the body of a card + +### CardFooter + +Source + +[code] + + CardFooter(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a card footer + +**Params** + + * `c` Components that go in the footer (often a `ModalCloseButton`) + + * `cls` Additional classes on the div + + * `kwargs` + +**Returns:** Container for the footer of a card + diff --git a/docs/vendor/monsterui/api_ref/docs_charts.md b/docs/vendor/monsterui/api_ref/docs_charts.md new file mode 100644 index 0000000..9ee8b54 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_charts.md @@ -0,0 +1,75 @@ +# Charts API Reference + +MonsterUI supports ApexCharts, a javascript library for rendering different charts like line and pie charts. See the full list of chart types here. + +To render a chart you'll need to include the ApexChart js in your app headers like this + +`app, rt = fast_app(hdrs=Theme.blue.headers(apex_charts=True))` + +Then create an `ApexChart` component as shown in the examples below. + +Generally, you should be able to take any chart from the ApexChart docs, convert the chart's options var to a python dict and plug it straight into MonsterUI's ApexChart component. + +## Example usage + +#### Line chart + +See Source + +See Output + +[code] + + def ex_line_chart(): + return ApexChart( + opts={ + "chart": {"type":"line", "zoom":{"enabled": False}, "toolbar":{"show":False}}, + "series": [{"name":"Desktops", "data": [186, 305, 237, 73, 209, 214, 355]}], + "xaxis": {"categories":["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]} + }, + cls='max-w-md max-h-md' + ) + +[/code] + +#### Pie chart + +See Source + +See Output + +[code] + + def ex_pie_chart(): + return ApexChart( + opts={ + "chart": {"type":"pie", "zoom":{"enabled": False}, "toolbar":{"show":False}}, + "series": [186, 305, 237, 73, 209, 214, 355], + "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"] + }, + cls='max-w-md max-h-md' + ) + +[/code] + +### ApexChart + +Source + +[code] + + ApexChart(*, opts: Dict, cls: enum.Enum | str | tuple = (), **kws) -> fastcore.xml.FT +[/code] + +> Apex chart component + +**Params** + + * `opts` ApexChart options used to render your chart (e.g. {"chart":{"type":"line"}, ...}) + + * `cls` Classes for the outer container + + * `kws` + +**Returns:** Div(Uk_chart(Script(...))) + diff --git a/docs/vendor/monsterui/api_ref/docs_containers.md b/docs/vendor/monsterui/api_ref/docs_containers.md new file mode 100644 index 0000000..7884234 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_containers.md @@ -0,0 +1,167 @@ +# Articles, Containers & Sections API Reference + +### ArticleMeta + +Source + +[code] + + ArticleMeta(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A metadata component for use within an Article showing things like date, author etc + +**Params** + + * `c` contents of ArticleMeta tag (often other tags) + + * `cls` Classes in addition to ArticleMeta styling + + * `kwargs` + +**Returns:** P(..., cls='uk-article-meta') + +### ArticleTitle + +Source + +[code] + + ArticleTitle(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A title component for use within an Article + +**Params** + + * `c` contents of ArticleTitle tag (often other tags) + + * `cls` Classes in addition to ArticleTitle styling + + * `kwargs` + +**Returns:** H1(..., cls='uk-article-title') + +### Article + +Source + +[code] + + Article(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A styled article container for blog posts or similar content + +**Params** + + * `c` contents of Article tag (often other tags) + + * `cls` Classes in addition to Article styling + + * `kwargs` + +**Returns:** Article(..., cls='uk-article') + +See Source + +See Output + +# Sample Article Title + +By: John Doe + +lorem ipsum dolor sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +[code] + + def ex_articles(): + return Article( + ArticleTitle("Sample Article Title"), + Subtitle("By: John Doe"), + P('lorem ipsum dolor sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')) + +[/code] + +### Container + +Source + +[code] + + Container(*c, cls=('mt-5', ), **kwargs) -> fastcore.xml.FT +[/code] + +> Div to be used as a container that often wraps large sections or a page of content + +**Params** + + * `c` Contents of Container tag (often other FT Components) + + * `cls` Classes in addition to Container styling + + * `kwargs` + +**Returns:** Container(..., cls='uk-container') + +* * * + +### ContainerT + +_Max width container sizes from https://franken-ui.dev/docs/container_ + +Option | Value | Option | Value +---|---|---|--- +xs | uk-container-xs | sm | uk-container-sm +lg | uk-container-lg | xl | uk-container-xl +expand | uk-container-expand | | + +See Source + +See Output + +This is a sample container with custom styling. + +[code] + + def ex_containers(): + return Container( + "This is a sample container with custom styling.", + cls=ContainerT.xs, + style="background-color: #FFA500; color: #000000") + +[/code] + +### Section + +Source + +[code] + + Section(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Section with styling and margins + +**Params** + + * `c` contents of Section tag (often other tags) + + * `cls` Classes in addition to Section styling + + * `kwargs` + +**Returns:** Div(..., cls='uk-section') + +* * * + +### SectionT + +_Section styles from https://franken-ui.dev/docs/section_ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +default | uk-section-default | muted | uk-section-muted | primary | uk-section-primary +secondary | uk-section-secondary | xs | uk-section-xsmall | sm | uk-section-small +lg | uk-section-large | xl | uk-section-xlarge | remove_vertical | uk-section-remove-vertical + diff --git a/docs/vendor/monsterui/api_ref/docs_dividers.md b/docs/vendor/monsterui/api_ref/docs_dividers.md new file mode 100644 index 0000000..d79dd40 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_dividers.md @@ -0,0 +1,122 @@ +# Dividers API Reference + +### Divider + +Source + +[code] + + Divider(*c, cls=('my-4', ), **kwargs) -> fastcore.xml.FT +[/code] + +> Divider with default styling and margin + +**Params** + + * `c` contents of Divider tag (often nothing) + + * `cls` Classes in addition to Divider styling + + * `kwargs` + +**Returns:** Hr(..., cls='uk-divider-icon') or Div(..., cls='uk-divider-vertical') + +* * * + +### DividerT + +_Divider Styles from https://franken-ui.dev/docs/divider_ + +Option | Value | Option | Value +---|---|---|--- +icon | uk-divider-icon | sm | uk-divider-sm +vertical | uk-divider-vertical | | + +See Source + +See Output + +Small Divider + +* * * + +Vertical Divider + +Icon Divider + +* * * +[code] + + def ex_dividers(): + return Div( + P("Small Divider"), + Divider(cls=DividerT.sm), + DivCentered( + P("Vertical Divider"), + Divider(cls=DividerT.vertical)), + DivCentered("Icon Divider"), + Divider(cls=DividerT.icon)) + +[/code] + +### DividerSplit + +Source + +[code] + + DividerSplit(*c, cls=(), line_cls=(), text_cls=()) +[/code] + +> Creates a simple horizontal line divider with configurable thickness and vertical spacing + +**Params** + + * `c` + + * `cls` + + * `line_cls` + + * `text_cls` + +See Source + +See Output + +Or continue with + +[code] + + def ex_dividersplit(): + return DividerSplit(P("Or continue with", cls=TextPresets.muted_sm)) + +[/code] + +### DividerLine + +Source + +[code] + + DividerLine(lwidth=2, y_space=4) +[/code] + +> **Params** + + * `lwidth` + + * `y_space` + +See Source + +See Output + +* * * +[code] + + def ex_dividerline(): + return DividerLine() + +[/code] + diff --git a/docs/vendor/monsterui/api_ref/docs_forms.md b/docs/vendor/monsterui/api_ref/docs_forms.md new file mode 100644 index 0000000..7bfe094 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_forms.md @@ -0,0 +1,733 @@ +# Forms and User Inputs API Reference + +### Example Form + +This form was live coded in a 5 minute video here + +See Source + +See Output + +### Emergency Contact Form + +Please fill out the form completely + +First Name + +Last Name + +Email + +Phone + +### Relationship to patient + +Parent + +Sibling + +Friend + +Spouse + +Significant Other + +Relative + +Child + +Other + +Address + +Address Line 2 + +City + +State + +Zip + +Submit Form + +[code] + + def ex_form(): + relationship = ["Parent",'Sibling', "Friend", "Spouse", "Significant Other", "Relative", "Child", "Other"] + return Div(cls='space-y-4')( + DivCentered( + H3("Emergency Contact Form"), + P("Please fill out the form completely", cls=TextPresets.muted_sm)), + Form(cls='space-y-4')( + Grid(LabelInput("First Name",id='fn'), LabelInput("Last Name",id='ln')), + Grid(LabelInput("Email", id='em'), LabelInput("Phone", id='ph')), + H3("Relationship to patient"), + Grid(*[LabelCheckboxX(o) for o in relationship], cols=4, cls='space-y-3'), + LabelInput("Address", id='ad'), + LabelInput("Address Line 2", id='ad2'), + Grid(LabelInput("City", id='ct'), LabelInput("State", id='st')), + LabelInput("Zip", id='zp'), + DivCentered(Button("Submit Form", cls=ButtonT.primary)))) + +[/code] + +See Source + +See Output + +Upload Button! + +Upload Zone + +[code] + + def ex_upload(): + return Div(Upload("Upload Button!", id='upload1'), + UploadZone(DivCentered(Span("Upload Zone"), UkIcon("upload")), id='upload2'), + cls='space-y-4') + +[/code] + +### FormLabel + +Source + +[code] + + FormLabel(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Label with default styling + +**Params** + + * `c` contents of FormLabel tag (often text) + + * `cls` Classes in addition to FormLabel styling + + * `kwargs` + +**Returns:** Label(..., cls='uk-form-label') + +See Source + +See Output + +Form Label + +[code] + + def ex_formlabel(): + return FormLabel("Form Label") + +[/code] + +### Input + +Source + +[code] + + Input(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> An Input with default styling + +**Params** + + * `c` contents of Input tag (often nothing) + + * `cls` Classes in addition to Input styling + + * `kwargs` + +**Returns:** Input(..., cls='uk-input') + +See Source + +See Output + +Input + +[code] + + def ex_input(): + return Div( + Input(placeholder="Enter text"), + LabelInput(label="Input", id='myid')) + +[/code] + +### LabelInput + +Source + +[code] + + LabelInput(label: str | fastcore.xml.FT, lbl_cls='', input_cls='', cls='space-y-2', id='', **kwargs) -> fastcore.xml.FT +[/code] + +> A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id + +**Params** + + * `label` FormLabel content (often text) + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `Input` + + * `cls` Classes on container (default is `'space-y-2'` to prevent scrunched up form elements) + + * `id` id for `FormLabel` and `Input` (`id`, `name` and `for` attributes are set to this value) + + * `kwargs` + +**Returns:** Div(cls='space-y-2')(`FormLabel`, `Input`) + +### LabelCheckboxX + +Source + +[code] + + LabelCheckboxX(label: str | fastcore.xml.FT, lbl_cls='', input_cls='', container=functools.partial(, 'div', void_=False), cls='flex items-center space-x-2', id='', **kwargs) -> fastcore.xml.FT +[/code] + +> A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id + +**Params** + + * `label` FormLabel content (often text) + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `CheckboxX` + + * `container` Container to wrap label and input in (default is Div) + + * `cls` Classes on container (default is 'flex items-center space-x-2') + + * `id` id for `FormLabel` and `CheckboxX` (`id`, `name` and `for` attributes are set to this value) + + * `kwargs` + +**Returns:** Div(cls='flex items-center space-x-2')(`FormLabel`, `CheckboxX`) + +### LabelSwitch + +Source + +[code] + + LabelSwitch(label: str | fastcore.xml.FT, lbl_cls='', input_cls='', cls='space-x-2', id='', *, container=functools.partial(, 'div', void_=False)) -> fastcore.xml.FT +[/code] + +> **Params** + + * `label` FormLabel content (often text) + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `Switch` + + * `container` Container to wrap label and input in (default is Div) + + * `cls` Classes on container (default is `'space-x-2'` to prevent scrunched up form elements) + + * `id` id for `FormLabel` and `Switch` (`id`, `name` and `for` attributes are set to this value) + +**Returns:** Div(cls='space-y-2')(`FormLabel`, `Switch`) + +### LabelRange + +Source + +[code] + + LabelRange(label: str | fastcore.xml.FT, lbl_cls='', input_cls='', cls='space-y-6', id='', value='', min=None, max=None, step=None, label_range=True, *, container=functools.partial(, 'div', void_=False)) -> fastcore.xml.FT +[/code] + +> A FormLabel and Range pair that provides default spacing and links/names them based on id + +**Params** + + * `label` FormLabel content (often text) + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `Range` + + * `container` Container to wrap label and input in (default is Div) + + * `cls` Classes on container (default is `'space-y-2'` to prevent scrunched up form elements) + + * `id` id for `FormLabel` and `Range` (`id`, `name` and `for` attributes are set to this value) + + * `value` Value for the range input + + * `min` Minimum value + + * `max` Maximum value + + * `step` Step size + + * `label_range` Whether to show the range value label (label for the `Range` component) + +**Returns:** Div(cls='space-y-2')(`FormLabel`, `Range`) + +### LabelTextArea + +Source + +[code] + + LabelTextArea(label: str | fastcore.xml.FT, value='', lbl_cls='', input_cls='', cls='space-y-2', id='', **kwargs) -> fastcore.xml.FT +[/code] + +> **Params** + + * `label` FormLabel content (often text) + + * `value` Value for the textarea + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `TextArea` + + * `cls` Classes on container (default is `'space-y-2'` to prevent scrunched up form elements) + + * `id` id for `FormLabel` and `TextArea` (`id`, `name` and `for` attributes are set to this value) + + * `kwargs` + +**Returns:** Div(cls='space-y-2')(`FormLabel`, `TextArea`) + +### LabelRadio + +Source + +[code] + + LabelRadio(label: str | fastcore.xml.FT, lbl_cls='', input_cls='', container=functools.partial(, 'div', void_=False), cls='flex items-center space-x-2', id='', **kwargs) -> fastcore.xml.FT +[/code] + +> A FormLabel and Radio pair that provides default spacing and links/names them based on id + +**Params** + + * `label` FormLabel content (often text) + + * `lbl_cls` Additional classes for `FormLabel` + + * `input_cls` Additional classes for `Radio` + + * `container` Container to wrap label and input in (default is Div) + + * `cls` Classes on container (default is 'flex items-center space-x-2') + + * `id` id for `FormLabel` and `Radio` (`id`, `name` and `for` attributes are set to this value) + + * `kwargs` + +**Returns:** Div(cls='flex items-center space-x-2')(`FormLabel`, `Radio`) + +### LabelSelect + +Source + +[code] + + LabelSelect(*option, label=(), lbl_cls=(), inp_cls=(), cls=('space-y-2',), id='', name='', placeholder='', searchable=False, select_kwargs=None, **kwargs) +[/code] + +> A FormLabel and Select pair that provides default spacing and links/names them based on id + +**Params** + + * `option` Options for the select dropdown (can use `Options` helper function to create) + + * `label` String or FT component for the label + + * `lbl_cls` Additional classes for the label + + * `inp_cls` Additional classes for the select input + + * `cls` Classes for the outer div + + * `id` ID for the select input + + * `name` Name attribute for the select input + + * `placeholder` Placeholder text for the select input + + * `searchable` Whether the select should be searchable + + * `select_kwargs` Additional Arguments passed to Select + + * `kwargs` + +### Progress + +Source + +[code] + + Progress(*c, cls=(), value='', max='100', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a progress bar + +**Params** + + * `c` Components to put in the progress bar (often nothing) + + * `cls` Additional classes on the progress bar + + * `value` Value of the progress bar + + * `max` Max value of the progress bar (defaults to 100 for percentage) + + * `kwargs` + +**Returns:** Progress(..., cls='uk-progress') + +See Source + +See Output + +[code] + + def ex_progress(): + return Progress(value=20, max=100) + +[/code] + +### Radio + +Source + +[code] + + Radio(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Radio with default styling + +**Params** + + * `c` contents of Radio tag (often nothing) + + * `cls` Classes in addition to Radio styling + + * `kwargs` + +**Returns:** Input(..., cls='uk-radio', type='radio') + +See Source + +See Output + +Radio + +[code] + + def ex_radio(): + return Div( + Radio(name="radio-group", id="radio1"), + LabelRadio(label="Radio", id='radio1',cls='flex items-center space-x-4')) + +[/code] + +### CheckboxX + +Source + +[code] + + CheckboxX(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Checkbox with default styling + +**Params** + + * `c` contents of CheckboxX tag (often nothing) + + * `cls` Classes in addition to CheckboxX styling + + * `kwargs` + +**Returns:** Input(..., cls='uk-checkbox', type='checkbox') + +See Source + +See Output + +Checkbox + +[code] + + def ex_checkbox(): + return Div( + CheckboxX(), + LabelCheckboxX(label="Checkbox", id='checkbox1')) + +[/code] + +### Range + +Source + +[code] + + Range(*c, value='', label=True, min=None, max=None, step=None, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Range with default styling + +**Params** + + * `c` contents of Range tag (often nothing) + + * `value` + + * `label` + + * `min` + + * `max` + + * `step` + + * `cls` Classes in addition to Range styling + + * `kwargs` + +**Returns:** Input(..., cls='uk-range', type='range') + +See Source + +See Output + +Basic Range + +Range with Label + +Multiple Values + +Custom Range + +[code] + + def ex_range(): + return Div( + Range(), + Range(label='kg', value="25,75", min=20, max=75), + LabelRange('Basic Range', value='50', min=0, max=100, step=1), + LabelRange('Range with Label', value='75', min=0, max=100, step=5, label_range=True), + LabelRange('Multiple Values', value='25,75', min=0, max=100, step=5, label_range=True), + LabelRange('Custom Range', value='500', min=0, max=1000, step=100, label_range=True) + ) + +[/code] + +### Switch + +Source + +[code] + + Switch(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Switch with default styling + +**Params** + + * `c` contents of Switch tag (often nothing) + + * `cls` Classes in addition to Switch styling + + * `kwargs` + +**Returns:** Input(..., cls='uk-toggle-switch uk-toggle-switch-primary min-w-9', type='checkbox') + +See Source + +See Output + +Switch + +[code] + + def ex_switch(): + return Div( + Switch(id="switch"), + LabelSwitch(label="Switch", id='switch')) + +[/code] + +### TextArea + +Source + +[code] + + TextArea(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Textarea with default styling + +**Params** + + * `c` contents of TextArea tag (often text) + + * `cls` Classes in addition to TextArea styling + + * `kwargs` + +**Returns:** TextArea(..., cls='uk-textarea') + +See Source + +See Output + +TextArea + +[code] + + def ex_textarea(): + return Div( + TextArea(placeholder="Enter multiple lines of text"), + LabelTextArea(label="TextArea", id='myid')) + +[/code] + +### Select + +Source + +[code] + + Select(*option, inp_cls=(), cls=('h-10',), cls_custom='button: uk-input-fake dropdown: w-full', id='', name='', placeholder='', searchable=False, insertable=False, select_kwargs=None, **kwargs) +[/code] + +> Creates a select dropdown with uk styling and option for adding a search box + +**Params** + + * `option` Options for the select dropdown (can use `Options` helper function to create) + + * `inp_cls` Additional classes for the select input + + * `cls` Classes for the outer div (default h-10 for consistent height) + + * `cls_custom` Classes for the Uk_Select web component + + * `id` ID for the select input + + * `name` Name attribute for the select input + + * `placeholder` Placeholder text for the select input + + * `searchable` Whether the select should be searchable + + * `insertable` Whether to allow user-defined options to be added + + * `select_kwargs` Additional Arguments passed to Select + + * `kwargs` + +See Source + +See Output + +Option 1Option 2Option 3 + +Select + +Option 1Option 2Option 3 + +[code] + + def ex_select(): + return Div( + Select(map(Option, ["Option 1", "Option 2", "Option 3"])), + LabelSelect(map(Option, ["Option 1", "Option 2", "Option 3"]), label="Select", id='myid')) + +[/code] + +### Example: Insertable Select + +In a production app, the user-inserted option would be saved server-side (db, session etc.) + +See Source + +See Output + +AppleOrangeBananaMango + +AppleBananaMangoOrange + +[code] + + def ex_insertable_select1(): + fruit_opts = ['apple', 'orange', 'banana', 'mango'] + + return Grid( + Select(Option('Apple', value='apple'), + Option('Orange', value='orange'), + Option('Banana', value='banana'), + Option('Mango', value='mango'), + id="fruit", icon=True, insertable=True, placeholder="Choose a fruit..."), + + Select(Optgroup(label="Fruit")( + *map(lambda l: Option(l.capitalize(), value=l), sorted(fruit_opts))), + id="fruit", icon=True, insertable=True, placeholder="Choose a fruit...", + cls_custom="button: uk-input-fake justify-between w-full; dropdown: w-full")) + +[/code] + +### Legend + +Source + +[code] + + Legend(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A Legend with default styling + +**Params** + + * `c` contents of Legend tag (often other tags) + + * `cls` Classes in addition to Legend styling + + * `kwargs` + +**Returns:** Legend(..., cls='uk-legend') + +### Fieldset + +Source + +[code] + + Fieldset(*c, cls='flex', **kwargs) -> fastcore.xml.FT +[/code] + +> A Fieldset with default styling + +**Params** + + * `c` contents of Fieldset tag (often other tags) + + * `cls` Classes in addition to Fieldset styling + + * `kwargs` + +**Returns:** Fieldset(..., cls='uk-fieldset') + diff --git a/docs/vendor/monsterui/api_ref/docs_html.md b/docs/vendor/monsterui/api_ref/docs_html.md new file mode 100644 index 0000000..135102c --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_html.md @@ -0,0 +1,15 @@ +# HTML Styling API Reference + +See Source + +See Output + +

Hello, World!

This is a paragraph

+ +[code] + + def ex_applyclasses(): + return apply_classes('

Hello, World!

This is a paragraph

') + +[/code] + diff --git a/docs/vendor/monsterui/api_ref/docs_icons_images.md b/docs/vendor/monsterui/api_ref/docs_icons_images.md new file mode 100644 index 0000000..d38ebf7 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_icons_images.md @@ -0,0 +1,166 @@ +# Icons & Images API Reference + +# Avatars + +See Source + +See Output + +[code] + + def ex_dicebear(): + return DivLAligned( + DiceBearAvatar('Isaac Flath',10,10), + DiceBearAvatar('Aaliyah',10,10), + DiceBearAvatar('Alyssa',10,10)) + +[/code] + +### DiceBearAvatar + +Source + +[code] + + DiceBearAvatar(seed_name: str, h: int = 20, w: int = 20) +[/code] + +> Creates an Avatar using https://dicebear.com/ + +**Params** + + * `seed_name` Seed name (ie 'Isaac Flath') + + * `h` Height + + * `w` Width + +# PlaceHolder Images + +See Source + +See Output + +[code] + + def ex_picsum(): + return Grid(PicSumImg(100,100), PicSumImg(100,100, blur=6),PicSumImg(100,100, grayscale=True)) + +[/code] + +### PicSumImg + +Source + +[code] + + PicSumImg(h: int = 200, w: int = 200, id: int = None, grayscale: bool = False, blur: int = None, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a placeholder image using https://picsum.photos/ + +**Params** + + * `h` Height in pixels + + * `w` Width in pixels + + * `id` Optional specific image ID to use + + * `grayscale` Whether to return grayscale version + + * `blur` Optional blur amount (1-10) + + * `kwargs` + +**Returns:** Img tag with picsum image + +# Icons + +Icons use Lucide icons - you can find a full list of icons in their docs. + +See Source + +See Output + +[code] + + def ex_icon(): + return Grid( + UkIcon('chevrons-right', height=15, width=15), + UkIcon('bug', height=15, width=15), + UkIcon('phone-call', height=15, width=15), + UkIcon('maximize-2', height=15, width=15), + UkIcon('thumbs-up', height=15, width=15),) + +[/code] + +### UkIcon + +Source + +[code] + + UkIcon(icon: str, height: int = None, width: int = None, stroke_width: int = None, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates an icon using lucide icons + +**Params** + + * `icon` Icon name from lucide icons + + * `height` + + * `width` + + * `stroke_width` Thickness of lines + + * `cls` Additional classes on the `Uk_icon` tag + + * `kwargs` + +**Returns:** a lucide icon of the specified size + +See Source + +See Output + +[code] + + def ex_iconlink(): + return DivLAligned( + UkIconLink('chevrons-right'), + UkIconLink('chevrons-right', button=True, cls=ButtonT.primary)) + +[/code] + +### UkIconLink + +Source + +[code] + + UkIconLink(icon: str, height: int = None, width: int = None, stroke_width: int = None, cls=(), button: bool = False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates an icon link using lucide icons + +**Params** + + * `icon` Icon name from lucide icons + + * `height` + + * `width` + + * `stroke_width` Thickness of lines + + * `cls` Additional classes on the icon + + * `button` Whether to use a button (defaults to a link) + + * `kwargs` + +**Returns:** a lucide icon button or link of the specified size + diff --git a/docs/vendor/monsterui/api_ref/docs_layout.md b/docs/vendor/monsterui/api_ref/docs_layout.md new file mode 100644 index 0000000..7858926 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_layout.md @@ -0,0 +1,417 @@ +# Layout (Flex and Grid) API Reference + +This page covers `Grid`s, which are often used for general structure, `Flex` which is often used for layout of components that are not grid based, padding and positioning that can help you make your layout look good, and dividers that can help break up the page + +## Grid + +See Source + +See Output + +Column 1 Item 1 + +Column 1 Item 2 + +Column 1 Item 3 + +Column 2 Item 1 + +Column 2 Item 2 + +Column 2 Item 3 + +Column 3 Item 1 + +Column 3 Item 2 + +Column 3 Item 3 + +[code] + + def ex_grid(): + return Grid( + Div( + P("Column 1 Item 1"), + P("Column 1 Item 2"), + P("Column 1 Item 3")), + Div( + P("Column 2 Item 1"), + P("Column 2 Item 2"), + P("Column 2 Item 3")), + Div( + P("Column 3 Item 1"), + P("Column 3 Item 2"), + P("Column 3 Item 3"))) + +[/code] + +### Grid + +Source + +[code] + + Grid(*div, cols_min: int = 1, cols_max: int = 4, cols_sm: int = None, cols_md: int = None, cols_lg: int = None, cols_xl: int = None, cols: int = None, cls='gap-4', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a responsive grid layout with smart defaults based on content + +**Params** + + * `div` `Div` components to put in the grid + + * `cols_min` Minimum number of columns at any screen size + + * `cols_max` Maximum number of columns allowed at any screen size + + * `cols_sm` Number of columns on small screens + + * `cols_md` Number of columns on medium screens + + * `cols_lg` Number of columns on large screens + + * `cols_xl` Number of columns on extra large screens + + * `cols` Number of columns on all screens + + * `cls` Additional classes on the grid (tip: `gap` provides spacing for grids) + + * `kwargs` + +**Returns:** Responsive grid component + +#### Practical Grid Example + +See Source + +See Output + +#### Laptop + +$999 + +Add to Cart + +#### Smartphone + +$599 + +Add to Cart + +#### Headphones + +$199 + +Add to Cart + +#### Smartwatch + +$299 + +Add to Cart + +#### Tablet + +$449 + +Add to Cart + +#### Camera + +$799 + +Add to Cart + +[code] + + def ex_product_grid(): + products = [ + {"name": "Laptop", "price": "$999", "img": "https://picsum.photos/200/100?random=1"}, + {"name": "Smartphone", "price": "$599", "img": "https://picsum.photos/200/100?random=2"}, + {"name": "Headphones", "price": "$199", "img": "https://picsum.photos/200/100?random=3"}, + {"name": "Smartwatch", "price": "$299", "img": "https://picsum.photos/200/100?random=4"}, + {"name": "Tablet", "price": "$449", "img": "https://picsum.photos/200/100?random=5"}, + {"name": "Camera", "price": "$799", "img": "https://picsum.photos/200/100?random=6"}, + ] + + product_cards = [ + Card( + Img(src=p["img"], alt=p["name"], style="width:100%; height:100px; object-fit:cover;"), + H4(p["name"], cls="mt-2"), + P(p["price"], cls=TextPresets.bold_sm), + Button("Add to Cart", cls=(ButtonT.primary, "mt-2")) + ) for p in products + ] + + return Grid(*product_cards, cols_lg=3) + +[/code] + +## Flex + +Play Flex Box Froggy to get an understanding of flex box. + +### DivFullySpaced + +Source + +[code] + + DivFullySpaced(*c, cls='w-full', **kwargs) +[/code] + +> Creates a flex div with it's components having as much space between them as possible + +**Params** + + * `c` Components + + * `cls` Classes for outer div (`w-full` makes it use all available width) + + * `kwargs` + +See Source + +See Output + +LeftCenterRight + +[code] + + def ex_fully_spaced_div(): + return DivFullySpaced( + Button("Left", cls=ButtonT.primary), + Button("Center", cls=ButtonT.secondary), + Button("Right", cls=ButtonT.destructive) + ) + +[/code] + +### DivCentered + +Source + +[code] + + DivCentered(*c, cls='space-y-4', vstack=True, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a flex div with it's components centered in it + +**Params** + + * `c` Components + + * `cls` Classes for outer div (`space-y-4` provides spacing between components) + + * `vstack` Whether to stack the components vertically + + * `kwargs` + +**Returns:** Div with components centered in it + +See Source + +See Output + +### Centered Title + +This content is centered both horizontally and vertically. + +[code] + + def ex_centered_div(): + return DivCentered( + H3("Centered Title"), + P("This content is centered both horizontally and vertically.") + ) + +[/code] + +### DivLAligned + +Source + +[code] + + DivLAligned(*c, cls='space-x-4', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a flex div with it's components aligned to the left + +**Params** + + * `c` Components + + * `cls` Classes for outer div + + * `kwargs` + +**Returns:** Div with components aligned to the left + +See Source + +See Output + +#### Left Aligned Title + +Some text that's left-aligned with the title and image. + +[code] + + def ex_l_aligned_div(): + return DivLAligned( + Img(src="https://picsum.photos/100/100?random=1", style="max-width: 100px;"), + H4("Left Aligned Title"), + P("Some text that's left-aligned with the title and image.") + ) + +[/code] + +### DivRAligned + +Source + +[code] + + DivRAligned(*c, cls='space-x-4', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a flex div with it's components aligned to the right + +**Params** + + * `c` Components + + * `cls` Classes for outer div + + * `kwargs` + +**Returns:** Div with components aligned to the right + +See Source + +See Output + +Action + +Right-aligned text + +[code] + + def ex_r_aligned_div(): + return DivRAligned( + Button("Action", cls=ButtonT.primary), + P("Right-aligned text"), + Img(src="https://picsum.photos/100/100?random=3", style="max-width: 100px;") + ) + +[/code] + +### DivVStacked + +Source + +[code] + + DivVStacked(*c, cls='space-y-4', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a flex div with it's components stacked vertically + +**Params** + + * `c` Components + + * `cls` Additional classes on the div (tip: `space-y-4` provides spacing between components) + + * `kwargs` + +**Returns:** Div with components stacked vertically + +See Source + +See Output + +## Vertical Stack + +First paragraph in the stack + +Second paragraph in the stack + +Action Button + +[code] + + def ex_v_stacked_div(): + return DivVStacked( + H2("Vertical Stack"), + P("First paragraph in the stack"), + P("Second paragraph in the stack"), + Button("Action Button", cls=ButtonT.secondary) + ) + +[/code] + +### DivHStacked + +Source + +[code] + + DivHStacked(*c, cls='space-x-4', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a flex div with it's components stacked horizontally + +**Params** + + * `c` Components + + * `cls` Additional classes on the div (`space-x-4` provides spacing between components) + + * `kwargs` + +**Returns:** Div with components stacked horizontally + +See Source + +See Output + +#### Column 1 + +Content for column 1 + +#### Column 2 + +Content for column 2 + +#### Column 3 + +Content for column 3 + +[code] + + def ex_h_stacked_div(): + return DivHStacked( + Div(H4("Column 1"), P("Content for column 1")), + Div(H4("Column 2"), P("Content for column 2")), + Div(H4("Column 3"), P("Content for column 3")) + ) + +[/code] + +* * * + +### FlexT + +_Flexbox modifiers using Tailwind CSS_ + +Option | Value | Option | Value | Option | Value | Option | Value +---|---|---|---|---|---|---|--- +block | flex | inline | inline-flex | left | justify-start | center | justify-center +right | justify-end | between | justify-between | around | justify-around | stretch | items-stretch +top | items-start | middle | items-center | bottom | items-end | row | flex-row +row_reverse | flex-row-reverse | column | flex-col | column_reverse | flex-col-reverse | nowrap | flex-nowrap +wrap | flex-wrap | wrap_reverse | flex-wrap-reverse | | | | + diff --git a/docs/vendor/monsterui/api_ref/docs_lightbox.md b/docs/vendor/monsterui/api_ref/docs_lightbox.md new file mode 100644 index 0000000..5f5cffb --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_lightbox.md @@ -0,0 +1,99 @@ +# Lightbox API Reference + +See Source + +See Output + +Open + +[code] + + def ex_lightbox1(): + return LightboxContainer( + LightboxItem(Button("Open"), href='https://picsum.photos/id/100/1280/720.webp', data_alt='A placeholder image to demonstrate the lightbox', data_caption='This is my super cool caption'), + ) + +[/code] + +See Source + +See Output + +Open + +[code] + + def ex_lightbox2(): + return LightboxContainer( + LightboxItem(Button("Open"), href='https://picsum.photos/id/100/1280/720.webp', data_alt='A placeholder image to demonstrate the lightbox', data_caption='Image 1'), + LightboxItem(href='https://picsum.photos/id/101/1280/720.webp', data_alt='A placeholder image to demonstrate the lightbox', data_caption='Image 2'), + LightboxItem(href='https://picsum.photos/id/102/1280/720.webp', data_alt='A placeholder image to demonstrate the lightbox', data_caption='Image 3'), + ) + +[/code] + +See Source + +See Output + +mp4YoutubeVimeoIframe + +[code] + + def ex_lightbox3(): + return LightboxContainer( + LightboxItem(Button("mp4"), href='https://yootheme.com/site/images/media/yootheme-pro.mp4'), + LightboxItem(Button("Youtube"), href='https://www.youtube.com/watch?v=c2pz2mlSfXA'), + LightboxItem(Button("Vimeo"), href='https://vimeo.com/1084537'), + LightboxItem(Button("Iframe"), data_type='iframe', href='https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d4740.819266853735!2d9.99008871708242!3d53.550454675412404!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x3f9d24afe84a0263!2sRathaus!5e0!3m2!1sde!2sde!4v1499675200938')) + +[/code] + +### LightboxContainer + +Source + +[code] + + LightboxContainer(*lightboxitem, data_uk_lightbox='counter: true', **kwargs) -> fastcore.xml.FT +[/code] + +> Lightbox container that will hold `LightboxItems` + +**Params** + + * `lightboxitem` `LightBoxItem`s that will be inside lightbox + + * `data_uk_lightbox` See https://franken-ui.dev/docs/2.0/lightbox for advanced options + + * `kwargs` + +**Returns:** Lightbox + +### LightboxItem + +Source + +[code] + + LightboxItem(*c, href, data_alt=None, data_caption=None, cls='', **kwargs) -> fastcore.xml.FT +[/code] + +> Anchor tag with appropriate structure to go inside a `LightBoxContainer` + +**Params** + + * `c` Component that when clicked will open the lightbox (often a button) + + * `href` Href to image, youtube video, vimeo, google maps, etc. + + * `data_alt` Alt text for the lightbox item/image + + * `data_caption` Caption for the item that shows below it + + * `cls` Class for the A tag (often nothing or `uk-btn`) + + * `kwargs` + +**Returns:** A(... href, data_alt, cls., ...) + diff --git a/docs/vendor/monsterui/api_ref/docs_lists.md b/docs/vendor/monsterui/api_ref/docs_lists.md new file mode 100644 index 0000000..c830c23 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_lists.md @@ -0,0 +1,67 @@ +# Lists API Reference + +See Source + +See Output + +#### disc List: + + * Item 1 + * Item 2 + +#### circle List: + + * Item 1 + * Item 2 + +#### square List: + + * Item 1 + * Item 2 + +#### decimal List: + + * Item 1 + * Item 2 + +#### hyphen List: + + * Item 1 + * Item 2 + +#### bullet List: + + * Item 1 + * Item 2 + +#### divider List: + + * Item 1 + * Item 2 + +#### striped List: + + * Item 1 + * Item 2 + +[code] + + def ex_lists(): + list_options = [(style,str(cls)) for style,cls in ListT.__members__.items()] + lists = [Div(H4(f"{style} List:"), Ul(Li("Item 1"), Li("Item 2"), cls=cls)) for style, cls in list_options] + return Grid(*lists) + +[/code] + +* * * + +### ListT + +_List styles using Tailwind CSS_ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +disc | list-disc list-inside | circle | list-[circle] list-inside | square | list-[square] list-inside +decimal | uk-list uk-list-decimal | hyphen | uk-list uk-list-hyphen | bullet | uk-list uk-list-bullet +divider | uk-list uk-list-divider | striped | uk-list uk-list-striped | | + diff --git a/docs/vendor/monsterui/api_ref/docs_loading.md b/docs/vendor/monsterui/api_ref/docs_loading.md new file mode 100644 index 0000000..2992215 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_loading.md @@ -0,0 +1,61 @@ +# Loading Indicators API Reference + +See Source + +See Output + +[code] + + def ex_loading1(): + return Loading() + +[/code] + +See Source + +See Output + +[code] + + def ex_loading2(): + types = [LoadingT.spinner, LoadingT.dots, LoadingT.ring, LoadingT.ball, LoadingT.bars, LoadingT.infinity] + sizes = [LoadingT.xs, LoadingT.sm, LoadingT.md, LoadingT.lg] + rows = [Div(*[Loading((t,s)) for s in sizes], cls='flex gap-4') for t in types] + return Div(*rows, cls='flex flex-col gap-4') + +[/code] + +### Loading + +Source + +[code] + + Loading(cls=(, ), htmx_indicator=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a loading animation component + +**Params** + + * `cls` Classes for indicator (generally `LoadingT` options) + + * `htmx_indicator` Add htmx-indicator class + + * `kwargs` + +**Returns:** Span(cls=...) + +* * * + +### LoadingT + +__ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +spinner | loading-spinner | dots | loading-dots | ring | loading-ring +ball | loading-ball | bars | loading-bars | infinity | loading-infinity +xs | loading-xsmall | sm | loading-small | md | loading-medium +lg | loading-large | | | | + diff --git a/docs/vendor/monsterui/api_ref/docs_markdown.md b/docs/vendor/monsterui/api_ref/docs_markdown.md new file mode 100644 index 0000000..067765b --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_markdown.md @@ -0,0 +1,146 @@ +# Markdown + automated HTML styling API Reference + +See Source + +See Output + +# Example Markdown + + * With **bold** and _italics_ + * With a link + +### And a subheading + +> This is a blockquote + +This supports inline latex: $e^{\pi i} + 1 = 0$ as well as block latex thanks to Katex. + +$$ \frac{1}{2\pi i} \oint_C \frac{f(z)}{z-z_0} dz $$ + +And even syntax highlighting thanks to Highlight.js! (Just make sure you set `highlightjs=True` in the headers function) + +[code] + + def add(a, b): + return a + b + +[/code] + +[code] + + def ex_markdown(): + md = '''# Example Markdown + + + With **bold** and *italics* + + With a [link](https://github.com) + + ### And a subheading + + > This is a blockquote + + This supports inline latex: $e^{\\pi i} + 1 = 0$ as well as block latex thanks to Katex. + + $$ + \\frac{1}{2\\pi i} \\oint_C \\frac{f(z)}{z-z_0} dz + $$ + + And even syntax highlighting thanks to Highlight.js! (Just make sure you set `highlightjs=True` in the headers function) + + ```python + def add(a, b): + return a + b + ``` + ''' + return render_md(md) + +[/code] + +You can overwrite the default styling for markdown rendering with your own css classes with `class_map + +See Source + +See Output + +With custom **bold** style + +> But no extra quote style because class_map overrides all default styled +[code] + + def ex_markdown2(): + md = '''With custom **bold** style\n\n > But no extra quote style because class_map overrides all default styled''' + return render_md(md, class_map={'b': 'text-red-500'}) + +[/code] + +You can modify the default styling for markdown rendering with your own css classes with `class_map_mods + +See Source + +See Output + +With custom **bold** style + +> But default quote style because class_map_mods replaces sepecified styles and leaves the rest as default +[code] + + def ex_markdown3(): + md = '''With custom **bold** style\n\n > But default quote style because class_map_mods replaces sepecified styles and leaves the rest as default''' + return render_md(md, class_map_mods={'b': 'text-red-500'}) + +[/code] + +This uses the `apply_classes` function, which can be used to apply classes to html strings. This is useful for applying styles to any html you get from an external source. + +See Source + +See Output + +

Hello, World!

This is a paragraph

+ +[code] + + def ex_applyclasses(): + return apply_classes('

Hello, World!

This is a paragraph

') + +[/code] + +One common external source is a markdown renderer. MonsterUI uses tailwind css for styling so you don't get any styling without specifying classes, `apply_classes` can do that for you. + +See Source + +See Output + +# Hi + +a link + +[code] + + def ex_applyclasses2(): + from mistletoe import markdown, HTMLRenderer + md = markdown('# Hi\n[a link](www.google.com)', renderer=HTMLRenderer) + return Safe(apply_classes(md)) + +[/code] + +### apply_classes + +Source + +[code] + + apply_classes(html_str: str, class_map=None, class_map_mods=None) -> str +[/code] + +> Apply classes to html string + +**Params** + + * `html_str` Html string + + * `class_map` Class map + + * `class_map_mods` Class map that will modify the class map map (for small changes to base map) + +**Returns:** Html string with classes applied + diff --git a/docs/vendor/monsterui/api_ref/docs_modals.md b/docs/vendor/monsterui/api_ref/docs_modals.md new file mode 100644 index 0000000..d11bda7 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_modals.md @@ -0,0 +1,215 @@ +# Modals API Reference + +### Example Modal + +See Source + +See Output + +Open Modal + +## Simple Test Modal + +With some somewhat brief content to show that it works! + +Close + +[code] + + def ex_modal(): + return Div( + Button("Open Modal",data_uk_toggle="target: #my-modal" ), + Modal(ModalTitle("Simple Test Modal"), + P("With some somewhat brief content to show that it works!", cls=TextPresets.muted_sm), + footer=ModalCloseButton("Close", cls=ButtonT.primary),id='my-modal')) + +[/code] + +### Modal + +Source + +[code] + + Modal(*c, header=None, footer=None, cls=(), dialog_cls=(), header_cls='p-6', body_cls='space-y-6', footer_cls=(), id='', open=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you + +**Params** + + * `c` Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.) + + * `header` Components that go in the `ModalHeader` (often a `ModalTitle`) + + * `footer` Components that go in the `ModalFooter` (often a `ModalCloseButton`) + + * `cls` Additional classes on the outermost `ModalContainer` + + * `dialog_cls` Additional classes on the `ModalDialog` + + * `header_cls` Additional classes on the `ModalHeader` + + * `body_cls` Additional classes on the `ModalBody` + + * `footer_cls` Additional classes on the `ModalFooter` + + * `id` id for the outermost container + + * `open` Whether the modal is open (typically used for HTMX controlled modals) + + * `kwargs` + +**Returns:** Fully styled modal FT Component + +### ModalCloseButton + +Source + +[code] + + ModalCloseButton(*c, cls='absolute top-3 right-3', htmx=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a button that closes a modal with js + +**Params** + + * `c` Components to put in the button (often text and/or an icon) + + * `cls` Additional classes on the button + + * `htmx` Whether to use HTMX to close the modal (must add hx_get to a route that closes the modal) + + * `kwargs` + +**Returns:** Button(..., cls='uk-modal-close') + `hx_target` and `hx_swap` if htmx is True + +The remainder of the Modal functions below are used internally by the `Modal` function for you. You shouldn't need to use them unless you're doing something really special. + +### ModalTitle + +Source + +[code] + + ModalTitle(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal title + +**Params** + + * `c` Components to put in the `ModalTitle` (often text) + + * `cls` Additional classes on the `ModalTitle` + + * `kwargs` + +**Returns:** H2(..., cls='uk-modal-title') + +### ModalFooter + +Source + +[code] + + ModalFooter(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal footer + +**Params** + + * `c` Components to put in the `ModalFooter` (often buttons) + + * `cls` Additional classes on the `ModalFooter` + + * `kwargs` + +**Returns:** Div(..., cls='uk-modal-footer') + +### ModalBody + +Source + +[code] + + ModalBody(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal body + +**Params** + + * `c` Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.) + + * `cls` Additional classes on the `ModalBody` + + * `kwargs` + +**Returns:** Div(..., cls='uk-modal-body') + +### ModalHeader + +Source + +[code] + + ModalHeader(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal header + +**Params** + + * `c` Components to put in the `ModalHeader` + + * `cls` Additional classes on the `ModalHeader` + + * `kwargs` + +**Returns:** Div(..., cls='uk-modal-header') + +### ModalDialog + +Source + +[code] + + ModalDialog(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal dialog + +**Params** + + * `c` Components to put in the `ModalDialog` (often `ModalBody`, `ModalHeader`, etc) + + * `cls` Additional classes on the `ModalDialog` + + * `kwargs` + +**Returns:** Div(..., cls='uk-modal-dialog') + +### ModalContainer + +Source + +[code] + + ModalContainer(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a modal container that components go in + +**Params** + + * `c` Components to put in the modal (often `ModalDialog`) + + * `cls` Additional classes on the `ModalContainer` + + * `kwargs` + +**Returns:** Div(..., cls='uk-modal uk-modal-container') + diff --git a/docs/vendor/monsterui/api_ref/docs_navigation.md b/docs/vendor/monsterui/api_ref/docs_navigation.md new file mode 100644 index 0000000..ce9e45b --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_navigation.md @@ -0,0 +1,412 @@ +# Navigation (Nav, NavBar, Tabs, etc.) API Reference + +# Nav, NavBar, DowDownNav, and Tab examples + +* * * + +## Nav + +See Source + +See Output + + * Option 1 + * Option 2 + * Option 3 + +[code] + + def ex_nav1(): + mbrs1 = [Li(A('Option 1'), cls='uk-active'), Li(A('Option 2')), Li(A('Option 3'))] + return NavContainer(*mbrs1) + +[/code] + +See Source + +See Output + + * NavHeaderLi + * Option 1 + * Option 2 + * Option 3 + * Subtitle Ex + +NavSubtitle text to be shown + + * * Parent Name + * Child 1 + * Child 2 + * Child 3 + +[code] + + def ex_nav2(): + mbrs1 = [Li(A('Option 1'), cls='uk-active'), Li(A('Option 2')), Li(A('Option 3'))] + mbrs2 = [Li(A('Child 1')), Li(A('Child 2')),Li(A('Child 3'))] + + return NavContainer( + NavHeaderLi("NavHeaderLi"), + *mbrs1, + Li(A(href='')(Div("Subtitle Ex",NavSubtitle("NavSubtitle text to be shown")))), + NavDividerLi(), + NavParentLi( + A('Parent Name'), + NavContainer(*mbrs2,parent=False), + ), + ) + +[/code] + +## Navbars + +Fully responsive simple navbar using the high level API. This will collapse to a hamburger menu on mobile devices. See the Scrollspy example for a more complex navbar example. + +See Source + +See Output + +### My Blog + +Page1Page2Page3 + +Page1Page2Page3 + +[code] + + def ex_navbar1(): + return NavBar(A("Page1",href='/rt1'), + A("Page2",href='/rt2'), + A("Page3",href='/rt3'), + brand=H3('My Blog')) + +[/code] + +See Source + +See Output + +Page1Page2 + +Page1Page2 + +[code] + + def ex_navbar2(): + return NavBar( + A(Input(placeholder='search')), + A(UkIcon("rocket")), + A('Page1',href='/rt1'), + A("Page2", href='/rt3'), + brand=DivLAligned(Img(src='/api_reference/logo.svg'),UkIcon('rocket',height=30,width=30))) + +[/code] + +## Drop Down Navs + +See Source + +See Output + +Open DropDown + + * Item 1 + * Item 2 + +[code] + + def ex_navdrop(): + return Div( + Button("Open DropDown"), + DropDownNavContainer(Li(A("Item 1",href=''),Li(A("Item 2",href=''))))) + +[/code] + +## Tabs + +See Source + +See Output + + * Active + * Item + * Item + * Disabled + +[code] + + def ex_tabs2(): + return Container( + TabContainer( + Li(A("Active",href='javascript:void(0);', cls='uk-active')), + Li(A("Item",href='javascript:void(0);')), + Li(A("Item",href='javascript:void(0);')), + Li(A("Disabled", cls='uk-disabled')))) + +[/code] + +A tabs can use any method of navigation (htmx, or href). However, often these are use in conjunction with switchers do to this client side + +See Source + +See Output + + * Active + * Item + * Item + * Disabled + + * # Tab 1 + + * # Tab 2 + + * # Tab 3 + +[code] + + def ex_tabs1(): + return Container( + TabContainer( + Li(A("Active",href='#', cls='uk-active')), + Li(A("Item",href='#')), + Li(A("Item",href='#')), + Li(A("Disabled",href='#', cls='uk-disabled')), + uk_switcher='connect: #component-nav; animation: uk-animation-fade', + alt=True), + Ul(id="component-nav", cls="uk-switcher")( + Li(H1("Tab 1")), + Li(H1("Tab 2")), + Li(H1("Tab 3")))) + +[/code] + +# API Docs + +### NavBar + +Source + +[code] + + NavBar(*c, brand=h3(('Title',),{'class': 'uk-h3 '}), right_cls='items-center space-x-4', mobile_cls='space-y-4', sticky: bool = False, uk_scrollspy_nav: bool | str = False, cls='p-4', scrollspy_cls=, menu_id=None) -> fastcore.xml.FT +[/code] + +> Creates a responsive navigation bar with mobile menu support + +**Params** + + * `c` Component for right side of navbar (Often A tag links) + + * `brand` Brand/logo component for left side + + * `right_cls` Spacing for desktop links + + * `mobile_cls` Spacing for mobile links + + * `sticky` Whether to stick to the top of the page while scrolling + + * `uk_scrollspy_nav` Whether to use scrollspy for navigation + + * `cls` Classes for navbar + + * `scrollspy_cls` Scrollspy class (usually ScrollspyT.*) + + * `menu_id` ID for menu container (used for mobile toggle) + +**Returns:** Responsive NavBar + +### TabContainer + +Source + +[code] + + TabContainer(*li, cls='', alt=False, **kwargs) -> fastcore.xml.FT +[/code] + +> A TabContainer where children will be different tabs + +**Params** + + * `li` Components + + * `cls` Additional classes on the `Ul` + + * `alt` Whether to use an alternative tab style + + * `kwargs` + +**Returns:** Tab container + +### NavContainer + +Source + +[code] + + NavContainer(*li, cls=, parent=True, uk_nav=False, uk_scrollspy_nav=False, sticky=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation container (useful for creating a sidebar navigation). A Nav is a list (NavBar is something different) + +**Params** + + * `li` List items are navigation elements (Special `Li` such as `NavParentLi`, `NavDividerLi`, `NavHeaderLi`, `NavSubtitle`, `NavCloseLi` can also be used) + + * `cls` Additional classes on the nav + + * `parent` Whether this nav is a _parent_ or _sub_ nav + + * `uk_nav` True for default collapsible behavior, see frankenui docs for more advanced options + + * `uk_scrollspy_nav` Activates scrollspy linking each item `A` tags `href` to content's `id` attribute + + * `sticky` Whether to stick to the top of the page while scrolling + + * `kwargs` + +**Returns:** FT Component that is a list of `Li` styled for a sidebar navigation menu + +* * * + +### NavT + +__ + +Option | Value | Option | Value +---|---|---|--- +default | uk-nav-default | primary | uk-nav-primary +secondary | uk-nav-secondary | | + +### NavCloseLi + +Source + +[code] + + NavCloseLi(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation list item with a close button + +**Params** + + * `c` Components + + * `cls` Additional classes on the li + + * `kwargs` + +**Returns:** Navigation list item with a close button + +### NavSubtitle + +Source + +[code] + + NavSubtitle(*c, cls=, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation subtitle + +**Params** + + * `c` Components + + * `cls` Additional classes on the div + + * `kwargs` + +**Returns:** Navigation subtitle + +### NavHeaderLi + +Source + +[code] + + NavHeaderLi(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation list item with a header + +**Params** + + * `c` Components + + * `cls` Additional classes on the li + + * `kwargs` + +**Returns:** Navigation list item with a header + +### NavDividerLi + +Source + +[code] + + NavDividerLi(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation list item with a divider + +**Params** + + * `c` Components + + * `cls` Additional classes on the li + + * `kwargs` + +**Returns:** Navigation list item with a divider + +### NavParentLi + +Source + +[code] + + NavParentLi(*nav_container, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a navigation list item with a parent nav for nesting + +**Params** + + * `nav_container` `NavContainer` container for a nested nav with `parent=False`) + + * `cls` Additional classes on the li + + * `kwargs` + +**Returns:** Navigation list item + +### DropDownNavContainer + +Source + +[code] + + DropDownNavContainer(*li, cls=, parent=True, uk_nav=False, uk_dropdown=True, **kwargs) -> fastcore.xml.FT +[/code] + +> A Nav that is part of a DropDown + +**Params** + + * `li` Components + + * `cls` Additional classes on the nav + + * `parent` Whether to use a parent nav + + * `uk_nav` True for default collapsible behavior, see https://franken-ui.dev/docs/nav#component-options for more advanced options + + * `uk_dropdown` Whether to use a dropdown + + * `kwargs` + +**Returns:** DropDown nav container + diff --git a/docs/vendor/monsterui/api_ref/docs_notifications.md b/docs/vendor/monsterui/api_ref/docs_notifications.md new file mode 100644 index 0000000..44733b3 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_notifications.md @@ -0,0 +1,167 @@ +# Alerts & Toasts API Reference + +### Alerts + +The simplest alert is a div wrapped with a span: + +See Source + +See Output + +This is a plain alert + +[code] + + def ex_alerts1(): return Alert("This is a plain alert") + +[/code] + +Alert colors are defined by the alert styles: + +See Source + +See Output + +Your purchase has been confirmed! + +[code] + + def ex_alerts2(): return Alert("Your purchase has been confirmed!", cls=AlertT.success) + +[/code] + +It often looks nice to use icons in alerts: + +See Source + +See Output + +Please enter a valid email. + +[code] + + def ex_alerts3(): + return Alert( + DivLAligned(UkIcon('triangle-alert'), + P("Please enter a valid email.")), + cls=AlertT.error) + +[/code] + +### Alert + +Source + +[code] + + Alert(*c, cls='', **kwargs) -> fastcore.xml.FT +[/code] + +> Alert informs users about important events. + +**Params** + + * `c` Content for Alert (often text and/or icon) + + * `cls` Class for the alert (often an `AlertT` option) + + * `kwargs` + +**Returns:** Div(Span(...), cls='alert', role='alert') + +* * * + +### AlertT + +_Alert styles from DaisyUI_ + +Option | Value | Option | Value +---|---|---|--- +info | alert-info | success | alert-success +warning | alert-warning | error | alert-error + +* * * + +### Toasts + +To define a toast with a particular location, add horizontal or vertical toast type classes: + +See Source + +See Output + +First Example Toast + +[code] + + def ex_toasts1(): + return Toast("First Example Toast", cls=(ToastHT.start, ToastVT.bottom), dur=300) + +[/code] + +To define toast colors, set the class of the alert wrapped by the toast: + +See Source + +See Output + +Second Example Toast + +[code] + + def ex_toasts2(): + return Toast("Second Example Toast", alert_cls=AlertT.info, dur=300) + +[/code] + +Toasts will disappear automatically after 5 seconds. To change the duration of the toast set the `dur` param like this `Toast('Content', dur=10)`. + +Here's a demo app showing how to trigger a toast. + +### Toast + +Source + +[code] + + Toast(*c, cls='', alert_cls='', dur=5.0, **kwargs) -> fastcore.xml.FT +[/code] + +> Toasts are stacked announcements, positioned on the corner of page. + +**Params** + + * `c` Content for toast (often test) + + * `cls` Classes for toast (often `ToastHT` and `ToastVT` options) + + * `alert_cls` classes for altert (often `AlertT` options) + + * `dur` no. of seconds before the toast disappears + + * `kwargs` + +**Returns:** Div(Alert(...), cls='toast') + +* * * + +### ToastHT + +_Horizontal position for Toast_ + +Option | Value | Option | Value +---|---|---|--- +start | toast-start | center | toast-center +end | toast-end | | + +* * * + +### ToastVT + +_Vertical position for Toast_ + +Option | Value | Option | Value +---|---|---|--- +top | toast-top | middle | toast-middle +bottom | toast-bottom | | + diff --git a/docs/vendor/monsterui/api_ref/docs_sliders.md b/docs/vendor/monsterui/api_ref/docs_sliders.md new file mode 100644 index 0000000..51233b4 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_sliders.md @@ -0,0 +1,219 @@ +# Carousel Sliders API Reference + +Here is a simple example of a slider: + +See Source + +See Output + +[code] + + def ex_sliders_1(): + return Slider(*[Img(src=f'https://picsum.photos/200/200?random={i}') for i in range(10)]) + +[/code] + +Here is a slider with cards: + +See Source + +See Output + +### Card 0 + +Card 0 content + +### Card 1 + +Card 1 content + +### Card 2 + +Card 2 content + +### Card 3 + +Card 3 content + +### Card 4 + +Card 4 content + +### Card 5 + +Card 5 content + +### Card 6 + +Card 6 content + +### Card 7 + +Card 7 content + +### Card 8 + +Card 8 content + +### Card 9 + +Card 9 content + +[code] + + def ex_sliders_2(): + def _card(i): return Card(H3(f'Card {i}'), P(f'Card {i} content')) + return Slider(*[_card(i) for i in range(10)]) + +[/code] + +Here is a slider with cards and autoplay: + +See Source + +See Output + +### Card 0 + +Card 0 content + +### Card 1 + +Card 1 content + +### Card 2 + +Card 2 content + +### Card 3 + +Card 3 content + +### Card 4 + +Card 4 content + +### Card 5 + +Card 5 content + +### Card 6 + +Card 6 content + +### Card 7 + +Card 7 content + +### Card 8 + +Card 8 content + +### Card 9 + +Card 9 content + +[code] + + def ex_sliders_3(): + def _card(i): return Card(H3(f'Card {i}'), P(f'Card {i} content')) + return Slider(*[_card(i) for i in range(10)], items_cls='gap-10', uk_slider='autoplay: true; autoplay-interval: 1000') + +[/code] + +Typically you want to use the `Slider` component, but if you need more control you can use the `SliderContainer`, `SliderItems`, and `SliderNav` components. + +### Slider + +Source + +[code] + + Slider(*c, cls='', items_cls='gap-4', nav=True, nav_cls='', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a slider with optional navigation arrows + +**Params** + + * `c` Items to show in slider + + * `cls` Classes for slider container + + * `items_cls` Classes for items container + + * `nav` Whether to show navigation arrows + + * `nav_cls` Classes for navigation arrows + + * `kwargs` + +**Returns:** SliderContainer(SliderItems(..., cls='gap-4'), SliderNav?) + +### SliderContainer + +Source + +[code] + + SliderContainer(*c, cls='', uk_slider=True, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a slider container + +**Params** + + * `c` Components + + * `cls` Additional classes on the container + + * `uk_slider` See FrankenUI Slider docs for more options + + * `kwargs` + +**Returns:** Div(..., cls='relative', uk_slider=True, ...) + +### SliderItems + +Source + +[code] + + SliderItems(*c, cls='', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a slider items container + +**Params** + + * `c` Components + + * `cls` Additional classes for the items + + * `kwargs` + +**Returns:** Div(..., cls='uk-slider-items uk-grid', ...) + +### SliderNav + +Source + +[code] + + SliderNav(cls='uk-position-small uk-hidden-hover', prev_cls='absolute left-0 top-1/2 -translate-y-1/2', next_cls='absolute right-0 top-1/2 -translate-y-1/2', **kwargs) -> fastcore.xml.FT +[/code] + +> Navigation arrows for Slider component + +**Params** + + * `cls` Additional classes for the navigation + + * `prev_cls` Additional classes for the previous navigation + + * `next_cls` Additional classes for the next navigation + + * `kwargs` + +**Returns:** Left and right navigation arrows for Slider component + diff --git a/docs/vendor/monsterui/api_ref/docs_steps.md b/docs/vendor/monsterui/api_ref/docs_steps.md new file mode 100644 index 0000000..b580300 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_steps.md @@ -0,0 +1,112 @@ +# Steps API Reference + +See Source + +See Output + + * Account Created + * Profile Setup + * Verification + +[code] + + def ex_steps2(): + return Steps( + LiStep("Account Created", cls=StepT.primary), + LiStep("Profile Setup", cls=StepT.neutral), + LiStep("Verification", cls=StepT.neutral), + cls="w-full") + +[/code] + +See Source + +See Output + + * Project Planning + * Design Phase + * Development + * Testing + * Deployment + +[code] + + def ex_steps3(): + return Steps( + LiStep("Project Planning", cls=StepT.success, data_content="📝"), + LiStep("Design Phase", cls=StepT.success, data_content="💡"), + LiStep("Development", cls=StepT.primary, data_content="🛠️"), + LiStep("Testing", cls=StepT.neutral, data_content="🔎"), + LiStep("Deployment", cls=StepT.neutral, data_content="🚀"), + cls=(StepsT.vertical, "min-h-[400px]")) + +[/code] + +# API Docs + +### Steps + +Source + +[code] + + Steps(*li, cls='', **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a steps container + +**Params** + + * `li` Each `Li` represent a step (generally use `LiStep`) + + * `cls` class for Steps (generally a `StepsT` option) + + * `kwargs` + +**Returns:** Ul(..., cls='steps') + +* * * + +### StepsT + +_Options for Steps_ + +Option | Value | Option | Value +---|---|---|--- +vertical | steps-vertical | horizonal | steps-horizonal + +### LiStep + +Source + +[code] + + LiStep(*c, cls='', data_content=None, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a step list item + +**Params** + + * `c` Description for Step that goes next to bubble (often text) + + * `cls` Additional step classes (generally a `StepT` component) + + * `data_content` Content for inside bubble (defaults to number, often an emoji) + + * `kwargs` + +**Returns:** Li(..., cls='step') + +* * * + +### StepT + +_Step styles for LiStep_ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +primary | step-primary | secondary | step-secondary | accent | step-accent +info | step-info | success | step-success | warning | step-warning +error | step-error | neutral | step-neutral | | + diff --git a/docs/vendor/monsterui/api_ref/docs_tables.md b/docs/vendor/monsterui/api_ref/docs_tables.md new file mode 100644 index 0000000..e6a6a64 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_tables.md @@ -0,0 +1,237 @@ +# Tables API Reference + +See Source + +See Output + +Name | Age | City +---|---|--- +Alice | 25 | New York +Bob | 30 | San Francisco +Charlie | 35 | London +Total | 90 +[code] + + def ex_tables0(): + return Table( + Thead(Tr(Th('Name'), Th('Age'), Th('City'))), + Tbody(Tr(Td('Alice'), Td('25'), Td('New York')), + Tr(Td('Bob'), Td('30'), Td('San Francisco')), + Tr(Td('Charlie'), Td('35'), Td('London'))), + Tfoot(Tr(Td('Total'), Td('90')))) + +[/code] + +See Source + +See Output + +Name | Age | City +---|---|--- +Alice | 25 | New York +Bob | 30 | San Francisco +Charlie | 35 | London +Total | 90 +[code] + + def ex_tables1(): + header = ['Name', 'Age', 'City'] + body = [['Alice', '25', 'New York'], + ['Bob', '30', 'San Francisco'], + ['Charlie', '35', 'London']] + footer = ['Total', '90'] + return TableFromLists(header, body, footer) + +[/code] + +See Source + +See Output + +NAME | AGE | CITY +---|---|--- +Alice | 30 years | New York +Bob | 25 years | London +[code] + + def ex_tables2(): + def body_render(k, v): + match k.lower(): + case 'name': return Td(v, cls='font-bold') + case 'age': return Td(f"{v} years") + case _: return Td(v) + + header_data = ['Name', 'Age', 'City'] + body_data =[{'Name': 'Alice', 'Age': 30, 'City': 'New York'}, + {'Name': 'Bob', 'Age': 25, 'City': 'London'}] + + return TableFromDicts(header_data, body_data, + header_cell_render=lambda v: Th(v.upper()), + body_cell_render=body_render) + +[/code] + +### Table + +Source + +[code] + + Table(*c, cls=(, , , ), **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a table + +**Params** + + * `c` Components (typically `Thead`, `Tbody`, `Tfoot`) + + * `cls` Additional classes on the table + + * `kwargs` + +**Returns:** Table component + +### TableFromLists + +Source + +[code] + + TableFromLists(header_data: Sequence, body_data: Sequence[Sequence], footer_data=None, header_cell_render=, body_cell_render=, footer_cell_render=, cls=(, , , ), sortable=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a Table from a list of header data and a list of lists of body data + +**Params** + + * `header_data` List of header data + + * `body_data` List of lists of body data + + * `footer_data` List of footer data + + * `header_cell_render` Function(content) -> FT that renders header cells + + * `body_cell_render` Function(key, content) -> FT that renders body cells + + * `footer_cell_render` Function(key, content) -> FT that renders footer cells + + * `cls` Additional classes on the table + + * `sortable` Whether to use sortable table + + * `kwargs` + +**Returns:** Table from lists + +### TableFromDicts + +Source + +[code] + + TableFromDicts(header_data: Sequence, body_data: Sequence[dict], footer_data=None, header_cell_render=, body_cell_render= at 0x752645f85b20>, footer_cell_render= at 0x752645f85bc0>, cls=(, , , ), sortable=False, **kwargs) -> fastcore.xml.FT +[/code] + +> Creates a Table from a list of header data and a list of dicts of body data + +**Params** + + * `header_data` List of header data + + * `body_data` List of dicts of body data + + * `footer_data` List of footer data + + * `header_cell_render` Function(content) -> FT that renders header cells + + * `body_cell_render` Function(key, content) -> FT that renders body cells + + * `footer_cell_render` Function(key, content) -> FT that renders footer cells + + * `cls` Additional classes on the table + + * `sortable` Whether to use sortable table + + * `kwargs` + +**Returns:** Styled Table + +* * * + +### TableT + +__ + +Option | Value | Option | Value | Option | Value +---|---|---|---|---|--- +divider | uk-table-divider | striped | uk-table-striped | hover | uk-table-hover +sm | uk-table-sm | lg | uk-table-lg | justify | uk-table-justify +middle | uk-table-middle | responsive | uk-table-responsive | | + +### Tbody + +Source + +[code] + + Tbody(*rows, cls=(), sortable=False, **kwargs) +[/code] + +> **Params** + + * `rows` + + * `cls` + + * `sortable` + + * `kwargs` + +### Th + +Source + +[code] + + Th(*c, cls=(), shrink=False, expand=False, small=False) +[/code] + +> **Params** + + * `c` Components that go in the cell + + * `cls` Additional classes on the cell container + + * `shrink` Whether to shrink the cell + + * `expand` Whether to expand the cell + + * `small` Whether to use a small table + +**Returns:** Table cell + +### Td + +Source + +[code] + + Td(*c, cls=(), shrink=False, expand=False, small=False) +[/code] + +> **Params** + + * `c` Components that go in the cell + + * `cls` Additional classes on the cell container + + * `shrink` Whether to shrink the cell + + * `expand` Whether to expand the cell + + * `small` Whether to use a small table + +**Returns:** Table cell + diff --git a/docs/vendor/monsterui/api_ref/docs_theme_headers.md b/docs/vendor/monsterui/api_ref/docs_theme_headers.md new file mode 100644 index 0000000..3ba759d --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_theme_headers.md @@ -0,0 +1,248 @@ +# Theme and Headers API Reference + +To get headers with a default theme use `hdrs=Theme..headers()`. For example for the blue theme you would use `hdrs=Theme.blue.headers()`. The theme integrated together different frameworks and allows tailwind, FrankenUI, HighlighJS, and DaisyUI components to work well together. + +Tailwind, FrankenUI and DaisyUI are imported by default. You must use DaisyUI headers to use anything in the `daisy` module, and FrankenUI headers to use anything in the `franken` module. + +HighlightJS is not added by default, but you can add it by setting `highlightjs=True` in the headers function. The `render_md` function will use HighlightJS for code blocks. + +Theme options are: + +Theme.slate + +Theme.stone + +Theme.gray + +Theme.neutral + +Theme.red + +Theme.rose + +Theme.orange + +Theme.green + +Theme.blue + +Theme.yellow + +Theme.violet + +Theme.zinc + +### Theme Picker + +See Source + +See Output + +ZincSlateStoneGrayNeutralRedRoseOrangeGreenBlueYellowVioletNoneSmallMediumLargeNoneSmallMediumLargeSmallDefaultLightDark + +[code] + + def ex_theme_switcher(): + return ThemePicker() + +[/code] + +### ThemePicker + +Source + +[code] + + ThemePicker(color=True, radii=True, shadows=True, font=True, mode=True, cls='p-4', custom_themes=[]) +[/code] + +> Theme picker component with configurable sections + +**Params** + + * `color` + + * `radii` + + * `shadows` + + * `font` + + * `mode` + + * `cls` + + * `custom_themes` + +### Custom Themes + + 1. You can use this theme as a starting point. + 2. Add the theme to your headers as a link like this `Link(rel="stylesheet", href="/custom_theme.css", type="text/css")` + 3. Then add the theme to the `ThemePicker` component. For example `ThemePicker(custom_themes=[('Grass', '#10b981')])` + +Themes are controlled with `bg-background text-foreground` classes on the `Body` tag. `fast_app` and `FastHTML` will do this for you automatically so you typically do not have to do anything + +### fast_app + +Source + +[code] + + fast_app(*args, pico=False, db_file: Optional[str] = None, render: Optional[] = None, hdrs: Optional[tuple] = None, ftrs: Optional[tuple] = None, tbls: Optional[dict] = None, before: Union[tuple, NoneType, fasthtml.core.Beforeware] = None, middleware: Optional[tuple] = None, live: bool = False, debug: bool = False, title: str = 'FastHTML page', routes: Optional[tuple] = None, exception_handlers: Optional[dict] = None, on_startup: Optional[] = None, on_shutdown: Optional[] = None, lifespan: Optional[] = None, default_hdrs=True, surreal: Optional[bool] = True, htmx: Optional[bool] = True, exts: Union[list, str, NoneType] = None, canonical: bool = True, secret_key: Optional[str] = None, key_fname: str = '.sesskey', session_cookie: str = 'session_', max_age: int = 31536000, sess_path: str = '/', same_site: str = 'lax', sess_https_only: bool = False, sess_domain: Optional[str] = None, htmlkw: Optional[dict] = None, bodykw: Optional[dict] = None, reload_attempts: Optional[int] = 1, reload_interval: Optional[int] = 1000, static_path: str = '.', body_wrap: = , nb_hdrs: bool = False) +[/code] + +> Create a FastHTML or FastHTMLWithLiveReload app with `bg-background text-foreground` to bodykw for frankenui themes + +**Params** + + * `db_file` Database file name, if needed + + * `render` Function used to render default database class + + * `hdrs` Additional FT elements to add to + + * `ftrs` Additional FT elements to add to end of + + * `tbls` Experimental mapping from DB table names to dict table definitions + + * `before` Functions to call prior to calling handler + + * `middleware` Standard Starlette middleware + + * `live` Enable live reloading + + * `debug` Passed to Starlette, indicating if debug tracebacks should be returned on errors + + * `title` Default page title + + * `routes` Passed to Starlette + + * `exception_handlers` Passed to Starlette + + * `on_startup` Passed to Starlette + + * `on_shutdown` Passed to Starlette + + * `lifespan` Passed to Starlette + + * `default_hdrs` Include default FastHTML headers such as HTMX script? + + * `pico` Include PicoCSS header? + + * `surreal` Include surreal.js/scope headers? + + * `htmx` Include HTMX header? + + * `exts` HTMX extension names to include + + * `canonical` Automatically include canonical link? + + * `secret_key` Signing key for sessions + + * `key_fname` Session cookie signing key file name + + * `session_cookie` Session cookie name + + * `max_age` Session cookie expiry time + + * `sess_path` Session cookie path + + * `same_site` Session cookie same site policy + + * `sess_https_only` Session cookie HTTPS only? + + * `sess_domain` Session cookie domain + + * `htmlkw` Attrs to add to the HTML tag + + * `bodykw` Attrs to add to the Body tag + + * `reload_attempts` Number of reload attempts when live reloading + + * `reload_interval` Time between reload attempts in ms + + * `static_path` Where the static file route points to, defaults to root dir + + * `body_wrap` FT wrapper for body contents + + * `nb_hdrs` If in notebook include headers inject headers in notebook DOM? + + * `args` + +### FastHTML + +Source + +[code] + + FastHTML(*args, pico=False, debug=False, routes=None, middleware=None, title: str = 'FastHTML page', exception_handlers=None, on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None, exts=None, before=None, after=None, surreal=True, htmx=True, default_hdrs=True, sess_cls=, secret_key=None, session_cookie='session_', max_age=31536000, sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', body_wrap=, htmlkw=None, nb_hdrs=False, canonical=True) +[/code] + +> Create a FastHTML app and adds `bg-background text-foreground` to bodykw for frankenui themes + +**Params** + + * `debug` + + * `routes` + + * `middleware` + + * `title` + + * `exception_handlers` + + * `on_startup` + + * `on_shutdown` + + * `lifespan` + + * `hdrs` + + * `ftrs` + + * `exts` + + * `before` + + * `after` + + * `surreal` + + * `htmx` + + * `default_hdrs` + + * `sess_cls` + + * `secret_key` + + * `session_cookie` + + * `max_age` + + * `sess_path` + + * `same_site` + + * `sess_https_only` + + * `sess_domain` + + * `key_fname` + + * `body_wrap` + + * `htmlkw` + + * `nb_hdrs` + + * `canonical` + + * `args` + + * `pico` + +> Users have said this site is helpful in creating your own themes. + diff --git a/docs/vendor/monsterui/api_ref/docs_typography.md b/docs/vendor/monsterui/api_ref/docs_typography.md new file mode 100644 index 0000000..d4ecfb6 --- /dev/null +++ b/docs/vendor/monsterui/api_ref/docs_typography.md @@ -0,0 +1,1037 @@ +# Typography API Reference + +Ready to go semantic options that cover most of what you need based on the HTML spec + +See Source + +See Output + +Titled + +# Titled + +# Level 1 Heading (H1) + +## Level 2 Heading (H2) + +### Level 3 Heading (H3) + +#### Level 4 Heading (H4) + +##### Level 5 Heading (H5) + +###### Level 6 Heading (H6) + +[code] + + def ex_headings(): + return Div( + Titled("Titled"), + H1("Level 1 Heading (H1)"), + H2("Level 2 Heading (H2)"), + H3("Level 3 Heading (H3)"), + H4("Level 4 Heading (H4)"), + H5("Level 5 Heading (H5)"), + H6("Level 6 Heading (H6)"), + ) + +[/code] + +See Source + +See Output + +## Semantic HTML Elements Demo + +Here's an example of _emphasized (Em)_ and **strong (Strong)** text. + +Some _italic text (I)_ and smaller text (Small) in a paragraph. + +You can highlight (Mark) text, show ~~deleted (Del)~~ and inserted (Ins) content. + +Chemical formulas use subscripts (Sub) and superscripts (Sup) like H2O. + +> The only way to do great work is to love what you do. +> +> Steve Jobs (Cite) + +As Shakespeare wrote, "All the world's a stage (Q)". + +Posted on 2024-01-29 + +Mozilla Foundation (Address) +331 E Evelyn Ave (Address) +Mountain View, CA 94041 (Address) +USA (Address) + +HTML (Dfn) (HyperText Markup Language (Abbr)) is the standard markup language for documents designed to be displayed in a web browser. + +Press `Ctrl (Kbd)` \+ `C (Kbd)` to copy. + +The command returned: Hello, World! (Samp) + +Let x (Var) be the variable in the equation. + +Figure 1: An example image with caption (Caption)Click to show more information (Summary) + +This is the detailed content that is initially hidden (P) + +123 (Data) is a number, and here's a Meter showing progress: + +Temperature: (with low/high/optimum values) + +€42.00 \- price example with semantic value + +Form calculation result: The sum is 42 (Output) + +### Blog Post Title (H3) + +By John Doe • 5 min read (Small) + +Article content here... + +This text has _proper name annotation (U)_ and this is ~~outdated information (S)~~ that's been superseded. + +[code] + + def ex_semantic_elements(): + return Div( + H2("Semantic HTML Elements Demo"), + # Text formatting examples + P("Here's an example of ", Em("emphasized (Em)"), " and ", Strong("strong (Strong)"), " text."), + P("Some ", I("italic text (I)"), " and ", Small("smaller text (Small)"), " in a paragraph."), + P("You can ", Mark("highlight (Mark)"), " text, show ", Del("deleted (Del)"), " and ", + Ins("inserted (Ins)"), " content."), + P("Chemical formulas use ", Sub("subscripts (Sub)"), " and ", Sup("superscripts (Sup)"), + " like H", Sub("2"), "O."), + # Quote examples + Blockquote( + P("The only way to do great work is to love what you do."), + Cite("Steve Jobs (Cite)")), + P("As Shakespeare wrote, ", Q("All the world's a stage (Q)"), "."), + # Time and Address + P("Posted on ", Time("2024-01-29", datetime="2024-01-29")), + Address( + "Mozilla Foundation (Address)", + Br(), + "331 E Evelyn Ave (Address)", + Br(), + "Mountain View, CA 94041 (Address)", + Br(), + "USA (Address)"), + # Technical and definition examples + P( + Dfn("HTML (Dfn)"), " (", + Abbr("HyperText Markup Language (Abbr)", title="HyperText Markup Language"), + ") is the standard markup language for documents designed to be displayed in a web browser."), + P("Press ", Kbd("Ctrl (Kbd)"), " + ", Kbd("C (Kbd)"), " to copy."), + P("The command returned: ", Samp("Hello, World! (Samp)")), + P("Let ", Var("x (Var)"), " be the variable in the equation."), + # Figure with caption + Figure( + PicSumImg(), + Caption("Figure 1: An example image with caption (Caption)")), + # Interactive elements + Details( + Summary("Click to show more information (Summary)"), + P("This is the detailed content that is initially hidden (P)")), + # Data representation + P( + Data("123 (Data)", value="123"), " is a number, and here's a Meter showing progress: ", + Meter(value=0.6, min=0, max=1)), + P( + "Temperature: ", + Meter(value=-1, min=-10, max=40, low=0, high=30, optimum=21), + " (with low/high/optimum values)"), + P( + Data("€42.00", value="42"), + " - price example with semantic value"), + # Output example + P("Form calculation result: ", Output("The sum is 42 (Output)", form="calc-form", for_="num1 num2")), + # Meta information example + Section( + H3("Blog Post Title (H3)"), + Small("By John Doe • 5 min read (Small)"), + P("Article content here...")), + # Text decoration examples + P("This text has ",U("proper name annotation (U)"), " and this is ",S("outdated information (S)"), " that's been superseded."), + cls='space-y-4' + ) + +[/code] + +See Source + +See Output + +`This is a CodeSpan element` + +> This is a blockquote element +[code] + + #This is a CodeBlock element + + def add(a,b): return a+b +[/code] + +[code] + + def ex_other(): + return Div( + CodeSpan("This is a CodeSpan element"), + Blockquote("This is a blockquote element"), + CodeBlock("#This is a CodeBlock element\n\ndef add(a,b): return a+b")) + +[/code] + +Styling text is possibly the most common style thing to do, so we have a couple of helpers for discoverability inside python. `TextPresets` is intended to be combinations are are widely applicable and used often, where `TextT` is intended to be more flexible options for you to combine together yourself. + +##### TextPresets.* + +See Source + +See Output + +This is muted_sm text + +This is muted_lg text + +This is bold_sm text + +This is bold_lg text + +This is md_weight_sm text + +This is md_weight_muted text + +[code] + + def ex_textpresets(): + return Grid(*[Div(P(f"This is {preset.name} text", cls=preset.value)) for preset in TextPresets]) + +[/code] + +##### TextT.* + +See Source + +See Output + +This is paragraph text + +This is lead text + +This is meta text + +This is gray text + +This is italic text + +This is xs text + +This is sm text + +This is lg text + +This is xl text + +This is light text + +This is normal text + +This is medium text + +This is bold text + +This is extrabold text + +This is primary text + +This is secondary text + +This is success text + +This is warning text + +This is error text + +This is info text + +This is left text + +This is right text + +This is center text + +This is justify text + +This is start text + +This is end text + +This is top text + +This is middle text + +This is bottom text + +This is truncate text + +This is break_ text + +This is nowrap text + +This is underline text + +This is highlight text + +[code] + + def ex_textt(): + return Grid(*[Div(P(f"This is {s.name} text", cls=s.value)) for s in TextT]) + +[/code] + +### API Reference + +* * * + +### TextPresets + +_Common Typography Presets_ + +Option | Value | Option | Value +---|---|---|--- +muted_sm | text-gray-500 dark:text-gray-200 text-sm | muted_lg | text-gray-500 dark:text-gray-200 text-lg +bold_sm | font-bold text-sm | bold_lg | font-bold text-lg +md_weight_sm | text-sm font-medium | md_weight_muted | font-medium text-gray-500 dark:text-gray-200 + +* * * + +### TextT + +_Text Styles from https://franken-ui.dev/docs/text_ + +Option | Value | Option | Value | Option | Value | Option | Value +---|---|---|---|---|---|---|--- +paragraph | uk-paragraph | lead | uk-text-lead | meta | uk-text-meta | gray | text-gray-500 dark:text-gray-200 +italic | italic | xs | text-xs | sm | text-sm | lg | text-lg +xl | text-xl | light | font-light | normal | font-normal | medium | font-medium +bold | font-bold | extrabold | font-extrabold | muted | text-gray-500 dark:text-gray-200 | primary | text-primary +secondary | text-secondary | success | text-success | warning | text-warning | error | text-error +info | text-info | left | text-left | right | text-right | center | text-center +justify | text-justify | start | text-start | end | text-end | top | align-top +middle | align-middle | bottom | align-bottom | truncate | uk-text-truncate | break_ | uk-text-break +nowrap | uk-text-nowrap | underline | underline | highlight | bg-yellow-200 dark:bg-yellow-800 text-black | | + +### H1 + +Source + +[code] + + H1(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H1 with styling and appropriate size + +**Params** + + * `c` Contents of H1 tag (often text) + + * `cls` Classes in addition to H1 styling + + * `kwargs` + +**Returns:** H1(..., cls='uk-h1') + +### H2 + +Source + +[code] + + H2(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H2 with styling and appropriate size + +**Params** + + * `c` Contents of H2 tag (often text) + + * `cls` Classes in addition to H2 styling + + * `kwargs` + +**Returns:** H2(..., cls='uk-h2') + +### H3 + +Source + +[code] + + H3(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H3 with styling and appropriate size + +**Params** + + * `c` Contents of H3 tag (often text) + + * `cls` Classes in addition to H3 styling + + * `kwargs` + +**Returns:** H3(..., cls='uk-h3') + +### H4 + +Source + +[code] + + H4(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H4 with styling and appropriate size + +**Params** + + * `c` Contents of H4 tag (often text) + + * `cls` Classes in addition to H4 styling + + * `kwargs` + +**Returns:** H4(..., cls='uk-h4') + +### H5 + +Source + +[code] + + H5(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H5 with styling and appropriate size + +**Params** + + * `c` Contents of H5 tag (often text) + + * `cls` Classes in addition to H5 styling + + * `kwargs` + +**Returns:** H5(..., cls='text-lg font-semibold') + +### H6 + +Source + +[code] + + H6(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> H6 with styling and appropriate size + +**Params** + + * `c` Contents of H6 tag (often text) + + * `cls` Classes in addition to H6 styling + + * `kwargs` + +**Returns:** H6(..., cls='text-base font-semibold') + +### CodeSpan + +Source + +[code] + + CodeSpan(*c, cls=(), **kwargs) -> fastcore.xml.FT +[/code] + +> A CodeSpan with Styling + +**Params** + + * `c` Contents of CodeSpan tag (inline text code snippets) + + * `cls` Classes in addition to CodeSpan styling + + * `kwargs` + +**Returns:** Code(..., cls='uk-codespan') + +### Blockquote + +Source + +[code] + + Blockquote(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Blockquote with Styling + +**Params** + + * `c` Contents of Blockquote tag (often text) + + * `cls` Classes in addition to Blockquote styling + + * `kwargs` + +**Returns:** Blockquote(..., cls='uk-blockquote') + +### CodeBlock + +Source + +[code] + + CodeBlock(*c: str, cls: enum.Enum | str | tuple = (), code_cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> CodeBlock with Styling + +**Params** + + * `c` Contents of Code tag (often text) + + * `cls` Classes for the outer container + + * `code_cls` Classes for the code tag + + * `kwargs` + +**Returns:** Div(Pre(Code(..., cls='uk-codeblock), cls='multiple tailwind styles'), cls='uk-block') + +### Em + +Source + +[code] + + Em(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled emphasis text + +**Params** + + * `c` Contents of Em tag (emphasis) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Em tag + +### Strong + +Source + +[code] + + Strong(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled strong text + +**Params** + + * `c` Contents of Strong tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Strong tag + +### I + +Source + +[code] + + I(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled italic text + +**Params** + + * `c` Contents of I tag (italics) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for I tag + +### Small + +Source + +[code] + + Small(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled small text + +**Params** + + * `c` Contents of Small tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Small tag + +### Mark + +Source + +[code] + + Mark(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled highlighted text + +**Params** + + * `c` Contents of Mark tag (highlighted text) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Mark tag + +### Sub + +Source + +[code] + + Sub(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled subscript text + +**Params** + + * `c` Contents of Sub tag (subscript) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Sub tag + +### Sup + +Source + +[code] + + Sup(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled superscript text + +**Params** + + * `c` Contents of Sup tag (superscript) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Sup tag + +### Del + +Source + +[code] + + Del(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled deleted text + +**Params** + + * `c` Contents of Del tag (deleted text) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Del tag + +### Ins + +Source + +[code] + + Ins(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled inserted text + +**Params** + + * `c` Contents of Ins tag (inserted text) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Ins tag + +### Dfn + +Source + +[code] + + Dfn(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled definition term with italic and medium weight + +**Params** + + * `c` Contents of Dfn tag (definition) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Dfn tag + +### Abbr + +Source + +[code] + + Abbr(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), title: str = None, **kwargs) -> fastcore.xml.FT +[/code] + +> Styled abbreviation with dotted underline + +**Params** + + * `c` Contents of Abbr tag + + * `cls` Additional classes + + * `title` Title attribute for abbreviation + + * `kwargs` + +**Returns:** Additional args for Abbr tag + +### Q + +Source + +[code] + + Q(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled quotation mark + +**Params** + + * `c` Contents of Q tag (quote) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Q tag + +### Kbd + +Source + +[code] + + Kbd(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled keyboard input with subtle background + +**Params** + + * `c` Contents of Kbd tag (keyboard input) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Kbd tag + +### Samp + +Source + +[code] + + Samp(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled sample output with subtle background + +**Params** + + * `c` Contents of Samp tag (sample output) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Samp tag + +### Var + +Source + +[code] + + Var(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled variable with italic monospace + +**Params** + + * `c` Contents of Var tag (variable) + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Var tag + +### Figure + +Source + +[code] + + Figure(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled figure container with card-like appearance + +**Params** + + * `c` Contents of Figure tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Figure tag + +### Caption + +Source + +[code] + + Caption(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled caption text + +**Params** + + * `c` + + * `cls` + + * `kwargs` + +### Details + +Source + +[code] + + Details(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled details element + +**Params** + + * `c` Contents of Details tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Details tag + +### Summary + +Source + +[code] + + Summary(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled summary element + +**Params** + + * `c` Contents of Summary tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Summary tag + +### Meter + +Source + +[code] + + Meter(*c: fastcore.xml.FT | str, value: float = None, min: float = None, max: float = None, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled meter element + +**Params** + + * `c` Contents of Meter tag + + * `value` Current value + + * `min` Minimum value + + * `max` Maximum value + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Meter tag + +### Data + +Source + +[code] + + Data(*c: fastcore.xml.FT | str, value: str = None, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled data element + +**Params** + + * `c` Contents of Data tag + + * `value` Value attribute + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Data tag + +### Output + +Source + +[code] + + Output(*c: fastcore.xml.FT | str, form: str = None, for_: str = None, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled output element for form results + +**Params** + + * `c` Contents of Output tag + + * `form` ID of form this output belongs to + + * `for_` IDs of elements this output is for + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Output tag + +### Address + +Source + +[code] + + Address(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), **kwargs) -> fastcore.xml.FT +[/code] + +> Styled address element + +**Params** + + * `c` Contents of Address tag + + * `cls` Additional classes + + * `kwargs` + +**Returns:** Additional args for Address tag + +### Time + +Source + +[code] + + Time(*c: fastcore.xml.FT | str, cls: enum.Enum | str | tuple = (), datetime: str = None, **kwargs) -> fastcore.xml.FT +[/code] + +> Styled time element + +**Params** + + * `c` Contents of Time tag + + * `cls` Additional classes + + * `datetime` datetime attribute + + * `kwargs` + +**Returns:** Additional args for Time tag + + *[HyperText Markup Language (Abbr)]: HyperText Markup Language + diff --git a/docs/vendor/monsterui/apilist.txt b/docs/vendor/monsterui/apilist.txt new file mode 100644 index 0000000..03976f2 --- /dev/null +++ b/docs/vendor/monsterui/apilist.txt @@ -0,0 +1,529 @@ +# monsterui Module Documentation + +## monsterui.core + +- `class ThemeRadii(Enum)` + Members: none, sm, md, lg + + +- `class ThemeShadows` + +- `class ThemeFont` + +- `class Theme(Enum)` + Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind + daisyui + highlight.js options + Members: slate, stone, gray, neutral, red, rose, orange, green, blue, yellow, violet, zinc + + - `headers(self, mode, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` + Create frankenui and tailwind cdns + + - `local_headers(self, mode, static_dir, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` + Create headers using local files downloaded from CDNs + + +## monsterui.daisy + +- `class AlertT(Enum)` + Alert styles from DaisyUI + Members: info, success, warning, error + + +- `def Alert(*c, **kwargs)` + Alert informs users about important events. + +- `class StepsT(Enum)` + Options for Steps + Members: vertical, horizonal + + +- `class StepT(Enum)` + Step styles for LiStep + Members: primary, secondary, accent, info, success, warning, error, neutral + + +- `def Steps(*li, **kwargs)` + Creates a steps container + +- `def LiStep(*c, **kwargs)` + Creates a step list item + +- `class LoadingT(Enum)` + Members: spinner, dots, ring, ball, bars, infinity, xs, sm, md, lg + + +- `def Loading(cls, htmx_indicator, **kwargs)` + Creates a loading animation component + +- `class ToastHT(Enum)` + Horizontal position for Toast + Members: start, center, end + + +- `class ToastVT(Enum)` + Vertical position for Toast + Members: top, middle, bottom + + +## monsterui.foundations + +> Data Structures and Utilties + +- `def stringify(o)` + Converts input types into strings that can be passed to FT components + +- `class VEnum(Enum)` + Members: + + - `__str__(self)` + - `__add__(self, other)` + - `__radd__(self, other)` + +## monsterui.franken + +- `class TextT(Enum)` + Text Styles from https://franken-ui.dev/docs/text + Members: paragraph, lead, meta, gray, italic, xs, sm, lg, xl, light, normal, medium, bold, extrabold, muted, primary, secondary, success, warning, error, info, left, right, center, justify, start, end, top, middle, bottom, truncate, break_, nowrap, underline, highlight + + +- `class TextPresets(Enum)` + Common Typography Presets + Members: muted_sm, muted_lg, bold_sm, bold_lg, md_weight_sm, md_weight_muted + + +- `def CodeSpan(*c, **kwargs)` + A CodeSpan with Styling + +- `def CodeBlock(*c, **kwargs)` + CodeBlock with Styling + +- `def H1(*c, **kwargs)` + H1 with styling and appropriate size + +- `def H2(*c, **kwargs)` + H2 with styling and appropriate size + +- `def H3(*c, **kwargs)` + H3 with styling and appropriate size + +- `def H4(*c, **kwargs)` + H4 with styling and appropriate size + +- `def H5(*c, **kwargs)` + H5 with styling and appropriate size + +- `def H6(*c, **kwargs)` + H6 with styling and appropriate size + +- `def Subtitle(*c, **kwargs)` + Styled muted_sm text designed to go under Headings and Titles + +- `def Q(*c, **kwargs)` + Styled quotation mark + +- `def Em(*c, **kwargs)` + Styled emphasis text + +- `def Strong(*c, **kwargs)` + Styled strong text + +- `def I(*c, **kwargs)` + Styled italic text + +- `def Small(*c, **kwargs)` + Styled small text + +- `def Mark(*c, **kwargs)` + Styled highlighted text + +- `def Del(*c, **kwargs)` + Styled deleted text + +- `def Ins(*c, **kwargs)` + Styled inserted text + +- `def Sub(*c, **kwargs)` + Styled subscript text + +- `def Sup(*c, **kwargs)` + Styled superscript text + +- `def Blockquote(*c, **kwargs)` + Blockquote with Styling + +- `def Caption(*c, **kwargs)` + Styled caption text + +- `def Cite(*c, **kwargs)` + Styled citation text + +- `def Time(*c, **kwargs)` + Styled time element + +- `def Address(*c, **kwargs)` + Styled address element + +- `def Abbr(*c, **kwargs)` + Styled abbreviation with dotted underline + +- `def Dfn(*c, **kwargs)` + Styled definition term with italic and medium weight + +- `def Kbd(*c, **kwargs)` + Styled keyboard input with subtle background + +- `def Samp(*c, **kwargs)` + Styled sample output with subtle background + +- `def Var(*c, **kwargs)` + Styled variable with italic monospace + +- `def Figure(*c, **kwargs)` + Styled figure container with card-like appearance + +- `def Details(*c, **kwargs)` + Styled details element + +- `def Summary(*c, **kwargs)` + Styled summary element + +- `def Data(*c, **kwargs)` + Styled data element + +- `def Meter(*c, **kwargs)` + Styled meter element + +- `def S(*c, **kwargs)` + Styled strikethrough text (different semantic meaning from Del) + +- `def U(*c, **kwargs)` + Styled underline (for proper names in Chinese, proper spelling etc) + +- `def Output(*c, **kwargs)` + Styled output element for form results + +- `def PicSumImg(h, w, id, grayscale, blur, **kwargs)` + Creates a placeholder image using https://picsum.photos/ + +- `def AccordionItem(title, *c)` + Creates a single item for use within an Accordion component, handling title, content, and open state. + +- `def Accordion(*c, **kwargs)` + Creates a styled Accordion container using accordion component. + +- `class ButtonT(Enum)` + Options for styling Buttons + Members: default, ghost, primary, secondary, destructive, text, link, xs, sm, lg, xl, icon + + +- `def Button(*c, **kwargs)` + Button with Styling (defaults to `submit` for form submission) + +- `class ContainerT(Enum)` + Max width container sizes from https://franken-ui.dev/docs/container + Members: xs, sm, lg, xl, expand + + +- `class BackgroundT(Enum)` + Members: muted, primary, secondary, default + + +- `def Container(*c, **kwargs)` + Div to be used as a container that often wraps large sections or a page of content + +- `def Titled(title, *c, **kwargs)` + Creates a standard page structure for titled page. Main(Container(title, content)) + +- `class DividerT(Enum)` + Divider Styles from https://franken-ui.dev/docs/divider + Members: icon, sm, vertical + + +- `def Divider(*c, **kwargs)` + Divider with default styling and margin + +- `def DividerSplit(*c)` + Creates a simple horizontal line divider with configurable thickness and vertical spacing + +- `def Article(*c, **kwargs)` + A styled article container for blog posts or similar content + +- `def ArticleTitle(*c, **kwargs)` + A title component for use within an Article + +- `def ArticleMeta(*c, **kwargs)` + A metadata component for use within an Article showing things like date, author etc + +- `class SectionT(Enum)` + Section styles from https://franken-ui.dev/docs/section + Members: default, muted, primary, secondary, xs, sm, lg, xl, remove_vertical + + +- `def Section(*c, **kwargs)` + Section with styling and margins + +- `def Form(*c, **kwargs)` + A Form with default spacing between form elements + +- `def Fieldset(*c, **kwargs)` + A Fieldset with default styling + +- `def Legend(*c, **kwargs)` + A Legend with default styling + +- `def Input(*c, **kwargs)` + An Input with default styling + +- `def Radio(*c, **kwargs)` + A Radio with default styling + +- `def CheckboxX(*c, **kwargs)` + A Checkbox with default styling + +- `def Range(*c, **kwargs)` + A Range with default styling + +- `def TextArea(*c, **kwargs)` + A Textarea with default styling + +- `def Switch(*c, **kwargs)` + A Switch with default styling + +- `def Upload(*c, **kwargs)` + A file upload component with default styling + +- `def UploadZone(*c, **kwargs)` + A file drop zone component with default styling + +- `def FormLabel(*c, **kwargs)` + A Label with default styling + +- `class LabelT(Enum)` + Members: primary, secondary, destructive + + +- `def Label(*c, **kwargs)` + FrankenUI labels, which look like pills + +- `def UkFormSection(title, description, *c)` + A form section with a title, description and optional button + +- `def GenericLabelInput(label, lbl_cls, input_cls, container, cls, id, input_fn, **kwargs)` + `Div(Label,Input)` component with Uk styling injected appropriately. Generally you should higher level API, such as `LabelInput` which is created for you in this library + +- `def LabelInput(label, lbl_cls, input_cls, cls, id, **kwargs)` + A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id + +- `def LabelRadio(label, lbl_cls, input_cls, container, cls, id, **kwargs)` + A FormLabel and Radio pair that provides default spacing and links/names them based on id + +- `def LabelCheckboxX(label, lbl_cls, input_cls, container, cls, id, **kwargs)` + A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id + +- `def Options(*c)` + Helper function to wrap things into `Option`s for use in `Select` + +- `def Select(*option, **kwargs)` + Creates a select dropdown with uk styling and option for adding a search box + +- `def LabelSelect(*option, **kwargs)` + A FormLabel and Select pair that provides default spacing and links/names them based on id + +- `@delegates(GenericLabelInput, but=['input_fn', 'cls']) def LabelRange(label, lbl_cls, input_cls, cls, id, value, min, max, step, label_range, **kwargs)` + A FormLabel and Range pair that provides default spacing and links/names them based on id + +- `class AT(Enum)` + Link styles from https://franken-ui.dev/docs/link + Members: muted, text, reset, primary, classic + + +- `class ListT(Enum)` + List styles using Tailwind CSS + Members: disc, circle, square, decimal, hyphen, bullet, divider, striped + + +- `def ModalContainer(*c, **kwargs)` + Creates a modal container that components go in + +- `def ModalDialog(*c, **kwargs)` + Creates a modal dialog + +- `def ModalHeader(*c, **kwargs)` + Creates a modal header + +- `def ModalBody(*c, **kwargs)` + Creates a modal body + +- `def ModalFooter(*c, **kwargs)` + Creates a modal footer + +- `def ModalTitle(*c, **kwargs)` + Creates a modal title + +- `def ModalCloseButton(*c, **kwargs)` + Creates a button that closes a modal with js + +- `def Modal(*c, **kwargs)` + Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you + +- `def Placeholder(*c, **kwargs)` + Creates a placeholder + +- `def Progress(*c, **kwargs)` + Creates a progress bar + +- `def UkIcon(icon, height, width, stroke_width, cls, **kwargs)` + Creates an icon using lucide icons + +- `def UkIconLink(icon, height, width, stroke_width, cls, button, **kwargs)` + Creates an icon link using lucide icons + +- `def DiceBearAvatar(seed_name, h, w)` + Creates an Avatar using https://dicebear.com/ + +- `def Center(*c, **kwargs)` + Centers contents both vertically and horizontally by default + +- `class FlexT(Enum)` + Flexbox modifiers using Tailwind CSS + Members: block, inline, left, center, right, between, around, stretch, top, middle, bottom, row, row_reverse, column, column_reverse, nowrap, wrap, wrap_reverse + + +- `def Grid(*div, **kwargs)` + Creates a responsive grid layout with smart defaults based on content + +- `def DivFullySpaced(*c, **kwargs)` + Creates a flex div with it's components having as much space between them as possible + +- `def DivCentered(*c, **kwargs)` + Creates a flex div with it's components centered in it + +- `def DivLAligned(*c, **kwargs)` + Creates a flex div with it's components aligned to the left + +- `def DivRAligned(*c, **kwargs)` + Creates a flex div with it's components aligned to the right + +- `def DivVStacked(*c, **kwargs)` + Creates a flex div with it's components stacked vertically + +- `def DivHStacked(*c, **kwargs)` + Creates a flex div with it's components stacked horizontally + +- `class NavT(Enum)` + Members: default, primary, secondary + + +- `def NavContainer(*li, **kwargs)` + Creates a navigation container (useful for creating a sidebar navigation). A Nav is a list (NavBar is something different) + +- `def NavParentLi(*nav_container, **kwargs)` + Creates a navigation list item with a parent nav for nesting + +- `def NavDividerLi(*c, **kwargs)` + Creates a navigation list item with a divider + +- `def NavHeaderLi(*c, **kwargs)` + Creates a navigation list item with a header + +- `def NavSubtitle(*c, **kwargs)` + Creates a navigation subtitle + +- `def NavCloseLi(*c, **kwargs)` + Creates a navigation list item with a close button + +- `class ScrollspyT(Enum)` + Members: underline, bold + + +- `def NavBar(*c)` + Creates a responsive navigation bar with mobile menu support + +- `def SliderContainer(*c, **kwargs)` + Creates a slider container + +- `def SliderItems(*c, **kwargs)` + Creates a slider items container + +- `def SliderNav(cls, prev_cls, next_cls, **kwargs)` + Navigation arrows for Slider component + +- `def Slider(*c, **kwargs)` + Creates a slider with optional navigation arrows + +- `def DropDownNavContainer(*li, **kwargs)` + A Nav that is part of a DropDown + +- `def TabContainer(*li, **kwargs)` + A TabContainer where children will be different tabs + +- `class CardT(Enum)` + Card styles from UIkit + Members: default, primary, secondary, destructive, hover + + +- `def CardTitle(*c, **kwargs)` + Creates a card title + +- `def CardHeader(*c, **kwargs)` + Creates a card header + +- `def CardBody(*c, **kwargs)` + Creates a card body + +- `def CardFooter(*c, **kwargs)` + Creates a card footer + +- `def CardContainer(*c, **kwargs)` + Creates a card container + +- `def Card(*c, **kwargs)` + Creates a Card with a header, body, and footer + +- `class TableT(Enum)` + Members: divider, striped, hover, sm, lg, justify, middle, responsive + + +- `def Table(*c, **kwargs)` + Creates a table + +- `def TableFromLists(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` + Creates a Table from a list of header data and a list of lists of body data + +- `def TableFromDicts(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` + Creates a Table from a list of header data and a list of dicts of body data + +- `def apply_classes(html_str, class_map, class_map_mods)` + Apply classes to html string + +- `class FrankenRenderer` + Custom renderer for Franken UI that handles image paths + + - `def __init__(self, *args, **kwargs)` + - `def render_image(self, token)` + Modify image paths if they're relative and self.img_dir is specified + + +- `def render_md(md_content, class_map, class_map_mods, img_dir, renderer)` + Renders markdown using mistletoe and lxml with custom image handling + +- `def ThemePicker(color, radii, shadows, font, mode, cls, custom_themes)` + Theme picker component with configurable sections + +- `def LightboxContainer(*lightboxitem, **kwargs)` + Lightbox container that will hold `LightboxItems` + +- `def LightboxItem(*c, **kwargs)` + Anchor tag with appropriate structure to go inside a `LightBoxContainer` + +- `def ApexChart(**kws)` + Apex chart component + +- `def ScrollSpy(headings, target_sel, offset, nav_id, smooth_scroll, **kwargs)` + Standalone Scrollspy nav menu, mapping heading tags from target into link + +- `def LoaderButton(*c, **kwargs)` + A button that displays a loading spinner when an HTMX request is in flight + +- `def ToggleBtn(*c, **kwargs)` + Styled toggle button component, acts like a switch + diff --git a/docs/vendor/monsterui/examples/auth.md b/docs/vendor/monsterui/examples/auth.md new file mode 100644 index 0000000..da11aa4 --- /dev/null +++ b/docs/vendor/monsterui/examples/auth.md @@ -0,0 +1,40 @@ +"""FrankenUI Auth Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * +from monsterui.all import * +from fasthtml.svg import * + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +@rt +def index(): + left = Div(cls="col-span-1 hidden flex-col justify-between bg-zinc-900 p-8 text-white lg:flex")( + Div(cls=(TextT.bold))("Acme Inc"), + Blockquote(cls="space-y-2")( + P(cls=TextT.lg)('"This library has saved me countless hours of work and helped me deliver stunning designs to my clients faster than ever before."'), + Footer(cls=TextT.sm)("Sofia Davis"))) + + right = Div(cls="col-span-2 flex flex-col p-8 lg:col-span-1")( + DivRAligned(Button("Login", cls=ButtonT.ghost)), + DivCentered(cls='flex-1')( + Container( + DivVStacked( + H3("Create an account"), + Small("Enter your email below to create your account", cls=TextT.muted)), + Form( + Input(placeholder="name@example.com"), + Button(Span(cls="mr-2", uk_spinner="ratio: 0.54"), "Sign in with Email", cls=(ButtonT.primary, "w-full"), disabled=True), + DividerSplit(Small("Or continue with"),cls=TextT.muted), + Button(UkIcon('github',cls='mr-2'), "Github", cls=(ButtonT.default, "w-full")), + cls='space-y-6'), + DivVStacked(Small( + "By clicking continue, you agree to our ", + A(cls=AT.muted, href="#demo")("Terms of Service")," and ", + A(cls=AT.muted, href="#demo")("Privacy Policy"),".", + cls=(TextT.muted,"text-center"))), + cls="space-y-6"))) + + return Title("Auth Example"),Grid(left,right,cols=2, gap=0,cls='h-screen') + + +serve() diff --git a/docs/vendor/monsterui/examples/cards.md b/docs/vendor/monsterui/examples/cards.md new file mode 100644 index 0000000..3245108 --- /dev/null +++ b/docs/vendor/monsterui/examples/cards.md @@ -0,0 +1,141 @@ +"""FrankenUI Cards Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * +from fasthtml.components import Uk_input_tag +from fasthtml.svg import * +from monsterui.all import * +import calendar +from datetime import datetime + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +CreateAccount = Card( + Grid(Button(DivLAligned(UkIcon('github'),Div('Github'))),Button('Google')), + DividerSplit("OR CONTINUE WITH", text_cls=TextPresets.muted_sm), + LabelInput('Email', id='email', placeholder='m@example.com'), + LabelInput('Password', id='password',placeholder='Password', type='Password'), + header=(H3('Create an Account'),Subtitle('Enter your email below to create your account')), + footer=Button('Create Account',cls=(ButtonT.primary,'w-full'))) + +PaypalSVG_data = "M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z" +AppleSVG_data = "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" +Card1Svg = Svg(viewBox="0 0 24 24", fill="none", stroke="currentColor", stroke_linecap="round", stroke_linejoin="round", stroke_width="2", cls="h-6 w-6 mr-1")(Rect(width="20", height="14", x="2", y="5", rx="2"),Path(d="M2 10h20")) +PaypalSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=PaypalSVG_data, fill="currentColor")), +AppleSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=AppleSVG_data, fill="currentColor")) + +PaymentMethod = Card( + Grid(Button(DivCentered(Card1Svg, "Card"), cls='h-20 border-2 border-primary'), + Button(DivCentered(PaypalSvg, "PayPal"), cls='h-20'), + Button(DivCentered(AppleSvg, "Apple"), cls='h-20')), + Form(LabelInput('Name', id='name', placeholder='John Doe'), + LabelInput('Card Number', id='card_number', placeholder='m@example.com'), + Grid(LabelSelect(*Options(*calendar.month_name[1:],selected_idx=0),label='Expires',id='expire_month'), + LabelSelect(*Options(*range(2024,2030),selected_idx=0), label='Year', id='expire_year'), + LabelInput('CVV', id='cvv',placeholder='CVV', cls='mt-0'))), + header=(H3('Payment Method'),Subtitle('Add a new payment method to your account.'))) + +area_opts = ('Team','Billing','Account','Deployment','Support') +severity_opts = ('Severity 1 (Highest)', 'Severity 2', 'Severity 3', 'Severity 4 (Lowest)') +ReportIssue = Card( + Grid(Div(LabelSelect(*Options(*area_opts), label='Area', id='area')), + Div(LabelSelect(*Options(*severity_opts),label='Severity',id='area'))), + LabelInput( label='Subject', id='subject', placeholder='I need help with'), + LabelTextArea( label='Description', id='description',placeholder='Please include all information relevant to your issue'), + Div(FormLabel('Tags', fr='#tags'), + Uk_input_tag(name="Tags",state="danger", value="Spam,Invalid", uk_cloak=True, id='tags')), + header=(H3('Report Issue'),Subtitle('What area are you having problems with?')), + footer = DivFullySpaced(Button('Cancel'), Button(cls=ButtonT.primary)('Submit'))) + +monster_desc ="Python-first beautifully designed components because you deserve to focus on features that matter and your app deserves to be beautiful from day one." +MonsterUI = Card(H4("Monster UI"), + Subtitle(monster_desc), + DivLAligned( + Div("Python"), + DivLAligned(UkIcon('star'),Div("20k"), cls='space-x-1'), + Div(datetime.now().strftime("%B %d, %Y")), + cls=('space-x-4',TextPresets.muted_sm))) + +def CookieTableRow(heading, description, active=False): + return Tr(Td(H5(heading)), + Td(P(description, cls=TextPresets.muted_sm)), + Td(Switch(checked=active))) + +CookieSettings = Card( + Table(Tbody( + CookieTableRow('Strictly Necessary', 'These cookies are essential in order to use the website and use its features.', True), + CookieTableRow('Functional Cookies', 'These cookies allow the website to provide personalized functionality.'), + CookieTableRow('Performance Cookies', 'These cookies help to improve the performance of the website.'))), + header=(H4('Cookie Settings'),Subtitle('Manage your cookie settings here.')), + footer=Button('Save Preferences', cls=(ButtonT.primary, 'w-full'))) + +team_members = [("Sofia Davis", "m@example.com", "Owner"),("Jackson Lee", "p@example.com", "Member"),] +def TeamMemberRow(name, email, role): + return DivFullySpaced( + DivLAligned( + DiceBearAvatar(name, 10,10), + Div(P(name, cls=(TextT.sm, TextT.medium)), + P(email, cls=TextPresets.muted_sm))), + Button(role, UkIcon('chevron-down', cls='ml-4')), + DropDownNavContainer(map(NavCloseLi, [ + A(Div('Viewer', NavSubtitle('Can view and comment.'))), + A(Div('Developer', NavSubtitle('Can view, comment and edit.'))), + A(Div('Billing', NavSubtitle('Can view, comment and manage billing.'))), + A(Div('Owner', NavSubtitle('Admin-level access to all resources.')))]))) + +TeamMembers = Card(*[TeamMemberRow(*member) for member in team_members], + header = (H4('Team Members'),Subtitle('Invite your team members to collaborate.'))) + +access_roles = ("Read and write access", "Read-only access") +team_members = [("Olivia Martin", "m@example.com", "Read and write access"), + ("Isabella Nguyen", "b@example.com", "Read-only access"), + ("Sofia Davis", "p@example.com", "Read-only access")] + +def TeamMemberRow(name, email, role): + return DivFullySpaced( + DivLAligned(DiceBearAvatar(name, 10,10), + Div(P(name, cls=(TextT.sm, TextT.medium)), + P(email, cls=TextPresets.muted_sm))), + Select(*Options(*access_roles, selected_idx=access_roles.index(role)))) + +ShareDocument = Card( + DivLAligned(Input(value='http://example.com/link/to/document'),Button('Copy link', cls='whitespace-nowrap')), + Divider(), + H4('People with access', cls=TextPresets.bold_sm), + *[TeamMemberRow(*member) for member in team_members], + header = (H4('Share this document'),Subtitle('Anyone with the link can view this document.'))) + +DateCard = Card(Button('Jan 20, 2024 - Feb 09, 2024')) + +section_content =(('bell','Everything',"Email digest, mentions & all activity."), + ('user',"Available","Only mentions and comments"), + ('ban', "Ignoring","Turn of all notifications")) + +def NotificationRow(icon, name, desc): + return Li(cls='-mx-1')(A(DivLAligned(UkIcon(icon),Div(P(name),P(desc, cls=TextPresets.muted_sm))))) + +Notifications = Card( + NavContainer( + *[NotificationRow(*row) for row in section_content], + cls=NavT.secondary), + header = (H4('Notification'),Subtitle('Choose what you want to be notified about.')), + body_cls='pt-0') + +TeamCard = Card( + DivLAligned( + DiceBearAvatar("Isaac Flath", h=24, w=24), + Div(H3("Isaac Flath"), P("Library Creator"))), + footer=DivFullySpaced( + DivHStacked(UkIcon("map-pin", height=16), P("Alexandria, VA")), + DivHStacked(*(UkIconLink(icon, height=16) for icon in ("mail", "linkedin", "github")))), + cls=CardT.hover) + +@rt +def index(): + return Title("Cards Example"),Container(Grid( + *map(Div,( + Div(PaymentMethod,CreateAccount, TeamCard, cls='space-y-4'), + Div(TeamMembers, ShareDocument,DateCard,Notifications, cls='space-y-4'), + Div(ReportIssue,MonsterUI,CookieSettings, cls='space-y-4'))), + cols_md=1, cols_lg=2, cols_xl=3)) + +serve() diff --git a/docs/vendor/monsterui/examples/dashboard.md b/docs/vendor/monsterui/examples/dashboard.md new file mode 100644 index 0000000..6fb2c62 --- /dev/null +++ b/docs/vendor/monsterui/examples/dashboard.md @@ -0,0 +1,106 @@ +"""FrankenUI Dashboard Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * # Bring in all of fasthtml +import fasthtml.common as fh # Used to get unstyled components +from monsterui.all import * # Bring in all of monsterui, including shadowing fasthtml components with styled components +from fasthtml.svg import * +import numpy as np +import plotly.express as px +import pandas as pd +import numpy as np + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +def generate_chart(num_points=30): + df = pd.DataFrame({ + 'Date': pd.date_range('2024-01-01', periods=num_points), + 'Revenue': np.random.normal(100, 10, num_points).cumsum(), + 'Users': np.random.normal(80, 8, num_points).cumsum(), + 'Growth': np.random.normal(60, 6, num_points).cumsum()}) + + fig = px.line(df, x='Date', y=['Revenue', 'Users', 'Growth'], template='plotly_white', line_shape='spline') + + fig.update_traces(mode='lines+markers') + fig.update_layout( + margin=dict(l=20, r=20, t=20, b=20), hovermode='x unified', + showlegend=True, legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1), + plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', + xaxis=dict(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)'), + yaxis=dict(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')) + + return fig.to_html(include_plotlyjs=True, full_html=False, config={'displayModeBar': False}) + +def InfoCard(title, value, change): return Card(H3(value),P(change, cls=TextPresets.muted_sm), header = H4(title)) + +rev = InfoCard("Total Revenue", "$45,231.89", "+20.1% from last month") +sub = InfoCard("Subscriptions", "+2350", "+180.1% from last month") +sal = InfoCard("Sales", "+12,234", "+19% from last month") +act = InfoCard("Active Now", "+573", "+201 since last hour") + +info_card_data = [("Total Revenue", "$45,231.89", "+20.1% from last month"), + ("Subscriptions", "+2350", "+180.1% from last month"), + ("Sales", "+12,234", "+19% from last month"), + ("Active Now", "+573", "+201 since last hour")] + +top_info_row = Grid(*[InfoCard(*row) for row in info_card_data]) + +def AvatarItem(name, email, amount): + return DivFullySpaced( + DivLAligned( + DiceBearAvatar(name, 9,9), + Div(Strong(name, cls=TextT.sm), + Address(A(email,href=f'mailto:{email}')))), + fh.Data(amount, cls="ml-auto font-medium", value=amount[2:])) + +recent_sales = Card( + Div(cls="space-y-8")( + *[AvatarItem(n,e,d) for (n,e,d) in ( + ("Olivia Martin", "olivia.martin@email.com", "+$1,999.00"), + ("Jackson Lee", "jackson.lee@email.com", "+$39.00"), + ("Isabella Nguyen", "isabella.nguyen@email.com", "+$299.00"), + ("William Kim", "will@email.com", "+$99.00"), + ("Sofia Davis", "sofia.davis@email.com", "+$39.00"))]), + header=Div(H3("Recent Sales"),Subtitle("You made 265 sales this month.")), + cls='col-span-3') + +teams = [["Alicia Koch"],['Acme Inc', 'Monster Inc.'],['Create a Team']] + +opt_hdrs = ["Personal", "Team", ""] + +team_dropdown = Select( + Optgroup(Option(A("Alicia Koch")), label="Personal Account"), + Optgroup(Option(A("Acme Inc")), Option(A("Monster Inc.")), label="Teams"), + Option(A("Create a Team")), + cls='flex items-center') + +hotkeys = [('Profile','⇧⌘P'),('Billing','⌘B'),('Settings','⌘S'),('New Team', ''), ('Logout', '')] + +def NavSpacedLi(t,s): return NavCloseLi(A(DivFullySpaced(P(t),P(s,cls=TextPresets.muted_sm)))) + +avatar_dropdown = Div( + DiceBearAvatar('Alicia Koch',8,8), + DropDownNavContainer( + NavHeaderLi('sveltecult',NavSubtitle("leader@sveltecult.com")), + *[NavSpacedLi(*hk) for hk in hotkeys],)) + +top_nav = NavBar( + team_dropdown, *map(A, ["Overview", "Customers", "Products", "Settings"]), + brand=DivLAligned(avatar_dropdown, Input(placeholder='Search'))) + +@rt +def index(): + return Title("Dashboard Example"), Container( + top_nav, + H2('Dashboard'), + TabContainer( + Li(A("Overview"),cls='uk-active'), + *map(lambda x: Li(A(x)), ["Analytics", "Reports", "Notifications"]), + alt=True), + top_info_row, + Grid( + Card(Safe(generate_chart(100)), cls='col-span-4'), + recent_sales, + gap=4,cols_xl=7,cols_lg=7,cols_md=1,cols_sm=1,cols_xs=1), + cls=('space-y-4', ContainerT.xl)) + +serve() diff --git a/docs/vendor/monsterui/examples/forms.md b/docs/vendor/monsterui/examples/forms.md new file mode 100644 index 0000000..fa473bc --- /dev/null +++ b/docs/vendor/monsterui/examples/forms.md @@ -0,0 +1,150 @@ +"""FrankenUI Forms Example built with MonsterUI (original design by ShadCN)""" + + +from fasthtml.common import * +from monsterui.all import * +from fasthtml.svg import * + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +def HelpText(c): return P(c,cls=TextPresets.muted_sm) + +def heading(): + return Div(cls="space-y-5")( + H2("Settings"), + Subtitle("Manage your account settings and set e-mail preferences."), + DividerSplit()) + + +sidebar = NavContainer( + *map(lambda x: Li(A(x)), ("Profile", "Account", "Appearance", "Notifications", "Display")), + uk_switcher="connect: #component-nav; animation: uk-animation-fade", + cls=(NavT.secondary,"space-y-4 p-4 w-1/5")) + + +def FormSectionDiv(*c, cls='space-y-2', **kwargs): return Div(*c, cls=cls, **kwargs) + +def FormLayout(title, subtitle, *content, cls='space-y-3 mt-4'): return Container(Div(H3(title), Subtitle(subtitle), DividerLine(), Form(*content, cls=cls))) + +def profile_form(): + content = (FormSectionDiv( + LabelInput("Username", placeholder='sveltecult', id='username'), + HelpText("This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.")), + FormSectionDiv( + LabelSelect( + Option("Select a verified email to display", value="", selected=True, disabled=True), + *[Option(o, value=o) for o in ('m@example.com', 'm@yahoo.com', 'm@cloud.com')], + label="Email", id="email"), + HelpText("You can manage verified email addresses in your email settings.")), + FormSectionDiv( + LabelTextArea("Bio", id="bio", placeholder="Tell us a little bit about yourself"), + HelpText("You can @mention other users and organizations to link to them."), + P("String must contain at least 4 character(s)", cls="text-destructive")), + FormSectionDiv( + FormLabel("URLs"), + HelpText("Add links to your website, blog, or social media profiles."), + Input(value="https://www.franken-ui.dev"), + Input(value="https://github.com/sveltecult/franken-ui"), + Button("Add URL")), + Button('Update profile', cls=ButtonT.primary)) + + return FormLayout('Profile', 'This is how others will see you on the site.', *content) + +def account_form(): + content = ( + FormSectionDiv( + LabelInput("Name", placeholder="Your name", id="name"), + HelpText("This is the name that will be displayed on your profile and in emails.")), + FormSectionDiv( + LabelInput("Date of Birth", type="date", placeholder="Pick a date", id="date_of_birth"), + HelpText("Your date of birth is used to calculate your age.")), + FormSectionDiv( + LabelSelect(*Options("Select a language", "English", "French", "German", "Spanish", "Portuguese", selected_idx=1, disabled_idxs={0}), + label='Language', id="language"), + HelpText("This is the language that will be used in the dashboard.")), + Button('Update profile', cls=ButtonT.primary)) + + return FormLayout('Account', 'Update your account settings. Set your preferred language and timezone.', *content) + +def appearance_form(): + def theme_item(bg_color, content_bg, text_bg): + common_content = f"space-y-2 rounded-md {content_bg} p-2 shadow-sm" + item_row = lambda: Div(cls=f"flex items-center space-x-2 {common_content}")( + Div(cls=f"h-4 w-4 rounded-full {text_bg}"), + Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}")) + + return Div(cls=f"space-y-2 rounded-sm {bg_color} p-2")( + Div(cls=common_content)( + Div(cls=f"h-2 w-[80px] rounded-lg {text_bg}"), + Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}")), + item_row(), + item_row()) + + common_toggle_cls = "block cursor-pointer items-center rounded-md border-2 border-muted p-1 ring-ring" + + content = ( + FormSectionDiv( + LabelSelect(*Options('Select a font family', 'Inter', 'Geist', 'Open Sans', selected_idx=2, disabled_idxs={0}), + label='Font Family', id='font_family'), + HelpText("Set the font you want to use in the dashboard.")), + FormSectionDiv( + FormLabel("Theme"), + HelpText("Select the theme for the dashboard."), + Grid( + A(id="theme-toggle-light", cls=common_toggle_cls)(theme_item("bg-[#ecedef]", "bg-white", "bg-[#ecedef]")), + A(id="theme-toggle-dark", cls=f"{common_toggle_cls} bg-popover")(theme_item("bg-slate-950", "bg-slate-800", "bg-slate-400")), + cols_max=2,cls=('max-w-md','gap-8'))), + Button('Update preferences', cls=ButtonT.primary)) + + return FormLayout('Appearance', 'Customize the appearance of the app. Automatically switch between day and night themes.', *content) + + +notification_items = [ + {"title": "Communication emails", "description": "Receive emails about your account activity.", "checked": False, "disabled": False}, + {"title": "Marketing emails", "description": "Receive emails about new products, features, and more.", "checked": False, "disabled": False}, + {"title": "Social emails", "description": "Receive emails for friend requests, follows, and more.", "checked": True, "disabled": False}, + {"title": "Security emails", "description": "Receive emails about your account activity and security.", "checked": True, "disabled": True}] + +def notifications_form(): + def RadioLabel(label): return DivLAligned(Radio(name="notification", checked=(label=="Nothing")), FormLabel(label)) + + def NotificationCard(item): + return Card( + Div(cls="space-y-0.5")( + FormLabel(Strong(item['title'], cls=TextT.sm), + HelpText(item['description'])))) + content = Div( + FormSectionDiv( + FormLabel("Notify me about"), + *map(RadioLabel, ["All new messages", "Direct messages and mentions", "Nothing"])), + Div( + H4("Email Notifications", cls="mb-4"), + Grid(*map(NotificationCard, notification_items), cols=1)), + LabelCheckboxX("Use different settings for my mobile devices", id="notification_mobile"), + HelpText("You can manage your mobile notifications in the mobile settings page."), + Button('Update notifications', cls=ButtonT.primary)) + + return FormLayout('Notifications', 'Configure how you receive notifications.', *content) + +def display_form(): + content = ( + Div(cls="space-y-2")( + Div(cls="mb-4")( + H5("Sidebar"), + Subtitle("Select the items you want to display in the sidebar.")), + *[Div(CheckboxX(id=f"display_{i}", checked=i in [0, 1, 2]),FormLabel(label)) + for i, label in enumerate(["Recents", "Home", "Applications", "Desktop", "Downloads", "Documents"])]), + Button('Update display', cls=ButtonT.primary)) + return FormLayout('Display', 'Turn items on or off to control what\'s displayed in the app.', *content) + +@rt +def index(): + return Title("Forms Example"),Container( + heading(), + Div(cls="flex gap-x-12")( + sidebar, + Ul(id="component-nav", cls="uk-switcher max-w-2xl")( + Li(cls="uk-active")(profile_form(), + *map(Li, [account_form(), appearance_form(), notifications_form(), display_form()]))))) + +serve() diff --git a/docs/vendor/monsterui/examples/mail.md b/docs/vendor/monsterui/examples/mail.md new file mode 100644 index 0000000..eaa597b --- /dev/null +++ b/docs/vendor/monsterui/examples/mail.md @@ -0,0 +1,111 @@ +"""FrankenUI Mail Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * +from monsterui.all import * +from fasthtml.svg import * +import pathlib, json +from datetime import datetime + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +sidebar_group1 = (('home', 'Inbox', '128'), ('file-text', 'Drafts', '9'), (' arrow-up-right', 'Sent', ''), + ('ban', 'Junk', '23'), ('trash', 'Trash', ''), ('folder', 'Archive', '')) + +sidebar_group2 = (('globe','Social','972'),('info','Updates','342'),('messages-square','Forums','128'), + ('shopping-cart','Shopping','8'),('shopping-bag','Promotions','21'),) + +def MailSbLi(icon, title, cnt): + return Li(A(DivLAligned(Span(UkIcon(icon)),Span(title),P(cnt, cls=TextPresets.muted_sm)),href='#', cls='hover:bg-secondary p-4')) + +sidebar = NavContainer( + NavHeaderLi(H3("Email"), cls='p-3'), + Li(Select(map(Option, ('alicia@example.com','alicia@gmail.com', 'alicia@yahoo.com')))), + *[MailSbLi(i, t, c) for i, t, c in sidebar_group1], + Li(Hr()), + *[MailSbLi(i, t, c) for i, t, c in sidebar_group2], + cls='mt-3') + +mail_data = json.load(open(pathlib.Path('data_/mail.json'))) + +def format_date(date_str): + date_obj = datetime.fromisoformat(date_str) + return date_obj.strftime("%Y-%m-%d %I:%M %p") + +def MailItem(mail): + cls_base = 'relative rounded-lg border border-border p-3 text-sm hover:bg-secondary space-y-2' + cls = f"{cls_base} {'bg-muted' if mail == mail_data[0] else ''} {'tag-unread' if not mail['read'] else 'tag-mail'}" + + return Li( + DivFullySpaced( + DivLAligned( + Strong(mail['name']), + Span(cls='flex h-2 w-2 rounded-full bg-blue-600') if not mail['read'] else ''), + Time(format_date(mail['date']), cls='text-xs')), + Small(mail['subject'], href=f"#mail-{mail['id']}"), + Div(mail['text'][:100] + '...', cls=TextPresets.muted_sm), + DivLAligned( + *[Label(A(label, href='#'), cls='uk-label-primary' if label == 'work' else '') for label in mail['labels']]), + cls=cls) + +def MailList(mails): return Ul(cls='js-filter space-y-2 p-4 pt-0')(*[MailItem(mail) for mail in mails]) + +def MailContent(): + return Div(cls='flex flex-col',uk_filter="target: .js-filter")( + Div(cls='flex px-4 py-2 ')( + H3('Inbox'), + TabContainer(Li(A("All Mail",href='#', role='button'),cls='uk-active', uk_filter_control="filter: .tag-mail"), + Li(A("Unread",href='#', role='button'), uk_filter_control="filter: .tag-unread"), + alt=True, cls='ml-auto max-w-40', )), + Div(cls='flex flex-1 flex-col')( + Div(cls='p-4')( + Div(cls='uk-inline w-full')( + Span(cls='uk-form-icon text-muted-foreground')(UkIcon('search')), + Input(placeholder='Search'))), + Div(cls='flex-1 overflow-y-auto max-h-[600px]')(MailList(mail_data)))) + +def IconNavItem(*d): return [Li(A(UkIcon(o[0],uk_tooltip=o[1]))) for o in d] +def IconNav(*c,cls=''): return Ul(cls=f'uk-iconnav {cls}')(*c) + +def MailDetailView(mail): + top_icons = [('folder','Archive'), ('ban','Move to junk'), ('trash','Move to trash')] + reply_icons = [('reply','Reply'), ('reply','Reply all'), ('forward','Forward')] + dropdown_items = ['Mark as unread', 'Star read', 'Add Label', 'Mute Thread'] + + return Container( + DivFullySpaced( + DivLAligned( + DivLAligned(*[UkIcon(o[0],uk_tooltip=o[1]) for o in top_icons]), + Div(UkIcon('clock', uk_tooltip='Snooze'), cls='pl-2'), + cls='space-x-2 divide-x divide-border'), + DivLAligned( + *[UkIcon(o[0],uk_tooltip=o[1]) for o in reply_icons], + Div(UkIcon('ellipsis-vertical',button=True)), + DropDownNavContainer(*map(lambda x: Li(A(x)), dropdown_items)))), + DivLAligned( + Span(mail['name'][:2], cls='flex h-10 w-10 items-center justify-center rounded-full bg-muted'), + Div(Strong(mail['name']), + Div(mail['subject']), + DivLAligned(P('Reply-To:'), A(mail['email'], href=f"mailto:{mail['email']}"), cls='space-x-1'), + P(Time(format_date(mail['date']))), + cls='space-y-1'+TextT.sm), + cls='m-4 space-x-4'), + DividerLine(), + P(mail['text'], cls=TextT.sm +'p-4'), + DividerLine(), + Div(TextArea(id='message', placeholder=f"Reply {mail['name']}"), + DivFullySpaced( + LabelSwitch('Mute this thread',id='mute'), + Button('Send', cls=ButtonT.primary)), + cls='space-y-4')) + +@rt +def index(): + return Title("Mail Example"),Container( + Grid(Div(sidebar, cls='col-span-1'), + Div(MailContent(), cls='col-span-2'), + Div(MailDetailView(mail_data[0]), cls='col-span-2'), + cols_sm=1, cols_md=1, cols_lg=5, cols_xl=5, + gap=0, cls='flex-1'), + cls=('flex', ContainerT.xl)) + +serve() diff --git a/docs/vendor/monsterui/examples/music.md b/docs/vendor/monsterui/examples/music.md new file mode 100644 index 0000000..5dfd3ef --- /dev/null +++ b/docs/vendor/monsterui/examples/music.md @@ -0,0 +1,138 @@ +"""FrankenUI Music Example build with MonsterUI (Original design by ShadCN)""" + +from fasthtml.common import * + +from monsterui.all import * + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +def MusicLi(t,hk=''): return Li(A(DivFullySpaced(t,P(hk,cls=TextPresets.muted_sm)))) + +music_items = [("About Music", "" ), + ("Preferences", "⌘" ), + ("Hide Music" , "⌘H" ), + ("Hide Others", "⇧⌘H"), + ("Quit Music" , "⌘Q" )] + +file_dd_items = [("New", ""), + ("Open Stream URL", "⌘U"), + ("Close Window", "⌘W"), + ("Library", ""), + ("Import", "⌘O"), + ("Burn Playlist to Disc", ""), + ("Show in Finder", "⇧⌘R"), + ("Convert", ""), + ("Page Setup", "Print")] + +edit_actions = [("Undo", "⌘Z"), + ("Redo", "⇧⌘Z"), + ("Cut", "⌘X"), + ("Copy", "⌘C"), + ("Paste", "⌘V"), + ("Select All", "⌘A"), + ("Deselect All", "⇧⌘A")] + +view_dd_data = ["Show Playing Next", "Show Lyrics", "Show Status Bar", "Hide Sidebar", "Enter Full Screen"] + + +music_headers = NavBar( + Button("Music", cls=ButtonT.ghost+TextT.gray),DropDownNavContainer(Li(A("Music"),NavContainer(map(lambda x: MusicLi(*x), music_items)))), + Button("File", cls=ButtonT.ghost+TextT.gray), DropDownNavContainer(Li(A("File"), NavContainer(map(lambda x: MusicLi(*x), file_dd_items)))), + Button("Edit", cls=ButtonT.ghost+TextT.gray), DropDownNavContainer(Li(A("Edit")),NavContainer( + *map(lambda x: MusicLi(*x), edit_actions), + Li(A(DivFullySpaced("Smart Dictation",UkIcon("mic")))), + Li(A(DivFullySpaced("Emojis & Symbols",UkIcon("globe")))))), + Button("View", cls=ButtonT.ghost+TextT.gray),DropDownNavContainer(Li(A("View"),NavContainer(map(lambda x: MusicLi(x), view_dd_data)))), + brand=DivLAligned(H2("Purrify")) + ) + + + + +# music_headers = NavBarContainer( +# NavBarLSide( +# NavBarNav( +# Li(A("Music"),NavBarNavContainer(map(lambda x: MusicLi(*x), music_items))), +# Li(A("File"), NavBarNavContainer(map(lambda x: MusicLi(*x), file_dd_items))), +# Li(A("Edit")), +# NavBarNavContainer( +# *map(lambda x: MusicLi(*x), edit_actions), +# Li(A(DivFullySpaced("Smart Dictation",UkIcon("mic")))), +# Li(A(DivFullySpaced("Emojis & Symbols",UkIcon("globe"))))), +# Li(A("View"), +# NavBarNavContainer(map(lambda x: MusicLi(x), view_dd_data))), +# Li(A("Account"), +# NavBarNavContainer( +# NavHeaderLi("Switch Account"), +# *map(MusicLi, ("Andy", "Benoit", "Luis", "Manage Family", "Add Account"))))))) + + +def Album(title,artist): + img_url = 'https://ucarecdn.com/e5607eaf-2b2a-43b9-ada9-330824b6afd7/music1.webp' + return Div( + Div(cls="overflow-hidden rounded-md")(Img(cls="transition-transform duration-200 hover:scale-105", src=img_url)), + Div(cls='space-y-1')(Strong(title),P(artist,cls=TextT.muted))) + +listen_now_albums = (("Roar", "Catty Perry"), ("Feline on a Prayer", "Cat Jovi"),("Fur Elise", "Ludwig van Beethovpurr"),("Purrple Rain", "Prince's Cat")) + +made_for_you_albums = [("Like a Feline", "Catdonna"), + ("Livin' La Vida Purrda", "Ricky Catin"), + ("Meow Meow Rocket", "Elton Cat"), + ("Rolling in the Purr", "Catdelle"), + ("Purrs of Silence", "Cat Garfunkel"), + ("Meow Me Maybe", "Carly Rae Purrsen"),] + +music_content = (Div(H3("Listen Now"), cls="mt-6 space-y-1"), + Subtitle("Top picks for you. Updated daily."), + DividerLine(), + Grid(*[Album(t,a) for t,a in listen_now_albums], cls='gap-8'), + Div(H3("Made for You"), cls="mt-6 space-y-1"), + Subtitle("Your personal playlists. Updated daily."), + DividerLine(), + Grid(*[Album(t,a) for t,a in made_for_you_albums], cols_xl=6)) + +tabs = TabContainer( + Li(A('Music', href='#'), cls='uk-active'), + Li(A('Podcasts', href='#')), + Li(A('Live', cls='opacity-50'), cls='uk-disabled'), + uk_switcher='connect: #component-nav; animation: uk-animation-fade', + alt=True) + +def podcast_tab(): + return Div( + Div(cls='space-y-3 mt-6')( + H3("New Episodes"), + Subtitle("Your favorite podcasts. Updated daily.")), + Div(cls="uk-placeholder flex h-[450px] items-center justify-center rounded-md mt-4",uk_placeholder=True)( + DivVStacked(cls="space-y-6")( + UkIcon("microphone", 3), + H4("No episodes added"), + Subtitle("You have not added any podcasts. Add one below."), + Button("Add Podcast", cls=ButtonT.primary)))) + +discoved_data = [("play-circle","Listen Now"), ("binoculars", "Browse"), ("rss","Radio")] +library_data = [("play-circle", "Playlists"), ("music", "Songs"), ("user", "Made for You"), ("users", "Artists"), ("bookmark", "Albums")] +playlists_data = [("library","Recently Added"), ("library","Recently Played")] + +def MusicSidebarLi(icon, text): return Li(A(DivLAligned(UkIcon(icon), P(text)))) +sidebar = NavContainer( + NavHeaderLi(H3("Discover")), *[MusicSidebarLi(*o) for o in discoved_data], + NavHeaderLi(H3("Library")), *[MusicSidebarLi(*o) for o in library_data], + NavHeaderLi(H3("Playlists")),*[MusicSidebarLi(*o) for o in playlists_data], + cls=(NavT.primary,'space-y-3','pl-8')) + +@rt +def index(): + return Title("Music Example"),Container(music_headers, DividerSplit(), + Grid(sidebar, + Div(cls="col-span-4 border-l border-border")( + Div(cls="px-8 py-6")( + DivFullySpaced( + Div(cls="max-w-80")(tabs), + Button(cls=ButtonT.primary)(DivLAligned(UkIcon('circle-plus')),Div("Add music"))), + Ul(id="component-nav", cls="uk-switcher")( + Li(*music_content), + Li(podcast_tab())))), + cols_sm=1, cols_md=1, cols_lg=5, cols_xl=5)) + +serve() diff --git a/docs/vendor/monsterui/examples/playground.md b/docs/vendor/monsterui/examples/playground.md new file mode 100644 index 0000000..8559789 --- /dev/null +++ b/docs/vendor/monsterui/examples/playground.md @@ -0,0 +1,69 @@ +"""FrankenUI Playground Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * +from monsterui.all import * +from fasthtml.svg import * + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +preset_options = ["Grammatical Standard English", "Summarize for a 2nd grader", + "Text to command","Q&A","English to other languages","Parse unstructured data", + "Classification","Natural language to Python","Explain code","Chat","More examples"] + +def playground_navbar(): + save_modal = Modal( + ModalTitle("Save preset"), + P("This will save the current playground state as a preset which you can access later or share with others.",cls=("mt-1.5", TextPresets.muted_sm)), + LabelInput("Name", id="name"), + LabelInput("Description", id="description"), + ModalCloseButton("Save", cls=ButtonT.primary), + id="save") + + share_dd = Div(cls="space-y-6 p-4")( + H3("Share preset"), + P("Anyone who has this link and an OpenAI account will be able to view this.", cls=TextPresets.muted_sm), + Div(Input(value="https://platform.openai.com/playground/p/7bbKYQvsVkNmVb8NGcdUOLae?model=text-davinci-003", readonly=True), + Button(UkIcon('copy'), cls=(ButtonT.primary, "uk-drop-close",'mt-4')))) + + rnav = ( + Select(*Options(*preset_options), name='preset', optgroup_label="Examples", + placeholder='Load a preset', searchable=True, cls='h-9 w-[200px] lg:w-[300px]'), + Button("Save", cls=ButtonT.secondary, data_uk_toggle="#save"),save_modal, + Button("View Code", cls=ButtonT.secondary), + Button("Share", cls=ButtonT.secondary),DropDownNavContainer(share_dd), + Button(UkIcon(icon="ellipsis"), cls=ButtonT.secondary), + DropDownNavContainer( + Li(A("Content filter preferences")), + NavDividerLi(), + Li(A("Delete preset", cls="text-destructive")), + uk_dropdown="mode: click")) + + return NavBar(*rnav, brand=H4('Playground')) + +rsidebar = NavContainer( + Select( + Optgroup(map(Option,("text-davinci-003", "text-curie-001", "text-babbage-001", "text-ada-001")),label='GPT-3'), + Optgroup(map(Option,("code-davinci-002", "code-cushman-001")),label='Codex'), + label="Model", + searchable=True), + LabelRange(label='Temperature', value='12'), + LabelRange(label='Maximum Length', value='80'), + LabelRange(label='Top P', value='40'), + cls='space-y-6 mt-8') + +@rt +def index(): + navbar = playground_navbar() + main_content = Div( + Div(cls="flex-1")( + Textarea(cls="uk-textarea h-full p-4", placeholder="Write a tagline for an ice cream shop")), + cls="flex h-[700px] p-8 w-4/5") + + bottom_buttons = Div( + Button("Submit", cls=ButtonT.primary), + Button(UkIcon(icon="history"), cls=ButtonT.secondary), + cls="flex gap-x-2") + + return Title("Playground Example"),Div(navbar, Div(cls="flex w-full")(main_content, rsidebar), bottom_buttons) + +serve() diff --git a/docs/vendor/monsterui/examples/scrollspy.md b/docs/vendor/monsterui/examples/scrollspy.md new file mode 100644 index 0000000..09cfa08 --- /dev/null +++ b/docs/vendor/monsterui/examples/scrollspy.md @@ -0,0 +1,125 @@ +"MonsterUI Scrollspy Example application" + +from fasthtml.common import * +from monsterui.all import * +import random + +# Using the "slate" theme with Highlight.js enabled +hdrs = Theme.slate.headers(highlightjs=True) +app, rt = fast_app(hdrs=hdrs) + +################################ +### Example Data and Content ### +################################ +products = [ + {"name": "Laptop", "price": "$999"}, + {"name": "Smartphone", "price": "$599"} +] + +code_example = """ +# Python Code Example +def greet(name): + return f"Hello, {name}!" + +print(greet("World")) +""" +testimonials = [ + {"name": "Alice", "feedback": "Great products and excellent customer service!"}, + {"name": "Bob", "feedback": "Fast shipping and amazing quality!"}, + {"name": "Charlie", "feedback": "Amazing experience! Will definitely buy again."}, + {"name": "Diana", "feedback": "Affordable prices and great variety!"}, + {"name": "Edward", "feedback": "Customer support was very helpful."}, + {"name": "Fiona", "feedback": "Loved the design and quality!"} +] + +# Team members +team = [ + {"name": "Isaac Flath", "role": "CEO"}, + {"name": "Benjamin Clavié", "role": "AI Researcher"}, + {"name": "Alexis Gallagher", "role": "ML Engineer"}, + {"name": "Hamel Husain", "role": "Data Scientist"}, + {"name": "Austin Huang", "role": "Software Engineer"}, + {"name": "Benjamin Warner", "role": "Product Manager"}, + {"name": "Jonathan Whitaker", "role": "UX Designer"}, + {"name": "Kerem Turgutlu", "role": "DevOps Engineer"}, + {"name": "Curtis Allan", "role": "DevOps Engineer"}, + {"name": "Audrey Roy Greenfeld", "role": "Security Analyst"}, + {"name": "Nathan Cooper", "role": "Full Stack Developer"}, + {"name": "Jeremy Howard", "role": "CTO"}, + {"name": "Wayde Gilliam", "role": "Cloud Architect"}, + {"name": "Daniel Roy Greenfeld", "role": "Blockchain Expert"}, + {"name": "Tommy Collins", "role": "AI Ethics Researcher"} +] + + +def ProductCard(p,img_id=1): + return Card( + PicSumImg(w=500, height=100, id=img_id), + DivFullySpaced(H4(p["name"]), P(Strong(p["price"], cls=TextT.sm))), + Button("Details", cls=(ButtonT.primary, "w-full"))) + +def TestimonialCard(t,img_id=1): + return Card( + DivLAligned(PicSumImg(w=50, h=50, cls='rounded-full', id=img_id), H4(t["name"])), + P(Q((t["feedback"])))) + + +def TeamCard(m,img_id=1): + return Card( + DivLAligned( + PicSumImg(w=50, h=50, cls='rounded-full', id=img_id), + Div(H4(m["name"]), P(m["role"]))), + DivRAligned( + UkIcon('twitter', cls='w-5 h-5'), + UkIcon('linkedin', cls='w-5 h-5'), + UkIcon('github', cls='w-5 h-5'), + cls=TextT.gray+'space-x-2' + ), + cls='p-3') + +################################ +### Navigation and Scrollspy ### +################################ + +scrollspy_links = ( + A("Welcome", href="#welcome-section"), + A("Products", href="#products-section"), + A("Testimonials", href="#testimonials-section"), + A("Team", href="#team-section"), + A("Code Example", href="#code-section")) +@rt +def index(): + def _Section(*c, **kwargs): return Section(*c, cls='space-y-3 my-48',**kwargs) + return Container( + NavBar( + *scrollspy_links, + brand=DivLAligned(H3("Scrollspy Demo!"),UkIcon('rocket',height=30,width=30)), + sticky=True, uk_scrollspy_nav=True, + scrollspy_cls=ScrollspyT.bold), + NavContainer( + *map(Li, scrollspy_links), + uk_scrollspy_nav=True, + sticky=True, + cls=(NavT.primary,'pt-20 px-5 pr-10')), + Container( + # Notice the ID of each section corresponds to the `scrollspy_links` dictionary + # So in scollspy `NavContainer` the `href` of each `Li` is the ID of the section + DivCentered( + H1("Welcome to the Store!"), + Subtitle("Explore our products and enjoy dynamic code examples."), + id="welcome-section"), + _Section(H2("Products"), + Grid(*[ProductCard(p,img_id=i) for i,p in enumerate(products)], cols_lg=2), + id="products-section"), + _Section(H2("Testimonials"), + Slider(*[TestimonialCard(t,img_id=i) for i,t in enumerate(testimonials)]), + id="testimonials-section"), + _Section(H2("Our Team"), + Grid(*[TeamCard(m,img_id=i) for i,m in enumerate(team)], cols_lg=2, cols_max=3), + id="team-section"), + _Section(H2("Code Example"), + CodeBlock(code_example, lang="python"), + id="code-section")), + cls=(ContainerT.xl,'uk-container-expand')) + +serve() diff --git a/docs/vendor/monsterui/examples/tasks.md b/docs/vendor/monsterui/examples/tasks.md new file mode 100644 index 0000000..c057cfb --- /dev/null +++ b/docs/vendor/monsterui/examples/tasks.md @@ -0,0 +1,118 @@ +"""FrankenUI Tasks Example built with MonsterUI (original design by ShadCN)""" + +from fasthtml.common import * +from monsterui.all import * +from fasthtml.svg import * +import json + +app, rt = fast_app(hdrs=Theme.blue.headers()) + +def LAlignedCheckTxt(txt): return DivLAligned(UkIcon(icon='check'), P(txt, cls=TextPresets.muted_sm)) + +with open('data_/status_list.json', 'r') as f: data = json.load(f) +with open('data_/statuses.json', 'r') as f: statuses = json.load(f) + +def _create_tbl_data(d): + return {'Done': d['selected'], 'Task': d['id'], 'Title': d['title'], + 'Status' : d['status'], 'Priority': d['priority'] } + +data = [_create_tbl_data(d) for d in data] +page_size = 15 +current_page = 0 +paginated_data = data[current_page*page_size:(current_page+1)*page_size] + +priority_dd = [{'priority': "low", 'count': 36 }, {'priority': "medium", 'count': 33 }, {'priority': "high", 'count': 31 }] + +status_dd = [{'status': "backlog", 'count': 21 },{'status': "todo", 'count': 21 },{'status': "progress", 'count': 20 },{'status': "done",'count': 19 },{'status': "cancelled", 'count': 19 }] + +def create_hotkey_li(hotkey): return NavCloseLi(A(DivFullySpaced(hotkey[0], Span(hotkey[1], cls=TextPresets.muted_sm)))) + +hotkeys_a = (('Profile','⇧⌘P'),('Billing','⌘B'),('Settings','⌘S'),('New Team','')) +hotkeys_b = (('Logout',''), ) + +avatar_opts = DropDownNavContainer( + NavHeaderLi(P('sveltecult'),NavSubtitle('leader@sveltecult.com')), + NavDividerLi(), + *map(create_hotkey_li, hotkeys_a), + NavDividerLi(), + *map(create_hotkey_li, hotkeys_b),) + +def CreateTaskModal(): + return Modal( + Div(cls='p-6')( + ModalTitle('Create Task'), + P('Fill out the information below to create a new task', cls=TextPresets.muted_sm), + Br(), + Form(cls='space-y-6')( + Grid(Div(Select(*map(Option,('Documentation', 'Bug', 'Feature')), label='Task Type', id='task_type')), + Div(Select(*map(Option,('In Progress', 'Backlog', 'Todo', 'Cancelled', 'Done')), label='Status', id='task_status')), + Div(Select(*map(Option, ('Low', 'Medium', 'High')), label='Priority', id='task_priority'))), + TextArea(label='Title', placeholder='Please describe the task that needs to be completed'), + DivRAligned( + ModalCloseButton('Cancel', cls=ButtonT.ghost), + ModalCloseButton('Submit', cls=ButtonT.primary), + cls='space-x-5'))), + id='TaskForm') + +page_heading = DivFullySpaced(cls='space-y-2')( + Div(cls='space-y-2')( + H2('Welcome back!'),P("Here's a list of your tasks for this month!", cls=TextPresets.muted_sm)), + Div(DiceBearAvatar("sveltcult",8,8),avatar_opts)) + +table_controls =(Input(cls='w-[250px]',placeholder='Filter task'), + Button("Status"), + DropDownNavContainer(map(NavCloseLi,[A(DivFullySpaced(P(a['status']), P(a['count'])),cls='capitalize') for a in status_dd])), + Button("Priority"), + DropDownNavContainer(map(NavCloseLi,[A(DivFullySpaced(LAlignedCheckTxt(a['priority']), a['count']),cls='capitalize') for a in priority_dd])), + Button("View"), + DropDownNavContainer(map(NavCloseLi,[A(LAlignedCheckTxt(o)) for o in ['Title','Status','Priority']])), + Button('Create Task',cls=(ButtonT.primary, TextPresets.bold_sm), data_uk_toggle="target: #TaskForm")) + +def task_dropdown(): + return Div(Button(UkIcon('ellipsis')), + DropDownNavContainer( + map(NavCloseLi,[ + *map(A,('Edit', 'Make a copy', 'Favorite')), + A(DivFullySpaced(*[P(o, cls=TextPresets.muted_sm) for o in ('Delete', '⌘⌫')]))]))) +def header_render(col): + match col: + case "Done": return Th(CheckboxX(), shrink=True) + case 'Actions': return Th("", shrink=True) + case _: return Th(col, expand=True) + +def cell_render(col, val): + def _Td(*args,cls='', **kwargs): return Td(*args, cls=f'p-2 {cls}',**kwargs) + match col: + case "Done": return _Td(shrink=True)(CheckboxX(selected=val)) + case "Task": return _Td(val, cls='uk-visible@s') # Hide on small screens + case "Title": return _Td(val, cls='font-medium', expand=True) + case "Status" | "Priority": return _Td(cls='uk-visible@m uk-text-nowrap capitalize')(Span(val)) + case "Actions": return _Td(task_dropdown(), shrink=True) + case _: raise ValueError(f"Unknown column: {col}") + +task_columns = ["Done", 'Task', 'Title', 'Status', 'Priority', 'Actions'] + +tasks_table = Div(cls='mt-4')( + TableFromDicts( + header_data=task_columns, + body_data=paginated_data, + body_cell_render=cell_render, + header_cell_render=header_render, + sortable=True, + cls=(TableT.responsive, TableT.sm, TableT.divider))) + + +def footer(): + total_pages = (len(data) + page_size - 1) // page_size + return DivFullySpaced( + Div('1 of 100 row(s) selected.', cls=TextPresets.muted_sm), + DivLAligned( + DivCentered(f'Page {current_page + 1} of {total_pages}', cls=TextT.sm), + DivLAligned(*[UkIconLink(icon=i, button=True) for i in ('chevrons-left', 'chevron-left', 'chevron-right', 'chevrons-right')]))) + +tasks_ui = Div(DivFullySpaced(DivLAligned(table_controls), cls='mt-8'), tasks_table, footer()) + +@rt +def index(): return Container(page_heading, tasks_ui, CreateTaskModal()) + +serve() diff --git a/docs/vendor/monsterui/examples/ticket.md b/docs/vendor/monsterui/examples/ticket.md new file mode 100644 index 0000000..4d86c0f --- /dev/null +++ b/docs/vendor/monsterui/examples/ticket.md @@ -0,0 +1,95 @@ +"""MonsterUI Help Desk Example - Professional Dashboard with DaisyUI components""" +from fasthtml.common import * +from monsterui.all import * +from datetime import datetime + +app, rt = fast_app(hdrs=Theme.blue.headers(daisy=True)) + +def TicketSteps(step): + return Steps( + LiStep("Submitted", data_content="📝", + cls=StepT.success if step > 0 else StepT.primary if step == 0 else StepT.neutral), + LiStep("In Review", data_content="🔎", + cls=StepT.success if step > 1 else StepT.primary if step == 1 else StepT.neutral), + LiStep("Processing", data_content="⚙️", + cls=StepT.success if step > 2 else StepT.primary if step == 2 else StepT.neutral), + LiStep("Resolved", data_content="✅", + cls=StepT.success if step > 3 else StepT.primary if step == 3 else StepT.neutral), + cls="w-full") + +def StatusBadge(status): + styles = {'high': AlertT.error, 'medium': AlertT.warning,'low': AlertT.info} + alert_type = styles.get(status, AlertT.info) + return Alert(f"{status.title()} Priority", cls=(alert_type,"w-32 shadow-sm")) + +def TicketCard(id, title, description, status, step, department): + return Card( + CardHeader( + DivFullySpaced( + Div(H3(f"#{id}", cls=TextT.muted), + H4(title), + cls='space-y-2'), + StatusBadge(status))), + CardBody( + P(description, cls=(TextT.muted, "mb-6")), + DividerSplit(cls="my-6"), + TicketSteps(step), + DividerSplit(cls="my-6"), + DivFullySpaced( + Div(Strong("Department"), + P(department), + cls=('space-y-3', TextPresets.muted_sm)), + Div(Strong("Last Updated"), + P(Time(datetime.now().strftime('%b %d, %H:%M'))), + cls=('space-y-3', TextPresets.muted_sm)), + Button("View Details", cls=ButtonT.primary), + cls='mt-6')), + cls=CardT.hover) + +def NewTicketModal(): + return Modal( + ModalHeader(H3("Create New Support Ticket")), + ModalBody( + Alert( + DivLAligned(UkIcon("info"), Span("Please provide as much detail as possible to help us assist you quickly.")), + cls=(AlertT.info,"mb-4")), + Form( + Grid(LabelInput("Title", id="title", placeholder="Brief description of your issue"), + LabelSelect(Options("IT Support", "HR", "Facilities", "Finance"), label="Department", id="department")), + LabelSelect(Options("Low", "Medium", "High"), label="Priority Level", id="priority"), + LabelTextArea("Description", id="description", placeholder="Please provide detailed information about your issue"), + DivRAligned( + Button("Cancel", cls=ButtonT.ghost, data_uk_toggle="target: #new-ticket"), + Button(Loading(cls=LoadingT.spinner), "Submit Ticket", cls=ButtonT.primary, data_uk_toggle="target: #success-toast; target: #new-ticket")), + cls='space-y-8')), + id="new-ticket") + +@rt +def index(): + tickets = [ + {'id': "TK-1001", 'title': "Cloud Storage Access Error", + 'description': "Unable to access cloud storage with persistent authorization errors. Multiple users affected across marketing department.", + 'status': 'high', 'step': 2, 'department': 'IT Support'}, + {'id': "TK-1002", 'title': "Email Integration Issue", + 'description': "Exchange server not syncing with mobile devices. Affecting external client communications.", + 'status': 'medium', 'step': 1, 'department': 'IT Support'}, + {'id': "TK-1003", 'title': "Office Equipment Setup", + 'description': "New department printer needs configuration and network integration. Required for upcoming client presentation.", + 'status': 'low', 'step': 0, 'department': 'Facilities'} + ] + + return Title("Help Desk Dashboard"), Container( + Section( + DivFullySpaced( + H2("Active Tickets"), + Button(UkIcon("plus-circle", cls="mr-2"), "New Ticket", cls=ButtonT.primary, data_uk_toggle="target: #new-ticket"), + cls='mb-8'), + Grid(*[TicketCard(**ticket) for ticket in tickets], cols=1), + cls="my-6"), + NewTicketModal(), + Toast(DivLAligned(UkIcon('check-circle', cls='mr-2'), "Ticket submitted successfully! Our team will review it shortly."), id="success-toast", alert_cls=AlertT.success, cls=(ToastHT.end, ToastVT.bottom)), + Loading(htmx_indicator=True, type=LoadingT.dots, cls="fixed top-0 right-0 m-4"), + cls="mx-auto max-w-7xl" + ) + +serve() \ No newline at end of file diff --git a/docs/vendor/monsterui/examples/tutorial_layout.md b/docs/vendor/monsterui/examples/tutorial_layout.md new file mode 100644 index 0000000..46e5ac5 --- /dev/null +++ b/docs/vendor/monsterui/examples/tutorial_layout.md @@ -0,0 +1,449 @@ +# MonterUI Page Layout Guide + +This guide will discuss 3 tools for laying out your app pages, Grid, Flexbox, and Columns. This page will discuss the strengths and when to use each individually, and then a section for how to combine them for more complex layouts at the end. + +> Note: This guide is designed to get you started building layouts quickly, not to teach you all the details needed to build every possible custom layout with pixel-perfect control. To get more detailed and lower-level control, explore the tailwind docs. + +This guide is for creating flexible layouts you envision, but does not discuss responsiveness to make different layouts that are both mobile and desktop friendly. Stay tunes for a responsiveness guide that will help with that! + +# Grid + +Grids are best for regular predictable layouts with lots of the same shape of things that may need to change a lot for different screen sizes. I think the best way to see what it can do is to see a bunch of examples, so here they are! + +## Minimal Image Cards + +This is a minimal example of a grid that just shows image and text. This is the foundation for many more complex layouts so make sure to understand what's going on here first before moving on! + +A grid lays things out in a...grid. As you can see, we have evenly sized cards by default. + +See Source + +See Output + +Image 0 + +Image 1 + +Image 2 + +Image 3 + +Image 4 + +Image 5 + +[code] + + def picsum_img(seed): return Img(src=f'https://picsum.photos/300/200?random={seed}') + + Grid(*[Card(picsum_img(i),P(f"Image {i}")) for i in range(6)]) +[/code] + +## Dashboard Example + +However, they don't have to be evenly sized! By providing `row-span-{int}` and `col-span-{int}` we can control how many rows or columns specific grid elements take up. By doing this, we can create a grid that has lots of different shapes and types of elements. + +Let's look at a dashboard layout at an examples of this. + +See Source + +See Output + +### SideBar + +Range For Filters + +A search Bar + +Choose Product Line + +Product Line AProduct Line BProduct Line CProduct Line D + +Include Inactive Users + +Include Users without order + +Include Users without email + +Total Users + +### 1,234 + +Active Now + +### 342 + +Revenue + +### $45,678 + +Conversion + +### 2.4% + +### Monthly Revenue + +Chart Goes Here + +### User Growth + +Chart Goes Here + +[code] + + def StatCard(title, value, color='primary'): + "A card with a statistics. Since there is no row/col span class it will take up 1 slot" + return Card(P(title, cls=TextPresets.muted_sm), H3(value, cls=f'text-{color}'),) + + stats = [StatCard(*data) for data in [ + ("Total Users", "1,234", "blue-600"), + ("Active Now", "342", "green-600"), + ("Revenue", "$45,678", "purple-600"), + ("Conversion", "2.4%", "amber-600")]] + + def ChartCard(title): + "A card for a chart. col-span-2 means it will take up 2 columns" + return Div(cls="col-span-2")( + Card(H3(title),Div("Chart Goes Here", cls="h-64 uk-background-muted"))) + chart_cards = [ChartCard(title) for title in ("Monthly Revenue", "User Growth")] + + + sidebar = Form( + H3("SideBar"), + LabelRange("Range For Filters", min=0, max=100), + LabelInput("A search Bar"), + LabelSelect(map(Option, ["Product Line A", "Product Line B", "Product Line C", "Product Line D"]), + label="Choose Product Line"), + LabelCheckboxX("Include Inactive Users"), + LabelCheckboxX("Include Users without order"), + LabelCheckboxX("Include Users without email"), + # This sidebar will take up 2 rows b/c of row-span-2 + cls='row-span-2 space-y-5' + ) + + Container(Grid(sidebar, *stats, *chart_cards, cols=5)) +[/code] + +# Flexbox + +Using Grid for the overall layout, and flex for the individual elements is a powerful pattern. With `MonsterUI` you can do quite a bit without knowing anything about flexbox, which is what will be taught here. + +However, flexbox is well worth learning about it in more detail. You will run into situations where you need more flexbox knowledge than is covered here to build your vision. Thankfully you can get that knowledge by playing a fantastic tutorial game called FlexBox Froggy! + +## Forms + +Often you want to stack things horizontally. You can use the `DivHStacked` component to do this. + +`DivHStacked` is a helper function for flexbox and creates a div with these classes by default `cls=(FlexT.block, FlexT.row, FlexT.middle, 'space-x-4')`. + +See Source + +See Output + +### Form with Input Groups + +Search Users + +Filter Tags + +Email List + +SubmitCancel + +[code] + + def InputGroup(label, placeholder='', button_text='Submit', cls=''): + # Div H Stacked makes the label and input show up on the same row instead of putting the input on a newline + return DivHStacked( + FormLabel(label, cls='whitespace-nowrap'), + Input(placeholder=placeholder)) + + Container( + H3("Form with Input Groups"), + Form(cls='space-y-4')( + InputGroup("Search Users", "Enter username..."), + InputGroup("Filter Tags", "Add tags...", "Add"), + InputGroup("Email List", "Enter email...", "Subscribe"), + Div(*( Button(UkIcon(icon, cls='mr-2'), text) for icon, text in [("rocket", "Submit"), ("circle-x", "Cancel")]), cls='space-x-4'))) +[/code] + +## Avatar + +You can use this same `DivHStacked` to align things like text next to images. And you can use `DivVStacked` to stack things vertically to create design structures you like. `DivVStacked` works by using `cls=(FlexT.block,FlexT.column,FlexT.middle)` + +See Source + +See Output + +John Doe + +[email protected] + ++1-123-456-7890 + +[code] + + # DivHStacked makes the a single row so text is to on same line as avatar + DivHStacked( + DiceBearAvatar("user"), + # DivVStacked stacks things vertically together and centers it with flex + DivVStacked( + P("John Doe", cls=TextT.lg), + P("[email protected]", cls=TextT.muted), + P("+1-123-456-7890"), cls=TextT.muted)) +[/code] + +## Pricing Card + +These can be combined with icons and other styling to create larger components like a pricing card. + +See Source + +See Output + +## Pro Plan + +### $99 + +per month + + * Unlimited users + + * 24/7 priority support + + * Custom branding options + + * Advanced analytics dashboard + + * Full API access + + * Priority request queue + +Subscribe Now + +[code] + + features = [ + "Unlimited users", + "24/7 priority support", + "Custom branding options", + "Advanced analytics dashboard", + "Full API access", + "Priority request queue" + ] + + + def PricingCard(plan, price, features): + "Create a polished pricing card with consistent styling" + return Card( + DivVStacked( # Center and veritcally stack the plan name and price + H2(plan), + H3(price, cls='text-primary'), + P('per month',cls=TextT.muted), + cls='space-y-1'), + # DivHStacked makes green check and feature Li show up on same row instead of newline + Ul(*[DivHStacked(UkIcon('check', cls='text-green-500 mr-2'), Li(feature)) for feature in features], + cls='space-y-4'), + Button("Subscribe Now", cls=(ButtonT.primary, 'w-full'))) + + DivVStacked(PricingCard("Pro Plan", "$99", features)) +[/code] + +## Footer + +Or you can combine things to make advanced footers that have titles, organized links, and icons! + +In this example we add another flex helper function, `DivFullySpaced`. `DivFullySpaced` is a flex class that puts as much space between items as possible + +See Source + +See Output + +### Company Name + +* * * + +#### Company + +AboutBlogCareersPress Kit + +#### Resources + +DocumentationHelp CenterStatusContact Sales + +#### Legal + +Terms of ServicePrivacy PolicyCookie SettingsAccessibility + +* * * + +© 2024 Company Name. All rights reserved. + +[code] + + def FooterLinkGroup(title, links): + # DivVStacked centers and makes title and each link stack vertically + return DivVStacked( + H4(title), + *[A(text, href=f"#{text.lower().replace(' ', '-')}", cls=TextT.muted) for text in links]) + + company = ["About", "Blog", "Careers", "Press Kit"] + resource = ["Documentation", "Help Center", "Status", "Contact Sales"] + legal = ["Terms of Service", "Privacy Policy", "Cookie Settings", "Accessibility"] + + Container(cls='uk-background-muted py-12')(Div( + # Company Name and social icons will be on the same row with as much sapce between as possible + DivFullySpaced( + H3("Company Name"), + # DivHStacked makes the icons be on the same row in a group + DivHStacked(*[UkIcon(icon, cls=TextT.lead) for icon in + ['twitter', 'facebook', 'github', 'linkedin']])), + DividerLine(), + DivFullySpaced( # Each child will be spread out as much as possible based on number of children + FooterLinkGroup("Company", company), + FooterLinkGroup("Resources", resource), + FooterLinkGroup("Legal", legal)), + DividerLine(), + P("© 2024 Company Name. All rights reserved.", cls=TextT.lead+TextT.sm), + cls='space-y-8 p-8')) +[/code] + +## Dashboard + +See Source + +See Output + +## Welcome back, Isaac! + +Here's what's happening with your projects today. + +Total Projects + +### 12 + ++2.5% from last month + +Hours Logged + +### 164 + ++12.3% from last month + +Tasks Complete + +### 64% + +-4.1% from last month + +Team Velocity + +### 23 + ++8.4% from last month + +### Recent Activity + +Sarah Chen completed Project Alpha deployment + +2h ago + +James Wilson commented on Project Beta + +4h ago + +Maria Garcia uploaded new design files + +6h ago + +Alex Kumar started Sprint Planning + +8h ago + +[code] + + def StatsCard(label, value, change): + color = 'green' if change[0] == '+' else 'red' + return Card(DivVStacked( # Stacks vertically and centers all elements + P(label, cls=TextPresets.muted_sm), + H3(value), + P(f"{change}% from last month", cls=f"text-{color}-600 text-sm"))) + + def RecentActivity(user, action, time): + return DivHStacked( # Makes Avatar and text be on same row + DiceBearAvatar(user, h=8, w=8), + P(f"{user} {action}", cls="flex-1"), + P(time, cls=TextPresets.muted_sm)) + + DivVStacked( # Centers the entire dashboard layout + # Page header + DivVStacked( # Stacks vertically and centers the title/subtitle + H2("Welcome back, Isaac!"), + P("Here's what's happening with your projects today.",cls=TextT.muted)), + + # DivHStacked puts all the stats cards on the same row + DivHStacked(*(StatsCard(label, value, change) + for label, value, change in [ + ("Total Projects", "12", "+2.5"), + ("Hours Logged", "164", "+12.3"), + ("Tasks Complete", "64%", "-4.1"), + ("Team Velocity", "23", "+8.4")] + )), + + # Recent activity + Card(*(RecentActivity(user, action, time) + for user, action, time in [ + ("Sarah Chen", "completed Project Alpha deployment", "2h ago"), + ("James Wilson", "commented on Project Beta", "4h ago"), + ("Maria Garcia", "uploaded new design files", "6h ago"), + ("Alex Kumar", "started Sprint Planning", "8h ago")]), + header=H3("Recent Activity"), + ), + cls="space-y-6" + ) +[/code] + +## Columns + +Columns are a great for sections that have a lot of text. + +See Source + +See Output + +# Lorem Ipsum + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium. + +Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor. + +[code] + + Container( + H1("Lorem Ipsum", cls="text-center mb-8"), + + # Use 2 columns for the main content + Div(cls="columns-2 gap-12")( + P("""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad + minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat."""), + + DivCentered(cls='mt-8')( + P("""Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur.""", + cls=(TextT.lg, TextT.bold, TextT.center, TextT.italic, "text-primary"))), + + P("""Excepteur sint occaecat cupidatat non proident, sunt in culpa qui + officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde + omnis iste natus error sit voluptatem accusantium doloremque laudantium."""), + + P("""Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit + aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem + sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor."""))) +[/code] + diff --git a/docs/vendor/monsterui/examples/tutorial_spacing.md b/docs/vendor/monsterui/examples/tutorial_spacing.md new file mode 100644 index 0000000..d3f752d --- /dev/null +++ b/docs/vendor/monsterui/examples/tutorial_spacing.md @@ -0,0 +1,262 @@ +# Padding & Margin & Spacing, Oh my! (MonsterUI Spacing Guide) + +This guide will cover some essentials about how to properly space apps and what the differences are between: + + * Padding + * Margin + * Spacing + * Gap + +Manipulating the space between components can make a huge difference to the percieved quality of the page. Being able to tweak the spacing can have a big impact! + +> Tip: I find it works best to get everything on the page without adjusting spacing much, and adjusting spacing at the end. + +## Abreviations: + +First a few abbreviations that are helpful to know with tailwind (and a convention we follow in `MonsterUI`). + + * `t`, `b`, `l`, `r` = top, bottom, left, right + * `p`, `m` = padding, margin + * `x`, `y` = horizontal, vertical + +That means: + + * `mt` means margin on top of the element + * `px` means padding on the x axis (both left and right) + * `space-y` means apply spacing on the y axis (both top and bottom) + +## Padding vs Margin + +Margin applies space to the left of the component, and padding applies space on the left inside of the component. + +Please reference the example with cards below: + + * `ml-20` applies space to the left of the card (outside the card) + * `pl-20` applies space on the left inside of the card (inside the card) + +This means that if you want to move the whole thing but keep the actual container unchanged, use margin. If you want to change the container by adding space inside of it, use padding. + +See Source + +See Output + +#### A Simple Card with ml-20 + +#### A Simple Card with pl-20 + +[code] + + Grid( + Card(H4("A Simple Card with ml-20",style='background-color: red'), + cls='ml-20'), + Card(H4("A Simple Card with pl-20", style='background-color: red'), + cls='pl-20')) +[/code] + +## Space vs gap + +Spacing and gap are both about setting the space between components. + + * Spacing applies margin to every element except for the first element in a group. + * Gap creates a gap between every element in flexbox elements and grids. + +> Rule of thumb: Use Gap when using grids. + +Let's take a look at some grid examples. + +#### Grid + +See Source + +See Output + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +[code] + + Grid( + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + cls='') +[/code] + +#### Grid with gap + +See Source + +See Output + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +[code] + + Grid( + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + cls='gap-4') +[/code] + +#### Grid with space + +See Source + +See Output + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +#### A Simple Card + +[code] + + Grid( + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + Card(H4("A Simple Card")), + cls='space-x-4 space-y-4') +[/code] + +### Grid with no gap or space + +The first example has no gap or not space applied. As expected this means the cards are flush with each other. Often this is not what you want, because a little space between cards looks much nicer. + +### Grid with gap + +The second example has the same grid but with gap applied. As youc an see, this gives constent space between all elements of the grid looks great! + +### Grid with space + +The third example has the same grid but with space applied. As you can see, it's not really what we want. However it's a really good illustration of how space works so let's notice a few things about it: + +**X Axis** + + * The first card is flush with the left side of the page (no margin) + * The card below it isn't flush with the left side of the page (has margin) + +**Y Axis** + + * The first card is flush with the heading immediately above it (no margin) + * The card top it's right isn't flush with the heading above it (has margin) + +So `space` applies margin to every element except for the first element in a group! + +> Tip: Use your browser developer tools to inspect the examples + +Next, let's look at a form example where `gap` isn't a good choice but `space` works beautifully! + +See Source + +See Output + +### My Form + +Name + +Phone + +Email + +[code] + + Form(DivCentered(H3("My Form")), + LabelInput("Name"), + Grid(LabelInput("Phone"), LabelInput("Email"), cols=2), + cls='') +[/code] + +See Source + +See Output + +### My Form with gap + +Name + +Phone + +Email + +[code] + + Form(DivCentered(H3("My Form with gap")), + LabelInput("Name"), + Grid(LabelInput("Phone"), LabelInput("Email"), cols=2), + cls='gap-y-5') +[/code] + +See Source + +See Output + +### My Form with Spacing + +Name + +Phone + +Email + +[code] + + Form(DivCentered(H3("My Form with Spacing")), + LabelInput("Name"), + Grid(LabelInput("Phone"), LabelInput("Email"), cols=2), + cls='space-y-5') +[/code] + +### Form with no gap or space + +The top form looks a bit scrunched with defaults, but it's certainly passable. There is a bit of a space between the label and it's associated input because of the defaults in MonsterUI. + +### Form with gap + +The second form with gap is identical to the first. Because we're not in a flex element or a grid, it doesn't do anything at all! + +### Form with space + +`Space-y-5` adds vertical space between each child which really spreads out the form for a nice aesthetic. If you recall from the grid example, it does not apply this margin to the first element - but in this situation (and many others) we do not want the spacing above the top element (heading) to be the same as the spacing between the form elements. + +> Tip: Use your browser developer tools to inspect the examples + +# Further reading + +For further reading, check out the Tailwind CSS guide, which other users have found to be a useful as an additional guide. + diff --git a/docs/vendor/monsterui/llms.txt b/docs/vendor/monsterui/llms.txt new file mode 100644 index 0000000..dd63006 --- /dev/null +++ b/docs/vendor/monsterui/llms.txt @@ -0,0 +1,44 @@ +# MonsterUI Documentation + +> MonsterUI is a python library which brings styling to python for FastHTML apps. + +## API Reference +- [API List](apilist.txt): Complete API Reference + +## Examples +- [Dashboard](examples/dashboard.md): FrankenUI Dashboard Example built with MonsterUI (original design by ShadCN) +- [Forms](examples/forms.md): FrankenUI Forms Example built with MonsterUI (original design by ShadCN) +- [Auth](examples/auth.md): FrankenUI Auth Example built with MonsterUI (original design by ShadCN) +- [Mail](examples/mail.md): FrankenUI Mail Example built with MonsterUI (original design by ShadCN) +- [Tasks](examples/tasks.md): FrankenUI Tasks Example built with MonsterUI (original design by ShadCN) +- [Music](examples/music.md): FrankenUI Music Example build with MonsterUI (Original design by ShadCN) +- [Scrollspy](examples/scrollspy.md): MonsterUI Scrollspy Example application +- [Cards](examples/cards.md): FrankenUI Cards Example built with MonsterUI (original design by ShadCN) +- [Ticket](examples/ticket.md): MonsterUI Help Desk Example - Professional Dashboard with DaisyUI components +- [Playground](examples/playground.md): FrankenUI Playground Example built with MonsterUI (original design by ShadCN) + +## Optional +- [Accordion | Link](api_ref/docs_accordion_link.md): Accordion API Reference +- [Button | Link](api_ref/docs_button_link.md): Buttons & Links API Reference +- [Cards](api_ref/docs_cards.md): Cards API Reference +- [Charts](api_ref/docs_charts.md): Charts API Reference +- [Containers](api_ref/docs_containers.md): Articles, Containers & Sections API Reference +- [Dividers](api_ref/docs_dividers.md): Dividers API Reference +- [Forms](api_ref/docs_forms.md): Forms and User Inputs API Reference +- [Html](api_ref/docs_html.md): HTML Styling API Reference +- [Icons | Images](api_ref/docs_icons_images.md): Icons & Images API Reference +- [Layout](api_ref/docs_layout.md): Layout (Flex and Grid) API Reference +- [Lightbox](api_ref/docs_lightbox.md): Lightbox API Reference +- [Lists](api_ref/docs_lists.md): Lists API Reference +- [Loading](api_ref/docs_loading.md): Loading Indicators API Reference +- [Markdown](api_ref/docs_markdown.md): Markdown + automated HTML styling API Reference +- [Modals](api_ref/docs_modals.md): Modals API Reference +- [Navigation](api_ref/docs_navigation.md): Navigation (Nav, NavBar, Tabs, etc.) API Reference +- [Notifications](api_ref/docs_notifications.md): Alerts & Toasts API Reference +- [Sliders](api_ref/docs_sliders.md): Carousel Sliders API Reference +- [Steps](api_ref/docs_steps.md): Steps API Reference +- [Tables](api_ref/docs_tables.md): Tables API Reference +- [Theme | Headers](api_ref/docs_theme_headers.md): Theme and Headers API Reference +- [Typography](api_ref/docs_typography.md): Typography API Reference +- [Layout](examples/tutorial_layout.md): MonsterUI Page Layout Guide +- [Spacing](examples/tutorial_spacing.md): Padding & Margin & Spacing, Oh my! (MonsterUI Spacing Guide) \ No newline at end of file diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f9e998f --- /dev/null +++ b/flake.nix @@ -0,0 +1,88 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + + # Python with custom packages + python = pkgs.python313.override { + self = python; + packageOverrides = pyfinal: pyprev: { + # Leaf packages (no custom dependencies) + mistletoe = pyfinal.callPackage ./packages/mistletoe.nix { }; + sse-starlette = pyfinal.callPackage ./packages/sse-starlette.nix { }; + fastcore = pyfinal.callPackage ./packages/fastcore.nix { }; + + # Second level (depend on leaf packages) + apswutils = pyfinal.callPackage ./packages/apswutils.nix { }; + fastlite = pyfinal.callPackage ./packages/fastlite.nix { }; + fastmigrate = pyfinal.callPackage ./packages/fastmigrate.nix { }; + + # Top level (depend on second level) + python-fasthtml = pyfinal.callPackage ./packages/python-fasthtml.nix { }; + monsterui = pyfinal.callPackage ./packages/monsterui.nix { }; + }; + }; + + # Shared Python environment for both dev and Docker + pythonEnv = python.withPackages (ps: with ps; [ + # From nixpkgs + uvicorn + starlette + httpx + pydantic + pydantic-settings + anyio + beautifulsoup4 + python-dateutil + oauthlib + itsdangerous + python-multipart + lxml + apsw + packaging + python-ulid + xxhash + + # Custom packages + fastcore + apswutils + fastlite + fastmigrate + sse-starlette + python-fasthtml + monsterui + mistletoe + + # Dev-only (not needed in Docker, but fine to include) + pytest + pytest-xdist + ruff + ]); + in + { + packages.${system} = { + dockerImage = import ./docker.nix { inherit pkgs pythonEnv python; }; + }; + + devShells.${system}.default = pkgs.mkShell { + buildInputs = [ + pythonEnv + + # Tools + pkgs.jq + pkgs.sqlite + pkgs.skopeo # For pushing Docker images + ]; + + shellHook = '' + echo "AnimalTrack development environment ready!" + echo "Run 'animaltrack serve' to start the app" + ''; + }; + }; +} diff --git a/packages/apswutils.nix b/packages/apswutils.nix new file mode 100644 index 0000000..2c094cb --- /dev/null +++ b/packages/apswutils.nix @@ -0,0 +1,34 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + fastcore, + apsw, +}: + +buildPythonPackage rec { + pname = "apswutils"; + version = "0.1.0"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:0n5yvm132rdzr7ixp0i07n8hk0zpwijjbnffrmr0z2khkljnnmsc"; + }; + + build-system = [ setuptools ]; + + dependencies = [ + fastcore + apsw + ]; + + doCheck = false; + + meta = { + description = "Utilities for working with APSW"; + homepage = "https://github.com/AnswerDotAI/apswutils"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/fastcore.nix b/packages/fastcore.nix new file mode 100644 index 0000000..f2ee026 --- /dev/null +++ b/packages/fastcore.nix @@ -0,0 +1,30 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + packaging, +}: + +buildPythonPackage rec { + pname = "fastcore"; + version = "1.8.16"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:1jc47l215dfc5d24pcg5zch25ingxpy1yd4afb2gndlbv08mr38d"; + }; + + build-system = [ setuptools ]; + + dependencies = [ packaging ]; + + doCheck = false; + + meta = { + description = "Python supercharged for fastai development"; + homepage = "https://github.com/fastai/fastcore"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/fastlite.nix b/packages/fastlite.nix new file mode 100644 index 0000000..c71556f --- /dev/null +++ b/packages/fastlite.nix @@ -0,0 +1,34 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + fastcore, + apswutils, +}: + +buildPythonPackage rec { + pname = "fastlite"; + version = "0.2.1"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:1hi5i68rpffghp254gpv332baqfkdl41j9zzxrdig5yicbm0p3qb"; + }; + + build-system = [ setuptools ]; + + dependencies = [ + fastcore + apswutils + ]; + + doCheck = false; + + meta = { + description = "A bit of extra usability for sqlite"; + homepage = "https://github.com/AnswerDotAI/fastlite"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/fastmigrate.nix b/packages/fastmigrate.nix new file mode 100644 index 0000000..1d449a5 --- /dev/null +++ b/packages/fastmigrate.nix @@ -0,0 +1,30 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + apswutils, +}: + +buildPythonPackage rec { + pname = "fastmigrate"; + version = "0.4.0"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:02pbmw3bxs4cdr1ni7z9832m8dwb12nmbjmmg5j55pczxjc2656v"; + }; + + build-system = [ setuptools ]; + + dependencies = [ apswutils ]; + + doCheck = false; + + meta = { + description = "Database migration tool for APSW/SQLite"; + homepage = "https://github.com/AnswerDotAI/fastmigrate"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/mistletoe.nix b/packages/mistletoe.nix new file mode 100644 index 0000000..fd0681b --- /dev/null +++ b/packages/mistletoe.nix @@ -0,0 +1,30 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, +}: + +buildPythonPackage rec { + pname = "mistletoe"; + version = "1.5.0"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:0i2d4v9rrk6275pjqf816izhv79pbinifdg0hb45w1zqigk3pdv8"; + }; + + build-system = [ setuptools ]; + + # No runtime dependencies + dependencies = [ ]; + + doCheck = false; + + meta = { + description = "A fast, extensible Markdown parser in pure Python"; + homepage = "https://github.com/miyuchina/mistletoe"; + license = lib.licenses.mit; + }; +} diff --git a/packages/monsterui.nix b/packages/monsterui.nix new file mode 100644 index 0000000..a01e68b --- /dev/null +++ b/packages/monsterui.nix @@ -0,0 +1,38 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + python-fasthtml, + fastcore, + lxml, + mistletoe, +}: + +buildPythonPackage rec { + pname = "monsterui"; + version = "1.0.34"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256:03krc10adsx6xf09lxw8gs8lq7jja8yyhw5hjrgss24fgal6163b"; + }; + + build-system = [ setuptools ]; + + dependencies = [ + python-fasthtml + fastcore + lxml + mistletoe + ]; + + doCheck = false; + + meta = { + description = "Beautiful UI components for FastHTML"; + homepage = "https://github.com/AnswerDotAI/MonsterUI"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/python-fasthtml.nix b/packages/python-fasthtml.nix new file mode 100644 index 0000000..cdb8ccf --- /dev/null +++ b/packages/python-fasthtml.nix @@ -0,0 +1,51 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + fastcore, + python-dateutil, + starlette, + oauthlib, + itsdangerous, + uvicorn, + httpx, + fastlite, + python-multipart, + beautifulsoup4, +}: + +buildPythonPackage rec { + pname = "python-fasthtml"; + version = "0.12.35"; + pyproject = true; + + src = fetchPypi { + pname = "python_fasthtml"; + inherit version; + hash = "sha256:15iq7zykqa1i7mkir2wzg0js2dvjplarq1jzb9igj61qb5f25p1l"; + }; + + build-system = [ setuptools ]; + + dependencies = [ + fastcore + python-dateutil + starlette + oauthlib + itsdangerous + uvicorn + httpx + fastlite + python-multipart + beautifulsoup4 + ]; + + doCheck = false; + + meta = { + description = "FastHTML - Modern web applications in pure Python"; + homepage = "https://github.com/AnswerDotAI/fasthtml"; + license = lib.licenses.asl20; + }; +} diff --git a/packages/sse-starlette.nix b/packages/sse-starlette.nix new file mode 100644 index 0000000..6d1aaa0 --- /dev/null +++ b/packages/sse-starlette.nix @@ -0,0 +1,31 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + anyio, +}: + +buildPythonPackage rec { + pname = "sse-starlette"; + version = "3.0.3"; + pyproject = true; + + src = fetchPypi { + pname = "sse_starlette"; + inherit version; + hash = "sha256:0wdr348bi3hd9ivbvcvsahxs441rn1vai30ck7m00qp18y3v1kw8"; + }; + + build-system = [ setuptools ]; + + dependencies = [ anyio ]; + + doCheck = false; + + meta = { + description = "SSE plugin for Starlette"; + homepage = "https://github.com/sysid/sse-starlette"; + license = lib.licenses.bsd3; + }; +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..001f035 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +requires = ["setuptools>=68.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "animaltrack" +version = "0.1.0" +description = "Event-sourced animal tracking for poultry farm management" +readme = "README.md" +requires-python = ">=3.11" +authors = [ + {name = "ppetru"} +] + +dependencies = [ + "python-fasthtml>=0.12.0", + "monsterui>=1.0.0", + "uvicorn>=0.24.0", + "fastmigrate>=0.4.0", + "pydantic>=2.0.0", + "pydantic-settings>=2.0.0", + "python-ulid>=2.0.0", + "xxhash>=3.0.0", + "python-multipart>=0.0.6", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "pytest-xdist>=3.5.0", + "ruff>=0.1.0", +] + +[project.scripts] +animaltrack = "animaltrack.cli:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "N", "W", "UP"] +ignore = ["E501"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = "test_*.py" +python_classes = "Test*" +python_functions = "test_*" +addopts = "--durations=20" diff --git a/src/animaltrack/__init__.py b/src/animaltrack/__init__.py new file mode 100644 index 0000000..d1815c6 --- /dev/null +++ b/src/animaltrack/__init__.py @@ -0,0 +1,4 @@ +# ABOUTME: AnimalTrack - Event-sourced animal tracking for poultry farm management. +# ABOUTME: Main package initialization. + +__version__ = "0.1.0" diff --git a/src/animaltrack/cli.py b/src/animaltrack/cli.py new file mode 100644 index 0000000..7839a06 --- /dev/null +++ b/src/animaltrack/cli.py @@ -0,0 +1,61 @@ +# ABOUTME: Command-line interface for AnimalTrack. +# ABOUTME: Provides migrate, seed, and serve commands. + +import argparse +import sys + + +def main(): + """Main entry point for the AnimalTrack CLI.""" + parser = argparse.ArgumentParser( + prog="animaltrack", + description="AnimalTrack - Event-sourced animal tracking for poultry farm management", + ) + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # migrate command + migrate_parser = subparsers.add_parser("migrate", help="Run database migrations") + migrate_parser.add_argument( + "--to", type=str, help="Migrate to specific version", default=None + ) + + # create-migration command + create_parser = subparsers.add_parser( + "create-migration", help="Create a new migration file" + ) + create_parser.add_argument("description", help="Description for the migration") + + # seed command + subparsers.add_parser("seed", help="Load seed data") + + # serve command + serve_parser = subparsers.add_parser("serve", help="Start the web server") + serve_parser.add_argument("--port", type=int, default=5000, help="Port to listen on") + serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to bind to") + + args = parser.parse_args() + + if args.command is None: + parser.print_help() + sys.exit(1) + + if args.command == "migrate": + print(f"Running migrations (to={args.to})...") + # TODO: Implement migration runner + print("Migration not yet implemented") + elif args.command == "create-migration": + print(f"Creating migration: {args.description}") + # TODO: Implement migration creation + print("Migration creation not yet implemented") + elif args.command == "seed": + print("Loading seed data...") + # TODO: Implement seeder + print("Seeding not yet implemented") + elif args.command == "serve": + print(f"Starting server on {args.host}:{args.port}...") + # TODO: Implement server + print("Server not yet implemented") + + +if __name__ == "__main__": + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..bfa2313 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# ABOUTME: Test package for AnimalTrack. +# ABOUTME: Contains unit, integration, and E2E tests. diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5f22522 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,22 @@ +# ABOUTME: Pytest configuration and fixtures for AnimalTrack tests. +# ABOUTME: Provides database fixtures, test clients, and common utilities. + +import pytest +import tempfile +import os + + +@pytest.fixture +def temp_db_path(): + """Create a temporary database file path for testing.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db_path = f.name + yield db_path + # Cleanup + if os.path.exists(db_path): + os.unlink(db_path) + # Also clean up WAL files if they exist + for suffix in ["-shm", "-wal", "-journal"]: + wal_path = db_path + suffix + if os.path.exists(wal_path): + os.unlink(wal_path)