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
4 changes: 4 additions & 0 deletions docs/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ If you want to apply a strict Content Security Policy (CSP), you can pass ``nonc
E.g. if using `Talisman
<https://github.com/wntrblm/flask-talisman>`_ it can be called with ``bootstrap.load_js(nonce=csp_nonce())``.

In order to use icon font, there is an additional helper called ``bootstrap.load_icon_font_css()``.
This is used only by ``render_icon(..., font=True)`` or can be globally controlled via ``BOOTSTRAP_ICON_USE_FONT``.
See its also the documentation for that marco.

Starter template
----------------

Expand Down
11 changes: 7 additions & 4 deletions docs/macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,9 @@ By default, it will enable the CSRF token check for all the POST requests, read
render_icon()
-------------

Render a Bootstrap icon.
Render a Bootstrap icon. This is either an SVG with a ``use`` element which refers to a locally hosted SVG sprite with an fragment identifier.
Note that serving the SVG sprite across a domain has an `issue with Chrome <https://issues.chromium.org/issues/41164645>`_.
Or it is possible to have a font icon rendered. This does support``BOOTSTRAP_SERVE_LOCAL`` but requires ``bootstrap.load_icon_font_css()`` in the template header.

Example
~~~~~~~
Expand All @@ -621,14 +623,15 @@ Example
API
~~~~

.. py:function:: render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None, classes=None)
.. py:function:: render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None, classes=None, font=False)

