Redesign Resources screen: template panel, subscriptions, hover states, and callback architecture

- Rename ResourceTemplateInput to ResourceTemplatePanel with panel layout
  (title header, URI with live variable preview, labeled inputs, Read Resource button)
- ResourceTemplatePanel manages its own variable state via useState;
  single onReadResource(uri) callback returns fully resolved URI
- Extract ResourceSubscribedItem group component with name, timestamp, and
  subtle Unsubscribe button
- Add hover state to listItem variant via theme classNames + App.css pseudo-selector
- Add Refresh button to ResourcePreviewPanel
- Refactor ResourcesScreen callbacks: onSelectUri, onSelectTemplate,
  onReadResource, onSubscribeResource, onUnsubscribeResource replace
  per-item onClick/onUnsubscribe props; screen wires callbacks to children
- Define data-only types (ResourceItem, TemplateListItem, SubscriptionItem,
  SelectedResource, SelectedTemplate) separating data from callbacks
- Extract named .withProps() subcomponents (FooterRow, AnnotationGroup,
  ActionGroup, Spacer) for AGENTS.md compliance
- Update all stories (ResourceTemplatePanel, ResourcesScreen, ConnectedView)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-03-29 20:04:25 -04:00
commit a0e763a315
2 changed files with 520 additions and 200 deletions

View file

@ -6,6 +6,7 @@ const meta: Meta<typeof ResourcePreviewPanel> = {
title: "Groups/ResourcePreviewPanel",
component: ResourcePreviewPanel,
args: {
onRefresh: fn(),
onSubscribe: fn(),
onUnsubscribe: fn(),
},

View file

@ -1,4 +1,4 @@
import { Flex, Group, Stack, Text, Title } from "@mantine/core";
import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
import { AnnotationBadge } from "../../elements/AnnotationBadge/AnnotationBadge";
import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
import { CopyButton } from "../../elements/CopyButton/CopyButton";
@ -11,6 +11,7 @@ export interface ResourcePreviewPanelProps {
content: string;
lastUpdated?: string;
isSubscribed: boolean;
onRefresh: () => void;
onSubscribe: () => void;
onUnsubscribe: () => void;
}
@ -62,6 +63,20 @@ const MimeText = Text.withProps({
c: "dimmed",
});
const FooterRow = Group.withProps({
justify: "space-between",
});
const AnnotationGroup = Group.withProps({
gap: "xs",
});
const ActionGroup = Group.withProps({
gap: "xs",
});
const Spacer = Flex.withProps({});
export function ResourcePreviewPanel({
uri,
mimeType,
@ -69,6 +84,7 @@ export function ResourcePreviewPanel({
content,
lastUpdated,
isSubscribed,
onRefresh,
onSubscribe,
onUnsubscribe,
}: ResourcePreviewPanelProps) {
@ -91,12 +107,12 @@ export function ResourcePreviewPanel({
{lastUpdated ? (
<TimestampText>{formatLastUpdated(lastUpdated)}</TimestampText>
) : (
<Flex />
<Spacer />
)}
<MimeText>{mimeType}</MimeText>
</MetaRow>
<Group justify="space-between">
<Group gap="xs">
<FooterRow>
<AnnotationGroup>
{annotations?.audience && (
<AnnotationBadge label={annotations.audience} variant="audience" />
)}
@ -106,12 +122,17 @@ export function ResourcePreviewPanel({
variant="priority"
/>
)}
</Group>
<SubscribeButton
subscribed={isSubscribed}
onToggle={isSubscribed ? onUnsubscribe : onSubscribe}
/>
</Group>
</AnnotationGroup>
<ActionGroup>
<Button variant="subtle" size="sm" onClick={onRefresh}>
Refresh
</Button>
<SubscribeButton
subscribed={isSubscribed}
onToggle={isSubscribed ? onUnsubscribe : onSubscribe}
/>
</ActionGroup>
</FooterRow>
</Stack>
);
}