160 lines
6 KiB
JSON
160 lines
6 KiB
JSON
|
|
{
|
||
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
|
|
"$id": "https://whynot.design/ir/schema/component.schema.json",
|
||
|
|
"title": "whynot IR — Component Contract",
|
||
|
|
"description": "Technology-neutral contract for one component in the whynot designbook. Extracted one-way from the canonical React designbook (WHYNOT-WP-0002). Adapters consume this to scaffold stubs and detect drift; they never write back to it.",
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": false,
|
||
|
|
"required": ["name", "group", "description", "props"],
|
||
|
|
"properties": {
|
||
|
|
"name": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Canonical component name in PascalCase (e.g. \"Button\"). The Lit adapter maps this to the <wn-button> tag.",
|
||
|
|
"pattern": "^[A-Z][A-Za-z0-9]*$"
|
||
|
|
},
|
||
|
|
"tag": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Suggested custom-element tag for web-component adapters (e.g. \"wn-button\"). Advisory; an adapter owns its own naming.",
|
||
|
|
"pattern": "^[a-z][a-z0-9-]*$"
|
||
|
|
},
|
||
|
|
"group": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Grouping from the designbook manifest (e.g. \"atoms\", \"chrome\", \"form\"). Mirrors src/elements/<group>.js in the Lit adapter."
|
||
|
|
},
|
||
|
|
"description": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "One- or two-sentence purpose, sourced from the React component's .prompt.md docs."
|
||
|
|
},
|
||
|
|
"props": {
|
||
|
|
"type": "array",
|
||
|
|
"description": "Public inputs. Each prop carries its React identity AND its projection onto an HTML attribute, so attribute-driven stacks (Lit, Vue, plain HTML) are first-class.",
|
||
|
|
"items": { "$ref": "#/$defs/prop" }
|
||
|
|
},
|
||
|
|
"slots": {
|
||
|
|
"type": "array",
|
||
|
|
"description": "Named/default content slots.",
|
||
|
|
"items": { "$ref": "#/$defs/slot" }
|
||
|
|
},
|
||
|
|
"events": {
|
||
|
|
"type": "array",
|
||
|
|
"description": "Events the component emits.",
|
||
|
|
"items": { "$ref": "#/$defs/event" }
|
||
|
|
},
|
||
|
|
"variants": {
|
||
|
|
"type": "array",
|
||
|
|
"description": "Variant axes — each axis is a named dimension with discrete values (e.g. axis \"variant\" = [primary, secondary]). Usually derived from an enum prop.",
|
||
|
|
"items": { "$ref": "#/$defs/variantAxis" }
|
||
|
|
},
|
||
|
|
"docsRef": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Path (relative to repo root) to the source docs in designbook/, e.g. designbook/components/atoms/Button/Button.prompt.md."
|
||
|
|
},
|
||
|
|
"exemplarRef": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Path (relative to repo root) to the reference render under ir/exemplars/, e.g. ir/exemplars/Button.html. Parity (T08) diffs the adapter's render against this."
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"$defs": {
|
||
|
|
"prop": {
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": false,
|
||
|
|
"required": ["name", "type", "attribute"],
|
||
|
|
"properties": {
|
||
|
|
"name": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "React prop name, camelCase (e.g. \"iconEnd\")."
|
||
|
|
},
|
||
|
|
"type": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Neutral type. \"enum\" pairs with `enum`. Non-attribute-portable shapes use \"object\", \"function\", or \"node\" and MUST set portable:false.",
|
||
|
|
"enum": ["string", "number", "boolean", "enum", "object", "function", "node"]
|
||
|
|
},
|
||
|
|
"attribute": {
|
||
|
|
"description": "HTML attribute name an attribute-driven adapter binds this prop to (kebab-case), e.g. \"icon-end\". `false` means the prop is intentionally not exposed as an attribute (property-only or non-portable).",
|
||
|
|
"oneOf": [
|
||
|
|
{ "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
||
|
|
{ "type": "boolean", "const": false }
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"enum": {
|
||
|
|
"type": "array",
|
||
|
|
"description": "Allowed values when type is \"enum\".",
|
||
|
|
"items": { "type": "string" }
|
||
|
|
},
|
||
|
|
"default": {
|
||
|
|
"description": "Default value as authored in the React source. Type matches `type`."
|
||
|
|
},
|
||
|
|
"required": {
|
||
|
|
"type": "boolean",
|
||
|
|
"default": false,
|
||
|
|
"description": "Whether the consumer must supply this prop."
|
||
|
|
},
|
||
|
|
"portable": {
|
||
|
|
"type": "boolean",
|
||
|
|
"default": true,
|
||
|
|
"description": "False marks props that do not map cleanly to an HTML attribute (objects, render props, callbacks). Adapters MUST surface non-portable props as drift, never silently drop them (see open risks)."
|
||
|
|
},
|
||
|
|
"description": {
|
||
|
|
"type": "string"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"allOf": [
|
||
|
|
{
|
||
|
|
"if": { "properties": { "type": { "const": "enum" } } },
|
||
|
|
"then": { "required": ["enum"] }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"slot": {
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": false,
|
||
|
|
"required": ["name"],
|
||
|
|
"properties": {
|
||
|
|
"name": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Slot name; use \"default\" for the unnamed default slot."
|
||
|
|
},
|
||
|
|
"description": { "type": "string" },
|
||
|
|
"required": { "type": "boolean", "default": false }
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"event": {
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": false,
|
||
|
|
"required": ["name"],
|
||
|
|
"properties": {
|
||
|
|
"name": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Emitted event name as dispatched by the component, e.g. \"wn-dismiss\"."
|
||
|
|
},
|
||
|
|
"description": { "type": "string" },
|
||
|
|
"detail": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Free-text description of the event's detail payload shape."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"variantAxis": {
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": false,
|
||
|
|
"required": ["axis", "values"],
|
||
|
|
"properties": {
|
||
|
|
"axis": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Name of the variant dimension, usually the driving prop name (e.g. \"variant\", \"size\")."
|
||
|
|
},
|
||
|
|
"values": {
|
||
|
|
"type": "array",
|
||
|
|
"minItems": 1,
|
||
|
|
"items": { "type": "string" },
|
||
|
|
"description": "Discrete values along this axis."
|
||
|
|
},
|
||
|
|
"default": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Default value for the axis."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|