@stigmer/react 0.2.2 → 0.3.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/composer/ComposerToolbar.d.ts +5 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +6 -3
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +17 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +32 -35
- package/composer/SessionComposer.js.map +1 -1
- package/execution/MessageEntry.d.ts +3 -1
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +30 -1
- package/execution/MessageEntry.js.map +1 -1
- package/github/index.d.ts +1 -1
- package/github/index.d.ts.map +1 -1
- package/github/index.js.map +1 -1
- package/github/useGitHubConnection.d.ts +70 -1
- package/github/useGitHubConnection.d.ts.map +1 -1
- package/github/useGitHubConnection.js +99 -20
- package/github/useGitHubConnection.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.d.ts.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +19 -3
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/models/HarnessSelector.d.ts +41 -0
- package/models/HarnessSelector.d.ts.map +1 -0
- package/models/HarnessSelector.js +74 -0
- package/models/HarnessSelector.js.map +1 -0
- package/models/ModelSelector.d.ts +26 -16
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +128 -48
- package/models/ModelSelector.js.map +1 -1
- package/models/__tests__/HarnessSelector.test.d.ts +2 -0
- package/models/__tests__/HarnessSelector.test.d.ts.map +1 -0
- package/models/__tests__/HarnessSelector.test.js +160 -0
- package/models/__tests__/HarnessSelector.test.js.map +1 -0
- package/models/__tests__/harness.test.d.ts +2 -0
- package/models/__tests__/harness.test.d.ts.map +1 -0
- package/models/__tests__/harness.test.js +50 -0
- package/models/__tests__/harness.test.js.map +1 -0
- package/models/__tests__/useModelRegistry.test.d.ts +2 -0
- package/models/__tests__/useModelRegistry.test.d.ts.map +1 -0
- package/models/__tests__/useModelRegistry.test.js +148 -0
- package/models/__tests__/useModelRegistry.test.js.map +1 -0
- package/models/harness.d.ts +21 -0
- package/models/harness.d.ts.map +1 -0
- package/models/harness.js +34 -0
- package/models/harness.js.map +1 -0
- package/models/index.d.ts +7 -2
- package/models/index.d.ts.map +1 -1
- package/models/index.js +3 -1
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +53 -13
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +51 -40
- package/models/registry.js.map +1 -1
- package/models/useModelRegistry.d.ts +39 -19
- package/models/useModelRegistry.d.ts.map +1 -1
- package/models/useModelRegistry.js +45 -23
- package/models/useModelRegistry.js.map +1 -1
- package/organization/OrgProfilePanel.d.ts.map +1 -1
- package/organization/OrgProfilePanel.js +23 -2
- package/organization/OrgProfilePanel.js.map +1 -1
- package/package.json +4 -4
- package/runner/RunnerFileBrowser.d.ts +11 -1
- package/runner/RunnerFileBrowser.d.ts.map +1 -1
- package/runner/RunnerFileBrowser.js +70 -7
- package/runner/RunnerFileBrowser.js.map +1 -1
- package/runner/RunnerListPanel.js +2 -1
- package/runner/RunnerListPanel.js.map +1 -1
- package/runner/WorkspaceRunnerSelector.d.ts +36 -0
- package/runner/WorkspaceRunnerSelector.d.ts.map +1 -0
- package/runner/WorkspaceRunnerSelector.js +63 -0
- package/runner/WorkspaceRunnerSelector.js.map +1 -0
- package/runner/__tests__/phase.test.js +6 -2
- package/runner/__tests__/phase.test.js.map +1 -1
- package/runner/index.d.ts +2 -0
- package/runner/index.d.ts.map +1 -1
- package/runner/index.js +1 -0
- package/runner/index.js.map +1 -1
- package/runner/phase.d.ts +9 -7
- package/runner/phase.d.ts.map +1 -1
- package/runner/phase.js +18 -12
- package/runner/phase.js.map +1 -1
- package/runner/useRunnerFileBrowser.d.ts.map +1 -1
- package/runner/useRunnerFileBrowser.js +26 -2
- package/runner/useRunnerFileBrowser.js.map +1 -1
- package/session/__tests__/useCreateSession.test.d.ts +2 -0
- package/session/__tests__/useCreateSession.test.d.ts.map +1 -0
- package/session/__tests__/useCreateSession.test.js +232 -0
- package/session/__tests__/useCreateSession.test.js.map +1 -0
- package/session/__tests__/useNewSessionFlow.test.d.ts +2 -0
- package/session/__tests__/useNewSessionFlow.test.d.ts.map +1 -0
- package/session/__tests__/useNewSessionFlow.test.js +199 -0
- package/session/__tests__/useNewSessionFlow.test.js.map +1 -0
- package/session/__tests__/useSessionConversation.test.js +37 -0
- package/session/__tests__/useSessionConversation.test.js.map +1 -1
- package/session/index.d.ts +1 -1
- package/session/index.d.ts.map +1 -1
- package/session/useCreateSession.d.ts +8 -0
- package/session/useCreateSession.d.ts.map +1 -1
- package/session/useCreateSession.js +2 -0
- package/session/useCreateSession.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +6 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +34 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/usePersistedModel.d.ts +16 -1
- package/session/usePersistedModel.d.ts.map +1 -1
- package/session/usePersistedModel.js +15 -6
- package/session/usePersistedModel.js.map +1 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +6 -1
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +11 -0
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +11 -2
- package/session/useSessionPageFlow.js.map +1 -1
- package/settings/MembersSection.d.ts.map +1 -1
- package/settings/MembersSection.js +7 -2
- package/settings/MembersSection.js.map +1 -1
- package/src/composer/ComposerToolbar.tsx +24 -1
- package/src/composer/SessionComposer.tsx +81 -44
- package/src/execution/MessageEntry.tsx +134 -1
- package/src/github/index.ts +1 -0
- package/src/github/useGitHubConnection.ts +162 -22
- package/src/identity-provider/IdentityProviderWizard.tsx +112 -3
- package/src/index.ts +16 -1
- package/src/models/HarnessSelector.tsx +130 -0
- package/src/models/ModelSelector.tsx +285 -81
- package/src/models/__tests__/HarnessSelector.test.tsx +190 -0
- package/src/models/__tests__/harness.test.ts +66 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +209 -0
- package/src/models/harness.ts +45 -0
- package/src/models/index.ts +7 -2
- package/src/models/registry.ts +122 -50
- package/src/models/useModelRegistry.ts +74 -24
- package/src/organization/OrgProfilePanel.tsx +98 -0
- package/src/runner/RunnerFileBrowser.tsx +227 -8
- package/src/runner/RunnerListPanel.tsx +13 -5
- package/src/runner/WorkspaceRunnerSelector.tsx +180 -0
- package/src/runner/__tests__/phase.test.ts +6 -2
- package/src/runner/index.ts +3 -0
- package/src/runner/phase.ts +18 -12
- package/src/runner/useRunnerFileBrowser.ts +39 -3
- package/src/session/__tests__/useCreateSession.test.tsx +296 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +258 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +53 -0
- package/src/session/index.ts +1 -1
- package/src/session/useCreateSession.ts +9 -0
- package/src/session/useNewSessionFlow.ts +46 -9
- package/src/session/usePersistedModel.ts +30 -6
- package/src/session/useSessionConversation.ts +6 -1
- package/src/session/useSessionPageFlow.ts +26 -2
- package/src/settings/MembersSection.tsx +23 -1
- package/src/workspace/WorkspaceEditor.tsx +176 -126
- package/src/workspace/index.ts +5 -0
- package/src/workspace/useRecentWorkspaces.ts +162 -0
- package/src/workspace/useWorkspaceEntries.ts +13 -0
- package/styles.css +1 -1
- package/workspace/WorkspaceEditor.d.ts +25 -22
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +64 -43
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/index.d.ts +2 -0
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +1 -0
- package/workspace/index.js.map +1 -1
- package/workspace/useRecentWorkspaces.d.ts +31 -0
- package/workspace/useRecentWorkspaces.d.ts.map +1 -0
- package/workspace/useRecentWorkspaces.js +117 -0
- package/workspace/useRecentWorkspaces.js.map +1 -0
- package/workspace/useWorkspaceEntries.d.ts +8 -0
- package/workspace/useWorkspaceEntries.d.ts.map +1 -1
- package/workspace/useWorkspaceEntries.js +4 -0
- package/workspace/useWorkspaceEntries.js.map +1 -1
|
@@ -26,7 +26,7 @@ export interface IdentityProviderWizardProps {
|
|
|
26
26
|
readonly className?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
type WizardStep = "pick" | "configure" | "review";
|
|
29
|
+
type WizardStep = "pick" | "configure" | "review" | "success";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Multi-step wizard for creating a new identity provider.
|
|
@@ -88,6 +88,9 @@ export function IdentityProviderWizard({
|
|
|
88
88
|
const [autoGrantRole, setAutoGrantRole] = useState<IamRole>(IamRole.iam_role_unspecified);
|
|
89
89
|
const [tenantOrgClaim, setTenantOrgClaim] = useState("");
|
|
90
90
|
|
|
91
|
+
// Success step
|
|
92
|
+
const [createdIdp, setCreatedIdp] = useState<IdentityProvider | null>(null);
|
|
93
|
+
|
|
91
94
|
// -- Step transitions ------------------------------------------------
|
|
92
95
|
|
|
93
96
|
const handlePickProvider = useCallback((selected: ProviderPreset) => {
|
|
@@ -186,7 +189,8 @@ export function IdentityProviderWizard({
|
|
|
186
189
|
}),
|
|
187
190
|
}),
|
|
188
191
|
});
|
|
189
|
-
|
|
192
|
+
setCreatedIdp(idp);
|
|
193
|
+
setStep("success");
|
|
190
194
|
} catch {
|
|
191
195
|
// error state is managed by useCreateIdentityProvider
|
|
192
196
|
}
|
|
@@ -194,7 +198,7 @@ export function IdentityProviderWizard({
|
|
|
194
198
|
[
|
|
195
199
|
name, org, jwksUri, issuers, audience, userinfoEndpoint,
|
|
196
200
|
isSso, oidcClientId, autoProvision, autoGrant, autoGrantRole,
|
|
197
|
-
tenantOrgClaim, create, clearError,
|
|
201
|
+
tenantOrgClaim, create, clearError,
|
|
198
202
|
],
|
|
199
203
|
);
|
|
200
204
|
|
|
@@ -267,6 +271,18 @@ export function IdentityProviderWizard({
|
|
|
267
271
|
onCancel={onCancel}
|
|
268
272
|
/>
|
|
269
273
|
)}
|
|
274
|
+
|
|
275
|
+
{step === "success" && createdIdp && (
|
|
276
|
+
<SuccessStep
|
|
277
|
+
identityProvider={createdIdp}
|
|
278
|
+
org={org}
|
|
279
|
+
isSso={isSso}
|
|
280
|
+
autoProvision={autoProvision}
|
|
281
|
+
autoGrant={autoGrant}
|
|
282
|
+
autoGrantRole={autoGrantRole}
|
|
283
|
+
onDone={() => onCreated?.(createdIdp)}
|
|
284
|
+
/>
|
|
285
|
+
)}
|
|
270
286
|
</div>
|
|
271
287
|
);
|
|
272
288
|
}
|
|
@@ -279,6 +295,7 @@ const STEPS: { key: WizardStep; label: string }[] = [
|
|
|
279
295
|
{ key: "pick", label: "Provider" },
|
|
280
296
|
{ key: "configure", label: "Configure" },
|
|
281
297
|
{ key: "review", label: "Review" },
|
|
298
|
+
{ key: "success", label: "Done" },
|
|
282
299
|
];
|
|
283
300
|
|
|
284
301
|
function StepIndicator({ current }: { current: WizardStep }) {
|
|
@@ -608,6 +625,98 @@ function ReviewStep({
|
|
|
608
625
|
);
|
|
609
626
|
}
|
|
610
627
|
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
// Success step (step 4)
|
|
630
|
+
// ---------------------------------------------------------------------------
|
|
631
|
+
|
|
632
|
+
function SuccessStep({
|
|
633
|
+
identityProvider,
|
|
634
|
+
org,
|
|
635
|
+
isSso,
|
|
636
|
+
autoProvision,
|
|
637
|
+
autoGrant,
|
|
638
|
+
autoGrantRole,
|
|
639
|
+
onDone,
|
|
640
|
+
}: {
|
|
641
|
+
identityProvider: IdentityProvider;
|
|
642
|
+
org: string;
|
|
643
|
+
isSso: boolean;
|
|
644
|
+
autoProvision: boolean;
|
|
645
|
+
autoGrant: boolean;
|
|
646
|
+
autoGrantRole: IamRole;
|
|
647
|
+
onDone: () => void;
|
|
648
|
+
}) {
|
|
649
|
+
const displayName =
|
|
650
|
+
identityProvider.spec?.displayName ||
|
|
651
|
+
identityProvider.metadata?.name ||
|
|
652
|
+
"Identity provider";
|
|
653
|
+
|
|
654
|
+
const roleName =
|
|
655
|
+
autoGrantRole !== IamRole.iam_role_unspecified
|
|
656
|
+
? IamRole[autoGrantRole]
|
|
657
|
+
: "viewer";
|
|
658
|
+
|
|
659
|
+
return (
|
|
660
|
+
<div className="space-y-4">
|
|
661
|
+
<div className="rounded-md border border-primary/30 bg-primary-subtle px-3 py-2.5">
|
|
662
|
+
<p className="text-xs font-medium text-foreground">
|
|
663
|
+
{displayName} created successfully
|
|
664
|
+
</p>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<div className="space-y-2">
|
|
668
|
+
<p className="text-xs font-medium text-foreground">What happens next</p>
|
|
669
|
+
|
|
670
|
+
{isSso ? (
|
|
671
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
672
|
+
Users can sign in via SSO at{" "}
|
|
673
|
+
<span className="font-mono text-foreground">
|
|
674
|
+
/login?org={org}
|
|
675
|
+
</span>
|
|
676
|
+
. Accounts are auto-provisioned and granted the{" "}
|
|
677
|
+
<span className="font-medium text-foreground">viewer</span> role on
|
|
678
|
+
this organization.
|
|
679
|
+
</p>
|
|
680
|
+
) : autoProvision && autoGrant ? (
|
|
681
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
682
|
+
Users authenticating with JWTs from this provider will be
|
|
683
|
+
automatically provisioned and granted the{" "}
|
|
684
|
+
<span className="font-medium text-foreground">{roleName}</span> role
|
|
685
|
+
on this organization. No additional setup is required.
|
|
686
|
+
</p>
|
|
687
|
+
) : autoProvision ? (
|
|
688
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
689
|
+
Accounts are auto-provisioned on first authentication, but no
|
|
690
|
+
organization role is granted automatically. Use the Members page to
|
|
691
|
+
grant access.
|
|
692
|
+
</p>
|
|
693
|
+
) : (
|
|
694
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
695
|
+
The trust relationship is configured. Accounts must be created
|
|
696
|
+
manually before users can authenticate.
|
|
697
|
+
</p>
|
|
698
|
+
)}
|
|
699
|
+
|
|
700
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
701
|
+
To verify the setup, have a user authenticate with a JWT from this
|
|
702
|
+
provider and confirm they can access the organization's resources.
|
|
703
|
+
</p>
|
|
704
|
+
</div>
|
|
705
|
+
|
|
706
|
+
<button
|
|
707
|
+
type="button"
|
|
708
|
+
onClick={onDone}
|
|
709
|
+
className={cn(
|
|
710
|
+
"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
711
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
712
|
+
)}
|
|
713
|
+
>
|
|
714
|
+
Done
|
|
715
|
+
</button>
|
|
716
|
+
</div>
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
611
720
|
// ---------------------------------------------------------------------------
|
|
612
721
|
// Shared primitives
|
|
613
722
|
// ---------------------------------------------------------------------------
|
package/src/index.ts
CHANGED
|
@@ -18,19 +18,32 @@ export {
|
|
|
18
18
|
export { type DeploymentMode, isResourceAvailable, ApiResourceKind } from "@stigmer/sdk";
|
|
19
19
|
export { CloudFeatureNotice, type CloudFeatureNoticeProps } from "./internal/CloudFeatureNotice";
|
|
20
20
|
|
|
21
|
-
// Models — data hook, styled
|
|
21
|
+
// Models — data hook, styled components, and registry data
|
|
22
22
|
export {
|
|
23
23
|
MODEL_REGISTRY,
|
|
24
24
|
DEFAULT_MODEL_ID,
|
|
25
|
+
DEFAULT_CURSOR_MODEL_ID,
|
|
26
|
+
DISABLED_PROVIDERS,
|
|
27
|
+
modelKey,
|
|
28
|
+
parseModelKey,
|
|
25
29
|
useModelRegistry,
|
|
26
30
|
ModelSelector,
|
|
31
|
+
HarnessSelector,
|
|
32
|
+
DEFAULT_HARNESS,
|
|
33
|
+
HARNESS_LABELS,
|
|
34
|
+
toProtoHarness,
|
|
35
|
+
fromProtoHarness,
|
|
27
36
|
} from "./models";
|
|
28
37
|
export type {
|
|
29
38
|
ModelInfo,
|
|
39
|
+
ParsedModelKey,
|
|
30
40
|
Provider,
|
|
31
41
|
CostTier,
|
|
32
42
|
UseModelRegistryReturn,
|
|
43
|
+
UseModelRegistryOptions,
|
|
33
44
|
ModelSelectorProps,
|
|
45
|
+
HarnessSelectorProps,
|
|
46
|
+
HarnessOption,
|
|
34
47
|
} from "./models";
|
|
35
48
|
|
|
36
49
|
// Workspace — behavior hooks and styled components
|
|
@@ -92,6 +105,7 @@ export type {
|
|
|
92
105
|
UseNewSessionFlowReturn,
|
|
93
106
|
UseSessionPageFlowOptions,
|
|
94
107
|
UseSessionPageFlowReturn,
|
|
108
|
+
UsePersistedModelOptions,
|
|
95
109
|
UsePersistedModelReturn,
|
|
96
110
|
UseEditSessionPrepReturn,
|
|
97
111
|
DraftResourceType,
|
|
@@ -315,6 +329,7 @@ export {
|
|
|
315
329
|
export type {
|
|
316
330
|
GitHubUser,
|
|
317
331
|
GitHubConnectOptions,
|
|
332
|
+
UseGitHubConnectionConfig,
|
|
318
333
|
UseGitHubConnectionReturn,
|
|
319
334
|
GitHubRepo,
|
|
320
335
|
GitHubBranch,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useRef, type KeyboardEvent } from "react";
|
|
4
|
+
import { HARNESS_LABELS, type HarnessOption } from "./harness";
|
|
5
|
+
|
|
6
|
+
const OPTIONS: readonly HarnessOption[] = ["native", "cursor"];
|
|
7
|
+
|
|
8
|
+
/** Props for {@link HarnessSelector}. */
|
|
9
|
+
export interface HarnessSelectorProps {
|
|
10
|
+
/** Currently selected harness. */
|
|
11
|
+
readonly value: HarnessOption;
|
|
12
|
+
/** Called when the user picks a different harness. */
|
|
13
|
+
readonly onValueChange: (harness: HarnessOption) => void;
|
|
14
|
+
/** Additional CSS class names for the root container. */
|
|
15
|
+
readonly className?: string;
|
|
16
|
+
/** When true, disables the selector. */
|
|
17
|
+
readonly disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compact segmented control for choosing the session execution engine.
|
|
22
|
+
*
|
|
23
|
+
* Renders two mutually exclusive options — "Stigmer" (native) and
|
|
24
|
+
* "Cursor" (premium) — as adjacent pill segments. The Cursor segment
|
|
25
|
+
* carries a subtle premium tier indicator.
|
|
26
|
+
*
|
|
27
|
+
* Built as a `radiogroup` with full arrow-key navigation and ARIA
|
|
28
|
+
* semantics. All visual properties flow through `--stgm-*` tokens.
|
|
29
|
+
*
|
|
30
|
+
* Platform builders who need different rendering use
|
|
31
|
+
* {@link HarnessOption} and {@link HARNESS_LABELS} directly.
|
|
32
|
+
*
|
|
33
|
+
* @deprecated Use {@link ModelSelector} in unified mode (without the
|
|
34
|
+
* `harness` prop) instead. The unified model picker embeds an engine
|
|
35
|
+
* tag on each model row, eliminating the need for a separate harness
|
|
36
|
+
* control. This component is kept for backward compatibility.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function LauncherToolbar() {
|
|
41
|
+
* const [harness, setHarness] = useState<HarnessOption>("native");
|
|
42
|
+
*
|
|
43
|
+
* return <HarnessSelector value={harness} onValueChange={setHarness} />;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function HarnessSelector({
|
|
48
|
+
value,
|
|
49
|
+
onValueChange,
|
|
50
|
+
className,
|
|
51
|
+
disabled,
|
|
52
|
+
}: HarnessSelectorProps) {
|
|
53
|
+
const groupRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
|
|
55
|
+
const handleKeyDown = useCallback(
|
|
56
|
+
(e: KeyboardEvent<HTMLDivElement>) => {
|
|
57
|
+
if (disabled) return;
|
|
58
|
+
|
|
59
|
+
const idx = OPTIONS.indexOf(value);
|
|
60
|
+
let next: number | undefined;
|
|
61
|
+
|
|
62
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
63
|
+
next = (idx + 1) % OPTIONS.length;
|
|
64
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
65
|
+
next = (idx - 1 + OPTIONS.length) % OPTIONS.length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (next !== undefined) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
onValueChange(OPTIONS[next]);
|
|
71
|
+
const buttons = groupRef.current?.querySelectorAll<HTMLButtonElement>("[role=radio]");
|
|
72
|
+
buttons?.[next]?.focus();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[value, onValueChange, disabled],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
ref={groupRef}
|
|
81
|
+
role="radiogroup"
|
|
82
|
+
aria-label="Execution engine"
|
|
83
|
+
onKeyDown={handleKeyDown}
|
|
84
|
+
className={[
|
|
85
|
+
"inline-flex items-center rounded-md border border-border bg-background p-0.5",
|
|
86
|
+
disabled ? "pointer-events-none opacity-50" : undefined,
|
|
87
|
+
className,
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join(" ")}
|
|
91
|
+
>
|
|
92
|
+
{OPTIONS.map((option) => {
|
|
93
|
+
const isActive = value === option;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
key={option}
|
|
98
|
+
type="button"
|
|
99
|
+
role="radio"
|
|
100
|
+
aria-checked={isActive}
|
|
101
|
+
aria-label={HARNESS_LABELS[option]}
|
|
102
|
+
tabIndex={isActive ? 0 : -1}
|
|
103
|
+
disabled={disabled}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
if (!isActive) onValueChange(option);
|
|
106
|
+
}}
|
|
107
|
+
className={[
|
|
108
|
+
"inline-flex items-center gap-1 rounded-[5px] px-2 py-1 text-xs transition-colors",
|
|
109
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
110
|
+
"disabled:pointer-events-none",
|
|
111
|
+
isActive
|
|
112
|
+
? "bg-accent font-medium text-foreground shadow-sm"
|
|
113
|
+
: "text-muted-foreground hover:text-foreground",
|
|
114
|
+
].join(" ")}
|
|
115
|
+
>
|
|
116
|
+
{HARNESS_LABELS[option]}
|
|
117
|
+
{option === "cursor" && (
|
|
118
|
+
<span
|
|
119
|
+
aria-label="premium"
|
|
120
|
+
className="text-[0.6rem] text-muted-foreground"
|
|
121
|
+
>
|
|
122
|
+
$$$
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
</button>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|