Skip to content

Commit

Permalink
support font icons
Browse files Browse the repository at this point in the history
  • Loading branch information
PanderMusubi committed Jan 18, 2025
1 parent ac1f3c0 commit c3f60b2
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 21 deletions.
3 changes: 3 additions & 0 deletions docs/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ 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)``. 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,13 +623,14 @@ Example
API
~~~~

.. py:function:: render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None)
.. py:function:: render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=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 font: Default to generate ``<svg></svg>``, if set to ``True``, it will generate ``<i></i>`` which uses icon font.
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
26 changes: 19 additions & 7 deletions examples/bootstrap4/templates/icon.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@
{% 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 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>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/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
26 changes: 19 additions & 7 deletions examples/bootstrap5/templates/icon.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@
{% 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 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>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 %}
16 changes: 16 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 @@ -122,6 +123,19 @@ def load_css(self, version=None, bootstrap_sri=None, bootswatch_theme=None):
css = f'<link rel="stylesheet" href="{boostrap_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']

Check warning on line 131 in flask_bootstrap/__init__.py

View check run for this annotation

Codecov / codecov/patch

flask_bootstrap/__init__.py#L131

Added line #L131 was not covered by tests
if serve_local:
icons_url = url_for('bootstrap.static', filename='font/bootstrap-icons.min.css')

Check warning on line 133 in flask_bootstrap/__init__.py

View check run for this annotation

Codecov / codecov/patch

flask_bootstrap/__init__.py#L133

Added line #L133 was not covered by tests
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)

Check warning on line 137 in flask_bootstrap/__init__.py

View check run for this annotation

Codecov / codecov/patch

flask_bootstrap/__init__.py#L135-L137

Added lines #L135 - L137 were not covered by tests

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 +241,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 +277,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.
6 changes: 5 additions & 1 deletion flask_bootstrap/templates/base/utils.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
{% endmacro %}


{% macro render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None) %}
{% macro render_icon(name, size=config.BOOTSTRAP_ICON_SIZE, color=config.BOOTSTRAP_ICON_COLOR, title=None, desc=None, font=False) %}
{% 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 %}"{% elif color in bootstrap_colors %} text-{{ color }}"{% else %}" style="color: {{ color }}"{% endif %}
width="{{ size }}" height="{{ size }}" fill="currentColor">
{% 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 %}


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 @@ -77,7 +77,7 @@ def icon_desc():
assert '<desc>A heart.</desc>' 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 @@ -93,3 +93,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):
@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

0 comments on commit c3f60b2

Please sign in to comment.