Skip to content

RijksICTGilde/jinja-roos-components

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

111 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Jinja ROOS Components

Modern, self-contained Jinja2 component library with built-in assets, designed for seamless integration with FastAPI, Flask, and other Python web frameworks.

Features

  • Custom Component Syntax: Use intuitive <c-button> tags in your templates
  • Self-Contained Assets: All CSS, JavaScript, fonts, and icons bundled in the package
  • RVO Design System: Built on the Netherlands Government Design System
  • HTMX Integration: Optional HTMX support for dynamic interactions
  • TypeScript Support: Full TypeScript development environment
  • Framework Agnostic: Works with FastAPI, Flask, Django, and more
  • Extensible: Easy to add new components

TLDR;

Most of this readme is generated by AI. Just check the examples folder for a working FastAPI example, for components reference, see the Component Reference when starting:

poetry run uvicorn examples.fastapi_app:app --reload

πŸ“¦ Installation

pip install jinja-roos-components

For development with asset building:

cd jinja-roos-components
npm install
npm run build

Quick Setup

You can find demo implementations in these repos:

FastAPI Integration

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from jinja_roos_components import setup_components

app = FastAPI()
templates = Jinja2Templates(directory="templates")

# Setup ROOS components with optional features
setup_components(
    templates,
    theme="operations",           # Theme: default, operations, dark
    htmx=True,                   # Enable HTMX integration
    user_css_files=[             # Additional CSS files
        "/static/custom.css"
    ],
    user_js_files=[              # Additional JS files  
        "/static/custom.js"
    ]
)

@app.get("/")
def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Flask Integration

from flask import Flask, render_template
from jinja2 import Environment, FileSystemLoader
from jinja_roos_components import setup_components

app = Flask(__name__)

# Setup Jinja2 with ROOS components
env = Environment(loader=FileSystemLoader('templates'))
setup_components(env, htmx=True)

@app.route("/")
def home():
    return env.get_template("index.html").render()

Component Usage

Complete Page Structure

Create complete pages with the c-page component:

<!-- templates/index.html -->
<c-page title="My Application" 
        description="A modern web application"
        bodyClass="app-layout">
    
    <c-card title="Welcome">
        <c-button kind="primary" label="Click me!" />
    </c-card>
</c-page>

Alternative: Template Extension

For more complex layouts, extend base templates:

<!-- templates/base.html -->
{% extends "layouts/base.html.j2" %}

{% block title %}My Application{% endblock %}

{% block content %}
    <c-card title="Welcome">
        <c-button kind="primary" label="Click me!" />
    </c-card>
{% endblock %}

Available Components

The library includes a comprehensive set of components including buttons, cards, forms, layouts, and more. For a complete list of available components and their usage:

  • Live Examples: See the examples folder and run the FastAPI app
  • Component Reference: Auto-generated documentation with all attributes and examples

To generate the latest component documentation:

poetry run python scripts/generate_component_docs.py

This creates examples/component-reference.html with complete documentation for all components, including attributes, types, defaults, and usage examples.

Development Guide

Adding New Components

  1. Create Component Template

Create a new Jinja2 template in src/jinja_roos_components/templates/components/:

<!-- jinja_roos_components/templates/components/input.html.j2 -->
{% set input_type = _component_context.type | default('text') %}
{% set placeholder = _component_context.placeholder | default('') %}
{% set required = _component_context.required | default(false) %}
{% set value = _component_context.value | default('') %}
{% set name = _component_context.name | default('') %}

<div class="rvo-form-field">
    {% if _component_context.label %}
        <label class="rvo-form-field__label" for="{{ name }}">
            {{ _component_context.label }}
            {% if required %}<span class="rvo-form-field__required">*</span>{% endif %}
        </label>
    {% endif %}
    
    <input 
        class="rvo-form-field__input {{ _component_context.class | default('') }}"
        type="{{ input_type }}"
        id="{{ name }}"
        name="{{ name }}"
        value="{{ value }}"
        placeholder="{{ placeholder }}"
        {% if required %}required{% endif %}
        data-roos-component="input"
        {% if _component_context.get('@input') %}
            oninput="{{ _component_context['@input'] }}"
        {% endif %}
    />
</div>
  1. Register Component Definition

Add to src/jinja_roos_components/definitions.json under components key:

    {
      "name": "input",
      "description": "RVO text input with various types and validation",
      "attributes": [
        {
          "name": "id",
          "type": "string",
          "required": false,
          "description": "HTML id attribute"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "HTML name attribute"
        },
        {
          "name": "type",
          "type": "enum",
          "enum_values": [
            "text",
            "email",
            "password",
            "tel",
            "url",
            "search",
            "number"
          ],
          "default": "text",
          "required": false,
          "description": "Input type"
        },
        {
          "name": "placeholder",
          "type": "string",
          "required": false,
          "description": "Placeholder text"
        },
        {
          "name": "disabled",
          "type": "boolean",
          "default": false,
          "required": false,
          "description": "Whether input is disabled"
        },
        {
          "name": "required",
          "type": "boolean",
          "default": false,
          "required": false,
          "description": "Whether input is required"
        },
        {
          "name": "size",
          "type": "enum",
          "enum_values": [
            "xs",
            "sm",
            "md",
            "lg",
            "max"
          ],
          "default": "md",
          "required": false,
          "description": "Input size"
        }
      ],
      "slots": [],
      "examples": [
        "<c-input name=\"email\" type=\"email\" placeholder=\"Enter email\" />"
      ],
      "allow_preview": true,
      "requires_children": false,
      "preview_example": null
    }
  1. Create TypeScript Component (Optional)

Create src/ts/components/input.ts:

import type { Component } from '../utils/registry';

