Skip to content

Commit

Permalink
Improve design system component anatomies schema
Browse files Browse the repository at this point in the history
  • Loading branch information
lukewarlow committed Oct 15, 2023
1 parent 8543707 commit 68726d5
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 47 deletions.
46 changes: 41 additions & 5 deletions site/src/components/anatomy.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
import React from 'react'
import './anatomy.css'

import { anatomiesByComponent } from '../sources'
import { componentsByName } from '../sources'

const Anatomy = ({ component }) => {
const anatomy = anatomiesByComponent[component]
const anatomies = componentsByName[component]
.filter(
(component) =>
!!component.anatomy && (!Array.isArray(component.anatomy) || component.anatomy.length > 0),
)
.map((component) => {
if (Array.isArray(component.anatomy)) {
return {
sourceName: component.sourceName,
anatomy: component.anatomy,
}
}

return {
sourceName: component.sourceName,
...component.anatomy,
}
})

if (Object.keys(anatomy).length === 0) {
if (anatomies.length === 0) {
return (
<div className="empty-anatomy">
None of the {component} JSON <code>/resources</code> define an anatomy.
</div>
)
}

function Child({ name, children }) {
return (
<li key={name}>
<p>{name}</p>
{children?.length > 0 && <ul>{children?.map((child) => Child(child))}</ul>}
</li>
)
}

return (
<ul className="anatomy">
{anatomy.map(({ name }) => (
<li key={name}>{name}</li>
{anatomies.map(({ anatomy, children, sourceName }) => (
<li key={sourceName}>
<span>{sourceName}</span>
{Array.isArray(anatomy) && (
<ul>
{anatomy.map(({ name }) => (
<li key={name}>{name}</li>
))}
</ul>
)}
{!Array.isArray(anatomy) && <ul>{children?.map((child) => Child(child))}</ul>}
</li>
))}
</ul>
)
Expand Down
2 changes: 1 addition & 1 deletion site/src/pages/components/file.research.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ layout: ../../layouts/ComponentLayout.astro
---

import ComponentCoverage from '../../components/component-coverage'
import FileAnatomy from '../../components/file-anatomy'
import FileAnatomy from '../../components/file-anatomy.jsx'
import Concepts from '../../components/concepts'

## Names
Expand Down
62 changes: 62 additions & 0 deletions site/src/schemas/component-anatomy.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$id": "component-anatomy.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Anatomy",
"description": "Anatomy of UI Component definition.",
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"description": "Each named part that makes up the whole of the component.",
"properties": {
"name": { "type": "string" },
"optional": { "type": "boolean" },
"implicit": { "type": "boolean" },
"description": { "type": "string" }
},
"additionalProperties": false,
"required": ["name"]
},
"uniqueItems": true
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"description": "The name of the component as defined in the design system.",
"type": "string"
},
"description": { "type": "string" },
"children": {
"$ref": "#/$defs/children"
}
},
"required": ["name"]
}
],
"$defs": {
"children": {
"type": "array",
"items": {
"type": "object",
"description": "Each named part that makes up the whole of the component.",
"properties": {
"name": { "type": "string" },
"optional": { "type": "boolean" },
"part": { "type": "string" },
"legacyPart": { "type": "string" },
"description": { "type": "string" },
"element": { "type": "string" },
"children": {
"$ref": "#/$defs/children"
}
},
"additionalProperties": false,
"required": ["name"]
}
}
},
"required": ["name"]
}
14 changes: 2 additions & 12 deletions site/src/schemas/component.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,7 @@
},

"anatomy": {
"type": "array",
"items": {
"type": "object",
"description": "Each named part that makes up the whole of the component.",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" }
},
"required": ["name"]
},
"uniqueItems": true
"$ref": "./component-anatomy.schema.json"
},

"concepts": {
Expand All @@ -57,5 +47,5 @@
}
},

