@inspectorhub/field-ui 0.0.0-stage → 0.1.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/dist/theme.css ADDED
@@ -0,0 +1,370 @@
1
+ /* ============================================================
2
+ Design System 0523 — theme layer shared by the web app and the
3
+ embeddable field editor package.
4
+
5
+ Single source of truth for the colour, radius, shadow and type tokens the
6
+ editor (and the shared-ui primitives bundled into it) paint with, the three
7
+ colour schemes (light, dark, field), and the few `.ih-*` classes those
8
+ components use. The web app imports this file from app/styles/tailwind.css;
9
+ a host application imports it after `@import "tailwindcss"` and adds
10
+ `@source` for the package (see the package README).
11
+
12
+ App-only tokens (agent portal accent, sidebar widths, the list spacing
13
+ step) and app-only classes stay in app/styles/tailwind.css.
14
+ ============================================================ */
15
+
16
+ @theme {
17
+ /* ── Primary palette ── */
18
+ --color-ih-primary: var(--ih-primary);
19
+ --color-ih-primary-600: var(--ih-primary-600);
20
+ --color-ih-primary-700: var(--ih-primary-700);
21
+ --color-ih-primary-tint: var(--ih-primary-tint);
22
+ --color-ih-primary-glow: var(--ih-primary-glow);
23
+ /* Readable foreground for content on `bg-ih-primary`. brandTokens() re-points
24
+ it per tenant by measuring both candidate WCAG ratios, so a bright brand
25
+ color flips to dark text.
26
+
27
+ NO FALLBACK, deliberately. This read `var(--ih-primary-fg, #ffffff)` and
28
+ `--ih-primary-fg` was declared in no theme block at all, so every
29
+ deployment without a tenant brand colour painted white — 4.53:1 on the light
30
+ primary, but 2.98:1 on the dark and field one, which is what the dark
31
+ primary buttons and active tab pills actually rendered. A fallback here is
32
+ strictly worse than none: it is one fixed colour answering a question that
33
+ has three per-theme answers, and because the token itself resolved to
34
+ nothing, `lint:contrast` read every call site as unmeasurable and skipped 78
35
+ of them in silence. The three theme blocks below are now the only source;
36
+ `checkAliasDefinitions` in scripts/lib/palette-invariants.mjs fails the gate
37
+ if one of them goes missing again. */
38
+ --color-ih-primary-fg: var(--ih-primary-fg);
39
+ /* The brand as TEXT — a different role from the brand as FILL. A tenant's
40
+ "my colour" means buttons and accent bars, and `--ih-primary` keeps their
41
+ exact hex for those; this one may move along the lightness axis until it
42
+ clears 4.5:1 on the card, because 63.6% of sRGB fails AA as text on white
43
+ and one token cannot promise both fidelity and readability. Glyphs take
44
+ `text-ih-primary-text`; `text-ih-primary` is only for a brand FILL
45
+ (e.g. a checkbox accent). */
46
+ --color-ih-primary-text: var(--ih-primary-text);
47
+
48
+ /* ── Foreground / text ── */
49
+ --color-ih-fg-1: var(--ih-fg-1);
50
+ --color-ih-fg-2: var(--ih-fg-2);
51
+ --color-ih-fg-3: var(--ih-fg-3);
52
+ --color-ih-fg-4: var(--ih-fg-4);
53
+ --color-ih-fg-5: var(--ih-fg-5);
54
+ --color-ih-fg-inverse: var(--ih-fg-inverse);
55
+
56
+ /* ── Background / surface ── */
57
+ --color-ih-bg-app: var(--ih-bg-app);
58
+ --color-ih-bg-card: var(--ih-bg-card);
59
+ --color-ih-bg-muted: var(--ih-bg-muted);
60
+ --color-ih-bg-inverse: var(--ih-bg-inverse);
61
+
62
+ /* ── Status / semantic ── */
63
+ --color-ih-ok: var(--ih-status-ok);
64
+ --color-ih-ok-bg: var(--ih-status-ok-bg);
65
+ --color-ih-ok-fg: var(--ih-status-ok-fg);
66
+ --color-ih-watch: var(--ih-status-watch);
67
+ --color-ih-watch-bg: var(--ih-status-watch-bg);
68
+ --color-ih-watch-fg: var(--ih-status-watch-fg);
69
+ --color-ih-bad: var(--ih-status-bad);
70
+ --color-ih-bad-bg: var(--ih-status-bad-bg);
71
+ --color-ih-bad-fg: var(--ih-status-bad-fg);
72
+ --color-ih-info: var(--ih-status-info);
73
+ --color-ih-info-bg: var(--ih-status-info-bg);
74
+ --color-ih-info-fg: var(--ih-status-info-fg);
75
+
76
+ /* ── Borders / shadows ── */
77
+ --color-ih-border: var(--ih-border-color, #e2e8f0);
78
+ --color-ih-border-strong: var(--ih-border-strong-color, #cbd5e1);
79
+
80
+ /* ── Overlay backdrop (single source for every modal/drawer scrim) ── */
81
+ --color-ih-backdrop: var(--ih-backdrop);
82
+
83
+ /* ── Radii (semantic; do not use raw rounded-md/lg/xl in primitives) ── */
84
+ --radius-ih-button: 6px;
85
+ --radius-ih-input: 8px;
86
+ --radius-ih-card: 8px;
87
+ --radius-ih-modal: 12px;
88
+ --radius-ih-pill: 4px;
89
+
90
+ /* ── Spacing scale extensions (established rhythms) ── */
91
+ --spacing-ih-card: 14px; /* compact card padding */
92
+
93
+ /* ── Shadows ── */
94
+ --shadow-ih-card: 0 1px 3px rgba(15, 23, 42, 0.08);
95
+ --shadow-ih-popover: 0 8px 32px rgba(15, 23, 42, 0.12);
96
+ --shadow-ih-focus: 0 0 0 3px var(--ih-primary-glow);
97
+
98
+ /* ── Font families ── */
99
+ --font-ih-display: "Bricolage Grotesque", "Inter", system-ui, sans-serif;
100
+ --font-ih-body: "Inter", system-ui, sans-serif;
101
+ --font-ih-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
102
+ }
103
+
104
+ /* ── CSS custom properties: light mode (default) ── */
105
+ :root {
106
+ /* #6366f1 (indigo-500) measured 4.47:1 on the white card — 0.03 short, and
107
+ symmetric, so the same 4.47 governed an indigo link AND white label text
108
+ on a filled indigo button. #6265f0 is one step of 1/255 per channel and
109
+ measures 4.53:1: below the threshold of vision, a correctness fix rather
110
+ than a visual one, and it retires 193 `lint:contrast` reports. */
111
+ --ih-primary: #6265f0;
112
+ --ih-primary-600: #4f46e5;
113
+ --ih-primary-700: #4338ca;
114
+ --ih-primary-tint: rgba(99, 102, 241, 0.10);
115
+ --ih-primary-glow: rgba(99, 102, 241, 0.25);
116
+ /* Equal to --ih-primary because the fixed default already clears 4.5:1 on
117
+ --ih-bg-card; a tenant colour that does not is moved down the lightness
118
+ axis by brandTokens() until it does. */
119
+ --ih-primary-text: #6265f0;
120
+ /* Text ON the primary fill. White measures 4.53:1 on #6265f0 here; #0f172a
121
+ would be 3.94:1, so light is the one theme where white is the right answer —
122
+ which is exactly why the old fixed `#ffffff` fallback looked correct. */
123
+ --ih-primary-fg: #ffffff;
124
+
125
+ --ih-fg-1: #0f172a;
126
+ --ih-fg-2: #475569;
127
+ --ih-fg-3: #64748b;
128
+ --ih-fg-4: #94a3b8;
129
+ --ih-fg-5: #cbd5e1;
130
+ --ih-fg-inverse: #fff;
131
+
132
+ /* Tells the BROWSER which palette to paint its own controls with: the
133
+ expanded <option> list, autofill highlight, scrollbars, date pickers,
134
+ spinners. Those are rendered by the UA, not by us, so no amount of
135
+ Tailwind reaches them — without this they stay light in every theme, which
136
+ is why a dark-mode select looked fine closed and became dark-on-dark the
137
+ moment it opened. `data-color-scheme` below is our ATTRIBUTE for swapping
138
+ tokens; this is the CSS PROPERTY that talks to the browser. Two different
139
+ things that read almost identically, which is how it went unnoticed. */
140
+ color-scheme: light;
141
+
142
+ --ih-bg-app: #f8fafc;
143
+ --ih-bg-card: #fff;
144
+ --ih-bg-muted: #f1f5f9;
145
+ --ih-bg-inverse: #0f172a;
146
+
147
+ --ih-status-ok: #10b981;
148
+ --ih-status-ok-bg: #ecfdf5;
149
+ --ih-status-ok-fg: #047857;
150
+ --ih-status-watch: #f59e0b;
151
+ --ih-status-watch-bg: #fffbeb;
152
+ --ih-status-watch-fg: #b45309;
153
+ /* red-600, not red-500. `--ih-status-bad` is a FILL: five destructive
154
+ buttons put white text on it, and on #ef4444 that is 3.76:1. #dc2626
155
+ measures 4.83:1. This one IS visible — a deeper red — and is deliberate;
156
+ a danger colour that cannot carry its own label is not a danger colour. */
157
+ --ih-status-bad: #dc2626;
158
+ --ih-status-bad-bg: #fef2f2;
159
+ --ih-status-bad-fg: #be123c;
160
+ --ih-status-info: #0ea5e9;
161
+ --ih-status-info-bg: #f0f9ff;
162
+ --ih-status-info-fg: #0369a1;
163
+
164
+ --ih-border-color: #e2e8f0;
165
+ --ih-border-strong-color: #cbd5e1;
166
+
167
+ --ih-backdrop: rgba(15, 23, 42, 0.55);
168
+ }
169
+
170
+ /* ── Dark mode (the 'field' scheme is dark-based and inherits these vars) ── */
171
+ html[data-color-scheme="dark"], html[data-color-scheme="field"], .dark {
172
+ /* See the note in :root — this is what makes native controls dark. */
173
+ color-scheme: dark;
174
+ --ih-primary: #818cf8;
175
+ --ih-primary-600: #6366f1;
176
+ --ih-primary-700: #4f46e5;
177
+ --ih-primary-tint: rgba(129, 140, 248, 0.10);
178
+ --ih-primary-glow: rgba(129, 140, 248, 0.25);
179
+ /* 4.90:1 on the dark card — already clears, so text and fill agree here.
180
+ The derivation's direction reverses in dark: a failing brand colour is
181
+ moved UP the lightness axis, not down. */
182
+ --ih-primary-text: #818cf8;
183
+ /* The fill is LIGHTER here (#818cf8), so the readable foreground flips to
184
+ near-black: 5.98:1, against 2.98:1 for white. Same value and same reason as
185
+ --ih-fg-inverse in this block — the engine's own primary button already
186
+ writes `text-ih-fg-inverse` and was therefore never broken, while the client
187
+ portal wrote `text-ih-primary-fg` and was. */
188
+ --ih-primary-fg: #0f172a;
189
+
190
+ --ih-fg-1: #f1f5f9;
191
+ --ih-fg-2: #cbd5e1;
192
+ --ih-fg-3: #94a3b8;
193
+ --ih-fg-4: #64748b;
194
+ --ih-fg-5: #334155;
195
+ --ih-fg-inverse: #0f172a;
196
+
197
+ --ih-bg-app: #0f172a;
198
+ --ih-bg-card: #1e293b;
199
+ /* IA-124 — this was #1e293b, i.e. IDENTICAL to --ih-bg-card, so every
200
+ muted-on-card surface in dark mode was invisible. `bg-ih-bg-muted` appears
201
+ 322 times, 150 of them as `hover:`, which is why the symptom was a scatter
202
+ of "that button feels dead" reports rather than one obvious break: muted on
203
+ the APP background still worked, muted on a card did nothing at all.
204
+ Verified by pixel-comparing the Invoices "View inspection" button before
205
+ and after hover — no change whatsoever.
206
+
207
+ Lighter than the card (not darker) because dark UIs elevate on hover, and
208
+ because it must also stay clearly apart from --ih-bg-app for the elements
209
+ that sit directly on the page background. */
210
+ --ih-bg-muted: #273549;
211
+ --ih-bg-inverse: #f8fafc;
212
+
213
+ --ih-status-ok-bg: rgba(16, 185, 129, 0.10);
214
+ --ih-status-ok-fg: #34d399;
215
+ --ih-status-watch-bg: rgba(245, 158, 11, 0.10);
216
+ --ih-status-watch-fg: #fbbf24;
217
+ /* Stays red-500 while light moved to red-600, because `--ih-fg-inverse` is
218
+ not the same colour in the two themes: light puts WHITE on this fill and
219
+ needs it darker (#dc2626 = 4.83:1, #ef4444 was 3.76:1), dark/field put
220
+ near-black #0f172a on it and need it LIGHTER (4.74:1 vs 3.70:1). Until
221
+ this line the token was declared once in `:root` and inherited here, so
222
+ darkening it for light silently broke both dark themes. */
223
+ --ih-status-bad: #ef4444;
224
+ --ih-status-bad-bg: rgba(239, 68, 68, 0.10);
225
+ --ih-status-bad-fg: #f87171;
226
+ --ih-status-info-bg: rgba(14, 165, 233, 0.10);
227
+ --ih-status-info-fg: #38bdf8;
228
+
229
+ --ih-border-color: #334155;
230
+ --ih-border-strong-color: #475569;
231
+
232
+ --ih-backdrop: rgba(2, 6, 23, 0.65);
233
+ }
234
+
235
+ /* ── Field mode (Track H, migration step 5) ──
236
+ High-contrast, large-type variant of dark for outdoor/sunlight use:
237
+ 18px base font (rem-based sizes scale up), near-black background,
238
+ brighter foregrounds and stronger borders. Inherits everything else
239
+ from the dark block above. */
240
+ html[data-color-scheme="field"] {
241
+ font-size: 18px;
242
+
243
+ /* Restated (field's card is darker, #0f172a): 5.99:1 here. */
244
+ --ih-primary-text: #818cf8;
245
+ /* Restated for explicitness, not because it differs: field does not re-declare
246
+ --ih-primary, so the fill is the dark block's #818cf8 and the answer is the
247
+ same #0f172a at 5.98:1. Stated here so the high-contrast theme's on-fill
248
+ colour can be read off its own block instead of inferred from the cascade.
249
+ TOKEN_INVARIANTS measures all three themes, so if field ever takes its own
250
+ --ih-primary these two stop agreeing and the gate says so. */
251
+ --ih-primary-fg: #0f172a;
252
+
253
+ --ih-fg-1: #ffffff;
254
+ --ih-fg-2: #e2e8f0;
255
+ --ih-fg-3: #cbd5e1;
256
+ --ih-fg-4: #94a3b8;
257
+
258
+ --ih-bg-app: #020617;
259
+ --ih-bg-card: #0f172a;
260
+ --ih-bg-muted: #1e293b;
261
+
262
+ --ih-status-ok-fg: #6ee7b7;
263
+ --ih-status-watch-fg: #fcd34d;
264
+ --ih-status-bad-fg: #fca5a5;
265
+ --ih-status-info-fg: #7dd3fc;
266
+
267
+ --ih-border-color: #475569;
268
+ --ih-border-strong-color: #64748b;
269
+ }
270
+
271
+ /* ── Utility classes from Design System §1.6 ── */
272
+ .ih-eyebrow {
273
+ font-size: 9px;
274
+ font-weight: 700;
275
+ text-transform: uppercase;
276
+ letter-spacing: 0.18em;
277
+ /* fg-3, not fg-4. This is a section LABEL — the sidebar's "Workspace" heading
278
+ — so it is text, and fg-4 is the decoration tier (chevrons, dividers,
279
+ placeholders): 2.56:1 on a light card and 3.07:1 on a dark one. fg-3 is
280
+ 4.76 / 5.71 / 12.02. The rule was invisible to `lint:contrast` because the
281
+ gate read Tailwind class strings only and this colour is set in CSS; that is
282
+ now covered by `cssTextRules`. */
283
+ color: var(--ih-fg-3);
284
+ }
285
+
286
+ .ih-input {
287
+ height: 36px;
288
+ padding: 0 12px;
289
+ border-radius: var(--radius-ih-input);
290
+ border: 1px solid var(--ih-border-color);
291
+ background: var(--ih-bg-card);
292
+ color: var(--ih-fg-1);
293
+ font-size: 13px;
294
+ outline: none;
295
+ transition: border-color 150ms, box-shadow 150ms;
296
+ }
297
+ .ih-input:focus {
298
+ border-color: var(--ih-primary);
299
+ box-shadow: var(--shadow-ih-focus);
300
+ }
301
+ /* IA-127 — the design system had NO :disabled rule at all, so a disabled Input
302
+ or Select rendered pixel-identical to a live one. Buttons had always dimmed
303
+ themselves (Button / IconButton / MenuItem / Pagination each hand-roll
304
+ `disabled:opacity-* disabled:cursor-not-allowed`); the form controls, which
305
+ share .ih-input, had nothing. The case that surfaced it: Edit Role locks the
306
+ Kind select and explains why underneath, and the control still looked exactly
307
+ like the two live selects below it.
308
+
309
+ Deliberately opacity + cursor rather than a background swap — a swap would
310
+ want --ih-bg-muted, and that token is only just distinguishable from the card
311
+ again (IA-124). Opacity works in all three themes without another token.
312
+ The chevron is a SIBLING of the select (Select.tsx draws it outside the
313
+ control), so it needs dimming of its own or the control fades while its arrow
314
+ stays bright. */
315
+ .ih-input:disabled {
316
+ opacity: 0.55;
317
+ cursor: not-allowed;
318
+ }
319
+ .ih-input:disabled + svg {
320
+ opacity: 0.55;
321
+ }
322
+
323
+ .ih-kbd {
324
+ display: inline-flex;
325
+ align-items: center;
326
+ justify-content: center;
327
+ min-width: 22px;
328
+ height: 22px;
329
+ padding: 0 5px;
330
+ border-radius: 4px;
331
+ background: var(--ih-bg-muted);
332
+ border: 1px solid var(--ih-border-color);
333
+ font-family: var(--font-ih-mono);
334
+ font-size: 11px;
335
+ /* fg-2, not fg-3, because this rule paints its OWN muted background: fg-3 on
336
+ --ih-bg-muted is 4.34:1 in light, 0.16 short. This is the "Ctrl K" hint next
337
+ to sidebar search — the one place the app names its search shortcut. */
338
+ color: var(--ih-fg-2);
339
+ }
340
+
341
+ .ih-pill {
342
+ display: inline-flex;
343
+ align-items: center;
344
+ gap: 4px;
345
+ padding: 3px 8px;
346
+ border-radius: 4px;
347
+ font-size: 11px;
348
+ font-weight: 700;
349
+ }
350
+ .ih-pill--sat { background: var(--ih-status-ok-bg); color: var(--ih-status-ok-fg); }
351
+ .ih-pill--monitor { background: var(--ih-status-watch-bg); color: var(--ih-status-watch-fg); }
352
+ .ih-pill--defect { background: var(--ih-status-bad-bg); color: var(--ih-status-bad-fg); }
353
+ /* fg-2, not fg-3: the three tone modifiers above each pair a -fg token with its
354
+ own tinted background, but this one sits on --ih-bg-muted, where fg-3 is
355
+ 4.34:1 in light. Its size comes from `.ih-pill` (11px), which is why the gate
356
+ has to read a modifier's BEM base class to know what it is measuring. */
357
+ .ih-pill--ni { background: var(--ih-bg-muted); color: var(--ih-fg-2); }
358
+
359
+ /* ── FileDropzone (shared-ui) ──
360
+ Marching-ants dashes on drag-over (one dash period = 8+6 = 14 units) and
361
+ the ok-flash when a file lands. Registered as @theme animations so
362
+ `animate-ih-dash-march` / `animate-ih-ok-flash` utilities exist. */
363
+ @keyframes ih-dash-march { to { stroke-dashoffset: -14; } }
364
+ @keyframes ih-ok-flash { from { background-color: var(--color-ih-ok-bg); } to { background-color: transparent; } }
365
+ @keyframes ih-slide-in-right { from { transform: translateX(100%); } to { transform: none; } }
366
+ @theme {
367
+ --animate-ih-dash-march: ih-dash-march 0.6s linear infinite;
368
+ --animate-ih-ok-flash: ih-ok-flash 1s ease-out;
369
+ --animate-ih-slide-in-right: ih-slide-in-right 0.2s ease-out;
370
+ }
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@inspectorhub/field-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "The OpenInspection field inspection editor as an embeddable React package.",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/InspectorHub/OpenInspection",
9
+ "directory": "packages/field-ui"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./testing": {
20
+ "types": "./dist/testing.d.ts",
21
+ "import": "./dist/testing.js"
22
+ },
23
+ "./theme.css": "./dist/theme.css"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "sideEffects": [
29
+ "*.css"
30
+ ],
31
+ "peerDependencies": {
32
+ "konva": "^10.2.5",
33
+ "lib0": "^0.2.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0",
36
+ "react-easy-crop": "^6.0.2",
37
+ "react-konva": "^19.2.5",
38
+ "react-photo-album": "^3.6.0",
39
+ "sortablejs": "^1.15.7",
40
+ "yet-another-react-lightbox": "^3.32.0",
41
+ "y-indexeddb": "^9.0.0",
42
+ "y-protocols": "^1.0.0",
43
+ "yjs": "^13.6.0"
44
+ }
45
+ }