Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,8 @@ examples/accessible-showcase-alpha6/fonts/*.ttf binary
docs/assets/accessible-showcase-alpha6/** -text whitespace=cr-at-eol
docs/assets/accessible-showcase-alpha6/*.pdf binary
docs/assets/accessible-showcase-alpha6/fonts/*.ttf binary

# The Jupyter download fix is a new notebook revision; old assets stay immutable.
examples/component-notebook-alpha6-r1/** text eol=lf
docs/assets/component-notebook-alpha6-r1/** -text
docs/assets/component-notebook-alpha6-r1/*.pdf binary
9 changes: 8 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
channel: [alpha3, alpha5, alpha6]
channel: [alpha3, alpha5, alpha6, alpha6-r1]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
Expand All @@ -87,6 +87,13 @@ jobs:
python -m pip install --only-binary=:all: -r tools/component-notebook-requirements.txt
- name: Execute the notebook and check PDF, HTML and ZIP downloads
run: python -I tools/verify_component_notebook.py --channel ${{ matrix.channel }} --check-downloads --out target/component-notebook
- name: Install the optional Jupyter browser test tools
if: matrix.channel == 'alpha6-r1'
run: |
python -m pip install --only-binary=:all: -r tools/component-notebook-browser-requirements.txt
- name: Edit, run and download in Jupyter Notebook
if: matrix.channel == 'alpha6-r1'
run: python -I tools/check_component_notebook_jupyter.py --reference target/component-notebook --out target/component-notebook/browser --browser chrome
- uses: actions/upload-artifact@v4
if: always()
with:
Expand Down
330 changes: 330 additions & 0 deletions docs/assets/component-notebook-alpha6-r1/bootstrap_components.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "welcome",
"metadata": {},
"source": [
"# Make a PDF from Python components\n",
"\n",
"Edit an order, customize its cards and table, and export a PDF plus styled\n",
"and unstyled HTML. You write Python values and components; no HTML strings\n",
"are needed. All sample data is fictional.\n",
"\n",
"**In Colab:** choose **Runtime → Run all**, sign in if prompted, and use a\n",
"standard CPU runtime. No GPU or paid plan is needed for this example;\n",
"[Colab availability and limits](https://research.google.com/colaboratory/faq.html)\n",
"still apply. Runtime files are temporary, so download your results before\n",
"leaving. Local Jupyter also works.\n",
"\n",
"**Experimental:** this notebook pins Fullbleed **2.6.0a6**. The component API\n",
"may change and has documented CSS limits. Bootstrap components are static\n",
"in both PDF and HTML; no Bootstrap JavaScript is included."
]
},
{
"cell_type": "markdown",
"id": "install-help",
"metadata": {},
"source": [
"## 1. Install the tested alpha\n",
"\n",
"Run this before importing Fullbleed. If you already imported a different\n",
"version in this notebook session, restart its kernel/runtime first.\n",
"The wheel supplies the engine and font; Jupyter supplies display tools."
]
},
{
"cell_type": "code",
"id": "install-fullbleed",
"metadata": {
"tags": [
"install-fullbleed"
]
},
"source": [
"%pip install --quiet --only-binary=:all: \"fullbleed==2.6.0a6\""
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "data-help",
"metadata": {},
"source": [
"## 2. Make the order yours\n",
"\n",
"Change a name, quantity or price, then rerun this cell and the cells below it.\n",
"Prices are decimal strings; this example omits tax, shipping and discounts.\n",
"Try `items=[]` to see the empty state, or add rows to explore pagination."
]
},
{
"cell_type": "code",
"id": "order-data",
"metadata": {},
"source": [
"order = {\n",
" \"number\": \"1048\",\n",
" \"customer\": \"Moss & Fern Studio\",\n",
" \"collection\": \"The autumn collection\",\n",
" \"status\": \"Ready to ship\",\n",
" \"items\": [\n",
" {\"item\": \"Studio notebooks\", \"quantity\": 24, \"unit_price\": \"15.00\"},\n",
" {\"item\": \"Cotton stationery sets\", \"quantity\": 12, \"unit_price\": \"18.00\"},\n",
" {\"item\": \"Archival prints\", \"quantity\": 8, \"unit_price\": \"60.00\"},\n",
" ],\n",
"}"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "styles-help",
"metadata": {},
"source": [
"## 3. Choose the accent and type size\n",
"\n",
"These values feed ordinary Python styles. Later, `.with_parts(...)` styles\n",
"a card's own title and body. The custom `dispatch-note` class gets its rule\n",
"from `Document(styles=...)`; a class name alone does not create a style."
]
},
{
"cell_type": "code",
"id": "style-controls",
"metadata": {},
"source": [
"accent = \"#205b47\"\n",
"title_size = 30"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "components-help",
"metadata": {},
"source": [
"## 4. Compose reusable components\n",
"\n",
"`Panel` supplies reusable card defaults. `Metric` is an ordinary Python\n",
"function. `DataTable` builds its structure from rows and column functions.\n",
"Bootstrap classes such as `mb-4` come with the components; your custom\n",
"classes need their own rules. Text values are escaped automatically.\n",
"\n",
"A column callback can return a component: `Strong(row[\"item\"])` emphasizes\n",
"each item inside its table cell. Return text or compose other components\n",
"without constructing an HTML string.\n"
]
},
{
"cell_type": "code",
"id": "components",
"metadata": {},
"source": [
"from decimal import Decimal\n",
"from fullbleed.ui.experimental import Document, Page, Strong, Theme, mm, pt, style, styled\n",
"from fullbleed.ui.experimental import bootstrap as bs\n",
"\n",
"def money(value):\n",
" return f\"USD {value:,.2f}\"\n",
"\n",
"Panel = styled(bs.Card, border_color=\"#d7e1de\", border_radius=\"8px\")\n",
"\n",
"def Metric(value, label):\n",
" return Panel(\n",
" bs.Heading(value, level=2, class_name=\"mb-1\", style=style(color=accent)),\n",
" bs.Text(label, class_name=\"text-secondary\"),\n",
" )\n",
"\n",
"def OrderSummary(data):\n",
" rows = [dict(row, amount=Decimal(row[\"unit_price\"]) * row[\"quantity\"])\n",
" for row in data[\"items\"]]\n",
" total = sum((row[\"amount\"] for row in rows), Decimal(\"0.00\"))\n",
" units = sum(row[\"quantity\"] for row in rows)\n",
" details = bs.DataTable(\n",
" rows,\n",
" columns=[\n",
" bs.Column(\"Item\", lambda row: Strong(row[\"item\"])),\n",
" bs.Column(\"Quantity\", lambda row: row[\"quantity\"], align=\"right\"),\n",
" bs.Column(\"Amount\", lambda row: money(row[\"amount\"]), align=\"right\"),\n",
" ],\n",
" striped=True,\n",
" caption=\"Order contents · USD\",\n",
" ) if rows else bs.Alert(\"No items in this order.\", variant=\"light\")\n",
" return Document(\n",
" bs.Box(bs.Text(\"NORTHSTAR / STUDIO\", class_name=\"fw-bold\"),\n",
" bs.Badge(data[\"status\"], variant=\"light\", class_name=\"ms-3\"),\n",
" class_name=\"mb-4\"),\n",
" bs.Heading(data[\"collection\"], level=1,\n",
" style=style(font_size=pt(title_size), color=accent, line_height=1.1)),\n",
" bs.Paragraph(f'Prepared for {data[\"customer\"]}', class_name=\"lead mb-4\"),\n",
" bs.Row(\n",
" bs.Col(Metric(str(units), \"Items in this order\"), span=5),\n",
" bs.Col(Metric(money(total), \"Order total\"), span=7),\n",
" gutter=3, class_name=\"mb-4 mx-0\",\n",
" ),\n",
" Panel(details, title=f'Order {data[\"number\"]}',\n",
" footer=\"Made with care. Packed with purpose.\").with_parts(\n",
" title=style(color=accent, font_size=pt(16)),\n",
" body=style(padding=\"1.25rem\"),\n",
" ),\n",
" bs.Alert(\"Please retain this copy with your shipment.\",\n",
" class_name=\"dispatch-note mt-4\"),\n",
" title=f'Order {data[\"number\"]}',\n",
" page=Page(size=\"A4\", margin=mm(16)),\n",
" theme=Theme(text=style(font_family=\"Inter, Arial, sans-serif\", font_size=pt(10),\n",
" color=\"#18382f\", line_height=1.4)),\n",
" styles={\".dispatch-note\": style(color=accent, border_color=accent,\n",
" background_color=\"#f0f5f2\")},\n",
" )\n",
"\n",
"document = OrderSummary(order)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "render-help",
"metadata": {},
"source": [
"## 5. Render and export\n",
"\n",
"The PDF renderer receives the bundled Inter font file explicitly. A CSS\n",
"family name does not load a font. Styled HTML keeps the family declaration;\n",
"its browser host supplies Inter separately, or uses the Arial/sans-serif fallback.\n",
"\n",
"`include_styles=False` removes generated and inline styling but keeps classes\n",
"for a host application's CSS. Both export modes also work on an individual\n",
"component, such as `Metric(\"44\", \"Items\").to_html(include_styles=False)`.\n",
"Styled Bootstrap fragments include global Bootstrap rules; use unstyled\n",
"fragments when the host page already provides its stylesheet."
]
},
{
"cell_type": "code",
"id": "render-export",
"metadata": {},
"source": [
"from importlib.resources import files\n",
"from pathlib import Path\n",
"from fullbleed.ui.experimental import Renderer\n",
"\n",
"output = Path(\"output\")\n",
"output.mkdir(exist_ok=True)\n",
"font = files(\"fullbleed_assets\").joinpath(\"fonts/Inter-Variable.ttf\")\n",
"renderer = Renderer(font_files=[str(font)],\n",
" document_timestamp=\"2026-10-01T00:00:00Z\")\n",
"prepared = document.prepare(renderer)\n",
"pdf_path = output / \"order.pdf\"\n",
"pdf_path.write_bytes(prepared.render_pdf())\n",
"(output / \"order.styled.html\").write_text(\n",
" document.to_html(include_styles=True), encoding=\"utf-8\", newline=\"\\n\")\n",
"(output / \"order.unstyled.html\").write_text(\n",
" document.to_html(include_styles=False), encoding=\"utf-8\", newline=\"\\n\")\n",
"print(f\"Saved {pdf_path} and both HTML exports.\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "preview",
"metadata": {},
"source": [
"from IPython.display import Image, display\n",
"\n",
"preview_paths = []\n",
"for number, png in enumerate(prepared.render_image_pages(dpi=110), start=1):\n",
" path = output / f\"order-page-{number}.png\"\n",
" path.write_bytes(png)\n",
" preview_paths.append(path)\n",
" display(Image(data=png, width=650))\n",
"print(f\"{len(preview_paths)} page(s). Review every page before using the document.\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "download-help",
"metadata": {},
"source": [
"## 6. Download your files\n",
"\n",
"This ZIP contains the current PDF, both HTML exports, and every current page\n",
"preview. In Colab the next cell starts the download. In local Jupyter it\n",
"displays a link. The runtime's `output/` folder also contains the individual\n",
"files. If you edit the order or styles, rerun from that edit through this cell."
]
},
{
"cell_type": "code",
"id": "download",
"metadata": {},
"source": [
"import sys\n",
"from zipfile import ZipFile, ZIP_DEFLATED\n",
"\n",
"bundle_path = output / \"component-order.zip\"\n",
"current_files = [pdf_path, output / \"order.styled.html\",\n",
" output / \"order.unstyled.html\", *preview_paths]\n",
"with ZipFile(bundle_path, \"w\", compression=ZIP_DEFLATED) as bundle:\n",
" for path in current_files:\n",
" bundle.write(path, arcname=path.name)\n",
"\n",
"if \"google.colab\" in sys.modules:\n",
" from google.colab import files\n",
" files.download(str(bundle_path))\n",
"else:\n",
" from IPython.display import FileLink\n",
" display(FileLink(bundle_path.as_posix()))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "next-steps",
"metadata": {},
"source": [
"## Keep composing\n",
"\n",
"- [Component guide and catalog](https://docs.fullbleed.dev/guides/bootstrap-components-preview/)\n",
" explain class names, named parts, fonts, static states and current limits.\n",
"- [Northstar and Common Ground source kits](https://docs.fullbleed.dev/guides/showcase-authoring/)\n",
" show a complete invoice and illustrated report in components and HTML/CSS.\n",
"- [The authored accessible guide](https://docs.fullbleed.dev/guides/accessible-showcase/)\n",
" includes the exact PDF, semantic source and scoped validation receipts.\n",
"- [Share an authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86)\n",
" if a composition or styling task feels awkward. Include the small component,\n",
" expected result and Fullbleed version; use fictional data in public posts.\n",
"- [Stable Python guide](https://docs.fullbleed.dev/guides/python-components/)\n",
" documents the established API separately from this alpha.\n",
"\n",
"The notebook and Fullbleed are MIT licensed. This example does not establish\n",
"complete Bootstrap CSS, browser, accessibility or PDF conformance."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"colab": {
"name": "bootstrap_components.ipynb",
"provenance": []
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading