├── __init__.py ├── pages ├── themes.py ├── authentication.py ├── headers.py ├── leaflet.py ├── app_cards.py ├── buttons.py ├── carousels.py ├── tabs.py ├── menu.py ├── footers.py ├── uploaders.py ├── stats.py ├── navbars.py ├── tables.py └── home.py ├── gunicorn_config.py ├── .gitignore ├── .vscode └── settings.json ├── assets ├── dbc.png ├── logo.png ├── plotly.png ├── home_logo.png ├── mantine.png ├── tabs-light.png ├── leaflet-light.png ├── buttons-light.svg ├── style.css ├── carousels-light.svg ├── menus-light.svg ├── uploaders-light.svg ├── navbars-light.svg ├── tables.svg ├── stats-light.svg ├── cards-light.svg ├── headers-light.svg ├── footers-light.svg └── auth-light.svg ├── requirements.txt ├── Dockerfile ├── readme.md ├── components └── navbar.py ├── app.py └── utils.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/themes.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gunicorn_config.py: -------------------------------------------------------------------------------- 1 | bind = "0.0.0.0:8050" 2 | workers = 3 3 | timeout = 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.yml 3 | environment/ 4 | .vscode 5 | deploy.py -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.typeCheckingMode": "basic" 3 | } -------------------------------------------------------------------------------- /assets/dbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/dbc.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/plotly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/plotly.png -------------------------------------------------------------------------------- /assets/home_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/home_logo.png -------------------------------------------------------------------------------- /assets/mantine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/mantine.png -------------------------------------------------------------------------------- /assets/tabs-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/tabs-light.png -------------------------------------------------------------------------------- /assets/leaflet-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spyhuntr/dmc-dbc-building-blocks/HEAD/assets/leaflet-light.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.35.92 2 | dash==2.18.2 3 | dash-mantine-components==0.15.1 4 | gunicorn==23.0.0 5 | requests==2.32.3 -------------------------------------------------------------------------------- /assets/buttons-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.14 2 | 3 | WORKDIR /app 4 | COPY . /app 5 | 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHON UNBUFFERED 1 10 | 11 | RUN apk add --no-cache python3 py3-pip wget curl \ 12 | && pip3 install -r requirements.txt 13 | 14 | EXPOSE 8050 15 | 16 | ENTRYPOINT [ "gunicorn", "--config", "gunicorn_config.py", "app:server" ] -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap'); 2 | 3 | .card-hdr { 4 | background-color: #f8f9fa; 5 | transition: all .2s ease-in-out; 6 | } 7 | 8 | .card-hdr:hover { 9 | transform: scale(1.05); 10 | } 11 | 12 | #home_logo img { 13 | transform: rotate(-35deg); 14 | } 15 | 16 | .mantine-LoadingOverlay-root{ 17 | position: fixed; 18 | } 19 | 20 | .mantine-Switch-track { 21 | padding: 0.5rem; 22 | } -------------------------------------------------------------------------------- /assets/carousels-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menus-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/uploaders-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/navbars-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/tables.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DMC / DBC Building Blocks 2 | 3 | #### This page is designed to be a collection of front end building blocks built in either [Dash Mantine Components](https://www.dash-mantine-components.com/) and [Dash Bootstrap Components](https://dash-bootstrap-components.opensource.faculty.ai/) to plug and play into everyone's dash applications. 4 | 5 | 6 | 7 | ## Contributors Guide 8 | Thank you for contributing to the community supported `Dash Mantine & Dash Bootstrap Building Blocks` project! 9 | 10 | ## What are building blocks? 11 | Building blocks are small UI components that are part of an app. These building blocks are not a full app but rather can be added into an existing app to enhance the UI/UX experience of your dash application. 12 | 13 | ## Code Review Checklist 14 | 15 | ### Basic Requirements 16 | - [ ] Runs error free when added to an app running the latest version of dash, dash-bootstrap-components and/or dash-mantine-components. 17 | - [ ] Has no errors in the browser's developer console 18 | - [ ] Is a minimal example that only includes code necessary to display the building block 19 | - [ ] Layout is responsive - i.e. is functional and looks nice in various browser window sizes 20 | - [ ] Any required data is self-contained in the app or uses an external data source such as in [Plotly's repo (https://github.com/plotly/datasets). 21 | 22 | 23 | ### Style guide (suggestions only) 24 | 25 | - [ ] Code is formatted with black 26 | 27 | Naming conventions 28 | - [ ] Uses descriptive variable names 29 | - [ ] Uses python standard snake_case variable names ie `submit_button` 30 | - [ ] In the `style` prop, uses camel case: ie `style={"textAlign": "center"}` 31 | 32 | 33 | Concise code 34 | - [ ] Uses concise syntax available in Dash>=2.0 35 | For example use `dcc.Dropdown(df.columns)` rather than `dcc.Dropdown(options = [{'label':c, 'value':c} for c in df.columns]` 36 | - [ ] Does not put callback `Input()`s `Output()`s or `State()`s in a list 37 | - [ ] Does not include props that are set to the defaults for the component. For example, it's not necessary to include `multi=False` in the `dcc.Dropdown`. Check the reference section of the docs to see the defaults for the components. 38 | - [ ] Does not include unused imports 39 | - [ ] Uses Minimal comments - only those necessary to describe "why" rather than just describing what the code does. 40 | - [ ] Uses f-strings rather than `.format()` 41 | 42 | 43 | In apps using `dash-bootstrap-components`: 44 | - [ ] Uses `className` prop whenever possible instead of the `style` prop. For example: `className="bg-primary"` rather than `style={"backgroundColor": "blue"}` 45 | - [ ] Uses `dbc.Container` as the outer container of the app rather than `html.Div` 46 | 47 | In apps using both `dash-bootstrap-component`and `dash-mantine-components` 48 | - [ ] Uses one library as the primary library and only uses the secondary library where necessary. 49 | -------------------------------------------------------------------------------- /components/navbar.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash_mantine_components as dmc 3 | 4 | head = dmc.AppShellHeader( 5 | p="sm", 6 | mb="1rem", 7 | children=[ 8 | dmc.Grid([ 9 | dmc.GridCol([ 10 | dmc.Group([ 11 | html.Div([ 12 | dmc.Image( 13 | src="/assets/logo.png" 14 | )], style={'width': 50}), 15 | dmc.Text( 16 | "Dash Mantine & Dash Bootstrap Building Blocks", 17 | size="xl", 18 | c="dimmed" 19 | ) 20 | ], visibleFrom="md"), 21 | dmc.Text( 22 | "DMC & DBC", 23 | size="xl", 24 | hiddenFrom="md", 25 | c="dimmed" 26 | ) 27 | ], 28 | span='auto'), 29 | dmc.GridCol( 30 | dmc.Group([ 31 | dmc.Anchor( 32 | dmc.Avatar( 33 | src="/assets/mantine.png" 34 | ), 35 | href="https://www.dash-mantine-components.com/", 36 | target='_blank' 37 | ), 38 | dmc.Anchor( 39 | dmc.Avatar( 40 | src="/assets/plotly.png" 41 | ), 42 | href="https://dash.plotly.com/dash-ag-grid", 43 | target='_blank' 44 | ), 45 | dmc.Menu( 46 | [ 47 | dmc.MenuTarget( 48 | dmc.Avatar( 49 | src="/assets/dbc.png", 50 | style={'cursor': 'pointer'}), 51 | ), 52 | dmc.MenuDropdown( 53 | [ 54 | dmc.MenuItem( 55 | "Docs", 56 | href="https://dash-bootstrap-components.opensource.faculty.ai", 57 | target="_blank" 58 | ), 59 | dmc.MenuItem( 60 | "Theme Explorer", 61 | href="https://hellodash.pythonanywhere.com/", 62 | target="_blank" 63 | ) 64 | ] 65 | ), 66 | ] 67 | ) 68 | ], gap='xs', mt='6px'), span='content', p=0, pr='2rem' 69 | ) 70 | ]) 71 | ]) -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dash, dcc 2 | import dash_mantine_components as dmc 3 | import sys 4 | import utils as u 5 | sys.path.append('../components') 6 | 7 | dash._dash_renderer._set_react_version('18.2.0') 8 | 9 | from components.navbar import head 10 | 11 | stylesheets = [ 12 | "https://unpkg.com/@mantine/code-highlight@7/styles.css", 13 | "https://unpkg.com/@mantine/notifications@7/styles.css" 14 | ] 15 | 16 | app = Dash(__name__, 17 | external_scripts=[{ 18 | "src": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/js/all.min.js", 19 | "crossorigin": "anonymous" 20 | }], 21 | meta_tags=[ 22 | {'name':'DMC-DBC-Building-Blocks', 'content':'DMC/DBC Building Blocks'}, 23 | {'name': 'viewport', 'content': 'width=device-width, initial-scale=1'}, 24 | {'http-equiv': 'X-UA-Compatible', 'content': 'IE=edge'}, 25 | {'property':'og:url', 'content': 'https://dmc-dbc-building-blocks.onrender.com/'}, 26 | {'property':'og:type', 'content': 'website'}, 27 | {'property':'og:title', 'content': 'DMC/DBC Building Blocks'}, 28 | {'property':'og:description','content':'Building blocks for DMC/DBC'}, 29 | {'property':'og:image', 'content':'https://dmc-dbc-building-blocks.onrender.com/assets/logo.png'}, 30 | {'property':'og:image:width', 'content':'500'}, 31 | {'property':'og:image:height', 'content':'100'} 32 | ], 33 | external_stylesheets=stylesheets, 34 | suppress_callback_exceptions=True, 35 | use_pages=True 36 | ) 37 | 38 | app.title = 'DMC & DBC Building Blocks' 39 | 40 | app.layout = dmc.MantineProvider( 41 | theme={ 42 | 'fontFamily': '"Open Sans", sans-serif' 43 | }, 44 | children=[ 45 | dmc.NotificationProvider(position="top-right"), 46 | dmc.AppShell([ 47 | head, 48 | dmc.AppShellMain( 49 | children=[ 50 | dcc.Loading(dmc.Container( 51 | dash.page_container, 52 | id="page-container", 53 | pb='1rem', 54 | size='xl' 55 | ), type="dot", fullscreen=True), 56 | html.Div([ 57 | dmc.Center(id='contributors', children=u.build_contributors()), 58 | dmc.Center([ 59 | dmc.Text( 60 | "Contributors", 61 | ) 62 | ]) 63 | ]) 64 | ], style={'backgroundColor': '#f1f3f5'} 65 | ) 66 | ], 67 | header={"height": 60}, 68 | padding="xl" 69 | ) 70 | ] 71 | ) 72 | 73 | server = app.server 74 | 75 | if __name__ == '__main__': 76 | app.run(dev_tools_hot_reload=True, debug=True) 77 | -------------------------------------------------------------------------------- /assets/stats-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pages/authentication.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/authentication', title='Authentication') 8 | prefix = 'examples/auth/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='auth-back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Authentication", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="auth-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('auth-sample-container', 'children'), 34 | Input('auth-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "auth-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "auth-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'auth-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'auth-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'auth-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def build_examples(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist()[0] 85 | 86 | if switch: 87 | 88 | if filename.endswith(('py', 'css')): 89 | return dmc.CodeHighlight( 90 | language='python', 91 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 92 | ) 93 | 94 | else: 95 | 96 | image_file = u.get_example_image('auth', examples[id['index']]['image']) 97 | 98 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 99 | -------------------------------------------------------------------------------- /pages/headers.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/headers', title='Headers') 8 | prefix = 'examples/headers/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='headers_back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title("Headers", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="headers-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('headers-sample-container', 'children'), 34 | Input('headers-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "headers-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "headers-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'headers-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'headers-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'headers-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def build_examples(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist()[0] 85 | 86 | if switch: 87 | 88 | if filename.endswith(('py', 'css')): 89 | return dmc.CodeHighlight( 90 | language='python', 91 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 92 | ) 93 | 94 | else: 95 | 96 | image_file = u.get_example_image('headers', examples[id['index']]['image']) 97 | 98 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 99 | -------------------------------------------------------------------------------- /pages/leaflet.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/leaflet', title='Leaflet') 8 | prefix = 'examples/leaflet/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='leaflet_back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title("Leaflet", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="leaflet-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('leaflet-sample-container', 'children'), 34 | Input('leaflet-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "leaflet-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "leaflet-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'leaflet-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'leaflet-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'leaflet-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def build_examples(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist()[0] 85 | 86 | if switch: 87 | 88 | if filename.endswith(('py', 'css')): 89 | return dmc.CodeHighlight( 90 | language='python', 91 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 92 | ) 93 | 94 | else: 95 | 96 | image_file = u.get_example_image('leaflet', examples[id['index']]['image']) 97 | 98 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 99 | -------------------------------------------------------------------------------- /assets/cards-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pages/app_cards.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/app_cards', title='Cards') 8 | prefix = 'examples/cards/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='cards-back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Application Cards", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="cards-sample-container", 27 | children=[] 28 | ), 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('cards-sample-container', 'children'), 34 | Input('cards-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | 40 | new_sample = dmc.Card( 41 | withBorder=True, 42 | radius="md", 43 | mb="3rem", 44 | shadow='xl', 45 | children=[ 46 | dmc.CardSection([ 47 | dmc.Grid( 48 | children=[ 49 | u.card_hdr( 50 | card_info['card_heading'], card_info['user'], card_info['deps']), 51 | dmc.Switch( 52 | id={ 53 | "type": "cards-code-switch", 54 | "index": key 55 | }, 56 | size="xl", 57 | onLabel="Code", 58 | offLabel="Preview", 59 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 60 | ], gutter=0, grow=True, justify='space-around'), 61 | ], inheritPadding=True, withBorder=True, py="xs"), 62 | 63 | html.Div( 64 | id={ 65 | "type": "cards-rendering", 66 | "index": key 67 | }, 68 | children=[] 69 | ) 70 | ] 71 | ) 72 | 73 | children.append(new_sample) 74 | 75 | return children 76 | 77 | 78 | @callback( 79 | Output({'type': 'cards-rendering', 'index': MATCH}, 'children'), 80 | Input({'type': 'cards-code-switch', 'index': MATCH}, 'checked'), 81 | State({'type': 'cards-code-switch', 'index': MATCH}, 'id') 82 | ) 83 | def state_change(switch, id): 84 | 85 | filename = examples[id['index']]['file'].namelist()[0] 86 | 87 | if switch: 88 | 89 | if filename.endswith(('py', 'css')): 90 | return dmc.CodeHighlight( 91 | language='python', 92 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 93 | ) 94 | 95 | else: 96 | 97 | image_file = u.get_example_image('cards', examples[id['index']]['image']) 98 | 99 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 100 | -------------------------------------------------------------------------------- /pages/buttons.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/buttons', title='Buttons') 8 | prefix = 'examples/buttons/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='buttons-back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Buttons", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="buttons-sample-container", 27 | children=[] 28 | ), 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('buttons-sample-container', 'children'), 34 | Input('buttons-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | 40 | new_sample = dmc.Card( 41 | withBorder=True, 42 | radius="md", 43 | mb="3rem", 44 | shadow='xl', 45 | children=[ 46 | dmc.CardSection([ 47 | dmc.Grid( 48 | children=[ 49 | u.card_hdr( 50 | card_info['card_heading'], card_info['user'], card_info['deps']), 51 | dmc.Switch( 52 | id={ 53 | "type": "buttons-code-switch", 54 | "index": key 55 | }, 56 | size="xl", 57 | onLabel="Code", 58 | offLabel="Preview", 59 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 60 | ], gutter=0, grow=True, justify='space-around'), 61 | ], inheritPadding=True, withBorder=True, py="xs"), 62 | 63 | html.Div( 64 | id={ 65 | "type": "buttons-rendering", 66 | "index": key 67 | }, 68 | children=[] 69 | ) 70 | ] 71 | ) 72 | 73 | children.append(new_sample) 74 | 75 | return children 76 | 77 | 78 | @callback( 79 | Output({'type': 'buttons-rendering', 'index': MATCH}, 'children'), 80 | Input({'type': 'buttons-code-switch', 'index': MATCH}, 'checked'), 81 | State({'type': 'buttons-code-switch', 'index': MATCH}, 'id') 82 | ) 83 | def state_change(switch, id): 84 | 85 | filename = examples[id['index']]['file'].namelist()[0] 86 | 87 | if switch: 88 | 89 | if filename.endswith(('py', 'css')): 90 | return dmc.CodeHighlight( 91 | language='python', 92 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 93 | ) 94 | 95 | else: 96 | 97 | image_file = u.get_example_image('buttons', examples[id['index']]['image']) 98 | 99 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 100 | -------------------------------------------------------------------------------- /pages/carousels.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/carousels', title='Carousels') 8 | prefix = 'examples/carousels/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='carousels-back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Carousels", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="carousels-sample-container", 27 | children=[] 28 | ), 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('carousels-sample-container', 'children'), 34 | Input('carousels-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | 40 | new_sample = dmc.Card( 41 | withBorder=True, 42 | radius="md", 43 | mb="3rem", 44 | shadow='xl', 45 | children=[ 46 | dmc.CardSection([ 47 | dmc.Grid( 48 | children=[ 49 | u.card_hdr( 50 | card_info['card_heading'], card_info['user'], card_info['deps']), 51 | dmc.Switch( 52 | id={ 53 | "type": "carousels-code-switch", 54 | "index": key 55 | }, 56 | size="xl", 57 | onLabel="Code", 58 | offLabel="Preview", 59 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 60 | ], gutter=0, grow=True, justify='space-around'), 61 | ], inheritPadding=True, withBorder=True, py="xs"), 62 | 63 | html.Div( 64 | id={ 65 | "type": "carousels-rendering", 66 | "index": key 67 | }, 68 | children=[] 69 | ) 70 | ] 71 | ) 72 | 73 | children.append(new_sample) 74 | 75 | return children 76 | 77 | 78 | @callback( 79 | Output({'type': 'carousels-rendering', 'index': MATCH}, 'children'), 80 | Input({'type': 'carousels-code-switch', 'index': MATCH}, 'checked'), 81 | State({'type': 'carousels-code-switch', 'index': MATCH}, 'id') 82 | ) 83 | def state_change(switch, id): 84 | 85 | filename = examples[id['index']]['file'].namelist()[0] 86 | 87 | if switch: 88 | 89 | if filename.endswith(('py', 'css')): 90 | return dmc.CodeHighlight( 91 | language='python', 92 | code=examples[id['index']]['file'].read(filename).decode('utf-8') 93 | ) 94 | 95 | else: 96 | 97 | image_file = u.get_example_image('carousels', examples[id['index']]['image']) 98 | 99 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 100 | -------------------------------------------------------------------------------- /pages/tabs.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, ctx, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/tabs', title='Home') 8 | prefix = 'examples/tabs/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='tabs-back-btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Tabs", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="tabs-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('tabs-sample-container', 'children'), 34 | Input('tabs-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "tabs-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "tabs-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'tabs-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'tabs-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'tabs-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | return html.Div([ 98 | dmc.Grid([ 99 | dmc.GridCol([ 100 | dmc.Text("Python", p="xs"), 101 | dmc.CodeHighlight( 102 | language='python', 103 | code=py_file, 104 | style={'border': '1px solid #ececec'}, 105 | mr='1rem' 106 | )], span=6), 107 | dmc.GridCol([ 108 | dmc.Group([dmc.Text("CSS"), dmc.Text( 109 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 110 | dmc.CodeHighlight( 111 | language='css', 112 | code=css_file, 113 | style={'border': '1px solid #ececec'} 114 | )], span=6) 115 | ], gutter=0) 116 | ]) 117 | else: 118 | image_file = u.get_example_image('tabs', examples[id['index']]['image']) 119 | 120 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 121 | -------------------------------------------------------------------------------- /pages/menu.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/menus', title='Menus') 8 | prefix = 'examples/menus/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='menus-back-btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Menus", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="menus-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('menus-sample-container', 'children'), 34 | Input('menus-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "menus-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "menus-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'menus-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'menus-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'menus-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | return html.Div([ 98 | dmc.Grid([ 99 | dmc.GridCol([ 100 | dmc.Text("Python", p="xs"), 101 | dmc.CodeHighlight( 102 | language='python', 103 | code=py_file, 104 | style={'border': '1px solid #ececec'}, 105 | mr='1rem' 106 | )], span=6), 107 | dmc.GridCol([ 108 | dmc.Group([dmc.Text("CSS"), dmc.Text( 109 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 110 | dmc.CodeHighlight( 111 | language='css', 112 | code=css_file, 113 | style={'border': '1px solid #ececec'} 114 | )], span=6) 115 | ], gutter=0) 116 | ]) 117 | else: 118 | image_file = u.get_example_image('menus', examples[id['index']]['image']) 119 | 120 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 121 | -------------------------------------------------------------------------------- /pages/footers.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, ctx, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/footers', title='Home') 8 | prefix = 'examples/footers/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='footers-back-btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title("Footers", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="footers-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('footers-sample-container', 'children'), 34 | Input('footers-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "footers-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "footers-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'footers-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'footers-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'footers-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | return html.Div([ 98 | dmc.Grid([ 99 | dmc.GridCol([ 100 | dmc.Text("Python", p="xs"), 101 | dmc.CodeHighlight( 102 | language='python', 103 | code=py_file, 104 | style={'border': '1px solid #ececec'}, 105 | mr='1rem' 106 | )], span=6), 107 | dmc.GridCol([ 108 | dmc.Group([dmc.Text("CSS"), dmc.Text( 109 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 110 | dmc.CodeHighlight( 111 | language='css', 112 | code=css_file, 113 | style={'border': '1px solid #ececec'} 114 | )], span=6) 115 | ], gutter=0) 116 | ]) 117 | else: 118 | image_file = u.get_example_image('footers', examples[id['index']]['image']) 119 | 120 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 121 | -------------------------------------------------------------------------------- /pages/uploaders.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, ctx, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/uploaders', title='Uploaders') 8 | prefix = 'examples/uploaders/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='uploaders-back-btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Uploaders / Downloaders", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="uploaders-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('uploaders-sample-container', 'children'), 34 | Input('uploaders-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "uploaders-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "uploaders-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'uploaders-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'uploaders-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'uploaders-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | return html.Div([ 98 | dmc.Grid([ 99 | dmc.GridCol([ 100 | dmc.Text("Python", p="xs"), 101 | dmc.CodeHighlight( 102 | language='python', 103 | code=py_file, 104 | style={'border': '1px solid #ececec'}, 105 | mr='1rem' 106 | )], span=6), 107 | dmc.GridCol([ 108 | dmc.Group([dmc.Text("CSS"), dmc.Text( 109 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 110 | dmc.CodeHighlight( 111 | language='css', 112 | code=css_file, 113 | style={'border': '1px solid #ececec'} 114 | )], span=6) 115 | ], gutter=0) 116 | ]) 117 | else: 118 | image_file = u.get_example_image('uploaders', examples[id['index']]['image']) 119 | 120 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 121 | -------------------------------------------------------------------------------- /pages/stats.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, ctx, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/stats', title='Home') 8 | prefix = 'examples/stats/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='stats-back-btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Stats", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="stats-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('stats-sample-container', 'children'), 34 | Input('stats-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "stats-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "stats-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'stats-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'stats-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'stats-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | if css_file is None: 98 | return html.Div([ 99 | dmc.Grid([ 100 | dmc.GridCol([ 101 | dmc.Text("Python", p="xs"), 102 | dmc.CodeHighlight( 103 | language='python', 104 | code=py_file, 105 | style={'border': '1px solid #ececec'}, 106 | mr='1rem' 107 | )]) 108 | ], gutter=0) 109 | ]) 110 | 111 | else: 112 | 113 | return html.Div([ 114 | dmc.Grid([ 115 | dmc.GridCol([ 116 | dmc.Text("Python", p="xs"), 117 | dmc.CodeHighlight( 118 | language='python', 119 | code=py_file, 120 | style={'border': '1px solid #ececec'}, 121 | mr='1rem' 122 | )], span=6), 123 | dmc.GridCol([ 124 | dmc.Group([dmc.Text("CSS"), dmc.Text( 125 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 126 | dmc.CodeHighlight( 127 | language='css', 128 | code=css_file, 129 | style={'border': '1px solid #ececec'} 130 | )], span=6) 131 | ], gutter=0) 132 | ]) 133 | else: 134 | image_file = u.get_example_image('stats', examples[id['index']]['image']) 135 | 136 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 137 | -------------------------------------------------------------------------------- /pages/navbars.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/navbars', title='Home') 8 | prefix = 'examples/navbars/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='navbars-back-btn', 17 | href="../", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Navbars", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="navbars-sample-container", 27 | children=[] 28 | ) 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('navbars-sample-container', 'children'), 34 | Input('navbars-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | new_sample = dmc.Card( 40 | withBorder=True, 41 | radius="md", 42 | mb="3rem", 43 | shadow='xl', 44 | children=[ 45 | dmc.CardSection([ 46 | dmc.Grid( 47 | children=[ 48 | u.card_hdr( 49 | card_info['card_heading'], card_info['user'], card_info['deps']), 50 | dmc.Switch( 51 | id={ 52 | "type": "navbars-code-switch", 53 | "index": key 54 | }, 55 | size="xl", 56 | onLabel="Code", 57 | offLabel="Preview", 58 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 59 | ], gutter=0, grow=True, justify='space-around'), 60 | ], inheritPadding=True, withBorder=True, py="xs"), 61 | 62 | html.Div( 63 | id={ 64 | "type": "navbars-rendering", 65 | "index": key 66 | }, 67 | children=[] 68 | ) 69 | ] 70 | ) 71 | 72 | children.append(new_sample) 73 | 74 | return children 75 | 76 | 77 | @callback( 78 | Output({'type': 'navbars-rendering', 'index': MATCH}, 'children'), 79 | Input({'type': 'navbars-code-switch', 'index': MATCH}, 'checked'), 80 | State({'type': 'navbars-code-switch', 'index': MATCH}, 'id') 81 | ) 82 | def state_change(switch, id): 83 | 84 | filename = examples[id['index']]['file'].namelist() 85 | 86 | py_file = None 87 | css_file = None 88 | for name in filename: 89 | 90 | if name.endswith('py'): 91 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 92 | 93 | if name.endswith('css'): 94 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 95 | 96 | if switch: 97 | if css_file is None: 98 | return html.Div([ 99 | dmc.Grid([ 100 | dmc.GridCol([ 101 | dmc.Text("Python", p="xs"), 102 | dmc.CodeHighlight( 103 | language='python', 104 | code=py_file, 105 | style={'border': '1px solid #ececec'}, 106 | mr='1rem' 107 | )]) 108 | ], gutter=0) 109 | ]) 110 | 111 | else: 112 | 113 | return html.Div([ 114 | dmc.Grid([ 115 | dmc.GridCol([ 116 | dmc.Text("Python", p="xs"), 117 | dmc.CodeHighlight( 118 | language='python', 119 | code=py_file, 120 | style={'border': '1px solid #ececec'}, 121 | mr='1rem' 122 | )], span=6), 123 | dmc.GridCol([ 124 | dmc.Group([dmc.Text("CSS"), dmc.Text( 125 | "Add to ./assets/style.css in your app", size='xs')], p='xs'), 126 | dmc.CodeHighlight( 127 | language='css', 128 | code=css_file, 129 | style={'border': '1px solid #ececec'} 130 | )], span=6) 131 | ], gutter=0) 132 | ]) 133 | else: 134 | image_file = u.get_example_image('navbars', examples[id['index']]['image']) 135 | 136 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') -------------------------------------------------------------------------------- /pages/tables.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import callback, Output, Input, State, MATCH, html, dcc 4 | import dash_mantine_components as dmc 5 | import utils as u 6 | 7 | dash.register_page(__name__, path='/tables', title='Tables') 8 | prefix = 'examples/tables/' 9 | 10 | examples = u.get_example_files(prefix) 11 | 12 | layout = dmc.Grid( 13 | children=[ 14 | dmc.GridCol( 15 | dcc.Link( 16 | id='tables-back_btn', 17 | href="/", 18 | children=[ 19 | dmc.Button("< Back to all categories", variant="outline") 20 | ]) 21 | ), 22 | dmc.GridCol( 23 | dmc.Title(f"Tables", order=1) 24 | ), 25 | dmc.GridCol( 26 | id="tables-sample-container", 27 | children=[] 28 | ), 29 | ]) 30 | 31 | 32 | @callback( 33 | Output('tables-sample-container', 'children'), 34 | Input('tables-sample-container', 'children') 35 | ) 36 | def build_layout(children): 37 | 38 | for key, card_info in enumerate(examples): 39 | 40 | new_sample = dmc.Card( 41 | withBorder=True, 42 | radius="md", 43 | mb="3rem", 44 | shadow='xl', 45 | children=[ 46 | dmc.CardSection([ 47 | dmc.Grid( 48 | children=[ 49 | u.card_hdr( 50 | card_info['card_heading'], card_info['user'], card_info['deps']), 51 | dmc.Switch( 52 | id={ 53 | "type": "tables-code-switch", 54 | "index": key 55 | }, 56 | size="xl", 57 | onLabel="Code", 58 | offLabel="Preview", 59 | style={"marginTop": "-0.2rem", 'marginRight':'0.5rem'}), 60 | ], gutter=0, grow=True, justify='space-around'), 61 | ], inheritPadding=True, withBorder=True, py="xs"), 62 | 63 | html.Div( 64 | id={ 65 | "type": "tables-rendering", 66 | "index": key 67 | }, 68 | children=[] 69 | ) 70 | ] 71 | ) 72 | 73 | children.append(new_sample) 74 | 75 | return children 76 | 77 | 78 | @callback( 79 | Output({'type': 'tables-rendering', 'index': MATCH}, 'children'), 80 | Input({'type': 'tables-code-switch', 'index': MATCH}, 'checked'), 81 | State({'type': 'tables-code-switch', 'index': MATCH}, 'id') 82 | ) 83 | def state_change(switch, id): 84 | 85 | filename = examples[id['index']]['file'].namelist() 86 | 87 | py_file = None 88 | css_file = None 89 | js_file = None 90 | for name in filename: 91 | 92 | if name.endswith('py'): 93 | py_file = examples[id['index']]['file'].read(name).decode('utf-8') 94 | 95 | if name.endswith('css'): 96 | css_file = examples[id['index']]['file'].read(name).decode('utf-8') 97 | 98 | if name.endswith('js'): 99 | js_file = examples[id['index']]['file'].read(name).decode('utf-8') 100 | 101 | 102 | if switch: 103 | return html.Div([ 104 | dmc.Grid([ 105 | dmc.GridCol([ 106 | dmc.Text("Python", p="xs"), 107 | dmc.CodeHighlight( 108 | language='python', 109 | code=py_file, 110 | style={'border': '1px solid #ececec'}, 111 | mr='1rem' 112 | )], span=6), 113 | dmc.GridCol([ 114 | dmc.Group([ 115 | dmc.Text("CSS"), 116 | dmc.Text("Add to ./assets/style.css in your app", size='xs') 117 | ], p='xs'), 118 | dmc.CodeHighlight( 119 | language='css', 120 | code=css_file, 121 | style={'border': '1px solid #ececec'} 122 | ), 123 | dmc.Group([ 124 | dmc.Text("JS"), 125 | dmc.Text("Add to ./assets folder in your app", size='xs') 126 | ], p='xs'), 127 | dmc.CodeHighlight( 128 | language='js', 129 | code=js_file, 130 | style={'border': '1px solid #ececec'} 131 | ) 132 | ], span=6) 133 | ], gutter=0) 134 | ]) 135 | else: 136 | image_file = u.get_example_image('tables', examples[id['index']]['image']) 137 | 138 | return dmc.Center(html.Img(src=f'data:image/png;base64,{image_file}'), p='xl') 139 | -------------------------------------------------------------------------------- /assets/headers-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import dash_mantine_components as dmc 3 | import logging 4 | from botocore.exceptions import ClientError 5 | import requests 6 | from zipfile import ZipFile 7 | import io 8 | from dash import html 9 | from typing import List, Union 10 | from base64 import b64encode 11 | 12 | bucket = 'dmc-dbc-building-blocks' 13 | 14 | s3_client = boto3.client('s3') 15 | 16 | def card_hdr(title: str, user: str, deps: str): 17 | 18 | link = dmc.Anchor( 19 | href=f"https://github.com/{user}", 20 | children=['@' + user], 21 | size="xs", 22 | ml="-0.7rem", 23 | style={'color': 'rgb(134, 142, 150)'}) 24 | 25 | menu_items: List[Union[dmc.MenuItem, dmc.MenuLabel]] = [dmc.MenuLabel("Dependencies")] 26 | for dep in list(deps.split(',')): 27 | menu_item = dmc.MenuItem(dep) 28 | 29 | menu_items.append(menu_item) 30 | 31 | menu = dmc.Menu([ 32 | dmc.MenuTarget( 33 | dmc.Avatar( 34 | html.I(className='fas fa-sitemap fa-fw'), size='sm' 35 | ) 36 | ), 37 | dmc.MenuDropdown( 38 | children=menu_items 39 | 40 | ), 41 | ], trigger='hover') 42 | 43 | return dmc.GridCol( 44 | children=[ 45 | dmc.Group([ 46 | dmc.Text( 47 | title, 48 | size="1.25rem" 49 | ), 50 | dmc.Text( 51 | 'Made by', 52 | size="xs", 53 | ), 54 | link, 55 | menu 56 | ], visibleFrom='sm'), 57 | dmc.Text( 58 | title, 59 | size="1.25rem", 60 | hiddenFrom='sm' 61 | ) 62 | ], style={'alignContent': 'center'}, span='auto') 63 | 64 | 65 | def build_contributors(): 66 | 67 | contributors = ['snehilvj', 'tcbegley', 'AnnMarieW', 'Spyhuntr', 'Sohibjon', 'pip-install-python', 68 | 'amihaiOff', 'BSd3v', 'RaghunathDewal', 'topaccina', 'martin2097'] 69 | contrib_group = [] 70 | for contributor in contributors: 71 | 72 | new_link = dmc.Anchor( 73 | dmc.Tooltip( 74 | label=contributor, 75 | withArrow=True, 76 | children=[ 77 | dmc.Avatar( 78 | src=f"https://github.com/{contributor}.png", radius="xl" 79 | ) 80 | ] 81 | ), 82 | href=f"https://github.com/{contributor}", 83 | target='_blank' 84 | ) 85 | 86 | contrib_group.append(new_link) 87 | 88 | return dmc.AvatarGroup(contrib_group) 89 | 90 | 91 | def create_presigned_post(bucket_name, object_name): 92 | """Generate a presigned URL S3 POST request to upload a file 93 | 94 | :param bucket_name: string 95 | :param object_name: string 96 | :param expiration: Time in seconds for the presigned URL to remain valid 97 | :return: Dictionary with the following keys: 98 | url: URL to post to 99 | fields: Dictionary of form fields and values to submit with the POST 100 | :return: None if error. 101 | """ 102 | 103 | try: 104 | response = s3_client.generate_presigned_post(bucket_name, 105 | object_name, 106 | ExpiresIn=3600) 107 | except ClientError as e: 108 | logging.error(e) 109 | return None 110 | 111 | # The response contains the presigned URL and required fields 112 | return response 113 | 114 | 115 | def upload_file(contents, filename, date): 116 | 117 | result = create_presigned_post(bucket, 'uploads/' + filename) 118 | 119 | if result is not None: 120 | # Upload file to S3 using presigned URL 121 | files = {'file': contents} 122 | r = requests.post(result['url'], data=result['fields'], files=files) 123 | 124 | return r 125 | 126 | 127 | def get_example_files(prefix): 128 | 129 | example_files = s3_client.list_objects_v2( 130 | Bucket=bucket, 131 | Prefix=prefix, 132 | Delimiter='/' 133 | ) 134 | 135 | file_list = [] 136 | for obj in example_files.get('Contents'): 137 | 138 | if obj['Size'] > 0: 139 | 140 | key = obj.get('Key') 141 | 142 | # sample file 143 | file = s3_client.get_object(Bucket=bucket, Key=key) 144 | # meta data on the file that has user-id etc on it 145 | header = s3_client.head_object(Bucket=bucket, Key=key) 146 | 147 | f = ZipFile(io.BytesIO(file['Body'].read()), 'r') 148 | user = header['ResponseMetadata']['HTTPHeaders']['x-amz-meta-userid'] 149 | card_heading = header['ResponseMetadata']['HTTPHeaders']['x-amz-meta-header'] 150 | image_file = header['ResponseMetadata']['HTTPHeaders']['x-amz-meta-image'] 151 | dependencies = header['ResponseMetadata']['HTTPHeaders']['x-amz-meta-deps'] 152 | 153 | file_payload = {'file': f, 154 | 'user': user, 155 | 'card_heading': card_heading, 156 | 'image': image_file, 157 | 'deps': dependencies} 158 | 159 | file_list.append(file_payload) 160 | 161 | return file_list 162 | 163 | 164 | def file_listings(prefix): 165 | 166 | num_files = 0 167 | 168 | example_files = s3_client.list_objects_v2( 169 | Bucket=bucket, 170 | Prefix=prefix, 171 | Delimiter='/' 172 | )['Contents'] 173 | 174 | for content in example_files: 175 | if content['Size'] > 0: 176 | num_files += 1 177 | 178 | return num_files 179 | 180 | 181 | def get_example_image(prefix, image_name): 182 | 183 | image = s3_client.get_object(Bucket=bucket, Key=f'examples/{prefix}/images/{image_name}') 184 | 185 | return b64encode(image['Body'].read()).decode('utf-8') -------------------------------------------------------------------------------- /assets/footers-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pages/home.py: -------------------------------------------------------------------------------- 1 | 2 | import dash 3 | from dash import html, dcc, Output, Input, callback, State 4 | import dash_mantine_components as dmc 5 | from utils import upload_file, file_listings 6 | import base64 7 | 8 | dash.register_page(__name__, path='/', title='DMC/DBC Home') 9 | 10 | layout = html.Div([ 11 | dmc.Grid( 12 | children=[ 13 | dmc.GridCol([ 14 | dmc.Center( 15 | style={"height": '100%'}, 16 | children=[ 17 | dmc.Stack([ 18 | dmc.Text( 19 | "Build your next Dash application even faster with premade responsive components designed" 20 | " and built by DMC and DBC maintainers and community.", 21 | size="xl", 22 | style={'textAlign': 'center'} 23 | ), 24 | dmc.Text("Contribute Building Block", style={'textAlign': 'center'}), 25 | dmc.Group([ 26 | dcc.Link( 27 | id='github-btn', 28 | href="https://github.com/Spyhuntr/dmc-dbc-building-blocks/issues/1#issue-1506988208", 29 | target='_blank', 30 | children=[ 31 | dmc.Button( 32 | "Github", 33 | leftSection=html.I( 34 | className='fa-brands fa-github fa-lg fa-fw'), 35 | ) 36 | ] 37 | ), 38 | dmc.Button( 39 | "Upload", 40 | id='add-building-block-btn', 41 | rightSection=html.I(className='fas fa-upload fa-lg fa-fw'), 42 | ) 43 | ]) 44 | ], align='center') 45 | ] 46 | ) 47 | ], span=8), 48 | dmc.GridCol([ 49 | dmc.Center( 50 | style={"height": '100%'}, 51 | children=[ 52 | dmc.Image( 53 | id='home_logo', 54 | src="/assets/home_logo.png", 55 | style={'width': 100, 'transform': 'rotate(-35deg)'} 56 | ) 57 | ] 58 | ) 59 | ], span=2, offset=1) 60 | ], mb='1rem'), 61 | dmc.SimpleGrid(id='card-grid', cols={"base": 1, "sm": 2, "lg": 4}, children=[]), 62 | dmc.Modal( 63 | id="upload-modal", 64 | size='xl', 65 | children=[ 66 | dmc.Text("Upload a zip file of your code. The filename should include your github handle " 67 | " if you would like to get credit for your building block. " 68 | "I will get a notification when new building blocks have been submitted.", 69 | style={'textAlign': 'center'}), 70 | dmc.Space(h=10), 71 | dmc.Text("What are building blocks?", 72 | style={'textAlign': 'center'}), 73 | dmc.Text("Building blocks are small UI components that are part of an app. " 74 | "These building blocks are not a full app but rather can be added into an " 75 | "existing app to enhance the UI/UX experience of your dash application.", 76 | style={'textAlign': 'center'}), 77 | dmc.Space(h=20), 78 | dcc.Upload( 79 | id='upload', 80 | max_size=3145728, 81 | accept='.zip,.py,.css', 82 | children=html.Div([ 83 | 'Drag and Drop or ', 84 | dmc.Anchor( 85 | "Select Files", 86 | href="#", 87 | ), 88 | ]), 89 | style={ 90 | 'lineHeight': '60px', 91 | 'borderWidth': '1px', 92 | 'borderStyle': 'dashed', 93 | 'borderRadius': '5px', 94 | 'textAlign': 'center' 95 | }, 96 | ), 97 | html.Div(id='output-upload') 98 | ]) 99 | ] 100 | ) 101 | 102 | 103 | @callback(Output('card-grid', 'children'), 104 | Input('card-grid', 'children'), 105 | State('card-grid', 'children'), 106 | ) 107 | def create_cards(_, children): 108 | 109 | card_info = [ 110 | {'image': '/assets/cards-light.svg', 'title': 'Application Cards', 'page': '/app_cards', 'prefix': 'examples/cards/'}, 111 | {'image': '/assets/navbars-light.svg', 'title': 'Navbars', 'page': '/navbars', 'prefix': 'examples/navbars/'}, 112 | {'image': '/assets/footers-light.svg', 'title': 'Footers', 'page': '/footers', 'prefix': 'examples/footers/'}, 113 | {'image': '/assets/headers-light.svg', 'title': 'Headers', 'page': '/headers', 'prefix': 'examples/headers/'}, 114 | {'image': '/assets/menus-light.svg', 'title': 'Menus', 'page': '/menus', 'prefix': 'examples/menus/'}, 115 | {'image': '/assets/uploaders-light.svg', 'title': 'Uploaders / Downloaders', 'page': '/uploaders', 'prefix': 'examples/uploaders/'}, 116 | {'image': '/assets/stats-light.svg', 'title': 'Stats', 'page': '/stats', 'prefix': 'examples/stats/'}, 117 | {'image': '/assets/tabs-light.png', 'title': 'Tabs', 'page': '/tabs', 'prefix': 'examples/tabs/'}, 118 | {'image': '/assets/auth-light.svg', 'title': 'Authentication', 'page': '/authentication', 'prefix': 'examples/auth/'}, 119 | {'image': '/assets/tables.svg', 'title': 'Tables', 'page': '/tables', 'prefix': 'examples/tables/'}, 120 | {'image': '/assets/carousels-light.svg', 'title': 'Carousels', 'page': '/carousels', 'prefix': 'examples/carousels/'}, 121 | {'image': '/assets/buttons-light.svg', 'title': 'Buttons', 'page': '/buttons', 'prefix': 'examples/buttons/'}, 122 | {'image': '/assets/leaflet-light.svg', 'title': 'Leaflet', 'page': '/leaflet', 'prefix': 'examples/leaflet/'} 123 | ] 124 | 125 | for card in card_info: 126 | card = html.Div([ 127 | dmc.Paper( 128 | children=[ 129 | dmc.Anchor( 130 | href=card['page'], 131 | children=[ 132 | html.Div( 133 | dmc.Image( 134 | src=card['image'], 135 | ), className='card-hdr' 136 | ), 137 | dmc.Text( 138 | card['title'], 139 | pt="md", 140 | pl="md" 141 | ), 142 | dmc.Text( 143 | f"{file_listings(card['prefix'])} Components", 144 | size="xs", 145 | pl="md", 146 | pb="md", 147 | c='dimmed' 148 | ) 149 | ], 150 | variant="text", 151 | style={'textDecoration': 'None'} 152 | )], 153 | shadow="xl", 154 | withBorder=True, 155 | radius="lg", 156 | style={"overflow": "hidden"} 157 | )]) 158 | children.append(card) 159 | 160 | return children 161 | 162 | 163 | @callback( 164 | Output('upload-modal', 'opened'), 165 | Input('add-building-block-btn', 'n_clicks'), 166 | prevent_initial_call=True, 167 | ) 168 | def open_modal(_): 169 | 170 | if _ is not None: 171 | return True 172 | 173 | 174 | @callback(Output('output-upload', 'children'), 175 | Input('upload', 'contents'), 176 | State('upload', 'filename'), 177 | State('upload', 'last_modified'), 178 | prevent_initial_call=True) 179 | def update_output(content, name, date): 180 | 181 | # the content needs to be split. It contains the type and the real content 182 | content_type, content_string = content.split(',') 183 | # Decode the base64 string 184 | content_decoded = base64.b64decode(content_string) 185 | 186 | message = upload_file(content_decoded, name, date) 187 | 188 | if message is not None: 189 | if message.status_code == 204: 190 | return dmc.Notification( 191 | id="upload-notify", 192 | action="show", 193 | message="File successfully uploaded!", 194 | icon=html.I(className='fas fa-check fa-lg fa-fw') 195 | ) 196 | else: 197 | return f"{message.status_code} - {message.reason}" -------------------------------------------------------------------------------- /assets/auth-light.svg: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------