export const InputComponent: Component = {
    selector: '[data-roos-component="input"]',

    init(element: Element): void {
        const input = element as HTMLInputElement;
        
        // Add real-time validation
        input.addEventListener('blur', () => {
            this.validateInput(input);
        });

        // Add styling enhancements
        input.addEventListener('focus', () => {
            input.classList.add('rvo-form-field__input--focused');
        });

        input.addEventListener('blur', () => {
            input.classList.remove('rvo-form-field__input--focused');
        });
    },

    validateInput(input: HTMLInputElement): void {
        // Custom validation logic
        const isValid = input.checkValidity();
        
        if (isValid) {
            input.classList.remove('rvo-form-field__input--error');
            input.classList.add('rvo-form-field__input--valid');
        } else {
            input.classList.add('rvo-form-field__input--error');
            input.classList.remove('rvo-form-field__input--valid');
        }
    },

    destroy(element: Element): void {
        // Cleanup if needed
    }
};
  1. Register TypeScript Component

Add to src/ts/roos.ts:

import { InputComponent } from './components/input';

// Register the component
registry.register('input', InputComponent);
  1. Build and Test
npm run build

Test your new component:

<c-input label="Email Address" type="email" required 
         placeholder="Enter your email" />

Development Workflow

  1. Start Development Server
npm run dev

This watches for TypeScript changes and rebuilds automatically.

  1. Build for Production
npm run build
  1. Code Quality
npm run lint          # ESLint
npm run prettier      # Code formatting
npm run typecheck     # TypeScript checking

Testing Components

Create test files in your project:

# test_components.py
from jinja2 import Environment, DictLoader
from jinja_roos_components import setup_components

def test_button_component():
    env = Environment(loader=DictLoader({
        'test.html': '<c-button variant="primary">Test</c-button>'
    }))
    setup_components(env)
    
    template = env.get_template('test.html')
    result = template.render()
    
    assert 'rvo-button--primary' in result
    assert 'Test' in result

🎯 Integration Examples

Operations Manager Integration

# In your operations-manager
from jinja_roos_components import setup_components

setup_components(
    templates,
    theme="operations",
    htmx=True,
    user_css_files=['/static/operations.css']
)
<!-- Environment creation form -->
<c-card title="Create New Environment">
    <form hx-post="/environments" hx-target="#result">
        <c-input label="Project Name" name="project_name" required />
        <c-input label="Environment" name="env_type" type="select" 
                 :options="['dev', 'staging', 'prod']" />
        
        <div class="form-actions">
            <c-button type="submit" variant="primary" 
                     @click="hx-indicator='#loading'">
                Create Environment
            </c-button>
            <c-button variant="secondary" @click="history.back()">
                Cancel
            </c-button>
        </div>
    </form>
    
    <div id="loading" class="htmx-indicator">
        <c-button :loading="true">Creating...</c-button>
    </div>
</c-card>

πŸ” Troubleshooting

Common Issues

Components not rendering:

  • Ensure setup_components() is called on your Jinja2 environment
  • Check that component templates are in the correct path
  • Verify webpack build completed successfully

Styles not loading:

  • Run npm run build to generate CSS assets
  • Check that static files are being served correctly
  • Verify RVO design system dependencies are installed

TypeScript errors:

  • Run npm run typecheck to see detailed errors
  • Ensure all component files are properly imported
  • Check TypeScript configuration in tsconfig.json

HTMX integration issues:

  • Ensure HTMX is loaded before ROOS components
  • Check that htmx=True is passed to setup_components()
  • Verify HTMX attributes are properly formatted

Performance Tips

  1. Lazy Loading: Components only initialize when found in DOM
  2. Asset Optimization: Production build includes minification and tree-shaking
  3. Component Reuse: Template includes are cached by Jinja2
  4. TypeScript Benefits: Type checking prevents runtime errors

πŸ“š API Reference

setup_components()

Configure ROOS Components in a Jinja2 environment.

Parameters:

  • jinja_env: Environment - Jinja2 environment instance
  • theme: str - Theme name (default, operations, dark)
  • htmx: bool - Enable HTMX integration
  • user_css_files: list[str] - Additional CSS files
  • user_js_files: list[str] - Additional JavaScript files
  • static_url_prefix: str - URL prefix for static assets

Component Attribute Types

  • String attributes: attr="value"
  • Dynamic attributes: :attr="expression"
  • Event handlers: @event="handler"
  • HTMX attributes: @click="hx-get='/api/data'" (when HTMX enabled)

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/new-component
  3. Add your component following the development guide
  4. Write tests for your component
  5. Run code quality checks: npm run lint && npm run typecheck
  6. Submit a pull request

RVO Design System dependencies

This package bundles static assets built against specific versions of the RVO Design System npm packages:

Package Version
@nl-rvo/assets 1.0.0
@nl-rvo/component-library-css 4.19.0
@nl-rvo/css-button 2.1.0
@nl-rvo/design-tokens 2.2.0

If your project loads any of these packages separately (e.g. via CDN in a base HTML template), use the same versions to avoid styling inconsistencies.

Note: @nl-rvo/assets (fonts and icons) is always served from this package's bundled static files β€” no CDN reference needed.

Tagging / releasing

Currently the package is not released on a package index. However, by using tags, users can still pin to a specific version.

  • Change "[Unreleased]" in CHANGES.md to new tag and create new "[Unreleased]" section on top
  • Change version in pyproject.toml
  • Tag on main with git tag -a 0.1 -m "0.1" (substitute new tags)

πŸ“„ License

EUROPEAN UNION PUBLIC LICENSE v1.2 - see LICENSE file for details.

πŸ”— Related Projects

About

No description, website, or topics provided.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors