Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support font icons #380

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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']
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 +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
Loading