{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "2039bd00-58d7-4c44-88f5-2a5c22e8e183", "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "from panel_material_ui import Page, Tabs\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "id": "a40d98c9-1d90-438d-b4ce-9ac35ec19254", "metadata": {}, "source": [ "The `Page` component is the equivalent of a `Template` in Panel, defining the overall layout of an application.\n", "\n", "Unlike a `Template`, the `Page` component is implemented entirely in JavaScript, allowing dynamic updates of its contents without re-rendering the entire layout.\n", "\n", "## Parameters:\n", "For details on other options for customizing the component see the layout and styling how-to guides.\n", "\n", "### Core\n", "\n", "* **`config`** (`Config`): Configuration object declaring custom CSS and JS files to load specifically for this template.\n", "* **`favicon`** (`Path | str | dict[str, str | Path]`): Favicon to render.\n", "* **`logo`** (`Path | str | dict[str, str | Path]`): Logo to render in the header. Can be a string, a pathlib.Path, or a dictionary with breakpoints as keys, e.g. {'sm': 'logo_mobile.png', 'md': 'logo.png'} or themes as keys, e.g. `{'dark': 'logo_dark.png', 'light': 'logo.png'}`.\n", "* **`meta`** (`Meta`): Meta tags and other HTML head elements.\n", "* **`template`** (`str | Path | jinja2.Template`): Overrides the default jinja2 template.\n", "* **`title`** (`str`): Title of the application.\n", "\n", "### Layout\n", "\n", "* **`header`** (`Children`): Items rendered in the header.\n", "* **`main`** (`Children`): Items rendered in the main area.\n", "* **`sidebar`** (`Children`): Items rendered in the sidebar.\n", "* **`contextbar`** (`Children`): Items rendered in the contextbar.\n", "\n", "### Sidebar\n", "\n", "* **`sidebar_open`** (`boolean`): Whether the sidebar is open or closed.\n", "* **`sidebar_resizable`** (`boolean`): Whether the sidebar is resizable.\n", "* **`sidebar_variant`** (`Literal[\"persistent\", \"temporary\", \"permanent\", \"auto\"]`): Whether the sidebar is persistent, temporary, permanent or automatically adapts based on screen size.\n", "* **`sidebar_width`** (`int`): Width of the sidebar.\n", "\n", "### Contextbar\n", "\n", "* **`contextbar_open`** (`boolean`): Whether the contextbar is open or closed.\n", "* **`contextbar_width`** (`int`): Width of the contextbar.\n", "\n", "### Indicators\n", "\n", "* **`busy`** (`boolean`, readonly): Linked to global busy state.\n", "* **`busy_indicator`** (`Literal[\"circular\", \"linear\"] | None`): Whether to render a linear, circular or no busy indicator.\n", "* **`theme_toggle`** (boolean): Whether to show a theme toggle button.\n", "___" ] }, { "cell_type": "markdown", "id": "5eb5b9e8-78ff-40d6-bea4-52523d283cb2", "metadata": {}, "source": [ "### π Basic Example: Main, Sidebar, and Contextbar\n", "\n", "This example creates a simple `Page` layout with content defined in the `main`, `sidebar`, and `contextbar` areas." ] }, { "cell_type": "code", "execution_count": null, "id": "80f6c1e1-b003-42b6-8a1c-617366a8b877", "metadata": {}, "outputs": [], "source": [ "page = Page(\n", " main=[\"## I'm the main area\"],\n", " sidebar_width=250,\n", " sidebar=[\"## I'm the sidebar\"],\n", " contextbar=[\"# I'm the contextbar\"],\n", " title=\"I'm a title\",\n", ")\n", "\n", "page.preview()" ] }, { "cell_type": "markdown", "id": "5acf4e86-43d9-422b-9177-d6b296649d67", "metadata": {}, "source": [ "#### π Whatβs Happening?\n", "\n", "- **`main`**: The primary content area of the page, here showing a simple Markdown heading.\n", "- **`sidebar`**: A side panel typically used for navigation or filters. Its width is set to `250` pixels.\n", "- **`contextbar`**: An optional, secondary sidebar often used for auxiliary info or tools.\n", "- **`title`**: Sets the page title shown in the browser tab and can be used in the layout." ] }, { "cell_type": "markdown", "id": "d15b438c-2675-478a-b966-afbc8c660a22", "metadata": {}, "source": [ "## Sidebar variants" ] }, { "cell_type": "markdown", "id": "d00f5b49-ac95-49fc-9c2c-761e3407650c", "metadata": {}, "source": [ "By default the `sidebar_variant` is set to `auto`, which switches from a persistent to a temporary sidebar on mobile." ] }, { "cell_type": "code", "execution_count": null, "id": "3a1dcbeb-577e-47e3-8b49-b06232ea4cd7", "metadata": {}, "outputs": [], "source": [ "temp_page = page.clone(\n", " sidebar_variant='temporary'\n", ")\n", "\n", "temp_page.preview()" ] }, { "cell_type": "markdown", "id": "b6b2107a-1a76-4253-81c2-512eacb7db20", "metadata": {}, "source": [ "The `sidebar` now overlays the main content.\n", "\n", "We can also make the sidebar `permanent` (disabling the sidebar toggle):" ] }, { "cell_type": "code", "execution_count": null, "id": "b25f56dc-372d-47b6-b1cb-4471f3935dc2", "metadata": {}, "outputs": [], "source": [ "temp_page = page.clone(\n", " sidebar_variant='permanent'\n", ")\n", "\n", "temp_page.preview()" ] }, { "cell_type": "markdown", "id": "94cab0c5-6506-4ab4-a616-23931e6b1b7b", "metadata": {}, "source": [ "### π¨ Custom Theming with `theme_config`\n", "\n", "This example demonstrates how to apply a custom theme to the `Page` layout using the `theme_config` option." ] }, { "cell_type": "code", "execution_count": null, "id": "5b3ee49d-1c77-4e33-9846-ae92cbbbc56a", "metadata": {}, "outputs": [], "source": [ "HEADER_COLOR = \"#2A3E5C\"\n", "PAPER_COLOR = \"#f8f8f8\"\n", "\n", "themed = page.clone(theme_config={\n", " 'palette': {\n", " 'primary': {\n", " 'main': HEADER_COLOR # The header is styled by the primary palette\n", " },\n", " 'background': {\n", " 'paper': PAPER_COLOR, # The remaining areas are paper colored\n", " },\n", " }\n", "}, theme_toggle=False)\n", "\n", "themed.preview()" ] }, { "cell_type": "markdown", "id": "bd282ae1-5179-4ee6-82c6-093141de2a5c", "metadata": {}, "source": [ "#### π― Key Customizations\n", "\n", "- **Primary Palette (`primary.main`)**: Sets the header color using a deep, modern blue-gray (`#2A3E5C`).\n", "- **Background (`background.paper`)**: Applies a light neutral background (`#f8f8f8`) to content areas like the main, sidebar, and contextbar.\n", "\n", ":::note\n", "By setting `theme_toggle=False`, the user is not shown a button to switch between light and dark modes β keeping the design consistent.\n", ":::" ] }, { "cell_type": "markdown", "id": "7f9422b5-558d-4f86-9a0c-2912595fb436", "metadata": {}, "source": [ "### π¨ Advanced Styling with `sx`\n", "\n", "This example builds on the previous themed layout, applying fine-grained custom styles using the `sx` parameter (which accepts CSS-like syntax)." ] }, { "cell_type": "code", "execution_count": null, "id": "0c84e902-da3b-49e3-9c8b-c0f6b61fdf08", "metadata": {}, "outputs": [], "source": [ "styled = themed.clone(sx={\n", " \"& .header\": {\n", " \"backgroundColor\": \"#673AB7\"\n", " },\n", " \"& .title\": {\n", " \"fontSize: 2.5em\"\n", " },\n", " \"& .main\": {\n", " \"backgroundColor\": \"#c3c3c3\",\n", " },\n", " \"&.mui-dark .main\": {\n", " \"backgroundColor\": \"#3f3f3f\",\n", " },\n", " \"& .sidebar\": {\n", " \"backgroundColor\": \"#e9e9e9\"\n", " },\n", " \"&.mui-dark .sidebar\": {\n", " \"backgroundColor\": \"#2a2a2a\",\n", " },\n", " \"& .contextbar\": {\n", " \"backgroundColor\": \"#525252\",\n", " \"color\": \"white\"\n", " },\n", "}, theme_toggle=True)\n", "\n", "pn.Tabs(\n", " ('Theme: Default', styled.preview()),\n", " ('Theme: Dark', styled.clone(dark_theme=True).preview())\n", ")" ] }, { "cell_type": "markdown", "id": "9708a811-e9cc-4459-81c2-42ead7fd7ab3", "metadata": {}, "source": [ "#### π Theme Toggle Enabled\n", "\n", "Unlike the previous example, this version re-enables the **theme toggle**, letting users switch between light and dark modes. The `sx` rules adapt accordingly β as shown with the conditional `\".mui-dark .main\"` style." ] }, { "cell_type": "markdown", "id": "95579baa-eb8b-4fe4-a655-f6560e816cec", "metadata": {}, "source": [ "### Configuring the page loader\n", "\n", "The `Page` component renders into a `template`, which, by default, includes a loading spinner. This loading spinner can be overridden with a custom template, e.g. below we define a custom loader generated with [loading.io](https://loading.io):" ] }, { "cell_type": "code", "execution_count": null, "id": "9c3f483c-c1f9-4ff0-abf7-f5a218799159", "metadata": {}, "outputs": [], "source": [ "template = \"\"\"\n", "{% extends \"base.html\" %}\n", "\n", "{% block loader_css %}\n", "@keyframes ldio-yzaezf3dcmj-1 {\n", " 0% { transform: rotate(0deg) }\n", " 50% { transform: rotate(-45deg) }\n", " 100% { transform: rotate(0deg) }\n", "}\n", "@keyframes ldio-yzaezf3dcmj-2 {\n", " 0% { transform: rotate(180deg) }\n", " 50% { transform: rotate(225deg) }\n", " 100% { transform: rotate(180deg) }\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(2) {\n", " transform: translate(-15px,0);\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(2) div {\n", " position: absolute;\n", " top: 40px;\n", " left: 40px;\n", " width: 120px;\n", " height: 60px;\n", " border-radius: 120px 120px 0 0;\n", " background: #fee547;\n", " animation: ldio-yzaezf3dcmj-1 1s linear infinite;\n", " transform-origin: 60px 60px\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(2) div:nth-child(2) {\n", " animation: ldio-yzaezf3dcmj-2 1s linear infinite\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(2) div:nth-child(3) {\n", " transform: rotate(-90deg);\n", " animation: none;\n", "}@keyframes ldio-yzaezf3dcmj-3 {\n", " 0% { transform: translate(190px,0); opacity: 0 }\n", " 20% { opacity: 1 }\n", " 100% { transform: translate(70px,0); opacity: 1 }\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(1) {\n", " display: block;\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(1) div {\n", " position: absolute;\n", " top: 92px;\n", " left: -8px;\n", " width: 16px;\n", " height: 16px;\n", " border-radius: 50%;\n", " background: #000000;\n", " animation: ldio-yzaezf3dcmj-3 1s linear infinite\n", "}\n", ".ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(1) { animation-delay: -0.67s }\n", ".ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(2) { animation-delay: -0.33s }\n", ".ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(3) { animation-delay: 0s }\n", ".loadingio-spinner-bean-eater-2by998twmg8 {\n", " width: 200px;\n", " height: 200px;\n", " display: inline-block;\n", " overflow: hidden;\n", " background: #ffffff;\n", "}\n", ".ldio-yzaezf3dcmj {\n", " width: 100%;\n", " height: 100%;\n", " position: relative;\n", " transform: translateZ(0) scale(1);\n", " backface-visibility: hidden;\n", " transform-origin: 0 0; /* see note above */\n", "}\n", ".ldio-yzaezf3dcmj div { box-sizing: content-box; }\n", "\n", ".loading {\n", " position: fixed;\n", " display: flex;\n", " flex-direction: column;\n", " justify-content: center;\n", " align-items: center;\n", " top: 0;\n", " right: 0;\n", " bottom: 0;\n", " left: 0;\n", " z-index: 1050;\n", " height: 100%;\n", " width: 100%;\n", " background-color: rgba(0, 0, 0, 0.6);\n", " font-size: 1.5rem;\n", " color: #ffffff;\n", "\n", " -webkit-animation: fadein 0.5s; /* Safari, Chrome and Opera > 12.1 */\n", " -moz-animation: fadein 0.5s; /* Firefox < 16 */\n", " -ms-animation: fadein 0.5s; /* Internet Explorer */\n", " -o-animation: fadein 0.5s; /* Opera < 12.1 */\n", " animation: fadein 0.5s;\n", "}\n", "{% endblock %}\n", "\n", "{% block loader %}\n", "