diff --git a/app/tokens/TOKENS.md b/app/tokens/TOKENS.md index f25d96c..fcc82c4 100644 --- a/app/tokens/TOKENS.md +++ b/app/tokens/TOKENS.md @@ -61,7 +61,7 @@ | `color.dark.primary-foreground` | `#f9f9f7` | Dark primary foreground | | `color.dark.secondary` | `#575753` | Dark secondary | | `color.dark.secondary-foreground` | `#f9f9f7` | Dark secondary text | -| `color.dark.muted` | `#575753` | Dark muted | +| `color.dark.muted` | `#2f2f2c` | Dark muted — grey.8 (distinct from grey.7 border so outlines on bg-muted surfaces remain visible) | | `color.dark.muted-foreground` | `#c4c4bd` | Dark muted text | | `color.dark.accent` | `#575753` | Dark accent/hover | | `color.dark.accent-foreground` | `#f9f9f7` | Dark accent text | diff --git a/app/tokens/tokens-dark.css b/app/tokens/tokens-dark.css index c16bf01..51ee151 100644 --- a/app/tokens/tokens-dark.css +++ b/app/tokens/tokens-dark.css @@ -23,8 +23,8 @@ --secondary: 87 87 83; /* Dark secondary text */ --secondary-foreground: 249 249 247; - /* Dark muted */ - --muted: 87 87 83; + /* Dark muted — grey.8 (distinct from grey.7 border so outlines on bg-muted surfaces remain visible) */ + --muted: 47 47 44; /* Dark muted text */ --muted-foreground: 196 196 189; /* Dark accent/hover */ diff --git a/app/tokens/tokens.ts b/app/tokens/tokens.ts index f7fee1a..10b9013 100644 --- a/app/tokens/tokens.ts +++ b/app/tokens/tokens.ts @@ -56,7 +56,7 @@ export const ColorTokens = { 'dark.primary-foreground': '#f9f9f7', 'dark.secondary': '#575753', 'dark.secondary-foreground': '#f9f9f7', - 'dark.muted': '#575753', + 'dark.muted': '#2f2f2c', 'dark.muted-foreground': '#c4c4bd', 'dark.accent': '#575753', 'dark.accent-foreground': '#f9f9f7', diff --git a/components/ui/code.tsx b/components/ui/code.tsx new file mode 100644 index 0000000..d967010 --- /dev/null +++ b/components/ui/code.tsx @@ -0,0 +1,63 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const codeVariants = cva( + 'bg-muted border border-border font-mono text-foreground', + { + variants: { + variant: { + inline: 'rounded text-xs px-1.5 py-0.5', + block: 'block rounded-md text-sm px-4 py-3 leading-relaxed break-all whitespace-pre-wrap', + }, + }, + defaultVariants: { + variant: 'inline', + }, + }, +) + +interface CodeProps + extends React.ComponentProps<'code'>, + VariantProps { + /** + * Optional language hint for future syntax-highlighting support. + * Emitted as `data-language` and as a `language-{lang}` class so + * highlighters like Prism/Shiki can pick it up later. + */ + language?: string +} + +function Code({ + className, + variant, + language, + ...props +}: CodeProps) { + const element = ( + + ) + + // For block variant, wrap in
 so copy-paste preserves whitespace
+  // and screen readers announce it as a code block.
+  if (variant === 'block') {
+    return (
+      
+        {element}
+      
+ ) + } + + return element +} + +export { Code, codeVariants } diff --git a/lib/catalog.ts b/lib/catalog.ts index e1580bb..d7ec43e 100644 --- a/lib/catalog.ts +++ b/lib/catalog.ts @@ -177,6 +177,15 @@ export const COMPONENT_CATALOG: ComponentSpec[] = [ props: 'variant?: "default" | "outline"; size?: "default" | "sm" | "lg"; pressed?: boolean', example: '', }, + { + name: 'Code', + file: 'components/ui/code.tsx', + category: 'primitives', + exports: ['Code', 'codeVariants'], + description: 'Inline or block code snippet. Always use this instead of hand-rolling /
 styling. Uses bg-muted + border-border so the outline stays visible in both light and dark modes. Block variant auto-wraps in 
 for whitespace preservation and break-all for long commands.',
+    props: 'variant?: "inline" | "block"; language?: string (optional, for future syntax highlighting)',
+    example: '

Install with pnpm install.

\n\n{`pnpm install\npnpm dev`}', + }, // ── Layout ────────────────────────────────────────────────────────────── { name: 'Card', diff --git a/skill/AGENTS.md b/skill/AGENTS.md index d0dce9d..bf4cbda 100644 --- a/skill/AGENTS.md +++ b/skill/AGENTS.md @@ -27,7 +27,7 @@ This project uses the **Greyhaven Design System**. ## Component Summary -37 components across 8 categories: primitives (10), layout (4), overlay (5), navigation (3), data (4), feedback (4), form (1), composition (6). +38 components across 8 categories: primitives (11), layout (4), overlay (5), navigation (3), data (4), feedback (4), form (1), composition (6). For full component specs, props, and examples, refer to the SKILL.md file or use the MCP `get_component(name)` tool. diff --git a/skill/SKILL.md b/skill/SKILL.md index 31ae42c..de18bc4 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -3,7 +3,7 @@ > **Auto-generated** by `scripts/generate-skill.ts` -- DO NOT EDIT by hand. > Re-generate: `pnpm skill:build` > -> **Components**: 37 | **Style**: shadcn/ui "new-york" +> **Components**: 38 | **Style**: shadcn/ui "new-york" > **Stack**: React 19, Radix UI, Tailwind CSS v4, CVA, tailwind-merge, clsx, Lucide icons > **Framework-agnostic**: No Next.js imports. Works with Vite, Remix, Astro, CRA, or any React setup. @@ -125,7 +125,7 @@ Source of truth: `tokens/*.json` (W3C DTCG format). | `color.dark.primary-foreground` | `{color.primitive.off-white}` | Dark primary foreground | | `color.dark.secondary` | `{color.primitive.grey.7}` | Dark secondary | | `color.dark.secondary-foreground` | `{color.primitive.off-white}` | Dark secondary text | -| `color.dark.muted` | `{color.primitive.grey.7}` | Dark muted | +| `color.dark.muted` | `{color.primitive.grey.8}` | Dark muted — grey.8 (distinct from grey.7 border so outlines on bg-muted surfaces remain visible) | | `color.dark.muted-foreground` | `{color.primitive.grey.3}` | Dark muted text | | `color.dark.accent` | `{color.primitive.grey.7}` | Dark accent/hover | | `color.dark.accent-foreground` | `{color.primitive.off-white}` | Dark accent text | @@ -235,7 +235,7 @@ Source of truth: `tokens/*.json` (W3C DTCG format). --- -## Component Catalog (37 components) +## Component Catalog (38 components) All components live in `components/ui/`. Import with `@/components/ui/`. @@ -341,6 +341,19 @@ All components live in `components/ui/`. Import with `@/components/ui/`. ``` +#### Code +- **File**: `components/ui/code.tsx` +- **Exports**: `Code`, `codeVariants` +- **Description**: Inline or block code snippet. Always use this instead of hand-rolling /
 styling. Uses bg-muted + border-border so the outline stays visible in both light and dark modes. Block variant auto-wraps in 
 for whitespace preservation and break-all for long commands.
+- **Props**: `variant?: "inline" | "block"; language?: string (optional, for future syntax highlighting)`
+- **Example**:
+```tsx
+

Install with pnpm install.

+ +{`pnpm install +pnpm dev`} +``` + ### Layout #### Card diff --git a/stories/Primitives/Code.stories.tsx b/stories/Primitives/Code.stories.tsx new file mode 100644 index 0000000..9aea41e --- /dev/null +++ b/stories/Primitives/Code.stories.tsx @@ -0,0 +1,90 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Code } from '@/components/ui/code' + +const meta = { + title: 'Primitives/Code', + component: Code, + tags: ['autodocs'], + parameters: { layout: 'centered' }, + argTypes: { + variant: { + control: 'select', + options: ['inline', 'block'], + }, + language: { control: 'text' }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Inline: Story = { + args: { + variant: 'inline', + children: 'pnpm install', + }, +} + +export const InlineInSentence: Story = { + render: () => ( +

+ To get started, run pnpm install and then{' '} + pnpm dev to start the development server on{' '} + localhost:3000. +

+ ), +} + +export const Block: Story = { + args: { + variant: 'block', + language: 'bash', + children: `pnpm install +pnpm dev +pnpm build`, + }, +} + +export const BlockLongCommand: Story = { + args: { + variant: 'block', + language: 'bash', + children: + 'curl -fsSL https://example.com/install.sh | bash -s -- --prefix=/usr/local --no-color --very-long-flag-that-should-wrap', + }, +} + +export const BlockTypescript: Story = { + args: { + variant: 'block', + language: 'ts', + children: `import { Code } from '@/components/ui/code' + +export function Example() { + return Hello, world! +}`, + }, +} + +export const AllVariants: Story = { + render: () => ( +
+
+

Inline

+

+ Use cn() from @/lib/utils to merge Tailwind classes. +

+
+ +
+

Block

+ + {`# install and run +pnpm install +pnpm dev`} + +
+
+ ), +} diff --git a/tokens/color.json b/tokens/color.json index f7f45b7..867e8b3 100644 --- a/tokens/color.json +++ b/tokens/color.json @@ -287,8 +287,8 @@ }, "muted": { "$type": "color", - "$value": "{color.primitive.grey.7}", - "$description": "Dark muted" + "$value": "{color.primitive.grey.8}", + "$description": "Dark muted — grey.8 (distinct from grey.7 border so outlines on bg-muted surfaces remain visible)" }, "muted-foreground": { "$type": "color",