Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ of a single commit, use `SKIP_DOCS_HOOK=1` or put `[skip-docs]` in the message.

- TypeScript strict mode
- Follow the existing formatter config; do not reformat unrelated files
- **Icons**: there is no icon library; icons are hand-authored inline SVGs,
with the commonly reused ones centralized in `app/components/ui/icons.tsx`.
When adding or touching an icon that toggles between two states based on
props/state (e.g. copy → check, menu → close) — not just shown/hidden —
consider animating the transition with `morphicons` instead of an instant
swap: `<MorphIcon icon={condition ? A : B} />` from `morphicons/react`, fed
path data on a shared 24x24 grid (`fitIcon()` first if the source art isn't
already on that grid). See `CLIPBOARD_MORPH_ICON`/`CHECK_MORPH_ICON` in
`app/components/ui/icons.tsx` and their use in
`app/demos/b20/components/CopyPromptButton.tsx`,
`app/internal-explorer/components/CopyButton.tsx`, and
`app/vibenet/components/CopyableValue.tsx`.

## Testing

Expand Down
7 changes: 7 additions & 0 deletions app/components/ui/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ export function ClipboardIcon({ size = 16, className }: { size?: number; classNa
);
}

// Raw path data (24x24 grid, matching ClipboardIcon/CheckIcon below) for
// morphicons' <MorphIcon>, which morphs between two icons on state change
// instead of an instant swap.
export const CLIPBOARD_MORPH_ICON =
'M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z';
export const CHECK_MORPH_ICON = 'M5 13l4 4L19 7';

export function CheckIcon({ size = 16, className }: { size?: number; className?: string }) {
return (
<svg
Expand Down
14 changes: 8 additions & 6 deletions app/demos/b20/components/CopyPromptButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { MorphIcon } from 'morphicons/react';

import { trackB20PromptCopy } from '../../../analytics/events';
import { CheckIcon, ClipboardIcon } from '../../../components/ui/icons';
import { CHECK_MORPH_ICON, CLIPBOARD_MORPH_ICON } from '../../../components/ui/icons';
import { cn } from '../../../components/ui/cn';
import type { B20Prompt } from '../lib/prompts';

Expand Down Expand Up @@ -50,11 +51,12 @@ export function CopyPromptButton({ prompt, module, className }: CopyPromptButton
className,
)}
>
{copied ? (
<CheckIcon size={14} className="text-bds-green-60" />
) : (
<ClipboardIcon size={14} />
)}
<MorphIcon
icon={copied ? CHECK_MORPH_ICON : CLIPBOARD_MORPH_ICON}
size={14}
strokeWidth={2}
className={copied ? 'text-bds-green-60' : undefined}
/>
{copied ? 'Copied' : 'Copy developer instructions'}
</button>
);
Expand Down
30 changes: 9 additions & 21 deletions app/internal-explorer/components/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use client';

import { useState } from 'react';
import { MorphIcon } from 'morphicons/react';

import { CHECK_MORPH_ICON, CLIPBOARD_MORPH_ICON } from '../../components/ui/icons';
import { cn } from '../../components/ui/cn';

// Small clipboard button used on the detail pages; swaps to a green check for
Expand All @@ -25,27 +27,13 @@ export function CopyButton({ text, className }: { text: string; className?: stri
className,
)}
>
{copied ? (
<svg
className="h-4 w-4 text-bds-green-60"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<title>Copied</title>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<title>Copy</title>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
)}
<MorphIcon
icon={copied ? CHECK_MORPH_ICON : CLIPBOARD_MORPH_ICON}
size={16}
strokeWidth={2}
label={copied ? 'Copied' : 'Copy'}
className={copied ? 'text-bds-green-60' : undefined}
/>
</button>
);
}
28 changes: 19 additions & 9 deletions app/vibenet/components/CopyableValue.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
'use client';

import { useCallback, useState } from 'react';
import { MorphIcon } from 'morphicons/react';
import { fitIcon } from 'morphicons';

import { CHECK_MORPH_ICON } from '../../components/ui/icons';
import { cn } from '../../components/ui/cn';

// Original artwork is drawn on a 40x40 grid; regrid it onto morphicons' 24x24
// baseline once, at module scope, so it can morph against CHECK_MORPH_ICON.
const COPY_MORPH_ICON = fitIcon(
'M16.6667 23.3333V26.6667C16.6667 28.5076 18.1591 30 20 30H26.6667C28.5076 30 30 28.5076 30 26.6667V20C30 18.1591 28.5076 16.6667 26.6667 16.6667H23.3333M23.3333 16.6667V13.3333C23.3333 11.4924 21.8409 10 20 10H13.3333C11.4924 10 10 11.4924 10 13.3333V20C10 21.8409 11.4924 23.3333 13.3333 23.3333H20C21.8409 23.3333 23.3333 21.8409 23.3333 20V16.6667Z',
40,
);

type CopyableValueProps = {
value: string;
display?: string;
Expand Down Expand Up @@ -41,15 +51,15 @@ export function CopyableValue({ value, display, className }: CopyableValueProps)
)}
>
<code className="truncate">{shown}</code>
{copied ? (
<svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="shrink-0 text-bds-green-60">
<path d="M20 6 9 17l-5-5" />
</svg>
) : (
<svg width={20} height={20} viewBox="0 0 40 40" fill="none" className="shrink-0 text-bds-gray-50 transition-colors group-hover:text-foreground">
<path d="M16.6667 23.3333V26.6667C16.6667 28.5076 18.1591 30 20 30H26.6667C28.5076 30 30 28.5076 30 26.6667V20C30 18.1591 28.5076 16.6667 26.6667 16.6667H23.3333M23.3333 16.6667V13.3333C23.3333 11.4924 21.8409 10 20 10H13.3333C11.4924 10 10 11.4924 10 13.3333V20C10 21.8409 11.4924 23.3333 13.3333 23.3333H20C21.8409 23.3333 23.3333 21.8409 23.3333 20V16.6667Z" stroke="currentColor" strokeWidth={2.5} />
</svg>
)}
<MorphIcon
icon={copied ? CHECK_MORPH_ICON : COPY_MORPH_ICON}
size={20}
strokeWidth={copied ? 2 : 2.5}
className={cn(
'shrink-0 transition-colors',
copied ? 'text-bds-green-60' : 'text-bds-gray-50 group-hover:text-foreground',
)}
/>
</button>
);
}
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@vercel/analytics": "^2.0.1",
"classnames": "^2.5.1",
"d3": "^7.9.0",
"morphicons": "^1.7.0",
"motion": "^12.0.0",
"next": "^15.5.20",
"react": "^18.3.1",
Expand Down