diff --git a/src/__tests__/pages/api/__tests__/[version]/[section]/[page]/props.test.ts b/src/__tests__/pages/api/__tests__/[version]/[section]/[page]/props.test.ts new file mode 100644 index 0000000..ea5c090 --- /dev/null +++ b/src/__tests__/pages/api/__tests__/[version]/[section]/[page]/props.test.ts @@ -0,0 +1,357 @@ +import { GET } from '../../../../../../../pages/api/[version]/[section]/[page]/props' +import { getConfig } from '../../../../../../../../cli/getConfig' +import { sentenceCase } from '../../../../../../../utils/case' + +/** + * Mock getConfig to return a test configuration + */ +jest.mock('../../../../../../../../cli/getConfig', () => ({ + getConfig: jest.fn().mockResolvedValue({ + outputDir: '/mock/output/dir', + }), +})) + +/** + * Mock node:path join function + */ +const mockJoin = jest.fn((...paths: string[]) => paths.join('/')) +jest.mock('node:path', () => ({ + join: (...args: any[]) => mockJoin(...args), +})) + +/** + * Mock node:fs readFileSync function + */ +const mockReadFileSync = jest.fn() +jest.mock('node:fs', () => ({ + readFileSync: (...args: any[]) => mockReadFileSync(...args), +})) + +/** + * Mock sentenceCase utility + */ +jest.mock('../../../../../../../utils/case', () => ({ + sentenceCase: jest.fn((id: string) => + // Simple mock: convert kebab-case to Sentence Case + id + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') + ), +})) + +const mockData = { + Alert: { + name: 'Alert', + description: '', + props: [ + { + name: 'variant', + type: 'string', + description: 'Alert variant style', + }, + ], + }, + Button: { + name: 'Button', + description: '', + props: [ + { + name: 'onClick', + type: 'function', + description: 'Click handler function', + }, + ], + }, + 'Sample Data Row': { + name: 'SampleDataRow', + description: '', + props: [ + { + name: 'applications', + type: 'number', + description: null, + required: true, + }, + ], + }, + 'Dashboard Wrapper': { + name: 'DashboardWrapper', + description: '', + props: [ + { + name: 'hasDefaultBreadcrumb', + type: 'boolean', + description: 'Flag to render sample breadcrumb if custom breadcrumb not passed', + }, + ], + }, + 'Keyboard Handler': { + name: 'KeyboardHandler', + description: '', + props: [ + { + name: 'containerRef', + type: 'React.RefObject', + description: 'Reference of the container to apply keyboard interaction', + defaultValue: 'null', + }, + ], + }, +} + +beforeEach(() => { + jest.clearAllMocks() + // Reset process.cwd mock + process.cwd = jest.fn(() => '/mock/workspace') + // Reset mockReadFileSync to return default mock data + mockReadFileSync.mockReturnValue(JSON.stringify(mockData)) +}) + +it('returns props data for a valid page', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(response.headers.get('Content-Type')).toBe('application/json; charset=utf-8') + expect(body).toHaveProperty('name') + expect(body).toHaveProperty('description') + expect(body).toHaveProperty('props') + expect(body.name).toBe('Alert') + expect(Array.isArray(body.props)).toBe(true) + expect(sentenceCase).toHaveBeenCalledWith('alert') +}) + +it('converts kebab-case page name to sentence case for lookup', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'sample-data-row' }, + url: new URL('http://localhost:4321/api/v6/components/sample-data-row/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body.name).toBe('SampleDataRow') + expect(sentenceCase).toHaveBeenCalledWith('sample-data-row') +}) + +it('handles multi-word page names correctly', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'dashboard-wrapper' }, + url: new URL('http://localhost:4321/api/v6/components/dashboard-wrapper/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body.name).toBe('DashboardWrapper') + expect(sentenceCase).toHaveBeenCalledWith('dashboard-wrapper') +}) + +it('returns 404 error when props data is not found', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'nonexistent' }, + url: new URL('http://localhost:4321/api/v6/components/nonexistent/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(404) + expect(body).toHaveProperty('error') + expect(body.error).toContain('nonexistent') + expect(body.error).toContain('not found') +}) + +it('returns 400 error when page parameter is missing', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(400) + expect(body).toHaveProperty('error') + expect(body.error).toContain('Page parameter is required') +}) + +it('returns 500 error when props.json file is not found', async () => { + mockReadFileSync.mockImplementation(() => { + const error = new Error('ENOENT: no such file or directory') + ; (error as any).code = 'ENOENT' + throw error + }) + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Props data not found') + expect(body).toHaveProperty('details') + expect(body.details).toContain('ENOENT') +}) + +it('returns 500 error when props.json contains invalid JSON', async () => { + mockReadFileSync.mockReturnValue('invalid json content') + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Props data not found') + expect(body).toHaveProperty('details') +}) + +it('returns 500 error when file read throws an error', async () => { + mockReadFileSync.mockImplementation(() => { + throw new Error('Permission denied') + }) + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Props data not found') + expect(body).toHaveProperty('details') + expect(body.details).toContain('Permission denied') +}) + +it('uses default outputDir when config does not provide one', async () => { + jest.mocked(getConfig).mockResolvedValueOnce({ + content: [], + propsGlobs: [], + outputDir: '', + }) + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body).toHaveProperty('name') + expect(mockJoin).toHaveBeenCalledWith('/mock/workspace/dist', 'props.json') +}) + +it('uses custom outputDir from config when provided', async () => { + jest.mocked(getConfig).mockResolvedValueOnce({ + outputDir: '/custom/output/path', + content: [], + propsGlobs: [], + }) + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body).toHaveProperty('name') + // Verify that join was called with custom outputDir + expect(mockJoin).toHaveBeenCalledWith('/custom/output/path', 'props.json') +}) + +it('reads props.json from the correct file path', async () => { + await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/props'), + } as any) + + // Verify readFileSync was called with the correct path + expect(mockReadFileSync).toHaveBeenCalledWith('/mock/output/dir/props.json') +}) + +it('returns full props structure with all fields', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'keyboard-handler' }, + url: new URL('http://localhost:4321/api/v6/components/keyboard-handler/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body).toHaveProperty('name') + expect(body).toHaveProperty('description') + expect(body).toHaveProperty('props') + expect(Array.isArray(body.props)).toBe(true) + expect(body.props.length).toBeGreaterThan(0) + expect(body.props[0]).toHaveProperty('name') + expect(body.props[0]).toHaveProperty('type') + expect(body.props[0]).toHaveProperty('description') +}) + +it('handles props with defaultValue field', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'keyboard-handler' }, + url: new URL('http://localhost:4321/api/v6/components/keyboard-handler/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + const propWithDefault = body.props.find((p: any) => p.defaultValue !== undefined) + if (propWithDefault) { + expect(propWithDefault).toHaveProperty('defaultValue') + } +}) + +it('handles props with required field', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'sample-data-row' }, + url: new URL('http://localhost:4321/api/v6/components/sample-data-row/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + const requiredProp = body.props.find((p: any) => p.required === true) + if (requiredProp) { + expect(requiredProp.required).toBe(true) + } +}) + +it('handles components with empty props array', async () => { + const emptyPropsData = { + 'Empty Component': { + name: 'EmptyComponent', + description: '', + props: [], + }, + } + mockReadFileSync.mockReturnValueOnce(JSON.stringify(emptyPropsData)) + + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'empty-component' }, + url: new URL('http://localhost:4321/api/v6/components/empty-component/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body.name).toBe('EmptyComponent') + expect(Array.isArray(body.props)).toBe(true) + expect(body.props).toEqual([]) +}) + +it('handles request when tab is in URL path but not in params', async () => { + // Note: props.ts route is at [page] level, so tab parameter is not available + // This test verifies the route works correctly with just page parameter + const response = await GET({ + params: { version: 'v6', section: 'components', page: 'alert' }, + url: new URL('http://localhost:4321/api/v6/components/alert/react/props'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body).toHaveProperty('name') + expect(body.name).toBe('Alert') +}) diff --git a/src/__tests__/pages/api/__tests__/[version]/[section]/names.test.ts b/src/__tests__/pages/api/__tests__/[version]/[section]/names.test.ts new file mode 100644 index 0000000..23edfde --- /dev/null +++ b/src/__tests__/pages/api/__tests__/[version]/[section]/names.test.ts @@ -0,0 +1,270 @@ +import { GET } from '../../../../../../pages/api/[version]/[section]/names' +import { getConfig } from '../../../../../../../cli/getConfig' + +/** + * Mock getConfig to return a test configuration + */ +jest.mock('../../../../../../../cli/getConfig', () => ({ + getConfig: jest.fn().mockResolvedValue({ + outputDir: '/mock/output/dir', + }), +})) + +/** + * Mock node:path join function + */ +const mockJoin = jest.fn((...paths: string[]) => paths.join('/')) +jest.mock('node:path', () => ({ + join: (...args: any[]) => mockJoin(...args), +})) + +/** + * Mock node:fs readFileSync function + */ +const mockReadFileSync = jest.fn() +jest.mock('node:fs', () => ({ + readFileSync: (...args: any[]) => mockReadFileSync(...args), +})) + +const mockData = { + Alert: { + name: 'Alert', + description: '', + props: [ + { + name: 'variant', + type: 'string', + description: 'Alert variant', + }, + ], + }, + Button: { + name: 'Button', + description: '', + props: [ + { + name: 'onClick', + type: 'function', + description: 'Click handler', + }, + ], + }, + Card: { + name: 'Card', + description: '', + props: [ + { + name: 'title', + type: 'string', + description: 'Card title', + }, + ], + }, + AlertProps: { + name: 'AlertProps', + description: '', + props: [ + { + name: 'someProp', + type: 'string', + description: null, + }, + ], + }, + ButtonComponentProps: { + name: 'ButtonComponentProps', + description: '', + props: [ + { + name: 'anotherProp', + type: 'string', + description: null, + }, + ], + }, +} + +beforeEach(() => { + jest.clearAllMocks() + // Reset process.cwd mock + process.cwd = jest.fn(() => '/mock/workspace') + // Reset mockReadFileSync to return default mock data + mockReadFileSync.mockReturnValue(JSON.stringify(mockData)) +}) + +it('returns filtered component names from props.json data', async () => { + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(response.headers.get('Content-Type')).toBe('application/json; charset=utf-8') + expect(Array.isArray(body)).toBe(true) + expect(body).toContain('Alert') + expect(body).toContain('Button') + expect(body).toContain('Card') + expect(body).not.toContain('AlertProps') + expect(body).not.toContain('ButtonComponentProps') +}) + +it('filters out all keys containing "Props" case-insensitively', async () => { + const testData = { + Alert: {}, + Button: {}, + AlertProps: {}, + ALERTPROPS: {}, + alertprops: {}, + ComponentProps: {}, + SomeComponentProps: {}, + } + + mockReadFileSync.mockReturnValue(JSON.stringify(testData)) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(body).toEqual(['Alert', 'Button']) + expect(body).not.toContain('AlertProps') + expect(body).not.toContain('ALERTPROPS') + expect(body).not.toContain('alertprops') + expect(body).not.toContain('ComponentProps') + expect(body).not.toContain('SomeComponentProps') +}) + +it('returns empty array when props.json has no valid component names', async () => { + const testData = { + AlertProps: {}, + ButtonProps: {}, + ComponentProps: {}, + } + + mockReadFileSync.mockReturnValue(JSON.stringify(testData)) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(Array.isArray(body)).toBe(true) + expect(body).toEqual([]) +}) + +it('returns empty array when props.json is empty', async () => { + mockReadFileSync.mockReturnValue(JSON.stringify({})) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(Array.isArray(body)).toBe(true) + expect(body).toEqual([]) +}) + +it('returns 500 error when props.json file is not found', async () => { + mockReadFileSync.mockImplementation(() => { + const error = new Error('ENOENT: no such file or directory') + ; (error as any).code = 'ENOENT' + throw error + }) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Component names data not found') + expect(body).toHaveProperty('details') + expect(body.details).toContain('ENOENT') +}) + +it('returns 500 error when props.json contains invalid JSON', async () => { + mockReadFileSync.mockReturnValue('invalid json content') + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Component names data not found') + expect(body).toHaveProperty('details') +}) + +it('returns 500 error when file read throws an error', async () => { + mockReadFileSync.mockImplementation(() => { + throw new Error('Permission denied') + }) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(500) + expect(body).toHaveProperty('error') + expect(body.error).toBe('Component names data not found') + expect(body).toHaveProperty('details') + expect(body.details).toContain('Permission denied') +}) + +it('uses default outputDir when config does not provide one', async () => { + jest.mocked(getConfig).mockResolvedValueOnce({ + content: [], + propsGlobs: [], + outputDir: '', + }) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(Array.isArray(body)).toBe(true) + expect(mockJoin).toHaveBeenCalledWith('/mock/workspace/dist', 'props.json') +}) + +it('uses custom outputDir from config when provided', async () => { + jest.mocked(getConfig).mockResolvedValueOnce({ + outputDir: '/custom/output/path', + content: [], + propsGlobs: [], + }) + + const response = await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + const body = await response.json() + + expect(response.status).toBe(200) + expect(Array.isArray(body)).toBe(true) + expect(mockJoin).toHaveBeenCalledWith('/custom/output/path', 'props.json') +}) + +it('reads props.json from the correct file path', async () => { + await GET({ + params: { version: 'v6', section: 'components' }, + url: new URL('http://localhost:4321/api/v6/components/names'), + } as any) + + expect(mockReadFileSync).toHaveBeenCalledWith('/mock/output/dir/props.json') +}) diff --git a/src/pages/api/[version]/[section]/[page]/props.ts b/src/pages/api/[version]/[section]/[page]/props.ts new file mode 100644 index 0000000..27493e0 --- /dev/null +++ b/src/pages/api/[version]/[section]/[page]/props.ts @@ -0,0 +1,49 @@ +import type { APIRoute } from 'astro' +import { createJsonResponse } from '../../../../../utils/apiHelpers' +import { getConfig } from '../../../../../../cli/getConfig' +import { join } from 'node:path' +import { readFileSync } from 'node:fs' +import { sentenceCase } from '../../../../../utils/case' + +export const prerender = false + +export const GET: APIRoute = async ({ params }) => { + const { page } = params + + if (!page) { + return createJsonResponse( + { error: 'Page parameter is required' }, + 400, + ) + } + + try { + const config = await getConfig(`${process.cwd()}/pf-docs.config.mjs`) + const outputDir = config?.outputDir || join(process.cwd(), 'dist') + + const propsFilePath = join(outputDir, 'props.json') + const propsDataFile = readFileSync(propsFilePath) + const props = JSON.parse(propsDataFile.toString()) + + const propsData = props[sentenceCase(page)] + + if (propsData === undefined) { + return createJsonResponse( + { error: `Props data for ${page} not found` }, + 404, + ) + } + + return createJsonResponse(propsData) + + } catch (error) { + const details = error instanceof Error ? error.message : String(error) + return createJsonResponse( + { error: 'Props data not found', details }, + 500, + ) + } +} + + + diff --git a/src/pages/api/[version]/[section]/names.ts b/src/pages/api/[version]/[section]/names.ts new file mode 100644 index 0000000..af41ded --- /dev/null +++ b/src/pages/api/[version]/[section]/names.ts @@ -0,0 +1,32 @@ +import type { APIRoute } from 'astro' +import { createJsonResponse } from '../../../../utils/apiHelpers' +import { getConfig } from '../../../../../cli/getConfig' +import { join } from 'node:path' +import { readFileSync } from 'node:fs' + +export const prerender = false + +export const GET: APIRoute = async ({ }) => { + try { + const config = await getConfig(`${process.cwd()}/pf-docs.config.mjs`) + const outputDir = config?.outputDir || join(process.cwd(), 'dist') + + const propsFilePath = join(outputDir, 'props.json') + const propsDataFile = readFileSync(propsFilePath) + const props = JSON.parse(propsDataFile.toString()) + + const propsKey = new RegExp("Props", 'i'); // ignore ComponentProps objects + const names = Object.keys(props).filter(name => !propsKey.test(name)) + + return createJsonResponse(names) + } catch (error) { + const details = error instanceof Error ? error.message : String(error) + return createJsonResponse( + { error: 'Component names data not found', details }, + 500, + ) + } +} + + + diff --git a/src/pages/api/index.ts b/src/pages/api/index.ts index cead58f..cdd59c4 100644 --- a/src/pages/api/index.ts +++ b/src/pages/api/index.ts @@ -98,6 +98,36 @@ export const GET: APIRoute = async () => example: ['alert', 'button', 'card'], }, }, + { + path: '/api/{version}/{section}/names', + method: 'GET', + description: 'Get component names that have props data', + parameters: [ + { + name: 'version', + in: 'path', + required: true, + type: 'string', + example: 'v6', + }, + { + name: 'section', + in: 'path', + required: true, + type: 'string', + example: 'components', + } + ], + returns: { + type: 'array', + items: 'string', + description: 'All component names with props data', + example: [ + 'Alert', + 'AlertGroup' + ], + }, + }, { path: '/api/{version}/{section}/{page}', method: 'GET', @@ -132,6 +162,46 @@ export const GET: APIRoute = async () => example: ['react', 'react-demos', 'html'], }, }, + { + path: '/api/{version}/{section}/{page}/props', + method: 'GET', + description: 'Get props for a specific component', + parameters: [ + { + name: 'version', + in: 'path', + required: true, + type: 'string', + example: 'v6', + }, + { + name: 'section', + in: 'path', + required: true, + type: 'string', + example: 'components', + }, + { + name: 'page', + in: 'path', + required: true, + type: 'string', + example: 'alert', + }, + ], + returns: { + type: 'array', + items: 'object', + description: 'Props for a specific component', + example: [ + { + name: 'actionClose', + type: 'React.ReactNode', + description: 'Close button; use the alert action close button component.', + }, + ], + }, + }, { path: '/api/{version}/{section}/{page}/{tab}', method: 'GET', diff --git a/src/pages/api/openapi.json.ts b/src/pages/api/openapi.json.ts index 0262f0d..babd078 100644 --- a/src/pages/api/openapi.json.ts +++ b/src/pages/api/openapi.json.ts @@ -23,7 +23,7 @@ export const GET: APIRoute = async ({ url }) => { const details = error instanceof Error ? error.message : String(error) return createJsonResponse( { error: 'Failed to load API index', details }, - 500 + 500, ) } @@ -103,7 +103,8 @@ export const GET: APIRoute = async ({ url }) => { '/openapi.json': { get: { summary: 'Get OpenAPI specification', - description: 'Returns the complete OpenAPI 3.0 specification for this API', + description: + 'Returns the complete OpenAPI 3.0 specification for this API', operationId: 'getOpenApiSpec', responses: { '200': { @@ -234,6 +235,70 @@ export const GET: APIRoute = async ({ url }) => { }, }, }, + '/{version}/{section}/names': { + get: { + summary: 'Get component names', + description: 'Returns the component names that have props data', + operationId: 'getNames', + parameters: [ + { + name: 'version', + in: 'path', + required: true, + description: 'Documentation version', + schema: { + type: 'string', + enum: versions, + }, + example: 'v6', + }, + { + name: 'section', + in: 'path', + required: true, + description: 'Documentation section', + schema: { + type: 'string', + }, + example: 'components', + }, + ], + responses: { + '200': { + description: 'Component names with props data', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'string' + }, + }, + example: [ + 'Alert', + 'AlertGroup' + ], + }, + }, + }, + '404': { + description: 'Props not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, '/{version}/{section}/{page}': { get: { summary: 'List tabs for a page', @@ -306,6 +371,102 @@ export const GET: APIRoute = async ({ url }) => { }, }, }, + '/{version}/{section}/{page}/props': { + get: { + summary: 'Get component props', + description: 'Returns the props for the specified component', + operationId: 'getProps', + parameters: [ + { + name: 'version', + in: 'path', + required: true, + description: 'Documentation version', + schema: { + type: 'string', + enum: versions, + }, + example: 'v6', + }, + { + name: 'section', + in: 'path', + required: true, + description: 'Documentation section', + schema: { + type: 'string', + }, + example: 'components', + }, + { + name: 'page', + in: 'path', + required: true, + description: 'Page ID (kebab-cased)', + schema: { + type: 'string', + }, + example: 'alert', + }, + ], + responses: { + '200': { + description: 'Props for the specified component', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + type: { type: 'string' }, + description: { type: 'string' }, + defaultValue: { type: 'string' }, + }, + }, + }, + example: [ + { + name: 'actionClose', + type: 'React.ReactNode', + description: + 'Close button; use the alert action close button component.', + }, + { + name: 'actionLinks', + type: 'React.ReactNode', + description: + 'Action links; use a single alert action link component or multiple wrapped in an array\nor React fragment.', + }, + { + name: 'children', + type: 'React.ReactNode', + description: 'Content rendered inside the alert.', + defaultValue: "''", + }, + ], + }, + }, + }, + '404': { + description: 'Props not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, '/{version}/{section}/{page}/{tab}': { get: { summary: 'Validate and redirect to text endpoint', @@ -357,7 +518,8 @@ export const GET: APIRoute = async ({ url }) => { ], responses: { '302': { - description: 'Redirects to /{version}/{section}/{page}/{tab}/text', + description: + 'Redirects to /{version}/{section}/{page}/{tab}/text', }, '404': { description: 'Tab not found', @@ -554,8 +716,7 @@ export const GET: APIRoute = async ({ url }) => { '/{version}/{section}/{page}/{tab}/examples/{example}': { get: { summary: 'Get example code', - description: - 'Returns the raw source code for a specific example', + description: 'Returns the raw source code for a specific example', operationId: 'getExampleCode', parameters: [ { @@ -619,7 +780,7 @@ export const GET: APIRoute = async ({ url }) => { type: 'string', }, example: - 'import React from \'react\';\nimport { Alert } from \'@patternfly/react-core\';\n\nexport const AlertBasic = () => ;', + "import React from 'react';\nimport { Alert } from '@patternfly/react-core';\n\nexport const AlertBasic = () => ;", }, }, },