diff --git a/src/app/[contentPageGroup]/[contentPage]/page.tsx b/src/app/[contentPageGroup]/[contentPage]/page.tsx index b0436410..ce5f7955 100644 --- a/src/app/[contentPageGroup]/[contentPage]/page.tsx +++ b/src/app/[contentPageGroup]/[contentPage]/page.tsx @@ -1,26 +1,15 @@ -'use client'; - import { css } from '@emotion/css'; import startCase from 'lodash/startCase'; -import { useEffect, useState } from 'react'; import { ContentstackRichText } from '@/components/content-stack'; import { getContentPage } from '@/utils/ContentStack/getContentstackResources'; -import { ContentPage as ContentPageType } from '@/utils/ContentStack/types'; -export default function ContentPage({ +export default async function ContentPage({ params: { contentPage: contentPageName }, }: { params: { contentPage: string }; }) { - const [contentPage, setContentPage] = useState(); - - useEffect(() => { - (async function () { - const contentPageObj = await getContentPage(startCase(contentPageName)); - setContentPage(contentPageObj); - })(); - }, [contentPageName]); + const contentPage = await getContentPage(startCase(contentPageName)); return (
; -} diff --git a/src/app/component/[component]/code-docs/client.tsx b/src/app/component/[component]/code-docs/client.tsx new file mode 100644 index 00000000..3a6e9897 --- /dev/null +++ b/src/app/component/[component]/code-docs/client.tsx @@ -0,0 +1,33 @@ +'use client'; +import { + InstallCard, + PropTableState, + PropsTable, + VersionCard, +} from '@/components/code-docs'; +import { codeDocsMetaCardsStyles, codeDocsPageStyles } from './codeDocs.styles'; + +interface CodeDocsContentProps { + componentName: string; + componentProps?: Array; + changelog: string | null; +} + +export const CodeDocsContent = ({ + componentName, + componentProps, + changelog, +}: CodeDocsContentProps) => { + return ( +
+
+ + +
+ + {componentProps?.map(({ name, props }) => { + return ; + })} +
+ ); +}; diff --git a/src/app/component/[component]/code-docs/codeDocs.styles.ts b/src/app/component/[component]/code-docs/codeDocs.styles.ts new file mode 100644 index 00000000..6cdcc3e6 --- /dev/null +++ b/src/app/component/[component]/code-docs/codeDocs.styles.ts @@ -0,0 +1,19 @@ +import { css } from '@emotion/css'; +import { breakpoints, spacing } from '@leafygreen-ui/tokens'; + +export const codeDocsPageStyles = css` + display: flex; + flex-direction: column; + gap: ${spacing[800]}px; +`; + +export const codeDocsMetaCardsStyles = css` + display: grid; + gap: ${spacing[800]}px; + grid-template-columns: 2fr 1fr; + max-width: 100%; + + @media (max-width: ${breakpoints.Tablet}px) { + grid-template-columns: 1fr; + } +`; diff --git a/src/app/component/[component]/code-docs/loading.tsx b/src/app/component/[component]/code-docs/loading.tsx new file mode 100644 index 00000000..581ae779 --- /dev/null +++ b/src/app/component/[component]/code-docs/loading.tsx @@ -0,0 +1,16 @@ +'use client'; +import { CardSkeleton } from '@leafygreen-ui/skeleton-loader'; +import { codeDocsMetaCardsStyles, codeDocsPageStyles } from './codeDocs.styles'; + +export default function Loading() { + return ( +
+
+ + +
+ + +
+ ); +} diff --git a/src/app/component/[component]/code-docs/page.tsx b/src/app/component/[component]/code-docs/page.tsx index e68ecdc9..feee61d9 100644 --- a/src/app/component/[component]/code-docs/page.tsx +++ b/src/app/component/[component]/code-docs/page.tsx @@ -1,107 +1,23 @@ -'use client'; +import { fetchTSDocs, fetchChangelog } from './server'; +import { CodeDocsContent } from './client'; +import { parseComponentPropsFromTSDocs } from './utils'; -import { useEffect, useState } from 'react'; -import { css } from '@emotion/css'; -import { TableSkeleton } from '@leafygreen-ui/skeleton-loader'; -import { spacing } from '@leafygreen-ui/tokens'; -import { InstallCard, PropsTable, VersionCard } from '@/components/code-docs'; -import { useMediaQuery } from '@/hooks/useMediaQuery'; -import { components } from '@/utils'; -import { - TSDocResponse, - PropTableState, - mergeProps, -} from '@/components/code-docs'; +export default async function Page({ + params, +}: { + params: { component: string }; +}) { + const componentName = params.component; -import { getTSDocFromServer, getChangelogFromServer } from './server'; - -export default function Page({ params }: { params: { component: string } }) { - const [isTablet] = useMediaQuery(['(max-width: 768px)'], { - fallback: [true], - }); - const [isLoading, setIsLoading] = useState(true); - const [componentProps, setComponentProps] = useState>( - [], - ); - - useEffect(() => { - if (!params.component) { - return; - } - - const component = params.component; - const subComponents = components.find( - componentMeta => - componentMeta.name.toLowerCase().replace(/\s/g, '') === - component.split('-').join(''), - )?.subComponents; - - getTSDocFromServer(component) - .then((response: Array) => { - if (response != null) { - if (!!subComponents) { - const propTables = response.filter(response => - subComponents.includes(response.displayName), - ); - - const reducedPropTables: Array = propTables.reduce( - (acc: Array, value: TSDocResponse) => { - const mergedProps = mergeProps(value.props); - return [ - ...acc, - { name: value.displayName, props: mergedProps }, - ]; - }, - [], - ); - - setComponentProps(reducedPropTables); - } else { - const centralProps = response.find(response => { - return response.displayName - .toLowerCase() - .replace(/\s/g, '') - .includes(component.toLowerCase().split('-').join('')); - }); - const mergedProps = mergeProps(centralProps?.props); - setComponentProps([{ name: component, props: mergedProps }]); - } - } - }) - .finally(() => setIsLoading(false)); - }, [params.component]); + const tsDocs = await fetchTSDocs(componentName); + const componentProps = parseComponentPropsFromTSDocs(tsDocs, componentName); + const changelog = await fetchChangelog(componentName); return ( -
-
- - - -
- - {isLoading ? ( - - ) : ( - componentProps.map(({ name, props }) => { - return ; - }) - )} -
+ ); } diff --git a/src/app/component/[component]/code-docs/server.ts b/src/app/component/[component]/code-docs/server.ts index c39959ee..c1cea881 100644 --- a/src/app/component/[component]/code-docs/server.ts +++ b/src/app/component/[component]/code-docs/server.ts @@ -1,15 +1,18 @@ -"use server"; +'use server'; -import { marked } from "marked"; +import { TSDocResponse } from '@/components/code-docs'; +import { marked } from 'marked'; -export async function getTSDocs(componentName: string = "button") { - if (typeof componentName !== "string") return null; +export async function fetchTSDocs( + componentName: string, +): Promise | null> { + if (typeof componentName !== 'string') return null; try { return await import(`@leafygreen-ui/${componentName}/tsdoc.json`).then( - (response) => { + response => { return response.default; - } + }, ); } catch (error) { console.warn(error); @@ -17,19 +20,15 @@ export async function getTSDocs(componentName: string = "button") { } } -export async function getTSDocFromServer(component: string) { - return await getTSDocs(component); -} - -export async function getChangelog( - componentName: string +export async function fetchChangelog( + componentName: string, ): Promise { try { const response = await fetch( - `https://cdn.jsdelivr.net/npm/@leafygreen-ui/${componentName}/CHANGELOG.md` + `https://cdn.jsdelivr.net/npm/@leafygreen-ui/${componentName}/CHANGELOG.md`, ); if (!response.ok) { - throw new Error("Failed to fetch Markdown file"); + throw new Error('Failed to fetch Markdown file'); } const markdown = await response.text(); const parsedMarkdown = marked(markdown); @@ -40,7 +39,3 @@ export async function getChangelog( return null; } } - -export async function getChangelogFromServer(component: string) { - return await getChangelog(component); -} diff --git a/src/app/component/[component]/code-docs/utils.ts b/src/app/component/[component]/code-docs/utils.ts new file mode 100644 index 00000000..3ce3806c --- /dev/null +++ b/src/app/component/[component]/code-docs/utils.ts @@ -0,0 +1,45 @@ +import { + PropTableState, + TSDocResponse, + mergeProps, +} from '@/components/code-docs'; +import { findComponent } from '@/utils/components'; +import { kebabCase } from 'lodash'; + +export function parseComponentPropsFromTSDocs( + tsDocs: Array | null, + componentName: string, +): Array | undefined { + if (!tsDocs) return; + + const componentMeta = findComponent(componentName); + const subComponents = componentMeta?.subComponents; + + if (!!subComponents) { + const propTables = tsDocs.filter(tsdoc => + subComponents.includes(tsdoc.displayName), + ); + + const reducedPropTables: Array = propTables.reduce( + (acc: Array, value: TSDocResponse) => { + const mergedProps = mergeProps(value.props); + return [...acc, { name: value.displayName, props: mergedProps }]; + }, + [], + ); + + return reducedPropTables; + } else { + const centralProps = tsDocs.find(tsdoc => { + console.log({ + displayName: tsdoc.displayName, + kebabCaseDisplay: kebabCase(tsdoc.displayName), + componentName, + kebabCaseName: kebabCase(componentName), + }); + return kebabCase(tsdoc.displayName).includes(kebabCase(componentName)); + }); + const mergedProps = mergeProps(centralProps?.props); + return [{ name: componentName, props: mergedProps }]; + } +} diff --git a/src/app/component/[component]/design-docs/client.tsx b/src/app/component/[component]/design-docs/client.tsx new file mode 100644 index 00000000..3ad75223 --- /dev/null +++ b/src/app/component/[component]/design-docs/client.tsx @@ -0,0 +1,13 @@ +'use client'; + +import { ContentstackRichText } from '@/components/content-stack'; +import { CSNode } from '@/components/content-stack/types'; +import { ErrorBoundary } from 'next/dist/client/components/error-boundary'; + +interface DesignDocsContentProps { + content?: CSNode; +} + +export const DesignDocsContent = ({ content }: DesignDocsContentProps) => { + return ; +}; diff --git a/src/app/component/[component]/design-docs/loading.tsx b/src/app/component/[component]/design-docs/loading.tsx new file mode 100644 index 00000000..6f17fb32 --- /dev/null +++ b/src/app/component/[component]/design-docs/loading.tsx @@ -0,0 +1,10 @@ +'use client'; +import { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader'; + +export default function Loading() { + return ( +
+ +
+ ); +} diff --git a/src/app/component/[component]/design-docs/page.tsx b/src/app/component/[component]/design-docs/page.tsx index 1b1de314..0e1353e0 100644 --- a/src/app/component/[component]/design-docs/page.tsx +++ b/src/app/component/[component]/design-docs/page.tsx @@ -1,16 +1,16 @@ -'use client'; - import { css } from '@emotion/css'; -import { ContentstackRichText } from '@/components/content-stack'; -import useComponentFields from '@/hooks/useComponentFields'; +import { fetchComponent } from '@/utils/ContentStack/getContentstackResources'; +import { DesignDocsContent } from './client'; -export default function Page({ +export default async function Page({ params: { component: componentName }, }: { params: { component: string }; }) { - const component = useComponentFields({ componentName, includeContent: true }); + const component = await fetchComponent(componentName, { + includeContent: true, + }); return (
- +
); } diff --git a/src/app/component/[component]/live-example/page.tsx b/src/app/component/[component]/live-example/page.tsx index 8e2736c9..348538cd 100644 --- a/src/app/component/[component]/live-example/page.tsx +++ b/src/app/component/[component]/live-example/page.tsx @@ -1,98 +1,40 @@ -"use client"; +'use client'; -import { useEffect, useState } from "react"; -import { css } from "@emotion/css"; -import Card from "@leafygreen-ui/card"; -import { borderRadius, color, spacing } from "@leafygreen-ui/tokens"; -import { mergeObjects } from "@/utils/mergeObjects"; +import { ReactElement, ReactNode, useEffect, useState } from 'react'; +import { css } from '@emotion/css'; +import Card from '@leafygreen-ui/card'; +import { borderRadius, color, spacing } from '@leafygreen-ui/tokens'; +import { mergeObjects } from '@/utils/mergeObjects'; import { - Data, + StoryData, KnobProps, ComponentProps, -} from "@/components/live-example/types"; -import { getStories } from "./server"; -import { Knobs } from "@/components/live-example/Knobs"; -import { useDarkMode } from "@leafygreen-ui/leafygreen-provider"; - -const OMIT_PROPS = [ - "aria-controls", - "as", - "baseFontSize", - "children", - "className", - "contentClassName", - "defaultOpen", - "href", - "id", - "inputValue", - "loadingIndicator", - "menuItems", - "name", - "onCurrentPageOptionChange", - "onDismiss", - "open", - "primaryButton", - "refButtonPosition", - "shouldTooltipUsePortal", - "stateNotifications", - "timeout", - "usePortal", - "value", - "trigger", -]; - -function constructArgValues(argValues: Record) { - let returnObj: Record = {}; - - for (let key in argValues) { - if (typeof argValues[key] !== "object") { - returnObj[key] = { value: argValues[key] }; - } else { - returnObj[key] = argValues[key]; - } - } - - return returnObj; -} - -function removeProps(object: Record) { - return Object.fromEntries( - Object.entries(object).filter(([key]) => !OMIT_PROPS.includes(key)) - ); -} - -function createDefaultProps(data: Data, darkMode: boolean) { - const combinedProps = mergeObjects( - constructArgValues(data.allData?.default?.args), - data.allData?.default?.argTypes - ); - - const filteredProps = removeProps(combinedProps); - - filteredProps.darkMode = { value: darkMode, control: "boolean" }; - - return filteredProps; -} +} from '@/components/live-example/types'; +import { loadStories } from './server'; +import { Knobs } from '@/components/live-example/Knobs'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { createDefaultProps } from './utils'; +import { CardSkeleton } from '@leafygreen-ui/skeleton-loader'; export default function Page({ params }: { params: { component: string } }) { const { darkMode } = useDarkMode(); - const [data, setData] = useState(); + const [data, setData] = useState(); const [knobProps, setKnobProps] = useState({}); const [componentProps, setComponentProps] = useState({}); + // When the component changes, re-fetch the LiveExample data useEffect(() => { - async function getAsyncStories() { - const response = await getStories(params.component); + loadStories(params.component).then(response => { if (response) { setData(response); } - } - getAsyncStories(); + }); }, [params.component]); + // When the story data changes, update the knobs useEffect(() => { if (data) { - const normalizedProps = createDefaultProps(data, darkMode); + const normalizedProps = createDefaultProps(data.meta, darkMode); setKnobProps(normalizedProps); const propsWithValue: ComponentProps = {}; @@ -106,12 +48,13 @@ export default function Page({ params }: { params: { component: string } }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); + // If the Context DarkMode changes, update the knobs useEffect(() => { - updateKnobValue("darkMode", darkMode); + updateKnobValue('darkMode', darkMode); }, [darkMode]); const updateKnobValue = (propName: string, newValue: any) => { - setKnobProps((props) => { + setKnobProps(props => { return { ...props, [propName]: { @@ -121,7 +64,7 @@ export default function Page({ params }: { params: { component: string } }) { }; }); - setComponentProps((props) => { + setComponentProps(props => { return { ...props, [propName]: newValue, @@ -129,46 +72,48 @@ export default function Page({ params }: { params: { component: string } }) { }); }; - if (data?.LiveExample) { - const Component = data.LiveExample; - return ( - +
-
- {/* @ts-expect-error */} - -
-
-
- + {/* @ts-expect-error */} +
- - ); - } - - return null; +
+
+ +
+
+ ) : ( + + ); } diff --git a/src/app/component/[component]/live-example/server.ts b/src/app/component/[component]/live-example/server.ts index 26490ccd..e0515c30 100644 --- a/src/app/component/[component]/live-example/server.ts +++ b/src/app/component/[component]/live-example/server.ts @@ -1,11 +1,14 @@ -import { Data } from "@/components/live-example/types"; -import { composeStories } from "@storybook/react"; +import { StoryData } from '@/components/live-example/types'; +import { composeStories } from '@storybook/react'; -export async function getStories(componentName: string) { - return await import(`@leafygreen-ui/${componentName}/stories`) - .then((response) => { - const { LiveExample } = composeStories(response); - return { LiveExample, allData: response } as Data; - }) - .catch((error) => console.warn(error)); +export async function loadStories(componentName: string) { + try { + const stories = await import(`@leafygreen-ui/${componentName}/stories`); + const { LiveExample, default: meta } = composeStories(stories); + return { LiveExample, meta } as StoryData; + } catch (error) { + console.log('ERROR LOADING STORIES'); + console.error(error); + return; + } } diff --git a/src/app/component/[component]/live-example/utils.ts b/src/app/component/[component]/live-example/utils.ts new file mode 100644 index 00000000..1c58eb41 --- /dev/null +++ b/src/app/component/[component]/live-example/utils.ts @@ -0,0 +1,62 @@ +import { StoryData } from '@/components/live-example'; +import { mergeObjects } from '@/utils'; +import { Meta } from '@storybook/react'; + +export const OMIT_PROPS = [ + 'aria-controls', + 'as', + 'baseFontSize', + 'children', + 'className', + 'contentClassName', + 'defaultOpen', + 'href', + 'id', + 'inputValue', + 'loadingIndicator', + 'menuItems', + 'name', + 'onCurrentPageOptionChange', + 'onDismiss', + 'open', + 'primaryButton', + 'refButtonPosition', + 'shouldTooltipUsePortal', + 'stateNotifications', + 'timeout', + 'usePortal', + 'value', + 'trigger', +]; + +export function constructArgValues(argValues: Record) { + let returnObj: Record = {}; + + for (let key in argValues) { + if (typeof argValues[key] !== 'object') { + returnObj[key] = { value: argValues[key] }; + } else { + returnObj[key] = argValues[key]; + } + } + + return returnObj; +} + +export function removeProps(object: Record) { + return Object.fromEntries( + Object.entries(object).filter(([key]) => !OMIT_PROPS.includes(key)), + ); +} + +export function createDefaultProps(meta: Meta, darkMode: boolean) { + const combinedProps = + meta && meta.args && meta.argTypes + ? mergeObjects(constructArgValues(meta.args), meta.argTypes) + : {}; + const filteredProps = removeProps(combinedProps); + + filteredProps.darkMode = { value: darkMode, control: 'boolean' }; + + return filteredProps; +} diff --git a/src/components/code-docs/InstallCard.tsx b/src/components/code-docs/InstallCard.tsx index 583965ef..cdd0b84a 100644 --- a/src/components/code-docs/InstallCard.tsx +++ b/src/components/code-docs/InstallCard.tsx @@ -1,4 +1,4 @@ -'use-client'; +'use client'; import React, { useState } from 'react'; import { css } from '@emotion/css'; diff --git a/src/components/code-docs/VersionCard.tsx b/src/components/code-docs/VersionCard.tsx index 19425354..fc8b85a3 100644 --- a/src/components/code-docs/VersionCard.tsx +++ b/src/components/code-docs/VersionCard.tsx @@ -12,34 +12,19 @@ import { spacing } from '@leafygreen-ui/tokens'; import { Subtitle } from '@leafygreen-ui/typography'; import { color } from '@leafygreen-ui/tokens'; -export const VersionCard = ({ - component, - getChangelog, -}: { +interface VersionCardProps { component: string; - getChangelog: (arg0: string) => Promise; -}) => { - const [isLoading, setIsLoading] = useState(true); + changelog: string | null; +} + +export const VersionCard = ({ component, changelog }: VersionCardProps) => { const [isModalOpen, setIsModalOpen] = useState(false); - const [changelog, setChangelog] = useState(null); const [version, setVersion] = useState(null); - useEffect(() => { - getChangelog(component) - .then(response => { - setChangelog(response); - }) - .finally(() => setIsLoading(false)); - }, [component, getChangelog]); - useEffect(() => { setVersion(changelog?.split('h2')[1]?.replace(/[>/<]+/g, '') ?? null); }, [changelog]); - if (isLoading) { - return ; - } - return ( { @@ -16,7 +17,26 @@ interface CSRichTextProps /** * Renders a ContentStack Node */ -export const ContentstackRichText = ({ +export const ContentStackRichText = ({ + content, + ...rest +}: CSRichTextProps): JSX.Element => { + return ( + { + console.error( + 'The above error occurred mapping the following content to an element', + content, + ); + return <>; + }} + > + + + ); +}; + +const ContentStackRichTextElement = ({ content, ...rest }: CSRichTextProps): JSX.Element => { @@ -28,7 +48,7 @@ export const ContentstackRichText = ({ const textContent = getCSNodeTextContent(content); if (textContent || nodeHasAssets(content)) { - // @ts-expect-error + /* @ts-expect-error */ return nodeTypeToElementMap[content.type]?.(content, rest); } else { return <>; diff --git a/src/components/content-stack/HorizontalLayout.tsx b/src/components/content-stack/HorizontalLayout.tsx index 2f3fdf20..e7ed1753 100644 --- a/src/components/content-stack/HorizontalLayout.tsx +++ b/src/components/content-stack/HorizontalLayout.tsx @@ -1,8 +1,8 @@ -import { css, cx } from "@emotion/css"; -import { Polymorph } from "@leafygreen-ui/polymorphic"; -import { spacing } from "@leafygreen-ui/tokens"; -import { ContentstackRichText } from "./ContentstackRichText"; -import { HorizontalLayoutBlockProps } from "./types"; +import { css, cx } from '@emotion/css'; +import { Polymorph } from '@leafygreen-ui/polymorphic'; +import { spacing } from '@leafygreen-ui/tokens'; +import { ContentStackRichText } from './ContentstackRichText'; +import { HorizontalLayoutBlockProps } from './types'; /// Note: can't use `css` from `@emotion/react` with `cx` const flexColumnStyles = css` @@ -16,7 +16,7 @@ const flexColumnStyles = css` export const HorizontalLayout = ({ column_1, column_2, - vertical_align = "start", + vertical_align = 'start', flex_ratio, }: HorizontalLayoutBlockProps) => { const [flex1, flex2] = flex_ratio?.match(/[0-9]+/g) ?? [1, 1]; @@ -29,7 +29,7 @@ export const HorizontalLayout = ({ `} >
diff --git a/src/components/content-stack/index.ts b/src/components/content-stack/index.ts index 4c4f6881..54734c2e 100644 --- a/src/components/content-stack/index.ts +++ b/src/components/content-stack/index.ts @@ -1 +1 @@ -export { ContentstackRichText } from "./ContentstackRichText"; +export { ContentStackRichText as ContentstackRichText } from './ContentstackRichText'; diff --git a/src/components/live-example/types.ts b/src/components/live-example/types.ts index 3219ce95..7b698ae8 100644 --- a/src/components/live-example/types.ts +++ b/src/components/live-example/types.ts @@ -1,10 +1,9 @@ -import React from "react"; +import React from 'react'; +import { Meta } from '@storybook/react'; -export interface Data { +export interface StoryData { LiveExample: React.ReactElement; - allData: { - default: { args: Array; argTypes: Array }; - }; + meta: Meta; } export type KnobProps = { @@ -14,14 +13,14 @@ export type KnobProps = { export type ComponentProps = { [key: string]: any }; export type Knobs = - | "string" - | "text" - | "number" - | "range" - | "date" - | "boolean" - | "array" - | "enum" - | "select" - | "radio" - | "none"; + | 'string' + | 'text' + | 'number' + | 'range' + | 'date' + | 'boolean' + | 'array' + | 'enum' + | 'select' + | 'radio' + | 'none'; diff --git a/src/hooks/useComponentFields.ts b/src/hooks/useComponentFields.ts index 088f1bd4..e68a8435 100644 --- a/src/hooks/useComponentFields.ts +++ b/src/hooks/useComponentFields.ts @@ -1,25 +1,28 @@ -"use client"; +'use client'; -import { useState, useEffect } from "react"; +import { useState, useEffect } from 'react'; -import { getComponent } from "@/utils/ContentStack/getContentstackResources"; -import { ComponentFields } from "@/utils/ContentStack/types"; +import { fetchComponent } from '@/utils/ContentStack/getContentstackResources'; +import { ComponentFields } from '@/utils/ContentStack/types'; +/** @deprecated */ export default function useComponentFields({ componentName, includeContent = false, }: { - componentName: string, - includeContent?: boolean, + componentName: string; + includeContent?: boolean; }) { const [component, setComponent] = useState(); useEffect(() => { (async function () { - const componentObj = await getComponent(componentName, { includeContent }); + const componentObj = await fetchComponent(componentName, { + includeContent, + }); setComponent(componentObj); })(); }, [componentName, includeContent]); return component; -} \ No newline at end of file +} diff --git a/src/utils/ContentStack/getContentstackResources.ts b/src/utils/ContentStack/getContentstackResources.ts index 74016d6b..785b1de5 100644 --- a/src/utils/ContentStack/getContentstackResources.ts +++ b/src/utils/ContentStack/getContentstackResources.ts @@ -1,19 +1,19 @@ -import Contentstack from "contentstack"; -import defaults from "lodash/defaults"; -import startCase from "lodash/startCase"; +import Contentstack from 'contentstack'; +import defaults from 'lodash/defaults'; +import startCase from 'lodash/startCase'; import { BlockPropsMap, ContentTypeUID, -} from "@/components/content-stack/types"; +} from '@/components/content-stack/types'; -import { ComponentFields, ContentPage, ContentPageGroup } from "./types"; +import { ComponentFields, ContentPage, ContentPageGroup } from './types'; const ENV_MAP = { - main: "main", - production: "main", - staging: "staging", - dev: "staging", + main: 'main', + production: 'main', + staging: 'staging', + dev: 'staging', } as const; const environment = ((): string => { @@ -35,27 +35,27 @@ interface QueryOptions { } const componentProperties = [ - "uid", - "title", - "description", - "url", - "figmaurl", - "private", - "codesandbox_url", + 'uid', + 'title', + 'description', + 'url', + 'figmaurl', + 'private', + 'codesandbox_url', ]; -const optionalComponentProperties = ["designguidelines"]; +const optionalComponentProperties = ['designguidelines']; /** * @returns All component objects, optionally with all associated content (i.e. guidelines) */ export async function getComponents( - options?: QueryOptions + options?: QueryOptions, ): Promise> { try { options = defaults(options, { includeContent: false }); const results: Array = ( - await Stack.ContentType("component") + await Stack.ContentType('component') .Query() .only([ ...componentProperties, @@ -66,7 +66,7 @@ export async function getComponents( )[0]; return results.sort((a, b) => a.title.localeCompare(b.title)); } catch (error) { - console.error("No Component pages found", error); + console.error('No Component pages found', error); // Return no component pages return []; } @@ -75,14 +75,14 @@ export async function getComponents( /** * @returns the component meta & optionally content for a given componentName */ -export async function getComponent( +export async function fetchComponent( componentName: string, - options?: QueryOptions + options?: QueryOptions, ): Promise { try { - const query = Stack.ContentType("component").Query(); + const query = Stack.ContentType('component').Query(); const result = await query - .where("title", startCase(componentName)) + .where('title', startCase(componentName)) .only([ ...componentProperties, ...(options?.includeContent ? optionalComponentProperties : []), @@ -91,7 +91,7 @@ export async function getComponent( .find(); return result[0][0]; } catch (error) { - console.error("Component page not found", error); + console.error('Component page not found', error); } } @@ -100,11 +100,11 @@ export async function getComponent( */ export async function getContentPageGroups(): Promise> { try { - const query = Stack.ContentType("content_page_group").Query(); + const query = Stack.ContentType('content_page_group').Query(); const pageGroups: Array = ( await query - .includeReference("content_pages") - .only(["content_pages", "uid", "title", "url", "iconname"]) + .includeReference('content_pages') + .only(['content_pages', 'uid', 'title', 'url', 'iconname']) .toJSON() .find() )[0].map(({ content_pages, ...meta }: ContentPageGroup) => { @@ -120,38 +120,38 @@ export async function getContentPageGroups(): Promise> { return pageGroups; } catch (error) { - console.error("No Content Page Groups found", error); + console.error('No Content Page Groups found', error); // Return no component pages return []; } } export async function getContentPage( - contentPageName: string + contentPageName: string, ): Promise { try { - const query = Stack.ContentType("content_page").Query(); + const query = Stack.ContentType('content_page').Query(); const result = await query - .where("title", startCase(contentPageName)) + .where('title', startCase(contentPageName)) .includeEmbeddedItems() .toJSON() .find(); return result[0][0]; } catch (error) { - console.error("Content page not found", error); + console.error('Content page not found', error); } } export async function getEntryById( content_type_uid: T, - uid: string + uid: string, ): Promise { try { const query = Stack.ContentType(content_type_uid).Entry(uid); const result = await query.includeEmbeddedItems().toJSON().fetch(); return result as BlockPropsMap[T]; } catch (error) { - console.error("Entry not found", error); + console.error('Entry not found', error); return {} as BlockPropsMap[T]; } } diff --git a/src/utils/components.ts b/src/utils/components.ts index 02443c15..3c03f69b 100644 --- a/src/utils/components.ts +++ b/src/utils/components.ts @@ -1,12 +1,12 @@ -import { startCase, toLower } from "lodash"; +import { kebabCase, startCase, toLower } from 'lodash'; const Group = { - Display: "display", - FormElements: "form-elements", - Dialogs: "dialogs", - Navigation: "navigation", - Notifications: "notifications", - Patterns: "patterns", + Display: 'display', + FormElements: 'form-elements', + Dialogs: 'dialogs', + Navigation: 'navigation', + Notifications: 'notifications', + Patterns: 'patterns', } as const; type Group = (typeof Group)[keyof typeof Group]; @@ -14,52 +14,52 @@ type Group = (typeof Group)[keyof typeof Group]; export { Group }; const Component = { - Badge: "badge", - Banner: "banner", - Button: "button", - Card: "card", - Callout: "callout", - Checkbox: "checkbox", - Chip: "chip", - Code: "code", - Combobox: "combobox", - ConfirmationModal: "confirmation-modal", - Copyable: "copyable", - DatePicker: "date-picker", - EmptyState: "empty-state", - ExpandableCard: "expandable-card", - FormFooter: "form-footer", - GuideCue: "guide-cue", - IconButton: "icon-button", - InfoSprinkle: "info-sprinkle", - InlineDefinition: "inline-definition", - LoadingIndicator: "loading-indicator", - Logo: "logo", - MarketingModal: "marketing-modal", - Menu: "menu", - Modal: "modal", - MongoNav: "mongo-nav", - NumberInput: "number-input", - Pagination: "pagination", - PasswordInput: "password-input", - Pipeline: "pipeline", - Popover: "popover", - RadioBoxGroup: "radio-box-group", - RadioGroup: "radio-group", - SearchInput: "search-input", - SegmentedControl: "segmented-control", - Select: "select", - SideNav: "side-nav", - SkeletonLoader: "skeleton-loader", - SplitButton: "split-button", - Stepper: "stepper", - Table: "table", - Tabs: "tabs", - TextArea: "text-area", - TextInput: "text-input", - Toast: "toast", - Toggle: "toggle", - Tooltip: "tooltip", + Badge: 'badge', + Banner: 'banner', + Button: 'button', + Card: 'card', + Callout: 'callout', + Checkbox: 'checkbox', + Chip: 'chip', + Code: 'code', + Combobox: 'combobox', + ConfirmationModal: 'confirmation-modal', + Copyable: 'copyable', + DatePicker: 'date-picker', + EmptyState: 'empty-state', + ExpandableCard: 'expandable-card', + FormFooter: 'form-footer', + GuideCue: 'guide-cue', + IconButton: 'icon-button', + InfoSprinkle: 'info-sprinkle', + InlineDefinition: 'inline-definition', + LoadingIndicator: 'loading-indicator', + Logo: 'logo', + MarketingModal: 'marketing-modal', + Menu: 'menu', + Modal: 'modal', + MongoNav: 'mongo-nav', + NumberInput: 'number-input', + Pagination: 'pagination', + PasswordInput: 'password-input', + Pipeline: 'pipeline', + Popover: 'popover', + RadioBoxGroup: 'radio-box-group', + RadioGroup: 'radio-group', + SearchInput: 'search-input', + SegmentedControl: 'segmented-control', + Select: 'select', + SideNav: 'side-nav', + SkeletonLoader: 'skeleton-loader', + SplitButton: 'split-button', + Stepper: 'stepper', + Table: 'table', + Tabs: 'tabs', + TextArea: 'text-area', + TextInput: 'text-input', + Toast: 'toast', + Toggle: 'toggle', + Tooltip: 'tooltip', } as const; type Component = (typeof Component)[keyof typeof Component]; @@ -71,13 +71,15 @@ const titlecase = (component: Component) => { const generateComponentNavPath = (component: Component) => `/component/${component}/live-example`; -export const components: Array<{ +export interface ComponentMeta { name: string; navPath: string; group: Group; subComponents?: Array; isPrivate?: boolean; -}> = [ +} + +export const components: Array = [ { name: titlecase(Component.Badge), navPath: generateComponentNavPath(Component.Badge), @@ -122,7 +124,7 @@ export const components: Array<{ name: titlecase(Component.Combobox), navPath: generateComponentNavPath(Component.Combobox), group: Group.FormElements, - subComponents: ["Combobox", "ComboboxOption", "ComboboxGroup"], + subComponents: ['Combobox', 'ComboboxOption', 'ComboboxGroup'], }, { name: titlecase(Component.ConfirmationModal), @@ -178,7 +180,7 @@ export const components: Array<{ name: titlecase(Component.LoadingIndicator), navPath: generateComponentNavPath(Component.LoadingIndicator), group: Group.Display, - subComponents: ["Spinner", "PageLoader"], + subComponents: ['Spinner', 'PageLoader'], }, { name: titlecase(Component.Logo), @@ -194,7 +196,7 @@ export const components: Array<{ name: titlecase(Component.Menu), navPath: generateComponentNavPath(Component.Menu), group: Group.Navigation, - subComponents: ["Menu", "MenuItem", "SubMenu"], + subComponents: ['Menu', 'MenuItem', 'SubMenu'], }, { name: titlecase(Component.Modal), @@ -220,7 +222,7 @@ export const components: Array<{ name: titlecase(Component.Pipeline), navPath: generateComponentNavPath(Component.Pipeline), group: Group.Display, - subComponents: ["Pipeline", "Stage"], + subComponents: ['Pipeline', 'Stage'], }, { name: titlecase(Component.Popover), @@ -231,13 +233,13 @@ export const components: Array<{ name: titlecase(Component.RadioBoxGroup), navPath: generateComponentNavPath(Component.RadioBoxGroup), group: Group.FormElements, - subComponents: ["RadioBoxGroup", "RadioBox"], + subComponents: ['RadioBoxGroup', 'RadioBox'], }, { name: titlecase(Component.RadioGroup), navPath: generateComponentNavPath(Component.RadioGroup), group: Group.FormElements, - subComponents: ["RadioGroup", "Radio"], + subComponents: ['RadioGroup', 'Radio'], }, { name: titlecase(Component.SearchInput), @@ -248,29 +250,29 @@ export const components: Array<{ name: titlecase(Component.SegmentedControl), navPath: generateComponentNavPath(Component.SegmentedControl), group: Group.Display, - subComponents: ["SegmentedControl", "SegmentedControlOption"], + subComponents: ['SegmentedControl', 'SegmentedControlOption'], }, { name: titlecase(Component.Select), navPath: generateComponentNavPath(Component.Select), group: Group.FormElements, - subComponents: ["Select", "Option", "OptionGroup"], + subComponents: ['Select', 'Option', 'OptionGroup'], }, { name: titlecase(Component.SideNav), navPath: generateComponentNavPath(Component.SideNav), group: Group.Navigation, - subComponents: ["SideNav", "SideNavItem", "SideNavGroup"], + subComponents: ['SideNav', 'SideNavItem', 'SideNavGroup'], }, { name: titlecase(Component.SkeletonLoader), navPath: generateComponentNavPath(Component.SkeletonLoader), group: Group.Display, subComponents: [ - "ParagraphSkeleton", - "FormSkeleton", - "TableSkeleton", - "CardSkeleton", + 'ParagraphSkeleton', + 'FormSkeleton', + 'TableSkeleton', + 'CardSkeleton', ], }, { @@ -282,26 +284,26 @@ export const components: Array<{ name: titlecase(Component.Stepper), navPath: generateComponentNavPath(Component.Stepper), group: Group.Navigation, - subComponents: ["Stepper", "Step"], + subComponents: ['Stepper', 'Step'], }, { name: titlecase(Component.Table), navPath: generateComponentNavPath(Component.Table), group: Group.Display, subComponents: [ - "Table", - "TableHead", - "HeaderRow", - "TableBody", - "Row", - "Cell", + 'Table', + 'TableHead', + 'HeaderRow', + 'TableBody', + 'Row', + 'Cell', ], }, { name: titlecase(Component.Tabs), navPath: generateComponentNavPath(Component.Tabs), group: Group.Navigation, - subComponents: ["Tabs", "Tab"], + subComponents: ['Tabs', 'Tab'], }, { name: titlecase(Component.TextArea), @@ -330,25 +332,26 @@ export const components: Array<{ }, { name: titlecase(Component.MongoNav), - navPath: "/private", + navPath: '/private', group: Group.Navigation, isPrivate: true, }, ]; -export type ComponentMeta = { - name: string; - navPath: string; - isPrivate?: boolean; -}; - export const groupedComponents = components.reduce((acc, obj) => { const { group, name, navPath, isPrivate } = obj; if (!acc[group]) { acc[group] = []; } - acc[group].push({ name, navPath, isPrivate }); + acc[group].push({ name, navPath, isPrivate, group }); // Sort the array by component value acc[group].sort((a, b) => a.name.localeCompare(b.name)); return acc; -}, {} as Record); +}, {} as Record>); + +/** + * Returns the component object matching a given name + */ +export const findComponent = (componentName: string) => { + return components.find(c => kebabCase(c.name) === kebabCase(componentName)); +};