:param name: The name of icon, you can find all available names at `Bootstrap Icon <https://icons.getbootstrap.com/>`_.
:param size: The size of icon, you can pass any vaild size value (e.g. ``32``/``'32px'``, ``1.5em``, etc.), default to
use configuration ``BOOTSTRAP_ICON_SIZE`` (default value is `'1em'`).
:param color: The color of icon, follow the context with ``currentColor`` if not set. Accept values are Bootstrap style name
(one of ``['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'muted']``) or any valid color
string (e.g. ``'red'``, ``'#ddd'`` or ``'(250, 250, 250)'``), default to use configuration ``BOOTSTRAP_ICON_COLOR`` (default value is ``None``).
:param title: The title of the icon for accessibility support.
:param desc: The description of the icon for accessibility support.
:param title: The title of the icon for accessibility support. This is not supported for ``font=True``.
:param desc: The description of the icon for accessibility support. This is not supported for ``font=True``.
:param classes: The classes to use for styling (e.g. ``'text-success bg-body-secondary p-2 rounded-3'``).
:param font: Generate ``<svg></svg>`` if set to ``False`` and generate ``<i></i>`` to use the icon font if set to ``True``, default to use configuration ``BOOTSTRAP_ICON_USE_FONT`` (default value is ``False``).
1 change: 1 addition & 0 deletions examples/bootstrap4/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<title>Bootstrap-Flask Demo Application</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
{{ bootstrap.load_css() }}
{{ bootstrap.load_icon_font_css() }}
<style>
pre {
background: #ddd;
Expand Down
34 changes: 23 additions & 11 deletions examples/bootstrap4/templates/icon.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,43 @@
{% from 'bootstrap4/utils.html' import render_icon %}

{% block content %}
<h2>Icon</h2>
<h2>SVG icon</h2>
<pre>{% raw %}{{ render_icon('heart') }}{% endraw %}</pre>
Output: {{ render_icon('heart') }}

<h2>Icon with custom size</h2>
<h2>SVG icon with custom size</h2>
<pre>{% raw %}{{ render_icon('heart', 32) }}{% endraw %}</pre>
Output: {{ render_icon('heart', 32) }}

<h2>Icon with custom size and Bootstrap color</h2>
<h2>SVG icon with custom size and Bootstrap color</h2>
<pre>{% raw %}{{ render_icon('heart', 25, 'primary') }}{% endraw %}</pre>
Output: {{ render_icon('heart', 25, 'primary') }}

<h2>Icon with custom size and custom color</h2>
<h2>SVG icon with custom size and custom color</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', 'red') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '2em', 'red') }}

<h2>Icon with additional classes for styling</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', classes='text-success bg-light p-2 rounded-lg') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '4em', classes='text-success bg-light p-2 rounded-lg') }}

<h2>Icon with title and descr</h2>
<h2>SVG icon with title and descr</h2>
<pre>{% raw %}{{ render_icon('heart', title='Heart', desc='A heart.') }}{% endraw %}</pre>
Output: {{ render_icon('heart', title='Heart', desc='A heart.') }}

<h2>Button example</h2>
<h2>SVG icon with additional classes for styling</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', classes='text-success bg-light p-2 rounded-lg') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '4em', classes='text-success bg-light p-2 rounded-lg') }}

<h2>Buttons with SVG icon</h2>
<a class="btn btn-primary text-white">Download {{ render_icon('arrow-down-circle') }}</a>
<a class="btn btn-success text-white">Bookmark {{ render_icon('bookmark-star') }}</a>
{% endblock %}

<h2>Font icon with custom size and Bootstrap color</h2>
<pre>{% raw %}{{ render_icon('heart', '25px', 'primary', font=True) }}{% endraw %}</pre>
Output: {{ render_icon('heart', '25px', 'primary', font=True) }}

<h2>Font icon with custom size and custom color</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', 'red', font=True) }}{% endraw %}</pre>
Output: {{ render_icon('heart', '2em', 'red', font=True) }}

<h2>Buttons with font icon</h2>
<a class="btn btn-primary text-white">Download {{ render_icon('arrow-down-circle', font=True) }}</a>
<a class="btn btn-success text-white">Bookmark {{ render_icon('bookmark-star', font=True) }}</a>
{% endblock %}
1 change: 1 addition & 0 deletions examples/bootstrap4/templates/icons.html
Original file line number Diff line number Diff line change
Expand Up @@ -16409,3 +16409,4 @@ <h2>Icons</h2>
</ul>
<p>This is a total of 2050 icons.</p>
{% endblock %}

1 change: 1 addition & 0 deletions examples/bootstrap5/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<title>Bootstrap-Flask Demo Application</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
{{ bootstrap.load_css() }}
{{ bootstrap.load_icon_font_css() }}
<style>
pre {
background: #ddd;
Expand Down
34 changes: 23 additions & 11 deletions examples/bootstrap5/templates/icon.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,43 @@
{% from 'bootstrap5/utils.html' import render_icon %}

{% block content %}
<h2>Icon</h2>
<h2>SVG icon</h2>
<pre>{% raw %}{{ render_icon('heart') }}{% endraw %}</pre>
Output: {{ render_icon('heart') }}

<h2>Icon with custom size</h2>
<h2>SVG icon with custom size</h2>
<pre>{% raw %}{{ render_icon('heart', 32) }}{% endraw %}</pre>
Output: {{ render_icon('heart', 32) }}

<h2>Icon with custom size and Bootstrap color</h2>
<h2>SVG icon with custom size and Bootstrap color</h2>
<pre>{% raw %}{{ render_icon('heart', 25, 'primary') }}{% endraw %}</pre>
Output: {{ render_icon('heart', 25, 'primary') }}

<h2>Icon with custom size and custom color</h2>
<h2>SVG icon with custom size and custom color</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', 'red') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '2em', 'red') }}

<h2>Icon with additional classes for styling</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', classes='text-success bg-body-secondary p-2 rounded-3') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '4em', classes='text-success bg-body-secondary p-2 rounded-3') }}

<h2>Icon with title and descr</h2>
<h2>SVG icon with title and descr</h2>
<pre>{% raw %}{{ render_icon('heart', title='Heart', desc='A heart.') }}{% endraw %}</pre>
Output: {{ render_icon('heart', title='Heart', desc='A heart.') }}

<h2>Button example</h2>
<h2>SVG icon with additional classes for styling</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', classes='text-success bg-body-secondary p-2 rounded-3') }}{% endraw %}</pre>
Output: {{ render_icon('heart', '4em', classes='text-success bg-body-secondary p-2 rounded-3') }}

<h2>Buttons with SVG icon</h2>
<a class="btn btn-primary text-white">Download {{ render_icon('arrow-down-circle') }}</a>
<a class="btn btn-success text-white">Bookmark {{ render_icon('bookmark-star') }}</a>
{% endblock %}

<h2>Font icon with custom size and Bootstrap color</h2>
<pre>{% raw %}{{ render_icon('heart', '25px', 'primary', font=True) }}{% endraw %}</pre>
Output: {{ render_icon('heart', '25px', 'primary', font=True) }}

<h2>Font icon with custom size and custom color</h2>
<pre>{% raw %}{{ render_icon('heart', '2em', 'red', font=True) }}{% endraw %}</pre>
Output: {{ render_icon('heart', '2em', 'red', font=True) }}

<h2>Buttons with font icon</h2>
<a class="btn btn-primary text-white">Download {{ render_icon('arrow-down-circle', font=True) }}</a>
<a class="btn btn-success text-white">Bookmark {{ render_icon('bookmark-star', font=True) }}</a>
{% endblock %}
1 change: 1 addition & 0 deletions examples/bootstrap5/templates/icons.html
Original file line number Diff line number Diff line change
Expand Up @@ -16409,3 +16409,4 @@ <h2>Icons</h2>
</ul>
<p>This is a total of 2050 icons.</p>
{% endblock %}

2 changes: 1 addition & 1 deletion examples/update-icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def generate(version):
number += 1
file.write('</ul>\n')
file.write(f'<p>This is a total of {number} icons.</p>\n')
file.write('{% endblock %}\n')
file.write('{% endblock %}\n\n')
print(f'For Bootstrap{version}, a total of {number} icons are supported.')

for value in (4, 5):
Expand Down
17 changes: 17 additions & 0 deletions flask_bootstrap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class _Bootstrap:
bootstrap_version = None
jquery_version = None
popper_version = None
icons_version = None
bootstrap_css_integrity = None
bootstrap_js_integrity = None
jquery_integrity = None
Expand Down Expand Up @@ -78,6 +79,7 @@ def init_app(self, app):
app.config.setdefault('BOOTSTRAP_BOOTSWATCH_THEME', None)
app.config.setdefault('BOOTSTRAP_ICON_SIZE', '1em')
app.config.setdefault('BOOTSTRAP_ICON_COLOR', None)
app.config.setdefault('BOOTSTRAP_ICON_USE_FONT', False)
app.config.setdefault('BOOTSTRAP_MSG_CATEGORY', 'primary')
app.config.setdefault('BOOTSTRAP_TABLE_VIEW_TITLE', 'View')
app.config.setdefault('BOOTSTRAP_TABLE_EDIT_TITLE', 'Edit')
Expand Down Expand Up @@ -122,6 +124,19 @@ def load_css(self, version=None, bootstrap_sri=None, bootswatch_theme=None):
css = f'<link rel="stylesheet" href="{bootstrap_url}">'
return Markup(css)

def load_icon_font_css(self):
"""Load Bootstrap's css icon font resource.

.. versionadded:: 2.4.2
"""
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
if serve_local:
icons_url = url_for('bootstrap.static', filename='font/bootstrap-icons.min.css')
else:
icons_url = f'{CDN_BASE}/bootstrap-icons@{self.icons_version}/font/bootstrap-icons.min.css'
css = f'<link rel="stylesheet" href="{icons_url}">'
return Markup(css)

def _get_js_script(self, version, name, sri, nonce):
"""Get <script> tag for JavaScript resources."""
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
Expand Down Expand Up @@ -227,6 +242,7 @@ def create_app():
bootstrap_version = '4.6.1'
jquery_version = '3.5.1'
popper_version = '1.16.1'
icons_version = '1.11.3'
bootstrap_css_integrity = 'sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn'
bootstrap_js_integrity = 'sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2'
jquery_integrity = 'sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0='
Expand Down Expand Up @@ -262,6 +278,7 @@ def create_app():
"""
bootstrap_version = '5.3.2'
popper_version = '2.11.8'
icons_version = '1.11.3'
bootstrap_css_integrity = 'sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN'
bootstrap_js_integrity = 'sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+'
popper_integrity = 'sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r'
Expand Down

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
17 changes: 11 additions & 6 deletions flask_bootstrap/templates/base/utils.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@
{% endmacro %}


{% macro render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None, classes=None) %}
{%- set bootstrap_colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'muted'] -%}
{% macro render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None, classes=None, font=config.BOOTSTRAP_ICON_USE_FONT) %}
{% set bootstrap_colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'muted'] %}
{%- if font == true -%}
<i class="bi-{{ name }}{% if color in bootstrap_colors %} text-{{ color }}{% endif %}" style="{% if color and color not in bootstrap_colors %}color: {{ color }}; {% endif %}font-size: {{ size }};"></i>
{%- else -%}
<svg class="bi{% if not color %}{% if classes %} {{ classes }}{% endif %}"
{%- elif color in bootstrap_colors %} text-{{ color }}"{% else %}" style="color: {{ color }}"{% endif -%}
{%- if size %} width="{{ size }}"{% endif %}{% if size %} height="{{ size }}"{% endif %} fill="currentColor">
{%- if title %}<title>{{ title }}</title>{% endif -%}
{%- if desc %}<desc>{{ desc }}</desc>{% endif -%}
<use xlink:href="{{ url_for('bootstrap.static', filename='icons/bootstrap-icons.svg') }}#{{ name }}"/></svg>
{%- endmacro %}
{% if title is not none %}<title>{{ title }}</title>{% endif %}
{% if desc is not none %}<desc>{{ desc }}</desc>{% endif %}
<use xlink:href="{{ url_for('bootstrap.static', filename='icons/bootstrap-icons.svg') }}#{{ name }}"/>
</svg>
{%- endif -%}
{% endmacro %}


{% macro arg_url_for(endpoint, base) %}
Expand Down
71 changes: 69 additions & 2 deletions tests/test_bootstrap4/test_render_icon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import render_template_string


def test_render_icon(app, client):
def test_render_icon_svg(app, client):
@app.route('/icon')
def icon():
return render_template_string('''
Expand Down Expand Up @@ -78,7 +78,7 @@ def icon_desc_classes():
assert 'class="bi text-success bg-light"' in data


def test_render_icon_config(app, client):
def test_render_icon_svg_config(app, client):
app.config['BOOTSTRAP_ICON_SIZE'] = 100
app.config['BOOTSTRAP_ICON_COLOR'] = 'success'

Expand All @@ -94,3 +94,70 @@ def icon():
assert 'width="100"' in data
assert 'height="100"' in data
assert 'text-success' in data


def test_render_icon_font(app, client):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the test for the config BOOTSTRAP_ICON_USE_FONT.

@app.route('/icon')
def icon():
return render_template_string('''
{% from 'bootstrap4/utils.html' import render_icon %}
{{ render_icon('heart', font=True) }}
''')

@app.route('/icon-size')
def icon_size():
return render_template_string('''
{% from 'bootstrap4/utils.html' import render_icon %}
{{ render_icon('heart', 32, font=True) }}
''')

@app.route('/icon-style')
def icon_style():
return render_template_string('''
{% from 'bootstrap4/utils.html' import render_icon %}
{{ render_icon('heart', color='primary', font=True) }}
''')

@app.route('/icon-color')
def icon_color():
return render_template_string('''
{% from 'bootstrap4/utils.html' import render_icon %}
{{ render_icon('heart', color='green', font=True) }}
''')

response = client.get('/icon')
data = response.get_data(as_text=True)
assert '<i class="bi-heart' in data
assert 'size: 1em;' in data

response = client.get('/icon-size')
data = response.get_data(as_text=True)
assert '<i class="bi-heart' in data
assert 'size: 32;' in data

response = client.get('/icon-style')
data = response.get_data(as_text=True)
assert '<i class="bi-heart' in data
assert ' text-primary' in data

response = client.get('/icon-color')
data = response.get_data(as_text=True)
assert '<i class="bi-heart' in data
assert 'color: green;' in data


def test_render_icon_font_config(app, client):
app.config['BOOTSTRAP_ICON_SIZE'] = 100
app.config['BOOTSTRAP_ICON_COLOR'] = 'success'

@app.route('/icon')
def icon():
return render_template_string('''
{% from 'bootstrap4/utils.html' import render_icon %}
{{ render_icon('heart', font=True) }}
''')

response = client.get('/icon')
data = response.get_data(as_text=True)
assert 'size: 100;' in data
assert 'text-success' in data
Loading