Skip to content

schaveyt/jinja-flowbite

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flowbite-Based Jinja Components

A collection of Flowbite component developed as Jinja Layout Templates and Macros.

Getting Started for Flask

For a pre-configured starter repo both web and desktop app, goto: https://github.com/schaveyt/jinja-flowbite-web-desktop-demo

Otherwise start from scratch following the steps below:

  1. Add the jinja-flowbite package to your python environment

    • Option 1: Add it to your requirements.txt
    • Option 2: Add it to your pyproject.toml if you are using poetry
    • Option 3: Install to your global packages: pip install jinja-flowbite
  2. Register the jinja_flowbite package to the Flask runtime

    from flask import Flask
    import jinja2
    
    app = Flask(__name__,)
    app.jinja_env.auto_reload = True
    
    enhanced_loader = jinja2.ChoiceLoader([
        jinja2.PackageLoader("jinja_flowbite", ""),
        app.jinja_loader
    ])
    
    app.jinja_loader = template_loader
  3. Register the jinja-flowbite source file with Tailwind configurations

    NOTE: Below is a standard flowbite setup with the key addition of adding the jinja-flowbite package .jinja files to the content section.

    /** @type {import('tailwindcss').Config} */
    module.exports = {
    
    content: [
        ".your_app_folder/templates/**/*.{html,jinja}",
        "./src/static/js/**/*.js",
        "./node_modules/flowbite/**/*.js",
        "./venv/Lib/site-packages/jinja_flowbite/**/*.{html,jinja}",
    ],
    darkMode: 'class',
    theme: {
        extend: {
            colors: {
                primary: { "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd", "400": "#60a5fa", "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8", "800": "#1e40af", "900": "#1e3a8a", "950": "#172554" }
            },
            maxHeight: {
                'table-xl': '60rem',
            }
        },
        fontFamily: {
            'body': [
                'Inter',
                'ui-sans-serif',
                'system-ui',
                '-apple-system',
                'system-ui',
                'Segoe UI',
                'Roboto',
                'Helvetica Neue',
                'Arial',
                'Noto Sans',
                'sans-serif',
                'Apple Color Emoji',
                'Segoe UI Emoji',
                'Segoe UI Symbol',
                'Noto Color Emoji'
            ],
            'sans': [
                'Inter',
                'ui-sans-serif',
                'system-ui',
                '-apple-system',
                'system-ui',
                'Segoe UI',
                'Roboto',
                'Helvetica Neue',
                'Arial',
                'Noto Sans',
                'sans-serif',
                'Apple Color Emoji',
                'Segoe UI Emoji',
                'Segoe UI Symbol',
                'Noto Color Emoji'
            ],
            'mono': [
                'ui-monospace',
                'SFMono-Regular',
                'MesloLGL Nerd Font Mono', 
                'Cascadia Mono',
                'Courier New'
            ]
        }
    },
    plugins: [
        require('flowbite/plugin')
    ],
    }
  4. In your HTML template, import the Components and use as Jinja macros

    {% import "jinja_flowbite/controls/button.jinja" as Button %}
    {% import 'jinja_flowbite/controls/card.jinja' as Card %}
    
    <div class="flex flex-col items-center space-y-6">
        
        {{ Card.render(title="This is a card") }}
    
        {{ Button.render(text="Click me") }}
    
    <div>
  5. IMPORTANT - Rebuild the application's css file

    • For example:

      npx tailwindcss -i ./{{ FLASK_APP_DIR_NAME }}/static/css/app.css -o ./{{ FLASK_APP_DIR_NAME }}/static/css/app.min.css"

