From 011b339645fcd2bfa47c6cdd4a58aa6e5cbeecdc Mon Sep 17 00:00:00 2001 From: DexterStorey Date: Thu, 30 Apr 2026 18:03:14 -0400 Subject: [PATCH 1/2] docs: reconcile local landing polish --- apps/docs/app/layout.tsx | 28 +- apps/docs/app/render/[kind]/[slug]/page.tsx | 7 +- apps/docs/app/styles/base.css | 80 +- apps/docs/app/styles/home.css | 1615 ++++++++++------- apps/docs/app/styles/primitives.css | 27 + apps/docs/app/styles/responsive.css | 341 +++- apps/docs/src/catalog-render-page.tsx | 22 +- apps/docs/src/component-playground.tsx | 17 +- apps/docs/src/primitive-playground.tsx | 17 +- apps/docs/src/rendering.ts | 60 +- .../src/components/feature-card/component.tsx | 50 + .../src/components/feature-card/examples.tsx | 34 + .../src/components/feature-card/index.tsx | 40 + .../src/components/feature-card/meta.ts | 27 + .../src/components/feature-card/schema.ts | 29 + packages/concrete/src/components/index.tsx | 1 + .../src/foundations/motion/styles.css | 1 + .../src/foundations/sizing/styles.css | 11 + .../src/foundations/textures/examples.tsx | 13 +- .../src/foundations/textures/schema.ts | 4 +- .../src/foundations/textures/styles.css | 21 + packages/concrete/src/primitives/index.tsx | 2 + .../src/primitives/scale-frame/component.tsx | 56 + .../src/primitives/scale-frame/examples.tsx | 30 + .../src/primitives/scale-frame/index.tsx | 30 + .../src/primitives/scale-frame/meta.ts | 26 + .../src/primitives/scale-frame/schema.ts | 14 + .../src/primitives/scale-frame/styles.css | 43 + .../src/primitives/tilt-frame/component.tsx | 126 ++ .../src/primitives/tilt-frame/examples.tsx | 31 + .../src/primitives/tilt-frame/index.tsx | 30 + .../src/primitives/tilt-frame/meta.ts | 26 + .../src/primitives/tilt-frame/schema.ts | 14 + .../src/primitives/tilt-frame/styles.css | 67 + packages/concrete/src/registry/items.tsx | 6 + packages/concrete/src/registry/types.ts | 3 + packages/concrete/src/styles/class-names.ts | 5 +- packages/concrete/src/styles/manifest.ts | 2 + .../src/tests/import-boundaries.test.ts | 3 +- packages/concrete/src/tests/registry.test.ts | 3 + 40 files changed, 2306 insertions(+), 656 deletions(-) create mode 100644 packages/concrete/src/components/feature-card/component.tsx create mode 100644 packages/concrete/src/components/feature-card/examples.tsx create mode 100644 packages/concrete/src/components/feature-card/index.tsx create mode 100644 packages/concrete/src/components/feature-card/meta.ts create mode 100644 packages/concrete/src/components/feature-card/schema.ts create mode 100644 packages/concrete/src/primitives/scale-frame/component.tsx create mode 100644 packages/concrete/src/primitives/scale-frame/examples.tsx create mode 100644 packages/concrete/src/primitives/scale-frame/index.tsx create mode 100644 packages/concrete/src/primitives/scale-frame/meta.ts create mode 100644 packages/concrete/src/primitives/scale-frame/schema.ts create mode 100644 packages/concrete/src/primitives/scale-frame/styles.css create mode 100644 packages/concrete/src/primitives/tilt-frame/component.tsx create mode 100644 packages/concrete/src/primitives/tilt-frame/examples.tsx create mode 100644 packages/concrete/src/primitives/tilt-frame/index.tsx create mode 100644 packages/concrete/src/primitives/tilt-frame/meta.ts create mode 100644 packages/concrete/src/primitives/tilt-frame/schema.ts create mode 100644 packages/concrete/src/primitives/tilt-frame/styles.css diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 3f53daf..09af8f6 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -28,27 +28,27 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac Concrete @@ -59,17 +59,21 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac ) } -function NpmIcon() { +function NpmLogo() { return ( -
{name} diff --git a/apps/docs/src/component-playground.tsx b/apps/docs/src/component-playground.tsx index 915a132..5db56e3 100644 --- a/apps/docs/src/component-playground.tsx +++ b/apps/docs/src/component-playground.tsx @@ -1,12 +1,9 @@ 'use client' -import { - type ComponentRegistryEntry, - getComponentDefinition, - renderDefinitionInput -} from '@rubriclab/concrete' +import { type ComponentRegistryEntry, getComponentDefinition } from '@rubriclab/concrete' import { useSearchParams } from 'next/navigation' import { CatalogPlayground } from './catalog-playground' +import { renderDefinitionFromSearchParams } from './rendering' type ComponentPlaygroundProps = { entry: ComponentRegistryEntry @@ -19,7 +16,15 @@ export function ComponentPlayground({ entry }: ComponentPlaygroundProps) { return ( ) diff --git a/apps/docs/src/rendering.ts b/apps/docs/src/rendering.ts index 3cacc54..ab8bcb7 100644 --- a/apps/docs/src/rendering.ts +++ b/apps/docs/src/rendering.ts @@ -1,12 +1,26 @@ -import { type ConcreteViewport, type RenderQuery, renderQuerySchema } from '@rubriclab/concrete' +import { + type ComponentDefinition, + type ConcreteViewport, + type FoundationDefinition, + type PrimitiveDefinition, + type RenderQuery, + renderDefinitionInput, + renderQuerySchema +} from '@rubriclab/concrete' +import type { ReactNode } from 'react' export type SearchParamsInput = Record +export type QueryValueSource = { + get: (name: string) => string | null +} export type ViewportSize = { height: number width: number } +type RenderableDefinition = ComponentDefinition | FoundationDefinition | PrimitiveDefinition + export function parseRenderQuery(searchParams: SearchParamsInput): RenderQuery { const flattened = flattenSearchParams(searchParams) return renderQuerySchema.parse(flattened) @@ -33,6 +47,44 @@ export function getViewportSize(viewport: ConcreteViewport): ViewportSize { } } +export function renderDefinitionFromSearchParams( + definition: RenderableDefinition, + searchParams: QueryValueSource | SearchParamsInput, + state: string +): ReactNode { + if (!hasDefinitionInputQuery(definition, searchParams)) { + return definition.renderExample(state) + } + + return ( + renderDefinitionInput(definition, createSearchParamsSource(searchParams)) ?? + definition.renderExample(state) + ) +} + +export function hasDefinitionInputQuery( + definition: RenderableDefinition, + searchParams: QueryValueSource | SearchParamsInput +): boolean { + const source = createSearchParamsSource(searchParams) + + return definition.controls.some(control => source.get(control.name) !== null) +} + +export function createSearchParamsSource( + searchParams: QueryValueSource | SearchParamsInput +): QueryValueSource { + if (isQueryValueSource(searchParams)) { + return searchParams + } + + const flattened = flattenSearchParams(searchParams) + + return { + get: (name: string) => flattened[name] ?? null + } +} + function flattenSearchParams(searchParams: SearchParamsInput): Record { const flattened: Record = {} @@ -51,3 +103,9 @@ function flattenSearchParams(searchParams: SearchParamsInput): Record, 'title'> & { + accent?: FeatureCardAccent + description?: ReactNode + icon?: IconName + interactive?: boolean + title: ReactNode +} + +// TECH-DEBT: Low-quality landing-page integration. This should become a composition of +// Surface, Header, Icon, and Text primitives instead of a bespoke component wrapper. +export function FeatureCard({ + accent = 'ink', + className, + description, + icon = 'sparkles', + interactive = true, + title, + ...props +}: FeatureCardProps) { + featureCardSchema.pick({ accent: true, interactive: true }).parse({ accent, interactive }) + + return ( + + + + + + {title} + {description ? {description} : null} + + + ) +} diff --git a/packages/concrete/src/components/feature-card/examples.tsx b/packages/concrete/src/components/feature-card/examples.tsx new file mode 100644 index 0000000..455c2fe --- /dev/null +++ b/packages/concrete/src/components/feature-card/examples.tsx @@ -0,0 +1,34 @@ +import { defineExamples } from '../../factories/createExamples' +import { FeatureCard } from './component' + +export const featureCardExamples = defineExamples({ + default: { + description: 'Compact system feature callout with subtle depth.', + render: () => ( + + ) + }, + set: { + description: 'A connected set for hero or footer summaries.', + render: () => ( + <> + + + + ) + } +}) diff --git a/packages/concrete/src/components/feature-card/index.tsx b/packages/concrete/src/components/feature-card/index.tsx new file mode 100644 index 0000000..3c66b36 --- /dev/null +++ b/packages/concrete/src/components/feature-card/index.tsx @@ -0,0 +1,40 @@ +import { exampleStates, renderExample } from '../../factories/createExamples' +import { createComponent } from '../../factories/createItems' +import { FeatureCard } from './component' +import { featureCardExamples } from './examples' +import { featureCardMeta } from './meta' +import { type FeatureCardValue, featureCardSchema } from './schema' + +export type { FeatureCardAccent, FeatureCardProps } from './component' +export { FeatureCard } from './component' +export type { FeatureCardInput, FeatureCardValue } from './schema' +export { featureCardPropsSchema, featureCardSchema } from './schema' + +export const featureCardComponentDefinition = createComponent({ + ...featureCardMeta, + component: FeatureCard, + kind: 'component', + renderExample: (state?: string) => renderExample(featureCardExamples, state), + renderInput: input => renderFeatureCardInput(featureCardSchema.parse(input)), + schema: featureCardSchema, + slug: 'feature-card', + states: exampleStates(featureCardExamples, ['default', 'set']) +}) + +function renderFeatureCardInput({ + accent, + description, + icon, + interactive, + title +}: FeatureCardValue) { + return ( + + ) +} diff --git a/packages/concrete/src/components/feature-card/meta.ts b/packages/concrete/src/components/feature-card/meta.ts new file mode 100644 index 0000000..98930e9 --- /dev/null +++ b/packages/concrete/src/components/feature-card/meta.ts @@ -0,0 +1,27 @@ +import { prop } from '../../registry/props' +import type { ConcretePressure, PrimitiveCategory } from '../../schemas' + +type FeatureCardMeta = { + category: PrimitiveCategory + description: string + guidance: string + name: string + pressure: readonly ConcretePressure[] + props: readonly ReturnType[] +} + +export const featureCardMeta = { + category: 'surface', + description: 'Compact feature callout for system concepts and product affordances.', + guidance: + 'FeatureCard is for short, connected ideas. Prefer a small set with parallel copy over isolated marketing cards.', + name: 'FeatureCard', + pressure: ['editorial', 'product', 'generative'], + props: [ + prop('title', 'ReactNode', 'Feature title.'), + prop('description', 'ReactNode', 'One-line supporting copy.'), + prop('icon', 'IconName', 'Concrete icon name.', 'sparkles'), + prop('accent', "'ink' | 'sky' | 'terminal' | 'ultra'", 'Small icon accent.', 'ink'), + prop('interactive', 'boolean', 'Enables subtle TiltFrame depth.', 'true') + ] +} as const satisfies FeatureCardMeta diff --git a/packages/concrete/src/components/feature-card/schema.ts b/packages/concrete/src/components/feature-card/schema.ts new file mode 100644 index 0000000..fb54493 --- /dev/null +++ b/packages/concrete/src/components/feature-card/schema.ts @@ -0,0 +1,29 @@ +import type { z } from 'zod/v4' +import { z as zod } from 'zod/v4' + +const featureCardIconValues = [ + 'bar-chart-3', + 'code', + 'command', + 'image', + 'panel-left', + 'panel-right', + 'square', + 'sliders-horizontal', + 'sparkles', + 'zap' +] as const + +export const featureCardSchema = zod + .object({ + accent: zod.enum(['ink', 'sky', 'terminal', 'ultra']).default('ink'), + description: zod.string().default('A compact feature for a system concept.'), + icon: zod.enum(featureCardIconValues).default('sparkles'), + interactive: zod.boolean().default(true), + title: zod.string().default('Feature') + }) + .strict() + +export { featureCardSchema as featureCardPropsSchema } +export type FeatureCardInput = z.input +export type FeatureCardValue = z.output diff --git a/packages/concrete/src/components/index.tsx b/packages/concrete/src/components/index.tsx index 717a8ab..9cf4f29 100644 --- a/packages/concrete/src/components/index.tsx +++ b/packages/concrete/src/components/index.tsx @@ -9,6 +9,7 @@ export * from './date-picker' export * from './date-range-picker' export * from './diagram-canvas' export * from './donut-chart' +export * from './feature-card' export * from './file-upload' export * from './flow-diagram' export * from './form-dialog' diff --git a/packages/concrete/src/foundations/motion/styles.css b/packages/concrete/src/foundations/motion/styles.css index f34e997..dd66348 100644 --- a/packages/concrete/src/foundations/motion/styles.css +++ b/packages/concrete/src/foundations/motion/styles.css @@ -63,6 +63,7 @@ --concrete-opacity-chart-area: 0.08; --concrete-opacity-chart-comparison: 0.18; --concrete-opacity-chart-point: 0.78; + --concrete-opacity-tilt-frame-glare: 0.72; --concrete-opacity-concept-frame-muted: 0.62; --concrete-opacity-concept-connector-muted: 0.52; --concrete-opacity-pagination-disabled: 0.48; diff --git a/packages/concrete/src/foundations/sizing/styles.css b/packages/concrete/src/foundations/sizing/styles.css index 27052be..afacebf 100644 --- a/packages/concrete/src/foundations/sizing/styles.css +++ b/packages/concrete/src/foundations/sizing/styles.css @@ -230,6 +230,17 @@ --concrete-measure-message-bubble: 68ch; --concrete-measure-empty-state: 360px; --concrete-measure-empty-body: 300px; + --concrete-measure-preview-stage-control: 420px; + --concrete-measure-preview-stage-media: 480px; + --concrete-measure-preview-stage-search: 520px; + --concrete-measure-preview-stage-feedback: 620px; + --concrete-measure-preview-stage-message: 680px; + --concrete-measure-preview-stage-data: 720px; + --concrete-measure-preview-stage-form: 760px; + --concrete-measure-preview-stage-composer: 860px; + --concrete-measure-preview-stage: var(--concrete-size-full); + --concrete-size-texture-preview: 96px; + --concrete-size-tilt-frame-perspective: 820px; --concrete-size-brand-mark: var(--concrete-size-button-medium); --concrete-size-brand-mark-glyph: 62%; --concrete-size-wordmark-inline: 108px; diff --git a/packages/concrete/src/foundations/textures/examples.tsx b/packages/concrete/src/foundations/textures/examples.tsx index d3c43d8..3f18341 100644 --- a/packages/concrete/src/foundations/textures/examples.tsx +++ b/packages/concrete/src/foundations/textures/examples.tsx @@ -4,9 +4,9 @@ import { textureTokens } from './schema' export const texturesExamples = defineExamples({ default: { - description: 'Quiet lattice, dot, and line textures for structure without decoration.', + description: 'Quiet lattice, dot, line, and depth textures for structure without decoration.', render: () => ( - +
{textureTokens.map(token => (
@@ -72,6 +72,13 @@ function getTextureStyle(token: string): CSSProperties { 'linear-gradient(var(--concrete-lattice) var(--concrete-border-width-hairline), transparent var(--concrete-border-width-hairline)), linear-gradient(90deg, var(--concrete-lattice) var(--concrete-border-width-hairline), transparent var(--concrete-border-width-hairline))', backgroundSize: 'var(--concrete-grid-unit) var(--concrete-grid-unit)' } + case 'depth': + return { + backgroundColor: 'var(--concrete-depth-background)', + backgroundImage: 'var(--concrete-depth-background-image)', + backgroundSize: + 'var(--concrete-size-full) var(--concrete-size-full), var(--concrete-grid-unit) var(--concrete-grid-unit), var(--concrete-grid-unit) var(--concrete-grid-unit), var(--concrete-size-full) var(--concrete-size-full)' + } default: return {} } @@ -85,6 +92,8 @@ function getTextureDescription(token: string): string { return 'reading and rows' case 'lattice': return 'diagram coordinates' + case 'depth': + return 'perspective grounds' default: return 'texture ground' } diff --git a/packages/concrete/src/foundations/textures/schema.ts b/packages/concrete/src/foundations/textures/schema.ts index 40f1e14..8454433 100644 --- a/packages/concrete/src/foundations/textures/schema.ts +++ b/packages/concrete/src/foundations/textures/schema.ts @@ -2,11 +2,11 @@ import { z } from 'zod/v4' export const textureFoundationSchema = z .object({ - variant: z.enum(['dots', 'lattice', 'lines']).default('lattice') + variant: z.enum(['dots', 'lattice', 'lines', 'depth']).default('lattice') }) .strict() -export const textureTokens = ['lattice', 'dots', 'lines'] as const +export const textureTokens = ['lattice', 'dots', 'lines', 'depth'] as const export type TextureFoundationInput = z.input export type TextureFoundationValue = z.output diff --git a/packages/concrete/src/foundations/textures/styles.css b/packages/concrete/src/foundations/textures/styles.css index ba38d37..8bbb3e0 100644 --- a/packages/concrete/src/foundations/textures/styles.css +++ b/packages/concrete/src/foundations/textures/styles.css @@ -29,6 +29,27 @@ --concrete-lattice: rgb(10 11 15 / 3.5%); --concrete-dots: rgb(10 11 15 / 14%); --concrete-lines: rgb(10 11 15 / 6%); + --concrete-depth: var(--concrete-inverse-background); + --concrete-depth-background: #070a10; + --concrete-depth-line: rgb(78 139 222 / 18%); + --concrete-depth-line-strong: rgb(78 139 222 / 32%); + --concrete-depth-glow: rgb(31 111 212 / 18%); + --concrete-depth-fade: rgb(7 10 16 / 92%); + --concrete-depth-background-image: + radial-gradient(circle at 50% 0, var(--concrete-depth-glow), transparent 42%), + linear-gradient(var(--concrete-depth-line) 1px, transparent 1px), + linear-gradient(90deg, var(--concrete-depth-line) 1px, transparent 1px), + linear-gradient(180deg, transparent 12%, var(--concrete-depth-glow) 54%, transparent); + --concrete-tilt-frame-glare-background: linear-gradient( + 135deg, + rgb(255 255 255 / 12%), + transparent 36% 70%, + rgb(78 139 222 / 8%) + ); + --concrete-tilt-frame-glare-shadow: + inset 0 0 0 1px rgb(78 139 222 / 14%), + inset 0 14px 24px -22px rgb(78 139 222 / 32%), + inset 0 -18px 28px -26px rgb(10 11 15 / 28%); } .concrete-lattice { diff --git a/packages/concrete/src/primitives/index.tsx b/packages/concrete/src/primitives/index.tsx index 1c7a361..6d33900 100644 --- a/packages/concrete/src/primitives/index.tsx +++ b/packages/concrete/src/primitives/index.tsx @@ -71,6 +71,7 @@ export * from './radio' export * from './rail' export * from './range' export * from './row' +export * from './scale-frame' export * from './scroll-area' export * from './search-input' export * from './section' @@ -94,6 +95,7 @@ export * from './target-line' export * from './text' export * from './textarea' export * from './time-list' +export * from './tilt-frame' export * from './token' export * from './token-rail' export * from './tool-call-panel' diff --git a/packages/concrete/src/primitives/scale-frame/component.tsx b/packages/concrete/src/primitives/scale-frame/component.tsx new file mode 100644 index 0000000..cba32e0 --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/component.tsx @@ -0,0 +1,56 @@ +import type { CSSProperties, HTMLAttributes, ReactNode } from 'react' +import { concreteClassNames } from '../../styles/class-names' +import { cn } from '../utils' + +export type ScaleFrameAlign = 'center' | 'end' | 'start' +export type ScaleFrameSurface = 'raised' | 'sunken' | 'transparent' + +export type ScaleFrameProps = HTMLAttributes & { + align?: ScaleFrameAlign + children?: ReactNode + scale?: number + surface?: ScaleFrameSurface +} + +type ScaleFrameStyle = CSSProperties & { + '--concrete-scale-frame-scale'?: string +} + +// TECH-DEBT: Low-quality docs-preview primitive. The raw numeric scale prop and inline +// custom property adapter should be replaced by tokenized preview/composition primitives. +export function ScaleFrame({ + align = 'center', + children, + className, + scale = 1, + style, + surface = 'transparent', + ...props +}: ScaleFrameProps) { + const frameStyle: ScaleFrameStyle = { + '--concrete-scale-frame-scale': String(getSafeScale(scale)), + ...style + } + + return ( +
+
+
{children}
+
+
+ ) +} + +function getSafeScale(scale: number): number { + if (!Number.isFinite(scale)) { + return 1 + } + + return Math.min(Math.max(scale, 0.15), 1.25) +} diff --git a/packages/concrete/src/primitives/scale-frame/examples.tsx b/packages/concrete/src/primitives/scale-frame/examples.tsx new file mode 100644 index 0000000..057795e --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/examples.tsx @@ -0,0 +1,30 @@ +import { defineExamples } from '../../factories/createExamples' +import { Button } from '../button' +import { Input } from '../input' +import { ScaleFrame } from './component' + +export const scaleFrameExamples = defineExamples({ + controls: { + description: 'A larger control group scaled inside fixed preview bounds.', + render: function renderControls() { + return ( + + + + + ) + } + }, + panel: { + description: 'Static product surfaces keep their layout while fitting dense previews.', + render: function renderPanel() { + return ( + + Latency p95 + 184ms +

Scaled output remains deterministic.

+
+ ) + } + } +}) diff --git a/packages/concrete/src/primitives/scale-frame/index.tsx b/packages/concrete/src/primitives/scale-frame/index.tsx new file mode 100644 index 0000000..68c3dc0 --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/index.tsx @@ -0,0 +1,30 @@ +import { exampleStates, renderExample } from '../../factories/createExamples' +import { createPrimitive } from '../../factories/createItems' +import { ScaleFrame } from './component' +import { scaleFrameExamples } from './examples' +import { scaleFrameMeta } from './meta' +import { type ScaleFrameValue, scaleFrameSchema } from './schema' + +export type { ScaleFrameAlign, ScaleFrameProps, ScaleFrameSurface } from './component' +export { ScaleFrame } from './component' +export type { ScaleFrameInput, ScaleFrameValue } from './schema' +export { scaleFramePropsSchema, scaleFrameSchema } from './schema' + +export const scaleFramePrimitiveDefinition = createPrimitive({ + ...scaleFrameMeta, + component: ScaleFrame, + kind: 'primitive', + renderExample: (state?: string) => renderExample(scaleFrameExamples, state), + renderInput: input => renderScaleFrameInput(scaleFrameSchema.parse(input)), + schema: scaleFrameSchema, + slug: 'scale-frame', + states: exampleStates(scaleFrameExamples, ['controls', 'panel']) +}) + +function renderScaleFrameInput({ align, body, scale, surface }: ScaleFrameValue) { + return ( + +
{body}
+
+ ) +} diff --git a/packages/concrete/src/primitives/scale-frame/meta.ts b/packages/concrete/src/primitives/scale-frame/meta.ts new file mode 100644 index 0000000..498cc90 --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/meta.ts @@ -0,0 +1,26 @@ +import { prop } from '../../registry/props' +import type { ConcretePressure, PrimitiveCategory } from '../../schemas' + +type ScaleFrameMeta = { + category: PrimitiveCategory + description: string + guidance: string + name: string + pressure: readonly ConcretePressure[] + props: readonly ReturnType[] +} + +export const scaleFrameMeta = { + category: 'surface', + description: 'Fixed-bounds preview container for scaled product surfaces.', + guidance: + 'Use ScaleFrame when a real primitive, component, or interface must fit a constrained preview without changing its internal layout.', + name: 'ScaleFrame', + pressure: ['product', 'generative', 'educational'], + props: [ + prop('scale', 'number', 'Visual scale applied inside stable frame bounds.', '1'), + prop('align', "'start' | 'center' | 'end'", 'Content alignment inside the frame.', 'center'), + prop('surface', "'raised' | 'sunken' | 'transparent'", 'Optional frame surface.', 'transparent'), + prop('children', 'ReactNode', 'Scaled frame content.') + ] +} as const satisfies ScaleFrameMeta diff --git a/packages/concrete/src/primitives/scale-frame/schema.ts b/packages/concrete/src/primitives/scale-frame/schema.ts new file mode 100644 index 0000000..05679f8 --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v4' + +export const scaleFrameSchema = z + .object({ + align: z.enum(['start', 'center', 'end']).default('center'), + body: z.string().default('Scaled preview content'), + scale: z.number().min(0.15).max(1.25).default(1), + surface: z.enum(['raised', 'sunken', 'transparent']).default('transparent') + }) + .strict() + +export { scaleFrameSchema as scaleFramePropsSchema } +export type ScaleFrameInput = z.input +export type ScaleFrameValue = z.output diff --git a/packages/concrete/src/primitives/scale-frame/styles.css b/packages/concrete/src/primitives/scale-frame/styles.css new file mode 100644 index 0000000..bd8bd3f --- /dev/null +++ b/packages/concrete/src/primitives/scale-frame/styles.css @@ -0,0 +1,43 @@ +.concrete-scale-frame { + display: grid; + min-width: var(--concrete-space-0); +} + +.concrete-scale-frame-surface { + display: grid; + width: var(--concrete-size-full); + min-width: var(--concrete-space-0); + overflow: hidden; + border: var(--concrete-border-width-hairline) solid transparent; + border-radius: var(--concrete-radius-4); +} + +.concrete-scale-frame[data-surface="raised"] .concrete-scale-frame-surface { + border-color: var(--concrete-border); + background: var(--concrete-surface); + box-shadow: var(--concrete-shadow-1); +} + +.concrete-scale-frame[data-surface="sunken"] .concrete-scale-frame-surface { + border-color: var(--concrete-border-soft); + background: var(--concrete-sunken); +} + +.concrete-scale-frame-content { + display: grid; + gap: var(--concrete-space-2); + min-width: var(--concrete-space-0); + place-self: center; + transform: scale(var(--concrete-scale-frame-scale)); + transform-origin: center; +} + +.concrete-scale-frame[data-align="start"] .concrete-scale-frame-content { + place-self: start; + transform-origin: top left; +} + +.concrete-scale-frame[data-align="end"] .concrete-scale-frame-content { + place-self: end; + transform-origin: right bottom; +} diff --git a/packages/concrete/src/primitives/tilt-frame/component.tsx b/packages/concrete/src/primitives/tilt-frame/component.tsx new file mode 100644 index 0000000..df0e6fa --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/component.tsx @@ -0,0 +1,126 @@ +'use client' + +import type { HTMLAttributes, PointerEvent, ReactNode } from 'react' +import { useEffect, useRef } from 'react' +import { concreteClassNames } from '../../styles/class-names' +import { cn } from '../utils' + +export type TiltFrameIntensity = 'medium' | 'subtle' +export type TiltFrameSurface = 'raised' | 'sunken' | 'transparent' + +export type TiltFrameProps = HTMLAttributes & { + children?: ReactNode + /** Enables cursor-driven rotation. */ + interactive?: boolean + /** Rotation strength. Keep hero/product uses subtle. */ + intensity?: TiltFrameIntensity + surface?: TiltFrameSurface +} + +const tiltIntensityDegrees = { + medium: 6.5, + subtle: 3.5 +} satisfies Record + +type PendingTilt = { + rotateX: number + rotateY: number + target: HTMLDivElement +} + +// TECH-DEBT: Low-quality landing-page primitive. Cursor-driven visual depth is too +// aesthetic-specific for the core surface vocabulary and needs a stricter role contract. +export function TiltFrame({ + children, + className, + interactive = true, + intensity = 'subtle', + onPointerLeave, + onPointerMove, + surface = 'raised', + ...props +}: TiltFrameProps) { + const frameReference = useRef(null) + const pendingTiltReference = useRef(null) + + useEffect(() => { + return () => { + if (frameReference.current !== null) { + window.cancelAnimationFrame(frameReference.current) + } + } + }, []) + + function updateTilt(event: PointerEvent) { + onPointerMove?.(event) + + if (!interactive) { + return + } + + const bounds = event.currentTarget.getBoundingClientRect() + const x = clampTiltAxis((event.clientX - bounds.left) / bounds.width - 0.5) + const y = clampTiltAxis((event.clientY - bounds.top) / bounds.height - 0.5) + const degrees = tiltIntensityDegrees[intensity] + + queueTilt(event.currentTarget, roundTiltValue(-y * degrees), roundTiltValue(x * degrees)) + } + + function resetTilt(event: PointerEvent) { + onPointerLeave?.(event) + + pendingTiltReference.current = null + + if (frameReference.current !== null) { + window.cancelAnimationFrame(frameReference.current) + frameReference.current = null + } + + event.currentTarget.style.removeProperty('--concrete-tilt-rotate-x') + event.currentTarget.style.removeProperty('--concrete-tilt-rotate-y') + } + + function queueTilt(target: HTMLDivElement, rotateX: number, rotateY: number) { + pendingTiltReference.current = { rotateX, rotateY, target } + + if (frameReference.current !== null) { + return + } + + frameReference.current = window.requestAnimationFrame(() => { + const pendingTilt = pendingTiltReference.current + frameReference.current = null + + if (!pendingTilt) { + return + } + + pendingTilt.target.style.setProperty('--concrete-tilt-rotate-x', `${pendingTilt.rotateX}deg`) + pendingTilt.target.style.setProperty('--concrete-tilt-rotate-y', `${pendingTilt.rotateY}deg`) + }) + } + + return ( +
+
+
{children}
+ +
+
+ ) +} + +function clampTiltAxis(value: number): number { + return Math.max(-0.5, Math.min(0.5, value)) +} + +function roundTiltValue(value: number): number { + return Math.round(value * 100) / 100 +} diff --git a/packages/concrete/src/primitives/tilt-frame/examples.tsx b/packages/concrete/src/primitives/tilt-frame/examples.tsx new file mode 100644 index 0000000..506dd8d --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/examples.tsx @@ -0,0 +1,31 @@ +import { defineExamples } from '../../factories/createExamples' +import { Button } from '../button' +import { Input } from '../input' +import { Slider } from '../slider' +import { Switch } from '../switch' +import { TiltFrame } from './component' + +export const tiltFrameExamples = defineExamples({ + controls: { + description: 'Stable container for live controls with subtle pointer depth.', + render: () => ( + + + + + + + ) + }, + surface: { + description: 'Sunken depth keeps the highlight quiet inside dense product surfaces.', + render: () => ( + + Generated panel +

One focused output, returned from typed props.

+
+ ) + } +}) diff --git a/packages/concrete/src/primitives/tilt-frame/index.tsx b/packages/concrete/src/primitives/tilt-frame/index.tsx new file mode 100644 index 0000000..7baafdd --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/index.tsx @@ -0,0 +1,30 @@ +import { exampleStates, renderExample } from '../../factories/createExamples' +import { createPrimitive } from '../../factories/createItems' +import { TiltFrame } from './component' +import { tiltFrameExamples } from './examples' +import { tiltFrameMeta } from './meta' +import { type TiltFrameValue, tiltFrameSchema } from './schema' + +export type { TiltFrameIntensity, TiltFrameProps, TiltFrameSurface } from './component' +export { TiltFrame } from './component' +export type { TiltFrameInput, TiltFrameValue } from './schema' +export { tiltFramePropsSchema, tiltFrameSchema } from './schema' + +export const tiltFramePrimitiveDefinition = createPrimitive({ + ...tiltFrameMeta, + component: TiltFrame, + kind: 'primitive', + renderExample: (state?: string) => renderExample(tiltFrameExamples, state), + renderInput: input => renderTiltFrameInput(tiltFrameSchema.parse(input)), + schema: tiltFrameSchema, + slug: 'tilt-frame', + states: exampleStates(tiltFrameExamples, ['controls', 'surface']) +}) + +function renderTiltFrameInput({ body, interactive, intensity, surface }: TiltFrameValue) { + return ( + + {body} + + ) +} diff --git a/packages/concrete/src/primitives/tilt-frame/meta.ts b/packages/concrete/src/primitives/tilt-frame/meta.ts new file mode 100644 index 0000000..d082961 --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/meta.ts @@ -0,0 +1,26 @@ +import { prop } from '../../registry/props' +import type { ConcretePressure, PrimitiveCategory } from '../../schemas' + +type TiltFrameMeta = { + category: PrimitiveCategory + description: string + guidance: string + name: string + pressure: readonly ConcretePressure[] + props: readonly ReturnType[] +} + +export const tiltFrameMeta = { + category: 'surface', + description: 'Pointer-aware depth container for one highlighted surface.', + guidance: + 'Use TiltFrame for one highlighted surface, not for every card in a dense grid. Keep the intensity subtle and pair with ScaleFrame when fixed preview scaling is needed.', + name: 'TiltFrame', + pressure: ['product', 'generative', 'educational'], + props: [ + prop('interactive', 'boolean', 'Enables cursor-driven rotation.', 'true'), + prop('intensity', "'subtle' | 'medium'", 'Rotation strength.', 'subtle'), + prop('surface', "'raised' | 'sunken' | 'transparent'", 'Frame surface treatment.', 'raised'), + prop('children', 'ReactNode', 'Frame content.') + ] +} as const satisfies TiltFrameMeta diff --git a/packages/concrete/src/primitives/tilt-frame/schema.ts b/packages/concrete/src/primitives/tilt-frame/schema.ts new file mode 100644 index 0000000..ddd4a57 --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v4' + +export const tiltFrameSchema = z + .object({ + body: z.string().default('Depth-aware surface'), + intensity: z.enum(['subtle', 'medium']).default('subtle'), + interactive: z.boolean().default(true), + surface: z.enum(['raised', 'sunken', 'transparent']).default('raised') + }) + .strict() + +export { tiltFrameSchema as tiltFramePropsSchema } +export type TiltFrameInput = z.input +export type TiltFrameValue = z.output diff --git a/packages/concrete/src/primitives/tilt-frame/styles.css b/packages/concrete/src/primitives/tilt-frame/styles.css new file mode 100644 index 0000000..9a3eb9a --- /dev/null +++ b/packages/concrete/src/primitives/tilt-frame/styles.css @@ -0,0 +1,67 @@ +.concrete-tilt-frame { + display: grid; + min-width: var(--concrete-space-0); + perspective: var(--concrete-size-tilt-frame-perspective); +} + +.concrete-tilt-frame-surface { + position: relative; + display: grid; + min-width: var(--concrete-space-0); + overflow: hidden; + border: var(--concrete-border-width-hairline) solid var(--concrete-border); + border-radius: var(--concrete-radius-4); + background: var(--concrete-surface); + box-shadow: var(--concrete-shadow-2); + transform: rotateX(var(--concrete-tilt-rotate-x, 0deg)) + rotateY(var(--concrete-tilt-rotate-y, 0deg)) translate3d(0, 0, 0); + transform-origin: center; + transform-style: preserve-3d; + transition: + transform var(--concrete-duration-fast) var(--concrete-ease), + border-color var(--concrete-duration-fast) var(--concrete-ease), + box-shadow var(--concrete-duration-fast) var(--concrete-ease); + backface-visibility: hidden; + contain: paint; + will-change: transform; +} + +.concrete-tilt-frame[data-surface="sunken"] .concrete-tilt-frame-surface { + border-color: var(--concrete-border-soft); + background: var(--concrete-sunken); + box-shadow: inset var(--concrete-space-0) var(--concrete-border-width-hairline) + var(--concrete-space-0) var(--concrete-surface); +} + +.concrete-tilt-frame[data-surface="transparent"] .concrete-tilt-frame-surface { + border-color: transparent; + background: transparent; + box-shadow: none; +} + +.concrete-tilt-frame[data-interactive="true"]:hover .concrete-tilt-frame-surface { + border-color: var(--concrete-border-strong); + box-shadow: var(--concrete-shadow-2); +} + +.concrete-tilt-frame-content { + display: grid; + gap: var(--concrete-space-2); + min-width: var(--concrete-space-0); + padding: var(--concrete-space-4); +} + +.concrete-tilt-frame-glare { + position: absolute; + inset: var(--concrete-space-0); + border-radius: inherit; + background: var(--concrete-tilt-frame-glare-background); + box-shadow: var(--concrete-tilt-frame-glare-shadow); + opacity: var(--concrete-opacity-hidden); + pointer-events: none; + transition: opacity var(--concrete-duration-fast) var(--concrete-ease); +} + +.concrete-tilt-frame[data-interactive="true"]:hover .concrete-tilt-frame-glare { + opacity: var(--concrete-opacity-tilt-frame-glare); +} diff --git a/packages/concrete/src/registry/items.tsx b/packages/concrete/src/registry/items.tsx index 80f1490..7a496d2 100644 --- a/packages/concrete/src/registry/items.tsx +++ b/packages/concrete/src/registry/items.tsx @@ -8,6 +8,7 @@ import { datePickerComponentDefinition } from '../components/date-picker' import { dateRangePickerComponentDefinition } from '../components/date-range-picker' import { diagramCanvasComponentDefinition } from '../components/diagram-canvas' import { donutChartComponentDefinition } from '../components/donut-chart' +import { featureCardComponentDefinition } from '../components/feature-card' import { fileUploadComponentDefinition } from '../components/file-upload' import { flowDiagramComponentDefinition } from '../components/flow-diagram' import { formDialogComponentDefinition } from '../components/form-dialog' @@ -104,6 +105,7 @@ import { radioPrimitiveDefinition } from '../primitives/radio' import { railPrimitiveDefinition } from '../primitives/rail' import { rangePrimitiveDefinition } from '../primitives/range' import { rowPrimitiveDefinition } from '../primitives/row' +import { scaleFramePrimitiveDefinition } from '../primitives/scale-frame' import { scrollAreaPrimitiveDefinition } from '../primitives/scroll-area' import { searchInputPrimitiveDefinition } from '../primitives/search-input' import { sectionPrimitiveDefinition } from '../primitives/section' @@ -127,6 +129,7 @@ import { targetLinePrimitiveDefinition } from '../primitives/target-line' import { textPrimitiveDefinition } from '../primitives/text' import { textareaPrimitiveDefinition } from '../primitives/textarea' import { timeListPrimitiveDefinition } from '../primitives/time-list' +import { tiltFramePrimitiveDefinition } from '../primitives/tilt-frame' import { tokenPrimitiveDefinition } from '../primitives/token' import { tokenRailPrimitiveDefinition } from '../primitives/token-rail' import { toolCallPanelPrimitiveDefinition } from '../primitives/tool-call-panel' @@ -245,6 +248,8 @@ export const primitiveDefinitions = [ framePrimitiveDefinition, timeListPrimitiveDefinition, toolCallPanelPrimitiveDefinition, + tiltFramePrimitiveDefinition, + scaleFramePrimitiveDefinition, brandMarkPrimitiveDefinition, wordmarkPrimitiveDefinition, iconPrimitiveDefinition @@ -277,6 +282,7 @@ export const componentDefinitions = [ donutChartComponentDefinition, heatmapComponentDefinition, chartComponentDefinition, + featureCardComponentDefinition, dataTableComponentDefinition, flowDiagramComponentDefinition, diagramCanvasComponentDefinition, diff --git a/packages/concrete/src/registry/types.ts b/packages/concrete/src/registry/types.ts index efee518..50e1817 100644 --- a/packages/concrete/src/registry/types.ts +++ b/packages/concrete/src/registry/types.ts @@ -88,6 +88,7 @@ export type PrimitiveSlug = | 'rail' | 'range' | 'row' + | 'scale-frame' | 'scroll-area' | 'search-input' | 'select' @@ -111,6 +112,7 @@ export type PrimitiveSlug = | 'textarea' | 'text' | 'time-list' + | 'tilt-frame' | 'token' | 'tooltip' | 'tool-call-panel' @@ -131,6 +133,7 @@ export type ComponentSlug = | 'data-table' | 'date-picker' | 'date-range-picker' + | 'feature-card' | 'file-upload' | 'flow-diagram' | 'form-dialog' diff --git a/packages/concrete/src/styles/class-names.ts b/packages/concrete/src/styles/class-names.ts index ea0dc18..ce147dc 100644 --- a/packages/concrete/src/styles/class-names.ts +++ b/packages/concrete/src/styles/class-names.ts @@ -52,7 +52,7 @@ optionRowCopy optionRowIcon pill progressError progressFill progressLined progressRing progressRingCenter progressRingFill progressRingTrack progressRingUnit progressShuttle progressSky progressTerminal progressThick progressThin progressTrack progressUltra radio radioChecked radioDot rail tokenRailItem range rangeTrack rangeValues root row rowIcon rowInteractive -rowLabel rowMeta +rowLabel rowMeta scaleFrame scaleFrameContent scaleFrameSurface `, ` section sectionBody segmentedProgress select selectWrap composerSendButton skeleton slider @@ -62,7 +62,8 @@ stat statDisplay statLabel statLarge statMeta statMuted statNumber statSky split splitAside splitBody stack statSmall statUnit statValue statXlarge statXsmall composerSubmitDock surface switch switchChecked switchTrack syntaxAttribute syntaxComment syntaxFunction syntaxIdentifier syntaxKeyword syntaxNumber syntaxOperator syntaxPunctuation syntaxString syntaxType tag tagActive tagClose tagError tagLarge text -tagOutline tagSelected tagSky tagSmall tagTerminal tagUltra textarea timeMenu +tagOutline tagSelected tagSky tagSmall tagTerminal tagUltra textarea tiltFrame tiltFrameContent +tiltFrameGlare tiltFrameSurface timeMenu trace traceStatus traceSteps traceSummaryMain traceSummaryText `, ` diff --git a/packages/concrete/src/styles/manifest.ts b/packages/concrete/src/styles/manifest.ts index 9c758c2..860248c 100644 --- a/packages/concrete/src/styles/manifest.ts +++ b/packages/concrete/src/styles/manifest.ts @@ -111,6 +111,7 @@ export const primitiveStyleSources = [ { kind: 'primitive', path: 'src/primitives/rail/styles.css' }, { kind: 'primitive', path: 'src/primitives/range/styles.css' }, { kind: 'primitive', path: 'src/primitives/row/styles.css' }, + { kind: 'primitive', path: 'src/primitives/scale-frame/styles.css' }, { kind: 'primitive', path: 'src/primitives/scroll-area/styles.css' }, { kind: 'primitive', path: 'src/primitives/search-input/styles.css' }, { kind: 'primitive', path: 'src/primitives/select/styles.css' }, @@ -134,6 +135,7 @@ export const primitiveStyleSources = [ { kind: 'primitive', path: 'src/primitives/textarea/styles.css' }, { kind: 'primitive', path: 'src/primitives/text/styles.css' }, { kind: 'primitive', path: 'src/primitives/time-list/styles.css' }, + { kind: 'primitive', path: 'src/primitives/tilt-frame/styles.css' }, { kind: 'primitive', path: 'src/primitives/token/styles.css' }, { kind: 'primitive', path: 'src/primitives/tooltip/styles.css' }, { kind: 'primitive', path: 'src/primitives/tool-call-panel/styles.css' }, diff --git a/packages/concrete/src/tests/import-boundaries.test.ts b/packages/concrete/src/tests/import-boundaries.test.ts index 859dfbb..4b6cde4 100644 --- a/packages/concrete/src/tests/import-boundaries.test.ts +++ b/packages/concrete/src/tests/import-boundaries.test.ts @@ -101,6 +101,7 @@ const dynamicPrimitiveInlineStyleFiles = [ 'packages/concrete/src/primitives/heatmap-grid/component.tsx', 'packages/concrete/src/primitives/progress/component.tsx', 'packages/concrete/src/primitives/range/component.tsx', + 'packages/concrete/src/primitives/scale-frame/component.tsx', 'packages/concrete/src/primitives/skeleton/component.tsx', 'packages/concrete/src/primitives/slider/component.tsx', 'packages/concrete/src/primitives/table/component.tsx', @@ -499,7 +500,7 @@ describe('Import boundaries', () => { ([key, value]) => value !== toConcreteSelector(key) ) - expect(concreteClassNameEntries.length).toBe(507) + expect(concreteClassNameEntries.length).toBe(513) expect(classNameRecord.button).toBe('concrete-button') expect(classNameRecord.diagramCanvasEdgeSelected).toBe('concrete-diagram-canvas-edge-selected') expect(classNameRecord.alertAction).toBe('concrete-alert-action') diff --git a/packages/concrete/src/tests/registry.test.ts b/packages/concrete/src/tests/registry.test.ts index 3329950..c2f84b0 100644 --- a/packages/concrete/src/tests/registry.test.ts +++ b/packages/concrete/src/tests/registry.test.ts @@ -666,6 +666,8 @@ describe('Concrete registry', () => { 'frame', 'time-list', 'tool-call-panel', + 'tilt-frame', + 'scale-frame', 'brand-mark', 'wordmark', 'icon' @@ -697,6 +699,7 @@ describe('Concrete registry', () => { 'donut-chart', 'heatmap', 'chart', + 'feature-card', 'data-table', 'flow-diagram', 'diagram-canvas', From 4c30a3e5a39a63f75572e5c251daa318d65e1a14 Mon Sep 17 00:00:00 2001 From: DexterStorey Date: Sat, 2 May 2026 13:38:34 -0400 Subject: [PATCH 2/2] chore: keep messy landing reference green --- SKILL.md | 8 ++++---- packages/concrete/src/primitives/index.tsx | 2 +- packages/concrete/src/registry/items.tsx | 2 +- packages/concrete/src/tests/import-boundaries.test.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SKILL.md b/SKILL.md index c8a1d9a..6c84ed1 100644 --- a/SKILL.md +++ b/SKILL.md @@ -56,7 +56,7 @@ import { foundationRegistry } from '@rubriclab/concrete/registry' This section is generated from `foundationRegistry`, `primitiveRegistry`, and `componentRegistry`. Run `bun run build:skill` after registry changes. -Concrete currently exposes 12 foundations, 106 primitives, and 33 components. +Concrete currently exposes 12 foundations, 108 primitives, and 34 components. ### Foundations @@ -148,7 +148,7 @@ Quiet texture grounds for diagrams, editorial frames, and educational examples. - Category: `foundation` - Pressure: `editorial`, `educational` - Guidance: Use texture as structure, never as decorative noise. -- Tokens: Tokens (3: `lattice`, `dots`, `lines`) +- Tokens: Tokens (4: `lattice`, `dots`, `lines`, `depth`) #### Iconography @@ -187,7 +187,7 @@ Primitives are the Concrete HTML vocabulary. They own DOM, scoped classes, schem - **Control** (14): Button (`button`), Toolbar Control (`toolbar-control`), IconButton (`icon-button`), ControlGroup (`control-group`), Token (`token`), SearchInput (`search-input`), PickerButton (`picker-button`), Listbox (`listbox`), Option row (`option-row`), Caret (`caret`), Chip (`chip`), Composer Surface (`composer-surface`), Token Rail (`token-rail`), Diagram Controls (`diagram-controls`) - **Form** (15): Input (`input`), Field (`field`), FieldRow (`field-row`), Calendar panel (`calendar-grid`), Dropzone (`dropzone`), Upload field (`upload-field`), Textarea (`textarea`), Select (`select`), Checkbox (`checkbox`), Radio (`radio`), Stepper (`stepper`), Range (`range`), Switch (`switch`), Slider (`slider`), Time list (`time-list`) - **Layout** (12): Stack (`stack`), Inline (`inline`), Cluster (`cluster`), Grid (`grid`), Split (`split`), Scroll Area (`scroll-area`), Dock (`dock`), Rail (`rail`), Section (`section`), Row (`row`), Divider (`divider`), Frame (`frame`) -- **Surface** (9): Surface (`surface`), Panel (`panel`), PickerSurface (`picker-surface`), Overlay (`overlay`), DialogSurface (`dialog-surface`), DrawerSurface (`drawer-surface`), DisclosurePanel (`disclosure-panel`), Message Bubble (`message-bubble`), Card (`card`) +- **Surface** (11): Surface (`surface`), Panel (`panel`), PickerSurface (`picker-surface`), Overlay (`overlay`), DialogSurface (`dialog-surface`), DrawerSurface (`drawer-surface`), DisclosurePanel (`disclosure-panel`), Message Bubble (`message-bubble`), Card (`card`), TiltFrame (`tilt-frame`), ScaleFrame (`scale-frame`) - **Typography** (6): Header (`header`), Text (`text`), Heading (`heading`), Label (`label`), Code (`code`), Kbd (`kbd`) - **Navigation** (3): MenuSurface (`menu-surface`), MenuGroup (`menu-group`), Link (`link`) - **Feedback** (9): Alert (`alert`), ValidationList (`validation-list`), Transcript item (`transcript-item`), Trace panel (`trace-panel`), Spinner (`spinner`), Empty state (`empty-state`), Tooltip (`tooltip`), Skeleton (`skeleton`), Tool-call panel (`tool-call-panel`) @@ -209,8 +209,8 @@ Components assemble primitives into reusable product behavior. They should not i - **Feedback** (3): Validation summary (`validation-summary`), Reasoning message (`reasoning-message`), Tool call message (`tool-call-message`) - **Media** (1): Image upload (`image-upload`) - **Data** (11): Metric card (`metric-card`), Meter (`meter`), Line chart (`line-chart`), Area chart (`area-chart`), Bar chart (`bar-chart`), Stacked bar chart (`stacked-bar-chart`), Donut chart (`donut-chart`), Heatmap (`heatmap`), Chart (`chart`), Data table (`data-table`), Flow diagram (`flow-diagram`) +- **Surface** (2): FeatureCard (`feature-card`), Message (`message`) - **Diagram** (1): Diagram canvas (`diagram-canvas`) -- **Surface** (1): Message (`message`) ## Usage Guide diff --git a/packages/concrete/src/primitives/index.tsx b/packages/concrete/src/primitives/index.tsx index 6d33900..e516ace 100644 --- a/packages/concrete/src/primitives/index.tsx +++ b/packages/concrete/src/primitives/index.tsx @@ -94,8 +94,8 @@ export * from './tag' export * from './target-line' export * from './text' export * from './textarea' -export * from './time-list' export * from './tilt-frame' +export * from './time-list' export * from './token' export * from './token-rail' export * from './tool-call-panel' diff --git a/packages/concrete/src/registry/items.tsx b/packages/concrete/src/registry/items.tsx index 7a496d2..5b68b33 100644 --- a/packages/concrete/src/registry/items.tsx +++ b/packages/concrete/src/registry/items.tsx @@ -128,8 +128,8 @@ import { tagPrimitiveDefinition } from '../primitives/tag' import { targetLinePrimitiveDefinition } from '../primitives/target-line' import { textPrimitiveDefinition } from '../primitives/text' import { textareaPrimitiveDefinition } from '../primitives/textarea' -import { timeListPrimitiveDefinition } from '../primitives/time-list' import { tiltFramePrimitiveDefinition } from '../primitives/tilt-frame' +import { timeListPrimitiveDefinition } from '../primitives/time-list' import { tokenPrimitiveDefinition } from '../primitives/token' import { tokenRailPrimitiveDefinition } from '../primitives/token-rail' import { toolCallPanelPrimitiveDefinition } from '../primitives/tool-call-panel' diff --git a/packages/concrete/src/tests/import-boundaries.test.ts b/packages/concrete/src/tests/import-boundaries.test.ts index 4b6cde4..f138988 100644 --- a/packages/concrete/src/tests/import-boundaries.test.ts +++ b/packages/concrete/src/tests/import-boundaries.test.ts @@ -500,7 +500,7 @@ describe('Import boundaries', () => { ([key, value]) => value !== toConcreteSelector(key) ) - expect(concreteClassNameEntries.length).toBe(513) + expect(concreteClassNameEntries.length).toBe(514) expect(classNameRecord.button).toBe('concrete-button') expect(classNameRecord.diagramCanvasEdgeSelected).toBe('concrete-diagram-canvas-edge-selected') expect(classNameRecord.alertAction).toBe('concrete-alert-action')