feat(ui): claude_code_plugins/MakeSkillPublicForm to shadcn

- antd Modal/Button/Checkbox + @tremor Text/Title/Badge \u2192 shadcn
  Dialog/Button/Checkbox/Badge.
- antd Steps/Step preserved for the 2-step wizard progress indicator.
- Published/domain badges use categorical emerald/blue palette;
  confirmation info callouts use blue/amber tints (file added to
  .eslintrc.json overrides).

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 16:42:24 +00:00
parent 3eaaf5ff1a
commit 5ac527d49d
No known key found for this signature in database
2 changed files with 125 additions and 80 deletions

View file

@ -64,6 +64,7 @@
"src/components/mcp_tools/mcp_server_columns.tsx",
"src/components/playground/chat_ui/A2AMetrics.tsx",
"src/components/common_components/PassThroughGuardrailsSection.tsx",
"src/components/claude_code_plugins/MakeSkillPublicForm.tsx",
"src/components/permissions/VectorStorePermissions.tsx",
"src/components/permissions/AgentPermissions.tsx",
"src/components/permissions/MCPServerPermissions.tsx",

View file

@ -1,7 +1,18 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Steps, Button, Checkbox } from "antd";
import { Text, Title, Badge } from "@tremor/react";
import { enableClaudeCodePlugin, disableClaudeCodePlugin } from "../networking";
import { Form, Steps } from "antd";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
enableClaudeCodePlugin,
disableClaudeCodePlugin,
} from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
import { Plugin } from "./types";
@ -60,10 +71,11 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
}
};
// Pre-check already-published skills when modal opens
useEffect(() => {
if (visible && skillsList.length > 0) {
setSelectedSkills(new Set(skillsList.filter((s) => s.enabled).map((s) => s.name)));
setSelectedSkills(
new Set(skillsList.filter((s) => s.enabled).map((s) => s.name)),
);
}
}, [visible, skillsList]);
@ -86,15 +98,19 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
return disableClaudeCodePlugin(accessToken, skill.name);
}
return Promise.resolve();
})
}),
);
NotificationsManager.success(`Skill Hub updated — ${selectedSkills.size} skill(s) published`);
NotificationsManager.success(
`Skill Hub updated — ${selectedSkills.size} skill(s) published`,
);
handleClose();
onSuccess();
} catch (error) {
console.error("Error publishing skills:", error);
NotificationsManager.fromBackend("Failed to update skills. Please try again.");
NotificationsManager.fromBackend(
"Failed to update skills. Please try again.",
);
} finally {
setLoading(false);
}
@ -107,65 +123,76 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
const renderStep1 = () => (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Title>Select Skills to Publish</Title>
<Checkbox
checked={allSelected}
indeterminate={isIndeterminate}
onChange={(e) => handleSelectAll(e.target.checked)}
disabled={skillsList.length === 0}
>
Select All ({skillsList.length})
</Checkbox>
<h3 className="text-lg font-semibold">Select Skills to Publish</h3>
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={
isIndeterminate ? "indeterminate" : allSelected ? true : false
}
onCheckedChange={(c) => handleSelectAll(c === true)}
disabled={skillsList.length === 0}
/>
<span className="text-sm">Select All ({skillsList.length})</span>
</label>
</div>
<Text className="text-sm text-gray-600">
<p className="text-sm text-muted-foreground">
Selected skills will be visible to all users in the Skill Hub.
Deselected skills will be unpublished.
</Text>
</p>
<div className="max-h-96 overflow-y-auto border rounded-lg p-4">
<div className="max-h-96 overflow-y-auto border border-border rounded-lg p-4">
<div className="space-y-3">
{skillsList.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<Text>No skills registered yet.</Text>
<div className="text-center py-8 text-muted-foreground">
<p>No skills registered yet.</p>
</div>
) : (
skillsList.map((skill) => (
<div
<label
key={skill.name}
className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50"
className="flex items-center space-x-3 p-3 border border-border rounded-lg hover:bg-muted cursor-pointer"
>
<Checkbox
checked={selectedSkills.has(skill.name)}
onChange={(e) => handleSkillSelection(skill.name, e.target.checked)}
onCheckedChange={(c) =>
handleSkillSelection(skill.name, c === true)
}
/>
<div className="flex-1">
<div className="flex items-center gap-2">
<Text className="font-medium font-mono text-sm">{skill.name}</Text>
<span className="font-medium font-mono text-sm">
{skill.name}
</span>
{skill.enabled && (
<Badge color="green" size="xs">Public</Badge>
<Badge className="text-xs bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300">
Public
</Badge>
)}
</div>
{skill.description && (
<Text className="text-xs text-gray-500 truncate max-w-sm">
<span className="text-xs text-muted-foreground truncate max-w-sm block">
{skill.description}
</Text>
</span>
)}
</div>
{skill.domain && (
<Badge color="blue" size="xs">{skill.domain}</Badge>
<Badge className="text-xs bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
{skill.domain}
</Badge>
)}
</div>
</label>
))
)}
</div>
</div>
{selectedSkills.size > 0 && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<Text className="text-sm text-blue-800">
<strong>{selectedSkills.size}</strong> skill{selectedSkills.size !== 1 ? "s" : ""} will be published
</Text>
<div className="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 rounded-lg p-3">
<p className="text-sm text-blue-800 dark:text-blue-200">
<strong>{selectedSkills.size}</strong> skill
{selectedSkills.size !== 1 ? "s" : ""} will be published
</p>
</div>
)}
</div>
@ -173,25 +200,32 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
const renderStep2 = () => (
<div className="space-y-4">
<Title>Confirm Publish to Skill Hub</Title>
<h3 className="text-lg font-semibold">Confirm Publish to Skill Hub</h3>
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<Text className="text-sm text-yellow-800">
<strong>Note:</strong> Published skills will be visible to all users in the Skill Hub tab.
Skills not in the list below will be unpublished.
</Text>
<div className="bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900 rounded-lg p-4">
<p className="text-sm text-amber-800 dark:text-amber-200">
<strong>Note:</strong> Published skills will be visible to all users
in the Skill Hub tab. Skills not in the list below will be unpublished.
</p>
</div>
<div className="space-y-3">
<Text className="font-medium">Skills to be published:</Text>
<div className="max-h-48 overflow-y-auto border rounded-lg p-3">
<p className="font-medium">Skills to be published:</p>
<div className="max-h-48 overflow-y-auto border border-border rounded-lg p-3">
<div className="space-y-2">
{Array.from(selectedSkills).map((name) => {
const skill = skillsList.find((s) => s.name === name);
return (
<div key={name} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<Text className="font-mono text-sm">{name}</Text>
{skill?.domain && <Badge color="blue" size="xs">{skill.domain}</Badge>}
<div
key={name}
className="flex items-center justify-between p-2 bg-muted rounded"
>
<span className="font-mono text-sm">{name}</span>
{skill?.domain && (
<Badge className="text-xs bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
{skill.domain}
</Badge>
)}
</div>
);
})}
@ -199,50 +233,60 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<Text className="text-sm text-blue-800">
Total: <strong>{selectedSkills.size}</strong> skill{selectedSkills.size !== 1 ? "s" : ""} will be published
</Text>
<div className="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 rounded-lg p-3">
<p className="text-sm text-blue-800 dark:text-blue-200">
Total: <strong>{selectedSkills.size}</strong> skill
{selectedSkills.size !== 1 ? "s" : ""} will be published
</p>
</div>
</div>
);
return (
<Modal
title="Publish to Skill Hub"
<Dialog
open={visible}
onCancel={handleClose}
footer={null}
width={700}
maskClosable={false}
onOpenChange={(o) => (!o ? handleClose() : undefined)}
>
<Form form={form} layout="vertical">
<Steps current={currentStep} className="mb-6">
<Step title="Select Skills" />
<Step title="Confirm" />
</Steps>
<DialogContent className="max-w-[700px]">
<DialogHeader>
<DialogTitle>Publish to Skill Hub</DialogTitle>
</DialogHeader>
<Form form={form} layout="vertical">
<Steps current={currentStep} className="mb-6">
<Step title="Select Skills" />
<Step title="Confirm" />
</Steps>
{currentStep === 0 ? renderStep1() : renderStep2()}
{currentStep === 0 ? renderStep1() : renderStep2()}
<div className="flex justify-between mt-6">
<Button onClick={currentStep === 0 ? handleClose : () => setCurrentStep(0)}>
{currentStep === 0 ? "Cancel" : "Previous"}
</Button>
<div className="flex space-x-2">
{currentStep === 0 && (
<Button onClick={handleNext} disabled={selectedSkills.size === 0}>
Next
</Button>
)}
{currentStep === 1 && (
<Button onClick={handleSubmit} loading={loading}>
Publish to Hub
</Button>
)}
<div className="flex justify-between mt-6">
<Button
variant="outline"
onClick={
currentStep === 0 ? handleClose : () => setCurrentStep(0)
}
>
{currentStep === 0 ? "Cancel" : "Previous"}
</Button>
<div className="flex space-x-2">
{currentStep === 0 && (
<Button
onClick={handleNext}
disabled={selectedSkills.size === 0}
>
Next
</Button>
)}
{currentStep === 1 && (
<Button onClick={handleSubmit} disabled={loading}>
{loading ? "Publishing..." : "Publish to Hub"}
</Button>
)}
</div>
</div>
</div>
</Form>
</Modal>
</Form>
</DialogContent>
</Dialog>
);
};