diff --git a/adr/061-concern-split-schemas.md b/adr/061-concern-split-schemas.md new file mode 100644 index 0000000..e9448fd --- /dev/null +++ b/adr/061-concern-split-schemas.md @@ -0,0 +1,189 @@ +# ADR: Schema Entry Points for Concern-Split Output + +**Branch**: `061-concern-split-schemas` +**Created**: 2026-07-05 +**Status**: DRAFT +**Deciders**: Nathan Curtis (author) +**Supersedes**: *(none)* + +--- + +## Context + +`specs-cli`'s `generate` command produces five distinct file shapes depending on `--split-components`, `--split-concerns`, and `--use-subfolders`: + +| Mode | Shape | Root structure | +|------|-------|-----------------| +| 1 | Whole library, one file | `{ components: { name: Component } }` | +| 2/3 | One component per file (flat or subfoldered) | `Component` | +| 4 | Concern-split, library-wide (`api.yaml`, `variants.yaml`, `examples.yaml`) | `{ components: { name: }, metadata: { generatedAt, componentCount, concern } }` | +| 5 | Concern-split, per-component (`Button/api.yaml`, etc.) | `` merged with `metadata: { ...Metadata, generatedAt, concern }` | + +Only modes 1–3 have a corresponding JSON Schema today: `components.schema.json` (mode 1) and `component.schema.json` (modes 2/3), unioned by `root.schema.json`. `Props`, `Anatomy`, `Variant`, `Variants`, `InstanceExamples`, and the slot-content-example map exist only as `#/definitions/*` fragments inside `component.schema.json` — they are not independently addressable root schemas, and no schema describes the partial-`Component` shapes that modes 4/5 actually emit. A file produced by `--split-concerns` has nothing to validate against today, whether opened in an editor with schema tooling or validated in CI. + +The concern split itself is fixed and implemented downstream (`specs-cli`'s `splitComponentByConcern`), grouping `Component` fields as: + +- **`api`**: `title`, `anatomy`, `props`, `metadata`, `subcomponents` (API-only, recursive) +- **`variants`**: `default`, `variants`, `invalidVariantCombinations` (if present), `metadata`, `subcomponents` (variants-only, recursive) +- **`examples`**: `metadata`, `slotContentExamples` (if present), `instanceExamples` (if present), `subcomponents` (examples-only, recursive) + +--- + +## Decision Drivers + +- **Additive-only**: new schema entry points must not change any existing file's shape or require a MAJOR bump. +- **Type ↔ schema symmetry (Constitution I, IV)**: any new schema root needs a corresponding named type in `types/`, and vice versa. +- **No logic in this package (Constitution II)**: the schema package only *describes* the concern shapes; it does not implement `splitComponentByConcern` — that logic is `specs-cli`'s. +- **Minimal, intentional public API (Constitution III)**: avoid introducing more schema/type surface than the five observed output shapes require. +- **Naming — code platforms first (Constitution VI)**: concern names (`api`, `variants`, `examples`) are already established CLI/config vocabulary (`--split-concerns`, `concern: 'api' | 'variants' | 'examples'`); no code-platform disagreement exists to resolve. + +--- + +## Options Considered + +### Option A: Three concern schemas + three "set" wrappers *(Selected)* + +Add six new schema files mirroring the existing `component.schema.json` / `components.schema.json` pairing, one pair per concern: + +- `component-api.schema.json`, `component-variants.schema.json`, `component-examples.schema.json` — each describes the per-component partial shape (mode 5's root). +- `components-api.schema.json`, `components-variants.schema.json`, `components-examples.schema.json` — each wraps its singular counterpart in `{ components: { name: ... } }` (mode 4's root), exactly as `components.schema.json` wraps `component.schema.json`. + +Each pair is backed by one new type in `types/`: `ComponentApi`, `ComponentVariants`, `ComponentExamples` — structural subsets of `Component`, referencing the same `Anatomy`, `Props`, `Variant`, `Variants`, `Metadata`, `InstanceExamples` types already exported. + +**Pros**: +- Mirrors an established, already-understood pattern (`component.schema.json` ↔ `components.schema.json`) — no new schema idiom introduced. +- Every one of the five observed output shapes gets an exact, addressable schema. +- Purely additive: new files, new types, no changes to existing schemas. + +**Cons / Trade-offs**: +- Six new files is more surface than a single "partial-component" schema with conditional requirements — but conditional (`if`/`then`) JSON Schema is harder for schema-aware tooling to give useful autocomplete against, so explicit shapes are preferred over a clever union. + +--- + +### Option B: Single generic `PartialComponent` schema with a `concern` discriminator + +One schema and one type, `PartialComponent`, where every `Component` field is optional and a sibling `metadata.concern` value is documented (not enforced) as indicating which subset should be populated. + +**Rejected because**: it validates far looser than what `specs-cli` actually emits (e.g. it would silently accept a `variants.yaml` file with a stray `props` key), which undermines Constitution IV's "mechanically verifiable" schema goal. It also can't distinguish the mode-4 wrapped shape from the mode-5 unwrapped shape without a `oneOf`, at which point it's no simpler than Option A. + +--- + +## Decision + +### Type changes (`types/`) + +| File | Change | Bump | +|------|--------|------| +| `types/Component.ts` (or new `types/ComponentConcerns.ts`) | Add `ComponentApi`, `ComponentVariants`, `ComponentExamples` types | MINOR | +| `types/index.ts` | Export the three new types | MINOR | + +**Example — new shape** (`types/ComponentConcerns.ts`): +```yaml +# New types, each a structural subset of Component +ComponentApi: + title: string + anatomy: Anatomy + props: Props + metadata: Metadata + subcomponents?: Record # recursive, API fields only + +ComponentVariants: + default: Variant + variants: Variants + invalidVariantCombinations?: PropConfigurations[] + metadata: Metadata + subcomponents?: Record + +ComponentExamples: + metadata: Metadata + slotContentExamples?: Record + instanceExamples?: InstanceExamples + subcomponents?: Record +``` + +### Schema changes (`schema/`) + +| File | Change | Bump | +|------|--------|------| +| `component-api.schema.json` | New — mode 5 `api` root, mirrors `#/definitions/ComponentApi` | MINOR | +| `component-variants.schema.json` | New — mode 5 `variants` root | MINOR | +| `component-examples.schema.json` | New — mode 5 `examples` root | MINOR | +| `components-api.schema.json` | New — mode 4 `api` root, wraps `component-api.schema.json` in `{ components: {...} }` | MINOR | +| `components-variants.schema.json` | New — mode 4 `variants` root | MINOR | +| `components-examples.schema.json` | New — mode 4 `examples` root | MINOR | +| `root.schema.json` | Extend the top-level `oneOf` to include all six new refs alongside the existing two | MINOR | + +**Example — new shape** (`schema/component-api.schema.json`): +```yaml +$schema: "http://json-schema.org/draft-07/schema#" +title: "Specs Component API Concern Schema" +description: "The api concern subset of a component: title, anatomy, props, metadata." +type: object +properties: + title: { $ref: "component.schema.json#/definitions/Component/properties/title" } + anatomy: { $ref: "component.schema.json#/definitions/Anatomy" } + props: { $ref: "component.schema.json#/definitions/Props" } + metadata: { $ref: "component.schema.json#/definitions/Metadata" } + subcomponents: + type: object + additionalProperties: { $ref: "#" } # recursive, same api-only shape +required: [title, props, metadata] +additionalProperties: false +``` + +**Example — set wrapper** (`schema/components-api.schema.json`, mirrors `components.schema.json`): +```yaml +type: object +properties: + components: + type: object + patternProperties: + "^[a-zA-Z0-9_-]+$": { $ref: "component-api.schema.json" } + additionalProperties: false + metadata: + type: object + properties: + generatedAt: { type: string, format: date-time } + componentCount: { type: integer } + concern: { const: "api" } +required: [components] +additionalProperties: false +``` + +### Notes + +- `variants` and `examples` schemas follow the same two-file pattern (singular + set), substituting the relevant `$ref`s and `concern` const per the field mapping in Context. +- The library-wide `metadata` block (`generatedAt`, `componentCount`, `concern`) at mode-4's root is *not* the same as `Component`'s per-component `Metadata` type — it's new, manifest-level metadata scoped to these six schemas only, not added to `Metadata` itself. +- `additionalProperties: false` mirrors the strictness already present in `components.schema.json`. + +--- + +## Type ↔ Schema Impact + +- **Symmetric**: Yes. +- **Parity check**: `ComponentApi` ↔ `component-api.schema.json`; `ComponentVariants` ↔ `component-variants.schema.json`; `ComponentExamples` ↔ `component-examples.schema.json`. Each "set" schema has no independent type — it's structurally `Record` etc., expressed the same way `components.schema.json` has no separate type from `component.schema.json`. + +--- + +## Downstream Impact + +| Consumer | Impact | Action required | +|----------|--------|-----------------| +| `specs-cli` | Concern-split output (modes 4/5) can now be validated against a real schema | Point `FileManifest` at the matching new schema URL per mode/concern when populating `metadata.schema` | +| `specs-from-figma` | None | No change — it produces the full `Component` object; splitting happens in `specs-cli` | +| `specs-plugin-2` | None | No change — plugin output is not concern-split | + +--- + +## Semver Decision + +**Version bump**: none beyond the active release target — lands within `0.28.0` (the version `release/schema-0.28.0+cli-0.25.0` already targets for this cycle), classified `MINOR` relative to the prior published version. + +**Justification**: Every change is additive — six new schema files and three new types. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types are MINOR. This ADR does not introduce a further bump on top of the release branch's reserved version. + +--- + +## Consequences + +- Concern-split output (`--split-concerns`, both library-wide and per-component modes) has a real, addressable JSON Schema for the first time — `root.schema.json`'s `oneOf` grows from 2 to 8 entries. +- `specs-cli` must be updated to select the correct one of eight schema URLs per output mode/concern when populating `metadata.schema.url` — this is tracked as `specs-cli` follow-up work, not part of this ADR's schema-package changes. diff --git a/adr/INDEX.md b/adr/INDEX.md index 63f8d11..d45d64f 100644 --- a/adr/INDEX.md +++ b/adr/INDEX.md @@ -4,6 +4,7 @@ | # | Title | Highlights | |---|-------|------------| +| 061 | Schema Entry Points for Concern-Split Output | | | 059 | Border Style and Dash Pattern — `borderStyle` and `borderDashPattern` on `Styles` | | | 058 | Wrapper Collapse Config Flag — `processing.wrapperCollapse` | | | 057 | Fix `Metadata.generator.version` type: `number` → `string` | | diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c87d6a0..2a396a1 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to `@directededges/specs-cli` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.25.0] - Unreleased + +### Added + +### Changed + +### Removed + + ## [0.24.0] - 2026-07-04 `specs transform` gains two new transformers — `react` and `stories` — that scaffold a working React component and a matching Storybook page directly from the spec, plus a `--components` filter to scope a run to specific components. The `contract` and `css` transformers pick up complementary additions (slot visibility rules, structural CSS fixes) to support the new component scaffolding. Generated filenames are now prefixed with the component name for clarity outside the folder tree — a breaking change for any tooling that hardcodes the old unprefixed filenames. diff --git a/packages/cli/package.json b/packages/cli/package.json index 94cbc15..f689f1a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@directededges/specs-cli", - "version": "0.24.0", + "version": "0.25.0", "description": "Command-line interface for Specs design system operations", "type": "module", "main": "./dist/index.js", diff --git a/packages/schema/CHANGELOG.md b/packages/schema/CHANGELOG.md index 7de72d1..ec588b2 100644 --- a/packages/schema/CHANGELOG.md +++ b/packages/schema/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to the Specs schema will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.28.0] - Unreleased + +### Added + +- `ComponentApi`, `ComponentVariants`, `ComponentExamples` — concern-split subsets of `Component`, each with a matching schema (`component-api`, `component-variants`, `component-examples`) and library-wide set wrapper (`components-api`, `components-variants`, `components-examples`); validates `specs-cli`'s `--split-concerns` output + +### Changed + +### Removed + + ## [0.27.0] - 2026-07-01 Dashed strokes are now first-class, typed data instead of falling through as an unrecognized style, and subcomponents carry enough Figma node identity for reverse-direction tools to resolve them back to a canvas location without a side-channel lookup. diff --git a/packages/schema/schema/component-api.schema.json b/packages/schema/schema/component-api.schema.json new file mode 100644 index 0000000..a97ddde --- /dev/null +++ b/packages/schema/schema/component-api.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component API Concern Schema", + "description": "The api concern subset of a component: title, anatomy, props, metadata. Produced by specs-cli's --split-concerns output (api.yaml files) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentApi": { + "type": "object", + "properties": { + "title": { "$ref": "component.schema.json#/definitions/Component/properties/title" }, + "anatomy": { "$ref": "component.schema.json#/definitions/Anatomy" }, + "props": { "$ref": "component.schema.json#/definitions/Props" }, + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "subcomponents": { + "type": "object", + "description": "The api concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentApi" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "required": ["title", "anatomy"], + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentApi" +} diff --git a/packages/schema/schema/component-examples.schema.json b/packages/schema/schema/component-examples.schema.json new file mode 100644 index 0000000..150b904 --- /dev/null +++ b/packages/schema/schema/component-examples.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component Examples Concern Schema", + "description": "The examples concern subset of a component: slotContentExamples, instanceExamples, metadata. Produced by specs-cli's --split-concerns output (examples.yaml files), omitted for components with no example data (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentExamples": { + "type": "object", + "properties": { + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "slotContentExamples": { + "type": "object", + "description": "Named slot-content examples for this component.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component.schema.json#/definitions/SlotContent" } + }, + "additionalProperties": false + }, + "instanceExamples": { "$ref": "component.schema.json#/definitions/InstanceExamples" }, + "subcomponents": { + "type": "object", + "description": "The examples concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentExamples" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentExamples" +} diff --git a/packages/schema/schema/component-variants.schema.json b/packages/schema/schema/component-variants.schema.json new file mode 100644 index 0000000..3898cb7 --- /dev/null +++ b/packages/schema/schema/component-variants.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component Variants Concern Schema", + "description": "The variants concern subset of a component: default variant, variants, invalidVariantCombinations, metadata. Produced by specs-cli's --split-concerns output (variants.yaml files) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentVariants": { + "type": "object", + "properties": { + "default": { "$ref": "component.schema.json#/definitions/Variant" }, + "variants": { "$ref": "component.schema.json#/definitions/Variants" }, + "invalidVariantCombinations": { + "type": "array", + "description": "Non-default prop values that when used in combination result in an invalid component state.", + "items": { "$ref": "component.schema.json#/definitions/PropConfigurations" } + }, + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "subcomponents": { + "type": "object", + "description": "The variants concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentVariants" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "required": ["default"], + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentVariants" +} diff --git a/packages/schema/schema/components-api.schema.json b/packages/schema/schema/components-api.schema.json new file mode 100644 index 0000000..1e213de --- /dev/null +++ b/packages/schema/schema/components-api.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components API Concern Set Schema", + "description": "A library-wide api-concern manifest: a set of named components, each conforming to the Specs component-api concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide api.yaml) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-api.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "api", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/components-examples.schema.json b/packages/schema/schema/components-examples.schema.json new file mode 100644 index 0000000..0263dca --- /dev/null +++ b/packages/schema/schema/components-examples.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components Examples Concern Set Schema", + "description": "A library-wide examples-concern manifest: a set of named components, each conforming to the Specs component-examples concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide examples.yaml), omitted when no component has example data (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-examples.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "examples", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/components-variants.schema.json b/packages/schema/schema/components-variants.schema.json new file mode 100644 index 0000000..a5c47bb --- /dev/null +++ b/packages/schema/schema/components-variants.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components Variants Concern Set Schema", + "description": "A library-wide variants-concern manifest: a set of named components, each conforming to the Specs component-variants concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide variants.yaml) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-variants.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "variants", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/root.schema.json b/packages/schema/schema/root.schema.json index c6778a3..642821a 100644 --- a/packages/schema/schema/root.schema.json +++ b/packages/schema/schema/root.schema.json @@ -5,6 +5,12 @@ "version": "0.12.0", "oneOf": [ { "$ref": "component.schema.json" }, - { "$ref": "components.schema.json" } + { "$ref": "components.schema.json" }, + { "$ref": "component-api.schema.json" }, + { "$ref": "component-variants.schema.json" }, + { "$ref": "component-examples.schema.json" }, + { "$ref": "components-api.schema.json" }, + { "$ref": "components-variants.schema.json" }, + { "$ref": "components-examples.schema.json" } ] } diff --git a/packages/schema/tests/ComponentConcerns.test-d.ts b/packages/schema/tests/ComponentConcerns.test-d.ts new file mode 100644 index 0000000..76349bc --- /dev/null +++ b/packages/schema/tests/ComponentConcerns.test-d.ts @@ -0,0 +1,62 @@ +import type { ComponentApi, ComponentVariants, ComponentExamples } from '../types/ComponentConcerns.js'; + +// ComponentApi — required fields only +const api: ComponentApi = { + title: 'Button', + anatomy: { root: { type: 'container' } }, +}; + +// ComponentApi — with optional props, metadata, and nested subcomponents (no metadata/subcomponents on the nested entry) +const apiFull: ComponentApi = { + title: 'Button', + anatomy: { root: { type: 'container' } }, + props: { label: { type: 'string', default: 'Click me' } }, + subcomponents: { + icon: { + title: 'Icon', + anatomy: { root: { type: 'container' } }, + }, + }, +}; + +const _nestedApi: Omit = { + title: 'Icon', + anatomy: { root: { type: 'container' } }, +}; +// @ts-expect-error — metadata is not assignable within a subcomponent's api concern +_nestedApi.metadata = {} as any; + +// ComponentVariants — required fields only +const variants: ComponentVariants = { + default: { layout: ['root'], elements: { root: {} } }, +}; + +// ComponentVariants — with optional variants and invalidVariantCombinations +const variantsFull: ComponentVariants = { + default: { layout: ['root'], elements: { root: {} } }, + variants: [], + invalidVariantCombinations: [{ disabled: true }], +}; + +// ComponentExamples — all fields optional +const examples: ComponentExamples = {}; + +// ComponentExamples — with instanceExamples and slotContentExamples +const examplesFull: ComponentExamples = { + instanceExamples: { + primary: { title: 'Primary', propConfigurations: {} }, + }, + slotContentExamples: { + composedLabel: { anatomy: { root: { type: 'container' } }, elements: { root: {} }, layout: ['root'] }, + }, +}; + +// Exported from index +import type { ComponentApi as IndexApi, ComponentVariants as IndexVariants, ComponentExamples as IndexExamples } from '../types/index.js'; +const _indexedApi: IndexApi = api; +const _indexedVariants: IndexVariants = variants; +const _indexedExamples: IndexExamples = examples; + +void apiFull; +void variantsFull; +void examplesFull; diff --git a/packages/schema/types/ComponentConcerns.ts b/packages/schema/types/ComponentConcerns.ts new file mode 100644 index 0000000..8f35049 --- /dev/null +++ b/packages/schema/types/ComponentConcerns.ts @@ -0,0 +1,101 @@ +import { Anatomy } from './Anatomy.js'; +import { Props } from './Props.js'; +import { Variant, Variants } from './Variant.js'; +import { Metadata } from './Metadata.js'; +import { PropConfigurations } from './PropConfigurations.js'; +import { InstanceExamples } from './InstanceExample.js'; +import { SlotContent } from './SlotContent.js'; + +/** + * The `api` concern subset of a `Component` — title, anatomy, and props, the + * fields needed to describe a component's public surface. Produced by + * `specs-cli`'s `--split-concerns` output (`api.yaml` files). + * @since 0.28.0 + */ +export type ComponentApi = { + /** + * The title of the component. + */ + title: string; + + /** + * The anatomy of the component. + */ + anatomy: Anatomy; + + /** + * The properties of the component. + */ + props?: Props; + + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * The api concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; + +/** + * The `variants` concern subset of a `Component` — default variant, variants, + * and invalid variant combinations. Produced by `specs-cli`'s + * `--split-concerns` output (`variants.yaml` files). + * @since 0.28.0 + */ +export type ComponentVariants = { + /** + * The default variant of the component. + */ + default: Variant; + + /** + * The variants of the component. + */ + variants?: Variants; + + /** + * Invalid variant combinations for the component. + */ + invalidVariantCombinations?: PropConfigurations[]; + + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * The variants concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; + +/** + * The `examples` concern subset of a `Component` — instance examples and + * slot-content examples. Produced by `specs-cli`'s `--split-concerns` output + * (`examples.yaml` files), omitted for components with no example data. + * @since 0.28.0 + */ +export type ComponentExamples = { + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * Named slot-content examples for this component. + */ + slotContentExamples?: Record; + + /** + * Named instance examples (documented usages) for this component. + */ + instanceExamples?: InstanceExamples; + + /** + * The examples concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; diff --git a/packages/schema/types/index.ts b/packages/schema/types/index.ts index 22d14aa..2a9845f 100644 --- a/packages/schema/types/index.ts +++ b/packages/schema/types/index.ts @@ -14,6 +14,7 @@ export type { Variant, Variants } from './Variant.js'; export type { Metadata } from './Metadata.js'; export type { Subcomponent, Subcomponents, SubcomponentSource } from './Subcomponent.js'; export type { InstanceExample, InstanceExamples } from './InstanceExample.js'; +export type { ComponentApi, ComponentVariants, ComponentExamples } from './ComponentConcerns.js'; // Element and structure types export type { Element, Elements, ElementType } from './Element.js';