@mp-consulting/homebridge-ui-kit 1.0.0
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.
- package/README.md +52 -0
- package/dist/kit.css +253 -0
- package/dist/kit.js +133 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @mp-consulting/homebridge-ui-kit
|
|
2
|
+
|
|
3
|
+
Brand design system for [@mp-consulting](https://github.com/mp-consulting) Homebridge plugins.
|
|
4
|
+
|
|
5
|
+
Provides shared CSS tokens, Bootstrap 5 overrides, and vanilla JS UI components used across all `@mp-consulting` Homebridge plugin config UIs.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
| File | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `dist/kit.css` | CSS tokens, Bootstrap overrides, and shared component styles |
|
|
12
|
+
| `dist/kit.js` | Vanilla JS helpers exposed as `window.MpKit` |
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Copy the built assets into your plugin's `homebridge-ui/public/lib/` directory and reference them in `index.html`:
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<link rel="stylesheet" href="lib/kit.css">
|
|
20
|
+
<!-- after your app scripts: -->
|
|
21
|
+
<script src="lib/kit.js"></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### MpKit API
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// Status badges
|
|
28
|
+
MpKit.StatusBadge.online() // → HTML string
|
|
29
|
+
MpKit.StatusBadge.offline()
|
|
30
|
+
MpKit.StatusBadge.checking()
|
|
31
|
+
MpKit.StatusBadge.disabled()
|
|
32
|
+
|
|
33
|
+
// Empty state
|
|
34
|
+
MpKit.EmptyState.render({ iconClass, title, hint })
|
|
35
|
+
|
|
36
|
+
// Loading placeholder
|
|
37
|
+
MpKit.Loading.render('Loading...')
|
|
38
|
+
|
|
39
|
+
// View switching (.mp-view elements)
|
|
40
|
+
MpKit.View.show('viewId')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install
|
|
47
|
+
npm run build # outputs to dist/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT © MP Consulting
|
package/dist/kit.css
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/* @mp-consulting/homebridge-ui-kit v1.0.0 */
|
|
2
|
+
|
|
3
|
+
/* ── Design tokens ──────────────────────────────────────────────────────────
|
|
4
|
+
CSS custom properties used throughout the design system.
|
|
5
|
+
Override these in your plugin's styles.css to customize per-plugin.
|
|
6
|
+
────────────────────────────────────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
/* Brand */
|
|
10
|
+
--mp-primary: #4f46e5;
|
|
11
|
+
--mp-primary-rgb: 79, 70, 229;
|
|
12
|
+
--mp-primary-hover: #4338ca;
|
|
13
|
+
--mp-primary-subtle: rgba(79, 70, 229, 0.12);
|
|
14
|
+
|
|
15
|
+
/* Status */
|
|
16
|
+
--mp-status-online: #10b981;
|
|
17
|
+
--mp-status-online-glow: rgba(16, 185, 129, 0.35);
|
|
18
|
+
--mp-status-offline: #ef4444;
|
|
19
|
+
--mp-status-checking: #94a3b8;
|
|
20
|
+
|
|
21
|
+
/* Surfaces */
|
|
22
|
+
--mp-surface: rgba(255, 255, 255, 0.03);
|
|
23
|
+
--mp-border: rgba(255, 255, 255, 0.08);
|
|
24
|
+
|
|
25
|
+
/* Shape */
|
|
26
|
+
--mp-radius: 0.5rem;
|
|
27
|
+
--mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── Bootstrap 5 overrides ──────────────────────────────────────────────────
|
|
31
|
+
Remap Bootstrap components to use brand tokens.
|
|
32
|
+
────────────────────────────────────────────────────────────────────────── */
|
|
33
|
+
|
|
34
|
+
/* Primary button */
|
|
35
|
+
.btn-primary {
|
|
36
|
+
--bs-btn-bg: var(--mp-primary);
|
|
37
|
+
--bs-btn-border-color: var(--mp-primary);
|
|
38
|
+
--bs-btn-hover-bg: var(--mp-primary-hover);
|
|
39
|
+
--bs-btn-hover-border-color: var(--mp-primary-hover);
|
|
40
|
+
--bs-btn-active-bg: var(--mp-primary-hover);
|
|
41
|
+
--bs-btn-active-border-color: var(--mp-primary-hover);
|
|
42
|
+
--bs-btn-focus-shadow-rgb: var(--mp-primary-rgb);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Primary spinner */
|
|
46
|
+
.spinner-border.text-primary,
|
|
47
|
+
.spinner-grow.text-primary {
|
|
48
|
+
color: var(--mp-primary) !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ── Components ─────────────────────────────────────────────────────────────
|
|
52
|
+
Reusable UI components used across all Homebridge plugins.
|
|
53
|
+
────────────────────────────────────────────────────────────────────────── */
|
|
54
|
+
|
|
55
|
+
/* ── Plugin header ──────────────────────────────────────── */
|
|
56
|
+
|
|
57
|
+
.mp-header {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 0.875rem;
|
|
61
|
+
margin-bottom: 0.5rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.mp-header-icon {
|
|
65
|
+
font-size: 2rem;
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
line-height: 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.mp-header-title {
|
|
71
|
+
font-size: 1.375rem;
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
margin: 0;
|
|
74
|
+
line-height: 1.3;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.mp-header-subtitle {
|
|
78
|
+
font-size: 0.875rem;
|
|
79
|
+
color: var(--bs-secondary-color);
|
|
80
|
+
margin: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── Status indicator dot ───────────────────────────────── */
|
|
84
|
+
|
|
85
|
+
.mp-status {
|
|
86
|
+
width: 8px;
|
|
87
|
+
height: 8px;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
display: inline-block;
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
vertical-align: middle;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mp-status-online {
|
|
95
|
+
background-color: var(--mp-status-online);
|
|
96
|
+
box-shadow: 0 0 5px var(--mp-status-online-glow);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.mp-status-offline {
|
|
100
|
+
background-color: var(--mp-status-offline);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.mp-status-checking {
|
|
104
|
+
background-color: var(--mp-status-checking);
|
|
105
|
+
animation: mp-pulse 1.5s ease-in-out infinite;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@keyframes mp-pulse {
|
|
109
|
+
0%, 100% { opacity: 1; }
|
|
110
|
+
50% { opacity: 0.35; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── Device card ────────────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
.mp-device-card {
|
|
116
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.mp-device-card:hover {
|
|
121
|
+
transform: translateY(-1px);
|
|
122
|
+
box-shadow: var(--mp-shadow-hover) !important;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ── Settings surface card ──────────────────────────────── */
|
|
126
|
+
|
|
127
|
+
.mp-settings-card {
|
|
128
|
+
background: var(--mp-surface);
|
|
129
|
+
border: 1px solid var(--mp-border) !important;
|
|
130
|
+
border-radius: var(--mp-radius);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ── Empty state ────────────────────────────────────────── */
|
|
134
|
+
|
|
135
|
+
.mp-empty-state {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
padding: 3rem 1.5rem;
|
|
141
|
+
text-align: center;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.mp-empty-state-icon {
|
|
145
|
+
font-size: 2.5rem;
|
|
146
|
+
color: var(--bs-secondary-color);
|
|
147
|
+
margin-bottom: 0.75rem;
|
|
148
|
+
line-height: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.mp-empty-state-title {
|
|
152
|
+
font-size: 0.9375rem;
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
color: var(--bs-secondary-color);
|
|
155
|
+
margin: 0 0 0.25rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.mp-empty-state-hint {
|
|
159
|
+
font-size: 0.8125rem;
|
|
160
|
+
color: var(--bs-secondary-color);
|
|
161
|
+
margin: 0;
|
|
162
|
+
opacity: 0.7;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Loading state ──────────────────────────────────────── */
|
|
166
|
+
|
|
167
|
+
.mp-loading {
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-direction: column;
|
|
170
|
+
align-items: center;
|
|
171
|
+
justify-content: center;
|
|
172
|
+
padding: 2.5rem 1.5rem;
|
|
173
|
+
gap: 0.75rem;
|
|
174
|
+
color: var(--bs-secondary-color);
|
|
175
|
+
font-size: 0.875rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ── Nav tabs ───────────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
.mp-tabs {
|
|
181
|
+
border-bottom: 1px solid var(--mp-border);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.mp-tabs .nav-link {
|
|
185
|
+
color: var(--bs-secondary-color);
|
|
186
|
+
border: none;
|
|
187
|
+
padding: 0.75rem 1.25rem;
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.mp-tabs .nav-link:hover {
|
|
192
|
+
color: var(--bs-body-color);
|
|
193
|
+
border: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.mp-tabs .nav-link.active {
|
|
197
|
+
color: var(--mp-primary);
|
|
198
|
+
background: transparent;
|
|
199
|
+
border: none;
|
|
200
|
+
border-bottom: 2px solid var(--mp-primary);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ── Metadata label ─────────────────────────────────────── */
|
|
204
|
+
|
|
205
|
+
.mp-label {
|
|
206
|
+
font-size: 0.75rem;
|
|
207
|
+
text-transform: uppercase;
|
|
208
|
+
letter-spacing: 0.5px;
|
|
209
|
+
color: var(--bs-secondary-color);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── View toggling ──────────────────────────────────────── */
|
|
213
|
+
|
|
214
|
+
.mp-view {
|
|
215
|
+
display: none;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.mp-view.active {
|
|
219
|
+
display: block;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ── Support footer ─────────────────────────────────────── */
|
|
223
|
+
|
|
224
|
+
.mp-footer {
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
justify-content: center;
|
|
228
|
+
flex-wrap: wrap;
|
|
229
|
+
gap: 0.75rem 1rem;
|
|
230
|
+
padding-top: 1.5rem;
|
|
231
|
+
margin-top: 2rem;
|
|
232
|
+
border-top: 1px solid var(--mp-border);
|
|
233
|
+
font-size: 0.8125rem;
|
|
234
|
+
color: var(--bs-secondary-color);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.mp-footer a {
|
|
238
|
+
color: var(--bs-secondary-color);
|
|
239
|
+
text-decoration: none;
|
|
240
|
+
display: inline-flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
gap: 0.3rem;
|
|
243
|
+
transition: color 0.15s ease;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.mp-footer a:hover {
|
|
247
|
+
color: var(--mp-primary);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.mp-footer-sep {
|
|
251
|
+
color: var(--mp-border);
|
|
252
|
+
user-select: none;
|
|
253
|
+
}
|
package/dist/kit.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* @mp-consulting/homebridge-ui-kit v1.0.0
|
|
2
|
+
Brand design system for Homebridge plugins
|
|
3
|
+
https://github.com/mp-consulting/homebridge-ui-kit */
|
|
4
|
+
|
|
5
|
+
(function (global) {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
var MpKit = {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* StatusBadge — returns inline HTML for device status badges.
|
|
12
|
+
*
|
|
13
|
+
* MpKit.StatusBadge.online() → green dot + "Online"
|
|
14
|
+
* MpKit.StatusBadge.offline() → red dot + "Offline"
|
|
15
|
+
* MpKit.StatusBadge.checking() → animated dot + "Checking..."
|
|
16
|
+
* MpKit.StatusBadge.disabled() → grey badge + "Disabled"
|
|
17
|
+
*/
|
|
18
|
+
StatusBadge: {
|
|
19
|
+
online: function (label) {
|
|
20
|
+
label = label || 'Online';
|
|
21
|
+
return '<span class="badge bg-success-subtle text-success">'
|
|
22
|
+
+ '<span class="mp-status mp-status-online me-1"></span>'
|
|
23
|
+
+ label + '</span>';
|
|
24
|
+
},
|
|
25
|
+
offline: function (label) {
|
|
26
|
+
label = label || 'Offline';
|
|
27
|
+
return '<span class="badge bg-danger-subtle text-danger">'
|
|
28
|
+
+ '<span class="mp-status mp-status-offline me-1"></span>'
|
|
29
|
+
+ label + '</span>';
|
|
30
|
+
},
|
|
31
|
+
checking: function (label) {
|
|
32
|
+
label = label || 'Checking...';
|
|
33
|
+
return '<span class="badge bg-secondary-subtle text-secondary">'
|
|
34
|
+
+ '<span class="mp-status mp-status-checking me-1"></span>'
|
|
35
|
+
+ label + '</span>';
|
|
36
|
+
},
|
|
37
|
+
disabled: function (label) {
|
|
38
|
+
label = label || 'Disabled';
|
|
39
|
+
return '<span class="badge bg-secondary">' + label + '</span>';
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* EmptyState — returns HTML for an empty list placeholder.
|
|
45
|
+
*
|
|
46
|
+
* MpKit.EmptyState.render({
|
|
47
|
+
* iconClass: 'bi bi-lightbulb',
|
|
48
|
+
* title: 'No devices found',
|
|
49
|
+
* hint: 'Click Discover to scan your network',
|
|
50
|
+
* })
|
|
51
|
+
*/
|
|
52
|
+
EmptyState: {
|
|
53
|
+
render: function (opts) {
|
|
54
|
+
opts = opts || {};
|
|
55
|
+
var icon = opts.iconClass || 'bi bi-inbox';
|
|
56
|
+
var title = opts.title || 'No items';
|
|
57
|
+
var hint = opts.hint || '';
|
|
58
|
+
return '<div class="mp-empty-state">'
|
|
59
|
+
+ '<i class="' + icon + ' mp-empty-state-icon"></i>'
|
|
60
|
+
+ '<p class="mp-empty-state-title">' + title + '</p>'
|
|
61
|
+
+ (hint ? '<p class="mp-empty-state-hint">' + hint + '</p>' : '')
|
|
62
|
+
+ '</div>';
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Loading — returns HTML for a centred loading spinner with message.
|
|
68
|
+
*
|
|
69
|
+
* MpKit.Loading.render('Loading device information...')
|
|
70
|
+
*/
|
|
71
|
+
Loading: {
|
|
72
|
+
render: function (message) {
|
|
73
|
+
message = message || 'Loading...';
|
|
74
|
+
return '<div class="mp-loading">'
|
|
75
|
+
+ '<div class="spinner-border spinner-border-sm text-secondary" role="status" aria-hidden="true"></div>'
|
|
76
|
+
+ '<span>' + message + '</span>'
|
|
77
|
+
+ '</div>';
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* View — controls which .mp-view element is visible.
|
|
83
|
+
*
|
|
84
|
+
* MpKit.View.show('listView') → shows #listView, hides all others
|
|
85
|
+
* MpKit.View.show('settingsView') → shows #settingsView, hides all others
|
|
86
|
+
*/
|
|
87
|
+
View: {
|
|
88
|
+
show: function (id) {
|
|
89
|
+
document.querySelectorAll('.mp-view').forEach(function (v) {
|
|
90
|
+
v.classList.remove('active');
|
|
91
|
+
});
|
|
92
|
+
var el = document.getElementById(id);
|
|
93
|
+
if (el) { el.classList.add('active'); }
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Footer — renders support links into a .mp-footer element.
|
|
99
|
+
*
|
|
100
|
+
* MpKit.Footer.render({
|
|
101
|
+
* github: 'https://github.com/mp-consulting/homebridge-...',
|
|
102
|
+
* npm: 'https://www.npmjs.com/package/@mp-consulting/...',
|
|
103
|
+
* changelog: 'https://github.com/.../blob/main/CHANGELOG.md',
|
|
104
|
+
* })
|
|
105
|
+
*/
|
|
106
|
+
Footer: {
|
|
107
|
+
render: function (opts) {
|
|
108
|
+
opts = opts || {};
|
|
109
|
+
var target = opts.target || '.mp-footer';
|
|
110
|
+
var el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
111
|
+
if (!el) { return; }
|
|
112
|
+
var links = [];
|
|
113
|
+
if (opts.github) {
|
|
114
|
+
links.push('<a href="' + opts.github + '" target="_blank" rel="noopener">'
|
|
115
|
+
+ '<i class="bi bi-github"></i>GitHub</a>');
|
|
116
|
+
}
|
|
117
|
+
if (opts.npm) {
|
|
118
|
+
links.push('<a href="' + opts.npm + '" target="_blank" rel="noopener">'
|
|
119
|
+
+ '<i class="bi bi-box-seam"></i>npm</a>');
|
|
120
|
+
}
|
|
121
|
+
if (opts.changelog) {
|
|
122
|
+
links.push('<a href="' + opts.changelog + '" target="_blank" rel="noopener">'
|
|
123
|
+
+ '<i class="bi bi-clock-history"></i>Changelog</a>');
|
|
124
|
+
}
|
|
125
|
+
el.innerHTML = links.join('<span class="mp-footer-sep">|</span>');
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
global.MpKit = MpKit;
|
|
132
|
+
|
|
133
|
+
})(window);
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mp-consulting/homebridge-ui-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Brand design system for @mp-consulting Homebridge plugins",
|
|
5
|
+
"author": "MP Consulting",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mp-consulting/homebridge-ui-kit.git"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "node scripts/build.js"
|
|
17
|
+
}
|
|
18
|
+
}
|