diff --git a/jest.config.ts b/jest.config.ts index e702870..b4cdbe0 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -20,7 +20,7 @@ const config: Config = { }, setupFilesAfterEnv: ['/test.setup.ts'], transformIgnorePatterns: [ - '/node_modules/(?!(change-case|@?nanostores|react-docgen|strip-indent)/)', + '/node_modules/(?!(change-case|@?nanostores|react-docgen|strip-indent|@patternfly/react-icons)/)', ], } diff --git a/src/components/AutoLinkHeader.tsx b/src/components/AutoLinkHeader.tsx new file mode 100644 index 0000000..d2608e5 --- /dev/null +++ b/src/components/AutoLinkHeader.tsx @@ -0,0 +1,56 @@ +import { Flex, FlexItem, Content, Button } from '@patternfly/react-core' +import LinkIcon from '@patternfly/react-icons/dist/esm/icons/link-icon' +import { slugger } from '../utils/slugger' +import { css } from '@patternfly/react-styles' + +interface AutoLinkHeaderProps extends React.HTMLProps { + id?: string + headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' + children: React.ReactNode + metaText?: React.ReactNode + className?: string +} + +export const AutoLinkHeader = ({ + id, + headingLevel, + children, + metaText, + className, +}: AutoLinkHeaderProps) => { + const slug = id || slugger(children) + + return ( + + + + + {children} + + + {metaText && {metaText}} + + ) +} diff --git a/src/components/CSSSearch.tsx b/src/components/CSSSearch.tsx new file mode 100644 index 0000000..8639adb --- /dev/null +++ b/src/components/CSSSearch.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react' +import { SearchInput } from '@patternfly/react-core' + +interface CSSSearchProps { + getDebouncedFilteredRows: (value: string) => void + 'aria-label'?: string + placeholder?: string +} + +export const CSSSearch: React.FC = ({ + getDebouncedFilteredRows, + 'aria-label': ariaLabel = 'Filter CSS Variables', + placeholder = 'Filter CSS Variables', +}: CSSSearchProps) => { + const [filterValue, setFilterValue] = useState('') + + const onFilterChange = ( + _event: React.FormEvent, + value: string, + ) => { + setFilterValue(value) + getDebouncedFilteredRows(value) + } + + return ( + + ) +} diff --git a/src/components/CSSTable.astro b/src/components/CSSTable.astro new file mode 100644 index 0000000..0707dde --- /dev/null +++ b/src/components/CSSTable.astro @@ -0,0 +1,33 @@ +--- +import { CSSTable as CSSTableComponent } from './CSSTable' +import { AutoLinkHeader } from './AutoLinkHeader' +import { Stack, StackItem } from '@patternfly/react-core' + +const { cssPrefix } = Astro.props +--- + +{ + cssPrefix?.length > 0 && ( + + + + CSS variables + + + + {cssPrefix.map((prefix: string, index: number) => ( + + 1} + cssPrefix={prefix} + client:only="react" + /> + + ))} + + ) +} diff --git a/src/components/CSSTable.tsx b/src/components/CSSTable.tsx new file mode 100644 index 0000000..0d2e1a6 --- /dev/null +++ b/src/components/CSSTable.tsx @@ -0,0 +1,268 @@ +import { debounce, List, ListItem, Stack } from '@patternfly/react-core' +import { Table, Thead, Th, Tr, Tbody, Td } from '@patternfly/react-table' +import { useState } from 'react' +import LevelUpAltIcon from '@patternfly/react-icons/dist/esm/icons/level-up-alt-icon' +import * as tokensModule from '@patternfly/react-tokens/dist/esm/componentIndex' +import React from 'react' +import { CSSSearch } from './CSSSearch' +import { AutoLinkHeader } from './AutoLinkHeader' + +type Value = { + name: string + value: string + values?: string[] +} + +type FileList = { + [key: string]: { + name: string + value: string + values?: Value[] + } +} + +type List = { + selector: string + property: string + token: string + value: string + values?: string[] +} + +type FilteredRows = { + cells: React.ReactNode[] + isOpen?: boolean + details?: { parent: number; fullWidth: boolean; data: React.ReactNode } +} + +interface CSSTableProps extends React.HTMLProps { + cssPrefix: string + hideSelectorColumn?: boolean + selector?: string + debounceLength?: number + autoLinkHeader?: boolean +} + +const isColorRegex = /^(#|rgb)/ +const mappingAsList = (property: string, values: string[]) => ( + + {property} + {values.map((entry: string) => ( + }> + {entry} + + ))} + +) + +const flattenList = (files: FileList[]) => { + const list = [] as List[] + files.forEach((file) => { + Object.entries(file).forEach(([selector, values]) => { + if (values !== undefined) { + Object.entries(values).forEach(([key, val]) => { + if (typeof val === 'object' && val !== null && 'name' in val) { + const v = val as unknown as Value + list.push({ + selector, + property: v.name, + token: key, + value: v.value, + values: v.values, + }) + } + }) + } + }) + }) + return list +} + +export const CSSTable: React.FunctionComponent = ({ + cssPrefix, + hideSelectorColumn = false, + selector, + debounceLength = 500, + autoLinkHeader, +}) => { + const prefixToken = cssPrefix.replace('pf-v6-', '').replace(/-+/g, '_') + + const applicableFiles = Object.entries(tokensModule) + .filter(([key, _val]) => prefixToken === key) + .sort(([key1], [key2]) => key1.localeCompare(key2)) + .map(([_key, val]) => { + if (selector) { + return { + selector: (val as Record)[selector], + } + } + return val + }) + + const flatList = flattenList(applicableFiles as any) + + const getFilteredRows = (searchRE?: RegExp) => { + const newFilteredRows = [] as FilteredRows[] + let rowNumber = -1 + flatList.forEach((row) => { + const { selector, property, value, values } = row + const passes = + !searchRE || + searchRE.test(selector) || + searchRE.test(property) || + searchRE.test(value) || + (values && searchRE.test(JSON.stringify(values))) + if (passes) { + const rowKey = `${selector}_${property}` + const isColor = isColorRegex.test(value) + const cells = [ + hideSelectorColumn ? [] : [selector], + property, +
+
+ {isColor && ( +
+ +
+ )} +
+ {isColor && '(In light theme)'} {value} +
+
+
, + ] + newFilteredRows.push({ + isOpen: values ? false : undefined, + cells, + details: values + ? { + parent: rowNumber, + fullWidth: true, + data: mappingAsList(property, values), + } + : undefined, + }) + rowNumber += 1 + if (values) { + rowNumber += 1 + } + } + }) + return newFilteredRows + } + + const INITIAL_REGEX = /.*/ + const [searchRE, setSearchRE] = useState(INITIAL_REGEX) + const [rows, setRows] = useState(getFilteredRows(searchRE)) + + const hasPrefixToRender = !(typeof cssPrefix === 'undefined') + + const onCollapse = ( + _event: React.MouseEvent, + rowKey: number, + isOpen: boolean, + ) => { + const collapseAll = rowKey === undefined + let newRows = Array.from(rows) + + if (collapseAll) { + newRows = newRows.map((r) => + r.isOpen === undefined ? r : { ...r, isOpen }, + ) + } else { + newRows[rowKey] = { ...newRows[rowKey], isOpen } + } + setRows(newRows) + } + + const getDebouncedFilteredRows = debounce((value) => { + const newSearchRE = new RegExp(value, 'i') + setSearchRE(newSearchRE) + setRows(getFilteredRows(newSearchRE)) + }, debounceLength) + + return ( + + {hasPrefixToRender && ( + <> + {autoLinkHeader && ( + {`Prefixed with '${cssPrefix}'`} + )} + + + + + {!hideSelectorColumn && ( + + + + )} + + + + + {!hideSelectorColumn ? ( + rows.map((row, rowIndex: number) => ( + + + + + + + {row.details ? ( + + {!row.details.fullWidth ? + + ) : null} + + )) + ) : ( + + {rows.map((row, rowIndex: number) => ( + + + + + ))} + + )} +
+ SelectorVariableValue
+ {row.cells[0]}{row.cells[1]}{row.cells[2]}
: null} + + {row.details.data} +
{row.cells[0]}{row.cells[1]}
+ + )} +
+ ) +} diff --git a/src/components/__tests__/CSSTable.test.tsx b/src/components/__tests__/CSSTable.test.tsx new file mode 100644 index 0000000..0b6d151 --- /dev/null +++ b/src/components/__tests__/CSSTable.test.tsx @@ -0,0 +1,139 @@ +import { render, screen, waitFor } from '@testing-library/react' +import { CSSTable } from '../CSSTable' +import userEvent from '@testing-library/user-event' + +jest.mock('@patternfly/react-tokens/dist/esm/componentIndex', () => ({ + test: { + '.test-selector': { + '--test-property': { + name: 'test-property', + value: 'test-value', + }, + '--another-property': { + name: 'another-property', + value: '#123456', + }, + }, + '.another-selector': { + '--list-property': { + name: 'list-property', + value: 'list-item-1', + values: ['list-item-1', 'list-item-2'], + }, + }, + }, + 'pf-v6-empty': {}, +})) + +it('renders without crashing', () => { + render() + expect( + screen.getByRole('grid', { + name: /CSS Variables prefixed with pf-v6-test/i, + }), + ).toBeInTheDocument() +}) + +it('renders the correct table headers when hideSelectorColumn is false (default)', () => { + render() + expect(screen.getByText('Selector')).toBeInTheDocument() + expect(screen.getByText('Variable')).toBeInTheDocument() + expect(screen.getByText('Value')).toBeInTheDocument() +}) + +it('renders the correct table headers when hideSelectorColumn is true', () => { + render() + expect(screen.queryByText('Selector')).not.toBeInTheDocument() + expect(screen.getByText('Variable')).toBeInTheDocument() + expect(screen.getByText('Value')).toBeInTheDocument() +}) + +it('renders the correct data rows', () => { + render() + expect( + screen.getByRole('row', { + name: '.test-selector test-property test-value', + }), + ).toBeInTheDocument() + expect( + screen.getByRole('row', { + name: '.test-selector another-property (In light theme) #123456', + }), + ).toBeInTheDocument() + expect( + screen.getByRole('row', { + name: 'Details .another-selector list-property list-item-1', + }), + ).toBeInTheDocument() +}) + +it('renders the "Prefixed with" header when autoLinkHeader is true', () => { + render() + expect( + screen.getByRole('heading', { + name: /Prefixed with 'pf-v6-test'/i, + level: 3, + }), + ).toBeInTheDocument() +}) + +it('does not render the "Prefixed with" header when autoLinkHeader is false (default)', () => { + render() + expect( + screen.queryByRole('heading', { + name: /Prefixed with 'pf-v6-test'/i, + level: 3, + }), + ).not.toBeInTheDocument() +}) + +it('filters rows based on search input', async () => { + const user = userEvent.setup() + render() + const searchInput = screen.getByPlaceholderText('Filter CSS Variables') + + expect( + screen.getByRole('row', { + name: '.test-selector test-property test-value', + }), + ).toBeInTheDocument() + expect( + screen.getByRole('row', { + name: 'Details .another-selector list-property list-item-1', + }), + ).toBeInTheDocument() + + await user.type(searchInput, 'test') + await waitFor(() => { + // row doesn't seem to work here for whatever reason + expect(screen.getAllByText('.test-selector')).toHaveLength(2) + expect(screen.getByText('test-property')).toBeInTheDocument() + expect(screen.queryByText('.another-selector')).not.toBeInTheDocument() + expect(screen.queryByText('list-property')).not.toBeInTheDocument() + }) + + await user.clear(searchInput) + await user.type(searchInput, 'list') + await waitFor(() => { + expect(screen.queryByText('.test-selector')).not.toBeInTheDocument() + expect(screen.queryByText('test-property')).not.toBeInTheDocument() + expect(screen.getByText('.another-selector')).toBeInTheDocument() + expect(screen.getAllByText('list-property')).toHaveLength(2) + }) +}) + +it('handles the case where the cssPrefix does not exist in tokensModule', () => { + render() + expect(screen.queryByRole('grid')).toBeInTheDocument() // Table should still render, but be empty + expect(screen.queryByText('.test-selector')).not.toBeInTheDocument() +}) + +it('renders correctly when hideSelectorColumn is true and there are rows', () => { + render() + expect(screen.queryByText('.test-selector')).not.toBeInTheDocument() + expect( + screen.getByRole('row', { + name: 'test-property', + }), + ).toBeInTheDocument() +}) diff --git a/src/content.config.ts b/src/content.config.ts index 6f02817..5e0a1c5 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -17,8 +17,8 @@ function defineContent(contentObj: CollectionDefinition) { // TODO: Expand for other packages that remain under the react umbrella (Table, CodeEditor, etc) const tabMap: any = { 'react-component-docs': 'react', - 'core-component-docs': 'html' - }; + 'core-component-docs': 'html', + } return defineCollection({ loader: glob({ base: dir, pattern }), @@ -28,7 +28,14 @@ function defineContent(contentObj: CollectionDefinition) { subsection: z.string().optional(), title: z.string().optional(), propComponents: z.array(z.string()).optional(), - tab: z.string().optional().default(tabMap[name]) + tab: z.string().optional().default(tabMap[name]), + cssPrefix: z + .union([ + z.string().transform((val) => [val]), + z.array(z.string()), + z.null().transform(() => undefined), + ]) + .optional(), }), }) } diff --git a/src/layouts/Main.astro b/src/layouts/Main.astro index e7ea14f..2913255 100644 --- a/src/layouts/Main.astro +++ b/src/layouts/Main.astro @@ -1,11 +1,11 @@ --- import '@patternfly/patternfly/patternfly.css' +import '../styles/global.scss' import { ClientRouter } from 'astro:transitions' import Page from '../components/Page.astro' import Masthead from '../components/Masthead.astro' import Navigation from '../components/Navigation.astro' - --- @@ -27,11 +27,15 @@ import Navigation from '../components/Navigation.astro' diff --git a/src/pages/[section]/[...page].astro b/src/pages/[section]/[...page].astro index 31d090b..7ea8b78 100644 --- a/src/pages/[section]/[...page].astro +++ b/src/pages/[section]/[...page].astro @@ -1,11 +1,12 @@ --- import { getCollection, render } from 'astro:content' -import { Title } from '@patternfly/react-core' +import { Title, Stack, StackItem } from '@patternfly/react-core' import MainLayout from '../../layouts/Main.astro' import { content } from '../../content' import { kebabCase } from 'change-case' import { componentTabs } from '../../globals' import PropsTables from '../../components/PropsTables.astro' +import CSSTable from '../../components/CSSTable.astro' export async function getStaticPaths() { const collections = await Promise.all( @@ -20,16 +21,18 @@ export async function getStaticPaths() { entry, title: entry.data.title, propComponents: entry.data.propComponents, + cssPrefix: entry.data.cssPrefix, }, })) } -const { entry, propComponents } = Astro.props +const { entry, propComponents, cssPrefix } = Astro.props const { title, id, section } = entry.data const { Content } = await render(entry) -if(section === 'components') { // if section is components, rewrite to first tab content - return Astro.rewrite(`/components/${kebabCase(id)}/${componentTabs[id][0]}`); +if (section === 'components') { + // if section is components, rewrite to first tab content + return Astro.rewrite(`/components/${kebabCase(id)}/${componentTabs[id][0]}`) } --- @@ -37,10 +40,17 @@ if(section === 'components') { // if section is components, rewrite to first tab { title && ( - {title} + {title} ) } - + + + + + + + + diff --git a/src/pages/[section]/[page]/[...tab].astro b/src/pages/[section]/[page]/[...tab].astro index 69f4288..5b64518 100644 --- a/src/pages/[section]/[page]/[...tab].astro +++ b/src/pages/[section]/[page]/[...tab].astro @@ -1,6 +1,6 @@ --- import { getCollection, render } from 'astro:content' -import { Title, PageSection } from '@patternfly/react-core' +import { Title, PageSection, Stack, StackItem } from '@patternfly/react-core' import MainLayout from '../../../layouts/Main.astro' import { content } from '../../../content' import { kebabCase } from 'change-case' @@ -27,6 +27,7 @@ import { dd, } from '../../../components/Content' import LiveExample from '../../../components/LiveExample.astro' +import CSSTable from '../../../components/CSSTable.astro' export async function getStaticPaths() { const collections = await Promise.all( @@ -62,7 +63,8 @@ export async function getStaticPaths() { return flatCol } -const { entry, propComponents } = Astro.props +const { entry, propComponents, cssPrefix } = Astro.props + const { title, id, section } = entry.data const { Content } = await render(entry) const currentPath = Astro.url.pathname @@ -127,6 +129,13 @@ const currentPath = Astro.url.pathname LiveExample, }} /> - + + + + + + + + diff --git a/src/pages/index.astro b/src/pages/index.astro index 401c06a..361ec6b 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,5 +1,4 @@ --- -import '../styles/global.scss' import MainLayout from '../layouts/Main.astro' --- diff --git a/src/styles/global.scss b/src/styles/global.scss index a6730dc..25ee5f0 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -1 +1,47 @@ -// custom global scss would go here +.circle { + height: 1em; + display: inline-block; + aspect-ratio: 1 / 1; + border-radius: 50%; + border: var(--pf-t--global--border--width--regular) solid + var(--pf-t--global--background--color--inverse--default); + box-shadow: var(--pf-t--global--box-shadow--sm); +} + +.rotate-90-deg { + transform: rotate(90deg); +} + +.ws-heading { + position: relative; +} + +.ws-heading-anchor { + color: var(--pf-t--global--icon--color--regular); + transform: translate(calc(-100% - var(--pf-t--global--spacer--xs)), -50%); + opacity: 0; + position: absolute; + left: 0; + top: 50%; + --pf-v6-c-content--a--Color: var(--pf-t--global--icon--color--regular); + --pf-v6-c-button--m-plain--PaddingInlineEnd: 0; + --pf-v6-c-button--m-plain--PaddingInlineStart: 0; + --pf-v6-c-button--MinWidth: unset; +} + +.ws-heading-anchor.pf-v6-c-button:hover, +.ws-heading-anchor.pf-v6-c-button:focus { + --pf-v6-c-button--hover--Color: var(--pf-t--global--icon--color--regular); + --pf-v6-c-button--BackgroundColor: transparent; + --pf-v6-c-content--a--hover--Color: var(--pf-t--global--icon--color--regular); +} + +.ws-heading-anchor-icon { + height: 0.75rem; + width: 0.75rem; +} + +.ws-heading:hover .ws-heading-anchor, +.ws-heading-anchor:focus { + opacity: 1; +} diff --git a/src/utils/__tests__/slugger.test.tsx b/src/utils/__tests__/slugger.test.tsx new file mode 100644 index 0000000..7b97569 --- /dev/null +++ b/src/utils/__tests__/slugger.test.tsx @@ -0,0 +1,26 @@ +import { slugger } from '../slugger' + +test('slugger', () => { + expect(slugger("Prefixed with 'pf-v6-c-alert-group'")).toBe( + 'prefixed-with-pf-v6-c-alert-group', + ) + expect(slugger(' has outer spaces ')).toBe('has-outer-spaces') + expect(slugger('Multiple spaces')).toBe('multiple--spaces') + expect(slugger('MiXeD CaSe')).toBe('mixed-case') + expect(slugger('!@#$%^&*()')).toBe('') + expect(slugger('with~tilda')).toBe('with~tilda') + expect(slugger('with !@# special')).toBe('with--special') + expect(slugger('page index')).toBe('page-') + expect(slugger('index')).toBe('') + expect(slugger('index page')).toBe('index-page') + expect(slugger('')).toBe('') + expect(slugger(null)).toBe('') + expect(slugger(undefined)).toBe('') + expect(slugger(0)).toBe('0') + expect(slugger([])).toBe('') + expect(slugger(true)).toBe('') + expect(slugger(false)).toBe('') + expect(slugger(React component)).toBe('') + expect(slugger()).toBe('') + expect(slugger([1, 2])).toBe('') +}) diff --git a/src/utils/index.ts b/src/utils/index.ts index ebb8c4f..cc321ce 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,2 @@ export * from './capitalize' +export * from './slugger' diff --git a/src/utils/slugger.ts b/src/utils/slugger.ts new file mode 100644 index 0000000..4063dc0 --- /dev/null +++ b/src/utils/slugger.ts @@ -0,0 +1,14 @@ +import React from 'react' + +// Should produce valid URLs and valid CSS ids +export const slugger = (children: React.ReactNode) => { + const value = React.Children.toArray(children) + .filter((child) => typeof child === 'string' || typeof child === 'number') + .join('') + return value + .toLowerCase() + .trim() + .replace(/index$/, '') + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9.\-~]/g, '') +} diff --git a/textContent/contribute.md b/textContent/contribute.md index 9bf5523..e2793fc 100644 --- a/textContent/contribute.md +++ b/textContent/contribute.md @@ -3,12 +3,14 @@ id: Contribute title: Contribute to PatternFly section: get-started propComponents: ['Button', 'BadgeCountObject'] +cssPrefix: pf-v6-c-about-modal-box --- -## Community contributions +## Community contributions -Thank you for your interest in contributing to PatternFly! We depend on community contributions to help our design system grow and evolve. We encourage everyone, regardless of background, to get involved. Common contributions include (but aren't limited to): -- New feature ideas. +Thank you for your interest in contributing to PatternFly! We depend on community contributions to help our design system grow and evolve. We encourage everyone, regardless of background, to get involved. Common contributions include (but aren't limited to): + +- New feature ideas. - Bug reports. - Documentation updates. @@ -20,29 +22,32 @@ If you have any ideas that don't fit into the projects outlined in this guide, p If you have skills in visual and interaction design, you can contribute to PatternFly's design by taking an existing issue or proposing a new feature, enhancement, or icon. If you are interested in any of these projects, [reach out on the patternfly-design Slack channel.](http://join.slack.com/t/patternfly/shared_invite/zt-1npmqswgk-bF2R1E2rglV8jz5DNTezMQ) -### Existing design issues +### Existing design issues The PatternFly design team is composed of visual and interaction designers who define the look and feel of the PatternFly library. The team follows an agile framework, planning their work in sprints, with a backlog that is tracked and managed via [this GitHub project board.](https://github.com/orgs/patternfly/projects/7/views/30) This board contains a list issues that are currently unassigned and waiting in the queue. If you see something here that you'd like to work on, leave a comment on the issue and a member of our team will reach out with next steps. ### New feature or enhancement + If you have an idea for a new design pattern, a new component type, or an existing feature improvement, we'd love to hear it. [Start by opening an issue in the patternfly-design repository.](https://github.com/patternfly/patternfly-design/issues) From there, a member of our team will reach out and work with you to plan and design a solution. ### New icons + We encourage designers to work with [the existing PatternFly icon set](/design-foundations/icons), which covers most common use cases. If your use case isn't covered, you can propose a new icon. To contribute a new icon, [start by opening an issue in the patternfly-design repository](https://github.com/patternfly/patternfly-design/issues) that describes your idea and why it's needed. A member of our team will reach out to you to discuss next steps. ## Code -The primary PatternFly libraries include HTML/CSS (commonly called "core") and React. If you're looking to contribute to PatternFly's codebase, these libraries are a good place to start. You can help out by taking existing issues, or creating issues for bugs and other changes. +The primary PatternFly libraries include HTML/CSS (commonly called "core") and React. If you're looking to contribute to PatternFly's codebase, these libraries are a good place to start. You can help out by taking existing issues, or creating issues for bugs and other changes. If you have any questions about these projects, you can reach out to us on our [patternfly-core](https://patternfly.slack.com/archives/C9Q224EFL) and [patternfly-react](https://patternfly.slack.com/archives/C4FM977N0) Slack channels. -### Existing development issues +### Existing development issues -To find work that has been approved, but not started, you can view open issues in our [patternfly](https://github.com/patternfly/patternfly/issues) (HTML/CSS) and [patternfly-react](https://github.com/patternfly/patternfly-react/issues) (React) repositories. If you find an issue that you'd like to work on, leave a comment and someone from our team will reach out to you with next steps. +To find work that has been approved, but not started, you can view open issues in our [patternfly](https://github.com/patternfly/patternfly/issues) (HTML/CSS) and [patternfly-react](https://github.com/patternfly/patternfly-react/issues) (React) repositories. If you find an issue that you'd like to work on, leave a comment and someone from our team will reach out to you with next steps. Be sure to view our detailed contribution instructions for both of these repositories: + - [Core contribution guidelines](https://github.com/patternfly/patternfly#guidelines-for-css-development) - [React contribution guidelines](https://github.com/patternfly/patternfly-react/blob/main/CONTRIBUTING.md#contribution-process) @@ -50,13 +55,13 @@ Be sure to view our detailed contribution instructions for both of these reposit If you believe that you've come across a PatternFly bug, alert our team, so that we can resolve the issue. To report a bug, follow these steps: -1. View the documentation for the feature, to confirm that the behavior is not functioning as intended. +1. View the documentation for the feature, to confirm that the behavior is not functioning as intended. 1. Search open issues in the [patternfly](https://github.com/patternfly/patternfly/issues) and [patternfly-react](https://github.com/patternfly/patternfly-react/issues) repositories to see if a related issue already exists. - - If the bug is present in only the React implementation of PatternFly, [create a bug issue in patternfly-react.](https://github.com/patternfly/patternfly-react/issues) - - If the bug can be seen on both the React and HTML/CSS side, [create a bug issue in patternfly](https://github.com/patternfly/patternfly/issues). + - If the bug is present in only the React implementation of PatternFly, [create a bug issue in patternfly-react.](https://github.com/patternfly/patternfly-react/issues) + - If the bug can be seen on both the React and HTML/CSS side, [create a bug issue in patternfly](https://github.com/patternfly/patternfly/issues). 1. Be sure to mention which project the bug was noticed in and if there is a deadline that the fix is needed for. -## Documentation +## Documentation Across our website, you can find PatternFly documentation that explains concepts, provides guidance, and outlines important resources for PatternFly users. Our documentation can always be improved, and we love to hear input from the people who use it. @@ -64,9 +69,10 @@ If you'd like to contribute to documentation, you can refer to our [detailed con ### Existing documentation issues -Our website documentation is contained in the [patternfly-org repository](https://github.com/patternfly/patternfly-org). If you find an issue that you'd like to work on, leave a comment and someone from our team will reach out to you with next steps. +Our website documentation is contained in the [patternfly-org repository](https://github.com/patternfly/patternfly-org). If you find an issue that you'd like to work on, leave a comment and someone from our team will reach out to you with next steps. ### Design guidelines + Our design guidelines are found across our component, layout, chart, and pattern web pages. These guides clarify usage details to help designers follow best practices to create strong UI solutions. -If you'd like to contribute to our design guidelines, you can open an issue in [patternfly-org](https://github.com/patternfly/patternfly-org) to propose a new page or updates to an existing page. From there, our team will work with you to author and publish your new content. \ No newline at end of file +If you'd like to contribute to our design guidelines, you can open an issue in [patternfly-org](https://github.com/patternfly/patternfly-org) to propose a new page or updates to an existing page. From there, our team will work with you to author and publish your new content.