@stigmer/react 0.0.84 → 0.0.86
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/demo/fixtures.d.ts +4 -0
- package/demo/fixtures.d.ts.map +1 -1
- package/demo/fixtures.js +4 -0
- package/demo/fixtures.js.map +1 -1
- package/demo/samples.d.ts +1 -0
- package/demo/samples.d.ts.map +1 -1
- package/demo/samples.js +1 -0
- package/demo/samples.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts +78 -18
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +82 -60
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/index.d.ts +2 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -1
- package/execution/index.js.map +1 -1
- package/index.d.ts +6 -4
- package/index.d.ts.map +1 -1
- package/index.js +4 -2
- package/index.js.map +1 -1
- package/library/ResourceListView.js +1 -1
- package/library/ResourceListView.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.d.ts +51 -0
- package/mcp-server/McpServerConnectDialog.d.ts.map +1 -0
- package/mcp-server/McpServerConnectDialog.js +164 -0
- package/mcp-server/McpServerConnectDialog.js.map +1 -0
- package/mcp-server/McpServerDetailView.js +2 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +7 -1
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/index.d.ts +2 -0
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +1 -0
- package/mcp-server/index.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.d.ts +41 -0
- package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -0
- package/oauth-app/CreateOAuthAppForm.js +140 -0
- package/oauth-app/CreateOAuthAppForm.js.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts +43 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.js +202 -0
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -0
- package/oauth-app/OAuthAppListPanel.d.ts +43 -0
- package/oauth-app/OAuthAppListPanel.d.ts.map +1 -0
- package/oauth-app/OAuthAppListPanel.js +79 -0
- package/oauth-app/OAuthAppListPanel.js.map +1 -0
- package/oauth-app/index.d.ts +15 -0
- package/oauth-app/index.d.ts.map +1 -0
- package/oauth-app/index.js +8 -0
- package/oauth-app/index.js.map +1 -0
- package/oauth-app/useCreateOAuthApp.d.ts +39 -0
- package/oauth-app/useCreateOAuthApp.d.ts.map +1 -0
- package/oauth-app/useCreateOAuthApp.js +50 -0
- package/oauth-app/useCreateOAuthApp.js.map +1 -0
- package/oauth-app/useDeleteOAuthApp.d.ts +31 -0
- package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -0
- package/oauth-app/useDeleteOAuthApp.js +43 -0
- package/oauth-app/useDeleteOAuthApp.js.map +1 -0
- package/oauth-app/useOAuthAppList.d.ts +32 -0
- package/oauth-app/useOAuthAppList.d.ts.map +1 -0
- package/oauth-app/useOAuthAppList.js +61 -0
- package/oauth-app/useOAuthAppList.js.map +1 -0
- package/oauth-app/useUpdateOAuthApp.d.ts +38 -0
- package/oauth-app/useUpdateOAuthApp.d.ts.map +1 -0
- package/oauth-app/useUpdateOAuthApp.js +49 -0
- package/oauth-app/useUpdateOAuthApp.js.map +1 -0
- package/package.json +4 -4
- package/src/demo/fixtures.ts +8 -0
- package/src/demo/samples.ts +2 -0
- package/src/execution/ArtifactPreviewModal.tsx +206 -128
- package/src/execution/index.ts +2 -2
- package/src/index.ts +24 -0
- package/src/library/ResourceListView.tsx +8 -8
- package/src/mcp-server/McpServerConnectDialog.tsx +527 -0
- package/src/mcp-server/McpServerDetailView.tsx +2 -1
- package/src/mcp-server/McpServerPicker.tsx +8 -1
- package/src/mcp-server/index.ts +3 -0
- package/src/oauth-app/CreateOAuthAppForm.tsx +449 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +671 -0
- package/src/oauth-app/OAuthAppListPanel.tsx +237 -0
- package/src/oauth-app/index.ts +14 -0
- package/src/oauth-app/useCreateOAuthApp.ts +70 -0
- package/src/oauth-app/useDeleteOAuthApp.ts +62 -0
- package/src/oauth-app/useOAuthAppList.ts +84 -0
- package/src/oauth-app/useUpdateOAuthApp.ts +69 -0
- package/styles.css +1 -1
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState, type FormEvent } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { getUserMessage } from "@stigmer/sdk";
|
|
6
|
+
import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
7
|
+
import { VendorApprovalStatus } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
8
|
+
import { useCreateOAuthApp } from "./useCreateOAuthApp";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Public API
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Props for {@link CreateOAuthAppForm}. */
|
|
15
|
+
export interface CreateOAuthAppFormProps {
|
|
16
|
+
/** Organization slug — the OAuth app will be created in this org. */
|
|
17
|
+
readonly org: string;
|
|
18
|
+
/** Fired with the newly created OAuth app on success. */
|
|
19
|
+
readonly onCreated?: (app: OAuthApp) => void;
|
|
20
|
+
/** Fired when the user cancels creation. */
|
|
21
|
+
readonly onCancel?: () => void;
|
|
22
|
+
/** Additional CSS class names for the root container. */
|
|
23
|
+
readonly className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Form for creating a new OAuth app within an organization.
|
|
28
|
+
*
|
|
29
|
+
* Collects the required OAuth configuration: **name**, **provider**,
|
|
30
|
+
* **client ID**, **client secret**, **authorization URL**, and
|
|
31
|
+
* **token URL**. An expandable "Advanced" section provides optional
|
|
32
|
+
* fields for scopes, userinfo URL, scope parameter name, and vendor
|
|
33
|
+
* approval settings.
|
|
34
|
+
*
|
|
35
|
+
* This is a pure presentational component with no dialog wrapper
|
|
36
|
+
* (headless-first). The parent is responsible for rendering it inside
|
|
37
|
+
* a card, dialog, or inline context as needed.
|
|
38
|
+
*
|
|
39
|
+
* All visual properties flow through `--stgm-*` design tokens.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <CreateOAuthAppForm
|
|
44
|
+
* org="acme"
|
|
45
|
+
* onCreated={(app) => {
|
|
46
|
+
* refetch();
|
|
47
|
+
* setShowForm(false);
|
|
48
|
+
* }}
|
|
49
|
+
* onCancel={() => setShowForm(false)}
|
|
50
|
+
* />
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function CreateOAuthAppForm({
|
|
54
|
+
org,
|
|
55
|
+
onCreated,
|
|
56
|
+
onCancel,
|
|
57
|
+
className,
|
|
58
|
+
}: CreateOAuthAppFormProps) {
|
|
59
|
+
const { create, isCreating, error, clearError } = useCreateOAuthApp();
|
|
60
|
+
|
|
61
|
+
const [name, setName] = useState("");
|
|
62
|
+
const [provider, setProvider] = useState("");
|
|
63
|
+
const [clientId, setClientId] = useState("");
|
|
64
|
+
const [clientSecret, setClientSecret] = useState("");
|
|
65
|
+
const [authorizationUrl, setAuthorizationUrl] = useState("");
|
|
66
|
+
const [tokenUrl, setTokenUrl] = useState("");
|
|
67
|
+
|
|
68
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
69
|
+
const [scopes, setScopes] = useState("");
|
|
70
|
+
const [userinfoUrl, setUserinfoUrl] = useState("");
|
|
71
|
+
const [scopeParameterName, setScopeParameterName] = useState("");
|
|
72
|
+
const [vendorApprovalStatus, setVendorApprovalStatus] = useState<
|
|
73
|
+
"unspecified" | "pending" | "approved" | "rejected"
|
|
74
|
+
>("unspecified");
|
|
75
|
+
const [vendorApprovalDocsUrl, setVendorApprovalDocsUrl] = useState("");
|
|
76
|
+
|
|
77
|
+
const trimmedName = name.trim();
|
|
78
|
+
const trimmedProvider = provider.trim();
|
|
79
|
+
const trimmedClientId = clientId.trim();
|
|
80
|
+
const trimmedClientSecret = clientSecret.trim();
|
|
81
|
+
const trimmedAuthUrl = authorizationUrl.trim();
|
|
82
|
+
const trimmedTokenUrl = tokenUrl.trim();
|
|
83
|
+
|
|
84
|
+
const canSubmit =
|
|
85
|
+
trimmedName !== "" &&
|
|
86
|
+
trimmedProvider !== "" &&
|
|
87
|
+
trimmedClientId !== "" &&
|
|
88
|
+
trimmedClientSecret !== "" &&
|
|
89
|
+
trimmedAuthUrl !== "" &&
|
|
90
|
+
trimmedTokenUrl !== "" &&
|
|
91
|
+
!isCreating;
|
|
92
|
+
|
|
93
|
+
const handleSubmit = useCallback(
|
|
94
|
+
async (e: FormEvent) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
if (!canSubmit) return;
|
|
97
|
+
|
|
98
|
+
clearError();
|
|
99
|
+
try {
|
|
100
|
+
const parsedScopes = scopes
|
|
101
|
+
.split(",")
|
|
102
|
+
.map((s) => s.trim())
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
|
|
105
|
+
const app = await create({
|
|
106
|
+
name: trimmedName,
|
|
107
|
+
org,
|
|
108
|
+
provider: trimmedProvider,
|
|
109
|
+
clientId: trimmedClientId,
|
|
110
|
+
clientSecret: trimmedClientSecret,
|
|
111
|
+
authorizationUrl: trimmedAuthUrl,
|
|
112
|
+
tokenUrl: trimmedTokenUrl,
|
|
113
|
+
...(parsedScopes.length > 0 && { scopes: parsedScopes }),
|
|
114
|
+
...(userinfoUrl.trim() && { userinfoUrl: userinfoUrl.trim() }),
|
|
115
|
+
...(scopeParameterName.trim() && {
|
|
116
|
+
scopeParameterName: scopeParameterName.trim(),
|
|
117
|
+
}),
|
|
118
|
+
...(vendorApprovalStatus !== "unspecified" && {
|
|
119
|
+
vendorApprovalStatus: APPROVAL_STATUS_MAP[vendorApprovalStatus],
|
|
120
|
+
}),
|
|
121
|
+
...(vendorApprovalDocsUrl.trim() && {
|
|
122
|
+
vendorApprovalDocsUrl: vendorApprovalDocsUrl.trim(),
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
onCreated?.(app);
|
|
126
|
+
} catch {
|
|
127
|
+
// error state is managed by useCreateOAuthApp
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
[
|
|
131
|
+
canSubmit,
|
|
132
|
+
trimmedName,
|
|
133
|
+
org,
|
|
134
|
+
trimmedProvider,
|
|
135
|
+
trimmedClientId,
|
|
136
|
+
trimmedClientSecret,
|
|
137
|
+
trimmedAuthUrl,
|
|
138
|
+
trimmedTokenUrl,
|
|
139
|
+
scopes,
|
|
140
|
+
userinfoUrl,
|
|
141
|
+
scopeParameterName,
|
|
142
|
+
vendorApprovalStatus,
|
|
143
|
+
vendorApprovalDocsUrl,
|
|
144
|
+
create,
|
|
145
|
+
clearError,
|
|
146
|
+
onCreated,
|
|
147
|
+
],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<form onSubmit={handleSubmit} className={cn("space-y-3", className)}>
|
|
152
|
+
<div className="space-y-3">
|
|
153
|
+
<FormField
|
|
154
|
+
id="stgm-oauth-name"
|
|
155
|
+
label="Name"
|
|
156
|
+
value={name}
|
|
157
|
+
onChange={setName}
|
|
158
|
+
placeholder="e.g. My Slack App"
|
|
159
|
+
disabled={isCreating}
|
|
160
|
+
required
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
<FormField
|
|
164
|
+
id="stgm-oauth-provider"
|
|
165
|
+
label="Provider"
|
|
166
|
+
value={provider}
|
|
167
|
+
onChange={setProvider}
|
|
168
|
+
placeholder="e.g. Slack, GitHub, Salesforce"
|
|
169
|
+
hint="Human-readable vendor name for display"
|
|
170
|
+
disabled={isCreating}
|
|
171
|
+
required
|
|
172
|
+
/>
|
|
173
|
+
|
|
174
|
+
<FormField
|
|
175
|
+
id="stgm-oauth-client-id"
|
|
176
|
+
label="Client ID"
|
|
177
|
+
value={clientId}
|
|
178
|
+
onChange={setClientId}
|
|
179
|
+
placeholder="OAuth client identifier"
|
|
180
|
+
disabled={isCreating}
|
|
181
|
+
required
|
|
182
|
+
/>
|
|
183
|
+
|
|
184
|
+
<FormField
|
|
185
|
+
id="stgm-oauth-client-secret"
|
|
186
|
+
label="Client secret"
|
|
187
|
+
value={clientSecret}
|
|
188
|
+
onChange={setClientSecret}
|
|
189
|
+
placeholder="OAuth client secret"
|
|
190
|
+
type="password"
|
|
191
|
+
disabled={isCreating}
|
|
192
|
+
required
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<FormField
|
|
196
|
+
id="stgm-oauth-auth-url"
|
|
197
|
+
label="Authorization URL"
|
|
198
|
+
value={authorizationUrl}
|
|
199
|
+
onChange={setAuthorizationUrl}
|
|
200
|
+
placeholder="https://vendor.com/oauth/authorize"
|
|
201
|
+
hint="Vendor's OAuth authorization endpoint"
|
|
202
|
+
disabled={isCreating}
|
|
203
|
+
required
|
|
204
|
+
/>
|
|
205
|
+
|
|
206
|
+
<FormField
|
|
207
|
+
id="stgm-oauth-token-url"
|
|
208
|
+
label="Token URL"
|
|
209
|
+
value={tokenUrl}
|
|
210
|
+
onChange={setTokenUrl}
|
|
211
|
+
placeholder="https://vendor.com/oauth/token"
|
|
212
|
+
hint="Vendor's OAuth token exchange endpoint"
|
|
213
|
+
disabled={isCreating}
|
|
214
|
+
required
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
{/* Advanced section — collapsed by default */}
|
|
218
|
+
<div>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
onClick={() => setShowAdvanced((v) => !v)}
|
|
222
|
+
className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-[0.65rem] font-medium transition-colors"
|
|
223
|
+
>
|
|
224
|
+
<ChevronIcon expanded={showAdvanced} />
|
|
225
|
+
Advanced settings
|
|
226
|
+
</button>
|
|
227
|
+
|
|
228
|
+
{showAdvanced && (
|
|
229
|
+
<div className="mt-2 space-y-3 border-l-2 border-border/60 pl-3">
|
|
230
|
+
<FormField
|
|
231
|
+
id="stgm-oauth-scopes"
|
|
232
|
+
label="Scopes"
|
|
233
|
+
value={scopes}
|
|
234
|
+
onChange={setScopes}
|
|
235
|
+
placeholder="read, write, admin"
|
|
236
|
+
hint="Comma-separated OAuth scopes to request"
|
|
237
|
+
disabled={isCreating}
|
|
238
|
+
/>
|
|
239
|
+
|
|
240
|
+
<FormField
|
|
241
|
+
id="stgm-oauth-userinfo-url"
|
|
242
|
+
label="Userinfo URL"
|
|
243
|
+
value={userinfoUrl}
|
|
244
|
+
onChange={setUserinfoUrl}
|
|
245
|
+
placeholder="https://vendor.com/userinfo"
|
|
246
|
+
hint="OIDC endpoint for fetching user profile data (optional)"
|
|
247
|
+
disabled={isCreating}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<FormField
|
|
251
|
+
id="stgm-oauth-scope-param"
|
|
252
|
+
label="Scope parameter name"
|
|
253
|
+
value={scopeParameterName}
|
|
254
|
+
onChange={setScopeParameterName}
|
|
255
|
+
placeholder="scope"
|
|
256
|
+
hint='Defaults to "scope". Some vendors use a non-standard name (e.g. "user_scope" for Slack).'
|
|
257
|
+
disabled={isCreating}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
<div className="space-y-1">
|
|
261
|
+
<label
|
|
262
|
+
htmlFor="stgm-oauth-approval-status"
|
|
263
|
+
className="text-xs font-medium text-foreground"
|
|
264
|
+
>
|
|
265
|
+
Vendor approval status
|
|
266
|
+
</label>
|
|
267
|
+
<select
|
|
268
|
+
id="stgm-oauth-approval-status"
|
|
269
|
+
value={vendorApprovalStatus}
|
|
270
|
+
onChange={(e) =>
|
|
271
|
+
setVendorApprovalStatus(
|
|
272
|
+
e.target.value as typeof vendorApprovalStatus,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
disabled={isCreating}
|
|
276
|
+
className={cn(
|
|
277
|
+
"w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
|
|
278
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
279
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
280
|
+
)}
|
|
281
|
+
>
|
|
282
|
+
<option value="unspecified">Unspecified (treated as approved)</option>
|
|
283
|
+
<option value="pending">Pending</option>
|
|
284
|
+
<option value="approved">Approved</option>
|
|
285
|
+
<option value="rejected">Rejected</option>
|
|
286
|
+
</select>
|
|
287
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
288
|
+
Vendor marketplace approval lifecycle status
|
|
289
|
+
</p>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<FormField
|
|
293
|
+
id="stgm-oauth-approval-docs"
|
|
294
|
+
label="Vendor approval docs URL"
|
|
295
|
+
value={vendorApprovalDocsUrl}
|
|
296
|
+
onChange={setVendorApprovalDocsUrl}
|
|
297
|
+
placeholder="https://docs.example.com/byoa"
|
|
298
|
+
hint="Help link shown when vendor approval is pending"
|
|
299
|
+
disabled={isCreating}
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
{error && (
|
|
307
|
+
<p className="text-destructive text-[0.65rem]" role="alert">
|
|
308
|
+
{getUserMessage(error)}
|
|
309
|
+
</p>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
<div className="flex items-center gap-2">
|
|
313
|
+
<button
|
|
314
|
+
type="submit"
|
|
315
|
+
disabled={!canSubmit}
|
|
316
|
+
className={cn(
|
|
317
|
+
"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
318
|
+
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
319
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
320
|
+
)}
|
|
321
|
+
>
|
|
322
|
+
{isCreating && <SpinnerIcon />}
|
|
323
|
+
Create OAuth app
|
|
324
|
+
</button>
|
|
325
|
+
|
|
326
|
+
{onCancel && (
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
onClick={onCancel}
|
|
330
|
+
disabled={isCreating}
|
|
331
|
+
className={cn(
|
|
332
|
+
"rounded-md px-3 py-1.5 text-xs",
|
|
333
|
+
"text-muted-foreground hover:text-foreground hover:bg-accent/50",
|
|
334
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
335
|
+
)}
|
|
336
|
+
>
|
|
337
|
+
Cancel
|
|
338
|
+
</button>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
</form>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Constants
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
const APPROVAL_STATUS_MAP = {
|
|
350
|
+
pending: VendorApprovalStatus.PENDING,
|
|
351
|
+
approved: VendorApprovalStatus.APPROVED,
|
|
352
|
+
rejected: VendorApprovalStatus.REJECTED,
|
|
353
|
+
} as const;
|
|
354
|
+
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// FormField (internal)
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
function FormField({
|
|
360
|
+
id,
|
|
361
|
+
label,
|
|
362
|
+
value,
|
|
363
|
+
onChange,
|
|
364
|
+
placeholder,
|
|
365
|
+
hint,
|
|
366
|
+
type = "text",
|
|
367
|
+
disabled,
|
|
368
|
+
required,
|
|
369
|
+
}: {
|
|
370
|
+
id: string;
|
|
371
|
+
label: string;
|
|
372
|
+
value: string;
|
|
373
|
+
onChange: (v: string) => void;
|
|
374
|
+
placeholder: string;
|
|
375
|
+
hint?: string;
|
|
376
|
+
type?: "text" | "password";
|
|
377
|
+
disabled: boolean;
|
|
378
|
+
required?: boolean;
|
|
379
|
+
}) {
|
|
380
|
+
return (
|
|
381
|
+
<div className="space-y-1">
|
|
382
|
+
<label htmlFor={id} className="text-xs font-medium text-foreground">
|
|
383
|
+
{label}
|
|
384
|
+
</label>
|
|
385
|
+
<input
|
|
386
|
+
id={id}
|
|
387
|
+
type={type}
|
|
388
|
+
value={value}
|
|
389
|
+
onChange={(e) => onChange(e.target.value)}
|
|
390
|
+
placeholder={placeholder}
|
|
391
|
+
disabled={disabled}
|
|
392
|
+
required={required}
|
|
393
|
+
className={cn(
|
|
394
|
+
"w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
|
|
395
|
+
"placeholder:text-muted-foreground",
|
|
396
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
397
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
398
|
+
)}
|
|
399
|
+
/>
|
|
400
|
+
{hint && (
|
|
401
|
+
<p className="text-[0.65rem] text-muted-foreground">{hint}</p>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// Icons
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
function ChevronIcon({ expanded }: { expanded: boolean }) {
|
|
412
|
+
return (
|
|
413
|
+
<svg
|
|
414
|
+
width="10"
|
|
415
|
+
height="10"
|
|
416
|
+
viewBox="0 0 16 16"
|
|
417
|
+
fill="none"
|
|
418
|
+
stroke="currentColor"
|
|
419
|
+
strokeWidth="2"
|
|
420
|
+
strokeLinecap="round"
|
|
421
|
+
strokeLinejoin="round"
|
|
422
|
+
aria-hidden="true"
|
|
423
|
+
className={cn(
|
|
424
|
+
"shrink-0 transition-transform",
|
|
425
|
+
expanded && "rotate-90",
|
|
426
|
+
)}
|
|
427
|
+
>
|
|
428
|
+
<path d="M6 4l4 4-4 4" />
|
|
429
|
+
</svg>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function SpinnerIcon() {
|
|
434
|
+
return (
|
|
435
|
+
<svg
|
|
436
|
+
width="12"
|
|
437
|
+
height="12"
|
|
438
|
+
viewBox="0 0 16 16"
|
|
439
|
+
fill="none"
|
|
440
|
+
stroke="currentColor"
|
|
441
|
+
strokeWidth="2"
|
|
442
|
+
strokeLinecap="round"
|
|
443
|
+
className="animate-spin"
|
|
444
|
+
aria-hidden="true"
|
|
445
|
+
>
|
|
446
|
+
<path d="M8 2a6 6 0 1 0 6 6" />
|
|
447
|
+
</svg>
|
|
448
|
+
);
|
|
449
|
+
}
|