@ompchamber/ui 1.0.0 → 2.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/package.json +2 -3
- package/src/components.tsx +54 -42
- package/src/index.tsx +114 -112
- package/src/styles.css +0 -187
- package/src/styles.d.css.ts +0 -16
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ompchamber/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Preact hooks and components for OMPChamber panel plugins.",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.tsx",
|
|
8
|
-
"./components": "./src/components.tsx"
|
|
9
|
-
"./styles.css": "./src/styles.css"
|
|
8
|
+
"./components": "./src/components.tsx"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"src"
|
package/src/components.tsx
CHANGED
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* The panel UI kit.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* These are ordinary host components, not a sandboxed mini-app's widgets: a
|
|
10
|
+
* plugin renders inside the chamber's own tree, so the kit uses the chamber's
|
|
11
|
+
* theme variables and Tailwind utilities directly and looks native in every
|
|
12
|
+
* palette with no CSS bridge.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
14
|
+
* The kit exists so a plugin does not re-invent the same six shapes — a titled
|
|
15
|
+
* panel, a label/value row, a text field, a button, an empty state, a note —
|
|
16
|
+
* and so those shapes stay consistent across plugins the way they are within
|
|
17
|
+
* the chamber itself.
|
|
18
|
+
*
|
|
19
|
+
* Every component takes its colours from the theme variables (`bg-paper`,
|
|
20
|
+
* `text-ink`, `border-ink/10`), never a literal, which is what keeps a plugin
|
|
21
|
+
* legible in the light and dark palettes without knowing they exist.
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
import type { ComponentChildren } from 'preact';
|
|
@@ -29,11 +31,11 @@ interface PanelProps {
|
|
|
29
31
|
/** A panel's outer chrome: a title bar over a scrolling body. */
|
|
30
32
|
export function Panel({ title, children }: PanelProps) {
|
|
31
33
|
return (
|
|
32
|
-
<div class="
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
</
|
|
36
|
-
<div class="
|
|
34
|
+
<div class="flex flex-col h-full w-full bg-paper text-ink">
|
|
35
|
+
<div class="flex-shrink-0 border-b border-ink/10 px-3 py-2">
|
|
36
|
+
<span class="text-xs font-semibold text-ink">{title}</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="flex-1 min-h-0 overflow-auto p-3">{children}</div>
|
|
37
39
|
</div>
|
|
38
40
|
);
|
|
39
41
|
}
|
|
@@ -41,60 +43,55 @@ export function Panel({ title, children }: PanelProps) {
|
|
|
41
43
|
interface FieldProps {
|
|
42
44
|
label: string;
|
|
43
45
|
value: string;
|
|
44
|
-
/** Shown under the value, for a path or an id that would otherwise wrap badly. */
|
|
45
46
|
hint?: string;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/** One labelled fact. The label is a micro-header, the value is machine text. */
|
|
49
50
|
export function Field({ label, value, hint }: FieldProps) {
|
|
50
51
|
return (
|
|
51
|
-
<div class="
|
|
52
|
-
<span class="
|
|
53
|
-
<span class="
|
|
54
|
-
|
|
55
|
-
</span>
|
|
56
|
-
{hint ? <span class="oc-hint">{hint}</span> : null}
|
|
52
|
+
<div class="flex flex-col gap-0.5">
|
|
53
|
+
<span class="text-[10px] uppercase font-mono text-ink/40">{label}</span>
|
|
54
|
+
<span class="text-xs font-mono text-ink break-all">{value}</span>
|
|
55
|
+
{hint ? <span class="text-[11px] text-ink/50">{hint}</span> : null}
|
|
57
56
|
</div>
|
|
58
57
|
);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/** A stack of `Field`s. */
|
|
62
61
|
export function FieldList({ children }: { children: ComponentChildren }) {
|
|
63
|
-
return <div class="
|
|
62
|
+
return <div class="flex flex-col gap-3">{children}</div>;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
interface TextAreaFieldProps {
|
|
67
|
-
id
|
|
66
|
+
id: string;
|
|
68
67
|
label: string;
|
|
68
|
+
value: string;
|
|
69
69
|
placeholder?: string;
|
|
70
|
-
/** `null` while the stored value has not been read yet. */
|
|
71
|
-
value: string | null;
|
|
72
70
|
hint?: string;
|
|
73
|
-
onInput: (
|
|
71
|
+
onInput: (value: string) => void;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/**
|
|
77
75
|
* A labelled textarea.
|
|
78
76
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
77
|
+
* Controlled through `onInput` rather than owning its own state, so the caller
|
|
78
|
+
* decides what a keystroke means — the session-persisting hook debounces the
|
|
79
|
+
* write, while a purely local field can keep the value in the caller.
|
|
82
80
|
*/
|
|
83
|
-
export function TextAreaField({ id, label,
|
|
81
|
+
export function TextAreaField({ id, label, value, placeholder, hint, onInput }: TextAreaFieldProps) {
|
|
84
82
|
return (
|
|
85
|
-
<div class="
|
|
86
|
-
<label class="
|
|
83
|
+
<div class="flex flex-col gap-1">
|
|
84
|
+
<label for={id} class="text-[10px] uppercase font-mono text-ink/40">
|
|
87
85
|
{label}
|
|
88
86
|
</label>
|
|
89
87
|
<textarea
|
|
90
88
|
id={id}
|
|
91
|
-
|
|
89
|
+
value={value}
|
|
92
90
|
placeholder={placeholder}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
onInput={(event) => onInput(event.currentTarget.value)}
|
|
91
|
+
onInput={(event) => onInput((event.currentTarget as HTMLTextAreaElement).value)}
|
|
92
|
+
class="w-full min-h-20 resize-y bg-canvas border border-ink/15 rounded px-2.5 py-1.5 text-xs font-mono text-ink placeholder-ink/35 focus:outline-none focus:border-ink/40"
|
|
96
93
|
/>
|
|
97
|
-
{hint ? <span class="
|
|
94
|
+
{hint ? <span class="text-[11px] text-ink/50">{hint}</span> : null}
|
|
98
95
|
</div>
|
|
99
96
|
);
|
|
100
97
|
}
|
|
@@ -103,13 +100,23 @@ interface ButtonProps {
|
|
|
103
100
|
children: ComponentChildren;
|
|
104
101
|
onClick: () => void;
|
|
105
102
|
disabled?: boolean;
|
|
103
|
+
/** The one action a panel wants the user to take. */
|
|
106
104
|
variant?: 'default' | 'primary';
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
/** A button.
|
|
107
|
+
/** A button. */
|
|
110
108
|
export function Button({ children, onClick, disabled, variant = 'default' }: ButtonProps) {
|
|
109
|
+
const tone =
|
|
110
|
+
variant === 'primary'
|
|
111
|
+
? 'bg-ink text-canvas border-ink hover:opacity-90'
|
|
112
|
+
: 'border-ink/15 hover:bg-ink/5 text-ink';
|
|
111
113
|
return (
|
|
112
|
-
<button
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={onClick}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
class={`inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded border transition-colors disabled:opacity-40 disabled:cursor-default ${tone}`}
|
|
119
|
+
>
|
|
113
120
|
{children}
|
|
114
121
|
</button>
|
|
115
122
|
);
|
|
@@ -117,10 +124,15 @@ export function Button({ children, onClick, disabled, variant = 'default' }: But
|
|
|
117
124
|
|
|
118
125
|
/** Centred placeholder for a panel with nothing to show. */
|
|
119
126
|
export function Empty({ children }: { children: ComponentChildren }) {
|
|
120
|
-
return
|
|
127
|
+
return (
|
|
128
|
+
<div class="h-full flex items-center justify-center px-6 text-center">
|
|
129
|
+
<p class="text-xs text-ink/45 font-mono">{children}</p>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
/** A message. `error` is the only chroma a panel should use. */
|
|
124
135
|
export function Note({ children, tone = 'default' }: { children: ComponentChildren; tone?: 'default' | 'error' }) {
|
|
125
|
-
|
|
136
|
+
const colour = tone === 'error' ? 'text-error' : 'text-ink/60';
|
|
137
|
+
return <p class={`text-[11px] ${colour}`}>{children}</p>;
|
|
126
138
|
}
|
package/src/index.tsx
CHANGED
|
@@ -4,54 +4,85 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The
|
|
7
|
+
* The UI kit a plugin renders with, and the host services it reads through.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Everything here runs IN THE HOST'S TREE — same Preact instance, same
|
|
10
|
+
* document, same theme — so the kit is plain Preact components, not a
|
|
11
|
+
* message-passing client. What it still must not do is reach into the host's
|
|
12
|
+
* own source: this is a published package, so the host INJECTS its services
|
|
13
|
+
* once at boot via `configureUiKit`, and the hooks read them.
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
* a component only fires if that component mounts; a panel whose components
|
|
15
|
-
* never called `useTheme()` silently stopped following the palette.
|
|
16
|
-
* - **The context arrives late.** `workspacePath` and `sessionId` are resolved
|
|
17
|
-
* asynchronously by the chamber, so they are usually empty when `ready`
|
|
18
|
-
* settles. A component that captured `info` once would show "none" over a real
|
|
19
|
-
* workspace.
|
|
15
|
+
* Two rules the hooks exist to enforce, because a plugin gets them wrong:
|
|
20
16
|
*
|
|
21
|
-
*
|
|
17
|
+
* - **Context arrives late.** The active session and workspace are resolved
|
|
18
|
+
* asynchronously, so a component that captured them once would show "none"
|
|
19
|
+
* over a real workspace. Every hook subscribes and re-renders.
|
|
20
|
+
* - **Session state is not component state.** The component is unmounted when
|
|
21
|
+
* its panel is hidden, so a value kept in `useState` alone would be lost on
|
|
22
|
+
* every tab switch. `useSessionValue` reads the chamber's store.
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
|
-
import {
|
|
25
|
-
import { useContext, useEffect, useState } from 'preact/hooks';
|
|
26
|
-
import type { ComponentChildren } from 'preact';
|
|
27
|
-
import { acquirePanel, type ChamberPanelApi, type PanelInfo } from '@ompchamber/plugin-sdk';
|
|
25
|
+
import { useCallback, useEffect, useState } from 'preact/hooks';
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
/** What the host seeds every plugin component with. */
|
|
28
|
+
export interface PanelContext {
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
workspacePath: string | null;
|
|
31
|
+
theme: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The host's own services, injected once.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately narrow: each field is something a plugin genuinely cannot do
|
|
38
|
+
* itself (read the chamber's per-session store, know the live palette, resolve
|
|
39
|
+
* the active workspace). Anything a plugin could read from the DOM it should.
|
|
40
|
+
*/
|
|
41
|
+
export interface UiKitServices {
|
|
42
|
+
/** The current panel context. */
|
|
43
|
+
context(): PanelContext;
|
|
44
|
+
/** Subscribe to context changes (a new session, a palette switch). */
|
|
45
|
+
subscribe(listener: (context: PanelContext) => void): () => void;
|
|
46
|
+
/** Read one per-session value. */
|
|
47
|
+
getSessionValue(sessionId: string | null, key: string): string | null;
|
|
48
|
+
/** Write one per-session value. */
|
|
49
|
+
setSessionValue(sessionId: string | null, key: string, value: string): void;
|
|
50
|
+
/** Read a text file inside the active workspace. Rejects with the reason. */
|
|
51
|
+
readWorkspaceFile(workspacePath: string | null, relativePath: string): Promise<string>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let services: UiKitServices | null = null;
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
/** Called ONCE by the host at boot. A plugin must never call this. */
|
|
57
|
+
export function configureUiKit(next: UiKitServices): void {
|
|
58
|
+
services = next;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function requireServices(): UiKitServices {
|
|
62
|
+
if (!services) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'The OMPChamber UI kit has no host services — this component must be rendered by the OMPChamber app.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return services;
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
/**
|
|
43
|
-
* The live panel
|
|
71
|
+
* The live panel context, re-rendering on every change.
|
|
44
72
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
73
|
+
* `useState` is seeded from the service and updated by its subscription, which
|
|
74
|
+
* is what makes a workspace that arrives after the first paint show up.
|
|
47
75
|
*/
|
|
48
|
-
export function usePanelInfo():
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
export function usePanelInfo(): PanelContext {
|
|
77
|
+
const [context, setContext] = useState<PanelContext>(() => requireServices().context());
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const api = requireServices();
|
|
81
|
+
setContext(api.context());
|
|
82
|
+
return api.subscribe(setContext);
|
|
83
|
+
}, []);
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
export function usePanelApi(): ChamberPanelApi {
|
|
54
|
-
return usePanelContext().api;
|
|
85
|
+
return context;
|
|
55
86
|
}
|
|
56
87
|
|
|
57
88
|
/** The live palette id. */
|
|
@@ -70,42 +101,31 @@ export interface SessionValue {
|
|
|
70
101
|
/**
|
|
71
102
|
* Per-session state, as a `useState` that persists.
|
|
72
103
|
*
|
|
73
|
-
* The
|
|
74
|
-
*
|
|
75
|
-
* switch. Reads once on mount, writes debounced — `input` fires per keystroke,
|
|
76
|
-
* and each one would otherwise be a postMessage round trip.
|
|
104
|
+
* The write is debounced: `onInput` fires per keystroke, and each one would
|
|
105
|
+
* otherwise be a store write and a request.
|
|
77
106
|
*/
|
|
78
107
|
export function useSessionValue(key: string, delayMs = 400): SessionValue {
|
|
79
|
-
const
|
|
108
|
+
const { sessionId } = usePanelInfo();
|
|
80
109
|
const [value, setValue] = useState<string | null>(null);
|
|
81
110
|
const [status, setStatus] = useState<SessionValue['status']>('loading');
|
|
82
111
|
const [error, setError] = useState<string | null>(null);
|
|
83
112
|
|
|
84
113
|
useEffect(() => {
|
|
85
|
-
let cancelled = false;
|
|
86
114
|
setStatus('loading');
|
|
87
|
-
|
|
88
|
-
(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
setError(cause instanceof Error ? cause.message : String(cause));
|
|
96
|
-
setStatus('error');
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
return () => {
|
|
100
|
-
cancelled = true;
|
|
101
|
-
};
|
|
102
|
-
}, [api, key]);
|
|
115
|
+
try {
|
|
116
|
+
setValue(requireServices().getSessionValue(sessionId, key) ?? '');
|
|
117
|
+
setStatus('idle');
|
|
118
|
+
} catch (cause) {
|
|
119
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
120
|
+
setStatus('error');
|
|
121
|
+
}
|
|
122
|
+
}, [sessionId, key]);
|
|
103
123
|
|
|
104
124
|
useEffect(() => {
|
|
105
125
|
if (value === null || status !== 'saving') return;
|
|
106
|
-
const timer = setTimeout(
|
|
126
|
+
const timer = setTimeout(() => {
|
|
107
127
|
try {
|
|
108
|
-
|
|
128
|
+
requireServices().setSessionValue(sessionId, key, value);
|
|
109
129
|
setStatus('saved');
|
|
110
130
|
} catch (cause) {
|
|
111
131
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -113,69 +133,51 @@ export function useSessionValue(key: string, delayMs = 400): SessionValue {
|
|
|
113
133
|
}
|
|
114
134
|
}, delayMs);
|
|
115
135
|
return () => clearTimeout(timer);
|
|
116
|
-
}, [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
setStatus('saving');
|
|
125
|
-
},
|
|
126
|
-
};
|
|
136
|
+
}, [sessionId, key, value, status, delayMs]);
|
|
137
|
+
|
|
138
|
+
const update = useCallback((next: string) => {
|
|
139
|
+
setValue(next);
|
|
140
|
+
setStatus('saving');
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
return { value, status, error, update };
|
|
127
144
|
}
|
|
128
145
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
export interface WorkspaceFile {
|
|
147
|
+
content: string | null;
|
|
148
|
+
loading: boolean;
|
|
149
|
+
error: string | null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Read one text file inside the active workspace. */
|
|
153
|
+
export function useWorkspaceFile(relativePath: string): WorkspaceFile {
|
|
154
|
+
const { workspacePath } = usePanelInfo();
|
|
155
|
+
const [content, setContent] = useState<string | null>(null);
|
|
156
|
+
const [loading, setLoading] = useState(true);
|
|
137
157
|
const [error, setError] = useState<string | null>(null);
|
|
138
158
|
|
|
139
159
|
useEffect(() => {
|
|
140
160
|
let cancelled = false;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
applyTheme(theme);
|
|
158
|
-
setValue((current) => (current ? { ...current, info: { ...current.info, theme } } : current));
|
|
159
|
-
};
|
|
160
|
-
api.onContext = (next) => {
|
|
161
|
-
if (next.theme) applyTheme(next.theme);
|
|
162
|
-
setValue((current) => (current ? { ...current, info: { ...current.info, ...next } } : current));
|
|
163
|
-
};
|
|
164
|
-
setValue({ api, info });
|
|
165
|
-
},
|
|
166
|
-
(cause: unknown) => {
|
|
167
|
-
if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause));
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
|
|
161
|
+
setLoading(true);
|
|
162
|
+
setError(null);
|
|
163
|
+
requireServices()
|
|
164
|
+
.readWorkspaceFile(workspacePath, relativePath)
|
|
165
|
+
.then(
|
|
166
|
+
(text) => {
|
|
167
|
+
if (cancelled) return;
|
|
168
|
+
setContent(text);
|
|
169
|
+
setLoading(false);
|
|
170
|
+
},
|
|
171
|
+
(cause: unknown) => {
|
|
172
|
+
if (cancelled) return;
|
|
173
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
174
|
+
setLoading(false);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
171
177
|
return () => {
|
|
172
178
|
cancelled = true;
|
|
173
|
-
api.onTheme = undefined;
|
|
174
|
-
api.onContext = undefined;
|
|
175
179
|
};
|
|
176
|
-
}, []);
|
|
180
|
+
}, [workspacePath, relativePath]);
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
if (!value) return null;
|
|
180
|
-
return <PanelContext.Provider value={value}>{children}</PanelContext.Provider>;
|
|
182
|
+
return { content, loading, error };
|
|
181
183
|
}
|
package/src/styles.css
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The UI kit's stylesheet.
|
|
3
|
-
*
|
|
4
|
-
* The frame has an opaque origin, so it cannot reach the chamber's stylesheet —
|
|
5
|
-
* the palette arrives as `data-theme` on `<html>` (set by `PanelProvider`) and
|
|
6
|
-
* this file maps it. That is the whole reason a kit ships CSS: without it, every
|
|
7
|
-
* panel would invent its own colours and drift from the app.
|
|
8
|
-
*
|
|
9
|
-
* The palette ids are the two the chamber ships most often. A plugin may add a
|
|
10
|
-
* block for a theme it wants to support; an unknown id leaves the defaults,
|
|
11
|
-
* which is a readable fallback rather than a broken one.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
:root {
|
|
15
|
-
--oc-bg: #faf8f3;
|
|
16
|
-
--oc-paper: #ffffff;
|
|
17
|
-
--oc-ink: #141310;
|
|
18
|
-
--oc-muted: rgba(20, 19, 16, 0.55);
|
|
19
|
-
--oc-faint: rgba(20, 19, 16, 0.38);
|
|
20
|
-
--oc-line: rgba(20, 19, 16, 0.14);
|
|
21
|
-
--oc-error: #b3261e;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
:root[data-theme='one-dark-pro-soft'] {
|
|
25
|
-
--oc-bg: #21252b;
|
|
26
|
-
--oc-paper: #282c34;
|
|
27
|
-
--oc-ink: #d7dae0;
|
|
28
|
-
--oc-muted: rgba(215, 218, 224, 0.6);
|
|
29
|
-
--oc-faint: rgba(215, 218, 224, 0.42);
|
|
30
|
-
--oc-line: rgba(215, 218, 224, 0.16);
|
|
31
|
-
--oc-error: #e06c75;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
html,
|
|
35
|
-
body {
|
|
36
|
-
margin: 0;
|
|
37
|
-
height: 100%;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
body {
|
|
41
|
-
background: var(--oc-bg);
|
|
42
|
-
color: var(--oc-ink);
|
|
43
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
44
|
-
font-size: 13px;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* Panel chrome ------------------------------------------------------------ */
|
|
48
|
-
|
|
49
|
-
.oc-panel {
|
|
50
|
-
display: flex;
|
|
51
|
-
flex-direction: column;
|
|
52
|
-
height: 100%;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.oc-panel-header {
|
|
56
|
-
padding: 12px 14px;
|
|
57
|
-
border-bottom: 1px solid var(--oc-line);
|
|
58
|
-
flex-shrink: 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.oc-panel-title {
|
|
62
|
-
margin: 0;
|
|
63
|
-
font-size: 13px;
|
|
64
|
-
font-weight: 600;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.oc-panel-body {
|
|
68
|
-
flex: 1;
|
|
69
|
-
min-height: 0;
|
|
70
|
-
display: flex;
|
|
71
|
-
flex-direction: column;
|
|
72
|
-
gap: 12px;
|
|
73
|
-
padding: 12px 14px 14px;
|
|
74
|
-
overflow: auto;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* Fields ------------------------------------------------------------------ */
|
|
78
|
-
|
|
79
|
-
.oc-fields {
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
gap: 8px;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.oc-field {
|
|
86
|
-
display: flex;
|
|
87
|
-
flex-direction: column;
|
|
88
|
-
gap: 2px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.oc-label {
|
|
92
|
-
font-size: 10px;
|
|
93
|
-
text-transform: uppercase;
|
|
94
|
-
letter-spacing: 0.06em;
|
|
95
|
-
color: var(--oc-muted);
|
|
96
|
-
font-family: ui-monospace, monospace;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.oc-value {
|
|
100
|
-
font-family: ui-monospace, monospace;
|
|
101
|
-
font-size: 12px;
|
|
102
|
-
word-break: break-all;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.oc-hint {
|
|
106
|
-
font-size: 11px;
|
|
107
|
-
color: var(--oc-muted);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/* Text field -------------------------------------------------------------- */
|
|
111
|
-
|
|
112
|
-
.oc-textarea-field {
|
|
113
|
-
flex: 1;
|
|
114
|
-
min-height: 0;
|
|
115
|
-
display: flex;
|
|
116
|
-
flex-direction: column;
|
|
117
|
-
gap: 6px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.oc-textarea {
|
|
121
|
-
flex: 1;
|
|
122
|
-
min-height: 80px;
|
|
123
|
-
resize: none;
|
|
124
|
-
border: 1px solid var(--oc-line);
|
|
125
|
-
border-radius: 6px;
|
|
126
|
-
padding: 8px;
|
|
127
|
-
font: 12px/1.6 ui-monospace, monospace;
|
|
128
|
-
background: transparent;
|
|
129
|
-
color: inherit;
|
|
130
|
-
outline: none;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.oc-textarea:focus {
|
|
134
|
-
border-color: var(--oc-ink);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.oc-textarea:disabled {
|
|
138
|
-
opacity: 0.5;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* Button ------------------------------------------------------------------ */
|
|
142
|
-
|
|
143
|
-
.oc-button {
|
|
144
|
-
font: inherit;
|
|
145
|
-
font-size: 12px;
|
|
146
|
-
padding: 5px 10px;
|
|
147
|
-
border-radius: 6px;
|
|
148
|
-
border: 1px solid var(--oc-line);
|
|
149
|
-
background: transparent;
|
|
150
|
-
color: inherit;
|
|
151
|
-
cursor: pointer;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.oc-button:hover:not(:disabled) {
|
|
155
|
-
background: color-mix(in srgb, var(--oc-ink) 6%, transparent);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.oc-button:disabled {
|
|
159
|
-
opacity: 0.45;
|
|
160
|
-
cursor: default;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.oc-button-primary {
|
|
164
|
-
background: var(--oc-ink);
|
|
165
|
-
color: var(--oc-bg);
|
|
166
|
-
border-color: var(--oc-ink);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* Messages ---------------------------------------------------------------- */
|
|
170
|
-
|
|
171
|
-
.oc-empty {
|
|
172
|
-
margin: 0;
|
|
173
|
-
padding: 24px 8px;
|
|
174
|
-
text-align: center;
|
|
175
|
-
font-size: 12px;
|
|
176
|
-
color: var(--oc-faint);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.oc-note {
|
|
180
|
-
margin: 0;
|
|
181
|
-
font-size: 11px;
|
|
182
|
-
color: var(--oc-muted);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.oc-note-error {
|
|
186
|
-
color: var(--oc-error);
|
|
187
|
-
}
|
package/src/styles.d.css.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type declaration for `@ompchamber/ui/styles.css`.
|
|
3
|
-
*
|
|
4
|
-
* TypeScript will not accept a side-effect import of a `.css` file without a
|
|
5
|
-
* declaration beside it, and the error (`TS2882: Cannot find module or type
|
|
6
|
-
* declarations for side-effect import`) is the first thing a plugin author would
|
|
7
|
-
* hit. This is the shape TypeScript looks for — `styles.css` resolves to
|
|
8
|
-
* `styles.d.css.ts` — and it requires `allowArbitraryExtensions: true` in the
|
|
9
|
-
* consumer's tsconfig, which the plugin tsconfig in the docs sets.
|
|
10
|
-
*
|
|
11
|
-
* It is exported rather than ambient so the file is a module, which is what
|
|
12
|
-
* `allowArbitraryExtensions` expects; the stylesheet itself is the payload, and
|
|
13
|
-
* nothing is imported from here at runtime.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export {};
|