"required": ["name", "url"]
"required": ["name"]
}
13 changes: 13 additions & 0 deletions site/src/sources/chromium.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@
},
{
"name": "File",
"anatomy": {
"name": "root",
"children": [
{
"name": "file-selector-button",
"part": "::file-selector-button",
"legacyPart": "::-webkit-file-upload-button"
},
{
"name": "label"
}
]
},
"concepts": [
{
"name": "Default",
Expand Down
57 changes: 28 additions & 29 deletions site/src/sources/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ export const sources = [
componentName: componentOpenUIName,
openUIName: conceptOpenUIName,
}
})
}),
}
}),
}))

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy
export const groupBy = (items, callbackFn) => {
return items.reduce((acc, currentValue, currentIndex) => {
let groupKey = callbackFn(currentValue, currentIndex);
let groupKey = callbackFn(currentValue, currentIndex)
if (!acc[groupKey]) {
acc[groupKey] = [];
acc[groupKey] = []
}
acc[groupKey].push(currentValue);
return acc;
}, {});
acc[groupKey].push(currentValue)
return acc
}, {})
}

export const get = (obj, path, defValue) => {
Expand All @@ -91,23 +91,18 @@ export const get = (obj, path, defValue) => {
// Regex explained: https://regexr.com/58j0k
const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
// Find value
const result = pathArray.reduce(
(prevObj, key) => prevObj && prevObj[key],
obj
)
const result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
// If found value is undefined return default value; otherwise return the value
return result === undefined ? defValue : result
}

export const uniqBy = (arr, iteratee) => {
if (typeof iteratee === 'string') {
const prop = iteratee
iteratee = item => item[prop]
iteratee = (item) => item[prop]
}

return arr.filter(
(x, i, self) => i === self.findIndex(y => iteratee(x) === iteratee(y))
)
return arr.filter((x, i, self) => i === self.findIndex((y) => iteratee(x) === iteratee(y)))
}

export const sourceNames = sources.map((source) => source.name)
Expand All @@ -118,30 +113,34 @@ const componentList = sources.flatMap((source) => source.components)
export const componentOriginalNames = componentList.map((component) => component.name)
export const componentsByName = groupBy(componentList, (component) => component.openUIName)

// Anatomies
export const anatomiesByComponent = Object.fromEntries(
Object.entries(componentsByName).map(([key, value]) =>
[key, uniqBy(value.flatMap((component) => component.anatomy).filter(Boolean), "name")])
)

// Concepts
const conceptList = componentList.flatMap((component) => component.concepts).filter(Boolean)

export const openUIConceptsByComponent = Object.fromEntries(Object.entries(groupBy(conceptList, concept => concept.componentName)).map(([key, value]) => [key, groupBy(value, concept => concept.openUIName)]))
export const openUIConceptsByComponent = Object.fromEntries(
Object.entries(groupBy(conceptList, (concept) => concept.componentName)).map(([key, value]) => [
key,
groupBy(value, (concept) => concept.openUIName),
]),
)

export const conceptsByComponent = Object.fromEntries(Object.entries(groupBy(conceptList, concept => concept.componentName)).map(([key, value]) => [key, groupBy(value, concept => concept.name)]))
export const conceptsByComponent = Object.fromEntries(
Object.entries(groupBy(conceptList, (concept) => concept.componentName)).map(([key, value]) => [
key,
groupBy(value, (concept) => concept.name),
]),
)

export const getSourcesWithComponentConcept = (
componentName,
conceptName,
conceptOpenUIName = conceptName,
) =>
[...new Set(
get(conceptsByComponent, [componentName, conceptName]).filter(
(concept) => concept.name === conceptName && concept.openUIName === conceptOpenUIName,
)
.map((concept) => concept.sourceName),
)]
) => [
...new Set(
get(conceptsByComponent, [componentName, conceptName])
.filter((concept) => concept.name === conceptName && concept.openUIName === conceptOpenUIName)
.map((concept) => concept.sourceName),
),
]

// Images
export const getImagesForComponentConcept = (componentOpenUIName, conceptOpenUIName) =>
Expand Down

0 comments on commit 68726d5

Please sign in to comment.