dash-dracula-theme 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dash_dracula/__init__.py +72 -0
- dash_dracula/assets/dracula.css +493 -0
- dash_dracula/bootstrap.py +226 -0
- dash_dracula/colors.py +94 -0
- dash_dracula/pdf.py +161 -0
- dash_dracula/plotly_template.py +47 -0
- dash_dracula_theme-0.1.0.dist-info/METADATA +102 -0
- dash_dracula_theme-0.1.0.dist-info/RECORD +11 -0
- dash_dracula_theme-0.1.0.dist-info/WHEEL +5 -0
- dash_dracula_theme-0.1.0.dist-info/licenses/LICENSE +21 -0
- dash_dracula_theme-0.1.0.dist-info/top_level.txt +1 -0
dash_dracula/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dash_dracula
|
|
3
|
+
~~~~~~~~~~~~
|
|
4
|
+
Dracula dark/light theme for Plotly Dash applications.
|
|
5
|
+
|
|
6
|
+
Quick start::
|
|
7
|
+
|
|
8
|
+
import dash
|
|
9
|
+
import dash_bootstrap_components as dbc
|
|
10
|
+
import dash_dracula as dr
|
|
11
|
+
|
|
12
|
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
|
|
13
|
+
theme_store = dr.apply_theme(app) # wire CSS + toggle callback
|
|
14
|
+
|
|
15
|
+
# In your layout:
|
|
16
|
+
# dr.theme_toggle_button() — the ☀️/🌙 button component
|
|
17
|
+
# theme_store — dcc.Store that must be in the layout
|
|
18
|
+
|
|
19
|
+
# In your figures:
|
|
20
|
+
# template="dracula" — auto-registered on import
|
|
21
|
+
# line_color=dr.CYAN — use palette constants directly
|
|
22
|
+
|
|
23
|
+
# For PDF export (always light):
|
|
24
|
+
# from dash_dracula.pdf import fig_to_print_image
|
|
25
|
+
# img = fig_to_print_image(fig, w_mm=170, h_mm=100)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# ── Auto-register the Plotly template on import ──────────────────────────────
|
|
29
|
+
from .plotly_template import register as _register, TEMPLATE_NAME
|
|
30
|
+
|
|
31
|
+
_register()
|
|
32
|
+
|
|
33
|
+
# ── Colour palette ───────────────────────────────────────────────────────────
|
|
34
|
+
from .colors import (
|
|
35
|
+
BACKGROUND,
|
|
36
|
+
CURRENT_LINE,
|
|
37
|
+
SELECTION,
|
|
38
|
+
FOREGROUND,
|
|
39
|
+
COMMENT,
|
|
40
|
+
RED,
|
|
41
|
+
ORANGE,
|
|
42
|
+
YELLOW,
|
|
43
|
+
GREEN,
|
|
44
|
+
CYAN,
|
|
45
|
+
PURPLE,
|
|
46
|
+
PINK,
|
|
47
|
+
COLORWAY,
|
|
48
|
+
NEUTRAL_COLORWAY,
|
|
49
|
+
PRINT_COLORS,
|
|
50
|
+
LIGHT_COLORS,
|
|
51
|
+
AS_DICT as PALETTE,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# ── Bootstrap / theme wiring ─────────────────────────────────────────────────
|
|
55
|
+
from .bootstrap import apply_theme, theme_toggle_button
|
|
56
|
+
|
|
57
|
+
# ── PDF utility (lazy — reportlab not required for basic theme usage) ─────────
|
|
58
|
+
from .pdf import fig_to_print_image
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
# Template
|
|
62
|
+
"TEMPLATE_NAME",
|
|
63
|
+
# Official palette
|
|
64
|
+
"BACKGROUND", "CURRENT_LINE", "SELECTION", "FOREGROUND", "COMMENT",
|
|
65
|
+
"RED", "ORANGE", "YELLOW", "GREEN", "CYAN", "PURPLE", "PINK",
|
|
66
|
+
# Convenience collections
|
|
67
|
+
"COLORWAY", "NEUTRAL_COLORWAY", "PRINT_COLORS", "LIGHT_COLORS", "PALETTE",
|
|
68
|
+
# Bootstrap helpers
|
|
69
|
+
"apply_theme", "theme_toggle_button",
|
|
70
|
+
# PDF
|
|
71
|
+
"fig_to_print_image",
|
|
72
|
+
]
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/* =============================================================
|
|
2
|
+
Dracula Theme
|
|
3
|
+
Palette: https://draculatheme.com/contribute
|
|
4
|
+
============================================================= */
|
|
5
|
+
|
|
6
|
+
/* ── CSS variables ─────────────────────────────────────────── */
|
|
7
|
+
:root {
|
|
8
|
+
--dr-bg: #282a36;
|
|
9
|
+
--dr-surface: #44475a;
|
|
10
|
+
--dr-fg: #f8f8f2;
|
|
11
|
+
--dr-comment: #6272a4;
|
|
12
|
+
--dr-cyan: #8be9fd;
|
|
13
|
+
--dr-green: #50fa7b;
|
|
14
|
+
--dr-orange: #ffb86c;
|
|
15
|
+
--dr-pink: #ff79c6;
|
|
16
|
+
--dr-purple: #bd93f9;
|
|
17
|
+
--dr-red: #ff5555;
|
|
18
|
+
--dr-yellow: #f1fa8c;
|
|
19
|
+
|
|
20
|
+
/* Override Bootstrap 5 vars */
|
|
21
|
+
--bs-body-bg: var(--dr-bg);
|
|
22
|
+
--bs-body-color: var(--dr-fg);
|
|
23
|
+
--bs-card-bg: var(--dr-surface);
|
|
24
|
+
--bs-card-border-color:var(--dr-comment);
|
|
25
|
+
--bs-border-color: var(--dr-comment);
|
|
26
|
+
--bs-link-color: var(--dr-cyan);
|
|
27
|
+
--bs-link-hover-color: var(--dr-purple);
|
|
28
|
+
--bs-secondary-color: var(--dr-comment);
|
|
29
|
+
--bs-light-rgb: 68, 71, 90;
|
|
30
|
+
--bs-dark-rgb: 40, 42, 54;
|
|
31
|
+
--bs-nav-tabs-border-color: var(--dr-comment);
|
|
32
|
+
--bs-nav-tabs-link-active-bg: var(--dr-surface);
|
|
33
|
+
--bs-nav-tabs-link-active-color: var(--dr-cyan);
|
|
34
|
+
--bs-nav-tabs-link-hover-border-color: var(--dr-comment);
|
|
35
|
+
/* Override Bootstrap primary so text-primary / btn-primary use Dracula purple */
|
|
36
|
+
--bs-primary: #bd93f9;
|
|
37
|
+
--bs-primary-rgb: 189, 147, 249;
|
|
38
|
+
--bs-link-color-rgb: 139, 233, 253;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Base ───────────────────────────────────────────────────── */
|
|
42
|
+
body {
|
|
43
|
+
background-color: var(--dr-bg) !important;
|
|
44
|
+
color: var(--dr-fg) !important;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ── Typography ─────────────────────────────────────────────── */
|
|
48
|
+
h1, h2, h3, h4, h5, h6 { color: var(--dr-fg); }
|
|
49
|
+
hr { border-color: var(--dr-comment); opacity: 0.5; }
|
|
50
|
+
.text-muted { color: var(--dr-comment) !important; }
|
|
51
|
+
.text-primary { color: #8be9fd !important; } /* cyan, readable on any Dracula bg */
|
|
52
|
+
.text-success { color: #50fa7b !important; }
|
|
53
|
+
.text-warning { color: #f1fa8c !important; }
|
|
54
|
+
.text-danger { color: #ff5555 !important; }
|
|
55
|
+
.text-info { color: #8be9fd !important; }
|
|
56
|
+
.fw-semibold { font-weight: 600; }
|
|
57
|
+
|
|
58
|
+
/* ── Cards ──────────────────────────────────────────────────── */
|
|
59
|
+
.card {
|
|
60
|
+
background-color: #44475a !important;
|
|
61
|
+
border-color: #6272a4 !important;
|
|
62
|
+
color: #f8f8f2 !important;
|
|
63
|
+
}
|
|
64
|
+
.card-title { color: #8be9fd !important; }
|
|
65
|
+
|
|
66
|
+
/* ── Buttons ────────────────────────────────────────────────── */
|
|
67
|
+
.btn-primary { background-color: #bd93f9; border-color: #bd93f9; color: #282a36; }
|
|
68
|
+
.btn-primary:hover { background-color: #ff79c6; border-color: #ff79c6; }
|
|
69
|
+
.btn-outline-primary { border-color: #bd93f9; color: #bd93f9; }
|
|
70
|
+
.btn-outline-primary:hover { background-color: #bd93f9; color: #282a36; }
|
|
71
|
+
.btn-success { background-color: #50fa7b; border-color: #50fa7b; color: #282a36; }
|
|
72
|
+
.btn-outline-secondary { border-color: #6272a4; color: #6272a4; }
|
|
73
|
+
.btn-outline-secondary:hover { background-color: #6272a4; color: #282a36; }
|
|
74
|
+
.btn-outline-info { border-color: #8be9fd; color: #8be9fd; }
|
|
75
|
+
.btn-outline-info:hover { background-color: #8be9fd; color: #282a36; }
|
|
76
|
+
|
|
77
|
+
/* ── Forms ──────────────────────────────────────────────────── */
|
|
78
|
+
.form-control,
|
|
79
|
+
.form-select,
|
|
80
|
+
input[type="text"],
|
|
81
|
+
input[type="number"] {
|
|
82
|
+
background-color: var(--dr-surface) !important;
|
|
83
|
+
color: var(--dr-fg) !important;
|
|
84
|
+
border-color: var(--dr-comment) !important;
|
|
85
|
+
}
|
|
86
|
+
.form-control:focus,
|
|
87
|
+
.form-select:focus,
|
|
88
|
+
input:focus {
|
|
89
|
+
background-color: var(--dr-surface) !important;
|
|
90
|
+
color: var(--dr-fg) !important;
|
|
91
|
+
border-color: var(--dr-purple) !important;
|
|
92
|
+
box-shadow: 0 0 0 0.2rem rgba(189, 147, 249, 0.25) !important;
|
|
93
|
+
}
|
|
94
|
+
.form-text { color: var(--dr-comment) !important; }
|
|
95
|
+
label { color: var(--dr-fg) !important; }
|
|
96
|
+
|
|
97
|
+
/* ── Tabs ───────────────────────────────────────────────────── */
|
|
98
|
+
.nav-tabs { border-bottom-color: var(--dr-comment) !important; }
|
|
99
|
+
.nav-tabs .nav-link {
|
|
100
|
+
color: var(--dr-comment) !important;
|
|
101
|
+
border-color: transparent !important;
|
|
102
|
+
}
|
|
103
|
+
.nav-tabs .nav-link:hover {
|
|
104
|
+
color: var(--dr-fg) !important;
|
|
105
|
+
border-color: var(--dr-comment) !important;
|
|
106
|
+
}
|
|
107
|
+
.nav-tabs .nav-link.active {
|
|
108
|
+
background-color: var(--dr-surface) !important;
|
|
109
|
+
color: var(--dr-cyan) !important;
|
|
110
|
+
border-color: var(--dr-comment) var(--dr-comment) var(--dr-surface) !important;
|
|
111
|
+
}
|
|
112
|
+
.tab-content { background-color: var(--dr-bg) !important; }
|
|
113
|
+
|
|
114
|
+
/* ── Accordion ──────────────────────────────────────────────── */
|
|
115
|
+
.accordion-button {
|
|
116
|
+
background-color: var(--dr-surface) !important;
|
|
117
|
+
color: var(--dr-fg) !important;
|
|
118
|
+
}
|
|
119
|
+
.accordion-button:not(.collapsed) {
|
|
120
|
+
background-color: var(--dr-surface) !important;
|
|
121
|
+
color: var(--dr-cyan) !important;
|
|
122
|
+
box-shadow: none !important;
|
|
123
|
+
}
|
|
124
|
+
.accordion-button::after { filter: invert(1) brightness(2); }
|
|
125
|
+
.accordion-body {
|
|
126
|
+
background-color: var(--dr-bg) !important;
|
|
127
|
+
color: var(--dr-fg) !important;
|
|
128
|
+
}
|
|
129
|
+
.accordion-item {
|
|
130
|
+
background-color: var(--dr-bg) !important;
|
|
131
|
+
border-color: var(--dr-comment) !important;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── dcc.Dropdown (Dash 4.x — Radix UI, dash-dropdown-* classes) ─────────── */
|
|
135
|
+
/* The Popover portal renders inside .dash-dropdown-wrapper, so CSS vars */
|
|
136
|
+
/* set here cascade into .dash-dropdown-content (the visible popup menu). */
|
|
137
|
+
|
|
138
|
+
/* Override Dash design-system CSS variables at the wrapper level */
|
|
139
|
+
.dash-dropdown-wrapper {
|
|
140
|
+
--Dash-Fill-Inverse-Strong: #44475a;
|
|
141
|
+
--Dash-Text-Strong: #f8f8f2;
|
|
142
|
+
--Dash-Text-Weak: #f8f8f2;
|
|
143
|
+
--Dash-Text-Disabled: #6272a4;
|
|
144
|
+
--Dash-Stroke-Strong: #6272a4;
|
|
145
|
+
--Dash-Fill-Interactive-Strong: #bd93f9;
|
|
146
|
+
--Dash-Fill-Interactive-Weak: rgba(189, 147, 249, 0.2);
|
|
147
|
+
--Dash-Fill-Disabled: #6272a4;
|
|
148
|
+
--Dash-Shading-Strong: rgba(0, 0, 0, 0.50);
|
|
149
|
+
--Dash-Shading-Weak: rgba(0, 0, 0, 0.25);
|
|
150
|
+
position: relative;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Trigger button */
|
|
154
|
+
.dash-dropdown {
|
|
155
|
+
background-color: #44475a !important;
|
|
156
|
+
border: 1px solid #6272a4 !important;
|
|
157
|
+
color: #f8f8f2 !important;
|
|
158
|
+
}
|
|
159
|
+
.dash-dropdown:focus {
|
|
160
|
+
border-color: #bd93f9 !important;
|
|
161
|
+
outline-color: #bd93f9 !important;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Value / placeholder text */
|
|
165
|
+
.dash-dropdown-value { color: #f8f8f2 !important; }
|
|
166
|
+
.dash-dropdown-placeholder { color: #6272a4 !important; }
|
|
167
|
+
.dash-dropdown-value-count { color: #f8f8f2 !important; background: rgba(189,147,249,0.2) !important; }
|
|
168
|
+
|
|
169
|
+
/* Caret icon & clear button */
|
|
170
|
+
.dash-dropdown-trigger-icon { color: #f8f8f2 !important; fill: #f8f8f2 !important; }
|
|
171
|
+
.dash-dropdown-clear { color: #6272a4 !important; }
|
|
172
|
+
.dash-dropdown-clear:hover { color: #ff5555 !important; }
|
|
173
|
+
|
|
174
|
+
/* ── Dropdown popup (the Radix Popover.Content) ────────────── */
|
|
175
|
+
.dash-dropdown-content {
|
|
176
|
+
background-color: #44475a !important;
|
|
177
|
+
border-color: #6272a4 !important;
|
|
178
|
+
color: #f8f8f2 !important;
|
|
179
|
+
z-index: 9999 !important;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Search area */
|
|
183
|
+
.dash-dropdown-search-container {
|
|
184
|
+
background-color: #282a36 !important;
|
|
185
|
+
border-color: #6272a4 !important;
|
|
186
|
+
}
|
|
187
|
+
.dash-dropdown-search {
|
|
188
|
+
background-color: transparent !important;
|
|
189
|
+
color: #f8f8f2 !important;
|
|
190
|
+
}
|
|
191
|
+
.dash-dropdown-search::placeholder { color: #6272a4 !important; }
|
|
192
|
+
|
|
193
|
+
/* Options list */
|
|
194
|
+
.dash-dropdown-options { background-color: #44475a !important; }
|
|
195
|
+
.dash-dropdown-option { background-color: #44475a !important; color: #f8f8f2 !important; }
|
|
196
|
+
.dash-dropdown-option:hover,
|
|
197
|
+
.dash-dropdown-option:focus-within { background-color: #6272a4 !important; color: #f8f8f2 !important; }
|
|
198
|
+
/* Selected option highlight (radio/checkbox checked inside option) */
|
|
199
|
+
.dash-dropdown-option:has(input:checked) { background-color: #44475a !important; color: #bd93f9 !important; }
|
|
200
|
+
.dash-dropdown-option:has(input:checked):hover { background-color: #6272a4 !important; }
|
|
201
|
+
|
|
202
|
+
/* Multi-select action buttons (Select All / Deselect All) */
|
|
203
|
+
.dash-dropdown-actions { background-color: #282a36 !important; border-color: #6272a4 !important; }
|
|
204
|
+
.dash-dropdown-action-button { color: #8be9fd !important; }
|
|
205
|
+
.dash-dropdown-action-button:hover { color: #bd93f9 !important; }
|
|
206
|
+
|
|
207
|
+
/* ── Bootstrap native <select> (dbc.Select) ────────────────── */
|
|
208
|
+
.form-select {
|
|
209
|
+
background-color: #44475a !important;
|
|
210
|
+
color: #f8f8f2 !important;
|
|
211
|
+
border-color: #6272a4 !important;
|
|
212
|
+
}
|
|
213
|
+
.form-select option { background-color: #44475a; color: #f8f8f2; }
|
|
214
|
+
|
|
215
|
+
/* ── Bootstrap dropdown menu ────────────────────────────────── */
|
|
216
|
+
.dropdown-menu { background-color: #44475a !important; border-color: #6272a4 !important; }
|
|
217
|
+
.dropdown-item { color: #f8f8f2 !important; }
|
|
218
|
+
.dropdown-item:hover,
|
|
219
|
+
.dropdown-item:focus { background-color: #6272a4 !important; color: #f8f8f2 !important; }
|
|
220
|
+
|
|
221
|
+
/* ── dash-ag-grid (ag-theme-alpine-dark Dracula overrides) ──────────────── */
|
|
222
|
+
/* ag-theme-alpine-dark already provides a dark background; we just nudge */
|
|
223
|
+
/* the colours to match Dracula. */
|
|
224
|
+
.ag-theme-alpine-dark {
|
|
225
|
+
--ag-background-color: #282a36;
|
|
226
|
+
--ag-header-background-color: #44475a;
|
|
227
|
+
--ag-odd-row-background-color: #2f3145;
|
|
228
|
+
--ag-row-hover-color: #44475a;
|
|
229
|
+
--ag-selected-row-background-color: rgba(189,147,249,0.25);
|
|
230
|
+
--ag-border-color: #6272a4;
|
|
231
|
+
--ag-header-foreground-color: #f8f8f2;
|
|
232
|
+
--ag-foreground-color: #f8f8f2;
|
|
233
|
+
--ag-secondary-foreground-color: #6272a4;
|
|
234
|
+
--ag-input-focus-border-color: #bd93f9;
|
|
235
|
+
--ag-range-selection-border-color: #bd93f9;
|
|
236
|
+
--ag-alpine-active-color: #bd93f9;
|
|
237
|
+
font-family: monospace;
|
|
238
|
+
font-size: 13px;
|
|
239
|
+
}
|
|
240
|
+
/* Cell editor (text / select input inside the grid) */
|
|
241
|
+
.ag-theme-alpine-dark .ag-popup-editor,
|
|
242
|
+
.ag-theme-alpine-dark .ag-select-list,
|
|
243
|
+
.ag-theme-alpine-dark .ag-select-list-item,
|
|
244
|
+
.ag-theme-alpine-dark .ag-popup,
|
|
245
|
+
.ag-theme-alpine-dark .ag-rich-select-list,
|
|
246
|
+
.ag-theme-alpine-dark .ag-select {
|
|
247
|
+
background-color: #44475a !important;
|
|
248
|
+
color: #f8f8f2 !important;
|
|
249
|
+
border-color: #6272a4 !important;
|
|
250
|
+
}
|
|
251
|
+
.ag-theme-alpine-dark .ag-select-list-item:hover,
|
|
252
|
+
.ag-theme-alpine-dark .ag-select-list-item.ag-active-item {
|
|
253
|
+
background-color: #6272a4 !important;
|
|
254
|
+
color: #f8f8f2 !important;
|
|
255
|
+
}
|
|
256
|
+
/* Input cells */
|
|
257
|
+
.ag-theme-alpine-dark .ag-cell-edit-wrapper input,
|
|
258
|
+
.ag-theme-alpine-dark .ag-text-field-input {
|
|
259
|
+
background-color: #282a36 !important;
|
|
260
|
+
color: #f8f8f2 !important;
|
|
261
|
+
border-color: #bd93f9 !important;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* ── RadioItems / Checkboxes ────────────────────────────────── */
|
|
265
|
+
.form-check-label { color: #f8f8f2 !important; }
|
|
266
|
+
/* Radio/checkbox circle on both dark body AND lighter card (#44475a) backgrounds */
|
|
267
|
+
.form-check-input {
|
|
268
|
+
background-color: #282a36 !important;
|
|
269
|
+
border: 2px solid #8be9fd !important; /* cyan border — visible on both bg colours */
|
|
270
|
+
}
|
|
271
|
+
.form-check-input:checked {
|
|
272
|
+
background-color: #bd93f9 !important;
|
|
273
|
+
border-color: #bd93f9 !important;
|
|
274
|
+
}
|
|
275
|
+
.form-check-input:focus {
|
|
276
|
+
border-color: #bd93f9 !important;
|
|
277
|
+
box-shadow: 0 0 0 0.2rem rgba(189,147,249,0.25) !important;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* ── Badges ─────────────────────────────────────────────────── */
|
|
281
|
+
.badge.bg-primary { background-color: var(--dr-purple) !important; }
|
|
282
|
+
.badge.bg-success { background-color: var(--dr-green) !important; color: var(--dr-bg) !important; }
|
|
283
|
+
.badge.bg-info { background-color: var(--dr-cyan) !important; color: var(--dr-bg) !important; }
|
|
284
|
+
|
|
285
|
+
/* ── Markdown (Handleiding tab) ─────────────────────────────── */
|
|
286
|
+
.dash-markdown h1 { color: var(--dr-purple); border-bottom: 1px solid var(--dr-comment); padding-bottom: 6px; }
|
|
287
|
+
.dash-markdown h2 { color: var(--dr-cyan); }
|
|
288
|
+
.dash-markdown h3 { color: var(--dr-green); }
|
|
289
|
+
.dash-markdown h4 { color: var(--dr-orange); }
|
|
290
|
+
.dash-markdown code {
|
|
291
|
+
background-color: var(--dr-surface);
|
|
292
|
+
color: var(--dr-yellow);
|
|
293
|
+
padding: 1px 5px;
|
|
294
|
+
border-radius: 4px;
|
|
295
|
+
}
|
|
296
|
+
.dash-markdown pre {
|
|
297
|
+
background-color: var(--dr-surface);
|
|
298
|
+
border: 1px solid var(--dr-comment);
|
|
299
|
+
border-radius: 6px;
|
|
300
|
+
padding: 12px;
|
|
301
|
+
color: var(--dr-fg);
|
|
302
|
+
}
|
|
303
|
+
.dash-markdown table { width: 100%; border-collapse: collapse; }
|
|
304
|
+
.dash-markdown th {
|
|
305
|
+
background-color: var(--dr-surface);
|
|
306
|
+
color: var(--dr-cyan);
|
|
307
|
+
border: 1px solid var(--dr-comment);
|
|
308
|
+
padding: 6px 10px;
|
|
309
|
+
}
|
|
310
|
+
.dash-markdown td {
|
|
311
|
+
border: 1px solid var(--dr-comment);
|
|
312
|
+
padding: 5px 10px;
|
|
313
|
+
color: var(--dr-fg);
|
|
314
|
+
}
|
|
315
|
+
.dash-markdown tr:nth-child(odd) td { background-color: #2f3145; }
|
|
316
|
+
|
|
317
|
+
/* ── Scrollbars ─────────────────────────────────────────────── */
|
|
318
|
+
* { scrollbar-color: var(--dr-comment) var(--dr-surface); scrollbar-width: thin; }
|
|
319
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
320
|
+
::-webkit-scrollbar-track { background: var(--dr-surface); }
|
|
321
|
+
::-webkit-scrollbar-thumb { background: var(--dr-comment); border-radius: 4px; }
|
|
322
|
+
::-webkit-scrollbar-thumb:hover { background: var(--dr-purple); }
|
|
323
|
+
|
|
324
|
+
/* ── Alert / toast ──────────────────────────────────────────── */
|
|
325
|
+
.alert { background-color: var(--dr-surface) !important; border-color: var(--dr-comment) !important; color: var(--dr-fg) !important; }
|
|
326
|
+
|
|
327
|
+
/* =============================================================
|
|
328
|
+
LIGHT THEME OVERRIDES (active when <body class="light-theme">)
|
|
329
|
+
Override the CSS variables → almost everything re-colours
|
|
330
|
+
automatically.
|
|
331
|
+
============================================================= */
|
|
332
|
+
body.light-theme {
|
|
333
|
+
/* Overwrite Dracula palette with clean light-mode colours */
|
|
334
|
+
--dr-bg: #f5f5f5;
|
|
335
|
+
--dr-surface: #ffffff;
|
|
336
|
+
--dr-fg: #212529;
|
|
337
|
+
--dr-comment: #6c757d;
|
|
338
|
+
--dr-cyan: #0066cc;
|
|
339
|
+
--dr-green: #198754;
|
|
340
|
+
--dr-orange: #e06c00;
|
|
341
|
+
--dr-pink: #c0357a;
|
|
342
|
+
--dr-purple: #6610f2;
|
|
343
|
+
--dr-red: #dc3545;
|
|
344
|
+
--dr-yellow: #b58900;
|
|
345
|
+
|
|
346
|
+
/* Bootstrap body variables */
|
|
347
|
+
--bs-body-bg: #f5f5f5;
|
|
348
|
+
--bs-body-color: #212529;
|
|
349
|
+
--bs-card-bg: #ffffff;
|
|
350
|
+
--bs-card-border-color:#dee2e6;
|
|
351
|
+
--bs-border-color: #dee2e6;
|
|
352
|
+
--bs-link-color: #0066cc;
|
|
353
|
+
--bs-link-hover-color: #6610f2;
|
|
354
|
+
--bs-secondary-color: #6c757d;
|
|
355
|
+
--bs-primary: #6610f2;
|
|
356
|
+
--bs-primary-rgb: 102, 16, 242;
|
|
357
|
+
--bs-link-color-rgb: 0, 102, 204;
|
|
358
|
+
--bs-nav-tabs-border-color: #dee2e6;
|
|
359
|
+
--bs-nav-tabs-link-active-bg: #ffffff;
|
|
360
|
+
--bs-nav-tabs-link-active-color: #0066cc;
|
|
361
|
+
--bs-nav-tabs-link-hover-border-color: #dee2e6;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Body & base text */
|
|
365
|
+
body.light-theme { background-color: #f5f5f5 !important; color: #212529 !important; }
|
|
366
|
+
body.light-theme h1,
|
|
367
|
+
body.light-theme h2,
|
|
368
|
+
body.light-theme h3,
|
|
369
|
+
body.light-theme h4,
|
|
370
|
+
body.light-theme h5,
|
|
371
|
+
body.light-theme h6 { color: #212529; }
|
|
372
|
+
body.light-theme hr { border-color: #dee2e6; opacity: 0.8; }
|
|
373
|
+
body.light-theme .text-muted { color: #6c757d !important; }
|
|
374
|
+
body.light-theme label { color: #212529 !important; }
|
|
375
|
+
|
|
376
|
+
/* Cards */
|
|
377
|
+
body.light-theme .card { background-color: #ffffff !important; border-color: #dee2e6 !important; color: #212529 !important; }
|
|
378
|
+
body.light-theme .card-title { color: #0066cc !important; }
|
|
379
|
+
body.light-theme .tab-content { background-color: #f5f5f5 !important; }
|
|
380
|
+
|
|
381
|
+
/* Nav tabs */
|
|
382
|
+
body.light-theme .nav-tabs { border-bottom-color: #dee2e6 !important; }
|
|
383
|
+
body.light-theme .nav-tabs .nav-link { color: #6c757d !important; border-color: transparent !important; }
|
|
384
|
+
body.light-theme .nav-tabs .nav-link:hover { color: #212529 !important; border-color: #dee2e6 !important; }
|
|
385
|
+
body.light-theme .nav-tabs .nav-link.active { background-color: #ffffff !important; color: #0066cc !important; border-color: #dee2e6 #dee2e6 #ffffff !important; }
|
|
386
|
+
|
|
387
|
+
/* Buttons */
|
|
388
|
+
body.light-theme .btn-primary { background-color: #6610f2; border-color: #6610f2; color: #fff; }
|
|
389
|
+
body.light-theme .btn-primary:hover { background-color: #520dc2; border-color: #520dc2; }
|
|
390
|
+
body.light-theme .btn-outline-primary { border-color: #6610f2; color: #6610f2; }
|
|
391
|
+
body.light-theme .btn-outline-primary:hover { background-color: #6610f2; color: #fff; }
|
|
392
|
+
body.light-theme .btn-success { background-color: #198754; border-color: #198754; color: #fff; }
|
|
393
|
+
body.light-theme .btn-outline-secondary { border-color: #adb5bd; color: #495057; }
|
|
394
|
+
body.light-theme .btn-outline-secondary:hover { background-color: #adb5bd; color: #212529; }
|
|
395
|
+
body.light-theme .btn-outline-info { border-color: #0dcaf0; color: #0dcaf0; }
|
|
396
|
+
|
|
397
|
+
/* Forms */
|
|
398
|
+
body.light-theme .form-control,
|
|
399
|
+
body.light-theme .form-select,
|
|
400
|
+
body.light-theme input[type="text"],
|
|
401
|
+
body.light-theme input[type="number"] {
|
|
402
|
+
background-color: #ffffff !important;
|
|
403
|
+
color: #212529 !important;
|
|
404
|
+
border-color: #ced4da !important;
|
|
405
|
+
}
|
|
406
|
+
body.light-theme .form-control:focus,
|
|
407
|
+
body.light-theme input:focus {
|
|
408
|
+
border-color: #6610f2 !important;
|
|
409
|
+
box-shadow: 0 0 0 0.2rem rgba(102,16,242,0.2) !important;
|
|
410
|
+
}
|
|
411
|
+
body.light-theme .form-check-label { color: #212529 !important; }
|
|
412
|
+
body.light-theme .form-check-input {
|
|
413
|
+
background-color: #ffffff !important;
|
|
414
|
+
border: 2px solid #adb5bd !important;
|
|
415
|
+
}
|
|
416
|
+
body.light-theme .form-check-input:checked {
|
|
417
|
+
background-color: #6610f2 !important;
|
|
418
|
+
border-color: #6610f2 !important;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Alerts */
|
|
422
|
+
body.light-theme .alert {
|
|
423
|
+
background-color: #e8f4fd !important;
|
|
424
|
+
border-color: #90c6f0 !important;
|
|
425
|
+
color: #1a4a70 !important;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* Accordion */
|
|
429
|
+
body.light-theme .accordion-button { background-color: #f0f0f0 !important; color: #212529 !important; }
|
|
430
|
+
body.light-theme .accordion-button:not(.collapsed) { background-color: #e9ecef !important; color: #0066cc !important; box-shadow: none !important; }
|
|
431
|
+
body.light-theme .accordion-button::after { filter: none; }
|
|
432
|
+
body.light-theme .accordion-body { background-color: #f5f5f5 !important; color: #212529 !important; }
|
|
433
|
+
body.light-theme .accordion-item { background-color: #f5f5f5 !important; border-color: #dee2e6 !important; }
|
|
434
|
+
|
|
435
|
+
/* dcc.Dropdown (Dash 4 Radix) */
|
|
436
|
+
body.light-theme .dash-dropdown-wrapper {
|
|
437
|
+
--Dash-Fill-Inverse-Strong: #ffffff;
|
|
438
|
+
--Dash-Text-Strong: #212529;
|
|
439
|
+
--Dash-Text-Weak: #495057;
|
|
440
|
+
--Dash-Text-Disabled: #adb5bd;
|
|
441
|
+
--Dash-Stroke-Strong: #ced4da;
|
|
442
|
+
--Dash-Fill-Interactive-Strong: #6610f2;
|
|
443
|
+
--Dash-Fill-Interactive-Weak: rgba(102,16,242,0.12);
|
|
444
|
+
--Dash-Fill-Disabled: #e9ecef;
|
|
445
|
+
--Dash-Shading-Strong: rgba(0,0,0,0.12);
|
|
446
|
+
--Dash-Shading-Weak: rgba(0,0,0,0.06);
|
|
447
|
+
}
|
|
448
|
+
body.light-theme .dash-dropdown { background-color: #ffffff !important; border-color: #ced4da !important; color: #212529 !important; }
|
|
449
|
+
body.light-theme .dash-dropdown-value { color: #212529 !important; }
|
|
450
|
+
body.light-theme .dash-dropdown-placeholder { color: #adb5bd !important; }
|
|
451
|
+
body.light-theme .dash-dropdown-trigger-icon { color: #495057 !important; fill: #495057 !important; }
|
|
452
|
+
body.light-theme .dash-dropdown-content { background-color: #ffffff !important; border-color: #ced4da !important; color: #212529 !important; }
|
|
453
|
+
body.light-theme .dash-dropdown-options { background-color: #ffffff !important; }
|
|
454
|
+
body.light-theme .dash-dropdown-option { background-color: #ffffff !important; color: #212529 !important; }
|
|
455
|
+
body.light-theme .dash-dropdown-option:hover { background-color: #f2f2f2 !important; }
|
|
456
|
+
body.light-theme .dash-dropdown-search-container { background-color: #f5f5f5 !important; border-color: #dee2e6 !important; }
|
|
457
|
+
body.light-theme .dash-dropdown-search { color: #212529 !important; }
|
|
458
|
+
body.light-theme .dash-dropdown-search::placeholder { color: #adb5bd !important; }
|
|
459
|
+
|
|
460
|
+
/* ag-grid */
|
|
461
|
+
body.light-theme .ag-theme-alpine-dark {
|
|
462
|
+
--ag-background-color: #ffffff;
|
|
463
|
+
--ag-header-background-color: #f0f0f0;
|
|
464
|
+
--ag-odd-row-background-color: #fafafa;
|
|
465
|
+
--ag-row-hover-color: #e9ecef;
|
|
466
|
+
--ag-selected-row-background-color: rgba(102,16,242,0.10);
|
|
467
|
+
--ag-border-color: #dee2e6;
|
|
468
|
+
--ag-header-foreground-color: #212529;
|
|
469
|
+
--ag-foreground-color: #212529;
|
|
470
|
+
--ag-secondary-foreground-color: #6c757d;
|
|
471
|
+
--ag-input-focus-border-color: #6610f2;
|
|
472
|
+
--ag-range-selection-border-color: #6610f2;
|
|
473
|
+
--ag-alpine-active-color: #6610f2;
|
|
474
|
+
}
|
|
475
|
+
body.light-theme .ag-theme-alpine-dark .ag-popup-editor,
|
|
476
|
+
body.light-theme .ag-theme-alpine-dark .ag-select-list,
|
|
477
|
+
body.light-theme .ag-theme-alpine-dark .ag-popup {
|
|
478
|
+
background-color: #ffffff !important;
|
|
479
|
+
color: #212529 !important;
|
|
480
|
+
border-color: #dee2e6 !important;
|
|
481
|
+
}
|
|
482
|
+
body.light-theme .ag-theme-alpine-dark .ag-cell-edit-wrapper input,
|
|
483
|
+
body.light-theme .ag-theme-alpine-dark .ag-text-field-input {
|
|
484
|
+
background-color: #f5f5f5 !important;
|
|
485
|
+
color: #212529 !important;
|
|
486
|
+
border-color: #6610f2 !important;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* Scrollbars */
|
|
490
|
+
body.light-theme * { scrollbar-color: #adb5bd #f5f5f5; }
|
|
491
|
+
body.light-theme ::-webkit-scrollbar-track { background: #f5f5f5; }
|
|
492
|
+
body.light-theme ::-webkit-scrollbar-thumb { background: #adb5bd; }
|
|
493
|
+
body.light-theme ::-webkit-scrollbar-thumb:hover { background: #6610f2; }
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dash_dracula.bootstrap
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Helpers to wire the Dracula theme into any Dash application:
|
|
5
|
+
|
|
6
|
+
* ``apply_theme(app, default_theme="dark")``
|
|
7
|
+
Copies ``dracula.css`` into the app's ``assets/`` folder, registers the
|
|
8
|
+
Plotly-graph theme-toggle ``clientside_callback`` on the app, and returns
|
|
9
|
+
a ``dcc.Store`` component that must be added to the layout.
|
|
10
|
+
|
|
11
|
+
* ``theme_toggle_button()``
|
|
12
|
+
Returns a pre-configured ``dbc.Button`` that drives the toggle.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
import dash
|
|
17
|
+
import dash_bootstrap_components as dbc
|
|
18
|
+
import dash_dracula as dr
|
|
19
|
+
|
|
20
|
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
|
|
21
|
+
theme_store = dr.apply_theme(app) # add theme_store to your layout
|
|
22
|
+
|
|
23
|
+
# Somewhere in your layout:
|
|
24
|
+
layout = dbc.Container([
|
|
25
|
+
dr.theme_toggle_button(),
|
|
26
|
+
theme_store,
|
|
27
|
+
...
|
|
28
|
+
])
|
|
29
|
+
"""
|
|
30
|
+
import os
|
|
31
|
+
import shutil
|
|
32
|
+
|
|
33
|
+
import dash
|
|
34
|
+
import dash_bootstrap_components as dbc
|
|
35
|
+
from dash import Input, Output, State, dcc
|
|
36
|
+
|
|
37
|
+
# ── JS payload ───────────────────────────────────────────────────────────────
|
|
38
|
+
# Extracted verbatim from app.py — kept in one place so both the original app
|
|
39
|
+
# and any new app share exactly the same toggle logic.
|
|
40
|
+
_TOGGLE_JS = """
|
|
41
|
+
function(n_clicks, current_theme) {
|
|
42
|
+
if (!n_clicks) {
|
|
43
|
+
return [window.dash_clientside.no_update, window.dash_clientside.no_update];
|
|
44
|
+
}
|
|
45
|
+
var new_theme = (current_theme === 'dark') ? 'light' : 'dark';
|
|
46
|
+
var is_light = (new_theme === 'light');
|
|
47
|
+
|
|
48
|
+
// Toggle CSS class on <body> — the light-theme CSS block handles the rest
|
|
49
|
+
document.body.classList.toggle('light-theme', is_light);
|
|
50
|
+
|
|
51
|
+
// Plotly graph layout overrides
|
|
52
|
+
var layout_light = {
|
|
53
|
+
paper_bgcolor: '#ffffff',
|
|
54
|
+
plot_bgcolor: '#f5f5f5',
|
|
55
|
+
'font.color': '#212529',
|
|
56
|
+
'title.font.color': '#212529',
|
|
57
|
+
'xaxis.gridcolor': '#dee2e6',
|
|
58
|
+
'xaxis.linecolor': '#adb5bd',
|
|
59
|
+
'xaxis.tickcolor': '#adb5bd',
|
|
60
|
+
'xaxis.tickfont.color': '#495057',
|
|
61
|
+
'xaxis.title.font.color': '#212529',
|
|
62
|
+
'yaxis.gridcolor': '#dee2e6',
|
|
63
|
+
'yaxis.linecolor': '#adb5bd',
|
|
64
|
+
'yaxis.tickcolor': '#adb5bd',
|
|
65
|
+
'yaxis.tickfont.color': '#495057',
|
|
66
|
+
'yaxis.title.font.color': '#212529',
|
|
67
|
+
'legend.bgcolor': '#ffffff',
|
|
68
|
+
'legend.bordercolor': '#dee2e6',
|
|
69
|
+
'legend.font.color': '#212529',
|
|
70
|
+
'coloraxis.colorbar.tickfont.color': '#212529',
|
|
71
|
+
'coloraxis.colorbar.title.font.color': '#212529',
|
|
72
|
+
'scene.bgcolor': '#f5f5f5',
|
|
73
|
+
'scene.xaxis.backgroundcolor': '#ffffff',
|
|
74
|
+
'scene.yaxis.backgroundcolor': '#ffffff',
|
|
75
|
+
'scene.zaxis.backgroundcolor': '#ffffff',
|
|
76
|
+
'scene.xaxis.gridcolor': '#dee2e6',
|
|
77
|
+
'scene.yaxis.gridcolor': '#dee2e6',
|
|
78
|
+
'scene.zaxis.gridcolor': '#dee2e6',
|
|
79
|
+
'scene.xaxis.linecolor': '#adb5bd',
|
|
80
|
+
'scene.yaxis.linecolor': '#adb5bd',
|
|
81
|
+
'scene.zaxis.linecolor': '#adb5bd',
|
|
82
|
+
'scene.xaxis.tickfont.color': '#495057',
|
|
83
|
+
'scene.yaxis.tickfont.color': '#495057',
|
|
84
|
+
'scene.zaxis.tickfont.color': '#495057',
|
|
85
|
+
'scene.xaxis.title.font.color': '#212529',
|
|
86
|
+
'scene.yaxis.title.font.color': '#212529',
|
|
87
|
+
'scene.zaxis.title.font.color': '#212529'
|
|
88
|
+
};
|
|
89
|
+
var layout_dark = {
|
|
90
|
+
paper_bgcolor: '#282a36',
|
|
91
|
+
plot_bgcolor: '#282a36',
|
|
92
|
+
'font.color': '#f8f8f2',
|
|
93
|
+
'title.font.color': '#f8f8f2',
|
|
94
|
+
'xaxis.gridcolor': '#44475a',
|
|
95
|
+
'xaxis.linecolor': '#6272a4',
|
|
96
|
+
'xaxis.tickcolor': '#6272a4',
|
|
97
|
+
'xaxis.tickfont.color': '#f8f8f2',
|
|
98
|
+
'xaxis.title.font.color': '#f8f8f2',
|
|
99
|
+
'yaxis.gridcolor': '#44475a',
|
|
100
|
+
'yaxis.linecolor': '#6272a4',
|
|
101
|
+
'yaxis.tickcolor': '#6272a4',
|
|
102
|
+
'yaxis.tickfont.color': '#f8f8f2',
|
|
103
|
+
'yaxis.title.font.color': '#f8f8f2',
|
|
104
|
+
'legend.bgcolor': '#44475a',
|
|
105
|
+
'legend.bordercolor': '#6272a4',
|
|
106
|
+
'legend.font.color': '#f8f8f2',
|
|
107
|
+
'coloraxis.colorbar.tickfont.color': '#f8f8f2',
|
|
108
|
+
'coloraxis.colorbar.title.font.color': '#f8f8f2',
|
|
109
|
+
'scene.bgcolor': '#282a36',
|
|
110
|
+
'scene.xaxis.backgroundcolor': '#282a36',
|
|
111
|
+
'scene.yaxis.backgroundcolor': '#282a36',
|
|
112
|
+
'scene.zaxis.backgroundcolor': '#282a36',
|
|
113
|
+
'scene.xaxis.gridcolor': '#44475a',
|
|
114
|
+
'scene.yaxis.gridcolor': '#44475a',
|
|
115
|
+
'scene.zaxis.gridcolor': '#44475a',
|
|
116
|
+
'scene.xaxis.linecolor': '#6272a4',
|
|
117
|
+
'scene.yaxis.linecolor': '#6272a4',
|
|
118
|
+
'scene.zaxis.linecolor': '#6272a4',
|
|
119
|
+
'scene.xaxis.tickfont.color': '#f8f8f2',
|
|
120
|
+
'scene.yaxis.tickfont.color': '#f8f8f2',
|
|
121
|
+
'scene.zaxis.tickfont.color': '#f8f8f2',
|
|
122
|
+
'scene.xaxis.title.font.color': '#f8f8f2',
|
|
123
|
+
'scene.yaxis.title.font.color': '#f8f8f2',
|
|
124
|
+
'scene.zaxis.title.font.color': '#f8f8f2'
|
|
125
|
+
};
|
|
126
|
+
var patch = is_light ? layout_light : layout_dark;
|
|
127
|
+
|
|
128
|
+
document.querySelectorAll('.js-plotly-plot').forEach(function(el) {
|
|
129
|
+
try { Plotly.relayout(el, patch); } catch(e) {}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return [new_theme, is_light ? '\\u{1F319} Donker' : '\\u2600\\uFE0F Licht'];
|
|
133
|
+
}
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _assets_src() -> str:
|
|
138
|
+
"""Absolute path to the ``assets/`` folder inside this package."""
|
|
139
|
+
return os.path.join(os.path.dirname(__file__), "assets")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _ensure_css_in_app(app: dash.Dash) -> None:
|
|
143
|
+
"""Copy ``dracula.css`` into the app's assets folder if not already there.
|
|
144
|
+
|
|
145
|
+
Dash 4 auto-serves exactly one ``assets/`` folder per app. The simplest
|
|
146
|
+
portable approach is to copy the CSS there at startup.
|
|
147
|
+
"""
|
|
148
|
+
src = os.path.join(_assets_src(), "dracula.css")
|
|
149
|
+
# Resolve the app's assets directory
|
|
150
|
+
try:
|
|
151
|
+
assets_folder = os.path.abspath(app.config.assets_folder)
|
|
152
|
+
except AttributeError:
|
|
153
|
+
assets_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
|
|
154
|
+
|
|
155
|
+
os.makedirs(assets_folder, exist_ok=True)
|
|
156
|
+
dest = os.path.join(assets_folder, "dracula.css")
|
|
157
|
+
if not os.path.exists(dest):
|
|
158
|
+
shutil.copy(src, dest)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def theme_toggle_button() -> dbc.Button:
|
|
162
|
+
"""Return the pre-configured dark/light toggle button.
|
|
163
|
+
|
|
164
|
+
The button's ``id`` must be ``"theme-toggle-btn"`` — the clientside
|
|
165
|
+
callback registered by :func:`apply_theme` listens for clicks on that id.
|
|
166
|
+
"""
|
|
167
|
+
return dbc.Button(
|
|
168
|
+
"☀️ Licht",
|
|
169
|
+
id="theme-toggle-btn",
|
|
170
|
+
color="secondary",
|
|
171
|
+
outline=True,
|
|
172
|
+
size="sm",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def apply_theme(
|
|
177
|
+
app: dash.Dash,
|
|
178
|
+
default_theme: str = "dark",
|
|
179
|
+
) -> dcc.Store:
|
|
180
|
+
"""Wire the Dracula theme into a Dash application.
|
|
181
|
+
|
|
182
|
+
1. Copies ``dracula.css`` into the app's ``assets/`` folder so Dash
|
|
183
|
+
serves it automatically.
|
|
184
|
+
2. Registers a ``clientside_callback`` on the app that toggles
|
|
185
|
+
``body.light-theme`` and re-colours all Plotly graphs when the
|
|
186
|
+
``#theme-toggle-btn`` button is clicked.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
app:
|
|
191
|
+
The ``dash.Dash`` instance to configure.
|
|
192
|
+
default_theme:
|
|
193
|
+
Either ``"dark"`` (default) or ``"light"``.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
dcc.Store
|
|
198
|
+
A hidden store component with ``id="theme-store"`` that **must** be
|
|
199
|
+
added to the app layout. It persists the current theme value so the
|
|
200
|
+
callback always has access to it.
|
|
201
|
+
|
|
202
|
+
Example
|
|
203
|
+
-------
|
|
204
|
+
::
|
|
205
|
+
|
|
206
|
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
|
|
207
|
+
theme_store = apply_theme(app)
|
|
208
|
+
|
|
209
|
+
app.layout = dbc.Container([
|
|
210
|
+
theme_toggle_button(),
|
|
211
|
+
theme_store,
|
|
212
|
+
...
|
|
213
|
+
])
|
|
214
|
+
"""
|
|
215
|
+
_ensure_css_in_app(app)
|
|
216
|
+
|
|
217
|
+
app.clientside_callback(
|
|
218
|
+
_TOGGLE_JS,
|
|
219
|
+
Output("theme-store", "data"),
|
|
220
|
+
Output("theme-toggle-btn", "children"),
|
|
221
|
+
Input("theme-toggle-btn", "n_clicks"),
|
|
222
|
+
State("theme-store", "data"),
|
|
223
|
+
prevent_initial_call=True,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return dcc.Store(id="theme-store", data=default_theme)
|
dash_dracula/colors.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dash_dracula.colors
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Official Dracula colour palette constants.
|
|
5
|
+
Source: https://draculatheme.com/contribute
|
|
6
|
+
|
|
7
|
+
All hex values are lowercase to match CSS convention.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# ── Official Dracula palette ─────────────────────────────────────────────────
|
|
11
|
+
# https://draculatheme.com/contribute
|
|
12
|
+
BACKGROUND = "#282a36" # main background
|
|
13
|
+
CURRENT_LINE = "#44475a" # current-line / selection highlight
|
|
14
|
+
SELECTION = "#44475a" # alias for CURRENT_LINE
|
|
15
|
+
FOREGROUND = "#f8f8f2" # default text
|
|
16
|
+
COMMENT = "#6272a4" # comments / secondary UI text
|
|
17
|
+
|
|
18
|
+
# Accent colours
|
|
19
|
+
RED = "#ff5555"
|
|
20
|
+
ORANGE = "#ffb86c"
|
|
21
|
+
YELLOW = "#f1fa8c"
|
|
22
|
+
GREEN = "#50fa7b"
|
|
23
|
+
CYAN = "#8be9fd"
|
|
24
|
+
PURPLE = "#bd93f9"
|
|
25
|
+
PINK = "#ff79c6"
|
|
26
|
+
|
|
27
|
+
# ── Plotly colorway (dark theme traces) ─────────────────────────────────────
|
|
28
|
+
# Order: most-distinct first so first few traces are always visually separated
|
|
29
|
+
COLORWAY = [CYAN, PURPLE, GREEN, ORANGE, PINK, RED, YELLOW]
|
|
30
|
+
|
|
31
|
+
# ── Neutral colorway (readable on both dark AND light backgrounds) ───────────
|
|
32
|
+
# Use these for data traces when your app supports a light-mode toggle.
|
|
33
|
+
NEUTRAL_COLORWAY = [
|
|
34
|
+
"#1f77b4", # Plotly default blue — readable on both backgrounds
|
|
35
|
+
"#7b2fbe", # mid-purple — readable on both backgrounds
|
|
36
|
+
"#2ca02c", # medium green — readable on both backgrounds
|
|
37
|
+
"#e09b00", # amber — readable on both backgrounds
|
|
38
|
+
"#c0357a", # dark pink — readable on both backgrounds
|
|
39
|
+
"#dc3545", # Bootstrap danger red
|
|
40
|
+
"#17becf", # teal
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# ── Print / PDF colours ──────────────────────────────────────────────────────
|
|
44
|
+
# Darkened Dracula variants — Dracula neon is unreadable on white paper.
|
|
45
|
+
PRINT_COLORS = {
|
|
46
|
+
"background": "#ffffff",
|
|
47
|
+
"surface": "#e8e8e8",
|
|
48
|
+
"foreground": "#000000",
|
|
49
|
+
"cyan": "#0055aa",
|
|
50
|
+
"purple": "#5533aa",
|
|
51
|
+
"green": "#006622",
|
|
52
|
+
"orange": "#cc5500",
|
|
53
|
+
"grid": "#999999",
|
|
54
|
+
"alt_row": "#f2f2f2",
|
|
55
|
+
# figure internals
|
|
56
|
+
"fig_dark": "#111111",
|
|
57
|
+
"fig_mid": "#444444",
|
|
58
|
+
"fig_grid": "#dddddd",
|
|
59
|
+
"fig_line": "#999999",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# ── Light-mode UI colours (CSS `body.light-theme` overrides) ─────────────────
|
|
63
|
+
LIGHT_COLORS = {
|
|
64
|
+
"background": "#f5f5f5",
|
|
65
|
+
"surface": "#ffffff",
|
|
66
|
+
"foreground": "#212529",
|
|
67
|
+
"comment": "#6c757d",
|
|
68
|
+
"cyan": "#0066cc",
|
|
69
|
+
"green": "#198754",
|
|
70
|
+
"orange": "#e06c00",
|
|
71
|
+
"pink": "#c0357a",
|
|
72
|
+
"purple": "#6610f2",
|
|
73
|
+
"red": "#dc3545",
|
|
74
|
+
"yellow": "#b58900",
|
|
75
|
+
"border": "#dee2e6",
|
|
76
|
+
"axis_line": "#adb5bd",
|
|
77
|
+
"tick_font": "#495057",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Convenience dict of all official palette colours ────────────────────────
|
|
81
|
+
AS_DICT = {
|
|
82
|
+
"background": BACKGROUND,
|
|
83
|
+
"current_line": CURRENT_LINE,
|
|
84
|
+
"selection": SELECTION,
|
|
85
|
+
"foreground": FOREGROUND,
|
|
86
|
+
"comment": COMMENT,
|
|
87
|
+
"red": RED,
|
|
88
|
+
"orange": ORANGE,
|
|
89
|
+
"yellow": YELLOW,
|
|
90
|
+
"green": GREEN,
|
|
91
|
+
"cyan": CYAN,
|
|
92
|
+
"purple": PURPLE,
|
|
93
|
+
"pink": PINK,
|
|
94
|
+
}
|
dash_dracula/pdf.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dash_dracula.pdf
|
|
3
|
+
~~~~~~~~~~~~~~~~~
|
|
4
|
+
Utility for rendering Plotly figures to print-ready ReportLab ``Image``
|
|
5
|
+
objects. The output is **always light** (white background, dark text)
|
|
6
|
+
regardless of the current dashboard theme.
|
|
7
|
+
|
|
8
|
+
Requires the ``pdf`` extra::
|
|
9
|
+
|
|
10
|
+
pip install "dash-dracula-theme[pdf]"
|
|
11
|
+
# or: pip install reportlab kaleido
|
|
12
|
+
|
|
13
|
+
Usage::
|
|
14
|
+
|
|
15
|
+
from dash_dracula.pdf import fig_to_print_image
|
|
16
|
+
|
|
17
|
+
img = fig_to_print_image(my_figure, w_mm=170, h_mm=100)
|
|
18
|
+
# img is a reportlab.platypus.Image ready to include in a PDF
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fig_to_print_image(fig_dict_or_fig, w_mm: float = 170, h_mm: float = 100):
|
|
23
|
+
"""Render a Plotly figure to a ReportLab ``Image``, fully overridden for print.
|
|
24
|
+
|
|
25
|
+
All Dracula dark-theme colours are stripped and replaced with a
|
|
26
|
+
``simple_white`` template on a white background with dark fonts.
|
|
27
|
+
3-D figures receive additional ``scene.bgcolor`` and axis-backgroundcolor
|
|
28
|
+
fixes that beat any baked-in template values.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
fig_dict_or_fig:
|
|
33
|
+
Either a Plotly ``go.Figure`` instance or a dict (e.g. from
|
|
34
|
+
``dcc.Graph.figure``).
|
|
35
|
+
w_mm:
|
|
36
|
+
Output width in millimetres.
|
|
37
|
+
h_mm:
|
|
38
|
+
Output height in millimetres.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
reportlab.platypus.Image or None
|
|
43
|
+
``None`` if rendering fails or the input is ``None``.
|
|
44
|
+
"""
|
|
45
|
+
if fig_dict_or_fig is None:
|
|
46
|
+
return None
|
|
47
|
+
try:
|
|
48
|
+
import io
|
|
49
|
+
|
|
50
|
+
import plotly.graph_objects as go
|
|
51
|
+
import plotly.io as pio
|
|
52
|
+
from reportlab.lib.units import mm
|
|
53
|
+
from reportlab.platypus import Image
|
|
54
|
+
|
|
55
|
+
fig_obj = (
|
|
56
|
+
go.Figure(fig_dict_or_fig)
|
|
57
|
+
if isinstance(fig_dict_or_fig, dict)
|
|
58
|
+
else fig_dict_or_fig
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
DARK = "#111111"
|
|
62
|
+
MID = "#444444"
|
|
63
|
+
GRID = "#dddddd"
|
|
64
|
+
LINE = "#999999"
|
|
65
|
+
WHITE = "white"
|
|
66
|
+
|
|
67
|
+
axis_style_2d = dict(
|
|
68
|
+
title_font=dict(color=DARK, size=11),
|
|
69
|
+
tickfont=dict(color=MID, size=9),
|
|
70
|
+
linecolor=LINE,
|
|
71
|
+
gridcolor=GRID,
|
|
72
|
+
zerolinecolor=LINE,
|
|
73
|
+
)
|
|
74
|
+
axis_style_3d = dict(
|
|
75
|
+
title_font=dict(color=DARK, size=10),
|
|
76
|
+
tickfont=dict(color=MID, size=8),
|
|
77
|
+
linecolor=LINE,
|
|
78
|
+
gridcolor=GRID,
|
|
79
|
+
backgroundcolor=WHITE,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
is_3d = bool(
|
|
83
|
+
fig_obj.data
|
|
84
|
+
and any(
|
|
85
|
+
(hasattr(t, "z") and hasattr(t, "surfacecolor"))
|
|
86
|
+
or t.type in ("surface", "scatter3d", "mesh3d")
|
|
87
|
+
for t in fig_obj.data
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
common = dict(
|
|
92
|
+
template="simple_white",
|
|
93
|
+
paper_bgcolor=WHITE,
|
|
94
|
+
font=dict(color=DARK, size=10),
|
|
95
|
+
title_font=dict(color=DARK, size=13),
|
|
96
|
+
legend=dict(
|
|
97
|
+
font=dict(color=DARK),
|
|
98
|
+
bgcolor=WHITE,
|
|
99
|
+
bordercolor=LINE,
|
|
100
|
+
borderwidth=1,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if is_3d:
|
|
105
|
+
fig_obj.update_layout(
|
|
106
|
+
**common,
|
|
107
|
+
scene=dict(
|
|
108
|
+
bgcolor=WHITE,
|
|
109
|
+
xaxis=dict(**axis_style_3d),
|
|
110
|
+
yaxis=dict(**axis_style_3d),
|
|
111
|
+
zaxis=dict(**axis_style_3d),
|
|
112
|
+
),
|
|
113
|
+
margin=dict(l=10, r=10, t=70, b=10),
|
|
114
|
+
)
|
|
115
|
+
# Direct attribute assignment beats any baked-in template values
|
|
116
|
+
try:
|
|
117
|
+
fig_obj.layout.template = None
|
|
118
|
+
fig_obj.layout.scene.bgcolor = WHITE
|
|
119
|
+
for _ax in (
|
|
120
|
+
fig_obj.layout.scene.xaxis,
|
|
121
|
+
fig_obj.layout.scene.yaxis,
|
|
122
|
+
fig_obj.layout.scene.zaxis,
|
|
123
|
+
):
|
|
124
|
+
_ax.backgroundcolor = WHITE
|
|
125
|
+
_ax.gridcolor = GRID
|
|
126
|
+
_ax.linecolor = LINE
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
else:
|
|
130
|
+
fig_obj.update_layout(
|
|
131
|
+
**common,
|
|
132
|
+
plot_bgcolor=WHITE,
|
|
133
|
+
xaxis=dict(**axis_style_2d),
|
|
134
|
+
yaxis=dict(**axis_style_2d),
|
|
135
|
+
margin=dict(l=75, r=20, t=60, b=70),
|
|
136
|
+
)
|
|
137
|
+
for ax in ["xaxis2", "xaxis3", "yaxis2", "yaxis3"]:
|
|
138
|
+
if getattr(fig_obj.layout, ax, None):
|
|
139
|
+
fig_obj.update_layout(**{ax: axis_style_2d})
|
|
140
|
+
|
|
141
|
+
# Fix colorbar and parcoords fonts
|
|
142
|
+
for trace in fig_obj.data:
|
|
143
|
+
if hasattr(trace, "colorbar") and trace.colorbar:
|
|
144
|
+
trace.colorbar.tickfont = dict(color=DARK)
|
|
145
|
+
if trace.colorbar.title:
|
|
146
|
+
trace.colorbar.title.font = dict(color=DARK)
|
|
147
|
+
if hasattr(trace, "labelfont"):
|
|
148
|
+
trace.labelfont = dict(color=DARK, size=10)
|
|
149
|
+
if hasattr(trace, "tickfont") and trace.type == "parcoords":
|
|
150
|
+
trace.tickfont = dict(color=MID, size=8)
|
|
151
|
+
|
|
152
|
+
png = pio.to_image(
|
|
153
|
+
fig_obj,
|
|
154
|
+
format="png",
|
|
155
|
+
width=int(w_mm * 3.78),
|
|
156
|
+
height=int(h_mm * 3.78),
|
|
157
|
+
scale=2,
|
|
158
|
+
)
|
|
159
|
+
return Image(io.BytesIO(png), width=w_mm * mm, height=h_mm * mm)
|
|
160
|
+
except Exception:
|
|
161
|
+
return None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dash_dracula.plotly_template
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Registers ``pio.templates["dracula"]`` — a Plotly layout template using the
|
|
5
|
+
official Dracula colour palette.
|
|
6
|
+
|
|
7
|
+
Call ``register()`` (or simply ``import dash_dracula``) before creating any
|
|
8
|
+
Plotly figures so that ``template="dracula"`` is available everywhere.
|
|
9
|
+
"""
|
|
10
|
+
import plotly.graph_objects as go
|
|
11
|
+
import plotly.io as pio
|
|
12
|
+
|
|
13
|
+
from .colors import (
|
|
14
|
+
BACKGROUND, CURRENT_LINE, FOREGROUND, COMMENT, COLORWAY,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
TEMPLATE_NAME = "dracula"
|
|
18
|
+
|
|
19
|
+
_AXIS_STYLE = dict(
|
|
20
|
+
gridcolor=CURRENT_LINE,
|
|
21
|
+
zerolinecolor=COMMENT,
|
|
22
|
+
linecolor=COMMENT,
|
|
23
|
+
tickcolor=COMMENT,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def register() -> None:
|
|
28
|
+
"""Register ``pio.templates["dracula"]``. Safe to call multiple times."""
|
|
29
|
+
if TEMPLATE_NAME in pio.templates:
|
|
30
|
+
return
|
|
31
|
+
pio.templates[TEMPLATE_NAME] = go.layout.Template(
|
|
32
|
+
layout=go.Layout(
|
|
33
|
+
paper_bgcolor=BACKGROUND,
|
|
34
|
+
plot_bgcolor=BACKGROUND,
|
|
35
|
+
font=dict(color=FOREGROUND),
|
|
36
|
+
colorway=COLORWAY,
|
|
37
|
+
xaxis=_AXIS_STYLE,
|
|
38
|
+
yaxis=_AXIS_STYLE,
|
|
39
|
+
legend=dict(bgcolor=CURRENT_LINE, bordercolor=COMMENT),
|
|
40
|
+
title=dict(font=dict(color=FOREGROUND)),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def unregister() -> None:
|
|
46
|
+
"""Remove the template from the registry (useful for testing)."""
|
|
47
|
+
pio.templates.pop(TEMPLATE_NAME, None)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dash-dracula-theme
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dracula dark/light theme for Plotly Dash applications
|
|
5
|
+
Author: hjeverts-VSM
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 hjeverts-VSM
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: dash>=4.0
|
|
32
|
+
Requires-Dist: plotly>=5.0
|
|
33
|
+
Requires-Dist: dash-bootstrap-components>=2.0
|
|
34
|
+
Provides-Extra: pdf
|
|
35
|
+
Requires-Dist: reportlab>=4.0; extra == "pdf"
|
|
36
|
+
Requires-Dist: kaleido>=0.2; extra == "pdf"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Dracula for [Dash](https://dash.plotly.com/)
|
|
40
|
+
|
|
41
|
+
> A dark theme for [Plotly Dash](https://dash.plotly.com/) — pip-installable Python package
|
|
42
|
+
> that gives any Dash application the full Dracula colour experience, including a light-mode
|
|
43
|
+
> toggle and always-light PDF export.
|
|
44
|
+
|
|
45
|
+
| Dark mode | Light mode |
|
|
46
|
+
|---|---|
|
|
47
|
+
|  |  |
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
All instructions can be found at [INSTALL.md](INSTALL.md).
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Dark mode** — full Dracula palette applied via CSS variables and a custom Plotly template
|
|
56
|
+
- **Light mode toggle** — single button switches the entire dashboard; all Plotly graphs
|
|
57
|
+
re-colour automatically via a clientside callback
|
|
58
|
+
- **PDF export** — `fig_to_print_image()` always produces print-ready figures on a white
|
|
59
|
+
background, regardless of the current dashboard theme
|
|
60
|
+
- **Official palette** — all 11 Dracula colours exposed as named Python constants
|
|
61
|
+
(`BACKGROUND`, `CYAN`, `PURPLE`, …)
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import dash
|
|
67
|
+
import dash_bootstrap_components as dbc
|
|
68
|
+
import dash_dracula as dr
|
|
69
|
+
|
|
70
|
+
# 1. Create the app with Bootstrap DARKLY as base
|
|
71
|
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
|
|
72
|
+
|
|
73
|
+
# 2. Wire the Dracula theme (copies CSS, registers clientside toggle)
|
|
74
|
+
theme_store = dr.apply_theme(app) # returns dcc.Store for the layout
|
|
75
|
+
|
|
76
|
+
# 3. Use Dracula colours and template in your figures
|
|
77
|
+
fig = go.Figure(layout=dict(template="dracula"))
|
|
78
|
+
fig.add_scatter(y=[1, 2, 3], line_color=dr.CYAN)
|
|
79
|
+
|
|
80
|
+
# 4. Export a figure to a print-ready ReportLab Image (always light)
|
|
81
|
+
from dash_dracula.pdf import fig_to_print_image
|
|
82
|
+
img = fig_to_print_image(fig, w_mm=170, h_mm=100)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See [sample/app.py](sample/app.py) for a complete minimal example.
|
|
86
|
+
|
|
87
|
+
## Team
|
|
88
|
+
|
|
89
|
+
This theme is maintained by the following person(s) and a bunch of
|
|
90
|
+
[awesome contributors](https://github.com/dracula/dash/graphs/contributors).
|
|
91
|
+
|
|
92
|
+
| [hjeverts](https://github.com/hjeverts) |
|
|
93
|
+
|---|
|
|
94
|
+
|
|
95
|
+
## Community
|
|
96
|
+
|
|
97
|
+
- [GitHub](https://github.com/dracula/dracula-theme/discussions) — Best for asking questions and discussing issues.
|
|
98
|
+
- [Discord](https://draculatheme.com/discord-invite) — Best for hanging out with the community.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[MIT License](LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
dash_dracula/__init__.py,sha256=qfK_WBbjn5x_axOcaXzDhYth8H6gxS2wBIuvPpD2bX0,2392
|
|
2
|
+
dash_dracula/bootstrap.py,sha256=Mq0cJzCTxg2wo1OBcSjkoVadYTkoW7l7LXqOTfryY8g,8272
|
|
3
|
+
dash_dracula/colors.py,sha256=hW7Rg3jfq7D1Of_uNp2R_PThfgNDL4CzABhSBk7rQ8A,3578
|
|
4
|
+
dash_dracula/pdf.py,sha256=nEA5ythfbF0OSamkp-QiVPW4PIduCqyqd_eXJnKqlbI,5338
|
|
5
|
+
dash_dracula/plotly_template.py,sha256=Jgi_RB9Csg5w8NlrlaLwHuYno0nljZHT9a1Fo8VgDso,1395
|
|
6
|
+
dash_dracula/assets/dracula.css,sha256=p6soy5CuCAqKwolXl1EKqgmhA0ugHMhBBnoLH-b0tqU,22471
|
|
7
|
+
dash_dracula_theme-0.1.0.dist-info/licenses/LICENSE,sha256=NUWfSilhzKdyXj0Ww-YxBxA5T50NXYYndGP-ruW6KHY,1090
|
|
8
|
+
dash_dracula_theme-0.1.0.dist-info/METADATA,sha256=p0PfySbGzsK1rFIyRGJUujP7rPhjRKGmdf3wU2kHuvo,4032
|
|
9
|
+
dash_dracula_theme-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
dash_dracula_theme-0.1.0.dist-info/top_level.txt,sha256=OxiE4rOyNPMh8CDwG3xEYY0M5UdbUI5BVKfhNoryeUg,13
|
|
11
|
+
dash_dracula_theme-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hjeverts-VSM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dash_dracula
|