react-openrouter-model-picker 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.
@@ -0,0 +1,230 @@
1
+ /*
2
+ * OpenRouter Model Picker — default styles.
3
+ * Everything is driven by --orp-* custom properties, so a host app themes the
4
+ * picker by overriding them on any ancestor (or on .orp itself). No Tailwind
5
+ * or other framework is required in the consuming app.
6
+ */
7
+ .orp {
8
+ --orp-fg: #0f172a;
9
+ --orp-muted: #64748b;
10
+ --orp-bg: #ffffff;
11
+ --orp-surface: #f8fafc;
12
+ --orp-border: #e2e8f0;
13
+ --orp-accent: #6366f1;
14
+ --orp-accent-fg: #4f46e5;
15
+ --orp-accent-bg: rgba(99, 102, 241, 0.1);
16
+ --orp-ring: rgba(99, 102, 241, 0.3);
17
+ --orp-danger: #ef4444;
18
+ --orp-info-fg: #2563eb;
19
+ --orp-info-bg: rgba(59, 130, 246, 0.1);
20
+ --orp-ok-fg: #059669;
21
+ --orp-ok-bg: rgba(16, 185, 129, 0.1);
22
+ --orp-warn-fg: #d97706;
23
+ --orp-warn-bg: rgba(245, 158, 11, 0.1);
24
+ --orp-violet-fg: #7c3aed;
25
+ --orp-violet-bg: rgba(139, 92, 246, 0.1);
26
+ --orp-radius: 12px;
27
+ --orp-radius-sm: 8px;
28
+ --orp-font: inherit;
29
+ --orp-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
30
+ --orp-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
31
+ --orp-z: 30;
32
+
33
+ position: relative;
34
+ font-family: var(--orp-font);
35
+ color: var(--orp-fg);
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 8px;
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ @media (prefers-color-scheme: dark) {
43
+ .orp:not([data-theme="light"]) {
44
+ --orp-fg: #f1f5f9;
45
+ --orp-muted: #94a3b8;
46
+ --orp-bg: #0f172a;
47
+ --orp-surface: #1e293b;
48
+ --orp-border: #334155;
49
+ --orp-accent-fg: #a5b4fc;
50
+ --orp-info-fg: #60a5fa;
51
+ --orp-ok-fg: #34d399;
52
+ --orp-warn-fg: #fbbf24;
53
+ --orp-violet-fg: #c4b5fd;
54
+ }
55
+ }
56
+ .orp[data-theme="dark"] {
57
+ --orp-fg: #f1f5f9;
58
+ --orp-muted: #94a3b8;
59
+ --orp-bg: #0f172a;
60
+ --orp-surface: #1e293b;
61
+ --orp-border: #334155;
62
+ --orp-accent-fg: #a5b4fc;
63
+ --orp-info-fg: #60a5fa;
64
+ --orp-ok-fg: #34d399;
65
+ --orp-warn-fg: #fbbf24;
66
+ --orp-violet-fg: #c4b5fd;
67
+ }
68
+
69
+ .orp *, .orp *::before, .orp *::after { box-sizing: border-box; }
70
+ .orp button { font: inherit; color: inherit; }
71
+
72
+ .orp__label {
73
+ display: flex;
74
+ justify-content: space-between;
75
+ align-items: center;
76
+ gap: 8px;
77
+ font-size: 14px;
78
+ font-weight: 600;
79
+ }
80
+ .orp__label-meta { font-size: 10px; font-weight: 400; color: var(--orp-muted); }
81
+
82
+ /* Summary button */
83
+ .orp__summary {
84
+ width: 100%;
85
+ text-align: left;
86
+ background: var(--orp-surface);
87
+ border: 1px solid var(--orp-border);
88
+ border-radius: var(--orp-radius);
89
+ padding: 12px 16px;
90
+ cursor: pointer;
91
+ transition: border-color 150ms;
92
+ }
93
+ .orp__summary:hover { border-color: var(--orp-muted); }
94
+ .orp__summary:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--orp-ring); }
95
+ .orp--open .orp__summary { border-color: var(--orp-accent); }
96
+ .orp--compact .orp__summary { padding: 8px 12px; }
97
+ .orp__summary:disabled { opacity: 0.5; cursor: not-allowed; }
98
+ .orp__summary-inner { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
99
+ .orp__summary-text { min-width: 0; }
100
+ .orp__summary-title { margin: 0; font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
101
+ .orp__summary-sub { margin: 0; font-size: 11px; color: var(--orp-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
102
+ .orp__summary-sub--error { color: var(--orp-danger); }
103
+ .orp__summary-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
104
+ .orp__chevron { color: var(--orp-muted); transition: transform 150ms; }
105
+ .orp--open .orp__chevron { transform: rotate(180deg); }
106
+ .orp__mono { font-family: var(--orp-mono); }
107
+
108
+ /* Dropdown — anchored to the summary button, not to the whole (flex) root */
109
+ .orp__anchor { position: relative; }
110
+ .orp__popover {
111
+ position: absolute;
112
+ z-index: var(--orp-z);
113
+ top: 100%;
114
+ left: 0; right: 0;
115
+ margin-top: 4px;
116
+ background: var(--orp-bg);
117
+ border: 1px solid var(--orp-border);
118
+ border-radius: var(--orp-radius);
119
+ box-shadow: var(--orp-shadow);
120
+ overflow: hidden;
121
+ }
122
+ .orp__toolbar { padding: 8px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--orp-border); }
123
+ .orp__search { position: relative; }
124
+ .orp__search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--orp-muted); }
125
+ .orp__input {
126
+ width: 100%;
127
+ padding: 8px 12px 8px 36px;
128
+ border-radius: var(--orp-radius-sm);
129
+ background: var(--orp-surface);
130
+ border: 1px solid var(--orp-border);
131
+ color: var(--orp-fg);
132
+ font: inherit;
133
+ font-size: 14px;
134
+ }
135
+ .orp__input::placeholder { color: var(--orp-muted); }
136
+ .orp__input:focus { outline: none; box-shadow: 0 0 0 2px var(--orp-ring); }
137
+ .orp__filters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
138
+ .orp__select {
139
+ padding: 4px 8px;
140
+ border-radius: var(--orp-radius-sm);
141
+ background: var(--orp-surface);
142
+ border: 1px solid var(--orp-border);
143
+ color: var(--orp-fg);
144
+ font: inherit;
145
+ font-size: 12px;
146
+ cursor: pointer;
147
+ }
148
+ .orp__sorts { display: flex; padding: 2px; border-radius: var(--orp-radius-sm); background: var(--orp-surface); border: 1px solid var(--orp-border); }
149
+ .orp__sort {
150
+ padding: 2px 8px;
151
+ border: 0;
152
+ border-radius: 6px;
153
+ background: transparent;
154
+ font-size: 11px;
155
+ font-weight: 500;
156
+ color: var(--orp-muted);
157
+ cursor: pointer;
158
+ }
159
+ .orp__sort:hover { color: var(--orp-fg); }
160
+ .orp__sort--active { background: var(--orp-bg); color: var(--orp-fg); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); }
161
+ .orp__count { margin-left: auto; font-size: 11px; color: var(--orp-muted); }
162
+ .orp__refresh {
163
+ padding: 4px 8px;
164
+ border-radius: var(--orp-radius-sm);
165
+ border: 1px solid var(--orp-border);
166
+ background: transparent;
167
+ font-size: 11px;
168
+ font-weight: 500;
169
+ color: var(--orp-muted);
170
+ cursor: pointer;
171
+ }
172
+ .orp__refresh:hover { color: var(--orp-fg); }
173
+ .orp__refresh:disabled { opacity: 0.5; cursor: default; }
174
+
175
+ .orp__list { max-height: 320px; overflow-y: auto; }
176
+ .orp__message { margin: 0; padding: 16px; font-size: 14px; color: var(--orp-muted); }
177
+ .orp__message--error { color: var(--orp-danger); }
178
+
179
+ .orp__auto {
180
+ width: 100%;
181
+ text-align: left;
182
+ padding: 8px 12px;
183
+ border: 0;
184
+ border-bottom: 1px solid var(--orp-border);
185
+ background: transparent;
186
+ font-size: 14px;
187
+ cursor: pointer;
188
+ }
189
+ .orp__auto:hover { background: var(--orp-surface); }
190
+ .orp__auto--selected { background: var(--orp-accent-bg); color: var(--orp-accent-fg); }
191
+ .orp__auto-id { margin-left: 8px; font-size: 11px; color: var(--orp-muted); }
192
+
193
+ /* Rows */
194
+ .orp__row {
195
+ width: 100%;
196
+ text-align: left;
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 12px;
200
+ padding: 8px 12px;
201
+ border: 0;
202
+ border-bottom: 1px solid var(--orp-border);
203
+ background: transparent;
204
+ cursor: pointer;
205
+ }
206
+ .orp__row:last-child { border-bottom: 0; }
207
+ .orp__row--active { background: var(--orp-surface); }
208
+ .orp__row--selected { background: var(--orp-accent-bg); }
209
+ .orp__row-main { min-width: 0; flex: 1; }
210
+ .orp__row-name { margin: 0; font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
211
+ .orp__row--selected .orp__row-name { font-weight: 600; color: var(--orp-accent-fg); }
212
+ .orp__row-default { margin-left: 8px; font-size: 10px; font-weight: 400; color: var(--orp-muted); }
213
+ .orp__row-id { margin: 0; font-size: 11px; color: var(--orp-muted); font-family: var(--orp-mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
214
+ .orp__row-side { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
215
+ .orp__ctx { width: 40px; text-align: right; font-size: 10px; color: var(--orp-muted); }
216
+ @media (max-width: 640px) { .orp__ctx { display: none; } }
217
+
218
+ .orp__price { font-size: 11px; font-family: var(--orp-mono); white-space: nowrap; }
219
+ .orp__price-sep { color: var(--orp-muted); }
220
+
221
+ /* Badges / chips */
222
+ .orp__badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 500; }
223
+ .orp__badge--info { background: var(--orp-info-bg); color: var(--orp-info-fg); }
224
+ .orp__badge--ok { background: var(--orp-ok-bg); color: var(--orp-ok-fg); }
225
+ .orp__details { display: flex; flex-wrap: wrap; gap: 6px; font-size: 10px; }
226
+ .orp__chip { padding: 2px 8px; border-radius: 999px; font-weight: 500; background: var(--orp-surface); border: 1px solid var(--orp-border); color: var(--orp-muted); }
227
+ .orp__chip--info { background: var(--orp-info-bg); color: var(--orp-info-fg); border-color: transparent; }
228
+ .orp__chip--ok { background: var(--orp-ok-bg); color: var(--orp-ok-fg); border-color: transparent; }
229
+ .orp__chip--warn { background: var(--orp-warn-bg); color: var(--orp-warn-fg); border-color: transparent; }
230
+ .orp__chip--violet { background: var(--orp-violet-bg); color: var(--orp-violet-fg); border-color: transparent; }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "react-openrouter-model-picker",
3
+ "version": "0.1.0",
4
+ "description": "Searchable React picker over every OpenRouter model, with live USD-per-1M-token prices, vendor filter, sorting and keyboard navigation. No Tailwind or state library required.",
5
+ "license": "MIT",
6
+ "author": "Bartlomiej Zimny",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/jash90/openrouter-model-picker.git"
10
+ },
11
+ "homepage": "https://github.com/jash90/openrouter-model-picker#readme",
12
+ "bugs": "https://github.com/jash90/openrouter-model-picker/issues",
13
+ "keywords": [
14
+ "openrouter",
15
+ "react",
16
+ "model-picker",
17
+ "llm",
18
+ "pricing",
19
+ "combobox",
20
+ "select"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": [
24
+ "*.css"
25
+ ],
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ },
35
+ "./styles.css": "./dist/styles.css",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "dev": "vite --config vite.demo.config.ts",
45
+ "build": "vite build && cp src/styles.css dist/styles.css",
46
+ "typecheck": "tsc -p tsconfig.json --noEmit",
47
+ "test": "vitest run",
48
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
49
+ },
50
+ "peerDependencies": {
51
+ "react": ">=18",
52
+ "react-dom": ">=18"
53
+ },
54
+ "devDependencies": {
55
+ "@testing-library/react": "^16.3.0",
56
+ "@types/node": "^22.20.2",
57
+ "@types/react": "^19.1.0",
58
+ "@types/react-dom": "^19.1.0",
59
+ "@vitejs/plugin-react": "^5.0.0",
60
+ "jsdom": "^26.1.0",
61
+ "react": "^19.1.0",
62
+ "react-dom": "^19.1.0",
63
+ "typescript": "^5.8.0",
64
+ "vite": "^7.0.0",
65
+ "vite-plugin-dts": "^4.5.0",
66
+ "vitest": "^3.2.0"
67
+ },
68
+ "engines": {
69
+ "node": ">=18"
70
+ }
71
+ }