Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
nodeLinker: node-modules
packageExtensions:
"@patternfly/patternfly-doc-core@*":
peerDependenciesMeta:
astro:
optional: true
"@patternfly/react-icons":
optional: true
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@patternfly/react-code-editor": "^6.5.1",
"@patternfly/react-core": "^6.5.1",
"@patternfly/react-table": "^6.5.1",
"@project-felt/ai-guidelines": "github:project-felt/ai-guidelines",
"monaco-editor": "0.54.0"
}
}
2 changes: 1 addition & 1 deletion packages/documentation-framework/scripts/cli/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function getSource(options) {
async function generate(options) {
const start = new Date();
console.log('write source files to patternfly-docs/generated');
const sourceMDWithOptions = (glob, source, ignore) => sourceMD(glob, source, ignore, options._name);
const sourceMDWithOptions = (glob, source, ignore, mdOptions) => sourceMD(glob, source, ignore, options._name, mdOptions);
getSource(options)(sourceMDWithOptions, sourceProps, sourceFunctionDocs);
await waitForProps();
processMD();
Expand Down
40 changes: 31 additions & 9 deletions packages/documentation-framework/scripts/md/parseMD.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ const globs = {
md: [],
};

function toReactComponent(mdFilePath, source, buildMode) {
function toReactComponent(mdFilePath, source, buildMode, { frontmatterDefaults, frontmatterMapping } = {}) {
// vfiles allow for nicer error messages and have native `unified` support
const vfile = toVfile.readSync(mdFilePath);

// Normalize void HTML elements to self-closing for MDX compatibility
const voidElements = 'area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr';
const voidTagRegex = new RegExp(`<(${voidElements})(\\s[^>]*?)\\s*(?<!/)>`, 'g');
vfile.contents = String(vfile.contents).replace(voidTagRegex, '<$1$2 />');

const relPath = path.relative(path.join(process.cwd(), '../..'), vfile.path).split(path.sep).join(path.posix.sep);

let jsx;
Expand All @@ -47,6 +52,23 @@ function toReactComponent(mdFilePath, source, buildMode) {
}
frontmatter = yaml.load(yamlNode.value);

// Apply frontmatter mapping (e.g., { title: "id" } maps title -> id)
if (frontmatterMapping) {
Object.entries(frontmatterMapping).forEach(([fromKey, toKey]) => {
if (frontmatter[fromKey] !== undefined && frontmatter[toKey] === undefined) {
frontmatter[toKey] = frontmatter[fromKey];
}
});
}
// Apply frontmatter defaults (e.g., { section: "AI" })
if (frontmatterDefaults) {
Object.entries(frontmatterDefaults).forEach(([key, value]) => {
if (frontmatter[key] === undefined) {
frontmatter[key] = value;
}
});
}

// Fail early
if (!frontmatter.id) {
file.fail('id attribute is required in frontmatter for PatternFly docs');
Expand Down Expand Up @@ -276,7 +298,7 @@ async function sourcePropsFile(file) {
});
}

function sourceMDFile(file, source, buildMode) {
function sourceMDFile(file, source, buildMode, options) {
if (path.basename(file).startsWith('_')) {
return;
}
Expand All @@ -285,7 +307,7 @@ function sourceMDFile(file, source, buildMode) {
if (source === 'design-guidelines' && file.includes('/accessibility/')) {
return;
}
const { jsx, pageData, outPath } = toReactComponent(file, source, buildMode);
const { jsx, pageData, outPath } = toReactComponent(file, source, buildMode, options);

if (jsx) {
fs.outputFileSync(outPath, jsx);
Expand Down Expand Up @@ -352,12 +374,12 @@ module.exports = {
async waitForProps() {
await Promise.all(pendingProps);
},
sourceMD(glob, source, ignore, buildMode) {
globs.md.push({ glob, source, ignore, buildMode });
sourceMD(glob, source, ignore, buildMode, options) {
globs.md.push({ glob, source, ignore, buildMode, options });
},
processMD() {
globs.md.forEach(({ glob, source, ignore, buildMode }) => {
globSync(glob, { ignore }).forEach(file => sourceMDFile(file, source, buildMode));
globs.md.forEach(({ glob, source, ignore, buildMode, options }) => {
globSync(glob, { ignore }).forEach(file => sourceMDFile(file, source, buildMode, options));
});
},
sourceFunctionDocs,
Expand All @@ -373,10 +395,10 @@ module.exports = {
propWatcher.on('add', onPropFile);
propWatcher.on('change', onPropFile);
});
globs.md.forEach(({ glob, source, ignore }) => {
globs.md.forEach(({ glob, source, ignore, options }) => {
const mdWatcher = chokidar.watch(globSync(glob, { ignored: ignore, ignoreInitial: true }));
function onMDFileChange(file) {
sourceMDFile(file, source, 'start');
sourceMDFile(file, source, 'start', options);
writeIndex();
}
mdWatcher.on('add', onMDFileChange);
Expand Down
28 changes: 25 additions & 3 deletions packages/documentation-framework/scripts/md/styled-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,35 @@ const styledMdTags = [

function styledTags() {
return tree => {
visit(tree, 'element', node => {
visit(tree, 'element', (node, index, parent) => {
node.properties.className = node.properties.className || '';

if (contentStyledMdTags.includes(node.tagName)) {

const hasDataType = node.properties && (node.properties.dataType || node.properties['data-type']);
const parentHasDataType = parent && parent.properties && (parent.properties.dataType || parent.properties['data-type']);
if (contentStyledMdTags.includes(node.tagName) && !hasDataType && !parentHasDataType) {
node.properties.className += `pf-v6-c-content--${node.tagName} pf-m-editorial`;
}

// GitHub-style admonitions: > [!NOTE], > [!TIP], > [!WARNING], etc.
if (node.tagName === 'blockquote') {
const firstP = node.children && node.children.find(c => c.tagName === 'p');
if (firstP && firstP.children) {
const firstText = firstP.children.find(c => c.type === 'text');
if (firstText) {
const match = firstText.value.match(/^\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]\s*/i);
if (match) {
const type = match[1].toLowerCase();
node.properties['data-admonition'] = type;
node.properties.className += ` ws-admonition ws-admonition-${type}`;
firstText.value = firstText.value.slice(match[0].length);
if (!firstText.value.trim() && firstP.children.length === 1) {
node.children = node.children.filter(c => c !== firstP);
}
}
}
}
}

if (styledMdTags.includes(node.tagName)) {
node.properties.className += node.properties.className ? ' ' : '';
node.properties.className += `ws-${node.tagName} `;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* Styles for @project-felt/ai-guidelines content */

/* Figure base styles */
[data-content-source="ai-guidelines"] figure[data-type] {
margin: var(--pf-t--global--spacer--lg) 0;
padding: var(--pf-t--global--spacer--md);
border: var(--pf-t--global--border--width--regular) solid var(--pf-t--global--border--color--default);
border-radius: var(--pf-t--global--border--radius--medium);
background-color: var(--pf-t--global--background--color--primary--default);
text-align: center;
max-width: 700px;
margin-inline: auto;
width: 100%;
}

[data-content-source="ai-guidelines"] figure[data-type="example landscape"] {
max-width: 100%;
}

[data-content-source="ai-guidelines"] figure[data-type] img {
max-width: 100%;
height: auto;
}

[data-content-source="ai-guidelines"] figure[data-type] figcaption {
margin-top: var(--pf-t--global--spacer--sm);
font-size: var(--pf-t--global--font--size--sm);
color: var(--pf-t--global--text--color--subtle);
text-align: left;
}

[data-content-source="ai-guidelines"] figure[data-type] figcaption:first-child {
margin-top: 0;
}

/* Inline icons in lists and paragraphs */
[data-content-source="ai-guidelines"] li img,
[data-content-source="ai-guidelines"] p img {
height: 1em;
width: auto;
max-width: none;
vertical-align: middle;
}

/* Do examples — green accent */
[data-content-source="ai-guidelines"] figure[data-type="do"] {
border-color: var(--pf-t--global--color--status--success--default);
}

[data-content-source="ai-guidelines"] figure[data-type="do"] figcaption::before {
content: "Do";
display: block;
font-weight: var(--pf-t--global--font--weight--body--bold);
color: var(--pf-t--global--color--status--success--default);
margin-bottom: var(--pf-t--global--spacer--xs);
}

/* Don't examples — red accent */
[data-content-source="ai-guidelines"] figure[data-type="dont"] {
border-color: var(--pf-t--global--color--status--danger--default);
}

[data-content-source="ai-guidelines"] figure[data-type="dont"] figcaption::before {
content: "Don't";
display: block;
font-weight: var(--pf-t--global--font--weight--body--bold);
color: var(--pf-t--global--color--status--danger--default);
margin-bottom: var(--pf-t--global--spacer--xs);
}

/* Figure groups */
[data-content-source="ai-guidelines"] ul[data-type] {
list-style: none;
padding: 0;
margin: var(--pf-t--global--spacer--lg) 0;
display: grid;
gap: var(--pf-t--global--spacer--md);
}

[data-content-source="ai-guidelines"] ul[data-type="dos-donts"] {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

[data-content-source="ai-guidelines"] ul[data-type="examples"] {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

[data-content-source="ai-guidelines"] ul[data-type] > li {
list-style: none;
padding: 0;
margin: 0;
}

[data-content-source="ai-guidelines"] ul[data-type] > li > figure[data-type] {
margin: 0;
height: 100%;
}

/* GitHub-style admonitions */
[data-content-source="ai-guidelines"] .ws-admonition {
border-left-width: 4px;
padding: var(--pf-t--global--spacer--md);
margin: var(--pf-t--global--spacer--md) 0;
}

[data-content-source="ai-guidelines"] .ws-admonition::before {
display: block;
font-weight: var(--pf-t--global--font--weight--body--bold);
margin-bottom: var(--pf-t--global--spacer--xs);
}

[data-content-source="ai-guidelines"] .ws-admonition-note {
border-left-color: var(--pf-t--global--color--status--info--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-note::before {
content: "Note";
color: var(--pf-t--global--color--status--info--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-tip {
border-left-color: var(--pf-t--global--color--status--success--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-tip::before {
content: "Tip";
color: var(--pf-t--global--color--status--success--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-warning {
border-left-color: var(--pf-t--global--color--status--warning--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-warning::before {
content: "Warning";
color: var(--pf-t--global--color--status--warning--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-important {
border-left-color: var(--pf-t--global--color--status--danger--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-important::before {
content: "Important";
color: var(--pf-t--global--color--status--danger--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-caution {
border-left-color: var(--pf-t--global--color--status--warning--default);
}

[data-content-source="ai-guidelines"] .ws-admonition-caution::before {
content: "Caution";
color: var(--pf-t--global--color--status--warning--default);
}

/* Internal Red Hat links — lock icon + sr-only text */
[data-content-source="ai-guidelines"] a[href*="url.corp.redhat.com"]::after {
content: " (requires Red Hat login)";
overflow: hidden;
display: inline-block;
width: 0.85em;
height: 0.85em;
margin-left: 0.25em;
vertical-align: middle;
text-indent: -9999px;
background-color: currentColor;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z'/%3E%3C/svg%3E");
mask-size: contain;
mask-repeat: no-repeat;
}
2 changes: 2 additions & 0 deletions packages/documentation-framework/templates/mdx.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import './content-sources/ai-guidelines.css';

p.pf-v6-c-content--p.ws-p {
margin: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/documentation-framework/templates/mdx.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const MDXChildTemplate = ({ Component, source, toc = [], index = 0, id }) => {
);
// Create dynamic component for @reach/router
const ChildComponent = () => (
<div className={source !== 'landing-pages' ? 'pf-v6-l-flex pf-v6-m-column pf-m-nowrap-on-2xl' : ''}>
<div className={source !== 'landing-pages' ? 'pf-v6-l-flex pf-v6-m-column pf-m-nowrap-on-2xl' : ''} data-content-source={source}>
{toc.length > 1 && <TableOfContents items={toc} />}
<Stack hasGutter className={(source !== 'landing-pages' && 'ws-example-page-wrapper')}>
{InlineAlerts}
Expand Down
Loading