@frockbot/plugin-ui-theme 0.0.0 → 0.1.1
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/frockbot.json +15 -0
- package/package.json +29 -6
- package/src/client/index.ts +6 -0
- package/src/client/theme.css +338 -0
- package/src/index.ts +1 -0
- package/src/manifest.ts +3 -0
- package/theme.test.ts +56 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +22 -0
- package/README.md +0 -3
package/frockbot.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 3,
|
|
3
|
+
"id": "ui-theme",
|
|
4
|
+
"displayName": "FrockBot UI Theme",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"compatibility": { "frockbot": ">=0.0.1" },
|
|
7
|
+
"contributions": {
|
|
8
|
+
"client": {
|
|
9
|
+
"entry": "./client",
|
|
10
|
+
"mounts": [],
|
|
11
|
+
"outlets": []
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"permissions": []
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-ui-theme",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./client": "./src/client/index.ts",
|
|
9
|
+
"./manifest": "./src/manifest.ts",
|
|
10
|
+
"./frockbot.json": "./frockbot.json",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"frockbot": {
|
|
14
|
+
"manifest": "./frockbot.json"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "vite build",
|
|
18
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@frockbot/client-core": "0.1.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "1.4.0",
|
|
25
|
+
"@types/node": "26.2.0",
|
|
26
|
+
"typescript": "5.9.3",
|
|
27
|
+
"vite": "8.2.2"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
6
32
|
"repository": {
|
|
7
33
|
"type": "git",
|
|
8
34
|
"url": "git+https://github.com/timoconnellaus/frockbot.git",
|
|
9
35
|
"directory": "packages/plugin-ui-theme"
|
|
10
|
-
},
|
|
11
|
-
"publishConfig": {
|
|
12
|
-
"access": "public"
|
|
13
36
|
}
|
|
14
37
|
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
color-scheme: dark;
|
|
7
|
+
color: #f4f2f6;
|
|
8
|
+
background: #1f1e24;
|
|
9
|
+
accent-color: #ec386b;
|
|
10
|
+
font-family:
|
|
11
|
+
Manrope,
|
|
12
|
+
ui-sans-serif,
|
|
13
|
+
-apple-system,
|
|
14
|
+
BlinkMacSystemFont,
|
|
15
|
+
"Segoe UI",
|
|
16
|
+
sans-serif;
|
|
17
|
+
font-synthesis: none;
|
|
18
|
+
|
|
19
|
+
/* Semantic color aliases. Feature plugins consume these names only. */
|
|
20
|
+
--frock-text: #f4f2f6;
|
|
21
|
+
--frock-text-muted: #aaa6b1;
|
|
22
|
+
--frock-text-subtle: #8d8896;
|
|
23
|
+
--frock-on-accent: #fff;
|
|
24
|
+
--frock-surface-window: #1f1e24;
|
|
25
|
+
--frock-surface: #211f26;
|
|
26
|
+
--frock-surface-sidebar: #1a191e;
|
|
27
|
+
--frock-surface-raised: #2c2a33;
|
|
28
|
+
--frock-surface-subtle: #26242c;
|
|
29
|
+
--frock-surface-accent: #432330;
|
|
30
|
+
--frock-surface-accent-soft: #2f2027;
|
|
31
|
+
--frock-border: #3a3742;
|
|
32
|
+
--frock-border-strong: #6c6874;
|
|
33
|
+
--frock-border-focus: #ec386b;
|
|
34
|
+
--frock-action-primary: #ec386b;
|
|
35
|
+
--frock-action-primary-hover: #d92d5f;
|
|
36
|
+
--frock-action-primary-pressed: #c62656;
|
|
37
|
+
--frock-action-secondary: #432330;
|
|
38
|
+
--frock-action-secondary-text: #f2a6bd;
|
|
39
|
+
--frock-success: #73d6aa;
|
|
40
|
+
--frock-success-surface: #203329;
|
|
41
|
+
--frock-success-border: #355f4a;
|
|
42
|
+
--frock-warning: #f0c36a;
|
|
43
|
+
--frock-warning-surface: #3a301e;
|
|
44
|
+
--frock-warning-border: #6d5a2e;
|
|
45
|
+
--frock-danger-text: #ff9aa8;
|
|
46
|
+
--frock-danger-surface: #3a2025;
|
|
47
|
+
--frock-danger-border: #70404a;
|
|
48
|
+
--frock-focus-ring: rgb(236 56 107 / 32%);
|
|
49
|
+
--frock-scrollbar: #514d5b;
|
|
50
|
+
--frock-overlay-tint: rgb(10 9 12 / 76%);
|
|
51
|
+
|
|
52
|
+
/* Shared typography, geometry, elevation, and motion aliases. */
|
|
53
|
+
--frock-font-display: "Archivo Black", Manrope, sans-serif;
|
|
54
|
+
--frock-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Type scale. Feature styles must size text with these aliases only;
|
|
58
|
+
* `scripts/check-ui-styles.ts` rejects literal font sizes. The steps match
|
|
59
|
+
* the desktop chat apps FrockBot sits beside: 13px is the working size,
|
|
60
|
+
* 15px carries titles and primary rows, 11px is reserved for eyebrows.
|
|
61
|
+
*/
|
|
62
|
+
--frock-text-xs: 11px;
|
|
63
|
+
--frock-text-sm: 12px;
|
|
64
|
+
--frock-text-base: 13px;
|
|
65
|
+
--frock-text-md: 14px;
|
|
66
|
+
--frock-text-lg: 15px;
|
|
67
|
+
--frock-text-xl: 17px;
|
|
68
|
+
--frock-text-2xl: 20px;
|
|
69
|
+
--frock-text-display: 24px;
|
|
70
|
+
--frock-leading-tight: 1.2;
|
|
71
|
+
--frock-leading-snug: 1.35;
|
|
72
|
+
--frock-leading-normal: 1.5;
|
|
73
|
+
--frock-tracking-eyebrow: 0.08em;
|
|
74
|
+
--frock-tracking-display: -0.02em;
|
|
75
|
+
|
|
76
|
+
/* Legacy aliases kept for older styles; prefer the scale above. */
|
|
77
|
+
--frock-font-body: var(--frock-text-md);
|
|
78
|
+
--frock-font-label: var(--frock-text-sm);
|
|
79
|
+
--frock-font-title: var(--frock-text-2xl);
|
|
80
|
+
|
|
81
|
+
/* Application geometry shared by the shell and the plugins it hosts. */
|
|
82
|
+
--frock-sidebar-width: 280px;
|
|
83
|
+
--frock-panel-width: 320px;
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Phone geometry. A hand-held viewport has room for one column, so the
|
|
87
|
+
* columns a desktop shows side by side become drawers over it; these are the
|
|
88
|
+
* shared measurements of that layout, in the theme rather than in the shell
|
|
89
|
+
* because panel plugins lay themselves out against the same edges.
|
|
90
|
+
*
|
|
91
|
+
* `--frock-viewport-height` is `100dvh` on a phone: a browser whose toolbar
|
|
92
|
+
* collapses on scroll changes the visible height without changing the layout
|
|
93
|
+
* viewport, and a composer pinned to `100%` of the latter ends up under the
|
|
94
|
+
* toolbar. `--frock-safe-bottom` clears the home indicator, and is zero on
|
|
95
|
+
* every device that has none.
|
|
96
|
+
*/
|
|
97
|
+
--frock-viewport-height: 100%;
|
|
98
|
+
--frock-drawer-width: min(300px, 84vw);
|
|
99
|
+
--frock-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
100
|
+
--frock-safe-top: env(safe-area-inset-top, 0px);
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* The size text is entered at. Mobile Safari zooms the page in on a focused
|
|
104
|
+
* field whose text is below 16px, and a zoomed page cannot be scrolled back
|
|
105
|
+
* out, so the composer and every plugin's inputs size through this alias
|
|
106
|
+
* rather than through the reading scale.
|
|
107
|
+
*/
|
|
108
|
+
--frock-text-input: var(--frock-text-md);
|
|
109
|
+
--frock-titlebar-height: 52px;
|
|
110
|
+
--frock-control-sm: 28px;
|
|
111
|
+
--frock-control-md: 32px;
|
|
112
|
+
--frock-control-lg: 36px;
|
|
113
|
+
--frock-avatar-sm: 28px;
|
|
114
|
+
--frock-avatar-md: 38px;
|
|
115
|
+
--frock-icon-sm: 14px;
|
|
116
|
+
--frock-icon-md: 16px;
|
|
117
|
+
--frock-icon-lg: 18px;
|
|
118
|
+
--frock-radius-control: 10px;
|
|
119
|
+
--frock-radius-card: 14px;
|
|
120
|
+
--frock-radius-panel: 20px;
|
|
121
|
+
--frock-settings-width: 520px;
|
|
122
|
+
--frock-shadow-card: 0 8px 22px rgb(0 0 0 / 24%);
|
|
123
|
+
--frock-shadow-panel: 18px 0 55px rgb(0 0 0 / 42%);
|
|
124
|
+
--frock-shadow-floating: 0 18px 45px rgb(0 0 0 / 44%);
|
|
125
|
+
--frock-shadow-icon: 0 8px 18px rgb(236 56 107 / 28%);
|
|
126
|
+
--frock-shadow-control: 0 8px 28px rgb(0 0 0 / 34%);
|
|
127
|
+
--frock-shadow-composer: 0 12px 32px rgb(0 0 0 / 38%);
|
|
128
|
+
--frock-shadow-right-panel: -16px 0 36px rgb(0 0 0 / 38%);
|
|
129
|
+
--frock-motion-fast: 140ms cubic-bezier(0.2, 0, 0, 1);
|
|
130
|
+
--frock-motion-panel: 220ms cubic-bezier(0.2, 0, 0, 1);
|
|
131
|
+
--frock-motion-enter: 260ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
132
|
+
--frock-fill-hover: rgb(244 242 246 / 7%);
|
|
133
|
+
--frock-fill-pressed: rgb(244 242 246 / 12%);
|
|
134
|
+
--frock-skeleton: rgb(244 242 246 / 8%);
|
|
135
|
+
--frock-skeleton-shine: rgb(255 255 255 / 12%);
|
|
136
|
+
--frock-layer-menu: 30;
|
|
137
|
+
--frock-layer-panel: 40;
|
|
138
|
+
--frock-layer-surface: 100;
|
|
139
|
+
|
|
140
|
+
/* Capability presentation aliases shared across first-party plugins. */
|
|
141
|
+
--frock-computer-accent: #67a9e8;
|
|
142
|
+
--frock-computer-accent-text: #9ecfff;
|
|
143
|
+
--frock-computer-surface: #1e3044;
|
|
144
|
+
--frock-computer-border: #315e87;
|
|
145
|
+
--frock-computer-viewer: #0c0c0c;
|
|
146
|
+
--frock-provider-google: #8ab4f8;
|
|
147
|
+
--frock-text-disabled: #77727f;
|
|
148
|
+
--frock-surface-disabled: #35323b;
|
|
149
|
+
--frock-danger-strong: #ff788c;
|
|
150
|
+
--frock-accent-border: #704052;
|
|
151
|
+
--frock-accent-text: #f2a6bd;
|
|
152
|
+
--frock-accent-surface: #321f28;
|
|
153
|
+
--frock-success-bright: #73d6aa;
|
|
154
|
+
--frock-overlay-border: rgb(255 255 255 / 25%);
|
|
155
|
+
--frock-shadow-accent: 0 10px 24px rgb(236 56 107 / 28%);
|
|
156
|
+
--frock-shadow-auth: 0 24px 70px rgb(0 0 0 / 42%);
|
|
157
|
+
--frock-shadow-avatar: 0 15px 35px rgb(0 0 0 / 34%);
|
|
158
|
+
--frock-shadow-dialog: 0 30px 90px rgb(0 0 0 / 62%);
|
|
159
|
+
--frock-thread-gradient: linear-gradient(
|
|
160
|
+
180deg,
|
|
161
|
+
var(--frock-surface) 0%,
|
|
162
|
+
var(--frock-surface) 78%,
|
|
163
|
+
var(--frock-surface-window) 100%
|
|
164
|
+
);
|
|
165
|
+
--frock-auth-background: radial-gradient(
|
|
166
|
+
circle at 50% 20%,
|
|
167
|
+
rgb(236 56 107 / 14%),
|
|
168
|
+
transparent 34%
|
|
169
|
+
);
|
|
170
|
+
--frock-flock-preview: linear-gradient(
|
|
171
|
+
160deg,
|
|
172
|
+
var(--frock-surface-accent),
|
|
173
|
+
var(--frock-surface-subtle)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
--frock-computer-success-border: #355f4a;
|
|
177
|
+
--frock-computer-error-border: #70404a;
|
|
178
|
+
--frock-computer-error-text: #ff9aa8;
|
|
179
|
+
--frock-computer-error-surface: #3a2025;
|
|
180
|
+
--frock-computer-grid: rgb(244 242 246 / 5%);
|
|
181
|
+
--frock-computer-inset: rgb(244 242 246 / 4%);
|
|
182
|
+
--frock-computer-control-tint: rgb(31 30 36 / 84%);
|
|
183
|
+
--frock-computer-control-surface: rgb(44 42 51 / 96%);
|
|
184
|
+
--frock-computer-focus: rgb(103 169 232 / 34%);
|
|
185
|
+
|
|
186
|
+
--frock-viewer-overlay: rgb(3 3 3 / 94%);
|
|
187
|
+
--frock-viewer-text: #dedede;
|
|
188
|
+
--frock-viewer-text-strong: #e1e1e1;
|
|
189
|
+
--frock-viewer-text-muted: #727272;
|
|
190
|
+
--frock-viewer-text-subtle: #858585;
|
|
191
|
+
--frock-viewer-border: #272727;
|
|
192
|
+
--frock-viewer-border-hover: #3d3d3d;
|
|
193
|
+
--frock-viewer-surface: #141414;
|
|
194
|
+
--frock-viewer-surface-hover: #1b1b1b;
|
|
195
|
+
--frock-viewer-stage: #0c0c0c;
|
|
196
|
+
--frock-viewer-frame: #252525;
|
|
197
|
+
--frock-viewer-close: #a8a8a8;
|
|
198
|
+
--frock-viewer-close-surface: #171717;
|
|
199
|
+
--frock-viewer-blue-border: #2f6699;
|
|
200
|
+
--frock-viewer-blue-text: #b8dcff;
|
|
201
|
+
--frock-viewer-blue-surface: #17324b;
|
|
202
|
+
--frock-viewer-status-border: #333;
|
|
203
|
+
--frock-viewer-status-surface: #151515;
|
|
204
|
+
--frock-viewer-success-border: #24513e;
|
|
205
|
+
--frock-viewer-success-text: #64c99b;
|
|
206
|
+
--frock-viewer-success-surface: #10251c;
|
|
207
|
+
--frock-viewer-control-border: #2b5f92;
|
|
208
|
+
--frock-viewer-control-text: #87c5ff;
|
|
209
|
+
--frock-viewer-control-surface: #102235;
|
|
210
|
+
--frock-viewer-error-border: #5c3034;
|
|
211
|
+
--frock-viewer-error-text: #e28b92;
|
|
212
|
+
--frock-viewer-error-surface: #2a1719;
|
|
213
|
+
--frock-viewer-grid: rgb(255 255 255 / 2%);
|
|
214
|
+
--frock-viewer-grid-surface: #121212;
|
|
215
|
+
--frock-viewer-mark-text: #90c9ff;
|
|
216
|
+
--frock-viewer-mark-surface: #142b42;
|
|
217
|
+
--frock-viewer-content-text: #c8c8c8;
|
|
218
|
+
--frock-viewer-content-muted: #767676;
|
|
219
|
+
--frock-viewer-button-border: #3e3e3e;
|
|
220
|
+
--frock-viewer-button-text: #d8d8d8;
|
|
221
|
+
--frock-viewer-button-surface: #292929;
|
|
222
|
+
--frock-viewer-button-border-hover: #575757;
|
|
223
|
+
--frock-viewer-control-shield: rgb(7 7 7 / 78%);
|
|
224
|
+
--frock-viewer-control-panel: rgb(20 20 20 / 92%);
|
|
225
|
+
--frock-viewer-panel-border: #303030;
|
|
226
|
+
--frock-viewer-shadow-control: 0 4px 18px rgb(0 0 0 / 24%);
|
|
227
|
+
--frock-viewer-shadow-stage: 0 22px 80px rgb(0 0 0 / 42%);
|
|
228
|
+
--frock-viewer-shadow-panel: 0 8px 28px rgb(0 0 0 / 35%);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
html,
|
|
232
|
+
body,
|
|
233
|
+
#app {
|
|
234
|
+
width: 100%;
|
|
235
|
+
height: 100%;
|
|
236
|
+
margin: 0;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
body {
|
|
241
|
+
background: var(--frock-surface-window);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
button,
|
|
245
|
+
input,
|
|
246
|
+
textarea,
|
|
247
|
+
select {
|
|
248
|
+
color: inherit;
|
|
249
|
+
font: inherit;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
button {
|
|
253
|
+
border: 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
button:focus-visible,
|
|
257
|
+
input:focus-visible,
|
|
258
|
+
textarea:focus-visible,
|
|
259
|
+
select:focus-visible,
|
|
260
|
+
summary:focus-visible {
|
|
261
|
+
outline: 3px solid var(--frock-focus-ring);
|
|
262
|
+
outline-offset: 2px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Shared motion primitives. Feature styles reference these by name. */
|
|
266
|
+
@keyframes frock-fade-in {
|
|
267
|
+
from {
|
|
268
|
+
opacity: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
to {
|
|
272
|
+
opacity: 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@keyframes frock-rise-in {
|
|
277
|
+
from {
|
|
278
|
+
opacity: 0;
|
|
279
|
+
transform: translateY(6px);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
to {
|
|
283
|
+
opacity: 1;
|
|
284
|
+
transform: translateY(0);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@keyframes frock-scale-in {
|
|
289
|
+
from {
|
|
290
|
+
opacity: 0;
|
|
291
|
+
transform: scale(0.97);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
to {
|
|
295
|
+
opacity: 1;
|
|
296
|
+
transform: scale(1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@keyframes frock-shimmer {
|
|
301
|
+
from {
|
|
302
|
+
transform: translateX(-100%);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
to {
|
|
306
|
+
transform: translateX(100%);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/*
|
|
311
|
+
* The phone breakpoint. `packages/plugin-shell/src/client/FrockBotApp.vue`
|
|
312
|
+
* matches the same width in `matchMedia`, because which layout is on screen
|
|
313
|
+
* decides behaviour as well as appearance — a drawer that is closed by default
|
|
314
|
+
* and a right panel that does not open over the conversation.
|
|
315
|
+
*/
|
|
316
|
+
@media (max-width: 640px) {
|
|
317
|
+
:root {
|
|
318
|
+
--frock-viewport-height: 100dvh;
|
|
319
|
+
--frock-text-input: 16px;
|
|
320
|
+
|
|
321
|
+
/*
|
|
322
|
+
* Touch, not a cursor. Apple and Google both put the smallest comfortable
|
|
323
|
+
* target at around 44px, and every control in the product is sized through
|
|
324
|
+
* these three aliases.
|
|
325
|
+
*/
|
|
326
|
+
--frock-control-sm: 34px;
|
|
327
|
+
--frock-control-md: 40px;
|
|
328
|
+
--frock-control-lg: 44px;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@media (prefers-reduced-motion: reduce) {
|
|
333
|
+
:root {
|
|
334
|
+
--frock-motion-fast: 0ms linear;
|
|
335
|
+
--frock-motion-panel: 0ms linear;
|
|
336
|
+
--frock-motion-enter: 0ms linear;
|
|
337
|
+
}
|
|
338
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, default as manifest } from "./manifest.js";
|
package/src/manifest.ts
ADDED
package/theme.test.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
const stylesheet = await Bun.file(
|
|
4
|
+
new URL("./src/client/theme.css", import.meta.url),
|
|
5
|
+
).text();
|
|
6
|
+
|
|
7
|
+
function token(name: string): string {
|
|
8
|
+
const match = stylesheet.match(new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, "i"));
|
|
9
|
+
if (!match) throw new Error(`Theme token ${name} is missing a hex value`);
|
|
10
|
+
return match[1].toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function luminance(color: string): number {
|
|
14
|
+
const channels = color
|
|
15
|
+
.slice(1)
|
|
16
|
+
.match(/.{2}/g)!
|
|
17
|
+
.map((channel) => Number.parseInt(channel, 16) / 255)
|
|
18
|
+
.map((channel) =>
|
|
19
|
+
channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,
|
|
20
|
+
);
|
|
21
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function contrast(foreground: string, background: string): number {
|
|
25
|
+
const brighter = Math.max(luminance(foreground), luminance(background));
|
|
26
|
+
const darker = Math.min(luminance(foreground), luminance(background));
|
|
27
|
+
return (brighter + 0.05) / (darker + 0.05);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("dark product theme", () => {
|
|
31
|
+
test("declares one dark native-control theme and the FrockBot accent", () => {
|
|
32
|
+
expect(stylesheet).toMatch(/color-scheme:\s*dark/);
|
|
33
|
+
expect(token("--frock-action-primary")).toBe("#ec386b");
|
|
34
|
+
|
|
35
|
+
for (const surface of [
|
|
36
|
+
"--frock-surface-window",
|
|
37
|
+
"--frock-surface",
|
|
38
|
+
"--frock-surface-sidebar",
|
|
39
|
+
"--frock-surface-raised",
|
|
40
|
+
"--frock-surface-subtle",
|
|
41
|
+
]) {
|
|
42
|
+
expect(luminance(token(surface))).toBeLessThan(0.2);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test.each(["success", "warning", "danger"])(
|
|
47
|
+
"%s status text has at least 4.5:1 contrast on its surface",
|
|
48
|
+
(status) => {
|
|
49
|
+
const text = token(
|
|
50
|
+
status === "danger" ? "--frock-danger-text" : `--frock-${status}`,
|
|
51
|
+
);
|
|
52
|
+
const surface = token(`--frock-${status}-surface`);
|
|
53
|
+
expect(contrast(text, surface)).toBeGreaterThanOrEqual(4.5);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"lib": ["ES2023", "DOM"],
|
|
11
|
+
"types": ["node", "vite/client"],
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts", "vite.config.ts"]
|
|
15
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
build: {
|
|
6
|
+
outDir: fileURLToPath(new URL("./dist", import.meta.url)),
|
|
7
|
+
emptyOutDir: true,
|
|
8
|
+
manifest: "manifest.json",
|
|
9
|
+
lib: {
|
|
10
|
+
entry: fileURLToPath(new URL("./src/client/index.ts", import.meta.url)),
|
|
11
|
+
formats: ["es"],
|
|
12
|
+
},
|
|
13
|
+
rollupOptions: {
|
|
14
|
+
external: ["@frockbot/client-core"],
|
|
15
|
+
output: {
|
|
16
|
+
entryFileNames: "assets/ui-theme-[hash].js",
|
|
17
|
+
chunkFileNames: "assets/chunk-[hash].js",
|
|
18
|
+
assetFileNames: "assets/[name]-[hash][extname]",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
package/README.md
DELETED