Create an Full Application Shell (Flask)

  1. Perform all the above getting started setup steps.

  2. Create a directory structure something like this:

    _ my_app/
      |_ static/
         |_ css
            |_ app.css               # contains tailwindcss instructions and your custom classes
            |_ app.min.css           # generated by tailwindcss npm tool
        |_ js
            |_ jinja-flowbite-app.js # will contain dark mode toggle logic 
      |_ templates/
        |_ components/
          |_ footer.jinja            # your custom footer content
          |_ sidebar.jinja           # your custom side bar content
        |_ layouts/
          |_ main_layout.html        # layout with header, sidebar, and footer
        |_ pages/
          |_ index.html              # template for the / route
          |_ index.py                # code for the / route
  3. In the jinja-flowbite-app.js, add the following code:

    // HANDLE DARK MODE ----------------------------------------------------------------------------
    
    var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
    var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
    
    // Change the icons inside the button based on previous settings
    if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && 
        window.matchMedia('(prefers-color-scheme: dark)').matches)) {
        themeToggleLightIcon.classList.remove('hidden');
    } else {
        themeToggleDarkIcon.classList.remove('hidden');
    }
    
    var themeToggleBtn = document.getElementById('theme-toggle');
    
    themeToggleBtn.addEventListener('click', function() {
    
        // toggle icons inside button
        themeToggleDarkIcon.classList.toggle('hidden');
        themeToggleLightIcon.classList.toggle('hidden');
    
        // if set via local storage previously
        if (localStorage.getItem('color-theme')) {
            if (localStorage.getItem('color-theme') === 'light') {
                document.documentElement.classList.add('dark');
                localStorage.setItem('color-theme', 'dark');
            } else {
                document.documentElement.classList.remove('dark');
                localStorage.setItem('color-theme', 'light');
            }
    
        // if NOT set via local storage previously
        } else {
            if (document.documentElement.classList.contains('dark')) {
                document.documentElement.classList.remove('dark');
                localStorage.setItem('color-theme', 'light');
            } else {
                document.documentElement.classList.add('dark');
                localStorage.setItem('color-theme', 'dark');
            }
        }
        
    });
  4. In the templates/layout/main_layout.html add the following content:

    {% import 'jinja_flowbite/icons/flowbite_logo.jinja' as flowbite_flowbite_icon %}
    {% import 'jinja_flowbite/controls/dark_mode_toggle.jinja' as flowbite_dark_mode_toggle %}
    
    {% extends "jinja_flowbite/layouts/main_layout.jinja" %}
    
    {% block flowbite_head_app_css_links_outlet %}
        <link href="{{url_for('static',filename='css/app.min.css')}}" rel="stylesheet" />
    {% endblock %}
    
    {% block flowbite_head_scripts_outlet %}
        <script src="{{url_for('static',filename='js/htmx.2.0.1.min.js')}}"></script>
    {% endblock %}
    
    
    {% block flowbite_body_scripts_outlet %}
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/flowbite.min.js"></script>
        <script src="{{url_for('static',filename='js/jinja-flowbite-app.js')}}"></script>
    {% endblock %}
    
    
    {% block flowbite_header_content_left_outlet  %}
        <!-- Application Title -->
        <a class="flex items-center ml-2 sm:ml-0" data-enhance-nav="false">
            {{ flowbite_flowbite_icon.render(width_css_class="w-8", width_css_class="h-8", class="text-primary-500 mr-3" ) }}
            <a href="/" class="self-center text-2xl font-semibold whitespace-nowrap text-white"> Application Title </a>
        </a>
    {% endblock %}
    
    
    {% block flowbite_header_content_right_outlet  %}
        {{ flowbite_dark_mode_toggle.render(class="mr-1" ) }}
    {% endblock %}
    
    
    {% block flowbite_page_sidebar_outlet %}
        {% include "components/sidebar_content.jinja" %}
    {% endblock %}
    
    
    {% block flowbite_footer_content_outlet %}
        {% include "components/footer_content.jinja" %}
    {% endblock %}
    
    
    {% block flowbite_page_body_outlet %}
    
        {% block page_body %}{% endblock %}
    
    {% endblock %}
  5. In the templates/components/footer.jinja add the following content:

    <!-- left side footer -->
    <div role="footer-left-controls" class="flex items-center">
        <p> Left side text</p>
    </div>
    
    <!-- right side footer -->    
    <div>
        <div id="app-version" class="text-sm mr-2 flex space-x-2 items-center">
            <span>Application Title</span>
            <span>v0.1</span>
        </div>
        
    </div>
  6. In the templates/components/footer.jinja add the following content:

    <!-- left side footer -->
    <div role="footer-left-controls" class="flex items-center">
        <p> Left side text</p>
    </div>
    
    <!-- right side footer -->    
    <div>
        <div id="app-version" class="text-sm mr-2 flex space-x-2 items-center">
            <span>Application Title</span>
            <span>v0.1</span>
        </div>
        
    </div>
  7. In the templates/components/sidebar.jinja add the following content:

    {% set inactive_classes = "hover:bg-gray-100 dark:hover:bg-gray-700" %}
    {% set active_classes = "bg-primary-300 dark:bg-primary-800" %}
    
    <div class="py-5 px-3">
    
        <ul class="pt-5 mt-5 space-y-2 border-t border-gray-200 dark:border-gray-700">
    
            <!-- Home NavItem -->
            <li>
                {% set nav_active_classes = active_classes if 'home' in request.endpoint else inactive_classes %}
                <a href="/home"
                    class="flex items-center p-2 text-base font-medium text-gray-900 rounded-lg transition duration-75 dark:text-white group {{ nav_active_classes }}">
                    <svg aria-hidden="true"
                        class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"
                        fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                        <path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path>
                        <path fill-rule="evenodd"
                            d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
                            clip-rule="evenodd"></path>
                    </svg>
                    <span class="ml-3">Home</span>
                </a>
            </li>
    
    
            <!-- Help NavItem -->
            <li>
                {% set nav_active_classes = active_classes if 'help' in request.endpoint else inactive_classes %}
                <a href="/help"
                    class="flex items-center p-2 text-base font-medium text-gray-900 rounded-lg transition duration-75 dark:text-white group {{ nav_active_classes }}">
                    <svg aria-hidden="true"
                        class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"
                        fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-2 0c0 .993-.241 1.929-.668 2.754l-1.524-1.525a3.997 3.997 0 00.078-2.183l1.562-1.562C15.802 8.249 16 9.1 16 10zm-5.165 3.913l1.58 1.58A5.98 5.98 0 0110 16a5.976 5.976 0 01-2.516-.552l1.562-1.562a4.006 4.006 0 001.789.027zm-4.677-2.796a4.002 4.002 0 01-.041-2.08l-.08.08-1.53-1.533A5.98 5.98 0 004 10c0 .954.223 1.856.619 2.657l1.54-1.54zm1.088-6.45A5.974 5.974 0 0110 4c.954 0 1.856.223 2.657.619l-1.54 1.54a4.002 4.002 0 00-2.346.033L7.246 4.668zM12 10a2 2 0 11-4 0 2 2 0 014 0z"
                            clip-rule="evenodd"></path>
                    </svg>
                    <span class="ml-3">Help</span>
                </a>
            </li>
        </ul>
    
    
    </div>
    
    
  8. In the templates/pages/home_page.html add the following content:

    {% extends "layouts/main_layout.html" %}
    {% block page_body %}
    
        <p class="text-3xl">Home</p>
        
    {% endblock %}
  9. In the templates/pages/home_page.py add the following content:

    from flask import render_template
    from my_app.webapp import app
    
    @app.route("/")
    def index_page():
        return render_template("pages/home_page.html",
                page_title="Home")
  10. In the templates/pages/help_page.html add the following content:

    {% extends "layouts/main_layout.html" %}
    {% block page_body %}
        <p class="text-3xl">Help</p>
    {% endblock %}
  11. In the templates/pages/help_page.py add the following content:

    from flask import render_template
    from my_app.webapp import app
    
    @app.route("/")
    def index_page():
        return render_template("pages/help_page.html",
                page_title="Help")
  12. Run the application and one should see the following:

    • custom header
    • custom footer
    • custom sidebar
      • the sidebar highlights the active page

END OF DOCUMENT